/
Enum.java
604 lines (574 loc) · 20.3 KB
/
Enum.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
/*
* Copyright 2002-2005 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.lang.enum;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.ClassUtils;
import org.apache.commons.lang.StringUtils;
/**
* <p>Abstract superclass for type-safe enums.</p>
*
* <p>One feature of the C programming language lacking in Java is enumerations. The
* C implementation based on ints was poor and open to abuse. The original Java
* recommendation and most of the JDK also uses int constants. It has been recognised
* however that a more robust type-safe class-based solution can be designed. This
* class follows the basic Java type-safe enumeration pattern.</p>
*
* <p><em>NOTE:</em>Due to the way in which Java ClassLoaders work, comparing
* Enum objects should always be done using <code>equals()</code>, not <code>==</code>.
* The equals() method will try == first so in most cases the effect is the same.</p>
*
* <p>Of course, if you actually want (or don't mind) Enums in different class
* loaders being non-equal, then you can use <code>==</code>.</p>
*
* <h4>Simple Enums</h4>
*
* <p>To use this class, it must be subclassed. For example:</p>
*
* <pre>
* public final class ColorEnum extends Enum {
* public static final ColorEnum RED = new ColorEnum("Red");
* public static final ColorEnum GREEN = new ColorEnum("Green");
* public static final ColorEnum BLUE = new ColorEnum("Blue");
*
* private ColorEnum(String color) {
* super(color);
* }
*
* public static ColorEnum getEnum(String color) {
* return (ColorEnum) getEnum(ColorEnum.class, color);
* }
*
* public static Map getEnumMap() {
* return getEnumMap(ColorEnum.class);
* }
*
* public static List getEnumList() {
* return getEnumList(ColorEnum.class);
* }
*
* public static Iterator iterator() {
* return iterator(ColorEnum.class);
* }
* }
* </pre>
*
* <p>As shown, each enum has a name. This can be accessed using <code>getName</code>.</p>
*
* <p>The <code>getEnum</code> and <code>iterator</code> methods are recommended.
* Unfortunately, Java restrictions require these to be coded as shown in each subclass.
* An alternative choice is to use the {@link EnumUtils} class.</p>
*
* <h4>Subclassed Enums</h4>
* <p>A hierarchy of Enum classes can be built. In this case, the superclass is
* unaffected by the addition of subclasses (as per normal Java). The subclasses
* may add additional Enum constants <em>of the type of the superclass</em>. The
* query methods on the subclass will return all of the Enum constants from the
* superclass and subclass.</p>
*
* <pre>
* public final class ExtraColorEnum extends ColorEnum {
* // NOTE: Color enum declared above is final, change that to get this
* // example to compile.
* public static final ColorEnum YELLOW = new ExtraColorEnum("Yellow");
*
* private ExtraColorEnum(String color) {
* super(color);
* }
*
* public static ColorEnum getEnum(String color) {
* return (ColorEnum) getEnum(ExtraColorEnum.class, color);
* }
*
* public static Map getEnumMap() {
* return getEnumMap(ExtraColorEnum.class);
* }
*
* public static List getEnumList() {
* return getEnumList(ExtraColorEnum.class);
* }
*
* public static Iterator iterator() {
* return iterator(ExtraColorEnum.class);
* }
* }
* </pre>
*
* <p>This example will return RED, GREEN, BLUE, YELLOW from the List and iterator
* methods in that order. The RED, GREEN and BLUE instances will be the same (==)
* as those from the superclass ColorEnum. Note that YELLOW is declared as a
* ColorEnum and not an ExtraColorEnum.</p>
*
* <h4>Functional Enums</h4>
*
* <p>The enums can have functionality by defining subclasses and
* overriding the <code>getEnumClass()</code> method:</p>
*
* <pre>
* public static final OperationEnum PLUS = new PlusOperation();
* private static final class PlusOperation extends OperationEnum {
* private PlusOperation() {
* super("Plus");
* }
* public int eval(int a, int b) {
* return a + b;
* }
* }
* public static final OperationEnum MINUS = new MinusOperation();
* private static final class MinusOperation extends OperationEnum {
* private MinusOperation() {
* super("Minus");
* }
* public int eval(int a, int b) {
* return a - b;
* }
* }
*
* private OperationEnum(String color) {
* super(color);
* }
*
* public final Class getEnumClass() { // NOTE: new method!
* return OperationEnum.class;
* }
*
* public abstract double eval(double a, double b);
*
* public static OperationEnum getEnum(String name) {
* return (OperationEnum) getEnum(OperationEnum.class, name);
* }
*
* public static Map getEnumMap() {
* return getEnumMap(OperationEnum.class);
* }
*
* public static List getEnumList() {
* return getEnumList(OperationEnum.class);
* }
*
* public static Iterator iterator() {
* return iterator(OperationEnum.class);
* }
* }
* </pre>
* <p>The code above will work on JDK 1.2. If JDK1.3 and later is used,
* the subclasses may be defined as anonymous.</p>
*
* <h4>Nested class Enums</h4>
*
* <p>Care must be taken with class loading when defining a static nested class
* for enums. The static nested class can be loaded without the surrounding outer
* class being loaded. This can result in an empty list/map/iterator being returned.
* One solution is to define a static block that references the outer class where
* the constants are defined. For example:</p>
*
* <pre>
* public final class Outer {
* public static final BWEnum BLACK = new BWEnum("Black");
* public static final BWEnum WHITE = new BWEnum("White");
*
* // static nested enum class
* public static final class BWEnum extends Enum {
*
* static {
* // explicitly reference BWEnum class to force constants to load
* Object obj = Outer.BLACK;
* }
*
* // ... other methods omitted
* }
* }
* </pre>
*
* <p>Although the above solves the problem, it is not recommended. The best solution
* is to define the constants in the enum class, and hold references in the outer class:
*
* <pre>
* public final class Outer {
* public static final BWEnum BLACK = BWEnum.BLACK;
* public static final BWEnum WHITE = BWEnum.WHITE;
*
* // static nested enum class
* public static final class BWEnum extends Enum {
* // only define constants in enum classes - private if desired
* private static final BWEnum BLACK = new BWEnum("Black");
* private static final BWEnum WHITE = new BWEnum("White");
*
* // ... other methods omitted
* }
* }
* </pre>
*
* <p>For more details, see the 'Nested' test cases.
*
* @deprecated Replaced by {@link org.apache.commons.lang.enums.Enum org.apache.commons.lang.enums.Enum}
* and will be removed in version 3.0. All classes in this package are deprecated and repackaged to
* {@link org.apache.commons.lang.enums} since <code>enum</code> is a Java 1.5 keyword.
* @see org.apache.commons.lang.enums.Enum
* @author Apache Avalon project
* @author Stephen Colebourne
* @author Chris Webb
* @author Mike Bowler
* @since 1.0
* @version $Id$
*/
public abstract class Enum implements Comparable, Serializable {
/** Lang version 1.0.1 serial compatibility */
private static final long serialVersionUID = -487045951170455942L;
// After discussion, the default size for HashMaps is used, as the
// sizing algorithm changes across the JDK versions
/**
* An empty <code>Map</code>, as JDK1.2 didn't have an empty map.
*/
private static final Map EMPTY_MAP = Collections.unmodifiableMap(new HashMap(0));
/**
* <code>Map</code>, key of class name, value of <code>Entry</code>.
*/
private static final Map cEnumClasses = new HashMap();
/**
* The string representation of the Enum.
*/
private final String iName;
/**
* The hashcode representation of the Enum.
*/
private transient final int iHashCode;
/**
* The toString representation of the Enum.
* @since 2.0
*/
protected transient String iToString = null;
/**
* <p>Enable the iterator to retain the source code order.</p>
*/
private static class Entry {
/**
* Map of Enum name to Enum.
*/
final Map map = new HashMap();
/**
* Map of Enum name to Enum.
*/
final Map unmodifiableMap = Collections.unmodifiableMap(map);
/**
* List of Enums in source code order.
*/
final List list = new ArrayList(25);
/**
* Map of Enum name to Enum.
*/
final List unmodifiableList = Collections.unmodifiableList(list);
/**
* <p>Restrictive constructor.</p>
*/
private Entry() {
}
}
/**
* <p>Constructor to add a new named item to the enumeration.</p>
*
* @param name the name of the enum object,
* must not be empty or <code>null</code>
* @throws IllegalArgumentException if the name is <code>null</code>
* or an empty string
* @throws IllegalArgumentException if the getEnumClass() method returns
* a null or invalid Class
*/
protected Enum(String name) {
super();
init(name);
iName = name;
iHashCode = 7 + getEnumClass().hashCode() + 3 * name.hashCode();
// cannot create toString here as subclasses may want to include other data
}
/**
* Initializes the enumeration.
*
* @param name the enum name
* @throws IllegalArgumentException if the name is null or empty or duplicate
* @throws IllegalArgumentException if the enumClass is null or invalid
*/
private void init(String name) {
if (StringUtils.isEmpty(name)) {
throw new IllegalArgumentException("The Enum name must not be empty or null");
}
Class enumClass = getEnumClass();
if (enumClass == null) {
throw new IllegalArgumentException("getEnumClass() must not be null");
}
Class cls = getClass();
boolean ok = false;
while (cls != null && cls != Enum.class && cls != ValuedEnum.class) {
if (cls == enumClass) {
ok = true;
break;
}
cls = cls.getSuperclass();
}
if (ok == false) {
throw new IllegalArgumentException("getEnumClass() must return a superclass of this class");
}
// create entry
Entry entry = (Entry) cEnumClasses.get(enumClass);
if (entry == null) {
entry = createEntry(enumClass);
cEnumClasses.put(enumClass, entry);
}
if (entry.map.containsKey(name)) {
throw new IllegalArgumentException("The Enum name must be unique, '" + name + "' has already been added");
}
entry.map.put(name, this);
entry.list.add(this);
}
/**
* <p>Handle the deserialization of the class to ensure that multiple
* copies are not wastefully created, or illegal enum types created.</p>
*
* @return the resolved object
*/
protected Object readResolve() {
Entry entry = (Entry) cEnumClasses.get(getEnumClass());
if (entry == null) {
return null;
}
return (Enum) entry.map.get(getName());
}
//--------------------------------------------------------------------------------
/**
* <p>Gets an <code>Enum</code> object by class and name.</p>
*
* @param enumClass the class of the Enum to get, must not
* be <code>null</code>
* @param name the name of the <code>Enum</code> to get,
* may be <code>null</code>
* @return the enum object, or <code>null</code> if the enum does not exist
* @throws IllegalArgumentException if the enum class
* is <code>null</code>
*/
protected static Enum getEnum(Class enumClass, String name) {
Entry entry = getEntry(enumClass);
if (entry == null) {
return null;
}
return (Enum) entry.map.get(name);
}
/**
* <p>Gets the <code>Map</code> of <code>Enum</code> objects by
* name using the <code>Enum</code> class.</p>
*
* <p>If the requested class has no enum objects an empty
* <code>Map</code> is returned.</p>
*
* @param enumClass the class of the <code>Enum</code> to get,
* must not be <code>null</code>
* @return the enum object Map
* @throws IllegalArgumentException if the enum class is <code>null</code>
* @throws IllegalArgumentException if the enum class is not a subclass of Enum
*/
protected static Map getEnumMap(Class enumClass) {
Entry entry = getEntry(enumClass);
if (entry == null) {
return EMPTY_MAP;
}
return entry.unmodifiableMap;
}
/**
* <p>Gets the <code>List</code> of <code>Enum</code> objects using the
* <code>Enum</code> class.</p>
*
* <p>The list is in the order that the objects were created (source code order).
* If the requested class has no enum objects an empty <code>List</code> is
* returned.</p>
*
* @param enumClass the class of the <code>Enum</code> to get,
* must not be <code>null</code>
* @return the enum object Map
* @throws IllegalArgumentException if the enum class is <code>null</code>
* @throws IllegalArgumentException if the enum class is not a subclass of Enum
*/
protected static List getEnumList(Class enumClass) {
Entry entry = getEntry(enumClass);
if (entry == null) {
return Collections.EMPTY_LIST;
}
return entry.unmodifiableList;
}
/**
* <p>Gets an <code>Iterator</code> over the <code>Enum</code> objects in
* an <code>Enum</code> class.</p>
*
* <p>The <code>Iterator</code> is in the order that the objects were
* created (source code order). If the requested class has no enum
* objects an empty <code>Iterator</code> is returned.</p>
*
* @param enumClass the class of the <code>Enum</code> to get,
* must not be <code>null</code>
* @return an iterator of the Enum objects
* @throws IllegalArgumentException if the enum class is <code>null</code>
* @throws IllegalArgumentException if the enum class is not a subclass of Enum
*/
protected static Iterator iterator(Class enumClass) {
return Enum.getEnumList(enumClass).iterator();
}
//-----------------------------------------------------------------------
/**
* <p>Gets an <code>Entry</code> from the map of Enums.</p>
*
* @param enumClass the class of the <code>Enum</code> to get
* @return the enum entry
*/
private static Entry getEntry(Class enumClass) {
if (enumClass == null) {
throw new IllegalArgumentException("The Enum Class must not be null");
}
if (Enum.class.isAssignableFrom(enumClass) == false) {
throw new IllegalArgumentException("The Class must be a subclass of Enum");
}
Entry entry = (Entry) cEnumClasses.get(enumClass);
return entry;
}
/**
* <p>Creates an <code>Entry</code> for storing the Enums.</p>
*
* <p>This accounts for subclassed Enums.</p>
*
* @param enumClass the class of the <code>Enum</code> to get
* @return the enum entry
*/
private static Entry createEntry(Class enumClass) {
Entry entry = new Entry();
Class cls = enumClass.getSuperclass();
while (cls != null && cls != Enum.class && cls != ValuedEnum.class) {
Entry loopEntry = (Entry) cEnumClasses.get(cls);
if (loopEntry != null) {
entry.list.addAll(loopEntry.list);
entry.map.putAll(loopEntry.map);
break; // stop here, as this will already have had superclasses added
}
cls = cls.getSuperclass();
}
return entry;
}
//-----------------------------------------------------------------------
/**
* <p>Retrieve the name of this Enum item, set in the constructor.</p>
*
* @return the <code>String</code> name of this Enum item
*/
public final String getName() {
return iName;
}
/**
* <p>Retrieves the Class of this Enum item, set in the constructor.</p>
*
* <p>This is normally the same as <code>getClass()</code>, but for
* advanced Enums may be different. If overridden, it must return a
* constant value.</p>
*
* @return the <code>Class</code> of the enum
* @since 2.0
*/
public Class getEnumClass() {
return getClass();
}
/**
* <p>Tests for equality.</p>
*
* <p>Two Enum objects are considered equal
* if they have the same class names and the same names.
* Identity is tested for first, so this method usually runs fast.</p>
*
* <p>If the parameter is in a different class loader than this instance,
* reflection is used to compare the names.</p>
*
* @param other the other object to compare for equality
* @return <code>true</code> if the Enums are equal
*/
public final boolean equals(Object other) {
if (other == this) {
return true;
} else if (other == null) {
return false;
} else if (other.getClass() == this.getClass()) {
// Ok to do a class cast to Enum here since the test above
// guarantee both
// classes are in the same class loader.
return iName.equals(((Enum) other).iName);
} else {
// This and other are in different class loaders, we must use reflection.
try {
Method mth = other.getClass().getMethod("getName", null);
String name = (String) mth.invoke(other, null);
return iName.equals(name);
} catch (NoSuchMethodException e) {
// ignore - should never happen
} catch (IllegalAccessException e) {
// ignore - should never happen
} catch (InvocationTargetException e) {
// ignore - should never happen
}
return false;
}
}
/**
* <p>Returns a suitable hashCode for the enumeration.</p>
*
* @return a hashcode based on the name
*/
public final int hashCode() {
return iHashCode;
}
/**
* <p>Tests for order.</p>
*
* <p>The default ordering is alphabetic by name, but this
* can be overridden by subclasses.</p>
*
* @see java.lang.Comparable#compareTo(Object)
* @param other the other object to compare to
* @return -ve if this is less than the other object, +ve if greater
* than, <code>0</code> of equal
* @throws ClassCastException if other is not an Enum
* @throws NullPointerException if other is <code>null</code>
*/
public int compareTo(Object other) {
if (other == this) {
return 0;
}
return iName.compareTo(((Enum) other).iName);
}
/**
* <p>Human readable description of this Enum item.</p>
*
* @return String in the form <code>type[name]</code>, for example:
* <code>Color[Red]</code>. Note that the package name is stripped from
* the type name.
*/
public String toString() {
if (iToString == null) {
String shortName = ClassUtils.getShortClassName(getEnumClass());
iToString = shortName + "[" + getName() + "]";
}
return iToString;
}
}