001    /*
002     * @(#)AbstractMap.java 1.32 01/12/03
003     *
004     * Copyright 2002 Sun Microsystems, Inc. All rights reserved.
005     * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
006     */
007    
008    package org.objectweb.jac.util;
009    
010    import java.util.AbstractSet;
011    import java.util.Collection;
012    import java.util.Iterator;
013    import java.util.Map.Entry;
014    import java.util.Map;
015    import java.util.Set;
016    import java.util.AbstractCollection;
017    
018    /**
019     * This class provides a skeletal implementation of the <tt>Map</tt>
020     * interface, to minimize the effort required to implement this interface. <p>
021     *
022     * To implement an unmodifiable map, the programmer needs only to extend this
023     * class and provide an implementation for the <tt>entrySet</tt> method, which
024     * returns a set-view of the map's mappings.  Typically, the returned set
025     * will, in turn, be implemented atop <tt>AbstractSet</tt>.  This set should
026     * not support the <tt>add</tt> or <tt>remove</tt> methods, and its iterator
027     * should not support the <tt>remove</tt> method.<p>
028     *
029     * To implement a modifiable map, the programmer must additionally override
030     * this class's <tt>put</tt> method (which otherwise throws an
031     * <tt>UnsupportedOperationException</tt>), and the iterator returned by
032     * <tt>entrySet().iterator()</tt> must additionally implement its
033     * <tt>remove</tt> method.<p>
034     *
035     * The programmer should generally provide a void (no argument) and map
036     * constructor, as per the recommendation in the <tt>Map</tt> interface
037     * specification.<p>
038     *
039     * The documentation for each non-abstract methods in this class describes its
040     * implementation in detail.  Each of these methods may be overridden if the
041     * map being implemented admits a more efficient implementation.
042     *
043     * @author  Josh Bloch
044     * @version 1.32, 12/03/01
045     * @see Map
046     * @see Collection
047     * @since 1.2
048     */
049    
050    public abstract class AbstractMap implements Map {
051        /**
052         * Sole constructor.  (For invocation by subclass constructors, typically
053         * implicit.)
054         */
055        protected AbstractMap() {
056        }
057    
058        // Query Operations
059    
060        /**
061         * Returns the number of key-value mappings in this map.  If the map
062         * contains more than <tt>Integer.MAX_VALUE</tt> elements, returns
063         * <tt>Integer.MAX_VALUE</tt>.<p>
064         *
065         * This implementation returns <tt>entrySet().size()</tt>.
066         *
067         * @return the number of key-value mappings in this map.
068         */
069        public int size() {
070            return entrySet().size();
071        }
072    
073        /**
074         * Returns <tt>true</tt> if this map contains no key-value mappings. <p>
075         *
076         * This implementation returns <tt>size() == 0</tt>.
077         *
078         * @return <tt>true</tt> if this map contains no key-value mappings.
079         */
080        public boolean isEmpty() {
081            return size() == 0;
082        }
083    
084        /**
085         * Returns <tt>true</tt> if this map maps one or more keys to this value.
086         * More formally, returns <tt>true</tt> if and only if this map contains
087         * at least one mapping to a value <tt>v</tt> such that <tt>(value==null ?
088         * v==null : value.equals(v))</tt>.  This operation will probably require
089         * time linear in the map size for most implementations of map.<p>
090         *
091         * This implementation iterates over entrySet() searching for an entry
092         * with the specified value.  If such an entry is found, <tt>true</tt> is
093         * returned.  If the iteration terminates without finding such an entry,
094         * <tt>false</tt> is returned.  Note that this implementation requires
095         * linear time in the size of the map.
096         *
097         * @param value value whose presence in this map is to be tested.
098         * 
099         * @return <tt>true</tt> if this map maps one or more keys to this value.
100         */
101        public boolean containsValue(Object value) {
102            Iterator i = entrySet().iterator();
103            if (value==null) {
104                while (i.hasNext()) {
105                    Entry e = (Entry) i.next();
106                    if (e.getValue()==null)
107                        return true;
108                }
109            } else {
110                while (i.hasNext()) {
111                    Entry e = (Entry) i.next();
112                    if (value.equals(e.getValue()))
113                        return true;
114                }
115            }
116            return false;
117        }
118    
119        /**
120         * Returns <tt>true</tt> if this map contains a mapping for the specified
121         * key. <p>
122         *
123         * This implementation iterates over <tt>entrySet()</tt> searching for an
124         * entry with the specified key.  If such an entry is found, <tt>true</tt>
125         * is returned.  If the iteration terminates without finding such an
126         * entry, <tt>false</tt> is returned.  Note that this implementation
127         * requires linear time in the size of the map; many implementations will
128         * override this method.
129         *
130         * @param key key whose presence in this map is to be tested.
131         * @return <tt>true</tt> if this map contains a mapping for the specified
132         *            key.
133         * 
134         * @throws NullPointerException key is <tt>null</tt> and this map does not
135         *            not permit <tt>null</tt> keys.
136         */
137        public boolean containsKey(Object key) {
138            Iterator i = entrySet().iterator();
139            if (key==null) {
140                while (i.hasNext()) {
141                    Entry e = (Entry) i.next();
142                    if (e.getKey()==null)
143                        return true;
144                }
145            } else {
146                while (i.hasNext()) {
147                    Entry e = (Entry) i.next();
148                    if (key.equals(e.getKey()))
149                        return true;
150                }
151            }
152            return false;
153        }
154    
155        /**
156         * Returns the value to which this map maps the specified key.  Returns
157         * <tt>null</tt> if the map contains no mapping for this key.  A return
158         * value of <tt>null</tt> does not <i>necessarily</i> indicate that the
159         * map contains no mapping for the key; it's also possible that the map
160         * explicitly maps the key to <tt>null</tt>.  The containsKey operation
161         * may be used to distinguish these two cases. <p>
162         *
163         * This implementation iterates over <tt>entrySet()</tt> searching for an
164         * entry with the specified key.  If such an entry is found, the entry's
165         * value is returned.  If the iteration terminates without finding such an
166         * entry, <tt>null</tt> is returned.  Note that this implementation
167         * requires linear time in the size of the map; many implementations will
168         * override this method.
169         *
170         * @param key key whose associated value is to be returned.
171         * @return the value to which this map maps the specified key.
172         * 
173         * @throws NullPointerException if the key is <tt>null</tt> and this map
174         *            does not not permit <tt>null</tt> keys.
175         * 
176         * @see #containsKey(Object)
177         */
178        public Object get(Object key) {
179            Iterator i = entrySet().iterator();
180            if (key==null) {
181                while (i.hasNext()) {
182                    Entry e = (Entry) i.next();
183                    if (e.getKey()==null)
184                        return e.getValue();
185                }
186            } else {
187                while (i.hasNext()) {
188                    Entry e = (Entry) i.next();
189                    if (key.equals(e.getKey()))
190                        return e.getValue();
191                }
192            }
193            return null;
194        }
195    
196    
197        // Modification Operations
198    
199        /**
200         * Associates the specified value with the specified key in this map
201         * (optional operation).  If the map previously contained a mapping for
202         * this key, the old value is replaced.<p>
203         *
204         * This implementation always throws an
205         * <tt>UnsupportedOperationException</tt>.
206         *
207         * @param key key with which the specified value is to be associated.
208         * @param value value to be associated with the specified key.
209         * 
210         * @return previous value associated with specified key, or <tt>null</tt>
211         *         if there was no mapping for key.  (A <tt>null</tt> return can
212         *         also indicate that the map previously associated <tt>null</tt>
213         *         with the specified key, if the implementation supports
214         *         <tt>null</tt> values.)
215         * 
216         * @throws UnsupportedOperationException if the <tt>put</tt> operation is
217         *            not supported by this map.
218         * 
219         * @throws ClassCastException if the class of the specified key or value
220         *            prevents it from being stored in this map.
221         * 
222         * @throws IllegalArgumentException if some aspect of this key or value *
223         *            prevents it from being stored in this map.
224         * 
225         * @throws NullPointerException this map does not permit <tt>null</tt>
226         *            keys or values, and the specified key or value is
227         *            <tt>null</tt>.
228         */
229        public Object put(Object key, Object value) {
230            throw new UnsupportedOperationException();
231        }
232    
233        /**
234         * Removes the mapping for this key from this map if present (optional
235         * operation). <p>
236         *
237         * This implementation iterates over <tt>entrySet()</tt> searching for an
238         * entry with the specified key.  If such an entry is found, its value is
239         * obtained with its <tt>getValue</tt> operation, the entry is is removed
240         * from the Collection (and the backing map) with the iterator's
241         * <tt>remove</tt> operation, and the saved value is returned.  If the
242         * iteration terminates without finding such an entry, <tt>null</tt> is
243         * returned.  Note that this implementation requires linear time in the
244         * size of the map; many implementations will override this method.<p>
245         *
246         * Note that this implementation throws an
247         * <tt>UnsupportedOperationException</tt> if the <tt>entrySet</tt> iterator
248         * does not support the <tt>remove</tt> method and this map contains a
249         * mapping for the specified key.
250         *
251         * @param key key whose mapping is to be removed from the map.
252         * @return previous value associated with specified key, or <tt>null</tt>
253         *         if there was no entry for key.  (A <tt>null</tt> return can
254         *         also indicate that the map previously associated <tt>null</tt>
255         *         with the specified key, if the implementation supports
256         *         <tt>null</tt> values.)
257         * @throws UnsupportedOperationException if the <tt>remove</tt> operation
258         *            is not supported by this map.
259         */
260        public Object remove(Object key) {
261            Iterator i = entrySet().iterator();
262            Entry correctEntry = null;
263            if (key==null) {
264                while (correctEntry==null && i.hasNext()) {
265                    Entry e = (Entry) i.next();
266                    if (e.getKey()==null)
267                        correctEntry = e;
268                }
269            } else {
270                while (correctEntry==null && i.hasNext()) {
271                    Entry e = (Entry) i.next();
272                    if (key.equals(e.getKey()))
273                        correctEntry = e;
274                }
275            }
276    
277            Object oldValue = null;
278            if (correctEntry !=null) {
279                oldValue = correctEntry.getValue();
280                i.remove();
281            }
282            return oldValue;
283        }
284    
285    
286        // Bulk Operations
287    
288        /**
289         * Copies all of the mappings from the specified map to this map
290         * (optional operation).  These mappings will replace any mappings that
291         * this map had for any of the keys currently in the specified map.<p>
292         *
293         * This implementation iterates over the specified map's
294         * <tt>entrySet()</tt> collection, and calls this map's <tt>put</tt>
295         * operation once for each entry returned by the iteration.<p>
296         *
297         * Note that this implementation throws an
298         * <tt>UnsupportedOperationException</tt> if this map does not support
299         * the <tt>put</tt> operation and the specified map is nonempty.
300         *
301         * @param t mappings to be stored in this map.
302         * 
303         * @throws UnsupportedOperationException if the <tt>putAll</tt> operation
304         *            is not supported by this map.
305         * 
306         * @throws ClassCastException if the class of a key or value in the
307         *            specified map prevents it from being stored in this map.
308         * 
309         * @throws IllegalArgumentException if some aspect of a key or value in
310         *            the specified map prevents it from being stored in this map.
311         * @throws NullPointerException the specified map is <tt>null</tt>, or if
312         *         this map does not permit <tt>null</tt> keys or values, and the
313         *         specified map contains <tt>null</tt> keys or values.
314         */
315        public void putAll(Map t) {
316            Iterator i = t.entrySet().iterator();
317            while (i.hasNext()) {
318                Entry e = (Entry) i.next();
319                put(e.getKey(), e.getValue());
320            }
321        }
322    
323        /**
324         * Removes all mappings from this map (optional operation). <p>
325         *
326         * This implementation calls <tt>entrySet().clear()</tt>.
327         *
328         * Note that this implementation throws an
329         * <tt>UnsupportedOperationException</tt> if the <tt>entrySet</tt>
330         * does not support the <tt>clear</tt> operation.
331         *
332         * @throws    UnsupportedOperationException clear is not supported
333         *            by this map.
334         */
335        public void clear() {
336            entrySet().clear();
337        }
338    
339    
340        // Views
341    
342        /**
343         * Each of these fields are initialized to contain an instance of the
344         * appropriate view the first time this view is requested.  The views are
345         * stateless, so there's no reason to create more than one of each.
346         */
347        transient volatile Set        keySet = null;
348        transient volatile Collection values = null;
349    
350        /**
351         * Returns a Set view of the keys contained in this map.  The Set is
352         * backed by the map, so changes to the map are reflected in the Set,
353         * and vice-versa.  (If the map is modified while an iteration over
354         * the Set is in progress, the results of the iteration are undefined.)
355         * The Set supports element removal, which removes the corresponding entry
356         * from the map, via the Iterator.remove, Set.remove,  removeAll
357         * retainAll, and clear operations.  It does not support the add or
358         * addAll operations.<p>
359         *
360         * This implementation returns a Set that subclasses
361         * AbstractSet.  The subclass's iterator method returns a "wrapper
362         * object" over this map's entrySet() iterator.  The size method delegates
363         * to this map's size method and the contains method delegates to this
364         * map's containsKey method.<p>
365         *
366         * The Set is created the first time this method is called,
367         * and returned in response to all subsequent calls.  No synchronization
368         * is performed, so there is a slight chance that multiple calls to this
369         * method will not all return the same Set.
370         *
371         * @return a Set view of the keys contained in this map.
372         */
373        public Set keySet() {
374            if (keySet == null) {
375                keySet = new AbstractSet() {
376                        public Iterator iterator() {
377                            return new Iterator() {
378                                    private Iterator i = entrySet().iterator();
379    
380                                    public boolean hasNext() {
381                                        return i.hasNext();
382                                    }
383    
384                                    public Object next() {
385                                        return ((Entry)i.next()).getKey();
386                                    }
387    
388                                    public void remove() {
389                                        i.remove();
390                                    }
391                                };
392                        }
393    
394                        public int size() {
395                            return AbstractMap.this.size();
396                        }
397    
398                        public boolean contains(Object k) {
399                            return AbstractMap.this.containsKey(k);
400                        }
401                    };
402            }
403            return keySet;
404        }
405    
406        /**
407         * Returns a collection view of the values contained in this map.  The
408         * collection is backed by the map, so changes to the map are reflected in
409         * the collection, and vice-versa.  (If the map is modified while an
410         * iteration over the collection is in progress, the results of the
411         * iteration are undefined.)  The collection supports element removal,
412         * which removes the corresponding entry from the map, via the
413         * <tt>Iterator.remove</tt>, <tt>Collection.remove</tt>,
414         * <tt>removeAll</tt>, <tt>retainAll</tt> and <tt>clear</tt> operations.
415         * It does not support the <tt>add</tt> or <tt>addAll</tt> operations.<p>
416         *
417         * This implementation returns a collection that subclasses abstract
418         * collection.  The subclass's iterator method returns a "wrapper object"
419         * over this map's <tt>entrySet()</tt> iterator.  The size method
420         * delegates to this map's size method and the contains method delegates
421         * to this map's containsValue method.<p>
422         *
423         * The collection is created the first time this method is called, and
424         * returned in response to all subsequent calls.  No synchronization is
425         * performed, so there is a slight chance that multiple calls to this
426         * method will not all return the same Collection.
427         *
428         * @return a collection view of the values contained in this map.
429         */
430        public Collection values() {
431            if (values == null) {
432                values = new AbstractCollection() {
433                        public Iterator iterator() {
434                            return new Iterator() {
435                                    private Iterator i = entrySet().iterator();
436    
437                                    public boolean hasNext() {
438                                        return i.hasNext();
439                                    }
440    
441                                    public Object next() {
442                                        return ((Entry)i.next()).getValue();
443                                    }
444    
445                                    public void remove() {
446                                        i.remove();
447                                    }
448                                };
449                        }
450    
451                        public int size() {
452                            return AbstractMap.this.size();
453                        }
454    
455                        public boolean contains(Object v) {
456                            return AbstractMap.this.containsValue(v);
457                        }
458                    };
459            }
460            return values;
461        }
462    
463        /**
464         * Returns a set view of the mappings contained in this map.  Each element
465         * in this set is a Map.Entry.  The set is backed by the map, so changes
466         * to the map are reflected in the set, and vice-versa.  (If the map is
467         * modified while an iteration over the set is in progress, the results of
468         * the iteration are undefined.)  The set supports element removal, which
469         * removes the corresponding entry from the map, via the
470         * <tt>Iterator.remove</tt>, <tt>Set.remove</tt>, <tt>removeAll</tt>,
471         * <tt>retainAll</tt> and <tt>clear</tt> operations.  It does not support
472         * the <tt>add</tt> or <tt>addAll</tt> operations.
473         *
474         * @return a set view of the mappings contained in this map.
475         */
476        public abstract Set entrySet();
477    
478    
479        // Comparison and hashing
480    
481        /**
482         * Compares the specified object with this map for equality.  Returns
483         * <tt>true</tt> if the given object is also a map and the two maps
484         * represent the same mappings.  More formally, two maps <tt>t1</tt> and
485         * <tt>t2</tt> represent the same mappings if
486         * <tt>t1.keySet().equals(t2.keySet())</tt> and for every key <tt>k</tt>
487         * in <tt>t1.keySet()</tt>, <tt> (t1.get(k)==null ? t2.get(k)==null :
488         * t1.get(k).equals(t2.get(k))) </tt>.  This ensures that the
489         * <tt>equals</tt> method works properly across different implementations
490         * of the map interface.<p>
491         *
492         * This implementation first checks if the specified object is this map;
493         * if so it returns <tt>true</tt>.  Then, it checks if the specified
494         * object is a map whose size is identical to the size of this set; if
495         * not, it it returns <tt>false</tt>.  If so, it iterates over this map's
496         * <tt>entrySet</tt> collection, and checks that the specified map
497         * contains each mapping that this map contains.  If the specified map
498         * fails to contain such a mapping, <tt>false</tt> is returned.  If the
499         * iteration completes, <tt>true</tt> is returned.
500         *
501         * @param o object to be compared for equality with this map.
502         * @return <tt>true</tt> if the specified object is equal to this map.
503         */
504        public boolean equals(Object o) {
505            if (o == this)
506                return true;
507    
508            if (!(o instanceof Map))
509                return false;
510            Map t = (Map) o;
511            if (t.size() != size())
512                return false;
513    
514            try {
515                Iterator i = entrySet().iterator();
516                while (i.hasNext()) {
517                    Entry e = (Entry) i.next();
518                    Object key = e.getKey();
519                    Object value = e.getValue();
520                    if (value == null) {
521                        if (!(t.get(key)==null && t.containsKey(key)))
522                            return false;
523                    } else {
524                        if (!value.equals(t.get(key)))
525                            return false;
526                    }
527                }
528            } catch(ClassCastException unused)   {
529                return false;
530            } catch(NullPointerException unused) {
531                return false;
532            }
533    
534            return true;
535        }
536    
537        /**
538         * Returns the hash code value for this map.  The hash code of a map is
539         * defined to be the sum of the hash codes of each entry in the map's
540         * <tt>entrySet()</tt> view.  This ensures that <tt>t1.equals(t2)</tt>
541         * implies that <tt>t1.hashCode()==t2.hashCode()</tt> for any two maps
542         * <tt>t1</tt> and <tt>t2</tt>, as required by the general contract of
543         * Object.hashCode.<p>
544         *
545         * This implementation iterates over <tt>entrySet()</tt>, calling
546         * <tt>hashCode</tt> on each element (entry) in the Collection, and adding
547         * up the results.
548         *
549         * @return the hash code value for this map.
550         * @see java.util.Map.Entry#hashCode()
551         * @see Object#hashCode()
552         * @see Object#equals(Object)
553         * @see Set#equals(Object)
554         */
555        public int hashCode() {
556            int h = 0;
557            Iterator i = entrySet().iterator();
558            while (i.hasNext())
559                h += i.next().hashCode();
560            return h;
561        }
562    
563        /**
564         * Returns a string representation of this map.  The string representation
565         * consists of a list of key-value mappings in the order returned by the
566         * map's <tt>entrySet</tt> view's iterator, enclosed in braces
567         * (<tt>"{}"</tt>).  Adjacent mappings are separated by the characters
568         * <tt>", "</tt> (comma and space).  Each key-value mapping is rendered as
569         * the key followed by an equals sign (<tt>"="</tt>) followed by the
570         * associated value.  Keys and values are converted to strings as by
571         * <tt>String.valueOf(Object)</tt>.<p>
572         *
573         * This implementation creates an empty string buffer, appends a left
574         * brace, and iterates over the map's <tt>entrySet</tt> view, appending
575         * the string representation of each <tt>map.entry</tt> in turn.  After
576         * appending each entry except the last, the string <tt>", "</tt> is
577         * appended.  Finally a right brace is appended.  A string is obtained
578         * from the stringbuffer, and returned.
579         *
580         * @return a String representation of this map.
581         */
582        public String toString() {
583            StringBuffer buf = new StringBuffer();
584            buf.append("{");
585    
586            Iterator i = entrySet().iterator();
587            boolean hasNext = i.hasNext();
588            while (hasNext) {
589                Entry e = (Entry) (i.next());
590                Object key = e.getKey();
591                Object value = e.getValue();
592                buf.append((key == this ?  "(this Map)" : key) + "=" + 
593                           (value == this ? "(this Map)": value));
594    
595                hasNext = i.hasNext();
596                if (hasNext)
597                    buf.append(", ");
598            }
599    
600            buf.append("}");
601            return buf.toString();
602        }
603        
604        /**
605         * Returns a shallow copy of this <tt>AbstractMap</tt> instance: the keys
606         * and values themselves are not cloned.
607         *
608         * @return a shallow copy of this map.
609         */
610        protected Object clone() throws CloneNotSupportedException {
611            AbstractMap result = (AbstractMap)super.clone();
612            result.keySet = null;
613            result.values = null;
614            return result;
615        }
616    
617        /**
618         * This should be made public as soon as possible.  It greately simplifies
619         * the task of implementing Map.
620         */
621        static class SimpleEntry implements Entry {
622            Object key;
623            Object value;
624    
625            public SimpleEntry(Object key, Object value) {
626                this.key   = key;
627                this.value = value;
628            }
629    
630            public SimpleEntry(Map.Entry e) {
631                this.key   = e.getKey();
632                this.value = e.getValue();
633            }
634    
635            public Object getKey() {
636                return key;
637            }
638    
639            public Object getValue() {
640                return value;
641            }
642    
643            public Object setValue(Object value) {
644                Object oldValue = this.value;
645                this.value = value;
646                return oldValue;
647            }
648    
649            public boolean equals(Object o) {
650                if (!(o instanceof Map.Entry))
651                    return false;
652                Map.Entry e = (Map.Entry)o;
653                return eq(key, e.getKey()) &&  eq(value, e.getValue());
654            }
655    
656            public int hashCode() {
657                Object v;
658                return ((key   == null)   ? 0 :   key.hashCode()) ^
659                    ((value == null)   ? 0 : value.hashCode());
660            }
661    
662            public String toString() {
663                return key + "=" + value;
664            }
665    
666            private static boolean eq(Object o1, Object o2) {
667                return (o1 == null ? o2 == null : o1.equals(o2));
668            }
669        }
670    }