/lib/docs/enum/com/plotnix/enum/Enum.java
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 */ 56package com.plotnix.enum; 57 58import java.util.*; 59import java.lang.reflect.*; 60import java.io.*; 61 62/** 63 * Enum represents a value characterized by the following properties: 64 * <ul> 65 * <li>It has an integer value that uniquely identifies it among 66 * other Enums belonging to the same class. By default, 67 * the integer value represents the order in which Enums are added 68 * to the class, starting with 0. Alternatively, an integer value 69 * can be assigned explicitly to the Enum in the constructor. 70 * </li> 71 * <li>It has a string value, which is also unique. By default, this 72 * string value is the name of a <code>public static Enum</code> field 73 * declared on this Enum's own class for this enum. If there is 74 * no such name and the string value was not explicitly specified in the 75 * constructor, then the string value is a string representation of the 76 * Enum's integer value. 77 * </li> 78 * <li>The toString() method of the Enum returns a human readable 79 * description of the Enum. The class attempts to locate a 80 * label for the constant in the resource bundle that has the same 81 * name as the class itself. If that fails and a default label 82 * was specified in the constructor, that label is returned. 83 * If no such label was specified, the string value of the constant is returned. 84 * </li> 85 * </ul> 86 * 87 * Static methods of the Enum class support: 88 * <ul> 89 * <li>Loading an Enum from .property file</li> 90 * <li>Automatic generation of Enum objects from integer constants</li> 91 * <li>Retrieving an array of all constants of a given type</li> 92 * <li>Retrieving an individual constant of a given type by its string or integer value</li> 93 * </ul> 94 * 95 * @author Dmitri Plotnikov 96 * @version $Revision$ $Date$ 97 */ 98public class Enum implements Serializable 99{ 100 private transient EnumList type; 101 private String string; 102 private int integer; 103 private String label; 104 105 private static HashMap byClass = new HashMap(); 106 107 /** 108 * Initialize the Enum, using default integer and string values. 109 */ 110 protected Enum(){ 111 type = getEnumList(getClass()); 112 integer = type.add(this); 113 } 114 115 /** 116 * Initialize the Enum with a string value. An integer value is assigned automatically. 117 */ 118 protected Enum(String string){ 119 type = getEnumList(getClass()); 120 this.string = string; 121 integer = type.add(this); 122 } 123 124 /** 125 * Initialize the Enum with a string value and a default label. 126 * An integer value is assigned automatically. 127 */ 128 protected Enum(String string, String label){ 129 type = getEnumList(getClass()); 130 this.string = string; 131 this.label = label; 132 integer = type.add(this); 133 } 134 135 /** 136 * Initialize the Enum with an integer value. A string value will be produced 137 * automatically. 138 */ 139 protected Enum(int integer){ 140 type = getEnumList(getClass()); 141 this.integer = integer; 142 type.add(this); 143 } 144 145 /** 146 * Initialize the Enum with an integer value and a default label. 147 * A string value will be produced automatically. 148 */ 149 protected Enum(int integer, String label){ 150 type = getEnumList(getClass()); 151 this.integer = integer; 152 this.label = label; 153 type.add(this); 154 } 155 156 /** 157 * Returns the string value of this Enum. 158 * Depending on how the Enum was created, the method behaves differently: 159 * <ui> 160 * <li>If the constant was created as a <code>public static Enum</code>, 161 * the name of that field is returned.</li> 162 * <li>If the constant was declared as a <code>public static int</code>, 163 * and registered with <code>initIntegerEnum</code>, 164 * the name of that integer field is returned.</li> 165 * <li>If the constant was loaded from a .properties file, the 166 * corresponding property name is returned.</li> 167 * </ul> 168 */ 169 public String stringValue(){ 170 if (string == null){ 171 string = type.getDefaultStringValue(this); 172 } 173 return string; 174 } 175 176 /** 177 * Returns the Enum's integer value. An Enum can be allocated with an explicitly 178 * specified integer value. If the integer value is not specified, the Enum's constructor 179 * gives it a default one, which is computed by adding one to the 180 * integer value of the previous Enum of the same type. 181 */ 182 public int intValue(){ 183 return integer; 184 } 185 186 /** 187 * Returns this Enum's human readable description in the default locale. 188 */ 189 public String toString(){ 190 return toString(Locale.getDefault()); 191 } 192 193 /** 194 * Returns this Enum's human readable description in the specified locale. 195 * If the resource bundle is not found or does not have a value for this Enum, 196 * the value of the default label specified during construction is returned. 197 * If that value was not unspecified, the Enum's string value is returned. 198 */ 199 public String toString(Locale locale){ 200 try { 201 ResourceBundle bundle = ResourceBundle.getBundle( 202 getClass().getName(), locale, getClass().getClassLoader()); 203 return bundle.getString(stringValue()); 204 } 205 catch(MissingResourceException e){ 206 } 207 if (label != null){ 208 return label; 209 } 210 return stringValue(); 211 } 212 213 /** 214 * Returns an array of all Enums of the specified class in the order 215 * they were created. 216 */ 217 public static Enum[] enum(Class enumClass){ 218 return getEnumList(enumClass).enum(); 219 } 220 221 /** 222 * Returns the Enum of the specified class that has the specified st, 223 * or null if there is no such Enum. 224 */ 225 public static Enum enum(Class enumClass, String string){ 226 return getEnumList(enumClass).get(string); 227 } 228 229 /** 230 * Returns the Enum of the specified class that has the specified integer value, 231 * or null if there is no such Enum. 232 */ 233 public static Enum enum(Class enumClass, int integer){ 234 return getEnumList(enumClass).get(integer); 235 } 236 237 protected static void initIntegerEnum(Class enumClass){ 238 Field fields[] = enumClass.getFields(); 239 for (int i = 0; i < fields.length; i++){ 240 int mod = fields[i].getModifiers(); 241 if (Modifier.isPublic(mod) && Modifier.isStatic(mod)){ 242 Class type = fields[i].getType(); 243 if (type == int.class || type == char.class || 244 type == short.class || type == byte.class){ 245 try { 246 Number n = (Number)fields[i].get(null); 247 Enum enum = (Enum)enumClass.newInstance(); 248 enum.integer = n.intValue(); 249 enum.string = fields[i].getName(); 250 } 251 catch (Exception e){ 252 throw new RuntimeException("Cannot init integer Enum class " + 253 enumClass.getName() + "\n " + e); 254 } 255 } 256 } 257 } 258 } 259 260 /** 261 * Loads properties from an <i>enumClass</i>.properties resource file. 262 * Uses property names as string values of the Enums. 263 */ 264 protected static void loadEnum(Class enumClass){ 265 allocateEnums(enumClass, loadKeys(enumClass), false); 266 } 267 268 /** 269 * Loads properties from an <i>enumClass</i>.properties resource file. 270 * Uses property names as integer values of the Enums. 271 */ 272 protected static void loadIntegerEnum(Class enumClass){ 273 allocateEnums(enumClass, loadKeys(enumClass), true); 274 } 275 276 /** 277 * Finds the EnumList registered for the specified class. Allocates 278 * one if it does not exist yet. 279 */ 280 private static EnumList getEnumList(Class clazz){ 281 EnumList type = (EnumList)byClass.get(clazz); 282 if (type == null){ 283 type = new EnumList(); 284 byClass.put(clazz, type); 285 } 286 return type; 287 } 288 289 /** 290 * Ensures uniqueness of Enums during deserialization. Finds an Enum 291 * that is already declared with the same integer value and returns 292 * that Enum instead of the current instance. 293 */ 294 protected Object readResolve() throws ObjectStreamException { 295 Class clazz = getClass(); 296 EnumList type = null; 297 try { 298 type = getEnumList(clazz); 299 } 300 catch (Exception ex){ 301 throw new InvalidClassException(clazz.getName(), 302 "Is not a valid Enum class, " + ex.getMessage()); 303 } 304 305 Enum enum = type.get(integer); 306 if (enum == null){ 307 throw new InvalidClassException(clazz.getName(), 308 "Enum is not associated with its own EnumList " + integer); 309 } 310 return enum; 311 } 312 313 /** 314 * Loads property names from an <i>enumClass</i>.properties file. 315 */ 316 private static List loadKeys(Class enumClass){ 317 final List keys = new ArrayList(); 318 String resName = enumClass.getName().replace('.', '/') + ".properties"; 319 try { 320 InputStream stream = null; 321 ClassLoader loader = enumClass.getClassLoader(); 322 if (loader != null) { 323 stream = loader.getResourceAsStream(resName); 324 } else { 325 stream = ClassLoader.getSystemResourceAsStream(resName); 326 } 327 328 if (stream == null){ 329 throw new RuntimeException("No such resource"); 330 } 331 332 stream = new BufferedInputStream(stream); 333 334 // We are using the Properties class as a parser for the resource file 335 new Properties(){ 336 public Object put(Object key, Object value){ 337 keys.add(key); 338 return super.put(key, value); 339 } 340 }.load(stream); 341 stream.close(); 342 } 343 catch (Exception ex){ 344 throw new RuntimeException("Cannot load values from: " + resName + 345 "\n " + ex); 346 } 347 return keys; 348 } 349 350 /** 351 * Creates Enums for all keys (string or integer) in the keys list. 352 */ 353 private static void allocateEnums(Class enumClass, List keys, boolean isInteger){ 354 Constructor constructor = null; 355 try { 356 constructor = enumClass.getConstructor( 357 new Class[]{isInteger ? int.class : String.class}); 358 } 359 catch (NoSuchMethodException ex){ 360 } 361 362 for (Iterator it = keys.iterator(); it.hasNext();){ 363 String key = (String)it.next(); 364 try { 365 if (isInteger){ 366 if (constructor != null){ 367 Integer code = Integer.decode(key); 368 constructor.newInstance(new Object[]{code}); 369 } 370 else { 371 int code = Integer.parseInt(key); 372 Enum enum = (Enum)enumClass.newInstance(); 373 enum.string = key; 374 enum.integer = code; 375 } 376 } 377 else { 378 if (constructor != null){ 379 constructor.newInstance(new Object[]{key}); 380 } 381 else { 382 Enum enum = (Enum)enumClass.newInstance(); 383 enum.string = key; 384 } 385 } 386 } 387 catch (Exception ex){ 388 throw new RuntimeException("Cannot allocate Enum " + enumClass.getName() + 389 "." + key + "\n " + ex); 390 } 391 } 392 } 393 394 /** 395 * There is one instance of EnumList per Enum type. The object maintains 396 * all sorts of per-type information about an Enum type. 397 */ 398 private static class EnumList { 399 private ArrayList enumList = new ArrayList(); 400 private Enum[] array; 401 private HashMap byString; 402 private HashMap byEnum; 403 404 private static final Enum[] ENUM_ARRAY = new Enum[0]; 405 406 /** 407 * Called automatically by the Enum constructor. 408 */ 409 public int add(Enum enum){ 410 enumList.add(enum); 411 array = null; 412 byString = null; 413 if (enumList.size() == 1){ 414 return 0; 415 } 416 else { 417 return ((Enum)enumList.get(enumList.size() - 2)).intValue() + 1; 418 } 419 } 420 421 /** 422 * Returns all array contained by the enum. 423 */ 424 public Enum[] enum(){ 425 if (array == null){ 426 array = (Enum[])enumList.toArray(ENUM_ARRAY); 427 } 428 return array; 429 } 430 431 /** 432 * Returns a Enum for the specified string value, or null if there 433 * is no such enum. 434 */ 435 public Enum get(String string){ 436 Enum enums[] = enum(); 437 if (enums.length < 10){ 438 for (int i = 0; i < enums.length; i++){ 439 if (enums[i].stringValue().equals(string)){ 440 return enums[i]; 441 } 442 } 443 return null; 444 } 445 446 if (byString == null){ 447 byString = new HashMap(); 448 Enum[] array = enum(); 449 for (int i = 0; i < array.length; i++){ 450 byString.put(array[i].stringValue(), array[i]); 451 } 452 } 453 return (Enum)byString.get(string); 454 } 455 456 /** 457 * Returns an Enum for the specified integer value, or null if there 458 * is no such Enum. 459 */ 460 public Enum get(int integer){ 461 Enum enums[] = enum(); 462 if (integer >= 0 && integer < enums.length){ 463 if (enums[integer].intValue() == integer){ 464 return enums[integer]; 465 } 466 } 467 468 for (int i = 0; i < enums.length; i++){ 469 if (enums[i].intValue() == integer){ 470 return enums[i]; 471 } 472 } 473 return null; 474 } 475 476 /** 477 * Returns the name of a public static field declared by 478 * the specified enum's class for the specified enum. 479 * If there is no such field, returns the enum's integer value. 480 */ 481 public String getDefaultStringValue(Enum enum){ 482 String string = null; 483 if (byEnum != null){ 484 string = (String)byEnum.get(enum); 485 if (string != null){ 486 return string; 487 } 488 } 489 else { 490 byEnum = new HashMap(); 491 } 492 Field fields[] = enum.getClass().getFields(); 493 for (int i = 0; i < fields.length; i++){ 494 int mod = fields[i].getModifiers(); 495 if (Modifier.isPublic(mod) && Modifier.isStatic(mod) && 496 Enum.class.isAssignableFrom(fields[i].getType())){ 497 try { 498 Enum e = (Enum)fields[i].get(null); 499 byEnum.put(e, fields[i].getName()); 500 if (e == enum){ 501 string = fields[i].getName(); 502 } 503 } 504 catch (IllegalAccessException e){ 505 // Ignore errors 506 } 507 } 508 } 509 if (string == null){ 510 string = String.valueOf(enum.intValue()); 511 } 512 return string; 513 } 514 } 515}