001    package biweekly.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 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             * Gets the value of a case object.
067             * @param object the object
068             * @return the object's value
069             */
070            protected abstract V valueOf(T object);
071    
072            /**
073             * Determines if a value is associated with a case object.
074             * @param object the object
075             * @param value the value
076             * @return true if it matches, false if not
077             */
078            protected abstract boolean matches(T object, V value);
079    
080            /**
081             * Searches for a case object by value, only looking at the case class'
082             * static constants (does not include runtime-defined objects).
083             * @param value the value
084             * @return the object or null if one wasn't found
085             */
086            public T find(V value) {
087                    return find(value, false, false);
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                    return find(value, true, true);
098            }
099    
100            /**
101             * Searches for a case object by value.
102             * @param value the value
103             * @param createIfNotFound true to create a new instance of the object if it
104             * can't be found, false to return "null" if not found
105             * @param searchRuntimeDefined true to include the runtime-defined objects
106             * in the search, false not to
107             * @return the object
108             */
109            protected T find(V value, boolean createIfNotFound, boolean searchRuntimeDefined) {
110                    if (preDefined == null) {
111                            init();
112                    }
113    
114                    for (T obj : preDefined) {
115                            if (matches(obj, value)) {
116                                    return obj;
117                            }
118                    }
119    
120                    if (searchRuntimeDefined) {
121                            for (T obj : runtimeDefined) {
122                                    if (matches(obj, value)) {
123                                            return obj;
124                                    }
125                            }
126                            if (createIfNotFound) {
127                                    T created = create(value);
128                                    runtimeDefined.add(created);
129                                    return created;
130                            }
131                    }
132                    return null;
133            }
134    
135            /**
136             * Gets all the static constants of the case class.
137             * @return all static constants
138             */
139            public Collection<T> all() {
140                    if (preDefined == null) {
141                            init();
142                    }
143                    return preDefined;
144            }
145    
146            @SuppressWarnings("unchecked")
147            private void init() {
148                    preDefined = new ArrayList<T>();
149                    runtimeDefined = new ArrayList<T>(0);
150    
151                    for (Field field : clazz.getFields()) {
152                            int modifiers = field.getModifiers();
153                            //@formatter:off
154                            if (Modifier.isStatic(modifiers) &&
155                                    Modifier.isPublic(modifiers) &&
156                                    field.getDeclaringClass() == clazz &&
157                                    field.getType() == clazz) {
158                                    //@formatter:on
159                                    try {
160                                            Object obj = field.get(null);
161                                            if (obj != null) {
162                                                    T c = (T) obj;
163                                                    preDefined.add(c);
164                                            }
165                                    } catch (Exception ex) {
166                                            //reflection error
167                                            //should never be throw because we check for "public static" and the correct type
168                                    }
169                            }
170                    }
171    
172                    preDefined = Collections.unmodifiableCollection(preDefined);
173            }
174    }