KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > lang > enums > 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.enums;
17
18 import java.io.Serializable JavaDoc;
19 import java.lang.reflect.InvocationTargetException JavaDoc;
20 import java.lang.reflect.Method JavaDoc;
21 import java.util.ArrayList JavaDoc;
22 import java.util.Collections JavaDoc;
23 import java.util.HashMap JavaDoc;
24 import java.util.Iterator JavaDoc;
25 import java.util.List JavaDoc;
26 import java.util.Map JavaDoc;
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  * @author Apache Avalon project
228  * @author Stephen Colebourne
229  * @author Chris Webb
230  * @author Mike Bowler
231  * @author Matthias Eichel
232  * @since 2.1 (class existed in enum package from v1.0)
233  * @version $Id: Enum.java 161243 2005-04-14 04:30:28Z ggregory $
234  */

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

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

250     private static final Map JavaDoc cEnumClasses = new HashMap JavaDoc();
251     
252     /**
253      * The string representation of the Enum.
254      */

255     private final String JavaDoc iName;
256     
257     /**
258      * The hashcode representation of the Enum.
259      */

260     private transient final int iHashCode;
261     
262     /**
263      * The toString representation of the Enum.
264      * @since 2.0
265      */

266     protected transient String JavaDoc iToString = null;
267
268     /**
269      * <p>Enable the iterator to retain the source code order.</p>
270      */

271     private static class Entry {
272         /**
273          * Map of Enum name to Enum.
274          */

275         final Map JavaDoc map = new HashMap JavaDoc();
276         /**
277          * Map of Enum name to Enum.
278          */

279         final Map JavaDoc unmodifiableMap = Collections.unmodifiableMap(map);
280         /**
281          * List of Enums in source code order.
282          */

283         final List JavaDoc list = new ArrayList JavaDoc(25);
284         /**
285          * Map of Enum name to Enum.
286          */

287         final List JavaDoc unmodifiableList = Collections.unmodifiableList(list);
288
289         /**
290          * <p>Restrictive constructor.</p>
291          */

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

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

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

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

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

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

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

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

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

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

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

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

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

530     public final boolean equals(Object JavaDoc other) {
531         if (other == this) {
532             return true;
533         } else if (other == null) {
534             return false;
535         } else if (other.getClass() == this.getClass()) {
536             // Ok to do a class cast to Enum here since the test above
537
// guarantee both
538
// classes are in the same class loader.
539
return iName.equals(((Enum JavaDoc) other).iName);
540         } else {
541             // This and other are in different class loaders, we must check indirectly
542
if (other.getClass().getName().equals(this.getClass().getName()) == false) {
543                 return false;
544             }
545             try {
546                 Method JavaDoc mth = other.getClass().getMethod("getName", null);
547                 String JavaDoc name = (String JavaDoc) mth.invoke(other, null);
548                 return iName.equals(name);
549             } catch (NoSuchMethodException JavaDoc e) {
550                 // ignore - should never happen
551
} catch (IllegalAccessException JavaDoc e) {
552                 // ignore - should never happen
553
} catch (InvocationTargetException JavaDoc 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 JavaDoc other) {
583         if (other == this) {
584             return 0;
585         }
586         return iName.compareTo(((Enum JavaDoc) 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 JavaDoc toString() {
597         if (iToString == null) {
598             String JavaDoc shortName = ClassUtils.getShortClassName(getEnumClass());
599             iToString = shortName + "[" + getName() + "]";
600         }
601         return iToString;
602     }
603     
604 }
605
Popular Tags