001package ball.util; 002/*- 003 * ########################################################################## 004 * Utilities 005 * %% 006 * Copyright (C) 2008 - 2022 Allen D. Ball 007 * %% 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 * ########################################################################## 020 */ 021import java.beans.ConstructorProperties; 022import java.lang.reflect.Constructor; 023import java.lang.reflect.Field; 024import java.lang.reflect.InvocationTargetException; 025import java.lang.reflect.Member; 026import java.lang.reflect.Method; 027import java.util.Arrays; 028import java.util.Map; 029import java.util.TreeMap; 030import java.util.TreeSet; 031 032import static java.util.Comparator.comparing; 033import static java.lang.reflect.Modifier.isStatic; 034import static java.lang.reflect.Modifier.isPublic; 035 036/** 037 * {@link Factory} base class. 038 * 039 * @param <T> The type of {@link Object} this 040 * {@link Factory} will produce. 041 * 042 * @author {@link.uri mailto:ball@hcf.dev Allen D. Ball} 043 */ 044public class Factory<T> extends TreeMap<Class<?>[],Member> { 045 private static final long serialVersionUID = -5733222257965875050L; 046 047 /** @serial */ private final Class<? extends T> type; 048 /** @serial */ private final Object factory; 049 050 /** 051 * Sole public constructor. 052 * 053 * @param type The {@link Class} of {@link Object} this 054 * {@link Factory} will produce. 055 * 056 * @throws NullPointerException 057 * If {@code type} is {@code null}. 058 */ 059 @ConstructorProperties({ "type" }) 060 public Factory(Class<? extends T> type) { this(type, null); } 061 062 /** 063 * Construct a {@link Factory} by wrapping a factory instance. 064 * 065 * @param type The {@link Class} of {@link Object} this 066 * {@link Factory} will produce. 067 * @param factory An {@link Object} factory for this 068 * {@code type} (may be {@code null}). 069 * 070 * @throws NullPointerException 071 * If {@code type} is {@code null}. 072 */ 073 @ConstructorProperties({ "type", "factory" }) 074 protected Factory(Class<? extends T> type, Object factory) { 075 super(comparing(Arrays::toString)); 076 077 this.type = type; 078 this.factory = factory; 079 080 CandidateSet set = new CandidateSet(type); 081 082 if (factory != null) { 083 Arrays.stream(factory.getClass().getMethods()) 084 .filter(t -> isPublic(t.getModifiers())) 085 .filter(t -> type.isAssignableFrom(t.getReturnType())) 086 .filter(t -> set.contains(t.getName())) 087 .forEach(t -> putIfAbsent(t.getParameterTypes(), t)); 088 } 089 090 Arrays.stream(type.getMethods()) 091 .filter(t -> (isPublic(t.getModifiers()) 092 && isStatic(t.getModifiers()))) 093 .filter(t -> type.isAssignableFrom(t.getReturnType())) 094 .filter(t -> (set.contains(t.getName()))) 095 .forEach(t -> putIfAbsent(t.getParameterTypes(), t)); 096 097 Arrays.stream(type.getConstructors()) 098 .filter(t -> isPublic(t.getModifiers())) 099 .forEach(t -> putIfAbsent(t.getParameterTypes(), t)); 100 } 101 102 /** 103 * Method to get the type ({@link Class}) of objects produced by this 104 * {@link Factory}. 105 * 106 * @return The type of {@link Object} produced by this 107 * {@link Factory}. 108 */ 109 public Class<? extends T> getType() { return type; } 110 111 /** 112 * Method to get the underlying factory {@link Object}. 113 * 114 * @return The underlying factory {@link Object} or {@code null} if 115 * there is none. 116 */ 117 public Object getFactory() { return factory; } 118 119 /** 120 * Method to get an {@link Object} instance. This method will first 121 * attempt to find and invoke a static factory method. If no factory 122 * method is found, it will then attempt to construct a new instance. 123 * 124 * @param parameters The parameter types to use to search for the 125 * {@link Object} static factory method or 126 * constructor. 127 * @param arguments The arguments to the {@link Object} static 128 * factory method or constructor. 129 * 130 * @return The {@link Object} instance. 131 * 132 * @throws IllegalAccessException 133 * If the specified {@link Constructor} or 134 * {@link Method} enforces Java language 135 * access control and the underlying 136 * {@link Constructor} or {@link Method} is 137 * inaccessible. 138 * @throws IllegalArgumentException 139 * If the number of actual and formal parameters 140 * differ; if an unwrapping conversion for 141 * primitive arguments fails; or if, after 142 * possible unwrapping, a parameter value cannot 143 * be converted to the corresponding formal 144 * parameter type by a method invocation 145 * conversion; if this {@link Constructor} or 146 * {@link Method} pertains to an 147 * {@link Enum} type. 148 * @throws InstantiationException 149 * If the underlying {@link Constructor} or 150 * {@link Method} represents an abstract 151 * {@link Class}. 152 * @throws InvocationTargetException 153 * If the underlying {@link Constructor} or 154 * {@link Method} fails for some reason. 155 * @throws NoSuchMethodException 156 * If the specified {@link Constructor} or 157 * {@link Method} does not exist. 158 */ 159 public T getInstance(Class<?>[] parameters, Object... arguments) throws IllegalAccessException, 160 IllegalArgumentException, 161 InstantiationException, 162 InvocationTargetException, 163 NoSuchMethodException { 164 return apply(getFactoryMethod(parameters), arguments); 165 } 166 167 /** 168 * Method to get an {@link Object} instance. This method will first 169 * attempt to find and invoke a static factory method. If no factory 170 * method is found, it will then attempt to construct a new instance. 171 * 172 * @param arguments The arguments to the {@link Object} static 173 * factory method or constructor. 174 * 175 * @return The {@link Object} instance. 176 * 177 * @throws IllegalAccessException 178 * If the specified {@link Constructor} or 179 * {@link Method} enforces Java language 180 * access control and the underlying 181 * {@link Constructor} or {@link Method} is 182 * inaccessible. 183 * @throws IllegalArgumentException 184 * If the number of actual and formal parameters 185 * differ; if an unwrapping conversion for 186 * primitive arguments fails; or if, after 187 * possible unwrapping, a parameter value cannot 188 * be converted to the corresponding formal 189 * parameter type by a method invocation 190 * conversion; if this {@link Constructor} or 191 * {@link Method} pertains to an 192 * {@link Enum} type. 193 * @throws InstantiationException 194 * If the underlying {@link Constructor} or 195 * {@link Method} represents an abstract 196 * {@link Class}. 197 * @throws InvocationTargetException 198 * If the underlying {@link Constructor} or 199 * {@link Method} fails for some reason. 200 * @throws NoSuchMethodException 201 * If the specified {@link Constructor} or 202 * {@link Method} does not exist. 203 */ 204 public T getInstance(Object... arguments) throws IllegalAccessException, 205 IllegalArgumentException, 206 InstantiationException, 207 InvocationTargetException, 208 NoSuchMethodException { 209 return getInstance(typesOf(arguments), arguments); 210 } 211 212 /** 213 * Method to determine if there is a factory {@link Member} (factory 214 * {@link Method}, static {@link Method}, or {@link Constructor}) to 215 * manufacture or get an {@link Object}. 216 * 217 * @param parameters The {@link Constructor} or {@link Method} 218 * parameter list. 219 * 220 * @return {@code true} if there is such a {@link Member}; 221 * {@code false} otherwise. 222 */ 223 public boolean hasFactoryMethodFor(Class<?>... parameters) { 224 boolean hasMember = false; 225 226 try { 227 getFactoryMethod(parameters); 228 hasMember = (get(parameters) != null); 229 } catch (NoSuchMethodException exception) { 230 hasMember = false; 231 } 232 233 return hasMember; 234 } 235 236 /** 237 * Method to get a factory {@link Member} (factory {@link Method}, 238 * static {@link Method}, or {@link Constructor}) to manufacture or get 239 * an {@link Object}. 240 * 241 * @param parameters The {@link Constructor} or {@link Method} 242 * parameter list. 243 * 244 * @return The factory {@link Member}. 245 * 246 * @throws NoSuchMethodException 247 * If the specified {@link Constructor} or 248 * {@link Method} does not exist. 249 */ 250 public Member getFactoryMethod(Class<?>... parameters) throws NoSuchMethodException { 251 if (! containsKey(parameters)) { 252 put(parameters, getType().getConstructor(parameters)); 253 } 254 255 return get(parameters); 256 } 257 258 /** 259 * Method to apply a factory {@link Member} (factory {@link Method}, 260 * static {@link Method}, or {@link Constructor}) to manufacture or get 261 * an {@link Object}. 262 * 263 * @param member The {@link Member} ({@link Constructor} or 264 * static {@link Method}) to invoke. 265 * @param arguments The array of arguments to apply. 266 * 267 * @return The {@link Object} instance. 268 * 269 * @throws IllegalAccessException 270 * If the specified {@link Constructor} or 271 * {@link Method} enforces Java language 272 * access control and the underlying 273 * {@link Constructor} or {@link Method} is 274 * inaccessible. 275 * @throws InstantiationException 276 * If the underlying {@link Constructor} or 277 * {@link Method} represents an abstract 278 * {@link Class}. 279 * @throws InvocationTargetException 280 * If the underlying {@link Constructor} or 281 * {@link Method} fails for some reason. 282 */ 283 public T apply(Member member, Object... arguments) throws IllegalAccessException, 284 InstantiationException, InvocationTargetException { 285 Object object = null; 286 287 if (member instanceof Method) { 288 object = ((Method) member).invoke(factory, arguments); 289 } else if (member instanceof Constructor) { 290 object = ((Constructor) member).newInstance(arguments); 291 } else if (member instanceof Field) { 292 object = ((Field) member).get(null); 293 } else { 294 throw new IllegalArgumentException("member=" + member); 295 } 296 297 return getType().cast(object); 298 } 299 300 @Override 301 public Member get(Object key) { 302 Member value = null; 303 304 if (key instanceof Class<?>[]) { 305 if (! super.containsKey(key)) { 306 for (Map.Entry<Class<?>[],Member> entry : entrySet()) { 307 if (isApplicable(entry.getKey(), (Class<?>[]) key)) { 308 value = entry.getValue(); 309 break; 310 } 311 } 312 313 super.put((Class<?>[]) key, value); 314 } 315 316 value = super.get(key); 317 } 318 319 return value; 320 } 321 322 /** 323 * Convenience method to get the types of an argument array. 324 * 325 * @param arguments The argument array. 326 * 327 * @return An array of types ({@link Class}s). 328 */ 329 protected static Class<?>[] typesOf(Object... arguments) { 330 Class<?>[] types = new Class<?>[arguments.length]; 331 332 for (int i = 0; i < types.length; i += 1) { 333 types[i] = arguments[i].getClass(); 334 } 335 336 return types; 337 } 338 339 /** 340 * Method to determine if an argument of the specified types may be 341 * applied to a method or constructor with the specified parameters. 342 * 343 * @see Class#isAssignableFrom(Class) 344 * 345 * @param parameters The parameter types. 346 * @param arguments The argument types. 347 * 348 * @return {@code true} if the length of the argument array is the same 349 * as the length of the parameter array and each parameter is 350 * assignable from its corresponding argument; {@code false} 351 * otherwise. 352 */ 353 protected static boolean isApplicable(Class<?>[] parameters, Class<?>... arguments) { 354 boolean match = (parameters.length == arguments.length); 355 356 for (int i = 0; match && i < arguments.length; i += 1) { 357 match &= parameters[i].isAssignableFrom(arguments[i]); 358 } 359 360 return match; 361 } 362 363 private class CandidateSet extends TreeSet<String> { 364 private static final long serialVersionUID = -7927801377734740425L; 365 366 public CandidateSet(Class<? extends T> type) { 367 super(Arrays.asList("compile", 368 "create", 369 "decode", 370 "forName", 371 "getDefault", 372 "getDefaultInstance", 373 "getInstance", 374 "getObjectInstance", 375 "new" + type.getSimpleName(), 376 "newInstance", 377 "valueOf")); 378 379 if (! (type.isAssignableFrom(Boolean.class) 380 || type.isAssignableFrom(Integer.class) 381 || type.isAssignableFrom(Long.class))) { 382 add("get" + type.getSimpleName()); 383 } 384 } 385 } 386}