View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.lang.enums;
18  
19  import java.io.Serializable;
20  import java.lang.reflect.InvocationTargetException;
21  import java.lang.reflect.Method;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.Map;
28  import java.util.WeakHashMap;
29  
30  import org.apache.commons.lang.ClassUtils;
31  import org.apache.commons.lang.StringUtils;
32  
33  /**
34   * <p>Abstract superclass for type-safe enums.</p>
35   *
36   * <p>One feature of the C programming language lacking in Java is enumerations. The
37   * C implementation based on ints was poor and open to abuse. The original Java
38   * recommendation and most of the JDK also uses int constants. It has been recognised
39   * however that a more robust type-safe class-based solution can be designed. This
40   * class follows the basic Java type-safe enumeration pattern.</p>
41   *
42   * <p><em>NOTE:</em> Due to the way in which Java ClassLoaders work, comparing
43   * Enum objects should always be done using <code>equals()</code>, not <code>==</code>.
44   * The equals() method will try == first so in most cases the effect is the same.</p>
45   * 
46   * <p>Of course, if you actually want (or don't mind) Enums in different class
47   * loaders being non-equal, then you can use <code>==</code>.</p>
48   * 
49   * <h4>Simple Enums</h4>
50   *
51   * <p>To use this class, it must be subclassed. For example:</p>
52   *
53   * <pre>
54   * public final class ColorEnum extends Enum {
55   *   public static final ColorEnum RED = new ColorEnum("Red");
56   *   public static final ColorEnum GREEN = new ColorEnum("Green");
57   *   public static final ColorEnum BLUE = new ColorEnum("Blue");
58   *
59   *   private ColorEnum(String color) {
60   *     super(color);
61   *   }
62   * 
63   *   public static ColorEnum getEnum(String color) {
64   *     return (ColorEnum) getEnum(ColorEnum.class, color);
65   *   }
66   * 
67   *   public static Map getEnumMap() {
68   *     return getEnumMap(ColorEnum.class);
69   *   }
70   * 
71   *   public static List getEnumList() {
72   *     return getEnumList(ColorEnum.class);
73   *   }
74   * 
75   *   public static Iterator iterator() {
76   *     return iterator(ColorEnum.class);
77   *   }
78   * }
79   * </pre>
80   *
81   * <p>As shown, each enum has a name. This can be accessed using <code>getName</code>.</p>
82   *
83   * <p>The <code>getEnum</code> and <code>iterator</code> methods are recommended.
84   * Unfortunately, Java restrictions require these to be coded as shown in each subclass.
85   * An alternative choice is to use the {@link EnumUtils} class.</p>
86   * 
87   * <h4>Subclassed Enums</h4>
88   * <p>A hierarchy of Enum classes can be built. In this case, the superclass is
89   * unaffected by the addition of subclasses (as per normal Java). The subclasses
90   * may add additional Enum constants <em>of the type of the superclass</em>. The
91   * query methods on the subclass will return all of the Enum constants from the
92   * superclass and subclass.</p>
93   *
94   * <pre>
95   * public final class ExtraColorEnum extends ColorEnum {
96   *   // NOTE: Color enum declared above is final, change that to get this
97   *   // example to compile.
98   *   public static final ColorEnum YELLOW = new ExtraColorEnum("Yellow");
99   *
100  *   private ExtraColorEnum(String color) {
101  *     super(color);
102  *   }
103  * 
104  *   public static ColorEnum getEnum(String color) {
105  *     return (ColorEnum) getEnum(ExtraColorEnum.class, color);
106  *   }
107  * 
108  *   public static Map getEnumMap() {
109  *     return getEnumMap(ExtraColorEnum.class);
110  *   }
111  * 
112  *   public static List getEnumList() {
113  *     return getEnumList(ExtraColorEnum.class);
114  *   }
115  * 
116  *   public static Iterator iterator() {
117  *     return iterator(ExtraColorEnum.class);
118  *   }
119  * }
120  * </pre>
121  *
122  * <p>This example will return RED, GREEN, BLUE, YELLOW from the List and iterator
123  * methods in that order. The RED, GREEN and BLUE instances will be the same (==) 
124  * as those from the superclass ColorEnum. Note that YELLOW is declared as a
125  * ColorEnum and not an ExtraColorEnum.</p>
126  * 
127  * <h4>Functional Enums</h4>
128  *
129  * <p>The enums can have functionality by defining subclasses and
130  * overriding the <code>getEnumClass()</code> method:</p>
131  * 
132  * <pre>
133  *   public static final OperationEnum PLUS = new PlusOperation();
134  *   private static final class PlusOperation extends OperationEnum {
135  *     private PlusOperation() {
136  *       super("Plus");
137  *     }
138  *     public int eval(int a, int b) {
139  *       return a + b;
140  *     }
141  *   }
142  *   public static final OperationEnum MINUS = new MinusOperation();
143  *   private static final class MinusOperation extends OperationEnum {
144  *     private MinusOperation() {
145  *       super("Minus");
146  *     }
147  *     public int eval(int a, int b) {
148  *       return a - b;
149  *     }
150  *   }
151  *
152  *   private OperationEnum(String color) {
153  *     super(color);
154  *   }
155  * 
156  *   public final Class getEnumClass() {     // NOTE: new method!
157  *     return OperationEnum.class;
158  *   }
159  *
160  *   public abstract double eval(double a, double b);
161  * 
162  *   public static OperationEnum getEnum(String name) {
163  *     return (OperationEnum) getEnum(OperationEnum.class, name);
164  *   }
165  * 
166  *   public static Map getEnumMap() {
167  *     return getEnumMap(OperationEnum.class);
168  *   }
169  * 
170  *   public static List getEnumList() {
171  *     return getEnumList(OperationEnum.class);
172  *   }
173  * 
174  *   public static Iterator iterator() {
175  *     return iterator(OperationEnum.class);
176  *   }
177  * }
178  * </pre>
179  * <p>The code above will work on JDK 1.2. If JDK1.3 and later is used,
180  * the subclasses may be defined as anonymous.</p>
181  * 
182  * <h4>Nested class Enums</h4>
183  *
184  * <p>Care must be taken with class loading when defining a static nested class
185  * for enums. The static nested class can be loaded without the surrounding outer
186  * class being loaded. This can result in an empty list/map/iterator being returned.
187  * One solution is to define a static block that references the outer class where
188  * the constants are defined. For example:</p>
189  *
190  * <pre>
191  * public final class Outer {
192  *   public static final BWEnum BLACK = new BWEnum("Black");
193  *   public static final BWEnum WHITE = new BWEnum("White");
194  *
195  *   // static nested enum class
196  *   public static final class BWEnum extends Enum {
197  * 
198  *     static {
199  *       // explicitly reference BWEnum class to force constants to load
200  *       Object obj = Outer.BLACK;
201  *     }
202  * 
203  *     // ... other methods omitted
204  *   }
205  * }
206  * </pre>
207  * 
208  * <p>Although the above solves the problem, it is not recommended. The best solution
209  * is to define the constants in the enum class, and hold references in the outer class:
210  *
211  * <pre>
212  * public final class Outer {
213  *   public static final BWEnum BLACK = BWEnum.BLACK;
214  *   public static final BWEnum WHITE = BWEnum.WHITE;
215  *
216  *   // static nested enum class
217  *   public static final class BWEnum extends Enum {
218  *     // only define constants in enum classes - private if desired
219  *     private static final BWEnum BLACK = new BWEnum("Black");
220  *     private static final BWEnum WHITE = new BWEnum("White");
221  * 
222  *     // ... other methods omitted
223  *   }
224  * }
225  * </pre>
226  * 
227  * <p>For more details, see the 'Nested' test cases.
228  *
229  * <h4>Lang Enums and Java 5.0 Enums</h4>
230  *
231  * <p>Enums were added to Java in Java 5.0. The main differences between Lang's 
232  * implementation and the new official JDK implementation are: </p>
233  * <ul>
234  * <li>The standard Enum is a not just a superclass, but is a type baked into the 
235  * language. </li>
236  * <li>The standard Enum does not support extension, so the standard methods that 
237  * are provided in the Lang enum are not available. </li>
238  * <li>Lang mandates a String name, whereas the standard Enum uses the class 
239  * name as its name. getName() changes to name(). </li>
240  * </ul>
241  *
242  * <p>Generally people should use the standard Enum. Migrating from the Lang 
243  * enum to the standard Enum is not as easy as it might be due to the lack of 
244  * class inheritence in standard Enums. This means that it's not possible 
245  * to provide a 'super-enum' which could provide the same utility methods 
246  * that the Lang enum does. The following utility class is a Java 5.0 
247  * version of our EnumUtils class and provides those utility methods. </p>
248  *
249  * <pre>
250  * import java.util.*;
251  * 
252  * public class EnumUtils {
253  * 
254  *   public static Enum getEnum(Class enumClass, String token) {
255  *     return Enum.valueOf(enumClass, token);
256  *   }
257  * 
258  *   public static Map getEnumMap(Class enumClass) {
259  *     HashMap map = new HashMap();
260  *     Iterator itr = EnumUtils.iterator(enumClass);
261  *     while(itr.hasNext()) {
262  *       Enum enm = (Enum) itr.next();
263  *       map.put( enm.name(), enm );
264  *     }
265  *     return map;
266  *   }
267  * 
268  *   public static List getEnumList(Class enumClass) {
269  *     return new ArrayList( EnumSet.allOf(enumClass) );
270  *   }
271  * 
272  *   public static Iterator iterator(Class enumClass) {
273  *     return EnumUtils.getEnumList(enumClass).iterator();
274  *   }
275  * }
276  * </pre>
277  * 
278  * @author Apache Avalon project
279  * @author Stephen Colebourne
280  * @author Chris Webb
281  * @author Mike Bowler
282  * @author Matthias Eichel
283  * @since 2.1 (class existed in enum package from v1.0)
284  * @version $Id: Enum.java 637362 2008-03-15 06:09:41Z bayard $
285  */
286 public abstract class Enum implements Comparable, Serializable {
287 
288     /**
289      * Required for serialization support.
290      * 
291      * @see java.io.Serializable
292      */
293     private static final long serialVersionUID = -487045951170455942L;
294     
295     // After discussion, the default size for HashMaps is used, as the
296     // sizing algorithm changes across the JDK versions
297     /**
298      * An empty <code>Map</code>, as JDK1.2 didn't have an empty map.
299      */
300     private static final Map EMPTY_MAP = Collections.unmodifiableMap(new HashMap(0));
301     
302     /**
303      * <code>Map</code>, key of class name, value of <code>Entry</code>.
304      */
305     private static Map cEnumClasses
306         // LANG-334: To avoid exposing a mutating map,
307         // we copy it each time we add to it. This is cheaper than
308         // using a synchronized map since we are almost entirely reads
309         = new WeakHashMap();
310     
311     /**
312      * The string representation of the Enum.
313      */
314     private final String iName;
315     
316     /**
317      * The hashcode representation of the Enum.
318      */
319     private transient final int iHashCode;
320     
321     /**
322      * The toString representation of the Enum.
323      * @since 2.0
324      */
325     protected transient String iToString = null;
326 
327     /**
328      * <p>Enable the iterator to retain the source code order.</p>
329      */
330     private static class Entry {
331         /**
332          * Map of Enum name to Enum.
333          */
334         final Map map = new HashMap();
335         /**
336          * Map of Enum name to Enum.
337          */
338         final Map unmodifiableMap = Collections.unmodifiableMap(map);
339         /**
340          * List of Enums in source code order.
341          */
342         final List list = new ArrayList(25);
343         /**
344          * Map of Enum name to Enum.
345          */
346         final List unmodifiableList = Collections.unmodifiableList(list);
347 
348         /**
349          * <p>Restrictive constructor.</p>
350          */
351         protected Entry() {
352             super();
353         }
354     }
355 
356     /**
357      * <p>Constructor to add a new named item to the enumeration.</p>
358      *
359      * @param name  the name of the enum object,
360      *  must not be empty or <code>null</code>
361      * @throws IllegalArgumentException if the name is <code>null</code>
362      *  or an empty string
363      * @throws IllegalArgumentException if the getEnumClass() method returns
364      *  a null or invalid Class
365      */
366     protected Enum(String name) {
367         super();
368         init(name);
369         iName = name;
370         iHashCode = 7 + getEnumClass().hashCode() + 3 * name.hashCode();
371         // cannot create toString here as subclasses may want to include other data
372     }
373 
374     /**
375      * Initializes the enumeration.
376      * 
377      * @param name  the enum name
378      * @throws IllegalArgumentException if the name is null or empty or duplicate
379      * @throws IllegalArgumentException if the enumClass is null or invalid
380      */
381     private void init(String name) {
382         if (StringUtils.isEmpty(name)) {
383             throw new IllegalArgumentException("The Enum name must not be empty or null");
384         }
385         
386         Class enumClass = getEnumClass();
387         if (enumClass == null) {
388             throw new IllegalArgumentException("getEnumClass() must not be null");
389         }
390         Class cls = getClass();
391         boolean ok = false;
392         while (cls != null && cls != Enum.class && cls != ValuedEnum.class) {
393             if (cls == enumClass) {
394                 ok = true;
395                 break;
396             }
397             cls = cls.getSuperclass();
398         }
399         if (ok == false) {
400             throw new IllegalArgumentException("getEnumClass() must return a superclass of this class");
401         }
402 
403         Entry entry;
404         synchronized( Enum.class ) { // LANG-334
405             // create entry
406             entry = (Entry) cEnumClasses.get(enumClass);
407             if (entry == null) {
408                 entry = createEntry(enumClass);
409                 Map myMap = new WeakHashMap( ); // we avoid the (Map) constructor to achieve JDK 1.2 support
410                 myMap.putAll( cEnumClasses );
411                 myMap.put(enumClass, entry);
412                 cEnumClasses = myMap;
413             }
414         }
415         if (entry.map.containsKey(name)) {
416             throw new IllegalArgumentException("The Enum name must be unique, '" + name + "' has already been added");
417         }
418         entry.map.put(name, this);
419         entry.list.add(this);
420     }
421 
422     /**
423      * <p>Handle the deserialization of the class to ensure that multiple
424      * copies are not wastefully created, or illegal enum types created.</p>
425      *
426      * @return the resolved object
427      */
428     protected Object readResolve() {
429         Entry entry = (Entry) cEnumClasses.get(getEnumClass());
430         if (entry == null) {
431             return null;
432         }
433         return entry.map.get(getName());
434     }
435     
436     //--------------------------------------------------------------------------------
437 
438     /**
439      * <p>Gets an <code>Enum</code> object by class and name.</p>
440      * 
441      * @param enumClass  the class of the Enum to get, must not
442      *  be <code>null</code>
443      * @param name  the name of the <code>Enum</code> to get,
444      *  may be <code>null</code>
445      * @return the enum object, or <code>null</code> if the enum does not exist
446      * @throws IllegalArgumentException if the enum class
447      *  is <code>null</code>
448      */
449     protected static Enum getEnum(Class enumClass, String name) {
450         Entry entry = getEntry(enumClass);
451         if (entry == null) {
452             return null;
453         }
454         return (Enum) entry.map.get(name);
455     }
456 
457     /**
458      * <p>Gets the <code>Map</code> of <code>Enum</code> objects by
459      * name using the <code>Enum</code> class.</p>
460      *
461      * <p>If the requested class has no enum objects an empty
462      * <code>Map</code> is returned.</p>
463      * 
464      * @param enumClass  the class of the <code>Enum</code> to get,
465      *  must not be <code>null</code>
466      * @return the enum object Map
467      * @throws IllegalArgumentException if the enum class is <code>null</code>
468      * @throws IllegalArgumentException if the enum class is not a subclass of Enum
469      */
470     protected static Map getEnumMap(Class enumClass) {
471         Entry entry = getEntry(enumClass);
472         if (entry == null) {
473             return EMPTY_MAP;
474         }
475         return entry.unmodifiableMap;
476     }
477 
478     /**
479      * <p>Gets the <code>List</code> of <code>Enum</code> objects using the
480      * <code>Enum</code> class.</p>
481      *
482      * <p>The list is in the order that the objects were created (source code order).
483      * If the requested class has no enum objects an empty <code>List</code> is
484      * returned.</p>
485      * 
486      * @param enumClass  the class of the <code>Enum</code> to get,
487      *  must not be <code>null</code>
488      * @return the enum object Map
489      * @throws IllegalArgumentException if the enum class is <code>null</code>
490      * @throws IllegalArgumentException if the enum class is not a subclass of Enum
491      */
492     protected static List getEnumList(Class enumClass) {
493         Entry entry = getEntry(enumClass);
494         if (entry == null) {
495             return Collections.EMPTY_LIST;
496         }
497         return entry.unmodifiableList;
498     }
499 
500     /**
501      * <p>Gets an <code>Iterator</code> over the <code>Enum</code> objects in
502      * an <code>Enum</code> class.</p>
503      *
504      * <p>The <code>Iterator</code> is in the order that the objects were
505      * created (source code order). If the requested class has no enum
506      * objects an empty <code>Iterator</code> is returned.</p>
507      * 
508      * @param enumClass  the class of the <code>Enum</code> to get,
509      *  must not be <code>null</code>
510      * @return an iterator of the Enum objects
511      * @throws IllegalArgumentException if the enum class is <code>null</code>
512      * @throws IllegalArgumentException if the enum class is not a subclass of Enum
513      */
514     protected static Iterator iterator(Class enumClass) {
515         return Enum.getEnumList(enumClass).iterator();
516     }
517 
518     //-----------------------------------------------------------------------
519     /**
520      * <p>Gets an <code>Entry</code> from the map of Enums.</p>
521      * 
522      * @param enumClass  the class of the <code>Enum</code> to get
523      * @return the enum entry
524      */
525     private static Entry getEntry(Class enumClass) {
526         if (enumClass == null) {
527             throw new IllegalArgumentException("The Enum Class must not be null");
528         }
529         if (Enum.class.isAssignableFrom(enumClass) == false) {
530             throw new IllegalArgumentException("The Class must be a subclass of Enum");
531         }
532         Entry entry = (Entry) cEnumClasses.get(enumClass);
533         return entry;
534     }
535     
536     /**
537      * <p>Creates an <code>Entry</code> for storing the Enums.</p>
538      *
539      * <p>This accounts for subclassed Enums.</p>
540      * 
541      * @param enumClass  the class of the <code>Enum</code> to get
542      * @return the enum entry
543      */
544     private static Entry createEntry(Class enumClass) {
545         Entry entry = new Entry();
546         Class cls = enumClass.getSuperclass();
547         while (cls != null && cls != Enum.class && cls != ValuedEnum.class) {
548             Entry loopEntry = (Entry) cEnumClasses.get(cls);
549             if (loopEntry != null) {
550                 entry.list.addAll(loopEntry.list);
551                 entry.map.putAll(loopEntry.map);
552                 break;  // stop here, as this will already have had superclasses added
553             }
554             cls = cls.getSuperclass();
555         }
556         return entry;
557     }
558     
559     //-----------------------------------------------------------------------
560     /**
561      * <p>Retrieve the name of this Enum item, set in the constructor.</p>
562      * 
563      * @return the <code>String</code> name of this Enum item
564      */
565     public final String getName() {
566         return iName;
567     }
568 
569     /**
570      * <p>Retrieves the Class of this Enum item, set in the constructor.</p>
571      * 
572      * <p>This is normally the same as <code>getClass()</code>, but for
573      * advanced Enums may be different. If overridden, it must return a
574      * constant value.</p>
575      * 
576      * @return the <code>Class</code> of the enum
577      * @since 2.0
578      */
579     public Class getEnumClass() {
580         return getClass();
581     }
582 
583     /**
584      * <p>Tests for equality.</p>
585      *
586      * <p>Two Enum objects are considered equal
587      * if they have the same class names and the same names.
588      * Identity is tested for first, so this method usually runs fast.</p>
589      * 
590      * <p>If the parameter is in a different class loader than this instance,
591      * reflection is used to compare the names.</p>
592      *
593      * @param other  the other object to compare for equality
594      * @return <code>true</code> if the Enums are equal
595      */
596     public final boolean equals(Object other) {
597         if (other == this) {
598             return true;
599         } else if (other == null) {
600             return false;
601         } else if (other.getClass() == this.getClass()) {
602             // Ok to do a class cast to Enum here since the test above
603             // guarantee both
604             // classes are in the same class loader.
605             return iName.equals(((Enum) other).iName);
606         } else {
607             // This and other are in different class loaders, we must check indirectly
608             if (other.getClass().getName().equals(this.getClass().getName()) == false) {
609                 return false;
610             }
611             return iName.equals( getNameInOtherClassLoader(other) );
612         }
613     }
614     
615     /**
616      * <p>Returns a suitable hashCode for the enumeration.</p>
617      *
618      * @return a hashcode based on the name
619      */
620     public final int hashCode() {
621         return iHashCode;
622     }
623 
624     /**
625      * <p>Tests for order.</p>
626      *
627      * <p>The default ordering is alphabetic by name, but this
628      * can be overridden by subclasses.</p>
629      * 
630      * <p>If the parameter is in a different class loader than this instance,
631      * reflection is used to compare the names.</p>
632      *
633      * @see java.lang.Comparable#compareTo(Object)
634      * @param other  the other object to compare to
635      * @return -ve if this is less than the other object, +ve if greater
636      *  than, <code>0</code> of equal
637      * @throws ClassCastException if other is not an Enum
638      * @throws NullPointerException if other is <code>null</code>
639      */
640     public int compareTo(Object other) {
641         if (other == this) {
642             return 0;
643         }
644         if (other.getClass() != this.getClass()) {
645             if (other.getClass().getName().equals(this.getClass().getName())) {
646                 return iName.compareTo( getNameInOtherClassLoader(other) );
647             }
648             throw new ClassCastException(
649                     "Different enum class '" + ClassUtils.getShortClassName(other.getClass()) + "'");
650         }
651         return iName.compareTo(((Enum) other).iName);
652     }
653 
654     /**
655      * <p>Use reflection to return an objects class name.</p>
656      *
657      * @param other The object to determine the class name for
658      * @return The class name
659      */
660     private String getNameInOtherClassLoader(Object other) {
661         try {
662             Method mth = other.getClass().getMethod("getName", null);
663             String name = (String) mth.invoke(other, null);
664             return name;
665         } catch (NoSuchMethodException e) {
666             // ignore - should never happen
667         } catch (IllegalAccessException e) {
668             // ignore - should never happen
669         } catch (InvocationTargetException e) {
670             // ignore - should never happen
671         }
672         throw new IllegalStateException("This should not happen");
673     }
674 
675     /**
676      * <p>Human readable description of this Enum item.</p>
677      * 
678      * @return String in the form <code>type[name]</code>, for example:
679      * <code>Color[Red]</code>. Note that the package name is stripped from
680      * the type name.
681      */
682     public String toString() {
683         if (iToString == null) {
684             String shortName = ClassUtils.getShortClassName(getEnumClass());
685             iToString = shortName + "[" + getName() + "]";
686         }
687         return iToString;
688     }
689     
690 }