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 org.apache.juneau.commons.reflect.ReflectionUtils.*;
020import static org.apache.juneau.commons.utils.AssertionUtils.*;
021import static org.apache.juneau.commons.utils.Utils.*;
022
023import java.util.*;
024import java.util.function.*;
025
026import org.apache.juneau.annotation.*;
027import org.apache.juneau.commons.reflect.*;
028
029/**
030 * Utility class for creating beans through creator methods.
031 *
032 * <p>
033 * Used for finding and invoking methods on an object that take in arbitrary parameters and returns bean instances.
034 *
035 * <p>
036 * This class is instantiated through the following methods:
037 * <ul class='javatree'>
038 *    <li class='jc'>{@link BeanStore}
039 *       <ul class='javatreec'>
040 *          <li class='jm'>{@link BeanStore#createMethodFinder(Class)}
041 *          <li class='jm'>{@link BeanStore#createMethodFinder(Class,Class)}
042 *          <li class='jm'>{@link BeanStore#createMethodFinder(Class,Object)}
043 *       </ul>
044 *    </li>
045 * </ul>
046 *
047 * <h5 class='section'>Example:</h5>
048 * <p class='bjava'>
049 *    <jc>// The bean we want to create.</jc>
050 *    <jk>public class</jk> A {}
051 *
052 *    <jc>// The bean that has a creator method for the bean above.</jc>
053 *    <jk>public class</jk> B {
054 *
055 *       <jc>// Creator method.</jc>
056 *       <jc>// Bean store must have a C bean and optionally a D bean.</jc>
057 *       <jk>public</jk> A createA(C <jv>c</jv>, Optional&lt;D&gt; <jv>d</jv>) {
058 *          <jk>return new</jk> A(<jv>c</jv>, <jv>d</jv>.orElse(<jk>null</jk>));
059 *       }
060 *    }
061 *
062 *    <jc>// Instantiate the bean with the creator method.</jc>
063 *    B <mv>b</mv> = <jk>new</jk> B();
064 *
065 *  <jc>// Create a bean store with some mapped beans.</jc>
066 *    BeanStore <mv>beanStore</mv> = BeanStore.<jsm>create</jsm>().addBean(C.<jk>class</jk>, <jk>new</jk> C());
067 *
068 *    <jc>// Instantiate the bean using the creator method.</jc>
069 *    A <mv>a</mv> = <mv>beanStore</mv>
070 *       .createMethodFinder(A.<jk>class</jk>, <mv>b</mv>)  <jc>// Looking for creator for A on b object.</jc>
071 *       .find(<js>"createA"</js>)                         <jc>// Look for method called "createA".</jc>
072 *       .thenFind(<js>"createA2"</js>)                    <jc>// Then look for method called "createA2".</jc>
073 *       .withDefault(()-&gt;<jk>new</jk> A())                        <jc>// Optionally supply a default value if method not found.</jc>
074 *       .run();                                  <jc>// Execute.</jc>
075 * </p>
076 *
077 * <h5 class='section'>See Also:</h5><ul>
078 *    <li class='jc'>{@link BeanStore}
079 * </ul>
080 *
081 * @param <T> The bean type being created.
082 */
083public class BeanCreateMethodFinder<T> {
084
085   private Class<T> beanType;
086   private final Class<?> resourceClass;
087   private final Object resource;
088   private final BeanStore beanStore;
089
090   private MethodInfo method;
091   private Object[] args;
092
093   BeanCreateMethodFinder(Class<T> beanType, Class<?> resourceClass, BeanStore beanStore) {
094      this.beanType = assertArgNotNull("beanType", beanType);
095      this.resource = null;
096      this.resourceClass = assertArgNotNull("resourceClass", resourceClass);
097      this.beanStore = BeanStore.of(beanStore);
098   }
099
100   BeanCreateMethodFinder(Class<T> beanType, Object resource, BeanStore beanStore) {
101      this.beanType = assertArgNotNull("beanType", beanType);
102      this.resource = assertArgNotNull("resource", resource);
103      this.resourceClass = resource.getClass();
104      this.beanStore = BeanStore.of(beanStore, resource);
105   }
106
107   /**
108    * Adds a bean to the lookup for parameters.
109    *
110    * @param <T2> The bean type.
111    * @param c The bean type.
112    * @param t The bean.
113    * @return This object.
114    */
115   public <T2> BeanCreateMethodFinder<T> addBean(Class<T2> c, T2 t) {
116      beanStore.addBean(c, t);
117      return this;
118   }
119
120   /**
121    * Find the method matching the specified predicate.
122    *
123    * <p>
124    * In order for the method to be used, it must adhere to the following restrictions:
125    * <ul>
126    *    <li>The method must be public.
127    *    <li>The method can be static.
128    *    <li>The method name must match exactly.
129    *    <li>The method must not be deprecated or annotated with {@link BeanIgnore}.
130    *    <li>The method must have all parameter types specified in <c>requiredParams</c>.
131    *    <li>The bean store must contain beans for all parameter types.
132    *    <li>The bean store may contain beans for all {@link Optional} parameter types.
133    * </ul>
134    *
135    * <p>
136    * This method can be called multiple times with different method names or required parameters until a match is found.
137    * <br>Once a method is found, subsequent calls to this method will be no-ops.
138    *
139    * See {@link BeanStore#createMethodFinder(Class, Object)} for usage.
140    *
141    * @param filter The predicate to apply.
142    * @return This object.
143    */
144   public BeanCreateMethodFinder<T> find(Predicate<MethodInfo> filter) {
145      // @formatter:off
146      if (method == null) {
147         info(resourceClass).getPublicMethod(
148            x -> x.isNotDeprecated()
149               && x.hasReturnType(beanType)
150               && ! x.hasAnnotation(BeanIgnore.class)
151               && filter.test(x)
152               && beanStore.hasAllParams(x)
153               && (x.isStatic() || nn(resource))
154         ).ifPresent(m -> {
155            method = m;
156            args = beanStore.getParams(m);
157         });
158      }
159      return this;
160      // @formatter:on
161   }
162
163   /**
164    * Executes the matched method and returns the result.
165    *
166    * @return The object returned by the method invocation, or the default value if method was not found.
167    * @throws ExecutableException If method invocation threw an exception.
168    */
169   @SuppressWarnings("unchecked")
170   public T run() throws ExecutableException {
171      if (nn(method))
172         return (T)method.invoke(resource, args);
173      return null;
174   }
175
176   /**
177    * Same as {@link #run()} but also executes a consumer if the returned value was not <jk>null</jk>.
178    *
179    * @param consumer The consumer of the response.
180    * @return The object returned by the method invocation, or the default value if method was not found, or {@link Optional#empty()}.
181    * @throws ExecutableException If method invocation threw an exception.
182    */
183   public T run(Consumer<? super T> consumer) throws ExecutableException {
184      T t = run();
185      if (nn(t))
186         consumer.accept(t);
187      return t;
188   }
189}