I posted earlier on reinventing the enum in Java for pre-JDK5 code. But I am also annoyed by one limitation in JDK5 enums, than they are not extendable at runtime or otherwise. So I copied the Enum class from the JDK and modified it to support non-final subclasses.
An example class and its extension from the unit tests, also demonstrating the abstract feature present in the JDK enum:
1 private abstract static class TestEnumA
2 extends ExtensibleEnum<TestEnumA> {
3 public static final TestEnumA A = new TestEnumA("A") {
4 void foo() {
5 }
6 };
7 public static final TestEnumA B = new TestEnumA("B") {
8 void foo() {
9 }
10 };
11
12 static {
13 new TestEnumA("C") {
14 void foo() {
15 }
16 };
17 }
18
19 abstract void foo();
20
21 public static TestEnumA[] values() {
22 return ExtensibleEnum.values(TestEnumA.class);
23 }
24
25 public static TestEnumA valueOf(final String name) {
26 return ExtensibleEnum.valueOf(TestEnumA.class, name);
27 }
28
29 protected TestEnumA(final String name) {
30 super(name);
31 }
32 }
33
34 private static abstract class TestEnumB
35 extends TestEnumA {
36 public static final TestEnumB D = new TestEnumB("D") {
37 void foo() {
38 }
39 };
40
41 public static TestEnumB[] values() {
42 return ExtensibleEnum.values(TestEnumB.class);
43 }
44
45 public static TestEnumB valueOf(final String name) {
46 return ExtensibleEnum.valueOf(TestEnumB.class, name);
47 }
48
49 protected TestEnumB(final String name) {
50 super(name);
51 }
52 }
53 } Note that if you ask for values() from TestEnumA, they include TestEnumB.D—a TestEnumA by subclassing.
Here is the base class. I am also trying out the "Code as HTML" plugin for IDEA—I hope the colorizing is not too much:
1 /*
2 * Copyright (c) 2006 B. K. Oxley (binkley) <binkley@alumni.rice.edu> under the
3 * terms of the Artistic License, including Clause 8. See
4 * http://www.opensource.org/licenses/artistic-license.php.
5 */
6
7 import java.io.Serializable;
8 import java.lang.reflect.Array;
9 import java.util.ArrayList;
10 import java.util.EnumMap;
11 import java.util.HashMap;
12 import java.util.List;
13 import java.util.Map;
14
15 /**
16 * {@code ExtensibleEnum} <strong>needs documentation</strong>.
17 *
18 * @author <a href="mailto:binkley@alumni.rice.edu">B. K. Oxley (binkley)</a>
19 * @version $Id$
20 * @todo {@code RuntimeExtensibleEnum} needs documentation
21 * @noinspection FinalizeDoesntCallSuperFinalize
22 * @since Dec 31, 2005 8:38:03 AM
23 */
24 public abstract class ExtensibleEnum<E extends ExtensibleEnum<E>>
25 implements Comparable<E>, Serializable {
26 private static final Map<Class<? extends ExtensibleEnum<?>>,
27 Map<String, ? extends ExtensibleEnum<?>>> BY_NAME
28 = new HashMap<Class<? extends ExtensibleEnum<?>>,
29 Map<String, ? extends ExtensibleEnum<?>>>(4);
30 private static final Map<Class<? extends ExtensibleEnum<?>>,
31 List<? extends ExtensibleEnum<?>>> BY_ORDINAL
32 = new HashMap<Class<? extends ExtensibleEnum<?>>,
33 List<? extends ExtensibleEnum<?>>>(4);
34 /**
35 * The JDK5 enum has ordinals in stepwise order; this implementation relaxes
36 * the constraint to simply monotonically increasing.
37 *
38 * @noinspection StaticNonFinalField
39 */
40 private static int ORDINAL;
41
42 /**
43 * The name of this enum constant, as declared in the enum declaration. Most
44 * programmers should use the {@link #toString} method rather than accessing
45 * this field.
46 */
47 private final String name;
48
49 /**
50 * Returns the name of this enum constant, exactly as declared in its enum
51 * declaration.
52 * <p/>
53 * <b>Most programmers should use the {@link #toString} method in preference
54 * to this one, as the toString method may return a more user-friendly
55 * name.</b> This method is designed primarily for use in specialized
56 * situations where correctness depends on getting the exact name, which
57 * will not vary from release to release.
58 *
59 * @return the name of this enum constant
60 */
61 public final String name() {
62 return name;
63 }
64
65 /**
66 * The ordinal of this enumeration constant (its position in the enum
67 * declaration, where the initial constant is assigned an ordinal of zero).
68 * <p/>
69 * Most programmers will have no use for this field. It is designed for use
70 * by sophisticated enum-based data structures, such as {@link
71 * java.util.EnumSet} and {@link EnumMap}.
72 */
73 private final int ordinal = ORDINAL++;
74
75 /**
76 * Returns the ordinal of this enumeration constant (its position in its
77 * enum declaration, where the initial constant is assigned an ordinal of
78 * zero).
79 * <p/>
80 * Most programmers will have no use for this method. It is designed for
81 * use by sophisticated enum-based data structures, such as {@link
82 * java.util.EnumSet} and {@link EnumMap}.
83 *
84 * @return the ordinal of this enumeration constant
85 */
86 public final int ordinal() {
87 return ordinal;
88 }
89
90 /**
91 * Sole constructor. Programmers cannot invoke this constructor. It is for
92 * use by code emitted by the compiler in response to enum type
93 * declarations.
94 *
95 * @param name - The name of this enum constant, which is the identifier
96 * used to declare it.
97 */
98 protected ExtensibleEnum(final String name) {
99 if (null == name)
100 throw new NullPointerException("Missing name");
101
102 this.name = name;
103
104 new EnumInstall(getDeclaringClass(), name, this).install();
105 }
106
107 private static class EnumInstall<T extends ExtensibleEnum<T>, U extends T> {
108 private final Class<T> enumType;
109 private final String name;
110 private final U instance;
111
112 EnumInstall(final Class<T> enumType, final String name,
113 final U instance) {
114 this.enumType = enumType;
115 this.name = name;
116 this.instance = instance;
117 }
118
119 void install() {
120 if (!ExtensibleEnum.class.isAssignableFrom(enumType))
121 return;
122
123 final EnumInstall<? super T, ? super U> parentInstall
124 = new EnumInstall(enumType.getSuperclass(), name, instance);
125
126 parentInstall.install();
127
128 try {
129 putEnum(enumType, name, instance);
130
131 } catch (final IllegalArgumentException e) {
132 parentInstall.uninstall();
133
134 throw e;
135 }
136 }
137
138 void uninstall() {
139 unputEnum(enumType, name);
140 }
141 }
142
143 private static <T extends ExtensibleEnum<T>, U extends T> void putEnum(
144 final Class<T> enumType, final String name, final U instance) {
145 if (!BY_NAME.containsKey(enumType)) {
146 BY_NAME.put(enumType, new HashMap<String, T>(4));
147 BY_ORDINAL.put(enumType, new ArrayList<T>(4));
148 }
149
150 final Map<String, T> nameMap = byName(enumType);
151
152 if (nameMap.containsKey(name))
153 throw new IllegalArgumentException("Duplicate name: " + name);
154
155 nameMap.put(name, instance);
156 byOrdinal(enumType).add(instance);
157 }
158
159 private static <T extends ExtensibleEnum<T>> void unputEnum(
160 final Class<T> enumType, final String name) {
161 if (!BY_NAME.containsKey(enumType))
162 return;
163
164 byName(enumType).remove(name);
165
166 final List<T> list = byOrdinal(enumType);
167
168 list.remove(list.size() - 1);
169 }
170
171 /**
172 * Returns the name of this enum constant, as contained in the declaration.
173 * This method may be overridden, though it typically isn't necessary or
174 * desirable. An enum type should override this method when a more
175 * "programmer-friendly" string form exists.
176 *
177 * @return the name of this enum constant
178 */
179 @Override
180 public String toString() {
181 return name;
182 }
183
184 /**
185 * Returns true if the specified object is equal to this enum constant.
186 *
187 * @param other the object to be compared for equality with this object.
188 *
189 * @return true if the specified object is equal to this enum constant.
190 */
191 @Override
192 public final boolean equals(final Object other) {
193 return this == other;
194 }
195
196 /**
197 * Returns a hash code for this enum constant.
198 *
199 * @return a hash code for this enum constant.
200 */
201 @Override
202 public final int hashCode() {
203 return System.identityHashCode(this);
204 }
205
206 /**
207 * Throws CloneNotSupportedException. This guarantees that enums are never
208 * cloned, which is necessary to preserve their "singleton" status.
209 *
210 * @return (never returns)
211 */
212 @Override
213 protected final Object clone()
214 throws CloneNotSupportedException {
215 throw new CloneNotSupportedException();
216 }
217
218 /**
219 * Compares this enum with the specified object for order. Returns a
220 * negative integer, zero, or a positive integer as this object is less
221 * than, equal to, or greater than the specified object.
222 * <p/>
223 * ExtensibleEnum constants are only comparable to other enum constants of
224 * the same enum type. The natural order implemented by this method is the
225 * order in which the constants are declared.
226 *
227 * @noinspection ObjectEquality
228 */
229 public final int compareTo(final E o) {
230 if (getClass() != o.getClass() // optimization
231 && getDeclaringClass() != o.getDeclaringClass())
232 throw new ClassCastException();
233
234 return new Integer(ordinal).compareTo(o.ordinal);
235 }
236
237 public static <T extends ExtensibleEnum> T[] values(
238 final Class<T> enumType) {
239 final List<T> values = byOrdinal(enumType);
240
241 return values.toArray((T[]) Array.newInstance(enumType, values.size()));
242 }
243
244 /**
245 * Returns the enum constant of the specified enum type with the specified
246 * name. The name must match exactly an identifier used to declare an enum
247 * constant in this type. (Extraneous whitespace characters are not
248 * permitted.)
249 *
250 * @param enumType the <tt>Class</tt> object of the enum type from which to
251 * return a constant
252 * @param name the name of the constant to return
253 *
254 * @return the enum constant of the specified enum type with the specified
255 * name
256 *
257 * @throws IllegalArgumentException if the specified enum type has no
258 * constant with the specified name, or the specified class object does not
259 * represent an enum type
260 * @throws NullPointerException if <tt>enumType</tt> or <tt>name</tt> is
261 * null
262 * @since 1.5
263 */
264 public static <T extends ExtensibleEnum> T valueOf(final Class<T> enumType,
265 final String name) {
266 // IDEA says cast is useless; JDK5 javac says it is necessary
267 //noinspection RedundantCast
268 final T result = (T) byName(enumType).get(name);
269
270 if (null != result)
271 return result;
272 if (null == name)
273 throw new NullPointerException("Name is null");
274
275 throw new IllegalArgumentException(
276 "No enum const " + enumType + '.' + name);
277 }
278
279 /**
280 * enum classes cannot have finalize methods.
281 */
282 @Override
283 protected final void finalize() { }
284
285 protected Class<?> getDeclaringClass() {
286 final Class<?> enumType = getClass();
287
288 return enumType.isAnonymousClass()
289 ? enumType.getSuperclass()
290 : enumType;
291 }
292
293 private static <T extends ExtensibleEnum<T>> Map<String, T> byName(
294 final Class<T> enumType) {
295 return (Map<String, T>) BY_NAME.get(enumType);
296 }
297
298 private static <T extends ExtensibleEnum<T>> List<T> byOrdinal(
299 final Class<T> enumType) {
300 return (List<T>) BY_ORDINAL.get(enumType);
301 }
302 } Happy New Year!