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!
No comments:
Post a Comment