001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 package org.apache.commons.lang.enums; 018 019 import java.io.Serializable; 020 import java.lang.reflect.InvocationTargetException; 021 import java.lang.reflect.Method; 022 import java.util.ArrayList; 023 import java.util.Collections; 024 import java.util.HashMap; 025 import java.util.Iterator; 026 import java.util.List; 027 import java.util.Map; 028 import java.util.WeakHashMap; 029 030 import org.apache.commons.lang.ClassUtils; 031 import org.apache.commons.lang.StringUtils; 032 033 /** 034 * <p>Abstract superclass for type-safe enums.</p> 035 * 036 * <p>One feature of the C programming language lacking in Java is enumerations. The 037 * C implementation based on ints was poor and open to abuse. The original Java 038 * recommendation and most of the JDK also uses int constants. It has been recognised 039 * however that a more robust type-safe class-based solution can be designed. This 040 * class follows the basic Java type-safe enumeration pattern.</p> 041 * 042 * <p><em>NOTE:</em> Due to the way in which Java ClassLoaders work, comparing 043 * Enum objects should always be done using <code>equals()</code>, not <code>==</code>. 044 * The equals() method will try == first so in most cases the effect is the same.</p> 045 * 046 * <p>Of course, if you actually want (or don't mind) Enums in different class 047 * loaders being non-equal, then you can use <code>==</code>.</p> 048 * 049 * <h4>Simple Enums</h4> 050 * 051 * <p>To use this class, it must be subclassed. For example:</p> 052 * 053 * <pre> 054 * public final class ColorEnum extends Enum { 055 * public static final ColorEnum RED = new ColorEnum("Red"); 056 * public static final ColorEnum GREEN = new ColorEnum("Green"); 057 * public static final ColorEnum BLUE = new ColorEnum("Blue"); 058 * 059 * private ColorEnum(String color) { 060 * super(color); 061 * } 062 * 063 * public static ColorEnum getEnum(String color) { 064 * return (ColorEnum) getEnum(ColorEnum.class, color); 065 * } 066 * 067 * public static Map getEnumMap() { 068 * return getEnumMap(ColorEnum.class); 069 * } 070 * 071 * public static List getEnumList() { 072 * return getEnumList(ColorEnum.class); 073 * } 074 * 075 * public static Iterator iterator() { 076 * return iterator(ColorEnum.class); 077 * } 078 * } 079 * </pre> 080 * 081 * <p>As shown, each enum has a name. This can be accessed using <code>getName</code>.</p> 082 * 083 * <p>The <code>getEnum</code> and <code>iterator</code> methods are recommended. 084 * Unfortunately, Java restrictions require these to be coded as shown in each subclass. 085 * An alternative choice is to use the {@link EnumUtils} class.</p> 086 * 087 * <h4>Subclassed Enums</h4> 088 * <p>A hierarchy of Enum classes can be built. In this case, the superclass is 089 * unaffected by the addition of subclasses (as per normal Java). The subclasses 090 * may add additional Enum constants <em>of the type of the superclass</em>. The 091 * query methods on the subclass will return all of the Enum constants from the 092 * superclass and subclass.</p> 093 * 094 * <pre> 095 * public final class ExtraColorEnum extends ColorEnum { 096 * // NOTE: Color enum declared above is final, change that to get this 097 * // example to compile. 098 * public static final ColorEnum YELLOW = new ExtraColorEnum("Yellow"); 099 * 100 * private ExtraColorEnum(String color) { 101 * super(color); 102 * } 103 * 104 * public static ColorEnum getEnum(String color) { 105 * return (ColorEnum) getEnum(ExtraColorEnum.class, color); 106 * } 107 * 108 * public static Map getEnumMap() { 109 * return getEnumMap(ExtraColorEnum.class); 110 * } 111 * 112 * public static List getEnumList() { 113 * return getEnumList(ExtraColorEnum.class); 114 * } 115 * 116 * public static Iterator iterator() { 117 * return iterator(ExtraColorEnum.class); 118 * } 119 * } 120 * </pre> 121 * 122 * <p>This example will return RED, GREEN, BLUE, YELLOW from the List and iterator 123 * methods in that order. The RED, GREEN and BLUE instances will be the same (==) 124 * as those from the superclass ColorEnum. Note that YELLOW is declared as a 125 * ColorEnum and not an ExtraColorEnum.</p> 126 * 127 * <h4>Functional Enums</h4> 128 * 129 * <p>The enums can have functionality by defining subclasses and 130 * overriding the <code>getEnumClass()</code> method:</p> 131 * 132 * <pre> 133 * public static final OperationEnum PLUS = new PlusOperation(); 134 * private static final class PlusOperation extends OperationEnum { 135 * private PlusOperation() { 136 * super("Plus"); 137 * } 138 * public int eval(int a, int b) { 139 * return a + b; 140 * } 141 * } 142 * public static final OperationEnum MINUS = new MinusOperation(); 143 * private static final class MinusOperation extends OperationEnum { 144 * private MinusOperation() { 145 * super("Minus"); 146 * } 147 * public int eval(int a, int b) { 148 * return a - b; 149 * } 150 * } 151 * 152 * private OperationEnum(String color) { 153 * super(color); 154 * } 155 * 156 * public final Class getEnumClass() { // NOTE: new method! 157 * return OperationEnum.class; 158 * } 159 * 160 * public abstract double eval(double a, double b); 161 * 162 * public static OperationEnum getEnum(String name) { 163 * return (OperationEnum) getEnum(OperationEnum.class, name); 164 * } 165 * 166 * public static Map getEnumMap() { 167 * return getEnumMap(OperationEnum.class); 168 * } 169 * 170 * public static List getEnumList() { 171 * return getEnumList(OperationEnum.class); 172 * } 173 * 174 * public static Iterator iterator() { 175 * return iterator(OperationEnum.class); 176 * } 177 * } 178 * </pre> 179 * <p>The code above will work on JDK 1.2. If JDK1.3 and later is used, 180 * the subclasses may be defined as anonymous.</p> 181 * 182 * <h4>Nested class Enums</h4> 183 * 184 * <p>Care must be taken with class loading when defining a static nested class 185 * for enums. The static nested class can be loaded without the surrounding outer 186 * class being loaded. This can result in an empty list/map/iterator being returned. 187 * One solution is to define a static block that references the outer class where 188 * the constants are defined. For example:</p> 189 * 190 * <pre> 191 * public final class Outer { 192 * public static final BWEnum BLACK = new BWEnum("Black"); 193 * public static final BWEnum WHITE = new BWEnum("White"); 194 * 195 * // static nested enum class 196 * public static final class BWEnum extends Enum { 197 * 198 * static { 199 * // explicitly reference BWEnum class to force constants to load 200 * Object obj = Outer.BLACK; 201 * } 202 * 203 * // ... other methods omitted 204 * } 205 * } 206 * </pre> 207 * 208 * <p>Although the above solves the problem, it is not recommended. The best solution 209 * is to define the constants in the enum class, and hold references in the outer class: 210 * 211 * <pre> 212 * public final class Outer { 213 * public static final BWEnum BLACK = BWEnum.BLACK; 214 * public static final BWEnum WHITE = BWEnum.WHITE; 215 * 216 * // static nested enum class 217 * public static final class BWEnum extends Enum { 218 * // only define constants in enum classes - private if desired 219 * private static final BWEnum BLACK = new BWEnum("Black"); 220 * private static final BWEnum WHITE = new BWEnum("White"); 221 * 222 * // ... other methods omitted 223 * } 224 * } 225 * </pre> 226 * 227 * <p>For more details, see the 'Nested' test cases. 228 * 229 * <h4>Lang Enums and Java 5.0 Enums</h4> 230 * 231 * <p>Enums were added to Java in Java 5.0. The main differences between Lang's 232 * implementation and the new official JDK implementation are: </p> 233 * <ul> 234 * <li>The standard Enum is a not just a superclass, but is a type baked into the 235 * language. </li> 236 * <li>The standard Enum does not support extension, so the standard methods that 237 * are provided in the Lang enum are not available. </li> 238 * <li>Lang mandates a String name, whereas the standard Enum uses the class 239 * name as its name. getName() changes to name(). </li> 240 * </ul> 241 * 242 * <p>Generally people should use the standard Enum. Migrating from the Lang 243 * enum to the standard Enum is not as easy as it might be due to the lack of 244 * class inheritence in standard Enums. This means that it's not possible 245 * to provide a 'super-enum' which could provide the same utility methods 246 * that the Lang enum does. The following utility class is a Java 5.0 247 * version of our EnumUtils class and provides those utility methods. </p> 248 * 249 * <pre> 250 * import java.util.*; 251 * 252 * public class EnumUtils { 253 * 254 * public static Enum getEnum(Class enumClass, String token) { 255 * return Enum.valueOf(enumClass, token); 256 * } 257 * 258 * public static Map getEnumMap(Class enumClass) { 259 * HashMap map = new HashMap(); 260 * Iterator itr = EnumUtils.iterator(enumClass); 261 * while(itr.hasNext()) { 262 * Enum enm = (Enum) itr.next(); 263 * map.put( enm.name(), enm ); 264 * } 265 * return map; 266 * } 267 * 268 * public static List getEnumList(Class enumClass) { 269 * return new ArrayList( EnumSet.allOf(enumClass) ); 270 * } 271 * 272 * public static Iterator iterator(Class enumClass) { 273 * return EnumUtils.getEnumList(enumClass).iterator(); 274 * } 275 * } 276 * </pre> 277 * 278 * @author Apache Avalon project 279 * @author Apache Software Foundation 280 * @author Chris Webb 281 * @author Mike Bowler 282 * @author Matthias Eichel 283 * @since 2.1 (class existed in enum package from v1.0) 284 * @version $Id: Enum.java 912394 2010-02-21 20:16:22Z niallp $ 285 */ 286 public abstract class Enum implements Comparable, Serializable { 287 288 /** 289 * Required for serialization support. 290 * 291 * @see java.io.Serializable 292 */ 293 private static final long serialVersionUID = -487045951170455942L; 294 295 // After discussion, the default size for HashMaps is used, as the 296 // sizing algorithm changes across the JDK versions 297 /** 298 * An empty <code>Map</code>, as JDK1.2 didn't have an empty map. 299 */ 300 private static final Map EMPTY_MAP = Collections.unmodifiableMap(new HashMap(0)); 301 302 /** 303 * <code>Map</code>, key of class name, value of <code>Entry</code>. 304 */ 305 private static Map cEnumClasses 306 // LANG-334: To avoid exposing a mutating map, 307 // we copy it each time we add to it. This is cheaper than 308 // using a synchronized map since we are almost entirely reads 309 = new WeakHashMap(); 310 311 /** 312 * The string representation of the Enum. 313 */ 314 private final String iName; 315 316 /** 317 * The hashcode representation of the Enum. 318 */ 319 private transient final int iHashCode; 320 321 /** 322 * The toString representation of the Enum. 323 * @since 2.0 324 */ 325 protected transient String iToString = null; 326 327 /** 328 * <p>Enable the iterator to retain the source code order.</p> 329 */ 330 private static class Entry { 331 /** 332 * Map of Enum name to Enum. 333 */ 334 final Map map = new HashMap(); 335 /** 336 * Map of Enum name to Enum. 337 */ 338 final Map unmodifiableMap = Collections.unmodifiableMap(map); 339 /** 340 * List of Enums in source code order. 341 */ 342 final List list = new ArrayList(25); 343 /** 344 * Map of Enum name to Enum. 345 */ 346 final List unmodifiableList = Collections.unmodifiableList(list); 347 348 /** 349 * <p>Restrictive constructor.</p> 350 */ 351 protected Entry() { 352 super(); 353 } 354 } 355 356 /** 357 * <p>Constructor to add a new named item to the enumeration.</p> 358 * 359 * @param name the name of the enum object, 360 * must not be empty or <code>null</code> 361 * @throws IllegalArgumentException if the name is <code>null</code> 362 * or an empty string 363 * @throws IllegalArgumentException if the getEnumClass() method returns 364 * a null or invalid Class 365 */ 366 protected Enum(String name) { 367 super(); 368 init(name); 369 iName = name; 370 iHashCode = 7 + getEnumClass().hashCode() + 3 * name.hashCode(); 371 // cannot create toString here as subclasses may want to include other data 372 } 373 374 /** 375 * Initializes the enumeration. 376 * 377 * @param name the enum name 378 * @throws IllegalArgumentException if the name is null or empty or duplicate 379 * @throws IllegalArgumentException if the enumClass is null or invalid 380 */ 381 private void init(String name) { 382 if (StringUtils.isEmpty(name)) { 383 throw new IllegalArgumentException("The Enum name must not be empty or null"); 384 } 385 386 Class enumClass = getEnumClass(); 387 if (enumClass == null) { 388 throw new IllegalArgumentException("getEnumClass() must not be null"); 389 } 390 Class cls = getClass(); 391 boolean ok = false; 392 while (cls != null && cls != Enum.class && cls != ValuedEnum.class) { 393 if (cls == enumClass) { 394 ok = true; 395 break; 396 } 397 cls = cls.getSuperclass(); 398 } 399 if (ok == false) { 400 throw new IllegalArgumentException("getEnumClass() must return a superclass of this class"); 401 } 402 403 Entry entry; 404 synchronized( Enum.class ) { // LANG-334 405 // create entry 406 entry = (Entry) cEnumClasses.get(enumClass); 407 if (entry == null) { 408 entry = createEntry(enumClass); 409 Map myMap = new WeakHashMap( ); // we avoid the (Map) constructor to achieve JDK 1.2 support 410 myMap.putAll( cEnumClasses ); 411 myMap.put(enumClass, entry); 412 cEnumClasses = myMap; 413 } 414 } 415 if (entry.map.containsKey(name)) { 416 throw new IllegalArgumentException("The Enum name must be unique, '" + name + "' has already been added"); 417 } 418 entry.map.put(name, this); 419 entry.list.add(this); 420 } 421 422 /** 423 * <p>Handle the deserialization of the class to ensure that multiple 424 * copies are not wastefully created, or illegal enum types created.</p> 425 * 426 * @return the resolved object 427 */ 428 protected Object readResolve() { 429 Entry entry = (Entry) cEnumClasses.get(getEnumClass()); 430 if (entry == null) { 431 return null; 432 } 433 return entry.map.get(getName()); 434 } 435 436 //-------------------------------------------------------------------------------- 437 438 /** 439 * <p>Gets an <code>Enum</code> object by class and name.</p> 440 * 441 * @param enumClass the class of the Enum to get, must not 442 * be <code>null</code> 443 * @param name the name of the <code>Enum</code> to get, 444 * may be <code>null</code> 445 * @return the enum object, or <code>null</code> if the enum does not exist 446 * @throws IllegalArgumentException if the enum class 447 * is <code>null</code> 448 */ 449 protected static Enum getEnum(Class enumClass, String name) { 450 Entry entry = getEntry(enumClass); 451 if (entry == null) { 452 return null; 453 } 454 return (Enum) entry.map.get(name); 455 } 456 457 /** 458 * <p>Gets the <code>Map</code> of <code>Enum</code> objects by 459 * name using the <code>Enum</code> class.</p> 460 * 461 * <p>If the requested class has no enum objects an empty 462 * <code>Map</code> is returned.</p> 463 * 464 * @param enumClass the class of the <code>Enum</code> to get, 465 * must not be <code>null</code> 466 * @return the enum object Map 467 * @throws IllegalArgumentException if the enum class is <code>null</code> 468 * @throws IllegalArgumentException if the enum class is not a subclass of Enum 469 */ 470 protected static Map getEnumMap(Class enumClass) { 471 Entry entry = getEntry(enumClass); 472 if (entry == null) { 473 return EMPTY_MAP; 474 } 475 return entry.unmodifiableMap; 476 } 477 478 /** 479 * <p>Gets the <code>List</code> of <code>Enum</code> objects using the 480 * <code>Enum</code> class.</p> 481 * 482 * <p>The list is in the order that the objects were created (source code order). 483 * If the requested class has no enum objects an empty <code>List</code> is 484 * returned.</p> 485 * 486 * @param enumClass the class of the <code>Enum</code> to get, 487 * must not be <code>null</code> 488 * @return the enum object Map 489 * @throws IllegalArgumentException if the enum class is <code>null</code> 490 * @throws IllegalArgumentException if the enum class is not a subclass of Enum 491 */ 492 protected static List getEnumList(Class enumClass) { 493 Entry entry = getEntry(enumClass); 494 if (entry == null) { 495 return Collections.EMPTY_LIST; 496 } 497 return entry.unmodifiableList; 498 } 499 500 /** 501 * <p>Gets an <code>Iterator</code> over the <code>Enum</code> objects in 502 * an <code>Enum</code> class.</p> 503 * 504 * <p>The <code>Iterator</code> is in the order that the objects were 505 * created (source code order). If the requested class has no enum 506 * objects an empty <code>Iterator</code> is returned.</p> 507 * 508 * @param enumClass the class of the <code>Enum</code> to get, 509 * must not be <code>null</code> 510 * @return an iterator of the Enum objects 511 * @throws IllegalArgumentException if the enum class is <code>null</code> 512 * @throws IllegalArgumentException if the enum class is not a subclass of Enum 513 */ 514 protected static Iterator iterator(Class enumClass) { 515 return Enum.getEnumList(enumClass).iterator(); 516 } 517 518 //----------------------------------------------------------------------- 519 /** 520 * <p>Gets an <code>Entry</code> from the map of Enums.</p> 521 * 522 * @param enumClass the class of the <code>Enum</code> to get 523 * @return the enum entry 524 */ 525 private static Entry getEntry(Class enumClass) { 526 if (enumClass == null) { 527 throw new IllegalArgumentException("The Enum Class must not be null"); 528 } 529 if (Enum.class.isAssignableFrom(enumClass) == false) { 530 throw new IllegalArgumentException("The Class must be a subclass of Enum"); 531 } 532 Entry entry = (Entry) cEnumClasses.get(enumClass); 533 534 if (entry == null) { 535 try { 536 // LANG-76 - try to force class initialization for JDK 1.5+ 537 Class.forName(enumClass.getName(), true, enumClass.getClassLoader()); 538 entry = (Entry) cEnumClasses.get(enumClass); 539 } catch (Exception e) { 540 // Ignore 541 } 542 } 543 544 return entry; 545 } 546 547 /** 548 * <p>Creates an <code>Entry</code> for storing the Enums.</p> 549 * 550 * <p>This accounts for subclassed Enums.</p> 551 * 552 * @param enumClass the class of the <code>Enum</code> to get 553 * @return the enum entry 554 */ 555 private static Entry createEntry(Class enumClass) { 556 Entry entry = new Entry(); 557 Class cls = enumClass.getSuperclass(); 558 while (cls != null && cls != Enum.class && cls != ValuedEnum.class) { 559 Entry loopEntry = (Entry) cEnumClasses.get(cls); 560 if (loopEntry != null) { 561 entry.list.addAll(loopEntry.list); 562 entry.map.putAll(loopEntry.map); 563 break; // stop here, as this will already have had superclasses added 564 } 565 cls = cls.getSuperclass(); 566 } 567 return entry; 568 } 569 570 //----------------------------------------------------------------------- 571 /** 572 * <p>Retrieve the name of this Enum item, set in the constructor.</p> 573 * 574 * @return the <code>String</code> name of this Enum item 575 */ 576 public final String getName() { 577 return iName; 578 } 579 580 /** 581 * <p>Retrieves the Class of this Enum item, set in the constructor.</p> 582 * 583 * <p>This is normally the same as <code>getClass()</code>, but for 584 * advanced Enums may be different. If overridden, it must return a 585 * constant value.</p> 586 * 587 * @return the <code>Class</code> of the enum 588 * @since 2.0 589 */ 590 public Class getEnumClass() { 591 return getClass(); 592 } 593 594 /** 595 * <p>Tests for equality.</p> 596 * 597 * <p>Two Enum objects are considered equal 598 * if they have the same class names and the same names. 599 * Identity is tested for first, so this method usually runs fast.</p> 600 * 601 * <p>If the parameter is in a different class loader than this instance, 602 * reflection is used to compare the names.</p> 603 * 604 * @param other the other object to compare for equality 605 * @return <code>true</code> if the Enums are equal 606 */ 607 public final boolean equals(Object other) { 608 if (other == this) { 609 return true; 610 } else if (other == null) { 611 return false; 612 } else if (other.getClass() == this.getClass()) { 613 // Ok to do a class cast to Enum here since the test above 614 // guarantee both 615 // classes are in the same class loader. 616 return iName.equals(((Enum) other).iName); 617 } else { 618 // This and other are in different class loaders, we must check indirectly 619 if (other.getClass().getName().equals(this.getClass().getName()) == false) { 620 return false; 621 } 622 return iName.equals( getNameInOtherClassLoader(other) ); 623 } 624 } 625 626 /** 627 * <p>Returns a suitable hashCode for the enumeration.</p> 628 * 629 * @return a hashcode based on the name 630 */ 631 public final int hashCode() { 632 return iHashCode; 633 } 634 635 /** 636 * <p>Tests for order.</p> 637 * 638 * <p>The default ordering is alphabetic by name, but this 639 * can be overridden by subclasses.</p> 640 * 641 * <p>If the parameter is in a different class loader than this instance, 642 * reflection is used to compare the names.</p> 643 * 644 * @see java.lang.Comparable#compareTo(Object) 645 * @param other the other object to compare to 646 * @return -ve if this is less than the other object, +ve if greater 647 * than, <code>0</code> of equal 648 * @throws ClassCastException if other is not an Enum 649 * @throws NullPointerException if other is <code>null</code> 650 */ 651 public int compareTo(Object other) { 652 if (other == this) { 653 return 0; 654 } 655 if (other.getClass() != this.getClass()) { 656 if (other.getClass().getName().equals(this.getClass().getName())) { 657 return iName.compareTo( getNameInOtherClassLoader(other) ); 658 } 659 throw new ClassCastException( 660 "Different enum class '" + ClassUtils.getShortClassName(other.getClass()) + "'"); 661 } 662 return iName.compareTo(((Enum) other).iName); 663 } 664 665 /** 666 * <p>Use reflection to return an objects class name.</p> 667 * 668 * @param other The object to determine the class name for 669 * @return The class name 670 */ 671 private String getNameInOtherClassLoader(Object other) { 672 try { 673 Method mth = other.getClass().getMethod("getName", null); 674 String name = (String) mth.invoke(other, null); 675 return name; 676 } catch (NoSuchMethodException e) { 677 // ignore - should never happen 678 } catch (IllegalAccessException e) { 679 // ignore - should never happen 680 } catch (InvocationTargetException e) { 681 // ignore - should never happen 682 } 683 throw new IllegalStateException("This should not happen"); 684 } 685 686 /** 687 * <p>Human readable description of this Enum item.</p> 688 * 689 * @return String in the form <code>type[name]</code>, for example: 690 * <code>Color[Red]</code>. Note that the package name is stripped from 691 * the type name. 692 */ 693 public String toString() { 694 if (iToString == null) { 695 String shortName = ClassUtils.getShortClassName(getEnumClass()); 696 iToString = shortName + "[" + getName() + "]"; 697 } 698 return iToString; 699 } 700 701 }