KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > lang > enum > Enum


1 /*
2  * Copyright 2002-2005 The Apache Software Foundation.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */

16 package org.apache.commons.lang.enum;
17
18 import java.io.Serializable;
19 import java.lang.reflect.InvocationTargetException;
20 import java.lang.reflect.Method;
21 import java.util.ArrayList;
22 import java.util.Collections;
23 import java.util.HashMap;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Map;
27
28 import org.apache.commons.lang.ClassUtils;
29 import org.apache.commons.lang.StringUtils;
30
31 /**
32  * <p>Abstract superclass for type-safe enums.</p>
33  *
34  * <p>One feature of the C programming language lacking in Java is enumerations. The
35  * C implementation based on ints was poor and open to abuse. The original Java
36  * recommendation and most of the JDK also uses int constants. It has been recognised
37  * however that a more robust type-safe class-based solution can be designed. This
38  * class follows the basic Java type-safe enumeration pattern.</p>
39  *
40  * <p><em>NOTE:</em>Due to the way in which Java ClassLoaders work, comparing
41  * Enum objects should always be done using <code>equals()</code>, not <code>==</code>.
42  * The equals() method will try == first so in most cases the effect is the same.</p>
43  *
44  * <p>Of course, if you actually want (or don't mind) Enums in different class
45  * loaders being non-equal, then you can use <code>==</code>.</p>
46  *
47  * <h4>Simple Enums</h4>
48  *
49  * <p>To use this class, it must be subclassed. For example:</p>
50  *
51  * <pre>
52  * public final class ColorEnum extends Enum {
53  * public static final ColorEnum RED = new ColorEnum("Red");
54  * public static final ColorEnum GREEN = new ColorEnum("Green");
55  * public static final ColorEnum BLUE = new ColorEnum("Blue");
56  *
57  * private ColorEnum(String color) {
58  * super(color);
59  * }
60  *
61  * public static ColorEnum getEnum(String color) {
62  * return (ColorEnum) getEnum(ColorEnum.class, color);
63  * }
64  *
65  * public static Map getEnumMap() {
66  * return getEnumMap(ColorEnum.class);
67  * }
68  *
69  * public static List getEnumList() {
70  * return getEnumList(ColorEnum.class);
71  * }
72  *
73  * public static Iterator iterator() {
74  * return iterator(ColorEnum.class);
75  * }
76  * }
77  * </pre>
78  *
79  * <p>As shown, each enum has a name. This can be accessed using <code>getName</code>.</p>
80  *
81  * <p>The <code>getEnum</code> and <code>iterator</code> methods are recommended.
82  * Unfortunately, Java restrictions require these to be coded as shown in each subclass.
83  * An alternative choice is to use the {@link EnumUtils} class.</p>
84  *
85  * <h4>Subclassed Enums</h4>
86  * <p>A hierarchy of Enum classes can be built. In this case, the superclass is
87  * unaffected by the addition of subclasses (as per normal Java). The subclasses
88  * may add additional Enum constants <em>of the type of the superclass</em>. The
89  * query methods on the subclass will return all of the Enum constants from the
90  * superclass and subclass.</p>
91  *
92  * <pre>
93  * public final class ExtraColorEnum extends ColorEnum {
94  * // NOTE: Color enum declared above is final, change that to get this
95  * // example to compile.
96  * public static final ColorEnum YELLOW = new ExtraColorEnum("Yellow");
97  *
98  * private ExtraColorEnum(String color) {
99  * super(color);
100  * }
101  *
102  * public static ColorEnum getEnum(String color) {
103  * return (ColorEnum) getEnum(ExtraColorEnum.class, color);
104  * }
105  *
106  * public static Map getEnumMap() {
107  * return getEnumMap(ExtraColorEnum.class);
108  * }
109  *
110  * public static List getEnumList() {
111  * return getEnumList(ExtraColorEnum.class);
112  * }
113  *
114  * public static Iterator iterator() {
115  * return iterator(ExtraColorEnum.class);
116  * }
117  * }
118  * </pre>
119  *
120  * <p>This example will return RED, GREEN, BLUE, YELLOW from the List and iterator
121  * methods in that order. The RED, GREEN and BLUE instances will be the same (==)
122  * as those from the superclass ColorEnum. Note that YELLOW is declared as a
123  * ColorEnum and not an ExtraColorEnum.</p>
124  *
125  * <h4>Functional Enums</h4>
126  *
127  * <p>The enums can have functionality by defining subclasses and
128  * overriding the <code>getEnumClass()</code> method:</p>
129  *
130  * <pre>
131  * public static final OperationEnum PLUS = new PlusOperation();
132  * private static final class PlusOperation extends OperationEnum {
133  * private PlusOperation() {
134  * super("Plus");
135  * }
136  * public int eval(int a, int b) {
137  * return a + b;
138  * }
139  * }
140  * public static final OperationEnum MINUS = new MinusOperation();
141  * private static final class MinusOperation extends OperationEnum {
142  * private MinusOperation() {
143  * super("Minus");
144  * }
145  * public int eval(int a, int b) {
146  * return a - b;
147  * }
148  * }
149  *
150  * private OperationEnum(String color) {
151  * super(color);
152  * }
153  *
154  * public final Class getEnumClass() { // NOTE: new method!
155  * return OperationEnum.class;
156  * }
157  *
158  * public abstract double eval(double a, double b);
159  *
160  * public static OperationEnum getEnum(String name) {
161  * return (OperationEnum) getEnum(OperationEnum.class, name);
162  * }
163  *
164  * public static Map getEnumMap() {
165  * return getEnumMap(OperationEnum.class);
166  * }
167  *
168  * public static List getEnumList() {
169  * return getEnumList(OperationEnum.class);
170  * }
171  *
172  * public static Iterator iterator() {
173  * return iterator(OperationEnum.class);
174  * }
175  * }
176  * </pre>
177  * <p>The code above will work on JDK 1.2. If JDK1.3 and later is used,
178  * the subclasses may be defined as anonymous.</p>
179  *
180  * <h4>Nested class Enums</h4>
181  *
182  * <p>Care must be taken with class loading when defining a static nested class
183  * for enums. The static nested class can be loaded without the surrounding outer
184  * class being loaded. This can result in an empty list/map/iterator being returned.
185  * One solution is to define a static block that references the outer class where
186  * the constants are defined. For example:</p>
187  *
188  * <pre>
189  * public final class Outer {
190  * public static final BWEnum BLACK = new BWEnum("Black");
191  * public static final BWEnum WHITE = new BWEnum("White");
192  *
193  * // static nested enum class
194  * public static final class BWEnum extends Enum {
195  *
196  * static {
197  * // explicitly reference BWEnum class to force constants to load
198  * Object obj = Outer.BLACK;
199  * }
200  *
201  * // ... other methods omitted
202  * }
203  * }
204  * </pre>
205  *
206  * <p>Although the above solves the problem, it is not recommended. The best solution
207  * is to define the constants in the enum class, and hold references in the outer class:
208  *
209  * <pre>
210  * public final class Outer {
211  * public static final BWEnum BLACK = BWEnum.BLACK;
212  * public static final BWEnum WHITE = BWEnum.WHITE;
213  *
214  * // static nested enum class
215  * public static final class BWEnum extends Enum {
216  * // only define constants in enum classes - private if desired
217  * private static final BWEnum BLACK = new BWEnum("Black");
218  * private static final BWEnum WHITE = new BWEnum("White");
219  *
220  * // ... other methods omitted
221  * }
222  * }
223  * </pre>
224  *
225  * <p>For more details, see the 'Nested' test cases.
226  *
227  * @deprecated Replaced by {@link org.apache.commons.lang.enums.Enum org.apache.commons.lang.enums.Enum}
228  * and will be removed in version 3.0. All classes in this package are deprecated and repackaged to
229  * {@link org.apache.commons.lang.enums} since <code>enum</code> is a Java 1.5 keyword.
230  * @see org.apache.commons.lang.enums.Enum
231  * @author Apache Avalon project
232  * @author Stephen Colebourne
233  * @author Chris Webb
234  * @author Mike Bowler
235  * @since 1.0
236  * @version $Id: Enum.java 161243 2005-04-14 04:30:28Z ggregory $
237  */

238 public abstract class Enum implements Comparable, Serializable {
239
240     /** Lang version 1.0.1 serial compatibility */
241     private static final long serialVersionUID = -487045951170455942L;
242     
243     // After discussion, the default size for HashMaps is used, as the
244
// sizing algorithm changes across the JDK versions
245
/**
246      * An empty <code>Map</code>, as JDK1.2 didn't have an empty map.
247      */

248     private static final Map EMPTY_MAP = Collections.unmodifiableMap(new HashMap(0));
249     
250     /**
251      * <code>Map</code>, key of class name, value of <code>Entry</code>.
252      */

253     private static final Map cEnumClasses = new HashMap();
254     
255     /**
256      * The string representation of the Enum.
257      */

258     private final String iName;
259     
260     /**
261      * The hashcode representation of the Enum.
262      */

263     private transient final int iHashCode;
264     
265     /**
266      * The toString representation of the Enum.
267      * @since 2.0
268      */

269     protected transient String iToString = null;
270
271     /**
272      * <p>Enable the iterator to retain the source code order.</p>
273      */

274     private static class Entry {
275         /**
276          * Map of Enum name to Enum.
277          */

278         final Map map = new HashMap();
279         /**
280          * Map of Enum name to Enum.
281          */

282         final Map unmodifiableMap = Collections.unmodifiableMap(map);
283         /**
284          * List of Enums in source code order.
285          */

286         final List list = new ArrayList(25);
287         /**
288          * Map of Enum name to Enum.
289          */

290         final List unmodifiableList = Collections.unmodifiableList(list);
291
292         /**
293          * <p>Restrictive constructor.</p>
294          */

295         private Entry() {
296         }
297     }
298
299     /**
300      * <p>Constructor to add a new named item to the enumeration.</p>
301      *
302      * @param name the name of the enum object,
303      * must not be empty or <code>null</code>
304      * @throws IllegalArgumentException if the name is <code>null</code>
305      * or an empty string
306      * @throws IllegalArgumentException if the getEnumClass() method returns
307      * a null or invalid Class
308      */

309     protected Enum(String name) {
310         super();
311         init(name);
312         iName = name;
313         iHashCode = 7 + getEnumClass().hashCode() + 3 * name.hashCode();
314         // cannot create toString here as subclasses may want to include other data
315
}
316
317     /**
318      * Initializes the enumeration.
319      *
320      * @param name the enum name
321      * @throws IllegalArgumentException if the name is null or empty or duplicate
322      * @throws IllegalArgumentException if the enumClass is null or invalid
323      */

324     private void init(String name) {
325         if (StringUtils.isEmpty(name)) {
326             throw new IllegalArgumentException("The Enum name must not be empty or null");
327         }
328         
329         Class enumClass = getEnumClass();
330         if (enumClass == null) {
331             throw new IllegalArgumentException("getEnumClass() must not be null");
332         }
333         Class cls = getClass();
334         boolean ok = false;
335         while (cls != null && cls != Enum.class && cls != ValuedEnum.class) {
336             if (cls == enumClass) {
337                 ok = true;
338                 break;
339             }
340             cls = cls.getSuperclass();
341         }
342         if (ok == false) {
343             throw new IllegalArgumentException("getEnumClass() must return a superclass of this class");
344         }
345         
346         // create entry
347
Entry entry = (Entry) cEnumClasses.get(enumClass);
348         if (entry == null) {
349             entry = createEntry(enumClass);
350             cEnumClasses.put(enumClass, entry);
351         }
352         if (entry.map.containsKey(name)) {
353             throw new IllegalArgumentException("The Enum name must be unique, '" + name + "' has already been added");
354         }
355         entry.map.put(name, this);
356         entry.list.add(this);
357     }
358
359     /**
360      * <p>Handle the deserialization of the class to ensure that multiple
361      * copies are not wastefully created, or illegal enum types created.</p>
362      *
363      * @return the resolved object
364      */

365     protected Object readResolve() {
366         Entry entry = (Entry) cEnumClasses.get(getEnumClass());
367         if (entry == null) {
368             return null;
369         }
370         return (Enum) entry.map.get(getName());
371     }
372     
373     //--------------------------------------------------------------------------------
374

375     /**
376      * <p>Gets an <code>Enum</code> object by class and name.</p>
377      *
378      * @param enumClass the class of the Enum to get, must not
379      * be <code>null</code>
380      * @param name the name of the <code>Enum</code> to get,
381      * may be <code>null</code>
382      * @return the enum object, or <code>null</code> if the enum does not exist
383      * @throws IllegalArgumentException if the enum class
384      * is <code>null</code>
385      */

386     protected static Enum getEnum(Class enumClass, String name) {
387         Entry entry = getEntry(enumClass);
388         if (entry == null) {
389             return null;
390         }
391         return (Enum) entry.map.get(name);
392     }
393
394     /**
395      * <p>Gets the <code>Map</code> of <code>Enum</code> objects by
396      * name using the <code>Enum</code> class.</p>
397      *
398      * <p>If the requested class has no enum objects an empty
399      * <code>Map</code> is returned.</p>
400      *
401      * @param enumClass the class of the <code>Enum</code> to get,
402      * must not be <code>null</code>
403      * @return the enum object Map
404      * @throws IllegalArgumentException if the enum class is <code>null</code>
405      * @throws IllegalArgumentException if the enum class is not a subclass of Enum
406      */

407     protected static Map getEnumMap(Class enumClass) {
408         Entry entry = getEntry(enumClass);
409         if (entry == null) {
410             return EMPTY_MAP;
411         }
412         return entry.unmodifiableMap;
413     }
414
415     /**
416      * <p>Gets the <code>List</code> of <code>Enum</code> objects using the
417      * <code>Enum</code> class.</p>
418      *
419      * <p>The list is in the order that the objects were created (source code order).
420      * If the requested class has no enum objects an empty <code>List</code> is
421      * returned.</p>
422      *
423      * @param enumClass the class of the <code>Enum</code> to get,
424      * must not be <code>null</code>
425      * @return the enum object Map
426      * @throws IllegalArgumentException if the enum class is <code>null</code>
427      * @throws IllegalArgumentException if the enum class is not a subclass of Enum
428      */

429     protected static List getEnumList(Class enumClass) {
430         Entry entry = getEntry(enumClass);
431         if (entry == null) {
432             return Collections.EMPTY_LIST;
433         }
434         return entry.unmodifiableList;
435     }
436
437     /**
438      * <p>Gets an <code>Iterator</code> over the <code>Enum</code> objects in
439      * an <code>Enum</code> class.</p>
440      *
441      * <p>The <code>Iterator</code> is in the order that the objects were
442      * created (source code order). If the requested class has no enum
443      * objects an empty <code>Iterator</code> is returned.</p>
444      *
445      * @param enumClass the class of the <code>Enum</code> to get,
446      * must not be <code>null</code>
447      * @return an iterator of the Enum objects
448      * @throws IllegalArgumentException if the enum class is <code>null</code>
449      * @throws IllegalArgumentException if the enum class is not a subclass of Enum
450      */

451     protected static Iterator iterator(Class enumClass) {
452         return Enum.getEnumList(enumClass).iterator();
453     }
454
455     //-----------------------------------------------------------------------
456
/**
457      * <p>Gets an <code>Entry</code> from the map of Enums.</p>
458      *
459      * @param enumClass the class of the <code>Enum</code> to get
460      * @return the enum entry
461      */

462     private static Entry getEntry(Class enumClass) {
463         if (enumClass == null) {
464             throw new IllegalArgumentException("The Enum Class must not be null");
465         }
466         if (Enum.class.isAssignableFrom(enumClass) == false) {
467             throw new IllegalArgumentException("The Class must be a subclass of Enum");
468         }
469         Entry entry = (Entry) cEnumClasses.get(enumClass);
470         return entry;
471     }
472     
473     /**
474      * <p>Creates an <code>Entry</code> for storing the Enums.</p>
475      *
476      * <p>This accounts for subclassed Enums.</p>
477      *
478      * @param enumClass the class of the <code>Enum</code> to get
479      * @return the enum entry
480      */

481     private static Entry createEntry(Class enumClass) {
482         Entry entry = new Entry();
483         Class cls = enumClass.getSuperclass();
484         while (cls != null && cls != Enum.class && cls != ValuedEnum.class) {
485             Entry loopEntry = (Entry) cEnumClasses.get(cls);
486             if (loopEntry != null) {
487                 entry.list.addAll(loopEntry.list);
488                 entry.map.putAll(loopEntry.map);
489                 break; // stop here, as this will already have had superclasses added
490
}
491             cls = cls.getSuperclass();
492         }
493         return entry;
494     }
495     
496     //-----------------------------------------------------------------------
497
/**
498      * <p>Retrieve the name of this Enum item, set in the constructor.</p>
499      *
500      * @return the <code>String</code> name of this Enum item
501      */

502     public final String getName() {
503         return iName;
504     }
505
506     /**
507      * <p>Retrieves the Class of this Enum item, set in the constructor.</p>
508      *
509      * <p>This is normally the same as <code>getClass()</code>, but for
510      * advanced Enums may be different. If overridden, it must return a
511      * constant value.</p>
512      *
513      * @return the <code>Class</code> of the enum
514      * @since 2.0
515      */

516     public Class getEnumClass() {
517         return getClass();
518     }
519
520     /**
521      * <p>Tests for equality.</p>
522      *
523      * <p>Two Enum objects are considered equal
524      * if they have the same class names and the same names.
525      * Identity is tested for first, so this method usually runs fast.</p>
526      *
527      * <p>If the parameter is in a different class loader than this instance,
528      * reflection is used to compare the names.</p>
529      *
530      * @param other the other object to compare for equality
531      * @return <code>true</code> if the Enums are equal
532      */

533     public final boolean equals(Object other) {
534         if (other == this) {
535             return true;
536         } else if (other == null) {
537             return false;
538         } else if (other.getClass() == this.getClass()) {
539             // Ok to do a class cast to Enum here since the test above
540
// guarantee both
541
// classes are in the same class loader.
542
return iName.equals(((Enum) other).iName);
543         } else {
544             // This and other are in different class loaders, we must use reflection.
545
try {
546                 Method mth = other.getClass().getMethod("getName", null);
547                 String name = (String) mth.invoke(other, null);
548                 return iName.equals(name);
549             } catch (NoSuchMethodException e) {
550                 // ignore - should never happen
551
} catch (IllegalAccessException e) {
552                 // ignore - should never happen
553
} catch (InvocationTargetException e) {
554                 // ignore - should never happen
555
}
556             return false;
557         }
558     }
559     
560     /**
561      * <p>Returns a suitable hashCode for the enumeration.</p>
562      *
563      * @return a hashcode based on the name
564      */

565     public final int hashCode() {
566         return iHashCode;
567     }
568
569     /**
570      * <p>Tests for order.</p>
571      *
572      * <p>The default ordering is alphabetic by name, but this
573      * can be overridden by subclasses.</p>
574      *
575      * @see java.lang.Comparable#compareTo(Object)
576      * @param other the other object to compare to
577      * @return -ve if this is less than the other object, +ve if greater
578      * than, <code>0</code> of equal
579      * @throws ClassCastException if other is not an Enum
580      * @throws NullPointerException if other is <code>null</code>
581      */

582     public int compareTo(Object other) {
583         if (other == this) {
584             return 0;
585         }
586         return iName.compareTo(((Enum) other).iName);
587     }
588
589     /**
590      * <p>Human readable description of this Enum item.</p>
591      *
592      * @return String in the form <code>type[name]</code>, for example:
593      * <code>Color[Red]</code>. Note that the package name is stripped from
594      * the type name.
595      */

596     public String toString() {
597         if (iToString == null) {
598             String shortName = ClassUtils.getShortClassName(getEnumClass());
599             iToString = shortName + "[" + getName() + "]";
600         }
601         return iToString;
602     }
603     
604 }
605
Popular Tags