/* * Copyright 2002-2004 The Apache Software Foundation. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.commons.lang.enums; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.apache.commons.lang.ClassUtils; import org.apache.commons.lang.StringUtils; /** *

* Abstract superclass for type-safe enums. *

* *

* One feature of the C programming language lacking in Java is enumerations. * The C implementation based on ints was poor and open to abuse. The original * Java recommendation and most of the JDK also uses int constants. It has been * recognised however that a more robust type-safe class-based solution can be * designed. This class follows the basic Java type-safe enumeration pattern. *

* *

* NOTE:Due to the way in which Java ClassLoaders work, comparing Enum * objects should always be done using equals(), not * ==. The equals() method will try == first so in most cases the * effect is the same. *

* *

* Of course, if you actually want (or don't mind) Enums in different class * loaders being non-equal, then you can use ==. *

* *

Simple Enums

* *

* To use this class, it must be subclassed. For example: *

* *
 * public final class ColorEnum extends Enum {
 *     public static final ColorEnum RED = new ColorEnum("Red");
 *     public static final ColorEnum GREEN = new ColorEnum("Green");
 *     public static final ColorEnum BLUE = new ColorEnum("Blue");
 * 
 *     private ColorEnum(String color) {
 * 	super(color);
 *     }
 * 
 *     public static ColorEnum getEnum(String color) {
 * 	return (ColorEnum) getEnum(ColorEnum.class, color);
 *     }
 * 
 *     public static Map getEnumMap() {
 * 	return getEnumMap(ColorEnum.class);
 *     }
 * 
 *     public static List getEnumList() {
 * 	return getEnumList(ColorEnum.class);
 *     }
 * 
 *     public static Iterator iterator() {
 * 	return iterator(ColorEnum.class);
 *     }
 * }
 * 
* *

* As shown, each enum has a name. This can be accessed using * getName. *

* *

* The getEnum and iterator methods are recommended. * Unfortunately, Java restrictions require these to be coded as shown in each * subclass. An alternative choice is to use the {@link EnumUtils} class. *

* *

Subclassed Enums

*

* A hierarchy of Enum classes can be built. In this case, the superclass is * unaffected by the addition of subclasses (as per normal Java). The subclasses * may add additional Enum constants of the type of the superclass. The * query methods on the subclass will return all of the Enum constants from the * superclass and subclass. *

* *
 * public final class ExtraColorEnum extends ColorEnum {
 *     // NOTE: Color enum declared above is final, change that to get this
 *     // example to compile.
 *     public static final ColorEnum YELLOW = new ExtraColorEnum("Yellow");
 * 
 *     private ExtraColorEnum(String color) {
 * 	super(color);
 *     }
 * 
 *     public static ColorEnum getEnum(String color) {
 * 	return (ColorEnum) getEnum(ExtraColorEnum.class, color);
 *     }
 * 
 *     public static Map getEnumMap() {
 * 	return getEnumMap(ExtraColorEnum.class);
 *     }
 * 
 *     public static List getEnumList() {
 * 	return getEnumList(ExtraColorEnum.class);
 *     }
 * 
 *     public static Iterator iterator() {
 * 	return iterator(ExtraColorEnum.class);
 *     }
 * }
 * 
* *

* This example will return RED, GREEN, BLUE, YELLOW from the List and iterator * methods in that order. The RED, GREEN and BLUE instances will be the same * (==) as those from the superclass ColorEnum. Note that YELLOW is declared as * a ColorEnum and not an ExtraColorEnum. *

* *

Functional Enums

* *

* The enums can have functionality by defining subclasses and overriding the * getEnumClass() method: *

* *
 *   public static final OperationEnum PLUS = new PlusOperation();
 *   private static final class PlusOperation extends OperationEnum {
 *     private PlusOperation() {
 *       super("Plus");
 *     }
 *     public int eval(int a, int b) {
 *       return (a + b);
 *     }
 *   }
 *   public static final OperationEnum MINUS = new MinusOperation();
 *   private static final class MinusOperation extends OperationEnum {
 *     private MinusOperation() {
 *       super("Minus");
 *     }
 *     public int eval(int a, int b) {
 *       return (a - b);
 *     }
 *   }
 *   private OperationEnum(String color) {
 *     super(color);
 *   }
 * 
 *   public final Class getEnumClass() {     // NOTE: new method!
 *     return OperationEnum.class;
 *   }
 *   public abstract double eval(double a, double b);
 * 
 *   public static OperationEnum getEnum(String name) {
 *     return (OperationEnum) getEnum(OperationEnum.class, name);
 *   }
 * 
 *   public static Map getEnumMap() {
 *     return getEnumMap(OperationEnum.class);
 *   }
 * 
 *   public static List getEnumList() {
 *     return getEnumList(OperationEnum.class);
 *   }
 * 
 *   public static Iterator iterator() {
 *     return iterator(OperationEnum.class);
 *   }
 * }
 * 
*

* The code above will work on JDK 1.2. If JDK1.3 and later is used, the * subclasses may be defined as anonymous. *

* *

Nested class Enums

* *

* Care must be taken with class loading when defining a static nested class for * enums. The static nested class can be loaded without the surrounding outer * class being loaded. This can result in an empty list/map/iterator being * returned. One solution is to define a static block that references the outer * class where the constants are defined. For example: *

* *
 * public final class Outer {
 *     public static final BWEnum BLACK = new BWEnum("Black");
 *     public static final BWEnum WHITE = new BWEnum("White");
 * 
 *     // static nested enum class
 *     public static final class BWEnum extends Enum {
 * 
 * 	static {
 * 	    // explicitly reference BWEnum class to force constants to load
 * 	    Object obj = Outer.BLACK;
 * 	}
 * 
 * 	// ... other methods omitted
 *     }
 * }
 * 
* *

* Although the above solves the problem, it is not recommended. The best * solution is to define the constants in the enum class, and hold references in * the outer class: * *

 * public final class Outer {
 *     public static final BWEnum BLACK = BWEnum.BLACK;
 *     public static final BWEnum WHITE = BWEnum.WHITE;
 * 
 *     // static nested enum class
 *     public static final class BWEnum extends Enum {
 * 	// only define constants in enum classes - private if desired
 * 	private static final BWEnum BLACK = new BWEnum("Black");
 * 	private static final BWEnum WHITE = new BWEnum("White");
 * 
 * 	// ... other methods omitted
 *     }
 * }
 * 
* *

* For more details, see the 'Nested' test cases. * * @deprecated Replaced by {@link org.apache.commons.lang.enums.Enum * org.apache.commons.lang.enums.Enum} and will be removed in * version 3.0. All classes in this package * net.sourceforge.fenixedu.net.sourceforge.fenixedu.are deprecated * and repackaged to {@link org.apache.commons.lang.enums} since * enum is a Java 1.5 keyword. * @see org.apache.commons.lang.enums.Enum * @author Apache Avalon project * @author Stephen Colebourne * @author Chris Webb * @author Mike Bowler * @since 1.0 * @version $Id$ */ public abstract class Enum implements Comparable, Serializable { /** Lang version 1.0.1 serial compatibility */ private static final long serialVersionUID = -487045951170455942L; // After discussion, the default size for HashMaps is used, as the // sizing algorithm changes across the JDK versions /** * An empty Map, as JDK1.2 didn't have an empty map. */ private static final Map EMPTY_MAP = Collections.unmodifiableMap(new HashMap(0)); /** * Map, key of class name, value of Entry. */ private static final Map cEnumClasses = new HashMap(); /** * The string representation of the Enum. */ private final String iName; /** * The hashcode representation of the Enum. */ private transient final int iHashCode; /** * The toString representation of the Enum. * * @since 2.0 */ protected transient String iToString = null; /** *

* Enable the iterator to retain the source code order. *

*/ private static class Entry { /** * Map of Enum name to Enum. */ final Map map = new HashMap(); /** * Map of Enum name to Enum. */ final Map unmodifiableMap = Collections.unmodifiableMap(map); /** * List of Enums in source code order. */ final List list = new ArrayList(25); /** * Map of Enum name to Enum. */ final List unmodifiableList = Collections.unmodifiableList(list); /** *

* Restrictive constructor. *

*/ private Entry() { } } /** *

* Constructor to add a new named item to the enumeration. *

* * @param name * the name of the enum object, must not be empty or * null * @throws IllegalArgumentException * if the name is null or an empty string * @throws IllegalArgumentException * if the getEnumClass() method returns a null or invalid Class */ protected Enum(String name) { super(); init(name); iName = name; iHashCode = 7 + getEnumClass().hashCode() + 3 * name.hashCode(); // cannot create toString here as subclasses may want to include other // data } /** * Initializes the enumeration. * * @param name * the enum name * @throws IllegalArgumentException * if the name is null or empty or duplicate * @throws IllegalArgumentException * if the enumClass is null or invalid */ private void init(String name) { if (StringUtils.isEmpty(name)) { throw new IllegalArgumentException("The Enum name must not be empty or null"); } Class enumClass = getEnumClass(); if (enumClass == null) { throw new IllegalArgumentException("getEnumClass() must not be null"); } Class cls = getClass(); boolean ok = false; while (cls != null && cls != Enum.class && cls != ValuedEnum.class) { if (cls == enumClass) { ok = true; break; } cls = cls.getSuperclass(); } if (ok == false) { throw new IllegalArgumentException("getEnumClass() must return a superclass of this class"); } // create entry Entry entry = (Entry) cEnumClasses.get(enumClass); if (entry == null) { entry = createEntry(enumClass); cEnumClasses.put(enumClass, entry); } if (entry.map.containsKey(name)) { throw new IllegalArgumentException("The Enum name must be unique, '" + name + "' has already been added"); } entry.map.put(name, this); entry.list.add(this); } /** *

* Handle the deserialization of the class to ensure that multiple copies * are not wastefully created, or illegal enum types created. *

* * @return the resolved object */ protected Object readResolve() { Entry entry = (Entry) cEnumClasses.get(getEnumClass()); if (entry == null) { return null; } return (Enum) entry.map.get(getName()); } // -------------------------------------------------------------------------- // ------ /** *

* Gets an Enum object by class and name. *

* * @param enumClass * the class of the Enum to get, must not be null * @param name * the name of the Enum to get, may be * null * @return the enum object, or null if the enum does not exist * @throws IllegalArgumentException * if the enum class is null */ protected static Enum getEnum(Class enumClass, String name) { Entry entry = getEntry(enumClass); if (entry == null) { return null; } return (Enum) entry.map.get(name); } /** *

* Gets the Map of Enum objects by name using the * Enum class. *

* *

* If the requested class has no enum objects an empty Map is * returned. *

* * @param enumClass * the class of the Enum to get, must not be * null * @return the enum object Map * @throws IllegalArgumentException * if the enum class is null * @throws IllegalArgumentException * if the enum class is not a subclass of Enum */ protected static Map getEnumMap(Class enumClass) { Entry entry = getEntry(enumClass); if (entry == null) { return EMPTY_MAP; } return entry.unmodifiableMap; } /** *

* Gets the List of Enum objects using the * Enum class. *

* *

* The list is in the order that the objects were created (source code * order). If the requested class has no enum objects an empty * List is returned. *

* * @param enumClass * the class of the Enum to get, must not be * null * @return the enum object Map * @throws IllegalArgumentException * if the enum class is null * @throws IllegalArgumentException * if the enum class is not a subclass of Enum */ protected static List getEnumList(Class enumClass) { Entry entry = getEntry(enumClass); if (entry == null) { return Collections.EMPTY_LIST; } return entry.unmodifiableList; } /** *

* Gets an Iterator over the Enum objects in an * Enum class. *

* *

* The Iterator is in the order that the objects were created * (source code order). If the requested class has no enum objects an empty * Iterator is returned. *

* * @param enumClass * the class of the Enum to get, must not be * null * @return an iterator of the Enum objects * @throws IllegalArgumentException * if the enum class is null * @throws IllegalArgumentException * if the enum class is not a subclass of Enum */ protected static Iterator iterator(Class enumClass) { return Enum.getEnumList(enumClass).iterator(); } // ----------------------------------------------------------------------- /** *

* Gets an Entry from the map of Enums. *

* * @param enumClass * the class of the Enum to get * @return the enum entry */ private static Entry getEntry(Class enumClass) { if (enumClass == null) { throw new IllegalArgumentException("The Enum Class must not be null"); } if (Enum.class.isAssignableFrom(enumClass) == false) { throw new IllegalArgumentException("The Class must be a subclass of Enum"); } Entry entry = (Entry) cEnumClasses.get(enumClass); return entry; } /** *

* Creates an Entry for storing the Enums. *

* *

* This accounts for subclassed Enums. *

* * @param enumClass * the class of the Enum to get * @return the enum entry */ private static Entry createEntry(Class enumClass) { Entry entry = new Entry(); Class cls = enumClass.getSuperclass(); while (cls != null && cls != Enum.class && cls != ValuedEnum.class) { Entry loopEntry = (Entry) cEnumClasses.get(cls); if (loopEntry != null) { entry.list.addAll(loopEntry.list); entry.map.putAll(loopEntry.map); break; // stop here, as this will already have had superclasses // added } cls = cls.getSuperclass(); } return entry; } // ----------------------------------------------------------------------- /** *

* Retrieve the name of this Enum item, set in the constructor. *

* * @return the String name of this Enum item */ public final String getName() { return iName; } /** *

* Retrieves the Class of this Enum item, set in the constructor. *

* *

* This is normally the same as getClass(), but for advanced * Enums may be different. If overridden, it must return a constant value. *

* * @return the Class of the enum * @since 2.0 */ public Class getEnumClass() { return getClass(); } /** *

* Tests for equality. *

* *

* Two Enum objects are considered equal if they have the same class names * and the same names. Identity is tested for first, so this method usually * runs fast. *

* *

* If the parameter is in a different class loader than this instance, * reflection is used to compare the names. *

* * @param other * the other object to compare for equality * @return true if the Enums are equal */ public final boolean equals(Object other) { if (other == this) { return true; } else if (other == null) { return false; } else if (other.getClass() == this.getClass()) { // Ok to do a class cast to Enum here since the test above // guarantee both // classes are in the same class loader. return iName.equals(((Enum) other).iName); } else { // This and other are in different class loaders, we must use // reflection. try { Method mth = other.getClass().getMethod("getName", (Class[]) null); String name = (String) mth.invoke(other, (Object[]) null); return iName.equals(name); } catch (NoSuchMethodException e) { // ignore - should never happen } catch (IllegalAccessException e) { // ignore - should never happen } catch (InvocationTargetException e) { // ignore - should never happen } return false; } } /** *

* Returns a suitable hashCode for the enumeration. *

* * @return a hashcode based on the name */ public final int hashCode() { return iHashCode; } /** *

* Tests for order. *

* *

* The default ordering is alphabetic by name, but this can be overridden by * subclasses. *

* * @see java.lang.Comparable#compareTo(Object) * @param other * the other object to compare to * @return -ve if this is less than the other object, +ve if greater than, * 0 of equal * @throws ClassCastException * if other is not an Enum * @throws NullPointerException * if other is null */ public int compareTo(Object other) { if (other == this) { return 0; } return iName.compareTo(((Enum) other).iName); } /** *

* Human readable description of this Enum item. *

* * @return String in the form type[name], for example: * Color[Red]. Note that the package * net.sourceforge.fenixedu.net.sourceforge.fenixedu.name is * stripped from the type name. */ public String toString() { if (iToString == null) { String shortName = ClassUtils.getShortClassName(getEnumClass()); iToString = shortName + "[" + getName() + "]"; } return iToString; } }