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 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    }