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.enum;
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     * @deprecated Replaced by {@link org.apache.commons.lang.enums.Enum org.apache.commons.lang.enums.Enum} 
230     *          and will be removed in version 3.0. All classes in this package are deprecated and repackaged to 
231     *          {@link org.apache.commons.lang.enums} since <code>enum</code> is a Java 1.5 keyword. 
232     * @see org.apache.commons.lang.enums.Enum
233     * @author Apache Avalon project
234     * @author Apache Software Foundation
235     * @author Chris Webb
236     * @author Mike Bowler
237     * @since 1.0
238     * @version $Id: Enum.java 912394 2010-02-21 20:16:22Z niallp $
239     */
240    public abstract class Enum implements Comparable, Serializable {
241    
242        /**
243         * Required for serialization support. Lang version 1.0.1 serial compatibility.
244         * 
245         * @see java.io.Serializable
246         */
247        private static final long serialVersionUID = -487045951170455942L;
248        
249        // After discussion, the default size for HashMaps is used, as the
250        // sizing algorithm changes across the JDK versions
251        /**
252         * An empty <code>Map</code>, as JDK1.2 didn't have an empty map.
253         */
254        private static final Map EMPTY_MAP = Collections.unmodifiableMap(new HashMap(0));
255        
256        /**
257         * <code>Map</code>, key of class name, value of <code>Entry</code>.
258         */
259        private static Map cEnumClasses
260            // LANG-334: To avoid exposing a mutating map,
261            // we copy it each time we add to it. This is cheaper than
262            // using a synchronized map since we are almost entirely reads
263            = new WeakHashMap();
264        
265        /**
266         * The string representation of the Enum.
267         */
268        private final String iName;
269        
270        /**
271         * The hashcode representation of the Enum.
272         */
273        private transient final int iHashCode;
274        
275        /**
276         * The toString representation of the Enum.
277         * @since 2.0
278         */
279        protected transient String iToString = null;
280    
281        /**
282         * <p>Enable the iterator to retain the source code order.</p>
283         */
284        private static class Entry {
285            /**
286             * Map of Enum name to Enum.
287             */
288            final Map map = new HashMap();
289            /**
290             * Map of Enum name to Enum.
291             */
292            final Map unmodifiableMap = Collections.unmodifiableMap(map);
293            /**
294             * List of Enums in source code order.
295             */
296            final List list = new ArrayList(25);
297            /**
298             * Map of Enum name to Enum.
299             */
300            final List unmodifiableList = Collections.unmodifiableList(list);
301    
302            /**
303             * <p>Restrictive constructor.</p>
304             */
305            protected Entry() {
306                super();
307            }
308        }
309    
310        /**
311         * <p>Constructor to add a new named item to the enumeration.</p>
312         *
313         * @param name  the name of the enum object,
314         *  must not be empty or <code>null</code>
315         * @throws IllegalArgumentException if the name is <code>null</code>
316         *  or an empty string
317         * @throws IllegalArgumentException if the getEnumClass() method returns
318         *  a null or invalid Class
319         */
320        protected Enum(String name) {
321            super();
322            init(name);
323            iName = name;
324            iHashCode = 7 + getEnumClass().hashCode() + 3 * name.hashCode();
325            // cannot create toString here as subclasses may want to include other data
326        }
327    
328        /**
329         * Initializes the enumeration.
330         * 
331         * @param name  the enum name
332         * @throws IllegalArgumentException if the name is null or empty or duplicate
333         * @throws IllegalArgumentException if the enumClass is null or invalid
334         */
335        private void init(String name) {
336            if (StringUtils.isEmpty(name)) {
337                throw new IllegalArgumentException("The Enum name must not be empty or null");
338            }
339            
340            Class enumClass = getEnumClass();
341            if (enumClass == null) {
342                throw new IllegalArgumentException("getEnumClass() must not be null");
343            }
344            Class cls = getClass();
345            boolean ok = false;
346            while (cls != null && cls != Enum.class && cls != ValuedEnum.class) {
347                if (cls == enumClass) {
348                    ok = true;
349                    break;
350                }
351                cls = cls.getSuperclass();
352            }
353            if (ok == false) {
354                throw new IllegalArgumentException("getEnumClass() must return a superclass of this class");
355            }
356    
357            Entry entry;
358            synchronized( Enum.class ) { // LANG-334
359                // create entry
360                entry = (Entry) cEnumClasses.get(enumClass);
361                if (entry == null) {
362                    entry = createEntry(enumClass);
363                    Map myMap = new WeakHashMap( ); // we avoid the (Map) constructor to achieve JDK 1.2 support
364                    myMap.putAll( cEnumClasses );
365                    myMap.put(enumClass, entry);
366                    cEnumClasses = myMap;
367                }
368            }
369            if (entry.map.containsKey(name)) {
370                throw new IllegalArgumentException("The Enum name must be unique, '" + name + "' has already been added");
371            }
372            entry.map.put(name, this);
373            entry.list.add(this);
374        }
375    
376        /**
377         * <p>Handle the deserialization of the class to ensure that multiple
378         * copies are not wastefully created, or illegal enum types created.</p>
379         *
380         * @return the resolved object
381         */
382        protected Object readResolve() {
383            Entry entry = (Entry) cEnumClasses.get(getEnumClass());
384            if (entry == null) {
385                return null;
386            }
387            return entry.map.get(getName());
388        }
389        
390        //--------------------------------------------------------------------------------
391    
392        /**
393         * <p>Gets an <code>Enum</code> object by class and name.</p>
394         * 
395         * @param enumClass  the class of the Enum to get, must not
396         *  be <code>null</code>
397         * @param name  the name of the <code>Enum</code> to get,
398         *  may be <code>null</code>
399         * @return the enum object, or <code>null</code> if the enum does not exist
400         * @throws IllegalArgumentException if the enum class
401         *  is <code>null</code>
402         */
403        protected static Enum getEnum(Class enumClass, String name) {
404            Entry entry = getEntry(enumClass);
405            if (entry == null) {
406                return null;
407            }
408            return (Enum) entry.map.get(name);
409        }
410    
411        /**
412         * <p>Gets the <code>Map</code> of <code>Enum</code> objects by
413         * name using the <code>Enum</code> class.</p>
414         *
415         * <p>If the requested class has no enum objects an empty
416         * <code>Map</code> is returned.</p>
417         * 
418         * @param enumClass  the class of the <code>Enum</code> to get,
419         *  must not be <code>null</code>
420         * @return the enum object Map
421         * @throws IllegalArgumentException if the enum class is <code>null</code>
422         * @throws IllegalArgumentException if the enum class is not a subclass of Enum
423         */
424        protected static Map getEnumMap(Class enumClass) {
425            Entry entry = getEntry(enumClass);
426            if (entry == null) {
427                return EMPTY_MAP;
428            }
429            return entry.unmodifiableMap;
430        }
431    
432        /**
433         * <p>Gets the <code>List</code> of <code>Enum</code> objects using the
434         * <code>Enum</code> class.</p>
435         *
436         * <p>The list is in the order that the objects were created (source code order).
437         * If the requested class has no enum objects an empty <code>List</code> is
438         * returned.</p>
439         * 
440         * @param enumClass  the class of the <code>Enum</code> to get,
441         *  must not be <code>null</code>
442         * @return the enum object Map
443         * @throws IllegalArgumentException if the enum class is <code>null</code>
444         * @throws IllegalArgumentException if the enum class is not a subclass of Enum
445         */
446        protected static List getEnumList(Class enumClass) {
447            Entry entry = getEntry(enumClass);
448            if (entry == null) {
449                return Collections.EMPTY_LIST;
450            }
451            return entry.unmodifiableList;
452        }
453    
454        /**
455         * <p>Gets an <code>Iterator</code> over the <code>Enum</code> objects in
456         * an <code>Enum</code> class.</p>
457         *
458         * <p>The <code>Iterator</code> is in the order that the objects were
459         * created (source code order). If the requested class has no enum
460         * objects an empty <code>Iterator</code> is returned.</p>
461         * 
462         * @param enumClass  the class of the <code>Enum</code> to get,
463         *  must not be <code>null</code>
464         * @return an iterator of the Enum objects
465         * @throws IllegalArgumentException if the enum class is <code>null</code>
466         * @throws IllegalArgumentException if the enum class is not a subclass of Enum
467         */
468        protected static Iterator iterator(Class enumClass) {
469            return Enum.getEnumList(enumClass).iterator();
470        }
471    
472        //-----------------------------------------------------------------------
473        /**
474         * <p>Gets an <code>Entry</code> from the map of Enums.</p>
475         * 
476         * @param enumClass  the class of the <code>Enum</code> to get
477         * @return the enum entry
478         */
479        private static Entry getEntry(Class enumClass) {
480            if (enumClass == null) {
481                throw new IllegalArgumentException("The Enum Class must not be null");
482            }
483            if (Enum.class.isAssignableFrom(enumClass) == false) {
484                throw new IllegalArgumentException("The Class must be a subclass of Enum");
485            }
486            Entry entry = (Entry) cEnumClasses.get(enumClass);
487    
488            if (entry == null) {
489                try {
490                    // LANG-76 - try to force class initialization for JDK 1.5+
491                    Class.forName(enumClass.getName(), true, enumClass.getClassLoader());
492                    entry = (Entry) cEnumClasses.get(enumClass);
493                } catch (Exception e) {
494                    // Ignore
495                }
496            }
497    
498            return entry;
499        }
500        
501        /**
502         * <p>Creates an <code>Entry</code> for storing the Enums.</p>
503         *
504         * <p>This accounts for subclassed Enums.</p>
505         * 
506         * @param enumClass  the class of the <code>Enum</code> to get
507         * @return the enum entry
508         */
509        private static Entry createEntry(Class enumClass) {
510            Entry entry = new Entry();
511            Class cls = enumClass.getSuperclass();
512            while (cls != null && cls != Enum.class && cls != ValuedEnum.class) {
513                Entry loopEntry = (Entry) cEnumClasses.get(cls);
514                if (loopEntry != null) {
515                    entry.list.addAll(loopEntry.list);
516                    entry.map.putAll(loopEntry.map);
517                    break;  // stop here, as this will already have had superclasses added
518                }
519                cls = cls.getSuperclass();
520            }
521            return entry;
522        }
523        
524        //-----------------------------------------------------------------------
525        /**
526         * <p>Retrieve the name of this Enum item, set in the constructor.</p>
527         * 
528         * @return the <code>String</code> name of this Enum item
529         */
530        public final String getName() {
531            return iName;
532        }
533    
534        /**
535         * <p>Retrieves the Class of this Enum item, set in the constructor.</p>
536         * 
537         * <p>This is normally the same as <code>getClass()</code>, but for
538         * advanced Enums may be different. If overridden, it must return a
539         * constant value.</p>
540         * 
541         * @return the <code>Class</code> of the enum
542         * @since 2.0
543         */
544        public Class getEnumClass() {
545            return getClass();
546        }
547    
548        /**
549         * <p>Tests for equality.</p>
550         *
551         * <p>Two Enum objects are considered equal
552         * if they have the same class names and the same names.
553         * Identity is tested for first, so this method usually runs fast.</p>
554         * 
555         * <p>If the parameter is in a different class loader than this instance,
556         * reflection is used to compare the names.</p>
557         *
558         * @param other  the other object to compare for equality
559         * @return <code>true</code> if the Enums are equal
560         */
561        public final boolean equals(Object other) {
562            if (other == this) {
563                return true;
564            } else if (other == null) {
565                return false;
566            } else if (other.getClass() == this.getClass()) {
567                // Ok to do a class cast to Enum here since the test above
568                // guarantee both
569                // classes are in the same class loader.
570                return iName.equals(((Enum) other).iName);
571            } else {
572                // This and other are in different class loaders, we must check indirectly
573                if (other.getClass().getName().equals(this.getClass().getName()) == false) {
574                    return false;
575                }
576                return iName.equals( getNameInOtherClassLoader(other) );
577            }
578        }
579        
580        /**
581         * <p>Returns a suitable hashCode for the enumeration.</p>
582         *
583         * @return a hashcode based on the name
584         */
585        public final int hashCode() {
586            return iHashCode;
587        }
588    
589        /**
590         * <p>Tests for order.</p>
591         *
592         * <p>The default ordering is alphabetic by name, but this
593         * can be overridden by subclasses.</p>
594         * 
595         * <p>If the parameter is in a different class loader than this instance,
596         * reflection is used to compare the names.</p>
597         *
598         * @see java.lang.Comparable#compareTo(Object)
599         * @param other  the other object to compare to
600         * @return -ve if this is less than the other object, +ve if greater
601         *  than, <code>0</code> of equal
602         * @throws ClassCastException if other is not an Enum
603         * @throws NullPointerException if other is <code>null</code>
604         */
605        public int compareTo(Object other) {
606            if (other == this) {
607                return 0;
608            }
609            if (other.getClass() != this.getClass()) {
610                if (other.getClass().getName().equals(this.getClass().getName())) {
611                    return iName.compareTo( getNameInOtherClassLoader(other) );
612                }
613                throw new ClassCastException(
614                        "Different enum class '" + ClassUtils.getShortClassName(other.getClass()) + "'");
615            }
616            return iName.compareTo(((Enum) other).iName);
617        }
618    
619        /**
620         * <p>Use reflection to return an objects class name.</p>
621         *
622         * @param other The object to determine the class name for
623         * @return The class name
624         */
625        private String getNameInOtherClassLoader(Object other) {
626            try {
627                Method mth = other.getClass().getMethod("getName", null);
628                String name = (String) mth.invoke(other, null);
629                return name;
630            } catch (NoSuchMethodException e) {
631                // ignore - should never happen
632            } catch (IllegalAccessException e) {
633                // ignore - should never happen
634            } catch (InvocationTargetException e) {
635                // ignore - should never happen
636            }
637            throw new IllegalStateException("This should not happen");
638        }
639    
640        /**
641         * <p>Human readable description of this Enum item.</p>
642         * 
643         * @return String in the form <code>type[name]</code>, for example:
644         * <code>Color[Red]</code>. Note that the package name is stripped from
645         * the type name.
646         */
647        public String toString() {
648            if (iToString == null) {
649                String shortName = ClassUtils.getShortClassName(getEnumClass());
650                iToString = shortName + "[" + getName() + "]";
651            }
652            return iToString;
653        }
654        
655    }