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