    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    }