| // *************************************************************************************************************************** |
| // * 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 org.apache.juneau.internal.StringUtils.*; |
| import static org.apache.juneau.reflect.ReflectFlags.*; |
| |
| import java.util.*; |
| import java.util.concurrent.*; |
| import java.util.function.*; |
| import java.util.stream.*; |
| |
| import org.apache.juneau.*; |
| import org.apache.juneau.annotation.*; |
| import org.apache.juneau.collections.*; |
| import org.apache.juneau.internal.*; |
| import org.apache.juneau.reflect.*; |
| |
| /** |
| * Factory for creating beans. |
| */ |
| public class BeanFactory { |
| |
| /** |
| * Non-existent bean factory. |
| */ |
| public static final class Null extends BeanFactory {} |
| |
| private final Map<Class<?>,Supplier<?>> beanMap = new ConcurrentHashMap<>(); |
| private final Optional<BeanFactory> parent; |
| private final Optional<Object> outer; |
| |
| /** |
| * Static creator. |
| * |
| * @return A new {@link BeanFactory} object. |
| */ |
| public static BeanFactory create() { |
| return new BeanFactory(); |
| } |
| |
| /** |
| * Static creator. |
| * |
| * @param parent Parent bean factory. Can be <jk>null</jk> if this is the root resource. |
| * @param outer Outer bean context to use when instantiating local classes. Can be <jk>null</jk>. |
| * @return A new {@link BeanFactory} object. |
| */ |
| public static BeanFactory of(BeanFactory parent, Object outer) { |
| return new BeanFactory(parent, outer); |
| } |
| |
| /** |
| * Default constructor. |
| */ |
| public BeanFactory() { |
| this(Optional.empty(), Optional.empty()); |
| } |
| |
| /** |
| * Constructor. |
| * |
| * @param parent Parent bean factory. Can be <jk>null</jk> if this is the root resource. |
| * @param outer Outer bean context to use when instantiating local classes. Can be <jk>null</jk>. |
| */ |
| public BeanFactory(BeanFactory parent, Object outer) { |
| this(Optional.ofNullable(parent), Optional.ofNullable(outer)); |
| } |
| |
| /** |
| * Constructor. |
| * |
| * @param parent - Optional parent bean factory. |
| * @param outer Outer bean context to use when instantiating local classes. |
| */ |
| public BeanFactory(Optional<BeanFactory> parent, Optional<Object> outer) { |
| this.parent = parent; |
| this.outer = outer; |
| } |
| |
| /** |
| * Returns the bean of the specified type. |
| * |
| * @param <T> The type of bean to return. |
| * @param c The type of bean to return. |
| * @return The bean. |
| */ |
| @SuppressWarnings("unchecked") |
| public <T> Optional<T> getBean(Class<T> c) { |
| Supplier<?> o = beanMap.get(c); |
| if (o == null && parent.isPresent()) |
| return parent.get().getBean(c); |
| T t = (T)(o == null ? null : o.get()); |
| return Optional.ofNullable(t); |
| } |
| |
| /** |
| * Returns the bean of the specified type. |
| * |
| * @param c The type of bean to return. |
| * @return The bean. |
| */ |
| public Optional<?> getBean(ClassInfo c) { |
| return getBean(c.inner()); |
| } |
| |
| /** |
| * Adds a bean of the specified type to this factory. |
| * |
| * @param <T> The class to associate this bean with. |
| * @param c The class to associate this bean with. |
| * @param t The bean. |
| * @return This object (for method chaining). |
| */ |
| public <T> BeanFactory addBean(Class<T> c, T t) { |
| if (t == null) |
| beanMap.remove(c); |
| else |
| beanMap.put(c, ()->t); |
| return this; |
| } |
| |
| /** |
| * Adds a bean supplier of the specified type to this factory. |
| * |
| * @param <T> The class to associate this bean with. |
| * @param c The class to associate this bean with. |
| * @param t The bean supplier. |
| * @return This object (for method chaining). |
| */ |
| public <T> BeanFactory addBeanSupplier(Class<T> c, Supplier<T> t) { |
| if (t == null) |
| beanMap.remove(c); |
| else |
| beanMap.put(c, t); |
| return this; |
| } |
| |
| /** |
| * Returns <jk>true</jk> if this factory contains the specified bean type instance. |
| * |
| * @param c The bean type to check. |
| * @return <jk>true</jk> if this factory contains the specified bean type instance. |
| */ |
| public boolean hasBean(Class<?> c) { |
| return getBean(c).isPresent(); |
| } |
| |
| /** |
| * Returns <jk>true</jk> if this factory contains the specified bean type instance. |
| * |
| * @param c The bean type to check. |
| * @return <jk>true</jk> if this factory contains the specified bean type instance. |
| */ |
| public final boolean hasBean(ClassInfo c) { |
| return hasBean(c.inner()); |
| } |
| |
| /** |
| * Creates a bean of the specified type. |
| * |
| * @param <T> The bean type to create. |
| * @param c The bean type to create. |
| * @return A newly-created bean. |
| * @throws ExecutableException If bean could not be created. |
| */ |
| public <T> T createBean(Class<T> c) throws ExecutableException { |
| |
| Optional<T> o = getBean(c); |
| if (o.isPresent()) |
| return o.get(); |
| |
| ClassInfo ci = ClassInfo.of(c); |
| |
| Supplier<String> msg = null; |
| |
| for (MethodInfo m : ci.getPublicMethods()) { |
| if (m.isAll(STATIC, NOT_DEPRECATED) && m.hasReturnType(c) && (!m.hasAnnotation(BeanIgnore.class))) { |
| String n = m.getSimpleName(); |
| if (isOneOf(n, "create","getInstance")) { |
| List<ClassInfo> missing = getMissingParamTypes(m.getParamTypes()); |
| if (missing.isEmpty()) |
| return m.invoke(null, getParams(m.getParamTypes())); |
| msg = ()-> "Static creator found but could not find prerequisites: " + missing.stream().map(x->x.getSimpleName()).collect(Collectors.joining(",")); |
| } |
| } |
| } |
| |
| if (ci.isInterface()) |
| throw new ExecutableException("Could not instantiate class {0}: {1}.", c.getName(), msg != null ? msg.get() : "Class is an interface"); |
| if (ci.isAbstract()) |
| throw new ExecutableException("Could not instantiate class {0}: {1}.", c.getName(), msg != null ? msg.get() : "Class is abstract"); |
| |
| for (ConstructorInfo cc : ci.getPublicConstructors()) { |
| List<ClassInfo> missing = getMissingParamTypes(cc.getParamTypes()); |
| if (missing.isEmpty()) |
| return cc.invoke(getParams(cc.getParamTypes())); |
| msg = ()-> "Public constructor found but could not find prerequisites: " + missing.stream().map(x->x.getSimpleName()).collect(Collectors.joining(",")); |
| } |
| |
| if (msg == null) |
| msg = () -> "Public constructor or creator not found"; |
| |
| throw new ExecutableException("Could not instantiate class {0}: {1}.", c.getName(), msg.get()); |
| } |
| |
| /** |
| * Create a method finder for finding bean creation methods. |
| * |
| * <h5 class='section'>Example:</h5> |
| * <p class='bcode w800'> |
| * <jc>// The bean we want to create.</jc> |
| * <jk>public class</jk> A {} |
| * |
| * <jc>// The bean that has a creator method for the bean above.</jc> |
| * <jk>public class</jk> B { |
| * |
| * <jc>// Creator method.</jc> |
| * <jc>// Bean factory must have a C bean and optionally a D bean.</jc> |
| * <jk>public</jk> A createA(C <mv>c</mv>, Optional<D> <mv>d</mv>) { |
| * <jk>return new</jk> A(<mv>c</mv>, <mv>d</mv>.orElse(<jk>null</jk>)); |
| * } |
| * } |
| * |
| * <jc>// Instantiate the bean with the creator method.</jc> |
| * B <mv>b</mv> = <jk>new</jk> B(); |
| * |
| * <jc>// Create a bean factory with some mapped beans.</jc> |
| * BeanFactory <mv>beanFactory</mv> = BeanFactory.<jsm>create</jsm>().addBean(C.<jk>class</jk>, <jk>new</jk> C()); |
| * |
| * <jc>// Instantiate the bean using the creator method.</jc> |
| * A <mv>a</mv> = <mv>beanFactory</mv> |
| * .beanCreateMethodFinder(A.<jk>class</jk>, <mv>b</mv>) <jc>// Looking for creator for A on b object.</jc> |
| * .find(<js>"createA"</js>) <jc>// Look for method called "createA".</jc> |
| * .thenFind(<js>"createA2"</js>) <jc>// Then look for method called "createA2".</jc> |
| * .withDefault(()-><jk>new</jk> A()) <jc>// Optionally supply a default value if method not found.</jc> |
| * .run(); <jc>// Execute.</jc> |
| * </p> |
| * |
| * @param <T> The bean type to create. |
| * @param c The bean type to create. |
| * @param resource The class containing the bean creator method. |
| * @return The created bean or the default value if method could not be found. |
| */ |
| public <T> BeanCreateMethodFinder<T> beanCreateMethodFinder(Class<T> c, Object resource) { |
| return new BeanCreateMethodFinder<>(c, resource, this); |
| } |
| |
| /** |
| * Given the list of param types, returns a list of types that are missing from this factory. |
| * |
| * @param paramTypes The param types to chec. |
| * @return A list of types that are missing from this factory. |
| */ |
| public List<ClassInfo> getMissingParamTypes(List<ClassInfo> paramTypes) { |
| List<ClassInfo> l = AList.create(); |
| for (int i = 0; i < paramTypes.size(); i++) { |
| ClassInfo pt = paramTypes.get(i); |
| ClassInfo ptu = pt.unwrap(Optional.class); |
| if (i == 0 && ptu.isInstance(outer.orElse(null))) |
| continue; |
| if (! hasBean(ptu)) |
| if (! pt.is(Optional.class)) |
| l.add(pt); |
| } |
| return l; |
| } |
| |
| /** |
| * Returns the corresponding beans in this factory for the specified param types. |
| * |
| * @param paramTypes The param types to get from this factory. |
| * @return The corresponding beans in this factory for the specified param types. |
| */ |
| public Object[] getParams(List<ClassInfo> paramTypes) { |
| Object[] o = new Object[paramTypes.size()]; |
| for (int i = 0; i < paramTypes.size(); i++) { |
| ClassInfo pt = paramTypes.get(i); |
| ClassInfo ptu = pt.unwrap(Optional.class); |
| if (i == 0 && ptu.isInstance(outer.orElse(null))) |
| o[i] = outer.get(); |
| else { |
| if (pt.is(Optional.class)) { |
| o[i] = getBean(ptu); |
| } else { |
| o[i] = getBean(ptu).get(); |
| } |
| } |
| } |
| return o; |
| } |
| |
| /** |
| * Returns the contents of this bean factory as a readable map of values. |
| * |
| * @return The contents of this bean factory as a readable map of values. |
| */ |
| public OMap toMap() { |
| return OMap.create() |
| .a("beanMap", beanMap.keySet().stream().map(x -> x.getSimpleName()).collect(Collectors.toList())) |
| .appendSkipNull("outer", ObjectUtils.identity(outer)) |
| .appendSkipNull("parent", parent.orElse(null)); |
| } |
| |
| @Override /* Object */ |
| public String toString() { |
| return toMap().toString(); |
| } |
| } |