KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > java > util > EnumMap


1 /*
2  * @(#)EnumMap.java 1.11 05/09/02
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package java.util;
9 import java.util.Map.Entry;
10
11 /**
12  * A specialized {@link Map} implementation for use with enum type keys. All
13  * of the keys in an enum map must come from a single enum type that is
14  * specified, explicitly or implicitly, when the map is created. Enum maps
15  * are represented internally as arrays. This representation is extremely
16  * compact and efficient.
17  *
18  * <p>Enum maps are maintained in the <i>natural order</i> of their keys
19  * (the order in which the enum constants are declared). This is reflected
20  * in the iterators returned by the collections views ({@link #keySet()},
21  * {@link #entrySet()}, and {@link #values()}).
22  *
23  * <p>Iterators returned by the collection views are <i>weakly consistent</i>:
24  * they will never throw {@link ConcurrentModificationException} and they may
25  * or may not show the effects of any modifications to the map that occur while
26  * the iteration is in progress.
27  *
28  * <p>Null keys are not permitted. Attempts to insert a null key will
29  * throw {@link NullPointerException}. Attempts to test for the
30  * presence of a null key or to remove one will, however, function properly.
31  * Null values are permitted.
32
33  * <P>Like most collection implementations <tt>EnumMap</tt> is not
34  * synchronized. If multiple threads access an enum map concurrently, and at
35  * least one of the threads modifies the map, it should be synchronized
36  * externally. This is typically accomplished by synchronizing on some
37  * object that naturally encapsulates the enum map. If no such object exists,
38  * the map should be "wrapped" using the {@link Collections#synchronizedMap}
39  * method. This is best done at creation time, to prevent accidental
40  * unsynchronized access:
41  *
42  * <pre>
43  * Map&lt;EnumKey, V&gt; m = Collections.synchronizedMap(new EnumMap(...));
44  * </pre>
45  *
46  * <p>Implementation note: All basic operations execute in constant time.
47  * They are likely (though not guaranteed) to be faster than their
48  * {@link HashMap} counterparts.
49  *
50  * <p>This class is a member of the
51  * <a HREF="{@docRoot}/../guide/collections/index.html">
52  * Java Collections Framework</a>.
53  *
54  * @author Josh Bloch
55  * @version 1.11, 09/02/05
56  * @see EnumSet
57  * @since 1.5
58  */

59 public class EnumMap<K extends Enum JavaDoc<K>, V> extends AbstractMap JavaDoc<K, V>
60     implements java.io.Serializable JavaDoc, Cloneable JavaDoc
61 {
62     /**
63      * The <tt>Class</tt> object for the enum type of all the keys of this map.
64      *
65      * @serial
66      */

67     private final Class JavaDoc<K> keyType;
68
69     /**
70      * All of the values comprising K. (Cached for performance.)
71      */

72     private transient K[] keyUniverse;
73
74     /**
75      * Array representation of this map. The ith element is the value
76      * to which universe[i] is currently mapped, or null if it isn't
77      * mapped to anything, or NULL if it's mapped to null.
78      */

79     private transient Object JavaDoc[] vals;
80
81     /**
82      * The number of mappings in this map.
83      */

84     private transient int size = 0;
85
86     /**
87      * Distinguished non-null value for representing null values.
88      */

89     private static final Object JavaDoc NULL = new Object JavaDoc();
90
91     private Object JavaDoc maskNull(Object JavaDoc value) {
92         return (value == null ? NULL : value);
93     }
94
95     private V unmaskNull(Object JavaDoc value) {
96         return (V) (value == NULL ? null : value);
97     }
98
99     private static Enum JavaDoc[] ZERO_LENGTH_ENUM_ARRAY = new Enum JavaDoc[0];
100
101     /**
102      * Creates an empty enum map with the specified key type.
103      *
104      * @param keyType the class object of the key type for this enum map
105      * @throws NullPointerException if <tt>keyType</tt> is null
106      */

107     public EnumMap(Class JavaDoc<K> keyType) {
108         this.keyType = keyType;
109         keyUniverse = keyType.getEnumConstants();
110         vals = new Object JavaDoc[keyUniverse.length];
111     }
112
113     /**
114      * Creates an enum map with the same key type as the specified enum
115      * map, initially containing the same mappings (if any).
116      *
117      * @param m the enum map from which to initialize this enum map
118      * @throws NullPointerException if <tt>m</tt> is null
119      */

120     public EnumMap(EnumMap JavaDoc<K, ? extends V> m) {
121         keyType = m.keyType;
122         keyUniverse = m.keyUniverse;
123         vals = (Object JavaDoc[]) m.vals.clone();
124         size = m.size;
125     }
126
127     /**
128      * Creates an enum map initialized from the specified map. If the
129      * specified map is an <tt>EnumMap</tt> instance, this constructor behaves
130      * identically to {@link #EnumMap(EnumMap)}. Otherwise, the specified map
131      * must contain at least one mapping (in order to determine the new
132      * enum map's key type).
133      *
134      * @param m the map from which to initialize this enum map
135      * @throws IllegalArgumentException if <tt>m</tt> is not an
136      * <tt>EnumMap</tt> instance and contains no mappings
137      * @throws NullPointerException if <tt>m</tt> is null
138      */

139     public EnumMap(Map JavaDoc<K, ? extends V> m) {
140         if (m instanceof EnumMap JavaDoc) {
141             EnumMap JavaDoc<K, ? extends V> em = (EnumMap JavaDoc<K, ? extends V>) m;
142             keyType = em.keyType;
143             keyUniverse = em.keyUniverse;
144             vals = (Object JavaDoc[]) em.vals.clone();
145             size = em.size;
146         } else {
147             if (m.isEmpty())
148                 throw new IllegalArgumentException JavaDoc("Specified map is empty");
149             keyType = m.keySet().iterator().next().getDeclaringClass();
150             keyUniverse = keyType.getEnumConstants();
151             vals = new Object JavaDoc[keyUniverse.length];
152             putAll(m);
153         }
154     }
155
156     // Query Operations
157

158     /**
159      * Returns the number of key-value mappings in this map.
160      *
161      * @return the number of key-value mappings in this map
162      */

163     public int size() {
164         return size;
165     }
166
167     /**
168      * Returns <tt>true</tt> if this map maps one or more keys to the
169      * specified value.
170      *
171      * @param value the value whose presence in this map is to be tested
172      * @return <tt>true</tt> if this map maps one or more keys to this value
173      */

174     public boolean containsValue(Object JavaDoc value) {
175         value = maskNull(value);
176
177         for (Object JavaDoc val : vals)
178             if (value.equals(val))
179                 return true;
180
181         return false;
182     }
183
184     /**
185      * Returns <tt>true</tt> if this map contains a mapping for the specified
186      * key.
187      *
188      * @param key the key whose presence in this map is to be tested
189      * @return <tt>true</tt> if this map contains a mapping for the specified
190      * key
191      */

192     public boolean containsKey(Object JavaDoc key) {
193         return isValidKey(key) && vals[((Enum JavaDoc)key).ordinal()] != null;
194     }
195
196     private boolean containsMapping(Object JavaDoc key, Object JavaDoc value) {
197         return isValidKey(key) &&
198             maskNull(value).equals(vals[((Enum JavaDoc)key).ordinal()]);
199     }
200
201     /**
202      * Returns the value to which this map maps the specified key, or null
203      * if this map contains no mapping for the specified key.
204      *
205      * @param key the key whose associated value is to be returned
206      * @return the value to which this map maps the specified key, or null
207      * if this map contains no mapping for the specified key
208      */

209     public V get(Object JavaDoc key) {
210         return (isValidKey(key) ?
211                 unmaskNull(vals[((Enum JavaDoc)key).ordinal()]) : null);
212     }
213
214     // Modification Operations
215

216     /**
217      * Associates the specified value with the specified key in this map.
218      * If the map previously contained a mapping for this key, the old
219      * value is replaced.
220      *
221      * @param key the key with which the specified value is to be associated
222      * @param value the value to be associated with the specified key
223      *
224      * @return the previous value associated with specified key, or
225      * <tt>null</tt> if there was no mapping for key. (A <tt>null</tt>
226      * return can also indicate that the map previously associated
227      * <tt>null</tt> with the specified key.)
228      * @throws NullPointerException if the specified key is null
229      */

230     public V put(K key, V value) {
231         typeCheck(key);
232
233         int index = ((Enum JavaDoc)key).ordinal();
234         Object JavaDoc oldValue = vals[index];
235         vals[index] = maskNull(value);
236         if (oldValue == null)
237             size++;
238         return unmaskNull(oldValue);
239     }
240
241     /**
242      * Removes the mapping for this key from this map if present.
243      *
244      * @param key the key whose mapping is to be removed from the map
245      * @return the previous value associated with specified key, or
246      * <tt>null</tt> if there was no entry for key. (A <tt>null</tt>
247      * return can also indicate that the map previously associated
248      * <tt>null</tt> with the specified key.)
249      */

250     public V remove(Object JavaDoc key) {
251         if (!isValidKey(key))
252             return null;
253         int index = ((Enum JavaDoc)key).ordinal();
254         Object JavaDoc oldValue = vals[index];
255         vals[index] = null;
256         if (oldValue != null)
257             size--;
258         return unmaskNull(oldValue);
259     }
260
261     private boolean removeMapping(Object JavaDoc key, Object JavaDoc value) {
262         if (!isValidKey(key))
263             return false;
264         int index = ((Enum JavaDoc)key).ordinal();
265         if (maskNull(value).equals(vals[index])) {
266             vals[index] = null;
267             size--;
268             return true;
269         }
270         return false;
271     }
272
273     /**
274      * Returns true if key is of the proper type to be a key in this
275      * enum map.
276      */

277     private boolean isValidKey(Object JavaDoc key) {
278         if (key == null)
279             return false;
280
281         // Cheaper than instanceof Enum followed by getDeclaringClass
282
Class JavaDoc keyClass = key.getClass();
283         return keyClass == keyType || keyClass.getSuperclass() == keyType;
284     }
285
286     // Bulk Operations
287

288     /**
289      * Copies all of the mappings from the specified map to this map.
290      * These mappings will replace any mappings that this map had for
291      * any of the keys currently in the specified map.
292      *
293      * @param m the mappings to be stored in this map
294      * @throws NullPointerException the specified map is null, or if
295      * one or more keys in the specified map are null
296      */

297     public void putAll(Map JavaDoc<? extends K, ? extends V> m) {
298         if (m instanceof EnumMap JavaDoc) {
299             EnumMap JavaDoc<? extends K, ? extends V> em =
300                 (EnumMap JavaDoc<? extends K, ? extends V>)m;
301             if (em.keyType != keyType) {
302                 if (em.isEmpty())
303                     return;
304                 throw new ClassCastException JavaDoc(em.keyType + " != " + keyType);
305             }
306
307             for (int i = 0; i < keyUniverse.length; i++) {
308                 Object JavaDoc emValue = em.vals[i];
309                 if (emValue != null) {
310                     if (vals[i] == null)
311                         size++;
312                     vals[i] = emValue;
313                 }
314             }
315         } else {
316             super.putAll(m);
317         }
318     }
319
320     /**
321      * Removes all mappings from this map.
322      */

323     public void clear() {
324         Arrays.fill(vals, null);
325         size = 0;
326     }
327
328     // Views
329

330     /**
331      * This field is initialized to contain an instance of the entry set
332      * view the first time this view is requested. The view is stateless,
333      * so there's no reason to create more than one.
334      */

335     private transient Set JavaDoc<Map.Entry JavaDoc<K,V>> entrySet = null;
336
337     /**
338      * Returns a {@link Set} view of the keys contained in this map.
339      * The returned set obeys the general contract outlined in
340      * {@link Map#keySet()}. The set's iterator will return the keys
341      * in their natural order (the order in which the enum constants
342      * are declared).
343      *
344      * @return a set view of the keys contained in this enum map
345      */

346     public Set JavaDoc<K> keySet() {
347         Set JavaDoc<K> ks = keySet;
348         if (ks != null)
349             return ks;
350         else
351             return keySet = new KeySet();
352     }
353
354     private class KeySet extends AbstractSet JavaDoc<K> {
355         public Iterator JavaDoc<K> iterator() {
356             return new KeyIterator();
357         }
358         public int size() {
359             return size;
360         }
361         public boolean contains(Object JavaDoc o) {
362             return containsKey(o);
363         }
364         public boolean remove(Object JavaDoc o) {
365             int oldSize = size;
366             EnumMap.this.remove(o);
367             return size != oldSize;
368         }
369         public void clear() {
370             EnumMap.this.clear();
371         }
372     }
373
374     /**
375      * Returns a {@link Collection} view of the values contained in this map.
376      * The returned collection obeys the general contract outlined in
377      * {@link Map#values()}. The collection's iterator will return the
378      * values in the order their corresponding keys appear in map,
379      * which is their natural order (the order in which the enum constants
380      * are declared).
381      *
382      * @return a collection view of the values contained in this map
383      */

384     public Collection JavaDoc<V> values() {
385         Collection JavaDoc<V> vs = values;
386         if (vs != null)
387             return vs;
388         else
389             return values = new Values();
390     }
391
392     private class Values extends AbstractCollection JavaDoc<V> {
393         public Iterator JavaDoc<V> iterator() {
394             return new ValueIterator();
395         }
396         public int size() {
397             return size;
398         }
399         public boolean contains(Object JavaDoc o) {
400             return containsValue(o);
401         }
402         public boolean remove(Object JavaDoc o) {
403             o = maskNull(o);
404
405             for (int i = 0; i < vals.length; i++) {
406                 if (o.equals(vals[i])) {
407                     vals[i] = null;
408                     size--;
409                     return true;
410                 }
411             }
412             return false;
413         }
414         public void clear() {
415             EnumMap.this.clear();
416         }
417     }
418
419     /**
420      * Returns a {@link Set} view of the mappings contained in this map.
421      * The returned set obeys the general contract outlined in
422      * {@link Map#keySet()}. The set's iterator will return the
423      * mappings in the order their keys appear in map, which is their
424      * natural order (the order in which the enum constants are declared).
425      *
426      * @return a set view of the mappings contained in this enum map
427      */

428     public Set JavaDoc<Map.Entry JavaDoc<K,V>> entrySet() {
429         Set JavaDoc<Map.Entry JavaDoc<K,V>> es = entrySet;
430         if (es != null)
431             return es;
432         else
433             return entrySet = new EntrySet();
434     }
435
436     private class EntrySet extends AbstractSet JavaDoc<Map.Entry JavaDoc<K,V>> {
437         public Iterator JavaDoc<Map.Entry JavaDoc<K,V>> iterator() {
438             return new EntryIterator();
439         }
440         public boolean contains(Object JavaDoc o) {
441             if (!(o instanceof Map.Entry JavaDoc))
442                 return false;
443             Map.Entry JavaDoc entry = (Map.Entry JavaDoc)o;
444             return containsMapping(entry.getKey(), entry.getValue());
445         }
446         public boolean remove(Object JavaDoc o) {
447             if (!(o instanceof Map.Entry JavaDoc))
448                 return false;
449             Map.Entry JavaDoc entry = (Map.Entry JavaDoc)o;
450             return removeMapping(entry.getKey(), entry.getValue());
451         }
452         public int size() {
453             return size;
454         }
455         public void clear() {
456             EnumMap.this.clear();
457         }
458         public Object JavaDoc[] toArray() {
459             return fillEntryArray(new Object JavaDoc[size]);
460         }
461         public <T> T[] toArray(T[] a) {
462         int size = size();
463         if (a.length < size)
464         a = (T[])java.lang.reflect.Array
465             .newInstance(a.getClass().getComponentType(), size);
466         if (a.length > size)
467         a[size] = null;
468             return (T[]) fillEntryArray(a);
469         }
470         private Object JavaDoc[] fillEntryArray(Object JavaDoc[] a) {
471             int j = 0;
472             for (int i = 0; i < vals.length; i++)
473                 if (vals[i] != null)
474                     a[j++] = new AbstractMap.SimpleEntry JavaDoc<K,V>(
475                         keyUniverse[i], unmaskNull(vals[i]));
476             return a;
477         }
478     }
479
480     private abstract class EnumMapIterator<T> implements Iterator JavaDoc<T> {
481         // Lower bound on index of next element to return
482
int index = 0;
483
484         // Index of last returned element, or -1 if none
485
int lastReturnedIndex = -1;
486
487         public boolean hasNext() {
488             while (index < vals.length && vals[index] == null)
489                 index++;
490             return index != vals.length;
491         }
492
493         public void remove() {
494             checkLastReturnedIndex();
495
496             if (vals[lastReturnedIndex] != null) {
497                 vals[lastReturnedIndex] = null;
498                 size--;
499             }
500             lastReturnedIndex = -1;
501         }
502
503         private void checkLastReturnedIndex() {
504             if (lastReturnedIndex < 0)
505                 throw new IllegalStateException JavaDoc();
506         }
507     }
508
509     private class KeyIterator extends EnumMapIterator<K> {
510         public K next() {
511             if (!hasNext())
512                 throw new NoSuchElementException JavaDoc();
513             lastReturnedIndex = index++;
514             return keyUniverse[lastReturnedIndex];
515         }
516     }
517
518     private class ValueIterator extends EnumMapIterator<V> {
519         public V next() {
520             if (!hasNext())
521                 throw new NoSuchElementException JavaDoc();
522             lastReturnedIndex = index++;
523             return unmaskNull(vals[lastReturnedIndex]);
524         }
525     }
526
527     /**
528      * Since we don't use Entry objects, we use the Iterator itself as entry.
529      */

530     private class EntryIterator extends EnumMapIterator<Map.Entry JavaDoc<K,V>>
531         implements Map.Entry JavaDoc<K,V>
532     {
533         public Map.Entry JavaDoc<K,V> next() {
534             if (!hasNext())
535                 throw new NoSuchElementException JavaDoc();
536             lastReturnedIndex = index++;
537             return this;
538         }
539
540         public K getKey() {
541             checkLastReturnedIndexForEntryUse();
542             return keyUniverse[lastReturnedIndex];
543         }
544
545         public V getValue() {
546             checkLastReturnedIndexForEntryUse();
547             return unmaskNull(vals[lastReturnedIndex]);
548         }
549
550         public V setValue(V value) {
551             checkLastReturnedIndexForEntryUse();
552             V oldValue = unmaskNull(vals[lastReturnedIndex]);
553             vals[lastReturnedIndex] = maskNull(value);
554             return oldValue;
555         }
556
557         public boolean equals(Object JavaDoc o) {
558             if (lastReturnedIndex < 0)
559                 return o == this;
560
561             if (!(o instanceof Map.Entry JavaDoc))
562                 return false;
563             Map.Entry JavaDoc e = (Map.Entry JavaDoc)o;
564             V ourValue = unmaskNull(vals[lastReturnedIndex]);
565             Object JavaDoc hisValue = e.getValue();
566             return e.getKey() == keyUniverse[lastReturnedIndex] &&
567                 (ourValue == hisValue ||
568                  (ourValue != null && ourValue.equals(hisValue)));
569         }
570
571         public int hashCode() {
572             if (lastReturnedIndex < 0)
573                 return super.hashCode();
574
575             Object JavaDoc value = vals[lastReturnedIndex];
576             return keyUniverse[lastReturnedIndex].hashCode()
577                 ^ (value == NULL ? 0 : value.hashCode());
578         }
579
580         public String JavaDoc toString() {
581             if (lastReturnedIndex < 0)
582                 return super.toString();
583
584             return keyUniverse[lastReturnedIndex] + "="
585                 + unmaskNull(vals[lastReturnedIndex]);
586         }
587
588         private void checkLastReturnedIndexForEntryUse() {
589             if (lastReturnedIndex < 0)
590                 throw new IllegalStateException JavaDoc("Entry was removed");
591         }
592     }
593
594     // Comparison and hashing
595

596     /**
597      * Compares the specified object with this map for equality. Returns
598      * <tt>true</tt> if the given object is also a map and the two maps
599      * represent the same mappings, as specified in the {@link
600      * Map#equals(Object)} contract.
601      *
602      * @param o the object to be compared for equality with this map
603      * @return <tt>true</tt> if the specified object is equal to this map
604      */

605     public boolean equals(Object JavaDoc o) {
606         if (!(o instanceof EnumMap JavaDoc))
607             return super.equals(o);
608
609         EnumMap JavaDoc em = (EnumMap JavaDoc)o;
610         if (em.keyType != keyType)
611             return size == 0 && em.size == 0;
612
613         // Key types match, compare each value
614
for (int i = 0; i < keyUniverse.length; i++) {
615             Object JavaDoc ourValue = vals[i];
616             Object JavaDoc hisValue = em.vals[i];
617             if (hisValue != ourValue &&
618                 (hisValue == null || !hisValue.equals(ourValue)))
619                 return false;
620         }
621         return true;
622     }
623
624     /**
625      * Returns a shallow copy of this enum map. (The values themselves
626      * are not cloned.
627      *
628      * @return a shallow copy of this enum map
629      */

630     public EnumMap JavaDoc<K, V> clone() {
631         EnumMap JavaDoc<K, V> result = null;
632         try {
633             result = (EnumMap JavaDoc<K, V>) super.clone();
634         } catch(CloneNotSupportedException JavaDoc e) {
635             throw new AssertionError JavaDoc();
636         }
637         result.vals = (Object JavaDoc[]) result.vals.clone();
638         return result;
639     }
640
641     /**
642      * Throws an exception if e is not of the correct type for this enum set.
643      */

644     private void typeCheck(K key) {
645         Class JavaDoc keyClass = key.getClass();
646         if (keyClass != keyType && keyClass.getSuperclass() != keyType)
647             throw new ClassCastException JavaDoc(keyClass + " != " + keyType);
648     }
649
650     private static final long serialVersionUID = 458661240069192865L;
651
652     /**
653      * Save the state of the <tt>EnumMap</tt> instance to a stream (i.e.,
654      * serialize it).
655      *
656      * @serialData The <i>size</i> of the enum map (the number of key-value
657      * mappings) is emitted (int), followed by the key (Object)
658      * and value (Object) for each key-value mapping represented
659      * by the enum map.
660      */

661     private void writeObject(java.io.ObjectOutputStream JavaDoc s)
662         throws java.io.IOException JavaDoc
663     {
664         // Write out the key type and any hidden stuff
665
s.defaultWriteObject();
666
667         // Write out size (number of Mappings)
668
s.writeInt(size);
669
670         // Write out keys and values (alternating)
671
for (Map.Entry JavaDoc<K,V> e : entrySet()) {
672             s.writeObject(e.getKey());
673             s.writeObject(e.getValue());
674         }
675     }
676
677     /**
678      * Reconstitute the <tt>EnumMap</tt> instance from a stream (i.e.,
679      * deserialize it).
680      */

681     private void readObject(java.io.ObjectInputStream JavaDoc s)
682         throws java.io.IOException JavaDoc, ClassNotFoundException JavaDoc
683     {
684         // Read in the key type and any hidden stuff
685
s.defaultReadObject();
686
687         keyUniverse = keyType.getEnumConstants();
688         vals = new Object JavaDoc[keyUniverse.length];
689
690         // Read in size (number of Mappings)
691
int size = s.readInt();
692
693         // Read the keys and values, and put the mappings in the HashMap
694
for (int i = 0; i < size; i++) {
695             K key = (K) s.readObject();
696             V value = (V) s.readObject();
697             put(key, value);
698         }
699     }
700 }
701
Popular Tags