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.enums;
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.WeakHashMap;
026: import java.util.Iterator;
027: import java.util.List;
028: import java.util.Map;
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: * <h4>Lang Enums and Java 5.0 Enums</h4>
230: *
231: * <p>Enums were added to Java in Java 5.0. The main differences between Lang's
232: * implementation and the new official JDK implementation are: </p>
233: * <ul>
234: * <li>The standard Enum is a not just a superclass, but is a type baked into the
235: * language. </li>
236: * <li>The standard Enum does not support extension, so the standard methods that
237: * are provided in the Lang enum are not available. </li>
238: * <li>Lang mandates a String name, whereas the standard Enum uses the class
239: * name as its name. getName() changes to name(). </li>
240: * </ul>
241: *
242: * <p>Generally people should use the standard Enum. Migrating from the Lang
243: * enum to the standard Enum is not as easy as it might be due to the lack of
244: * class inheritence in standard Enums. This means that it's not possible
245: * to provide a 'super-enum' which could provide the same utility methods
246: * that the Lang enum does. The following utility class is a Java 5.0
247: * version of our EnumUtils class and provides those utility methods. </p>
248: *
249: * <pre>
250: * import java.util.*;
251: *
252: * public class EnumUtils {
253: *
254: * public static Enum getEnum(Class enumClass, String token) {
255: * return Enum.valueOf(enumClass, token);
256: * }
257: *
258: * public static Map getEnumMap(Class enumClass) {
259: * HashMap map = new HashMap();
260: * Iterator itr = EnumUtils.iterator(enumClass);
261: * while(itr.hasNext()) {
262: * Enum enm = (Enum) itr.next();
263: * map.put( enm.name(), enm );
264: * }
265: * return map;
266: * }
267: *
268: * public static List getEnumList(Class enumClass) {
269: * return new ArrayList( EnumSet.allOf(enumClass) );
270: * }
271: *
272: * public static Iterator iterator(Class enumClass) {
273: * return EnumUtils.getEnumList(enumClass).iterator();
274: * }
275: * }
276: * </pre>
277: *
278: * @author Apache Avalon project
279: * @author Stephen Colebourne
280: * @author Chris Webb
281: * @author Mike Bowler
282: * @author Matthias Eichel
283: * @since 2.1 (class existed in enum package from v1.0)
284: * @version $Id: Enum.java 466285 2006-10-20 22:36:21Z bayard $
285: */
286: public abstract class Enum implements Comparable, Serializable {
287:
288: /**
289: * Required for serialization support.
290: *
291: * @see java.io.Serializable
292: */
293: private static final long serialVersionUID = -487045951170455942L;
294:
295: // After discussion, the default size for HashMaps is used, as the
296: // sizing algorithm changes across the JDK versions
297: /**
298: * An empty <code>Map</code>, as JDK1.2 didn't have an empty map.
299: */
300: private static final Map EMPTY_MAP = Collections
301: .unmodifiableMap(new HashMap(0));
302:
303: /**
304: * <code>Map</code>, key of class name, value of <code>Entry</code>.
305: */
306: private static final Map cEnumClasses = new WeakHashMap();
307:
308: /**
309: * The string representation of the Enum.
310: */
311: private final String iName;
312:
313: /**
314: * The hashcode representation of the Enum.
315: */
316: private transient final int iHashCode;
317:
318: /**
319: * The toString representation of the Enum.
320: * @since 2.0
321: */
322: protected transient String iToString = null;
323:
324: /**
325: * <p>Enable the iterator to retain the source code order.</p>
326: */
327: private static class Entry {
328: /**
329: * Map of Enum name to Enum.
330: */
331: final Map map = new HashMap();
332: /**
333: * Map of Enum name to Enum.
334: */
335: final Map unmodifiableMap = Collections.unmodifiableMap(map);
336: /**
337: * List of Enums in source code order.
338: */
339: final List list = new ArrayList(25);
340: /**
341: * Map of Enum name to Enum.
342: */
343: final List unmodifiableList = Collections
344: .unmodifiableList(list);
345:
346: /**
347: * <p>Restrictive constructor.</p>
348: */
349: protected Entry() {
350: super ();
351: }
352: }
353:
354: /**
355: * <p>Constructor to add a new named item to the enumeration.</p>
356: *
357: * @param name the name of the enum object,
358: * must not be empty or <code>null</code>
359: * @throws IllegalArgumentException if the name is <code>null</code>
360: * or an empty string
361: * @throws IllegalArgumentException if the getEnumClass() method returns
362: * a null or invalid Class
363: */
364: protected Enum(String name) {
365: super ();
366: init(name);
367: iName = name;
368: iHashCode = 7 + getEnumClass().hashCode() + 3 * name.hashCode();
369: // cannot create toString here as subclasses may want to include other data
370: }
371:
372: /**
373: * Initializes the enumeration.
374: *
375: * @param name the enum name
376: * @throws IllegalArgumentException if the name is null or empty or duplicate
377: * @throws IllegalArgumentException if the enumClass is null or invalid
378: */
379: private void init(String name) {
380: if (StringUtils.isEmpty(name)) {
381: throw new IllegalArgumentException(
382: "The Enum name must not be empty or null");
383: }
384:
385: Class enumClass = getEnumClass();
386: if (enumClass == null) {
387: throw new IllegalArgumentException(
388: "getEnumClass() must not be null");
389: }
390: Class cls = getClass();
391: boolean ok = false;
392: while (cls != null && cls != Enum.class
393: && cls != ValuedEnum.class) {
394: if (cls == enumClass) {
395: ok = true;
396: break;
397: }
398: cls = cls.getSuperclass();
399: }
400: if (ok == false) {
401: throw new IllegalArgumentException(
402: "getEnumClass() must return a superclass of this class");
403: }
404:
405: // create entry
406: Entry entry = (Entry) cEnumClasses.get(enumClass);
407: if (entry == null) {
408: entry = createEntry(enumClass);
409: cEnumClasses.put(enumClass, entry);
410: }
411: if (entry.map.containsKey(name)) {
412: throw new IllegalArgumentException(
413: "The Enum name must be unique, '" + name
414: + "' has already been added");
415: }
416: entry.map.put(name, this );
417: entry.list.add(this );
418: }
419:
420: /**
421: * <p>Handle the deserialization of the class to ensure that multiple
422: * copies are not wastefully created, or illegal enum types created.</p>
423: *
424: * @return the resolved object
425: */
426: protected Object readResolve() {
427: Entry entry = (Entry) cEnumClasses.get(getEnumClass());
428: if (entry == null) {
429: return null;
430: }
431: return entry.map.get(getName());
432: }
433:
434: //--------------------------------------------------------------------------------
435:
436: /**
437: * <p>Gets an <code>Enum</code> object by class and name.</p>
438: *
439: * @param enumClass the class of the Enum to get, must not
440: * be <code>null</code>
441: * @param name the name of the <code>Enum</code> to get,
442: * may be <code>null</code>
443: * @return the enum object, or <code>null</code> if the enum does not exist
444: * @throws IllegalArgumentException if the enum class
445: * is <code>null</code>
446: */
447: protected static Enum getEnum(Class enumClass, String name) {
448: Entry entry = getEntry(enumClass);
449: if (entry == null) {
450: return null;
451: }
452: return (Enum) entry.map.get(name);
453: }
454:
455: /**
456: * <p>Gets the <code>Map</code> of <code>Enum</code> objects by
457: * name using the <code>Enum</code> class.</p>
458: *
459: * <p>If the requested class has no enum objects an empty
460: * <code>Map</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 the enum object Map
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 Map getEnumMap(Class enumClass) {
469: Entry entry = getEntry(enumClass);
470: if (entry == null) {
471: return EMPTY_MAP;
472: }
473: return entry.unmodifiableMap;
474: }
475:
476: /**
477: * <p>Gets the <code>List</code> of <code>Enum</code> objects using the
478: * <code>Enum</code> class.</p>
479: *
480: * <p>The list is in the order that the objects were created (source code order).
481: * If the requested class has no enum objects an empty <code>List</code> is
482: * returned.</p>
483: *
484: * @param enumClass the class of the <code>Enum</code> to get,
485: * must not be <code>null</code>
486: * @return the enum object Map
487: * @throws IllegalArgumentException if the enum class is <code>null</code>
488: * @throws IllegalArgumentException if the enum class is not a subclass of Enum
489: */
490: protected static List getEnumList(Class enumClass) {
491: Entry entry = getEntry(enumClass);
492: if (entry == null) {
493: return Collections.EMPTY_LIST;
494: }
495: return entry.unmodifiableList;
496: }
497:
498: /**
499: * <p>Gets an <code>Iterator</code> over the <code>Enum</code> objects in
500: * an <code>Enum</code> class.</p>
501: *
502: * <p>The <code>Iterator</code> is in the order that the objects were
503: * created (source code order). If the requested class has no enum
504: * objects an empty <code>Iterator</code> is returned.</p>
505: *
506: * @param enumClass the class of the <code>Enum</code> to get,
507: * must not be <code>null</code>
508: * @return an iterator of the Enum objects
509: * @throws IllegalArgumentException if the enum class is <code>null</code>
510: * @throws IllegalArgumentException if the enum class is not a subclass of Enum
511: */
512: protected static Iterator iterator(Class enumClass) {
513: return Enum.getEnumList(enumClass).iterator();
514: }
515:
516: //-----------------------------------------------------------------------
517: /**
518: * <p>Gets an <code>Entry</code> from the map of Enums.</p>
519: *
520: * @param enumClass the class of the <code>Enum</code> to get
521: * @return the enum entry
522: */
523: private static Entry getEntry(Class enumClass) {
524: if (enumClass == null) {
525: throw new IllegalArgumentException(
526: "The Enum Class must not be null");
527: }
528: if (Enum.class.isAssignableFrom(enumClass) == false) {
529: throw new IllegalArgumentException(
530: "The Class must be a subclass of Enum");
531: }
532: Entry entry = (Entry) cEnumClasses.get(enumClass);
533: return entry;
534: }
535:
536: /**
537: * <p>Creates an <code>Entry</code> for storing the Enums.</p>
538: *
539: * <p>This accounts for subclassed Enums.</p>
540: *
541: * @param enumClass the class of the <code>Enum</code> to get
542: * @return the enum entry
543: */
544: private static Entry createEntry(Class enumClass) {
545: Entry entry = new Entry();
546: Class cls = enumClass.getSuperclass();
547: while (cls != null && cls != Enum.class
548: && cls != ValuedEnum.class) {
549: Entry loopEntry = (Entry) cEnumClasses.get(cls);
550: if (loopEntry != null) {
551: entry.list.addAll(loopEntry.list);
552: entry.map.putAll(loopEntry.map);
553: break; // stop here, as this will already have had superclasses added
554: }
555: cls = cls.getSuperclass();
556: }
557: return entry;
558: }
559:
560: //-----------------------------------------------------------------------
561: /**
562: * <p>Retrieve the name of this Enum item, set in the constructor.</p>
563: *
564: * @return the <code>String</code> name of this Enum item
565: */
566: public final String getName() {
567: return iName;
568: }
569:
570: /**
571: * <p>Retrieves the Class of this Enum item, set in the constructor.</p>
572: *
573: * <p>This is normally the same as <code>getClass()</code>, but for
574: * advanced Enums may be different. If overridden, it must return a
575: * constant value.</p>
576: *
577: * @return the <code>Class</code> of the enum
578: * @since 2.0
579: */
580: public Class getEnumClass() {
581: return getClass();
582: }
583:
584: /**
585: * <p>Tests for equality.</p>
586: *
587: * <p>Two Enum objects are considered equal
588: * if they have the same class names and the same names.
589: * Identity is tested for first, so this method usually runs fast.</p>
590: *
591: * <p>If the parameter is in a different class loader than this instance,
592: * reflection is used to compare the names.</p>
593: *
594: * @param other the other object to compare for equality
595: * @return <code>true</code> if the Enums are equal
596: */
597: public final boolean equals(Object other) {
598: if (other == this ) {
599: return true;
600: } else if (other == null) {
601: return false;
602: } else if (other.getClass() == this .getClass()) {
603: // Ok to do a class cast to Enum here since the test above
604: // guarantee both
605: // classes are in the same class loader.
606: return iName.equals(((Enum) other).iName);
607: } else {
608: // This and other are in different class loaders, we must check indirectly
609: if (other.getClass().getName().equals(
610: this .getClass().getName()) == false) {
611: return false;
612: }
613: return iName.equals(getNameInOtherClassLoader(other));
614: }
615: }
616:
617: /**
618: * <p>Returns a suitable hashCode for the enumeration.</p>
619: *
620: * @return a hashcode based on the name
621: */
622: public final int hashCode() {
623: return iHashCode;
624: }
625:
626: /**
627: * <p>Tests for order.</p>
628: *
629: * <p>The default ordering is alphabetic by name, but this
630: * can be overridden by subclasses.</p>
631: *
632: * <p>If the parameter is in a different class loader than this instance,
633: * reflection is used to compare the names.</p>
634: *
635: * @see java.lang.Comparable#compareTo(Object)
636: * @param other the other object to compare to
637: * @return -ve if this is less than the other object, +ve if greater
638: * than, <code>0</code> of equal
639: * @throws ClassCastException if other is not an Enum
640: * @throws NullPointerException if other is <code>null</code>
641: */
642: public int compareTo(Object other) {
643: if (other == this ) {
644: return 0;
645: }
646: if (other.getClass() != this .getClass()) {
647: if (other.getClass().getName().equals(
648: this .getClass().getName())) {
649: return iName
650: .compareTo(getNameInOtherClassLoader(other));
651: }
652: throw new ClassCastException("Different enum class '"
653: + ClassUtils.getShortClassName(other.getClass())
654: + "'");
655: }
656: return iName.compareTo(((Enum) other).iName);
657: }
658:
659: /**
660: * <p>Use reflection to return an objects class name.</p>
661: *
662: * @param other The object to determine the class name for
663: * @return The class name
664: */
665: private String getNameInOtherClassLoader(Object other) {
666: try {
667: Method mth = other.getClass().getMethod("getName", null);
668: String name = (String) mth.invoke(other, null);
669: return name;
670: } catch (NoSuchMethodException e) {
671: // ignore - should never happen
672: } catch (IllegalAccessException e) {
673: // ignore - should never happen
674: } catch (InvocationTargetException e) {
675: // ignore - should never happen
676: }
677: throw new IllegalStateException("This should not happen");
678: }
679:
680: /**
681: * <p>Human readable description of this Enum item.</p>
682: *
683: * @return String in the form <code>type[name]</code>, for example:
684: * <code>Color[Red]</code>. Note that the package name is stripped from
685: * the type name.
686: */
687: public String toString() {
688: if (iToString == null) {
689: String shortName = ClassUtils
690: .getShortClassName(getEnumClass());
691: iToString = shortName + "[" + getName() + "]";
692: }
693: return iToString;
694: }
695:
696: }
|