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