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