001package ezvcard.util; 002 003import java.lang.reflect.Field; 004import java.lang.reflect.Modifier; 005import java.util.ArrayList; 006import java.util.Collection; 007import java.util.Collections; 008 009/* 010 Copyright (c) 2012-2023, 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 * <p> 036 * Manages objects that are like enums in that they are constant, but unlike 037 * enums in that new instances can be created during runtime. This class ensures 038 * that all instances of a class are unique, so they can be safely compared 039 * using "==" (provided their constructors are private). 040 * </p> 041 * <p> 042 * This class awkwardly mimics the "case class" feature in Scala. 043 * </p> 044 * <p> 045 * <b>Example:</b> 046 * </p> 047 * <pre class="brush:java"> 048 * public class Color { 049 * public static final CaseClasses<Color, String> VALUES = new ColorCaseClasses(); 050 * public static final Color RED = new Color("red"); 051 * public static final Color GREEN = new Color("green"); 052 * public static final Color BLUE = new Color("blue"); 053 * 054 * private final String name; 055 * 056 * // Constructor should be PRIVATE in order to prevent users from 057 * // instantiating their own objects and circumventing the CaseClasses 058 * // object. 059 * private Color(String name) { 060 * this.name = name; 061 * } 062 * 063 * public String getName() { 064 * return name; 065 * } 066 * 067 * // CaseClasses implementation is an inner class because the Color 068 * // constructor is private. 069 * private static class ColorCaseClasses extends CaseClasses<Color, String> { 070 * public ColorCaseClasses() { 071 * super(Color.class); 072 * } 073 * 074 * @Override 075 * protected Color create(String value) { 076 * return new Color(value); 077 * } 078 * 079 * @Override 080 * protected boolean matches(Color object, String value) { 081 * return object.getName().equalsIgnoreCase(value); 082 * } 083 * } 084 * } 085 * 086 * public class Test { 087 * @Test 088 * public void test() { 089 * assertTrue(Color.RED == Color.VALUES.find("Red")); 090 * assertTrue(Color.RED == Color.VALUES.get("Red")); 091 * 092 * assertNull(Color.VALUES.find("purple")); 093 * Color purple = Color.VALUES.get("purple"); 094 * assertEquals("purple", purple.getName()); 095 * assertTrue(purple == Color.VALUES.get("Purple")); 096 * } 097 * } 098 * </pre> 099 * @author Michael Angstadt 100 * 101 * @param <T> the case class 102 * @param <V> the value that the class holds (e.g. String) 103 */ 104public abstract class CaseClasses<T, V> { 105 protected final Class<T> clazz; 106 private volatile Collection<T> preDefined = null; 107 private Collection<T> runtimeDefined = null; 108 109 /** 110 * Creates a new case class collection. 111 * @param clazz the case class 112 */ 113 public CaseClasses(Class<T> clazz) { 114 this.clazz = clazz; 115 } 116 117 /** 118 * Creates a new instance of the case class. 119 * @param value the value to give the instance 120 * @return the new instance 121 */ 122 protected abstract T create(V value); 123 124 /** 125 * Determines if a case object is "equal to" the given value. 126 * @param object the case object 127 * @param value the value 128 * @return true if it matches, false if not 129 */ 130 protected abstract boolean matches(T object, V value); 131 132 /** 133 * Searches for a case object by value, only looking at the case class' 134 * static constants (does not search runtime-defined constants). 135 * @param value the value 136 * @return the object or null if one wasn't found 137 */ 138 public T find(V value) { 139 checkInit(); 140 141 for (T obj : preDefined) { 142 if (matches(obj, value)) { 143 return obj; 144 } 145 } 146 return null; 147 } 148 149 /** 150 * Searches for a case object by value, creating a new object if one cannot 151 * be found. 152 * @param value the value 153 * @return the object 154 */ 155 public T get(V value) { 156 T found = find(value); 157 if (found != null) { 158 return found; 159 } 160 161 synchronized (runtimeDefined) { 162 for (T obj : runtimeDefined) { 163 if (matches(obj, value)) { 164 return obj; 165 } 166 } 167 168 T created = create(value); 169 runtimeDefined.add(created); 170 return created; 171 } 172 } 173 174 /** 175 * Gets all the static constants of the case class (does not include 176 * runtime-defined constants). 177 * @return all static constants 178 */ 179 public Collection<T> all() { 180 checkInit(); 181 return preDefined; 182 } 183 184 /** 185 * Checks to see if this class's fields were initialized yet, and 186 * initializes them if they haven't been initialized. This method is 187 * thread-safe. 188 */ 189 private void checkInit() { 190 if (preDefined == null) { 191 synchronized (this) { 192 //"double check idiom" (Bloch p.283) 193 if (preDefined == null) { 194 init(); 195 } 196 } 197 } 198 } 199 200 /** 201 * Initializes this class's fields. 202 */ 203 private void init() { 204 Collection<T> preDefined = new ArrayList<>(); 205 for (Field field : clazz.getFields()) { 206 if (!isPreDefinedField(field)) { 207 continue; 208 } 209 210 try { 211 Object obj = field.get(null); 212 if (obj != null) { 213 T c = clazz.cast(obj); 214 preDefined.add(c); 215 } 216 } catch (Exception e) { 217 //reflection error 218 //should never be thrown because we check for "public static" and the correct type 219 throw new RuntimeException(e); 220 } 221 } 222 223 runtimeDefined = new ArrayList<>(0); 224 this.preDefined = Collections.unmodifiableCollection(preDefined); 225 } 226 227 /** 228 * Determines if a field should be treated as a predefined case object. 229 * @param field the field 230 * @return true if it's a predefined case object, false if not 231 */ 232 private boolean isPreDefinedField(Field field) { 233 int modifiers = field.getModifiers(); 234 235 //@formatter:off 236 return 237 Modifier.isStatic(modifiers) && 238 Modifier.isPublic(modifiers) && 239 field.getDeclaringClass() == clazz && 240 field.getType() == clazz; 241 //@formatter:on 242 } 243}