source: src/main/java/agents/org/apache/commons/lang/enums/Enum.java

Last change on this file was 127, checked in by Wouter Pasman, 6 years ago

#41 ROLL BACK of rev.126 . So this version is equal to rev. 125

File size: 24.6 KB
Line 
1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17package agents.org.apache.commons.lang.enums;
18
19import java.io.Serializable;
20import java.lang.reflect.InvocationTargetException;
21import java.lang.reflect.Method;
22import java.util.ArrayList;
23import java.util.Collections;
24import java.util.HashMap;
25import java.util.Iterator;
26import java.util.List;
27import java.util.Map;
28import java.util.WeakHashMap;
29
30import agents.org.apache.commons.lang.ClassUtils;
31import agents.org.apache.commons.lang.StringUtils;
32
33/**
34 * <p>Abstract superclass for type-safe enums.</p>
35 *
36 * <p>One feature of the C programming language lacking in Java is enumerations. The
37 * C implementation based on ints was poor and open to abuse. The original Java
38 * recommendation and most of the JDK also uses int constants. It has been recognised
39 * however that a more robust type-safe class-based solution can be designed. This
40 * class follows the basic Java type-safe enumeration pattern.</p>
41 *
42 * <p><em>NOTE:</em> Due to the way in which Java ClassLoaders work, comparing
43 * Enum objects should always be done using <code>equals()</code>, not <code>==</code>.
44 * The equals() method will try == first so in most cases the effect is the same.</p>
45 *
46 * <p>Of course, if you actually want (or don't mind) Enums in different class
47 * loaders being non-equal, then you can use <code>==</code>.</p>
48 *
49 * <h4>Simple Enums</h4>
50 *
51 * <p>To use this class, it must be subclassed. For example:</p>
52 *
53 * <pre>
54 * public final class ColorEnum extends Enum {
55 * public static final ColorEnum RED = new ColorEnum("Red");
56 * public static final ColorEnum GREEN = new ColorEnum("Green");
57 * public static final ColorEnum BLUE = new ColorEnum("Blue");
58 *
59 * private ColorEnum(String color) {
60 * super(color);
61 * }
62 *
63 * public static ColorEnum getEnum(String color) {
64 * return (ColorEnum) getEnum(ColorEnum.class, color);
65 * }
66 *
67 * public static Map getEnumMap() {
68 * return getEnumMap(ColorEnum.class);
69 * }
70 *
71 * public static List getEnumList() {
72 * return getEnumList(ColorEnum.class);
73 * }
74 *
75 * public static Iterator iterator() {
76 * return iterator(ColorEnum.class);
77 * }
78 * }
79 * </pre>
80 *
81 * <p>As shown, each enum has a name. This can be accessed using <code>getName</code>.</p>
82 *
83 * <p>The <code>getEnum</code> and <code>iterator</code> methods are recommended.
84 * Unfortunately, Java restrictions require these to be coded as shown in each subclass.
85 * An alternative choice is to use the {@link EnumUtils} class.</p>
86 *
87 * <h4>Subclassed Enums</h4>
88 * <p>A hierarchy of Enum classes can be built. In this case, the superclass is
89 * unaffected by the addition of subclasses (as per normal Java). The subclasses
90 * may add additional Enum constants <em>of the type of the superclass</em>. The
91 * query methods on the subclass will return all of the Enum constants from the
92 * superclass and subclass.</p>
93 *
94 * <pre>
95 * public final class ExtraColorEnum extends ColorEnum {
96 * // NOTE: Color enum declared above is final, change that to get this
97 * // example to compile.
98 * public static final ColorEnum YELLOW = new ExtraColorEnum("Yellow");
99 *
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 Apache Software Foundation
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 912394 2010-02-21 20:16:22Z niallp $
285 */
286public 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.unmodifiableMap(new HashMap(0));
301
302 /**
303 * <code>Map</code>, key of class name, value of <code>Entry</code>.
304 */
305 private static Map cEnumClasses
306 // LANG-334: To avoid exposing a mutating map,
307 // we copy it each time we add to it. This is cheaper than
308 // using a synchronized map since we are almost entirely reads
309 = new WeakHashMap();
310
311 /**
312 * The string representation of the Enum.
313 */
314 private final String iName;
315
316 /**
317 * The hashcode representation of the Enum.
318 */
319 private transient final int iHashCode;
320
321 /**
322 * The toString representation of the Enum.
323 * @since 2.0
324 */
325 protected transient String iToString = null;
326
327 /**
328 * <p>Enable the iterator to retain the source code order.</p>
329 */
330 private static class Entry {
331 /**
332 * Map of Enum name to Enum.
333 */
334 final Map map = new HashMap();
335 /**
336 * Map of Enum name to Enum.
337 */
338 final Map unmodifiableMap = Collections.unmodifiableMap(map);
339 /**
340 * List of Enums in source code order.
341 */
342 final List list = new ArrayList(25);
343 /**
344 * Map of Enum name to Enum.
345 */
346 final List unmodifiableList = Collections.unmodifiableList(list);
347
348 /**
349 * <p>Restrictive constructor.</p>
350 */
351 protected Entry() {
352 super();
353 }
354 }
355
356 /**
357 * <p>Constructor to add a new named item to the enumeration.</p>
358 *
359 * @param name the name of the enum object,
360 * must not be empty or <code>null</code>
361 * @throws IllegalArgumentException if the name is <code>null</code>
362 * or an empty string
363 * @throws IllegalArgumentException if the getEnumClass() method returns
364 * a null or invalid Class
365 */
366 protected Enum(String name) {
367 super();
368 init(name);
369 iName = name;
370 iHashCode = 7 + getEnumClass().hashCode() + 3 * name.hashCode();
371 // cannot create toString here as subclasses may want to include other data
372 }
373
374 /**
375 * Initializes the enumeration.
376 *
377 * @param name the enum name
378 * @throws IllegalArgumentException if the name is null or empty or duplicate
379 * @throws IllegalArgumentException if the enumClass is null or invalid
380 */
381 private void init(String name) {
382 if (StringUtils.isEmpty(name)) {
383 throw new IllegalArgumentException("The Enum name must not be empty or null");
384 }
385
386 Class enumClass = getEnumClass();
387 if (enumClass == null) {
388 throw new IllegalArgumentException("getEnumClass() must not be null");
389 }
390 Class cls = getClass();
391 boolean ok = false;
392 while (cls != null && cls != Enum.class && cls != ValuedEnum.class) {
393 if (cls == enumClass) {
394 ok = true;
395 break;
396 }
397 cls = cls.getSuperclass();
398 }
399 if (ok == false) {
400 throw new IllegalArgumentException("getEnumClass() must return a superclass of this class");
401 }
402
403 Entry entry;
404 synchronized( Enum.class ) { // LANG-334
405 // create entry
406 entry = (Entry) cEnumClasses.get(enumClass);
407 if (entry == null) {
408 entry = createEntry(enumClass);
409 Map myMap = new WeakHashMap( ); // we avoid the (Map) constructor to achieve JDK 1.2 support
410 myMap.putAll( cEnumClasses );
411 myMap.put(enumClass, entry);
412 cEnumClasses = myMap;
413 }
414 }
415 if (entry.map.containsKey(name)) {
416 throw new IllegalArgumentException("The Enum name must be unique, '" + name + "' has already been added");
417 }
418 entry.map.put(name, this);
419 entry.list.add(this);
420 }
421
422 /**
423 * <p>Handle the deserialization of the class to ensure that multiple
424 * copies are not wastefully created, or illegal enum types created.</p>
425 *
426 * @return the resolved object
427 */
428 protected Object readResolve() {
429 Entry entry = (Entry) cEnumClasses.get(getEnumClass());
430 if (entry == null) {
431 return null;
432 }
433 return entry.map.get(getName());
434 }
435
436 //--------------------------------------------------------------------------------
437
438 /**
439 * <p>Gets an <code>Enum</code> object by class and name.</p>
440 *
441 * @param enumClass the class of the Enum to get, must not
442 * be <code>null</code>
443 * @param name the name of the <code>Enum</code> to get,
444 * may be <code>null</code>
445 * @return the enum object, or <code>null</code> if the enum does not exist
446 * @throws IllegalArgumentException if the enum class
447 * is <code>null</code>
448 */
449 protected static Enum getEnum(Class enumClass, String name) {
450 Entry entry = getEntry(enumClass);
451 if (entry == null) {
452 return null;
453 }
454 return (Enum) entry.map.get(name);
455 }
456
457 /**
458 * <p>Gets the <code>Map</code> of <code>Enum</code> objects by
459 * name using the <code>Enum</code> class.</p>
460 *
461 * <p>If the requested class has no enum objects an empty
462 * <code>Map</code> is returned.</p>
463 *
464 * @param enumClass the class of the <code>Enum</code> to get,
465 * must not be <code>null</code>
466 * @return the enum object Map
467 * @throws IllegalArgumentException if the enum class is <code>null</code>
468 * @throws IllegalArgumentException if the enum class is not a subclass of Enum
469 */
470 protected static Map getEnumMap(Class enumClass) {
471 Entry entry = getEntry(enumClass);
472 if (entry == null) {
473 return EMPTY_MAP;
474 }
475 return entry.unmodifiableMap;
476 }
477
478 /**
479 * <p>Gets the <code>List</code> of <code>Enum</code> objects using the
480 * <code>Enum</code> class.</p>
481 *
482 * <p>The list is in the order that the objects were created (source code order).
483 * If the requested class has no enum objects an empty <code>List</code> is
484 * returned.</p>
485 *
486 * @param enumClass the class of the <code>Enum</code> to get,
487 * must not be <code>null</code>
488 * @return the enum object Map
489 * @throws IllegalArgumentException if the enum class is <code>null</code>
490 * @throws IllegalArgumentException if the enum class is not a subclass of Enum
491 */
492 protected static List getEnumList(Class enumClass) {
493 Entry entry = getEntry(enumClass);
494 if (entry == null) {
495 return Collections.EMPTY_LIST;
496 }
497 return entry.unmodifiableList;
498 }
499
500 /**
501 * <p>Gets an <code>Iterator</code> over the <code>Enum</code> objects in
502 * an <code>Enum</code> class.</p>
503 *
504 * <p>The <code>Iterator</code> is in the order that the objects were
505 * created (source code order). If the requested class has no enum
506 * objects an empty <code>Iterator</code> is returned.</p>
507 *
508 * @param enumClass the class of the <code>Enum</code> to get,
509 * must not be <code>null</code>
510 * @return an iterator of the Enum objects
511 * @throws IllegalArgumentException if the enum class is <code>null</code>
512 * @throws IllegalArgumentException if the enum class is not a subclass of Enum
513 */
514 protected static Iterator iterator(Class enumClass) {
515 return Enum.getEnumList(enumClass).iterator();
516 }
517
518 //-----------------------------------------------------------------------
519 /**
520 * <p>Gets an <code>Entry</code> from the map of Enums.</p>
521 *
522 * @param enumClass the class of the <code>Enum</code> to get
523 * @return the enum entry
524 */
525 private static Entry getEntry(Class enumClass) {
526 if (enumClass == null) {
527 throw new IllegalArgumentException("The Enum Class must not be null");
528 }
529 if (Enum.class.isAssignableFrom(enumClass) == false) {
530 throw new IllegalArgumentException("The Class must be a subclass of Enum");
531 }
532 Entry entry = (Entry) cEnumClasses.get(enumClass);
533
534 if (entry == null) {
535 try {
536 // LANG-76 - try to force class initialization for JDK 1.5+
537 Class.forName(enumClass.getName(), true, enumClass.getClassLoader());
538 entry = (Entry) cEnumClasses.get(enumClass);
539 } catch (Exception e) {
540 // Ignore
541 }
542 }
543
544 return entry;
545 }
546
547 /**
548 * <p>Creates an <code>Entry</code> for storing the Enums.</p>
549 *
550 * <p>This accounts for subclassed Enums.</p>
551 *
552 * @param enumClass the class of the <code>Enum</code> to get
553 * @return the enum entry
554 */
555 private static Entry createEntry(Class enumClass) {
556 Entry entry = new Entry();
557 Class cls = enumClass.getSuperclass();
558 while (cls != null && cls != Enum.class && cls != ValuedEnum.class) {
559 Entry loopEntry = (Entry) cEnumClasses.get(cls);
560 if (loopEntry != null) {
561 entry.list.addAll(loopEntry.list);
562 entry.map.putAll(loopEntry.map);
563 break; // stop here, as this will already have had superclasses added
564 }
565 cls = cls.getSuperclass();
566 }
567 return entry;
568 }
569
570 //-----------------------------------------------------------------------
571 /**
572 * <p>Retrieve the name of this Enum item, set in the constructor.</p>
573 *
574 * @return the <code>String</code> name of this Enum item
575 */
576 public final String getName() {
577 return iName;
578 }
579
580 /**
581 * <p>Retrieves the Class of this Enum item, set in the constructor.</p>
582 *
583 * <p>This is normally the same as <code>getClass()</code>, but for
584 * advanced Enums may be different. If overridden, it must return a
585 * constant value.</p>
586 *
587 * @return the <code>Class</code> of the enum
588 * @since 2.0
589 */
590 public Class getEnumClass() {
591 return getClass();
592 }
593
594 /**
595 * <p>Tests for equality.</p>
596 *
597 * <p>Two Enum objects are considered equal
598 * if they have the same class names and the same names.
599 * Identity is tested for first, so this method usually runs fast.</p>
600 *
601 * <p>If the parameter is in a different class loader than this instance,
602 * reflection is used to compare the names.</p>
603 *
604 * @param other the other object to compare for equality
605 * @return <code>true</code> if the Enums are equal
606 */
607 public final boolean equals(Object other) {
608 if (other == this) {
609 return true;
610 } else if (other == null) {
611 return false;
612 } else if (other.getClass() == this.getClass()) {
613 // Ok to do a class cast to Enum here since the test above
614 // guarantee both
615 // classes are in the same class loader.
616 return iName.equals(((Enum) other).iName);
617 } else {
618 // This and other are in different class loaders, we must check indirectly
619 if (other.getClass().getName().equals(this.getClass().getName()) == false) {
620 return false;
621 }
622 return iName.equals( getNameInOtherClassLoader(other) );
623 }
624 }
625
626 /**
627 * <p>Returns a suitable hashCode for the enumeration.</p>
628 *
629 * @return a hashcode based on the name
630 */
631 public final int hashCode() {
632 return iHashCode;
633 }
634
635 /**
636 * <p>Tests for order.</p>
637 *
638 * <p>The default ordering is alphabetic by name, but this
639 * can be overridden by subclasses.</p>
640 *
641 * <p>If the parameter is in a different class loader than this instance,
642 * reflection is used to compare the names.</p>
643 *
644 * @see java.lang.Comparable#compareTo(Object)
645 * @param other the other object to compare to
646 * @return -ve if this is less than the other object, +ve if greater
647 * than, <code>0</code> of equal
648 * @throws ClassCastException if other is not an Enum
649 * @throws NullPointerException if other is <code>null</code>
650 */
651 public int compareTo(Object other) {
652 if (other == this) {
653 return 0;
654 }
655 if (other.getClass() != this.getClass()) {
656 if (other.getClass().getName().equals(this.getClass().getName())) {
657 return iName.compareTo( getNameInOtherClassLoader(other) );
658 }
659 throw new ClassCastException(
660 "Different enum class '" + ClassUtils.getShortClassName(other.getClass()) + "'");
661 }
662 return iName.compareTo(((Enum) other).iName);
663 }
664
665 /**
666 * <p>Use reflection to return an objects class name.</p>
667 *
668 * @param other The object to determine the class name for
669 * @return The class name
670 */
671 private String getNameInOtherClassLoader(Object other) {
672 try {
673 Method mth = other.getClass().getMethod("getName", null);
674 String name = (String) mth.invoke(other, null);
675 return name;
676 } catch (NoSuchMethodException e) {
677 // ignore - should never happen
678 } catch (IllegalAccessException e) {
679 // ignore - should never happen
680 } catch (InvocationTargetException e) {
681 // ignore - should never happen
682 }
683 throw new IllegalStateException("This should not happen");
684 }
685
686 /**
687 * <p>Human readable description of this Enum item.</p>
688 *
689 * @return String in the form <code>type[name]</code>, for example:
690 * <code>Color[Red]</code>. Note that the package name is stripped from
691 * the type name.
692 */
693 public String toString() {
694 if (iToString == null) {
695 String shortName = ClassUtils.getShortClassName(getEnumClass());
696 iToString = shortName + "[" + getName() + "]";
697 }
698 return iToString;
699 }
700
701}
Note: See TracBrowser for help on using the repository browser.