All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.commons.lang.enums.Enum Maven / Gradle / Ivy

There is a newer version: 4.0.1
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 java.util.WeakHashMap;

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. * *

Lang Enums and Java 5.0 Enums

* *

Enums were added to Java in Java 5.0. The main differences between Lang's * implementation and the new official JDK implementation are:

*
    *
  • The standard Enum is a not just a superclass, but is a type baked into the * language.
  • *
  • The standard Enum does not support extension, so the standard methods that * are provided in the Lang enum are not available.
  • *
  • Lang mandates a String name, whereas the standard Enum uses the class * name as its name. getName() changes to name().
  • *
* *

Generally people should use the standard Enum. Migrating from the Lang * enum to the standard Enum is not as easy as it might be due to the lack of * class inheritence in standard Enums. This means that it's not possible * to provide a 'super-enum' which could provide the same utility methods * that the Lang enum does. The following utility class is a Java 5.0 * version of our EnumUtils class and provides those utility methods.

* *
 * import java.util.*;
 * 
 * public class EnumUtils {
 * 
 *   public static Enum getEnum(Class enumClass, String token) {
 *     return Enum.valueOf(enumClass, token);
 *   }
 * 
 *   public static Map getEnumMap(Class enumClass) {
 *     HashMap map = new HashMap();
 *     Iterator itr = EnumUtils.iterator(enumClass);
 *     while(itr.hasNext()) {
 *       Enum enm = (Enum) itr.next();
 *       map.put( enm.name(), enm );
 *     }
 *     return map;
 *   }
 * 
 *   public static List getEnumList(Class enumClass) {
 *     return new ArrayList( EnumSet.allOf(enumClass) );
 *   }
 * 
 *   public static Iterator iterator(Class enumClass) {
 *     return EnumUtils.getEnumList(enumClass).iterator();
 *   }
 * }
 * 
* * @author Apache Avalon project * @author Apache Software Foundation * @author Chris Webb * @author Mike Bowler * @author Matthias Eichel * @since 2.1 (class existed in enum package from v1.0) * @version $Id: Enum.java 912394 2010-02-21 20:16:22Z niallp $ */ public abstract class Enum implements Comparable, Serializable { /** * Required for serialization support. * * @see java.io.Serializable */ 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 Map cEnumClasses // LANG-334: To avoid exposing a mutating map, // we copy it each time we add to it. This is cheaper than // using a synchronized map since we are almost entirely reads = new WeakHashMap(); /** * 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.

*/ protected Entry() { super(); } } /** *

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"); } Entry entry; synchronized( Enum.class ) { // LANG-334 // create entry entry = (Entry) cEnumClasses.get(enumClass); if (entry == null) { entry = createEntry(enumClass); Map myMap = new WeakHashMap( ); // we avoid the (Map) constructor to achieve JDK 1.2 support myMap.putAll( cEnumClasses ); myMap.put(enumClass, entry); cEnumClasses = myMap; } } 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 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); if (entry == null) { try { // LANG-76 - try to force class initialization for JDK 1.5+ Class.forName(enumClass.getName(), true, enumClass.getClassLoader()); entry = (Entry) cEnumClasses.get(enumClass); } catch (Exception e) { // Ignore } } 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 check indirectly if (other.getClass().getName().equals(this.getClass().getName()) == false) { return false; } return iName.equals( getNameInOtherClassLoader(other) ); } } /** *

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.

* *

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

* * @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; } if (other.getClass() != this.getClass()) { if (other.getClass().getName().equals(this.getClass().getName())) { return iName.compareTo( getNameInOtherClassLoader(other) ); } throw new ClassCastException( "Different enum class '" + ClassUtils.getShortClassName(other.getClass()) + "'"); } return iName.compareTo(((Enum) other).iName); } /** *

Use reflection to return an objects class name.

* * @param other The object to determine the class name for * @return The class name */ private String getNameInOtherClassLoader(Object other) { try { Method mth = other.getClass().getMethod("getName", null); String name = (String) mth.invoke(other, null); return name; } catch (NoSuchMethodException e) { // ignore - should never happen } catch (IllegalAccessException e) { // ignore - should never happen } catch (InvocationTargetException e) { // ignore - should never happen } throw new IllegalStateException("This should not happen"); } /** *

Human readable description of this Enum item.

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




© 2015 - 2024 Weber Informatics LLC | Privacy Policy