PageRenderTime 118ms CodeModel.GetById 79ms app.highlight 32ms RepoModel.GetById 1ms app.codeStats 0ms

/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 */
 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}