1. /* ====================================================================
  2. * The Apache Software License, Version 1.1
  3. *
  4. * Copyright (c) 2002-2003 The Apache Software Foundation. All rights
  5. * reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without
  8. * modification, are permitted provided that the following conditions
  9. * are met:
  10. *
  11. * 1. Redistributions of source code must retain the above copyright
  12. * notice, this list of conditions and the following disclaimer.
  13. *
  14. * 2. Redistributions in binary form must reproduce the above copyright
  15. * notice, this list of conditions and the following disclaimer in
  16. * the documentation and/or other materials provided with the
  17. * distribution.
  18. *
  19. * 3. The end-user documentation included with the redistribution, if
  20. * any, must include the following acknowledgement:
  21. * "This product includes software developed by the
  22. * Apache Software Foundation (http://www.apache.org/)."
  23. * Alternately, this acknowledgement may appear in the software itself,
  24. * if and wherever such third-party acknowledgements normally appear.
  25. *
  26. * 4. The names "The Jakarta Project", "Commons", and "Apache Software
  27. * Foundation" must not be used to endorse or promote products derived
  28. * from this software without prior written permission. For written
  29. * permission, please contact apache@apache.org.
  30. *
  31. * 5. Products derived from this software may not be called "Apache"
  32. * nor may "Apache" appear in their names without prior written
  33. * permission of the Apache Software Foundation.
  34. *
  35. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  36. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  37. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  38. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  39. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  40. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  41. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  42. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  43. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  44. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  45. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  46. * SUCH DAMAGE.
  47. * ====================================================================
  48. *
  49. * This software consists of voluntary contributions made by many
  50. * individuals on behalf of the Apache Software Foundation. For more
  51. * information on the Apache Software Foundation, please see
  52. * <http://www.apache.org/>.
  53. */
  54. package org.apache.commons.lang.enum;
  55. import java.io.Serializable;
  56. import java.lang.reflect.InvocationTargetException;
  57. import java.lang.reflect.Method;
  58. import java.util.ArrayList;
  59. import java.util.Collections;
  60. import java.util.HashMap;
  61. import java.util.Iterator;
  62. import java.util.List;
  63. import java.util.Map;
  64. import org.apache.commons.lang.ClassUtils;
  65. import org.apache.commons.lang.StringUtils;
  66. /**
  67. * <p>Abstract superclass for type-safe enums.</p>
  68. *
  69. * <p>One feature of the C programming language lacking in Java is enumerations. The
  70. * C implementation based on ints was poor and open to abuse. The original Java
  71. * recommendation and most of the JDK also uses int constants. It has been recognised
  72. * however that a more robust type-safe class-based solution can be designed. This
  73. * class follows the basic Java type-safe enumeration pattern.</p>
  74. *
  75. * <p><em>NOTE:</em>Due to the way in which Java ClassLoaders work, comparing
  76. * Enum objects should always be done using <code>equals()</code>, not <code>==</code>.
  77. * The equals() method will try == first so in most cases the effect is the same.</p>
  78. *
  79. * <p>Of course, if you actually want (or don't mind) Enums in different class
  80. * loaders being non-equal, then you can use <code>==</code>.</p>
  81. *
  82. * <h4>Simple Enums</h4>
  83. *
  84. * <p>To use this class, it must be subclassed. For example:</p>
  85. *
  86. * <pre>
  87. * public final class ColorEnum extends Enum {
  88. * public static final ColorEnum RED = new ColorEnum("Red");
  89. * public static final ColorEnum GREEN = new ColorEnum("Green");
  90. * public static final ColorEnum BLUE = new ColorEnum("Blue");
  91. *
  92. * private ColorEnum(String color) {
  93. * super(color);
  94. * }
  95. *
  96. * public static ColorEnum getEnum(String color) {
  97. * return (ColorEnum) getEnum(ColorEnum.class, color);
  98. * }
  99. *
  100. * public static Map getEnumMap() {
  101. * return getEnumMap(ColorEnum.class);
  102. * }
  103. *
  104. * public static List getEnumList() {
  105. * return getEnumList(ColorEnum.class);
  106. * }
  107. *
  108. * public static Iterator iterator() {
  109. * return iterator(ColorEnum.class);
  110. * }
  111. * }
  112. * </pre>
  113. *
  114. * <p>As shown, each enum has a name. This can be accessed using <code>getName</code>.</p>
  115. *
  116. * <p>The <code>getEnum</code> and <code>iterator</code> methods are recommended.
  117. * Unfortunately, Java restrictions require these to be coded as shown in each subclass.
  118. * An alternative choice is to use the {@link EnumUtils} class.</p>
  119. *
  120. * <h4>Subclassed Enums</h4>
  121. * <p>A hierarchy of Enum classes can be built. In this case, the superclass is
  122. * unaffected by the addition of subclasses (as per normal Java). The subclasses
  123. * may add additional Enum constants <em>of the type of the superclass</em>. The
  124. * query methods on the subclass will return all of the Enum constants from the
  125. * superclass and subclass.</p>
  126. *
  127. * <pre>
  128. * public final class ExtraColorEnum extends ColorEnum {
  129. * // NOTE: Color enum declared above is final, change that to get this
  130. * // example to compile.
  131. * public static final ColorEnum YELLOW = new ExtraColorEnum("Yellow");
  132. *
  133. * private ExtraColorEnum(String color) {
  134. * super(color);
  135. * }
  136. *
  137. * public static ColorEnum getEnum(String color) {
  138. * return (ColorEnum) getEnum(ExtraColorEnum.class, color);
  139. * }
  140. *
  141. * public static Map getEnumMap() {
  142. * return getEnumMap(ExtraColorEnum.class);
  143. * }
  144. *
  145. * public static List getEnumList() {
  146. * return getEnumList(ExtraColorEnum.class);
  147. * }
  148. *
  149. * public static Iterator iterator() {
  150. * return iterator(ExtraColorEnum.class);
  151. * }
  152. * }
  153. * </pre>
  154. *
  155. * <p>This example will return RED, GREEN, BLUE, YELLOW from the List and iterator
  156. * methods in that order. The RED, GREEN and BLUE instances will be the same (==)
  157. * as those from the superclass ColorEnum. Note that YELLOW is declared as a
  158. * ColorEnum and not an ExtraColorEnum.</p>
  159. *
  160. * <h4>Functional Enums</h4>
  161. *
  162. * <p>The enums can have functionality by defining subclasses and
  163. * overriding the <code>getEnumClass()</code> method:</p>
  164. *
  165. * <pre>
  166. * public static final OperationEnum PLUS = new PlusOperation();
  167. * private static final class PlusOperation extends OperationEnum {
  168. * private PlusOperation() {
  169. * super("Plus");
  170. * }
  171. * public int eval(int a, int b) {
  172. * return (a + b);
  173. * }
  174. * }
  175. * public static final OperationEnum MINUS = new MinusOperation();
  176. * private static final class MinusOperation extends OperationEnum {
  177. * private MinusOperation() {
  178. * super("Minus");
  179. * }
  180. * public int eval(int a, int b) {
  181. * return (a - b);
  182. * }
  183. * }
  184. *
  185. * private OperationEnum(String color) {
  186. * super(color);
  187. * }
  188. *
  189. * public final Class getEnumClass() { // NOTE: new method!
  190. * return OperationEnum.class;
  191. * }
  192. *
  193. * public abstract double eval(double a, double b);
  194. *
  195. * public static OperationEnum getEnum(String name) {
  196. * return (OperationEnum) getEnum(OperationEnum.class, name);
  197. * }
  198. *
  199. * public static Map getEnumMap() {
  200. * return getEnumMap(OperationEnum.class);
  201. * }
  202. *
  203. * public static List getEnumList() {
  204. * return getEnumList(OperationEnum.class);
  205. * }
  206. *
  207. * public static Iterator iterator() {
  208. * return iterator(OperationEnum.class);
  209. * }
  210. * }
  211. * </pre>
  212. * <p>The code above will work on JDK 1.2. If JDK1.3 and later is used,
  213. * the subclasses may be defined as anonymous.</p>
  214. *
  215. * @author Apache Avalon project
  216. * @author Stephen Colebourne
  217. * @author Chris Webb
  218. * @author Mike Bowler
  219. * @since 1.0
  220. * @version $Id: Enum.java,v 1.21 2003/08/21 15:52:55 ggregory Exp $
  221. */
  222. public abstract class Enum implements Comparable, Serializable {
  223. /** Lang version 1.0.1 serial compatability */
  224. private static final long serialVersionUID = -487045951170455942L;
  225. // After discussion, the default size for HashMaps is used, as the
  226. // sizing algorithm changes across the JDK versions
  227. /**
  228. * An empty <code>Map</code>, as JDK1.2 didn't have an empty map.
  229. */
  230. private static final Map EMPTY_MAP = Collections.unmodifiableMap(new HashMap(0));
  231. /**
  232. * <code>Map</code>, key of class name, value of <code>Entry</code>.
  233. */
  234. private static final Map cEnumClasses = new HashMap();
  235. /**
  236. * The string representation of the Enum.
  237. */
  238. private final String iName;
  239. /**
  240. * The hashcode representation of the Enum.
  241. */
  242. private transient final int iHashCode;
  243. /**
  244. * The toString representation of the Enum.
  245. * @since 2.0
  246. */
  247. protected transient String iToString = null;
  248. /**
  249. * <p>Enable the iterator to retain the source code order.</p>
  250. */
  251. private static class Entry {
  252. /**
  253. * Map of Enum name to Enum.
  254. */
  255. final Map map = new HashMap();
  256. /**
  257. * Map of Enum name to Enum.
  258. */
  259. final Map unmodifiableMap = Collections.unmodifiableMap(map);
  260. /**
  261. * List of Enums in source code order.
  262. */
  263. final List list = new ArrayList(25);
  264. /**
  265. * Map of Enum name to Enum.
  266. */
  267. final List unmodifiableList = Collections.unmodifiableList(list);
  268. /**
  269. * <p>Restrictive constructor.</p>
  270. */
  271. private Entry() {
  272. }
  273. }
  274. /**
  275. * <p>Constructor to add a new named item to the enumeration.</p>
  276. *
  277. * @param name the name of the enum object,
  278. * must not be empty or <code>null</code>
  279. * @throws IllegalArgumentException if the name is <code>null</code>
  280. * or an empty string
  281. * @throws IllegalArgumentException if the getEnumClass() method returns
  282. * a null or invalid Class
  283. */
  284. protected Enum(String name) {
  285. super();
  286. init(name);
  287. iName = name;
  288. iHashCode = 7 + getEnumClass().hashCode() + 3 * name.hashCode();
  289. // cannot create toString here as subclasses may want to include other data
  290. }
  291. /**
  292. * Initializes the enumeration.
  293. *
  294. * @param name the enum name
  295. * @throws IllegalArgumentException if the name is null or empty or duplicate
  296. * @throws IllegalArgumentException if the enumClass is null or invalid
  297. */
  298. private void init(String name) {
  299. if (StringUtils.isEmpty(name)) {
  300. throw new IllegalArgumentException("The Enum name must not be empty or null");
  301. }
  302. Class enumClass = getEnumClass();
  303. if (enumClass == null) {
  304. throw new IllegalArgumentException("getEnumClass() must not be null");
  305. }
  306. Class cls = getClass();
  307. boolean ok = false;
  308. while (cls != null && cls != Enum.class && cls != ValuedEnum.class) {
  309. if (cls == enumClass) {
  310. ok = true;
  311. break;
  312. }
  313. cls = cls.getSuperclass();
  314. }
  315. if (ok == false) {
  316. throw new IllegalArgumentException("getEnumClass() must return a superclass of this class");
  317. }
  318. // create entry
  319. Entry entry = (Entry) cEnumClasses.get(enumClass);
  320. if (entry == null) {
  321. entry = createEntry(enumClass);
  322. cEnumClasses.put(enumClass, entry);
  323. }
  324. if (entry.map.containsKey(name)) {
  325. throw new IllegalArgumentException("The Enum name must be unique, '" + name + "' has already been added");
  326. }
  327. entry.map.put(name, this);
  328. entry.list.add(this);
  329. }
  330. /**
  331. * <p>Handle the deserialization of the class to ensure that multiple
  332. * copies are not wastefully created, or illegal enum types created.</p>
  333. *
  334. * @return the resolved object
  335. */
  336. protected Object readResolve() {
  337. Entry entry = (Entry) cEnumClasses.get(getEnumClass());
  338. if (entry == null) {
  339. return null;
  340. }
  341. return (Enum) entry.map.get(getName());
  342. }
  343. //--------------------------------------------------------------------------------
  344. /**
  345. * <p>Gets an <code>Enum</code> object by class and name.</p>
  346. *
  347. * @param enumClass the class of the Enum to get, must not
  348. * be <code>null</code>
  349. * @param name the name of the <code>Enum</code> to get,
  350. * may be <code>null</code>
  351. * @return the enum object, or null if the enum does not exist
  352. * @throws IllegalArgumentException if the enum class
  353. * is <code>null</code>
  354. */
  355. protected static Enum getEnum(Class enumClass, String name) {
  356. Entry entry = getEntry(enumClass);
  357. if (entry == null) {
  358. return null;
  359. }
  360. return (Enum) entry.map.get(name);
  361. }
  362. /**
  363. * <p>Gets the <code>Map</code> of <code>Enum</code> objects by
  364. * name using the <code>Enum</code> class.</p>
  365. *
  366. * <p>If the requested class has no enum objects an empty
  367. * <code>Map</code> is returned.</p>
  368. *
  369. * @param enumClass the class of the <code>Enum</code> to get,
  370. * must not be <code>null</code>
  371. * @return the enum object Map
  372. * @throws IllegalArgumentException if the enum class is <code>null</code>
  373. * @throws IllegalArgumentException if the enum class is not a subclass of Enum
  374. */
  375. protected static Map getEnumMap(Class enumClass) {
  376. Entry entry = getEntry(enumClass);
  377. if (entry == null) {
  378. return EMPTY_MAP;
  379. }
  380. return entry.unmodifiableMap;
  381. }
  382. /**
  383. * <p>Gets the <code>List</code> of <code>Enum</code> objects using the
  384. * <code>Enum</code> class.</p>
  385. *
  386. * <p>The list is in the order that the objects were created (source code order).
  387. * If the requested class has no enum objects an empty <code>List</code> is
  388. * returned.</p>
  389. *
  390. * @param enumClass the class of the <code>Enum</code> to get,
  391. * must not be <code>null</code>
  392. * @return the enum object Map
  393. * @throws IllegalArgumentException if the enum class is <code>null</code>
  394. * @throws IllegalArgumentException if the enum class is not a subclass of Enum
  395. */
  396. protected static List getEnumList(Class enumClass) {
  397. Entry entry = getEntry(enumClass);
  398. if (entry == null) {
  399. return Collections.EMPTY_LIST;
  400. }
  401. return entry.unmodifiableList;
  402. }
  403. /**
  404. * <p>Gets an <code>Iterator</code> over the <code>Enum</code> objects in
  405. * an <code>Enum</code> class.</p>
  406. *
  407. * <p>The <code>Iterator</code> is in the order that the objects were
  408. * created (source code order). If the requested class has no enum
  409. * objects an empty <code>Iterator</code> is returned.</p>
  410. *
  411. * @param enumClass the class of the <code>Enum</code> to get,
  412. * must not be <code>null</code>
  413. * @return an iterator of the Enum objects
  414. * @throws IllegalArgumentException if the enum class is <code>null</code>
  415. * @throws IllegalArgumentException if the enum class is not a subclass of Enum
  416. */
  417. protected static Iterator iterator(Class enumClass) {
  418. return Enum.getEnumList(enumClass).iterator();
  419. }
  420. //-----------------------------------------------------------------------
  421. /**
  422. * <p>Gets an <code>Entry</code> from the map of Enums.</p>
  423. *
  424. * @param enumClass the class of the <code>Enum</code> to get
  425. * @return the enum entry
  426. */
  427. private static Entry getEntry(Class enumClass) {
  428. if (enumClass == null) {
  429. throw new IllegalArgumentException("The Enum Class must not be null");
  430. }
  431. if (Enum.class.isAssignableFrom(enumClass) == false) {
  432. throw new IllegalArgumentException("The Class must be a subclass of Enum");
  433. }
  434. Entry entry = (Entry) cEnumClasses.get(enumClass);
  435. return entry;
  436. }
  437. /**
  438. * <p>Creates an <code>Entry</code> for storing the Enums.</p>
  439. *
  440. * <p>This accounts for subclassed Enums.</p>
  441. *
  442. * @param enumClass the class of the <code>Enum</code> to get
  443. * @return the enum entry
  444. */
  445. private static Entry createEntry(Class enumClass) {
  446. Entry entry = new Entry();
  447. Class cls = enumClass.getSuperclass();
  448. while (cls != null && cls != Enum.class && cls != ValuedEnum.class) {
  449. Entry loopEntry = (Entry) cEnumClasses.get(cls);
  450. if (loopEntry != null) {
  451. entry.list.addAll(loopEntry.list);
  452. entry.map.putAll(loopEntry.map);
  453. break; // stop here, as this will already have had superclasses added
  454. }
  455. cls = cls.getSuperclass();
  456. }
  457. return entry;
  458. }
  459. //-----------------------------------------------------------------------
  460. /**
  461. * <p>Retrieve the name of this Enum item, set in the constructor.</p>
  462. *
  463. * @return the <code>String</code> name of this Enum item
  464. */
  465. public final String getName() {
  466. return iName;
  467. }
  468. /**
  469. * <p>Retrieves the Class of this Enum item, set in the constructor.</p>
  470. *
  471. * <p>This is normally the same as <code>getClass()</code>, but for
  472. * advanced Enums may be different. If overridden, it must return a
  473. * constant value.</p>
  474. *
  475. * @return the <code>Class</code> of the enum
  476. * @since 2.0
  477. */
  478. public Class getEnumClass() {
  479. return getClass();
  480. }
  481. /**
  482. * <p>Tests for equality.</p>
  483. *
  484. * <p>Two Enum objects are considered equal
  485. * if they have the same class names and the same names.
  486. * Identity is tested for first, so this method usually runs fast.</p>
  487. *
  488. * @param other the other object to compare for equality
  489. * @return <code>true</code> if the Enums are equal
  490. */
  491. public final boolean equals(Object other) {
  492. if (other == this) {
  493. return true;
  494. } else if (other == null) {
  495. return false;
  496. } else if (other.getClass() == this.getClass()) {
  497. // shouldn't happen, but...
  498. return iName.equals(((Enum) other).iName);
  499. } else if (((Enum) other).getEnumClass().getName().equals(getEnumClass().getName())) {
  500. // different classloaders
  501. try {
  502. // try to avoid reflection
  503. return iName.equals(((Enum) other).iName);
  504. } catch (ClassCastException ex) {
  505. // use reflection
  506. try {
  507. Method mth = other.getClass().getMethod("getName", null);
  508. String name = (String) mth.invoke(other, null);
  509. return iName.equals(name);
  510. } catch (NoSuchMethodException ex2) {
  511. // ignore - should never happen
  512. } catch (IllegalAccessException ex2) {
  513. // ignore - should never happen
  514. } catch (InvocationTargetException ex2) {
  515. // ignore - should never happen
  516. }
  517. return false;
  518. }
  519. } else {
  520. return false;
  521. }
  522. }
  523. /**
  524. * <p>Returns a suitable hashCode for the enumeration.</p>
  525. *
  526. * @return a hashcode based on the name
  527. */
  528. public final int hashCode() {
  529. return iHashCode;
  530. }
  531. /**
  532. * <p>Tests for order.</p>
  533. *
  534. * <p>The default ordering is alphabetic by name, but this
  535. * can be overridden by subclasses.</p>
  536. *
  537. * @see java.lang.Comparable#compareTo(Object)
  538. * @param other the other object to compare to
  539. * @return -ve if this is less than the other object, +ve if greater
  540. * than, <code>0</code> of equal
  541. * @throws ClassCastException if other is not an Enum
  542. * @throws NullPointerException if other is <code>null</code>
  543. */
  544. public int compareTo(Object other) {
  545. if (other == this) {
  546. return 0;
  547. }
  548. return iName.compareTo(((Enum) other).iName);
  549. }
  550. /**
  551. * <p>Human readable description of this Enum item.</p>
  552. *
  553. * @return String in the form <code>type[name]</code>, for example:
  554. * <code>Color[Red]</code>. Note that the package name is stripped from
  555. * the type name.
  556. */
  557. public String toString() {
  558. if (iToString == null) {
  559. String shortName = ClassUtils.getShortClassName(getEnumClass());
  560. iToString = shortName + "[" + getName() + "]";
  561. }
  562. return iToString;
  563. }
  564. }