001    package ezvcard.util;
002    
003    import java.lang.reflect.Field;
004    import java.lang.reflect.Modifier;
005    import java.util.ArrayList;
006    import java.util.Collection;
007    import java.util.Collections;
008    
009    /*
010     Copyright (c) 2013, Michael Angstadt
011     All rights reserved.
012    
013     Redistribution and use in source and binary forms, with or without
014     modification, are permitted provided that the following conditions are met: 
015    
016     1. Redistributions of source code must retain the above copyright notice, this
017     list of conditions and the following disclaimer. 
018     2. Redistributions in binary form must reproduce the above copyright notice,
019     this list of conditions and the following disclaimer in the documentation
020     and/or other materials provided with the distribution. 
021    
022     THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
023     ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
024     WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
025     DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
026     ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
027     (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
028     LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
029     ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
030     (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
031     SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
032     */
033    
034    /**
035     * Manages objects that are like enums in that they are constant, but unlike
036     * enums in that new instances can be created during runtime. This class ensures
037     * that all instances of a class are unique, so they can be safely compared
038     * using "==" (provided their constructors are private). It mimics the
039     * "case class" feature in Scala.
040     * @author Michael Angstadt
041     * 
042     * @param <T> the class
043     * @param <V> the value that the class holds (e.g. String)
044     */
045    public abstract class CaseClasses<T, V> {
046            protected final Class<T> clazz;
047            protected volatile Collection<T> preDefined = null;
048            protected Collection<T> runtimeDefined = null;
049    
050            /**
051             * Creates a new case class collection.
052             * @param clazz the case class
053             */
054            public CaseClasses(Class<T> clazz) {
055                    this.clazz = clazz;
056            }
057    
058            /**
059             * Creates a new instance of the case class.
060             * @param value the value to give the instance
061             * @return the new instance
062             */
063            protected abstract T create(V value);
064    
065            /**
066             * Determines if a value is associated with a case object.
067             * @param object the object
068             * @param value the value
069             * @return true if it matches, false if not
070             */
071            protected abstract boolean matches(T object, V value);
072    
073            /**
074             * Searches for a case object by value, only looking at the case class'
075             * static constants (does not include runtime-defined objects).
076             * @param value the value
077             * @return the object or null if one wasn't found
078             */
079            public T find(V value) {
080                    checkInit();
081    
082                    for (T obj : preDefined) {
083                            if (matches(obj, value)) {
084                                    return obj;
085                            }
086                    }
087                    return null;
088            }
089    
090            /**
091             * Searches for a case object by value, creating a new object if one cannot
092             * be found.
093             * @param value the value
094             * @return the object
095             */
096            public T get(V value) {
097                    T found = find(value);
098                    if (found != null) {
099                            return found;
100                    }
101    
102                    synchronized (runtimeDefined) {
103                            for (T obj : runtimeDefined) {
104                                    if (matches(obj, value)) {
105                                            return obj;
106                                    }
107                            }
108    
109                            T created = create(value);
110                            runtimeDefined.add(created);
111                            return created;
112                    }
113            }
114    
115            /**
116             * Gets all the static constants of the case class.
117             * @return all static constants
118             */
119            public Collection<T> all() {
120                    checkInit();
121                    return preDefined;
122            }
123    
124            private void checkInit() {
125                    if (preDefined == null) {
126                            synchronized (this) {
127                                    //"double check idiom" (Bloch p.283)
128                                    if (preDefined == null) {
129                                            init();
130                                    }
131                            }
132                    }
133            }
134    
135            private void init() {
136                    Collection<T> preDefined = new ArrayList<T>();
137                    for (Field field : clazz.getFields()) {
138                            int modifiers = field.getModifiers();
139                            //@formatter:off
140                            if (Modifier.isStatic(modifiers) &&
141                                    Modifier.isPublic(modifiers) &&
142                                    field.getDeclaringClass() == clazz &&
143                                    field.getType() == clazz) {
144                                    //@formatter:on
145                                    try {
146                                            Object obj = field.get(null);
147                                            if (obj != null) {
148                                                    T c = clazz.cast(obj);
149                                                    preDefined.add(c);
150                                            }
151                                    } catch (Exception ex) {
152                                            //reflection error
153                                            //should never be thrown because we check for "public static" and the correct type
154                                            throw new RuntimeException(ex);
155                                    }
156                            }
157                    }
158    
159                    runtimeDefined = new ArrayList<T>(0);
160                    this.preDefined = Collections.unmodifiableCollection(preDefined);
161            }
162    }