/lib/docs/enum/com/plotnix/enum/Enum.java

http://mp-rechnungs-und-kundenverwaltung.googlecode.com/ · Java · 515 lines · 282 code · 36 blank · 197 comment · 62 complexity · 2cb46faea44e6bee139bd21e17a29040 MD5 · raw file

  1. /*
  2. * $Header$
  3. * $Revision$
  4. * $Date$
  5. *
  6. * ====================================================================
  7. * The PLOTNIX Software License, Version 1.0
  8. *
  9. *
  10. * Copyright (c) 2001 The PLOTNIX Software Foundation. All rights
  11. * reserved.
  12. *
  13. * Redistribution and use in source and binary forms, with or without
  14. * modification, are permitted provided that the following conditions
  15. * are met:
  16. *
  17. * 1. Redistributions of source code must retain the above copyright
  18. * notice, this list of conditions and the following disclaimer.
  19. *
  20. * 2. Redistributions in binary form must reproduce the above copyright
  21. * notice, this list of conditions and the following disclaimer in
  22. * the documentation and/or other materials provided with the
  23. * distribution.
  24. *
  25. * 3. The end-user documentation included with the redistribution,
  26. * if any, must include the following acknowledgment:
  27. * "This product includes software developed by the
  28. * PLOTNIX, Inc (http://www.plotnix.com/)."
  29. * Alternately, this acknowledgment may appear in the software itself,
  30. * if and wherever such third-party acknowledgments normally appear.
  31. *
  32. * 4. The name "PLOTNIX" must not be used to endorse or promote
  33. * products derived from this software without prior written
  34. * permission. For written permission, please contact dmitri@plotnix.com.
  35. *
  36. * 5. Products derived from this software may not be called "PLOTNIX",
  37. * nor may "PLOTNIX" appear in their name, without prior written
  38. * permission of the PLOTNIX, Inc.
  39. *
  40. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
  41. * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  42. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  43. * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
  44. * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  45. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  46. * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
  47. * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
  48. * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  49. * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
  50. * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  51. * SUCH DAMAGE.
  52. * ====================================================================
  53. *
  54. * For more information on PLOTNIX, Inc, please see <http://www.plotnix.com/>.
  55. */
  56. package com.plotnix.enum;
  57. import java.util.*;
  58. import java.lang.reflect.*;
  59. import java.io.*;
  60. /**
  61. * Enum represents a value characterized by the following properties:
  62. * <ul>
  63. * <li>It has an integer value that uniquely identifies it among
  64. * other Enums belonging to the same class. By default,
  65. * the integer value represents the order in which Enums are added
  66. * to the class, starting with 0. Alternatively, an integer value
  67. * can be assigned explicitly to the Enum in the constructor.
  68. * </li>
  69. * <li>It has a string value, which is also unique. By default, this
  70. * string value is the name of a <code>public static Enum</code> field
  71. * declared on this Enum's own class for this enum. If there is
  72. * no such name and the string value was not explicitly specified in the
  73. * constructor, then the string value is a string representation of the
  74. * Enum's integer value.
  75. * </li>
  76. * <li>The toString() method of the Enum returns a human readable
  77. * description of the Enum. The class attempts to locate a
  78. * label for the constant in the resource bundle that has the same
  79. * name as the class itself. If that fails and a default label
  80. * was specified in the constructor, that label is returned.
  81. * If no such label was specified, the string value of the constant is returned.
  82. * </li>
  83. * </ul>
  84. *
  85. * Static methods of the Enum class support:
  86. * <ul>
  87. * <li>Loading an Enum from .property file</li>
  88. * <li>Automatic generation of Enum objects from integer constants</li>
  89. * <li>Retrieving an array of all constants of a given type</li>
  90. * <li>Retrieving an individual constant of a given type by its string or integer value</li>
  91. * </ul>
  92. *
  93. * @author Dmitri Plotnikov
  94. * @version $Revision$ $Date$
  95. */
  96. public class Enum implements Serializable
  97. {
  98. private transient EnumList type;
  99. private String string;
  100. private int integer;
  101. private String label;
  102. private static HashMap byClass = new HashMap();
  103. /**
  104. * Initialize the Enum, using default integer and string values.
  105. */
  106. protected Enum(){
  107. type = getEnumList(getClass());
  108. integer = type.add(this);
  109. }
  110. /**
  111. * Initialize the Enum with a string value. An integer value is assigned automatically.
  112. */
  113. protected Enum(String string){
  114. type = getEnumList(getClass());
  115. this.string = string;
  116. integer = type.add(this);
  117. }
  118. /**
  119. * Initialize the Enum with a string value and a default label.
  120. * An integer value is assigned automatically.
  121. */
  122. protected Enum(String string, String label){
  123. type = getEnumList(getClass());
  124. this.string = string;
  125. this.label = label;
  126. integer = type.add(this);
  127. }
  128. /**
  129. * Initialize the Enum with an integer value. A string value will be produced
  130. * automatically.
  131. */
  132. protected Enum(int integer){
  133. type = getEnumList(getClass());
  134. this.integer = integer;
  135. type.add(this);
  136. }
  137. /**
  138. * Initialize the Enum with an integer value and a default label.
  139. * A string value will be produced automatically.
  140. */
  141. protected Enum(int integer, String label){
  142. type = getEnumList(getClass());
  143. this.integer = integer;
  144. this.label = label;
  145. type.add(this);
  146. }
  147. /**
  148. * Returns the string value of this Enum.
  149. * Depending on how the Enum was created, the method behaves differently:
  150. * <ui>
  151. * <li>If the constant was created as a <code>public static Enum</code>,
  152. * the name of that field is returned.</li>
  153. * <li>If the constant was declared as a <code>public static int</code>,
  154. * and registered with <code>initIntegerEnum</code>,
  155. * the name of that integer field is returned.</li>
  156. * <li>If the constant was loaded from a .properties file, the
  157. * corresponding property name is returned.</li>
  158. * </ul>
  159. */
  160. public String stringValue(){
  161. if (string == null){
  162. string = type.getDefaultStringValue(this);
  163. }
  164. return string;
  165. }
  166. /**
  167. * Returns the Enum's integer value. An Enum can be allocated with an explicitly
  168. * specified integer value. If the integer value is not specified, the Enum's constructor
  169. * gives it a default one, which is computed by adding one to the
  170. * integer value of the previous Enum of the same type.
  171. */
  172. public int intValue(){
  173. return integer;
  174. }
  175. /**
  176. * Returns this Enum's human readable description in the default locale.
  177. */
  178. public String toString(){
  179. return toString(Locale.getDefault());
  180. }
  181. /**
  182. * Returns this Enum's human readable description in the specified locale.
  183. * If the resource bundle is not found or does not have a value for this Enum,
  184. * the value of the default label specified during construction is returned.
  185. * If that value was not unspecified, the Enum's string value is returned.
  186. */
  187. public String toString(Locale locale){
  188. try {
  189. ResourceBundle bundle = ResourceBundle.getBundle(
  190. getClass().getName(), locale, getClass().getClassLoader());
  191. return bundle.getString(stringValue());
  192. }
  193. catch(MissingResourceException e){
  194. }
  195. if (label != null){
  196. return label;
  197. }
  198. return stringValue();
  199. }
  200. /**
  201. * Returns an array of all Enums of the specified class in the order
  202. * they were created.
  203. */
  204. public static Enum[] enum(Class enumClass){
  205. return getEnumList(enumClass).enum();
  206. }
  207. /**
  208. * Returns the Enum of the specified class that has the specified st,
  209. * or null if there is no such Enum.
  210. */
  211. public static Enum enum(Class enumClass, String string){
  212. return getEnumList(enumClass).get(string);
  213. }
  214. /**
  215. * Returns the Enum of the specified class that has the specified integer value,
  216. * or null if there is no such Enum.
  217. */
  218. public static Enum enum(Class enumClass, int integer){
  219. return getEnumList(enumClass).get(integer);
  220. }
  221. protected static void initIntegerEnum(Class enumClass){
  222. Field fields[] = enumClass.getFields();
  223. for (int i = 0; i < fields.length; i++){
  224. int mod = fields[i].getModifiers();
  225. if (Modifier.isPublic(mod) && Modifier.isStatic(mod)){
  226. Class type = fields[i].getType();
  227. if (type == int.class || type == char.class ||
  228. type == short.class || type == byte.class){
  229. try {
  230. Number n = (Number)fields[i].get(null);
  231. Enum enum = (Enum)enumClass.newInstance();
  232. enum.integer = n.intValue();
  233. enum.string = fields[i].getName();
  234. }
  235. catch (Exception e){
  236. throw new RuntimeException("Cannot init integer Enum class " +
  237. enumClass.getName() + "\n " + e);
  238. }
  239. }
  240. }
  241. }
  242. }
  243. /**
  244. * Loads properties from an <i>enumClass</i>.properties resource file.
  245. * Uses property names as string values of the Enums.
  246. */
  247. protected static void loadEnum(Class enumClass){
  248. allocateEnums(enumClass, loadKeys(enumClass), false);
  249. }
  250. /**
  251. * Loads properties from an <i>enumClass</i>.properties resource file.
  252. * Uses property names as integer values of the Enums.
  253. */
  254. protected static void loadIntegerEnum(Class enumClass){
  255. allocateEnums(enumClass, loadKeys(enumClass), true);
  256. }
  257. /**
  258. * Finds the EnumList registered for the specified class. Allocates
  259. * one if it does not exist yet.
  260. */
  261. private static EnumList getEnumList(Class clazz){
  262. EnumList type = (EnumList)byClass.get(clazz);
  263. if (type == null){
  264. type = new EnumList();
  265. byClass.put(clazz, type);
  266. }
  267. return type;
  268. }
  269. /**
  270. * Ensures uniqueness of Enums during deserialization. Finds an Enum
  271. * that is already declared with the same integer value and returns
  272. * that Enum instead of the current instance.
  273. */
  274. protected Object readResolve() throws ObjectStreamException {
  275. Class clazz = getClass();
  276. EnumList type = null;
  277. try {
  278. type = getEnumList(clazz);
  279. }
  280. catch (Exception ex){
  281. throw new InvalidClassException(clazz.getName(),
  282. "Is not a valid Enum class, " + ex.getMessage());
  283. }
  284. Enum enum = type.get(integer);
  285. if (enum == null){
  286. throw new InvalidClassException(clazz.getName(),
  287. "Enum is not associated with its own EnumList " + integer);
  288. }
  289. return enum;
  290. }
  291. /**
  292. * Loads property names from an <i>enumClass</i>.properties file.
  293. */
  294. private static List loadKeys(Class enumClass){
  295. final List keys = new ArrayList();
  296. String resName = enumClass.getName().replace('.', '/') + ".properties";
  297. try {
  298. InputStream stream = null;
  299. ClassLoader loader = enumClass.getClassLoader();
  300. if (loader != null) {
  301. stream = loader.getResourceAsStream(resName);
  302. } else {
  303. stream = ClassLoader.getSystemResourceAsStream(resName);
  304. }
  305. if (stream == null){
  306. throw new RuntimeException("No such resource");
  307. }
  308. stream = new BufferedInputStream(stream);
  309. // We are using the Properties class as a parser for the resource file
  310. new Properties(){
  311. public Object put(Object key, Object value){
  312. keys.add(key);
  313. return super.put(key, value);
  314. }
  315. }.load(stream);
  316. stream.close();
  317. }
  318. catch (Exception ex){
  319. throw new RuntimeException("Cannot load values from: " + resName +
  320. "\n " + ex);
  321. }
  322. return keys;
  323. }
  324. /**
  325. * Creates Enums for all keys (string or integer) in the keys list.
  326. */
  327. private static void allocateEnums(Class enumClass, List keys, boolean isInteger){
  328. Constructor constructor = null;
  329. try {
  330. constructor = enumClass.getConstructor(
  331. new Class[]{isInteger ? int.class : String.class});
  332. }
  333. catch (NoSuchMethodException ex){
  334. }
  335. for (Iterator it = keys.iterator(); it.hasNext();){
  336. String key = (String)it.next();
  337. try {
  338. if (isInteger){
  339. if (constructor != null){
  340. Integer code = Integer.decode(key);
  341. constructor.newInstance(new Object[]{code});
  342. }
  343. else {
  344. int code = Integer.parseInt(key);
  345. Enum enum = (Enum)enumClass.newInstance();
  346. enum.string = key;
  347. enum.integer = code;
  348. }
  349. }
  350. else {
  351. if (constructor != null){
  352. constructor.newInstance(new Object[]{key});
  353. }
  354. else {
  355. Enum enum = (Enum)enumClass.newInstance();
  356. enum.string = key;
  357. }
  358. }
  359. }
  360. catch (Exception ex){
  361. throw new RuntimeException("Cannot allocate Enum " + enumClass.getName() +
  362. "." + key + "\n " + ex);
  363. }
  364. }
  365. }
  366. /**
  367. * There is one instance of EnumList per Enum type. The object maintains
  368. * all sorts of per-type information about an Enum type.
  369. */
  370. private static class EnumList {
  371. private ArrayList enumList = new ArrayList();
  372. private Enum[] array;
  373. private HashMap byString;
  374. private HashMap byEnum;
  375. private static final Enum[] ENUM_ARRAY = new Enum[0];
  376. /**
  377. * Called automatically by the Enum constructor.
  378. */
  379. public int add(Enum enum){
  380. enumList.add(enum);
  381. array = null;
  382. byString = null;
  383. if (enumList.size() == 1){
  384. return 0;
  385. }
  386. else {
  387. return ((Enum)enumList.get(enumList.size() - 2)).intValue() + 1;
  388. }
  389. }
  390. /**
  391. * Returns all array contained by the enum.
  392. */
  393. public Enum[] enum(){
  394. if (array == null){
  395. array = (Enum[])enumList.toArray(ENUM_ARRAY);
  396. }
  397. return array;
  398. }
  399. /**
  400. * Returns a Enum for the specified string value, or null if there
  401. * is no such enum.
  402. */
  403. public Enum get(String string){
  404. Enum enums[] = enum();
  405. if (enums.length < 10){
  406. for (int i = 0; i < enums.length; i++){
  407. if (enums[i].stringValue().equals(string)){
  408. return enums[i];
  409. }
  410. }
  411. return null;
  412. }
  413. if (byString == null){
  414. byString = new HashMap();
  415. Enum[] array = enum();
  416. for (int i = 0; i < array.length; i++){
  417. byString.put(array[i].stringValue(), array[i]);
  418. }
  419. }
  420. return (Enum)byString.get(string);
  421. }
  422. /**
  423. * Returns an Enum for the specified integer value, or null if there
  424. * is no such Enum.
  425. */
  426. public Enum get(int integer){
  427. Enum enums[] = enum();
  428. if (integer >= 0 && integer < enums.length){
  429. if (enums[integer].intValue() == integer){
  430. return enums[integer];
  431. }
  432. }
  433. for (int i = 0; i < enums.length; i++){
  434. if (enums[i].intValue() == integer){
  435. return enums[i];
  436. }
  437. }
  438. return null;
  439. }
  440. /**
  441. * Returns the name of a public static field declared by
  442. * the specified enum's class for the specified enum.
  443. * If there is no such field, returns the enum's integer value.
  444. */
  445. public String getDefaultStringValue(Enum enum){
  446. String string = null;
  447. if (byEnum != null){
  448. string = (String)byEnum.get(enum);
  449. if (string != null){
  450. return string;
  451. }
  452. }
  453. else {
  454. byEnum = new HashMap();
  455. }
  456. Field fields[] = enum.getClass().getFields();
  457. for (int i = 0; i < fields.length; i++){
  458. int mod = fields[i].getModifiers();
  459. if (Modifier.isPublic(mod) && Modifier.isStatic(mod) &&
  460. Enum.class.isAssignableFrom(fields[i].getType())){
  461. try {
  462. Enum e = (Enum)fields[i].get(null);
  463. byEnum.put(e, fields[i].getName());
  464. if (e == enum){
  465. string = fields[i].getName();
  466. }
  467. }
  468. catch (IllegalAccessException e){
  469. // Ignore errors
  470. }
  471. }
  472. }
  473. if (string == null){
  474. string = String.valueOf(enum.intValue());
  475. }
  476. return string;
  477. }
  478. }
  479. }