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 Stephen Colebourne
235     * @author Chris Webb
236     * @author Mike Bowler
237     * @since 1.0
238     * @version $Id: Enum.java 637362 2008-03-15 06:09:41Z bayard $
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            return entry;
488        }
489        
490        /**
491         * <p>Creates an <code>Entry</code> for storing the Enums.</p>
492         *
493         * <p>This accounts for subclassed Enums.</p>
494         * 
495         * @param enumClass  the class of the <code>Enum</code> to get
496         * @return the enum entry
497         */
498        private static Entry createEntry(Class enumClass) {
499            Entry entry = new Entry();
500            Class cls = enumClass.getSuperclass();
501            while (cls != null && cls != Enum.class && cls != ValuedEnum.class) {
502                Entry loopEntry = (Entry) cEnumClasses.get(cls);
503                if (loopEntry != null) {
504                    entry.list.addAll(loopEntry.list);
505                    entry.map.putAll(loopEntry.map);
506                    break;  // stop here, as this will already have had superclasses added
507                }
508                cls = cls.getSuperclass();
509            }
510            return entry;
511        }
512        
513        //-----------------------------------------------------------------------
514        /**
515         * <p>Retrieve the name of this Enum item, set in the constructor.</p>
516         * 
517         * @return the <code>String</code> name of this Enum item
518         */
519        public final String getName() {
520            return iName;
521        }
522    
523        /**
524         * <p>Retrieves the Class of this Enum item, set in the constructor.</p>
525         * 
526         * <p>This is normally the same as <code>getClass()</code>, but for
527         * advanced Enums may be different. If overridden, it must return a
528         * constant value.</p>
529         * 
530         * @return the <code>Class</code> of the enum
531         * @since 2.0
532         */
533        public Class getEnumClass() {
534            return getClass();
535        }
536    
537        /**
538         * <p>Tests for equality.</p>
539         *
540         * <p>Two Enum objects are considered equal
541         * if they have the same class names and the same names.
542         * Identity is tested for first, so this method usually runs fast.</p>
543         * 
544         * <p>If the parameter is in a different class loader than this instance,
545         * reflection is used to compare the names.</p>
546         *
547         * @param other  the other object to compare for equality
548         * @return <code>true</code> if the Enums are equal
549         */
550        public final boolean equals(Object other) {
551            if (other == this) {
552                return true;
553            } else if (other == null) {
554                return false;
555            } else if (other.getClass() == this.getClass()) {
556                // Ok to do a class cast to Enum here since the test above
557                // guarantee both
558                // classes are in the same class loader.
559                return iName.equals(((Enum) other).iName);
560            } else {
561                // This and other are in different class loaders, we must check indirectly
562                if (other.getClass().getName().equals(this.getClass().getName()) == false) {
563                    return false;
564                }
565                return iName.equals( getNameInOtherClassLoader(other) );
566            }
567        }
568        
569        /**
570         * <p>Returns a suitable hashCode for the enumeration.</p>
571         *
572         * @return a hashcode based on the name
573         */
574        public final int hashCode() {
575            return iHashCode;
576        }
577    
578        /**
579         * <p>Tests for order.</p>
580         *
581         * <p>The default ordering is alphabetic by name, but this
582         * can be overridden by subclasses.</p>
583         * 
584         * <p>If the parameter is in a different class loader than this instance,
585         * reflection is used to compare the names.</p>
586         *
587         * @see java.lang.Comparable#compareTo(Object)
588         * @param other  the other object to compare to
589         * @return -ve if this is less than the other object, +ve if greater
590         *  than, <code>0</code> of equal
591         * @throws ClassCastException if other is not an Enum
592         * @throws NullPointerException if other is <code>null</code>
593         */
594        public int compareTo(Object other) {
595            if (other == this) {
596                return 0;
597            }
598            if (other.getClass() != this.getClass()) {
599                if (other.getClass().getName().equals(this.getClass().getName())) {
600                    return iName.compareTo( getNameInOtherClassLoader(other) );
601                }
602                throw new ClassCastException(
603                        "Different enum class '" + ClassUtils.getShortClassName(other.getClass()) + "'");
604            }
605            return iName.compareTo(((Enum) other).iName);
606        }
607    
608        /**
609         * <p>Use reflection to return an objects class name.</p>
610         *
611         * @param other The object to determine the class name for
612         * @return The class name
613         */
614        private String getNameInOtherClassLoader(Object other) {
615            try {
616                Method mth = other.getClass().getMethod("getName", null);
617                String name = (String) mth.invoke(other, null);
618                return name;
619            } catch (NoSuchMethodException e) {
620                // ignore - should never happen
621            } catch (IllegalAccessException e) {
622                // ignore - should never happen
623            } catch (InvocationTargetException e) {
624                // ignore - should never happen
625            }
626            throw new IllegalStateException("This should not happen");
627        }
628    
629        /**
630         * <p>Human readable description of this Enum item.</p>
631         * 
632         * @return String in the form <code>type[name]</code>, for example:
633         * <code>Color[Red]</code>. Note that the package name is stripped from
634         * the type name.
635         */
636        public String toString() {
637            if (iToString == null) {
638                String shortName = ClassUtils.getShortClassName(getEnumClass());
639                iToString = shortName + "[" + getName() + "]";
640            }
641            return iToString;
642        }
643        
644    }