001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.cp;
014
015import static org.apache.juneau.common.internal.ArgUtils.*;
016
017import java.util.*;
018import java.util.function.*;
019
020import org.apache.juneau.*;
021import org.apache.juneau.annotation.*;
022import org.apache.juneau.reflect.*;
023
024/**
025 * Utility class for creating beans through creator methods.
026 *
027 * <p>
028 * Used for finding and invoking methods on an object that take in arbitrary parameters and returns bean instances.
029 *
030 * <p>
031 * This class is instantiated through the following methods:
032 * <ul class='javatree'>
033 *    <li class='jc'>{@link BeanStore}
034 *       <ul class='javatreec'>
035 *          <li class='jm'>{@link BeanStore#createMethodFinder(Class)}
036 *          <li class='jm'>{@link BeanStore#createMethodFinder(Class,Class)}
037 *          <li class='jm'>{@link BeanStore#createMethodFinder(Class,Object)}
038 *       </ul>
039 *    </li>
040 * </ul>
041 *
042 * <h5 class='section'>Example:</h5>
043 * <p class='bjava'>
044 *    <jc>// The bean we want to create.</jc>
045 *    <jk>public class</jk> A {}
046 *
047 *    <jc>// The bean that has a creator method for the bean above.</jc>
048 *    <jk>public class</jk> B {
049 *
050 *       <jc>// Creator method.</jc>
051 *       <jc>// Bean store must have a C bean and optionally a D bean.</jc>
052 *       <jk>public</jk> A createA(C <jv>c</jv>, Optional&lt;D&gt; <jv>d</jv>) {
053 *          <jk>return new</jk> A(<jv>c</jv>, <jv>d</jv>.orElse(<jk>null</jk>));
054 *       }
055 *    }
056 *
057 *    <jc>// Instantiate the bean with the creator method.</jc>
058 *    B <mv>b</mv> = <jk>new</jk> B();
059 *
060 *  <jc>// Create a bean store with some mapped beans.</jc>
061 *    BeanStore <mv>beanStore</mv> = BeanStore.<jsm>create</jsm>().addBean(C.<jk>class</jk>, <jk>new</jk> C());
062 *
063 *    <jc>// Instantiate the bean using the creator method.</jc>
064 *    A <mv>a</mv> = <mv>beanStore</mv>
065 *       .createMethodFinder(A.<jk>class</jk>, <mv>b</mv>)  <jc>// Looking for creator for A on b object.</jc>
066 *       .find(<js>"createA"</js>)                         <jc>// Look for method called "createA".</jc>
067 *       .thenFind(<js>"createA2"</js>)                    <jc>// Then look for method called "createA2".</jc>
068 *       .withDefault(()-&gt;<jk>new</jk> A())                        <jc>// Optionally supply a default value if method not found.</jc>
069 *       .run();                                  <jc>// Execute.</jc>
070 * </p>
071 *
072 * <h5 class='section'>See Also:</h5><ul>
073 *    <li class='jc'>{@link BeanStore}
074 * </ul>
075 *
076 * @param <T> The bean type being created.
077 */
078public class BeanCreateMethodFinder<T> {
079
080   private Class<T> beanType;
081   private final Class<?> resourceClass;
082   private final Object resource;
083   private final BeanStore beanStore;
084
085   private MethodInfo method;
086   private Object[] args;
087
088   private Supplier<T> def = ()->null;
089
090   BeanCreateMethodFinder(Class<T> beanType, Object resource, BeanStore beanStore) {
091      this.beanType = assertArgNotNull("beanType", beanType);
092      this.resource = assertArgNotNull("resource", resource);
093      this.resourceClass = resource.getClass();
094      this.beanStore = BeanStore.of(beanStore, resource);
095   }
096
097   BeanCreateMethodFinder(Class<T> beanType, Class<?> resourceClass, BeanStore beanStore) {
098      this.beanType = assertArgNotNull("beanType", beanType);
099      this.resource = null;
100      this.resourceClass = assertArgNotNull("resourceClass", resourceClass);
101      this.beanStore = BeanStore.of(beanStore);
102   }
103
104   /**
105    * Adds a bean to the lookup for parameters.
106    *
107    * @param <T2> The bean type.
108    * @param c The bean type.
109    * @param t The bean.
110    * @return This object.
111    */
112   public <T2> BeanCreateMethodFinder<T> addBean(Class<T2> c, T2 t) {
113      beanStore.addBean(c, t);
114      return this;
115   }
116
117   /**
118    * Find the method matching the specified predicate.
119    *
120    * <p>
121    * In order for the method to be used, it must adhere to the following restrictions:
122    * <ul>
123    *    <li>The method must be public.
124    *    <li>The method can be static.
125    *    <li>The method name must match exactly.
126    *    <li>The method must not be deprecated or annotated with {@link BeanIgnore}.
127    *    <li>The method must have all parameter types specified in <c>requiredParams</c>.
128    *    <li>The bean store must contain beans for all parameter types.
129    *    <li>The bean store may contain beans for all {@link Optional} parameter types.
130    * </ul>
131    *
132    * <p>
133    * This method can be called multiple times with different method names or required parameters until a match is found.
134    * <br>Once a method is found, subsequent calls to this method will be no-ops.
135    *
136    * See {@link BeanStore#createMethodFinder(Class, Object)} for usage.
137    *
138    * @param filter The predicate to apply.
139    * @return This object.
140    */
141   public BeanCreateMethodFinder<T> find(Predicate<MethodInfo> filter) {
142      if (method == null) {
143         method = ClassInfo.of(resourceClass).getPublicMethod(
144            x -> x.isNotDeprecated()
145            && x.hasReturnType(beanType)
146            && x.hasNoAnnotation(BeanIgnore.class)
147            && filter.test(x)
148            && beanStore.hasAllParams(x)
149            && (x.isStatic() || resource != null)
150         );
151         if (method != null)
152            args = beanStore.getParams(method);
153      }
154      return this;
155   }
156
157   /**
158    * Shortcut for calling <c>find(<jv>x</jv> -&gt; <jv>x</jv>.hasName(<jv>methodName</jv>))</c>.
159    *
160    * @param methodName The method name to match.
161    * @return This object.
162    */
163   public BeanCreateMethodFinder<T> find(String methodName) {
164      return find(x -> x.hasName(methodName));
165   }
166
167   /**
168    * Identical to {@link #find(Predicate)} but named for fluent-style calls.
169    *
170    * @param filter The predicate to apply.
171    * @return This object.
172    */
173   public BeanCreateMethodFinder<T> thenFind(Predicate<MethodInfo> filter) {
174      return find(filter);
175   }
176
177   /**
178    * Identical to {@link #find(Predicate)} but named for fluent-style calls.
179    *
180    * @param methodName The method name to match.
181    * @return This object.
182    */
183   public BeanCreateMethodFinder<T> thenFind(String methodName) {
184      return find(methodName);
185   }
186
187   /**
188    * A default value to return if no matching methods were found.
189    *
190    * @param def The default value.  Can be <jk>null</jk>.
191    * @return This object.
192    */
193   public BeanCreateMethodFinder<T> withDefault(T def) {
194      return withDefault(()->def);
195   }
196
197   /**
198    * A default value to return if no matching methods were found.
199    *
200    * @param def The default value.
201    * @return This object.
202    */
203   public BeanCreateMethodFinder<T> withDefault(Supplier<T> def) {
204      assertArgNotNull("def", def);
205      this.def = def;
206      return this;
207   }
208
209   /**
210    * Executes the matched method and returns the result.
211    *
212    * @return The object returned by the method invocation, or the default value if method was not found.
213    * @throws ExecutableException If method invocation threw an exception.
214    */
215   @SuppressWarnings("unchecked")
216   public T run() throws ExecutableException {
217      if (method != null)
218         return (T)method.invoke(resource, args);
219      return def.get();
220   }
221
222   /**
223    * Same as {@link #run()} but also executes a consumer if the returned value was not <jk>null</jk>.
224    *
225    * @param consumer The consumer of the response.
226    * @return The object returned by the method invocation, or the default value if method was not found, or {@link Optional#empty()}.
227    * @throws ExecutableException If method invocation threw an exception.
228    */
229   public T run(Consumer<? super T> consumer) throws ExecutableException {
230      T t = run();
231      if (t != null)
232         consumer.accept(t);
233      return t;
234   }
235}