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