/* * 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 ==
.
*
* 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.
*
* 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. *
* *
* 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. *
* ** 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.
*
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.
*
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.
*
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.
*
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.
*
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 theEnum
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 theString
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.
*
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 * @returntrue
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 formtype[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;
}
}