001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.cp; 014 015import static java.util.stream.Collectors.*; 016import static org.apache.juneau.Visibility.*; 017import static org.apache.juneau.internal.CollectionUtils.*; 018 019import java.util.*; 020import java.util.function.*; 021 022import org.apache.juneau.*; 023import org.apache.juneau.annotation.*; 024import org.apache.juneau.reflect.*; 025 026/** 027 * Utility class for creating beans through constructors, creator methods, and builders. 028 * 029 * <p> 030 * Uses a {@link BeanStore} to find available ways to construct beans via injection of beans from the store. 031 * 032 * <p> 033 * This class is instantiated through the following method: 034 * <ul class='javatree'> 035 * <li class='jc'>{@link BeanStore} 036 * <ul class='javatreec'> 037 * <li class='jm'>{@link BeanStore#createBean(Class)} 038 * </ul> 039 * </li> 040 * </ul> 041 * 042 * <h5 class='section'>Example:</h5> 043 * <p class='bjava'> 044 * <jc>// Construct and throw a RuntimeException using a bean store.</jc> 045 * <jk>throw</jk> BeanStore 046 * .<jsm>create</jsm>() 047 * .build() 048 * .addBean(Throwable.<jk>class</jk>, <jv>cause</jv>) 049 * .addBean(String.<jk>class</jk>, <jv>msg</jv>) 050 * .addBean(Object[].<jk>class</jk>, <jv>args</jv>) 051 * .createBean(RuntimeException.<jk>class</jk>) 052 * .run(); 053 * </p> 054 * 055 * <p> 056 * Looks for the following methods for creating a bean: 057 * <ol class='spaced-list'> 058 * <li>Looks for a singleton no-arg method of the form: 059 * <p class='bjava'> 060 * <jk>public static</jk> MyClass <jsm>getInstance</jsm>(); 061 * </p> 062 * <ul> 063 * <li>Deprecated and {@link BeanIgnore @BeanIgnore-annotated} methods are ignored. 064 * </ul> 065 * <li>Looks for a static creator method of the form: 066 * <p class='bjava'> 067 * <jk>public static</jk> MyClass <jsm>create</jsm>(<ja><args></ja>); 068 * </p> 069 * <ul> 070 * <li>All arguments except {@link Optional} and {@link List} parameters must have beans available in the store. 071 * <li>If multiple methods are found, the one with the most matching parameters is used. 072 * <li>Deprecated and {@link BeanIgnore @BeanIgnore-annotated} methods are ignored. 073 * </ul> 074 * <li>Looks for a public constructor of the form: 075 * <p class='bjava'> 076 * <jk>public</jk> MyClass(<ja><args></ja>); 077 * </p> 078 * <ul> 079 * <li>All arguments except {@link Optional} and {@link List} parameters must have beans available in the store. 080 * <li>If multiple methods are found, the one with the most matching parameters is used. 081 * <li>Deprecated and {@link BeanIgnore @BeanIgnore-annotated} methods are ignored. 082 * </ul> 083 * <li>Looks for a protected constructor of the form: 084 * <p class='bjava'> 085 * <jk>protected</jk> MyClass(<ja><args></ja>); 086 * </p> 087 * <ul> 088 * <li>All arguments except {@link Optional} and {@link List} parameters must have beans available in the store. 089 * <li>If multiple methods are found, the one with the most matching parameters is used. 090 * <li>Deprecated and {@link BeanIgnore @BeanIgnore-annotated} methods are ignored. 091 * </ul> 092 * <li>Looks for a static no-arg create method that returns a builder object that can be passed in to a protected constructor. 093 * <p class='bjava'> 094 * <jk>public static</jk> MyClass.Builder <jsm>create</jsm>(); 095 * 096 * <jk>protected</jk> MyClass(MyClass.Builder <jv>builder</jv>); 097 * </p> 098 * <ul> 099 * <li>Deprecated and {@link BeanIgnore @BeanIgnore-annotated} methods are ignored. 100 * </ul> 101 * </ol> 102 * 103 * <h5 class='section'>Notes:</h5><ul> 104 * <li class='note'>The {@link #builder(Class,Object)} method can be used to set an existing initialized builder object to pass to a constructor. 105 * <li class='note'>An existing initialized builder can be set using the {@link #builder(Class,Object)} method. 106 * </ul> 107 * 108 * <h5 class='section'>See Also:</h5><ul> 109 * <li class='jc'>{@link BeanStore} 110 * </ul> 111 * 112 * @param <T> The bean type being created. 113 */ 114public class BeanCreator<T> { 115 116 //----------------------------------------------------------------------------------------------------------------- 117 // Static 118 //----------------------------------------------------------------------------------------------------------------- 119 120 /** 121 * Shortcut for calling <c>BeanStore.INSTANCE.createBean(beanType)</c>. 122 * 123 * @param <T> The bean type to create. 124 * @param beanType The bean type to create. 125 * @return A new creator. 126 */ 127 public static <T> BeanCreator<T> of(Class<T> beanType) { 128 return BeanStore.INSTANCE.createBean(beanType); 129 } 130 131 //----------------------------------------------------------------------------------------------------------------- 132 // Instance 133 //----------------------------------------------------------------------------------------------------------------- 134 135 private final BeanStore store; 136 private ClassInfo type; 137 private Object builder; 138 private T impl; 139 private boolean silent; 140 141 /** 142 * Constructor. 143 * 144 * @param type The bean type being created. 145 * @param store The bean store creating this creator. 146 */ 147 protected BeanCreator(Class<T> type, BeanStore store) { 148 this.type = ClassInfo.of(type); 149 this.store = BeanStore.of(store, store.outer.orElse(null)); 150 } 151 152 /** 153 * Allows you to specify a subclass of the specified bean type to create. 154 * 155 * @param value The value for this setting. 156 * @return This object. 157 */ 158 public BeanCreator<T> type(Class<?> value) { 159 type = ClassInfo.of(value); 160 return this; 161 } 162 163 /** 164 * Allows you to specify a subclass of the specified bean type to create. 165 * 166 * @param value The value for this setting. 167 * @return This object. 168 */ 169 public BeanCreator<T> type(ClassInfo value) { 170 return type(value == null ? null : value.inner()); 171 } 172 173 /** 174 * Allows you to specify a specific instance for the build method to return. 175 * 176 * @param value The value for this setting. 177 * @return This object. 178 */ 179 public BeanCreator<T> impl(T value) { 180 impl = value; 181 return this; 182 } 183 184 /** 185 * Adds an argument to this creator. 186 * 187 * @param <T2> The parameter type. 188 * @param beanType The parameter type. 189 * @param bean The parameter value. 190 * @return This object. 191 */ 192 public <T2> BeanCreator<T> arg(Class<T2> beanType, T2 bean) { 193 store.add(beanType, bean); 194 return this; 195 } 196 197 /** 198 * Suppresses throwing of {@link ExecutableException ExecutableExceptions} from the {@link #run()} method when 199 * a form of creation cannot be found. 200 * 201 * @return This object. 202 */ 203 public BeanCreator<T> silent() { 204 silent = true; 205 return this; 206 } 207 208 /** 209 * Specifies a builder object for the bean type. 210 * 211 * <h5 class='section'>Notes:</h5><ul> 212 * <li class='note'>When specified, we don't look for a static creator method. 213 * </ul> 214 * 215 * @param <B> The class type of the builder. 216 * @param type The class type of the builder. 217 * @param value The value for this setting. 218 * @return This object. 219 */ 220 @SuppressWarnings("unchecked") 221 public <B> BeanCreator<T> builder(Class<B> type, B value) { 222 builder = value; 223 Class<?> t = value.getClass(); 224 do { 225 store.add((Class<T>)t, (T)value); 226 t = t.getSuperclass(); 227 } while(t != null && ! t.equals(type)); 228 return this; 229 } 230 231 /** 232 * Same as {@link #run()} but returns the alternate value if a method of creation could not be found. 233 * 234 * @param other The other bean to use. 235 * @return Either the created or other bean. 236 */ 237 public T orElse(T other) { 238 return execute().orElse(other); 239 } 240 241 /** 242 * Same as {@link #run()} but returns the value wrapped in an {@link Optional}. 243 * 244 * @return A new bean wrapped in an {@link Optional}. 245 */ 246 public Optional<T> execute() { 247 return optional(silent().run()); 248 } 249 250 /** 251 * Creates the bean. 252 * 253 * @return A new bean. 254 * @throws ExecutableException if bean could not be created and {@link #silent()} was not enabled. 255 */ 256 public T run() { 257 258 if (impl != null) 259 return impl; 260 261 if (type == null) 262 return null; 263 264 Value<String> found = Value.empty(); 265 266 // Look for getInstance(Builder). 267 if (builder != null) { 268 MethodInfo m = type.getPublicMethod( 269 x -> x.isStatic() 270 && x.isNotDeprecated() 271 && x.hasNumParams(1) 272 && x.getParam(0).canAccept(builder) 273 && x.hasReturnType(type) 274 && x.hasNoAnnotation(BeanIgnore.class) 275 && x.hasName("getInstance") 276 ); 277 if (m != null) 278 return m.invoke(null, builder); 279 } 280 281 // Look for getInstance(). 282 if (builder == null) { 283 MethodInfo m = type.getPublicMethod( 284 x -> x.isStatic() 285 && x.isNotDeprecated() 286 && x.hasNoParams() 287 && x.hasReturnType(type) 288 && x.hasNoAnnotation(BeanIgnore.class) 289 && x.hasName("getInstance") 290 ); 291 if (m != null) 292 return m.invoke(null); 293 } 294 295 if (builder == null) { 296 // Look for static creator methods. 297 298 Match<MethodInfo> match = new Match<>(); 299 300 // Look for static creator method. 301 type.forEachPublicMethod(x -> isStaticCreateMethod(x), x -> { 302 found.set("STATIC_CREATOR"); 303 if (hasAllParams(x)) 304 match.add(x); 305 }); 306 307 if (match.isPresent()) 308 return match.get().invoke(null, getParams(match.get())); 309 } 310 311 if (type.isInterface()) { 312 if (silent) 313 return null; 314 throw new ExecutableException("Could not instantiate class {0}: {1}.", type.getName(), "Class is an interface"); 315 } 316 317 if (type.isAbstract()) { 318 if (silent) 319 return null; 320 throw new ExecutableException("Could not instantiate class {0}: {1}.", type.getName(), "Class is abstract"); 321 } 322 323 // Look for public constructor. 324 Match<ConstructorInfo> constructorMatch = new Match<>(); 325 type.forEachPublicConstructor(x -> true, x -> { 326 found.setIfEmpty("PUBLIC_CONSTRUCTOR"); 327 if (hasAllParams(x)) 328 constructorMatch.add(x); 329 }); 330 331 // Look for protected constructor. 332 if (! constructorMatch.isPresent()) { 333 type.forEachDeclaredConstructor(x -> x.isProtected(), x -> { 334 found.setIfEmpty("PROTECTED_CONSTRUCTOR"); 335 if (hasAllParams(x)) 336 constructorMatch.add(x); 337 }); 338 } 339 340 // Execute. 341 if (constructorMatch.isPresent()) 342 return constructorMatch.get().invoke(getParams(constructorMatch.get())); 343 344 if (builder == null) { 345 // Look for static-builder/protected-constructor pair. 346 Value<T> value = Value.empty(); 347 type.forEachDeclaredConstructor(x -> x.hasNumParams(1) && x.isVisible(PROTECTED), x -> { 348 Class<?> pt = x.getParam(0).getParameterType().inner(); 349 MethodInfo m = type.getPublicMethod(y -> isStaticCreateMethod(y, pt)); 350 if (m != null) { 351 Object builder = m.invoke(null); 352 value.set(x.accessible().invoke(builder)); 353 } 354 }); 355 if (value.isPresent()) 356 return value.get(); 357 } 358 359 if (silent) 360 return null; 361 362 String msg = null; 363 if (found.isEmpty()) { 364 msg = "No public/protected constructors found"; 365 } else if (found.get().equals("STATIC_CREATOR")) { 366 msg = "Static creator found but could not find prerequisites: " + type.getPublicMethods().stream().filter(x -> isStaticCreateMethod(x)).map(x -> getMissingParams(x)).sorted().collect(joining(" or ")); 367 } else if (found.get().equals("PUBLIC_CONSTRUCTOR")) { 368 msg = "Public constructor found but could not find prerequisites: " + type.getPublicConstructors().stream().map(x -> getMissingParams(x)).sorted().collect(joining(" or ")); 369 } else { 370 msg = "Protected constructor found but could not find prerequisites: " + type.getDeclaredConstructors().stream().filter(x -> x.isProtected()).map(x -> getMissingParams(x)).sorted().collect(joining(" or ")); 371 } 372 throw new ExecutableException("Could not instantiate class {0}: {1}.", type.getName(), msg); 373 } 374 375 /** 376 * Converts this creator into a supplier. 377 * 378 * @return A supplier that returns the results of the {@link #run()} method. 379 */ 380 public Supplier<T> supplier() { 381 return ()->run(); 382 } 383 384 private boolean isStaticCreateMethod(MethodInfo m) { 385 return isStaticCreateMethod(m, type.inner()); 386 } 387 388 private boolean isStaticCreateMethod(MethodInfo m, Class<?> type) { 389 return m.isStatic() 390 && m.isNotDeprecated() 391 && m.hasReturnType(type) 392 && m.hasNoAnnotation(BeanIgnore.class) 393 && m.hasName("create"); 394 } 395 396 //----------------------------------------------------------------------------------------------------------------- 397 // Helpers 398 //----------------------------------------------------------------------------------------------------------------- 399 400 static class Match<T extends ExecutableInfo> { 401 T executable = null; 402 int numMatches = -1; 403 404 @SuppressWarnings("unchecked") 405 void add(T ei) { 406 if (ei.getParamCount() > numMatches) { 407 numMatches = ei.getParamCount(); 408 executable = (T)ei.accessible(); 409 } 410 } 411 412 boolean isPresent() { 413 return executable != null; 414 } 415 416 T get() { 417 return executable; 418 } 419 } 420 421 private boolean hasAllParams(ExecutableInfo ei) { 422 return store.hasAllParams(ei); 423 } 424 425 private Object[] getParams(ExecutableInfo ei) { 426 return store.getParams(ei); 427 } 428 429 private String getMissingParams(ExecutableInfo ei) { 430 return store.getMissingParams(ei); 431 } 432}