001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     * 
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     * 
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.lang.enums;
018    
019    import java.io.Serializable;
020    import java.lang.reflect.InvocationTargetException;
021    import java.lang.reflect.Method;
022    import java.util.ArrayList;
023    import java.util.Collections;
024    import java.util.HashMap;
025    import java.util.Iterator;
026    import java.util.List;
027    import java.util.Map;
028    import java.util.WeakHashMap;
029    
030    import org.apache.commons.lang.ClassUtils;
031    import org.apache.commons.lang.StringUtils;
032    
033    /**
034     * <p>Abstract superclass for type-safe enums.</p>
035     *
036     * <p>One feature of the C programming language lacking in Java is enumerations. The
037     * C implementation based on ints was poor and open to abuse. The original Java
038     * recommendation and most of the JDK also uses int constants. It has been recognised
039     * however that a more robust type-safe class-based solution can be designed. This
040     * class follows the basic Java type-safe enumeration pattern.</p>
041     *
042     * <p><em>NOTE:</em> Due to the way in which Java ClassLoaders work, comparing
043     * Enum objects should always be done using <code>equals()</code>, not <code>==</code>.
044     * The equals() method will try == first so in most cases the effect is the same.</p>
045     * 
046     * <p>Of course, if you actually want (or don't mind) Enums in different class
047     * loaders being non-equal, then you can use <code>==</code>.</p>
048     * 
049     * <h4>Simple Enums</h4>
050     *
051     * <p>To use this class, it must be subclassed. For example:</p>
052     *
053     * <pre>
054     * public final class ColorEnum extends Enum {
055     *   public static final ColorEnum RED = new ColorEnum("Red");
056     *   public static final ColorEnum GREEN = new ColorEnum("Green");
057     *   public static final ColorEnum BLUE = new ColorEnum("Blue");
058     *
059     *   private ColorEnum(String color) {
060     *     super(color);
061     *   }
062     * 
063     *   public static ColorEnum getEnum(String color) {
064     *     return (ColorEnum) getEnum(ColorEnum.class, color);
065     *   }
066     * 
067     *   public static Map getEnumMap() {
068     *     return getEnumMap(ColorEnum.class);
069     *   }
070     * 
071     *   public static List getEnumList() {
072     *     return getEnumList(ColorEnum.class);
073     *   }
074     * 
075     *   public static Iterator iterator() {
076     *     return iterator(ColorEnum.class);
077     *   }
078     * }
079     * </pre>
080     *
081     * <p>As shown, each enum has a name. This can be accessed using <code>getName</code>.</p>
082     *
083     * <p>The <code>getEnum</code> and <code>iterator</code> methods are recommended.
084     * Unfortunately, Java restrictions require these to be coded as shown in each subclass.
085     * An alternative choice is to use the {@link EnumUtils} class.</p>
086     * 
087     * <h4>Subclassed Enums</h4>
088     * <p>A hierarchy of Enum classes can be built. In this case, the superclass is
089     * unaffected by the addition of subclasses (as per normal Java). The subclasses
090     * may add additional Enum constants <em>of the type of the superclass</em>. The
091     * query methods on the subclass will return all of the Enum constants from the
092     * superclass and subclass.</p>
093     *
094     * <pre>
095     * public final class ExtraColorEnum extends ColorEnum {
096     *   // NOTE: Color enum declared above is final, change that to get this
097     *   // example to compile.
098     *   public static final ColorEnum YELLOW = new ExtraColorEnum("Yellow");
099     *
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 Apache Software Foundation
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 912394 2010-02-21 20:16:22Z niallp $
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    
534            if (entry == null) {
535                try {
536                    // LANG-76 - try to force class initialization for JDK 1.5+
537                    Class.forName(enumClass.getName(), true, enumClass.getClassLoader());
538                    entry = (Entry) cEnumClasses.get(enumClass);
539                } catch (Exception e) {
540                    // Ignore
541                }
542            }
543    
544            return entry;
545        }
546        
547        /**
548         * <p>Creates an <code>Entry</code> for storing the Enums.</p>
549         *
550         * <p>This accounts for subclassed Enums.</p>
551         * 
552         * @param enumClass  the class of the <code>Enum</code> to get
553         * @return the enum entry
554         */
555        private static Entry createEntry(Class enumClass) {
556            Entry entry = new Entry();
557            Class cls = enumClass.getSuperclass();
558            while (cls != null && cls != Enum.class && cls != ValuedEnum.class) {
559                Entry loopEntry = (Entry) cEnumClasses.get(cls);
560                if (loopEntry != null) {
561                    entry.list.addAll(loopEntry.list);
562                    entry.map.putAll(loopEntry.map);
563                    break;  // stop here, as this will already have had superclasses added
564                }
565                cls = cls.getSuperclass();
566            }
567            return entry;
568        }
569        
570        //-----------------------------------------------------------------------
571        /**
572         * <p>Retrieve the name of this Enum item, set in the constructor.</p>
573         * 
574         * @return the <code>String</code> name of this Enum item
575         */
576        public final String getName() {
577            return iName;
578        }
579    
580        /**
581         * <p>Retrieves the Class of this Enum item, set in the constructor.</p>
582         * 
583         * <p>This is normally the same as <code>getClass()</code>, but for
584         * advanced Enums may be different. If overridden, it must return a
585         * constant value.</p>
586         * 
587         * @return the <code>Class</code> of the enum
588         * @since 2.0
589         */
590        public Class getEnumClass() {
591            return getClass();
592        }
593    
594        /**
595         * <p>Tests for equality.</p>
596         *
597         * <p>Two Enum objects are considered equal
598         * if they have the same class names and the same names.
599         * Identity is tested for first, so this method usually runs fast.</p>
600         * 
601         * <p>If the parameter is in a different class loader than this instance,
602         * reflection is used to compare the names.</p>
603         *
604         * @param other  the other object to compare for equality
605         * @return <code>true</code> if the Enums are equal
606         */
607        public final boolean equals(Object other) {
608            if (other == this) {
609                return true;
610            } else if (other == null) {
611                return false;
612            } else if (other.getClass() == this.getClass()) {
613                // Ok to do a class cast to Enum here since the test above
614                // guarantee both
615                // classes are in the same class loader.
616                return iName.equals(((Enum) other).iName);
617            } else {
618                // This and other are in different class loaders, we must check indirectly
619                if (other.getClass().getName().equals(this.getClass().getName()) == false) {
620                    return false;
621                }
622                return iName.equals( getNameInOtherClassLoader(other) );
623            }
624        }
625        
626        /**
627         * <p>Returns a suitable hashCode for the enumeration.</p>
628         *
629         * @return a hashcode based on the name
630         */
631        public final int hashCode() {
632            return iHashCode;
633        }
634    
635        /**
636         * <p>Tests for order.</p>
637         *
638         * <p>The default ordering is alphabetic by name, but this
639         * can be overridden by subclasses.</p>
640         * 
641         * <p>If the parameter is in a different class loader than this instance,
642         * reflection is used to compare the names.</p>
643         *
644         * @see java.lang.Comparable#compareTo(Object)
645         * @param other  the other object to compare to
646         * @return -ve if this is less than the other object, +ve if greater
647         *  than, <code>0</code> of equal
648         * @throws ClassCastException if other is not an Enum
649         * @throws NullPointerException if other is <code>null</code>
650         */
651        public int compareTo(Object other) {
652            if (other == this) {
653                return 0;
654            }
655            if (other.getClass() != this.getClass()) {
656                if (other.getClass().getName().equals(this.getClass().getName())) {
657                    return iName.compareTo( getNameInOtherClassLoader(other) );
658                }
659                throw new ClassCastException(
660                        "Different enum class '" + ClassUtils.getShortClassName(other.getClass()) + "'");
661            }
662            return iName.compareTo(((Enum) other).iName);
663        }
664    
665        /**
666         * <p>Use reflection to return an objects class name.</p>
667         *
668         * @param other The object to determine the class name for
669         * @return The class name
670         */
671        private String getNameInOtherClassLoader(Object other) {
672            try {
673                Method mth = other.getClass().getMethod("getName", null);
674                String name = (String) mth.invoke(other, null);
675                return name;
676            } catch (NoSuchMethodException e) {
677                // ignore - should never happen
678            } catch (IllegalAccessException e) {
679                // ignore - should never happen
680            } catch (InvocationTargetException e) {
681                // ignore - should never happen
682            }
683            throw new IllegalStateException("This should not happen");
684        }
685    
686        /**
687         * <p>Human readable description of this Enum item.</p>
688         * 
689         * @return String in the form <code>type[name]</code>, for example:
690         * <code>Color[Red]</code>. Note that the package name is stripped from
691         * the type name.
692         */
693        public String toString() {
694            if (iToString == null) {
695                String shortName = ClassUtils.getShortClassName(getEnumClass());
696                iToString = shortName + "[" + getName() + "]";
697            }
698            return iToString;
699        }
700        
701    }