All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.juneau.cp.BeanCreator Maven / Gradle / Ivy

// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
// * with the License.  You may obtain a copy of the License at                                                              *
// *                                                                                                                         *
// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
// *                                                                                                                         *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
// * specific language governing permissions and limitations under the License.                                              *
// ***************************************************************************************************************************
package org.apache.juneau.cp;

import static java.util.stream.Collectors.*;
import static org.apache.juneau.Visibility.*;
import static org.apache.juneau.internal.CollectionUtils.*;

import java.util.*;
import java.util.function.*;

import org.apache.juneau.*;
import org.apache.juneau.annotation.*;
import org.apache.juneau.reflect.*;

/**
 * Utility class for creating beans through constructors, creator methods, and builders.
 *
 * 

* Uses a {@link BeanStore} to find available ways to construct beans via injection of beans from the store. * *

* This class is instantiated through the following method: *

    *
  • {@link BeanStore} *
      *
    • {@link BeanStore#createBean(Class)} *
    *
  • *
* *
Example:
*

* // Construct and throw a RuntimeException using a bean store. * throw BeanStore * .create() * .build() * .addBean(Throwable.class, cause) * .addBean(String.class, msg) * .addBean(Object[].class, args) * .createBean(RuntimeException.class) * .run(); *

* *

* Looks for the following methods for creating a bean: *

    *
  1. Looks for a singleton no-arg method of the form: *

    * public static MyClass getInstance(); *

    *
      *
    • Deprecated and {@link BeanIgnore @BeanIgnore-annotated} methods are ignored. *
    *
  2. Looks for a static creator method of the form: *

    * public static MyClass create(<args>); *

    *
      *
    • All arguments except {@link Optional} and {@link List} parameters must have beans available in the store. *
    • If multiple methods are found, the one with the most matching parameters is used. *
    • Deprecated and {@link BeanIgnore @BeanIgnore-annotated} methods are ignored. *
    *
  3. Looks for a public constructor of the form: *

    * public MyClass(<args>); *

    *
      *
    • All arguments except {@link Optional} and {@link List} parameters must have beans available in the store. *
    • If multiple methods are found, the one with the most matching parameters is used. *
    • Deprecated and {@link BeanIgnore @BeanIgnore-annotated} methods are ignored. *
    *
  4. Looks for a protected constructor of the form: *

    * protected MyClass(<args>); *

    *
      *
    • All arguments except {@link Optional} and {@link List} parameters must have beans available in the store. *
    • If multiple methods are found, the one with the most matching parameters is used. *
    • Deprecated and {@link BeanIgnore @BeanIgnore-annotated} methods are ignored. *
    *
  5. Looks for a static no-arg create method that returns a builder object that can be passed in to a protected constructor. *

    * public static MyClass.Builder create(); * * protected MyClass(MyClass.Builder builder); *

    *
      *
    • Deprecated and {@link BeanIgnore @BeanIgnore-annotated} methods are ignored. *
    *
* *
Notes:
    *
  • The {@link #builder(Class,Object)} method can be used to set an existing initialized builder object to pass to a constructor. *
  • An existing initialized builder can be set using the {@link #builder(Class,Object)} method. *
* *
See Also:
    *
  • {@link BeanStore} *
* * @param The bean type being created. */ public class BeanCreator { //----------------------------------------------------------------------------------------------------------------- // Static //----------------------------------------------------------------------------------------------------------------- /** * Shortcut for calling BeanStore.INSTANCE.createBean(beanType). * * @param The bean type to create. * @param beanType The bean type to create. * @return A new creator. */ public static BeanCreator of(Class beanType) { return BeanStore.INSTANCE.createBean(beanType); } //----------------------------------------------------------------------------------------------------------------- // Instance //----------------------------------------------------------------------------------------------------------------- private final BeanStore store; private ClassInfo type; private Object builder; private T impl; private boolean silent; /** * Constructor. * * @param type The bean type being created. * @param store The bean store creating this creator. */ protected BeanCreator(Class type, BeanStore store) { this.type = ClassInfo.of(type); this.store = BeanStore.of(store, store.outer.orElse(null)); } /** * Allows you to specify a subclass of the specified bean type to create. * * @param value The value for this setting. * @return This object. */ public BeanCreator type(Class value) { type = ClassInfo.of(value); return this; } /** * Allows you to specify a subclass of the specified bean type to create. * * @param value The value for this setting. * @return This object. */ public BeanCreator type(ClassInfo value) { return type(value == null ? null : value.inner()); } /** * Allows you to specify a specific instance for the build method to return. * * @param value The value for this setting. * @return This object. */ public BeanCreator impl(T value) { impl = value; return this; } /** * Adds an argument to this creator. * * @param The parameter type. * @param beanType The parameter type. * @param bean The parameter value. * @return This object. */ public BeanCreator arg(Class beanType, T2 bean) { store.add(beanType, bean); return this; } /** * Suppresses throwing of {@link ExecutableException ExecutableExceptions} from the {@link #run()} method when * a form of creation cannot be found. * * @return This object. */ public BeanCreator silent() { silent = true; return this; } /** * Specifies a builder object for the bean type. * *
Notes:
    *
  • When specified, we don't look for a static creator method. *
* * @param The class type of the builder. * @param type The class type of the builder. * @param value The value for this setting. * @return This object. */ @SuppressWarnings("unchecked") public BeanCreator builder(Class type, B value) { builder = value; Class t = value.getClass(); do { store.add((Class)t, (T)value); t = t.getSuperclass(); } while(t != null && ! t.equals(type)); return this; } /** * Same as {@link #run()} but returns the alternate value if a method of creation could not be found. * * @param other The other bean to use. * @return Either the created or other bean. */ public T orElse(T other) { return execute().orElse(other); } /** * Same as {@link #run()} but returns the value wrapped in an {@link Optional}. * * @return A new bean wrapped in an {@link Optional}. */ public Optional execute() { return optional(silent().run()); } /** * Creates the bean. * * @return A new bean. * @throws ExecutableException if bean could not be created and {@link #silent()} was not enabled. */ public T run() { if (impl != null) return impl; if (type == null) return null; Value found = Value.empty(); // Look for getInstance(Builder). if (builder != null) { MethodInfo m = type.getPublicMethod( x -> x.isStatic() && x.isNotDeprecated() && x.hasNumParams(1) && x.getParam(0).canAccept(builder) && x.hasReturnType(type) && x.hasNoAnnotation(BeanIgnore.class) && x.hasName("getInstance") ); if (m != null) return m.invoke(null, builder); } // Look for getInstance(). if (builder == null) { MethodInfo m = type.getPublicMethod( x -> x.isStatic() && x.isNotDeprecated() && x.hasNoParams() && x.hasReturnType(type) && x.hasNoAnnotation(BeanIgnore.class) && x.hasName("getInstance") ); if (m != null) return m.invoke(null); } if (builder == null) { // Look for static creator methods. Match match = new Match<>(); // Look for static creator method. type.forEachPublicMethod(x -> isStaticCreateMethod(x), x -> { found.set("STATIC_CREATOR"); if (hasAllParams(x)) match.add(x); }); if (match.isPresent()) return match.get().invoke(null, getParams(match.get())); } if (type.isInterface()) { if (silent) return null; throw new ExecutableException("Could not instantiate class {0}: {1}.", type.getName(), "Class is an interface"); } if (type.isAbstract()) { if (silent) return null; throw new ExecutableException("Could not instantiate class {0}: {1}.", type.getName(), "Class is abstract"); } // Look for public constructor. Match constructorMatch = new Match<>(); type.forEachPublicConstructor(x -> true, x -> { found.setIfEmpty("PUBLIC_CONSTRUCTOR"); if (hasAllParams(x)) constructorMatch.add(x); }); // Look for protected constructor. if (! constructorMatch.isPresent()) { type.forEachDeclaredConstructor(ConstructorInfo::isProtected, x -> { found.setIfEmpty("PROTECTED_CONSTRUCTOR"); if (hasAllParams(x)) constructorMatch.add(x); }); } // Execute. if (constructorMatch.isPresent()) return constructorMatch.get().invoke(getParams(constructorMatch.get())); if (builder == null) { // Look for static-builder/protected-constructor pair. Value value = Value.empty(); type.forEachDeclaredConstructor(x -> x.hasNumParams(1) && x.isVisible(PROTECTED), x -> { Class pt = x.getParam(0).getParameterType().inner(); MethodInfo m = type.getPublicMethod(y -> isStaticCreateMethod(y, pt)); if (m != null) { Object builder = m.invoke(null); value.set(x.accessible().invoke(builder)); } }); if (value.isPresent()) return value.get(); } if (silent) return null; String msg = null; if (found.isEmpty()) { msg = "No public/protected constructors found"; } else if (found.get().equals("STATIC_CREATOR")) { msg = "Static creator found but could not find prerequisites: " + type.getPublicMethods().stream().filter(x -> isStaticCreateMethod(x)).map(x -> getMissingParams(x)).sorted().collect(joining(" or ")); } else if (found.get().equals("PUBLIC_CONSTRUCTOR")) { msg = "Public constructor found but could not find prerequisites: " + type.getPublicConstructors().stream().map(x -> getMissingParams(x)).sorted().collect(joining(" or ")); } else { msg = "Protected constructor found but could not find prerequisites: " + type.getDeclaredConstructors().stream().filter(ConstructorInfo::isProtected).map(x -> getMissingParams(x)).sorted().collect(joining(" or ")); } throw new ExecutableException("Could not instantiate class {0}: {1}.", type.getName(), msg); } /** * Converts this creator into a supplier. * * @return A supplier that returns the results of the {@link #run()} method. */ public Supplier supplier() { return ()->run(); } private boolean isStaticCreateMethod(MethodInfo m) { return isStaticCreateMethod(m, type.inner()); } private boolean isStaticCreateMethod(MethodInfo m, Class type) { return m.isStatic() && m.isNotDeprecated() && m.hasReturnType(type) && m.hasNoAnnotation(BeanIgnore.class) && m.hasName("create"); } //----------------------------------------------------------------------------------------------------------------- // Helpers //----------------------------------------------------------------------------------------------------------------- static class Match { T executable = null; int numMatches = -1; @SuppressWarnings("unchecked") void add(T ei) { if (ei.getParamCount() > numMatches) { numMatches = ei.getParamCount(); executable = (T)ei.accessible(); } } boolean isPresent() { return executable != null; } T get() { return executable; } } private boolean hasAllParams(ExecutableInfo ei) { return store.hasAllParams(ei); } private Object[] getParams(ExecutableInfo ei) { return store.getParams(ei); } private String getMissingParams(ExecutableInfo ei) { return store.getMissingParams(ei); } }