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.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: * @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 Stephen Colebourne
235: * @author Chris Webb
236: * @author Mike Bowler
237: * @since 1.0
238: * @version $Id: Enum.java 447975 2006-09-19 21:20:56Z bayard $
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 final Map cEnumClasses = new WeakHashMap();
260:
261: /**
262: * The string representation of the Enum.
263: */
264: private final String iName;
265:
266: /**
267: * The hashcode representation of the Enum.
268: */
269: private transient final int iHashCode;
270:
271: /**
272: * The toString representation of the Enum.
273: * @since 2.0
274: */
275: protected transient String iToString = null;
276:
277: /**
278: * <p>Enable the iterator to retain the source code order.</p>
279: */
280: private static class Entry {
281: /**
282: * Map of Enum name to Enum.
283: */
284: final Map map = new HashMap();
285: /**
286: * Map of Enum name to Enum.
287: */
288: final Map unmodifiableMap = Collections.unmodifiableMap(map);
289: /**
290: * List of Enums in source code order.
291: */
292: final List list = new ArrayList(25);
293: /**
294: * Map of Enum name to Enum.
295: */
296: final List unmodifiableList = Collections.unmodifiableList(list);
297:
298: /**
299: * <p>Restrictive constructor.</p>
300: */
301: protected Entry() {
302: super ();
303: }
304: }
305:
306: /**
307: * <p>Constructor to add a new named item to the enumeration.</p>
308: *
309: * @param name the name of the enum object,
310: * must not be empty or <code>null</code>
311: * @throws IllegalArgumentException if the name is <code>null</code>
312: * or an empty string
313: * @throws IllegalArgumentException if the getEnumClass() method returns
314: * a null or invalid Class
315: */
316: protected Enum(String name) {
317: super ();
318: init(name);
319: iName = name;
320: iHashCode = 7 + getEnumClass().hashCode() + 3 * name.hashCode();
321: // cannot create toString here as subclasses may want to include other data
322: }
323:
324: /**
325: * Initializes the enumeration.
326: *
327: * @param name the enum name
328: * @throws IllegalArgumentException if the name is null or empty or duplicate
329: * @throws IllegalArgumentException if the enumClass is null or invalid
330: */
331: private void init(String name) {
332: if (StringUtils.isEmpty(name)) {
333: throw new IllegalArgumentException("The Enum name must not be empty or null");
334: }
335:
336: Class enumClass = getEnumClass();
337: if (enumClass == null) {
338: throw new IllegalArgumentException("getEnumClass() must not be null");
339: }
340: Class cls = getClass();
341: boolean ok = false;
342: while (cls != null && cls != Enum.class && cls != ValuedEnum.class) {
343: if (cls == enumClass) {
344: ok = true;
345: break;
346: }
347: cls = cls.getSuperclass();
348: }
349: if (ok == false) {
350: throw new IllegalArgumentException("getEnumClass() must return a superclass of this class");
351: }
352:
353: // create entry
354: Entry entry = (Entry) cEnumClasses.get(enumClass);
355: if (entry == null) {
356: entry = createEntry(enumClass);
357: cEnumClasses.put(enumClass, entry);
358: }
359: if (entry.map.containsKey(name)) {
360: throw new IllegalArgumentException("The Enum name must be unique, '" + name + "' has already been added");
361: }
362: entry.map.put(name, this );
363: entry.list.add(this );
364: }
365:
366: /**
367: * <p>Handle the deserialization of the class to ensure that multiple
368: * copies are not wastefully created, or illegal enum types created.</p>
369: *
370: * @return the resolved object
371: */
372: protected Object readResolve() {
373: Entry entry = (Entry) cEnumClasses.get(getEnumClass());
374: if (entry == null) {
375: return null;
376: }
377: return entry.map.get(getName());
378: }
379:
380: //--------------------------------------------------------------------------------
381:
382: /**
383: * <p>Gets an <code>Enum</code> object by class and name.</p>
384: *
385: * @param enumClass the class of the Enum to get, must not
386: * be <code>null</code>
387: * @param name the name of the <code>Enum</code> to get,
388: * may be <code>null</code>
389: * @return the enum object, or <code>null</code> if the enum does not exist
390: * @throws IllegalArgumentException if the enum class
391: * is <code>null</code>
392: */
393: protected static Enum getEnum(Class enumClass, String name) {
394: Entry entry = getEntry(enumClass);
395: if (entry == null) {
396: return null;
397: }
398: return (Enum) entry.map.get(name);
399: }
400:
401: /**
402: * <p>Gets the <code>Map</code> of <code>Enum</code> objects by
403: * name using the <code>Enum</code> class.</p>
404: *
405: * <p>If the requested class has no enum objects an empty
406: * <code>Map</code> is returned.</p>
407: *
408: * @param enumClass the class of the <code>Enum</code> to get,
409: * must not be <code>null</code>
410: * @return the enum object Map
411: * @throws IllegalArgumentException if the enum class is <code>null</code>
412: * @throws IllegalArgumentException if the enum class is not a subclass of Enum
413: */
414: protected static Map getEnumMap(Class enumClass) {
415: Entry entry = getEntry(enumClass);
416: if (entry == null) {
417: return EMPTY_MAP;
418: }
419: return entry.unmodifiableMap;
420: }
421:
422: /**
423: * <p>Gets the <code>List</code> of <code>Enum</code> objects using the
424: * <code>Enum</code> class.</p>
425: *
426: * <p>The list is in the order that the objects were created (source code order).
427: * If the requested class has no enum objects an empty <code>List</code> is
428: * returned.</p>
429: *
430: * @param enumClass the class of the <code>Enum</code> to get,
431: * must not be <code>null</code>
432: * @return the enum object Map
433: * @throws IllegalArgumentException if the enum class is <code>null</code>
434: * @throws IllegalArgumentException if the enum class is not a subclass of Enum
435: */
436: protected static List getEnumList(Class enumClass) {
437: Entry entry = getEntry(enumClass);
438: if (entry == null) {
439: return Collections.EMPTY_LIST;
440: }
441: return entry.unmodifiableList;
442: }
443:
444: /**
445: * <p>Gets an <code>Iterator</code> over the <code>Enum</code> objects in
446: * an <code>Enum</code> class.</p>
447: *
448: * <p>The <code>Iterator</code> is in the order that the objects were
449: * created (source code order). If the requested class has no enum
450: * objects an empty <code>Iterator</code> is returned.</p>
451: *
452: * @param enumClass the class of the <code>Enum</code> to get,
453: * must not be <code>null</code>
454: * @return an iterator of the Enum objects
455: * @throws IllegalArgumentException if the enum class is <code>null</code>
456: * @throws IllegalArgumentException if the enum class is not a subclass of Enum
457: */
458: protected static Iterator iterator(Class enumClass) {
459: return Enum.getEnumList(enumClass).iterator();
460: }
461:
462: //-----------------------------------------------------------------------
463: /**
464: * <p>Gets an <code>Entry</code> from the map of Enums.</p>
465: *
466: * @param enumClass the class of the <code>Enum</code> to get
467: * @return the enum entry
468: */
469: private static Entry getEntry(Class enumClass) {
470: if (enumClass == null) {
471: throw new IllegalArgumentException("The Enum Class must not be null");
472: }
473: if (Enum.class.isAssignableFrom(enumClass) == false) {
474: throw new IllegalArgumentException("The Class must be a subclass of Enum");
475: }
476: Entry entry = (Entry) cEnumClasses.get(enumClass);
477: return entry;
478: }
479:
480: /**
481: * <p>Creates an <code>Entry</code> for storing the Enums.</p>
482: *
483: * <p>This accounts for subclassed Enums.</p>
484: *
485: * @param enumClass the class of the <code>Enum</code> to get
486: * @return the enum entry
487: */
488: private static Entry createEntry(Class enumClass) {
489: Entry entry = new Entry();
490: Class cls = enumClass.getSuperclass();
491: while (cls != null && cls != Enum.class && cls != ValuedEnum.class) {
492: Entry loopEntry = (Entry) cEnumClasses.get(cls);
493: if (loopEntry != null) {
494: entry.list.addAll(loopEntry.list);
495: entry.map.putAll(loopEntry.map);
496: break; // stop here, as this will already have had superclasses added
497: }
498: cls = cls.getSuperclass();
499: }
500: return entry;
501: }
502:
503: //-----------------------------------------------------------------------
504: /**
505: * <p>Retrieve the name of this Enum item, set in the constructor.</p>
506: *
507: * @return the <code>String</code> name of this Enum item
508: */
509: public final String getName() {
510: return iName;
511: }
512:
513: /**
514: * <p>Retrieves the Class of this Enum item, set in the constructor.</p>
515: *
516: * <p>This is normally the same as <code>getClass()</code>, but for
517: * advanced Enums may be different. If overridden, it must return a
518: * constant value.</p>
519: *
520: * @return the <code>Class</code> of the enum
521: * @since 2.0
522: */
523: public Class getEnumClass() {
524: return getClass();
525: }
526:
527: /**
528: * <p>Tests for equality.</p>
529: *
530: * <p>Two Enum objects are considered equal
531: * if they have the same class names and the same names.
532: * Identity is tested for first, so this method usually runs fast.</p>
533: *
534: * <p>If the parameter is in a different class loader than this instance,
535: * reflection is used to compare the names.</p>
536: *
537: * @param other the other object to compare for equality
538: * @return <code>true</code> if the Enums are equal
539: */
540: public final boolean equals(Object other) {
541: if (other == this ) {
542: return true;
543: } else if (other == null) {
544: return false;
545: } else if (other.getClass() == this .getClass()) {
546: // Ok to do a class cast to Enum here since the test above
547: // guarantee both
548: // classes are in the same class loader.
549: return iName.equals(((Enum) other).iName);
550: } else {
551: // This and other are in different class loaders, we must use reflection.
552: if (other.getClass().getName().equals(this .getClass().getName()) == false) {
553: return false;
554: }
555: return iName.equals( getNameInOtherClassLoader(other) );
556: }
557: }
558:
559: /**
560: * <p>Returns a suitable hashCode for the enumeration.</p>
561: *
562: * @return a hashcode based on the name
563: */
564: public final int hashCode() {
565: return iHashCode;
566: }
567:
568: /**
569: * <p>Tests for order.</p>
570: *
571: * <p>The default ordering is alphabetic by name, but this
572: * can be overridden by subclasses.</p>
573: *
574: * <p>If the parameter is in a different class loader than this instance,
575: * reflection is used to compare the names.</p>
576: *
577: * @see java.lang.Comparable#compareTo(Object)
578: * @param other the other object to compare to
579: * @return -ve if this is less than the other object, +ve if greater
580: * than, <code>0</code> of equal
581: * @throws ClassCastException if other is not an Enum
582: * @throws NullPointerException if other is <code>null</code>
583: */
584: public int compareTo(Object other) {
585: if (other == this ) {
586: return 0;
587: }
588: if (other.getClass() != this .getClass()) {
589: if (other.getClass().getName().equals(this .getClass().getName())) {
590: return iName.compareTo( getNameInOtherClassLoader(other) );
591: }
592: }
593: return iName.compareTo(((Enum) other).iName);
594: }
595:
596: /**
597: * <p>Use reflection to return an objects class name.</p>
598: *
599: * @param other The object to determine the class name for
600: * @return The class name
601: */
602: private String getNameInOtherClassLoader(Object other) {
603: try {
604: Method mth = other.getClass().getMethod("getName", null);
605: String name = (String) mth.invoke(other, null);
606: return name;
607: } catch (NoSuchMethodException e) {
608: // ignore - should never happen
609: } catch (IllegalAccessException e) {
610: // ignore - should never happen
611: } catch (InvocationTargetException e) {
612: // ignore - should never happen
613: }
614: throw new IllegalStateException("This should not happen");
615: }
616:
617: /**
618: * <p>Human readable description of this Enum item.</p>
619: *
620: * @return String in the form <code>type[name]</code>, for example:
621: * <code>Color[Red]</code>. Note that the package name is stripped from
622: * the type name.
623: */
624: public String toString() {
625: if (iToString == null) {
626: String shortName = ClassUtils.getShortClassName(getEnumClass());
627: iToString = shortName + "[" + getName() + "]";
628: }
629: return iToString;
630: }
631:
632:}
|