1
2
3
4
5
6 package ch.twiddlefinger.inet.rewinder.model.parser.conversion;
7
8 import java.io.InvalidObjectException;
9 import java.io.ObjectStreamException;
10 import java.io.Serializable;
11
12 import java.util.Collection;
13 import java.util.Collections;
14
15
16 /***
17 * <p> This class represents the base class for all enumerates; its semantic
18 * is somewhat similar to the new JDK Tiger <code>enum</code> base type,
19 * with some additional capabilities. In particular:<ul>
20 * <li> The enumeration is extendable (if the constructor is not private).
21 * For example:<pre>
22 * public class Event {
23 * public static abstract class BaseId extends Enum {
24 * public static final Collection VALUES = getInstances(BaseId.class);
25 * }
26 * }
27 * public class MouseEvent extends Event {
28 * public static class Id extends BaseId {
29 * public static final Collection VALUES = getInstances(Id.class);
30 * public static final Id MOUSE_CLICKED = new Id();
31 * public static final Id MOUSE_PRESSED = new Id();
32 * public static final Id MOUSE_RELEASED = new Id();
33 * }
34 * }
35 * public class KeyEvent extends Event {
36 * public static class Id extends BaseId {
37 * public static final Collection VALUES = getInstances(Id.class);
38 * public static final Id KEY_PRESSED = new Id();
39 * public static final Id KEY_RELEASED = new Id();
40 * }
41 * }</pre></li>
42 * <li> {@link Enum}'s instances can be mapped to custom values (up to
43 * <code>long</code>) for use in <code>switch</code> statements,
44 * bit-wise sets (values power of 2) or look-up tables.
45 * Duplications (name or value) would result in exception thrown
46 * during initialization:
47 * <pre>
48 * public static final class Color extends Enum {
49 * public static final Collection VALUES = getInstances(Color.class);
50 * private Color(int value, String name) { // Not extendable (private)
51 * super(value, name);
52 * }
53 * public static final int RED_VALUE = 0xFF0000;
54 * public static final Color RED = new Color(RED_VALUE, "red");
55 *
56 * public static final int GREEN_VALUE = 0x00FF00;
57 * public static final Color GREEN = new Color(GREEN_VALUE, "green");
58 *
59 * public static final int BLUE_VALUE = 0x0000FF;
60 * public static final Color BLUE = new Color(BLUE_VALUE, "blue");
61 * }
62 * ...
63 * Color color;
64 * switch (color.intValue()) {
65 * case Color.RED_VALUE:
66 * case Color.GREEN_VALUE:
67 * case Color.BLUE_VALUE:
68 * }
69 * </pre></li>
70 * <li> Enum's hierarchies may exist, as {@link Enum} are
71 * not implicitly <code>final</code>. For example:<pre>
72 * public static class WeekDay extends Enum {
73 * public static final Collection VALUES = getInstances(WeekDay.class);
74 * public static final WeekDay SATURDAY = new WeekDay();
75 * public static final WeekDay SUNDAY = new WeekDay();
76 * }
77 * public static class WorkDay extends WeekDay {
78 * public static final Collection VALUES = getInstances(WorkDay.class);
79 * public static final WorkDay MONDAY = new WorkDay();
80 * public static final WorkDay TUESDAY = new WorkDay();
81 * public static final WorkDay WEDNESDAY = new WorkDay();
82 * public static final WorkDay THURSDAY = new WorkDay();
83 * public static final WorkDay FRIDAY = new WorkDay();
84 * }
85 * ...
86 * for (Iterator i = WorkDay.VALUES.iterator(); i.hasNext(); ) {
87 * // Iterates through the five workdays.
88 * }
89 * for (Iterator i = WeekDay.VALUES.iterator(); i.hasNext(); ) {
90 * // Iterates through the seven weekdays.
91 * }</pre></li>
92 * <li> Finally, this class does not require the latest JDK1.5+</li></ul>
93 *
94 * <p> <b>Limitations:</b> Unlike the JDK1.5 <code>enum</code> base type,
95 * instances of this class cannot be used directly in a <code>switch</code>
96 * statement. Although, for mapped's values (see <code>Color</code> example
97 * above) a <code>switch</code> on the enum's value is always possible.
98 * This approach is also faster as it does not require
99 * a "multiway if-statement" or "array indirection"
100 * (Ref. <a href=
101 * "http://jcp.org/aboutJava/communityprocess/jsr/tiger/enum.html">
102 * JDK 1.5 Enum: IV. Implementation Notes</a>).</p>
103 *
104 * <p><i> This class is <b>public domain</b> (not copyrighted).</i></p>
105 * @author <a href="mailto:jean-marie@dautelle.com">Jean-Marie Dautelle</a>
106 * @version 5.3, December 10, 2003
107 */
108 public abstract class Enum extends Number implements Serializable, Comparable {
109 /***
110 * Holds the value-to-enum mappings per class.
111 */
112 private static final FastMap VALUE_ENUM_PER_CLASS = new FastMap();
113
114 /***
115 * Holds the name-to-enum mappings per class.
116 */
117 private static final FastMap NAME_ENUM_PER_CLASS = new FastMap();
118 private static final LongValue KEY_VALUE = new LongValue(0);
119
120 /***
121 * Holds the value of this enum.
122 */
123 private transient final long _value;
124
125 /***
126 * Holds the name of this enum (for anonymous class: "className#ordinal").
127 */
128 private final String _name;
129
130 /***
131 * Default constructor. The value of this enum is one more than the previous
132 * enum (starting at <code>0</code>). This constructor is typically used for
133 * anonymous extendable enumerations (with value automatically assigned).
134 *
135 * @throws IllegalStateException if an enum with same value already exists.
136 */
137 protected Enum() {
138 this(null, null);
139 }
140
141 /***
142 * Creates an enumerate of specified name. The value of this enum is
143 * one more than the previous enum (starting at <code>0</code>).
144 * This constructor is typically used for extendable named enumerations
145 * (with value automatically assigned).
146 *
147 * @param name the name for this enum.
148 * @throws IllegalStateException if an enum with same name or same value
149 * already exists.
150 */
151 protected Enum(String name) {
152 this(null, name);
153 }
154
155 /***
156 * Creates an enumerate of specified value. This constructor is typically
157 * used for non-extendable anonymous enumerations (e.g. masks with value
158 * power of 2).
159 *
160 * @param value the value for this enum.
161 * @throws IllegalStateException if an enum with same value already exists.
162 */
163 protected Enum(long value) {
164 this(new LongValue(value), null);
165 }
166
167 /***
168 * Creates an enumerate of specified value and specified name.
169 * This constructor is typically used for enumeration usable in
170 * <code>switch</code> statements (e.g. state machine) with values
171 * being defined by <code>public final static int/long</code> constants.
172 *
173 * @param value the value for this enum.
174 * @param name the name for this enum.
175 * @throws IllegalStateException if an enum with same name or same value
176 * already exists.
177 */
178 protected Enum(long value, String name) {
179 this(new LongValue(value), name);
180 }
181
182 /***
183 * Base constructor.
184 *
185 * @param value the value for this enum or <code>null</code> if none.
186 * @param name the name for this enum or <code>null</code> if none.
187 * @throws IllegalStateException if an enum with same name or same value
188 * already exists.
189 */
190 private Enum(LongValue value, String name) {
191 synchronized (Enum.class) {
192 Class rootClass = this.getRootClass();
193
194
195 FastMap values = (FastMap) VALUE_ENUM_PER_CLASS.get(rootClass);
196
197 if (values == null) {
198 values = new FastMap();
199 VALUE_ENUM_PER_CLASS.put(rootClass, values);
200 }
201
202 if (value == null) {
203 value = new LongValue(values.size());
204 }
205
206 if (values.containsKey(value)) {
207 throw new IllegalStateException("Value: " + value +
208 " already assigned");
209 }
210
211 values.put(value, this);
212
213
214 for (Class enumClass = getClass(); enumClass != rootClass;
215 enumClass = enumClass.getSuperclass()) {
216 values = (FastMap) VALUE_ENUM_PER_CLASS.get(enumClass);
217
218 if (values == null) {
219 values = new FastMap();
220 VALUE_ENUM_PER_CLASS.put(enumClass, values);
221 }
222
223 values.put(value, this);
224 }
225
226
227 FastMap names = (FastMap) NAME_ENUM_PER_CLASS.get(rootClass);
228
229 if (names == null) {
230 names = new FastMap();
231 NAME_ENUM_PER_CLASS.put(rootClass, names);
232 }
233
234 if (name == null) {
235
236
237 FastMap map = (FastMap) NAME_ENUM_PER_CLASS.get(this.getClass());
238 int ordinal = (map != null) ? map.size() : 0;
239 name = this.getClass().getName() + '#' + ordinal;
240 }
241
242 if (names.containsKey(name)) {
243 throw new IllegalStateException("Name: " + name +
244 " already assigned");
245 }
246
247 names.put(name, this);
248
249
250 for (Class enumClass = getClass(); enumClass != rootClass;
251 enumClass = enumClass.getSuperclass()) {
252 names = (FastMap) NAME_ENUM_PER_CLASS.get(enumClass);
253
254 if (names == null) {
255 names = new FastMap();
256 NAME_ENUM_PER_CLASS.put(enumClass, names);
257 }
258
259 names.put(name, this);
260 }
261
262 _value = value._long;
263 _name = name;
264 }
265 }
266
267 private Class getRootClass() {
268 Class rootClass = this.getClass();
269
270 while (rootClass.getSuperclass() != Enum.class) {
271 rootClass = rootClass.getSuperclass();
272 }
273
274 return rootClass;
275 }
276
277 /***
278 * Compares this enum with the specified object for order. Returns a
279 * negative integer, zero, or a positive integer as this object is less
280 * than, equal to, or greater than the specified object.
281 *
282 * @param o the object to be compared.
283 * @return a negative integer, zero, or a positive integer as this object
284 * is less than, equal to, or greater than the specified object.
285 * @throws ClassCastException if the specified object's type prevents it
286 * from being compared to this object.
287 */
288 public int compareTo(Object o) {
289 if (this.getRootClass() == ((Enum) o).getRootClass()) {
290 return (((Enum) o)._value == _value) ? 0
291 : ((((Enum) o)._value < _value)
292 ? (-1) : 1);
293 } else {
294 throw new ClassCastException("Cannot compare " + this.getClass() +
295 " with " + o.getClass());
296 }
297 }
298
299 /***
300 * Returns a read-only list of the specified enumeration.
301 * The collection returned is backed by the actual collection of enums
302 * -- so it grows as more enums are defined. The iteration order of
303 * the collection returned is always the declarative order of the
304 * {@link Enum} regardless of their actual values. This method is
305 * typically used to export the constant <code>VALUES</code> as in<pre>
306 * public static Color extends Enum {
307 * public static final Collection VALUES = getInstances(Color.class);
308 * ...
309 * }</pre>
310 *
311 * @param enumClass the enum class.
312 * @return an unmodifiable view of the enum collection.
313 */
314 protected static Collection getInstances(Class enumClass) {
315 synchronized (Enum.class) {
316 FastMap values = (FastMap) VALUE_ENUM_PER_CLASS.get(enumClass);
317
318 if (values == null) {
319 values = new FastMap();
320 VALUE_ENUM_PER_CLASS.put(enumClass, values);
321 }
322
323 return Collections.unmodifiableCollection(values.values());
324 }
325 }
326
327 /***
328 * Returns the enum with the specified value. This method also ensures
329 * that the specified class has been initialized.
330 *
331 * @param value the value of the enum to search for.
332 * @param enumClass the class of the enum to return.
333 * @return the enum with the specified value or <code>null</code>
334 * if not found.
335 */
336 public static Enum valueOf(long value, Class enumClass) {
337 synchronized (Enum.class) {
338 ensureInitialization(enumClass);
339
340 FastMap values = (FastMap) VALUE_ENUM_PER_CLASS.get(enumClass);
341 KEY_VALUE._long = value;
342
343 return (values != null) ? (Enum) values.get(KEY_VALUE) : null;
344 }
345 }
346
347 /***
348 * Returns the enum with the specified name. This method also ensures
349 * that the specified class has been initialized.
350 *
351 * @param name the name of the enum to search for.
352 * @param enumClass the class of the enum to return.
353 * @return the enum such as <code>name.equals(enum.getName())</code>
354 * or <code>null</code> if not found.
355 */
356 public static Enum valueOf(CharSequence name, Class enumClass) {
357 synchronized (Enum.class) {
358 ensureInitialization(enumClass);
359
360 FastMap names = (FastMap) NAME_ENUM_PER_CLASS.get(enumClass);
361
362 return (names != null) ? (Enum) names.get(name) : null;
363 }
364 }
365
366
367
368
369
370 /***
371 * Returns this enum's value as a <code>int</code>.
372 * This may involve rounding or truncation.
373 *
374 * @return the numeric value represented by this enum after conversion
375 * to type <code>int</code>.
376 */
377 public final int intValue() {
378 return (int) _value;
379 }
380
381 /***
382 * Returns this enum's value as a <code>long</code>.
383 *
384 * @return the numeric value represented by this enum after conversion
385 * to type <code>long</code>.
386 */
387 public final long longValue() {
388 return _value;
389 }
390
391 /***
392 * Returns this enum's value as a <code>float</code>.
393 * This may involve rounding.
394 *
395 * @return the numeric value represented by this enum after conversion
396 * to type <code>float</code>.
397 */
398 public final float floatValue() {
399 return _value;
400 }
401
402 /***
403 * Returns this enum's value as a <code>double</code>.
404 * This may involve rounding.
405 *
406 * @return the numeric value represented by this enum after conversion
407 * to type <code>double</code>.
408 */
409 public final double doubleValue() {
410 return _value;
411 }
412
413 /***
414 * Returns the unique name for this enum. For anonymous enum,
415 * the name is constructed from the enum class and the declarative
416 * order of the enum <b>within</b> its class (e.g. <code>Color#0</code>
417 * for the first declared color in the <code>Color</code> class).
418 *
419 * @return the name specified at construction or an unique name derived
420 * from the enum class name and its declarative order.
421 */
422 public final String getName() {
423 return _name;
424 }
425
426 /***
427 * Returns a string representation of this enum.
428 *
429 * @return {@link #getName()}
430 */
431 public String toString() {
432 return _name;
433 }
434
435 /***
436 * Overrides <code>readResolve()</code> to support
437 * <code>Serialization</code>. Enum are uniquely identified by their
438 * name (independantly of their values to avoid class loading dependencies).
439 *
440 * @return the unique enum identified by the serialized name.
441 * @throws ObjectStreamException if the enum is not found.
442 * @see #getName()
443 */
444 protected Object readResolve() throws ObjectStreamException {
445
446 Enum enum = valueOf(this._name, this.getClass());
447
448 if (enum != null) {
449 return enum;
450 } else {
451 throw new InvalidObjectException("Enum: " + this + " not found");
452 }
453 }
454
455 /***
456 * Throws CloneNotSupportedException. This guarantees that enums
457 * are never cloned, which is necessary to preserve their "singleton"
458 * status.
459 *
460 * @return (never returns)
461 */
462 protected final Object clone() throws CloneNotSupportedException {
463 throw new CloneNotSupportedException("Enum cannot be cloned");
464 }
465
466 /***
467 * Ensures initialization of the specified enum class.
468 *
469 * @param enumClass the class to ensure the initialization.
470 */
471 private static void ensureInitialization(Class enumClass) {
472 if (!VALUE_ENUM_PER_CLASS.containsKey(enumClass)) {
473
474
475 try {
476 Class.forName(enumClass.getName());
477 } catch (ClassNotFoundException e) {
478 throw new InternalError();
479 }
480 }
481 }
482
483 /***
484 * This class represents a long value for value-to-enum mappings.
485 */
486 private static final class LongValue {
487 long _long;
488
489 LongValue(long value) {
490 _long = value;
491 }
492
493 public boolean equals(Object that) {
494 return (this._long == ((LongValue) that)._long);
495 }
496
497 public int hashCode() {
498 return (int) _long;
499 }
500 }
501 }