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 java.util.*;
020import java.util.function.*;
021
022import org.apache.juneau.*;
023import org.apache.juneau.annotation.*;
024import org.apache.juneau.common.utils.*;
025import org.apache.juneau.reflect.*;
026
027/**
028 * Utility class for creating beans through creator methods.
029 *
030 * <p>
031 * Used for finding and invoking methods on an object that take in arbitrary parameters and returns bean instances.
032 *
033 * <p>
034 * This class is instantiated through the following methods:
035 * <ul class='javatree'>
036 *    <li class='jc'>{@link BeanStore}
037 *       <ul class='javatreec'>
038 *          <li class='jm'>{@link BeanStore#createMethodFinder(Class)}
039 *          <li class='jm'>{@link BeanStore#createMethodFinder(Class,Class)}
040 *          <li class='jm'>{@link BeanStore#createMethodFinder(Class,Object)}
041 *       </ul>
042 *    </li>
043 * </ul>
044 *
045 * <h5 class='section'>Example:</h5>
046 * <p class='bjava'>
047 *    <jc>// The bean we want to create.</jc>
048 *    <jk>public class</jk> A {}
049 *
050 *    <jc>// The bean that has a creator method for the bean above.</jc>
051 *    <jk>public class</jk> B {
052 *
053 *       <jc>// Creator method.</jc>
054 *       <jc>// Bean store must have a C bean and optionally a D bean.</jc>
055 *       <jk>public</jk> A createA(C <jv>c</jv>, Optional&lt;D&gt; <jv>d</jv>) {
056 *          <jk>return new</jk> A(<jv>c</jv>, <jv>d</jv>.orElse(<jk>null</jk>));
057 *       }
058 *    }
059 *
060 *    <jc>// Instantiate the bean with the creator method.</jc>
061 *    B <mv>b</mv> = <jk>new</jk> B();
062 *
063 *  <jc>// Create a bean store with some mapped beans.</jc>
064 *    BeanStore <mv>beanStore</mv> = BeanStore.<jsm>create</jsm>().addBean(C.<jk>class</jk>, <jk>new</jk> C());
065 *
066 *    <jc>// Instantiate the bean using the creator method.</jc>
067 *    A <mv>a</mv> = <mv>beanStore</mv>
068 *       .createMethodFinder(A.<jk>class</jk>, <mv>b</mv>)  <jc>// Looking for creator for A on b object.</jc>
069 *       .find(<js>"createA"</js>)                         <jc>// Look for method called "createA".</jc>
070 *       .thenFind(<js>"createA2"</js>)                    <jc>// Then look for method called "createA2".</jc>
071 *       .withDefault(()-&gt;<jk>new</jk> A())                        <jc>// Optionally supply a default value if method not found.</jc>
072 *       .run();                                  <jc>// Execute.</jc>
073 * </p>
074 *
075 * <h5 class='section'>See Also:</h5><ul>
076 *    <li class='jc'>{@link BeanStore}
077 * </ul>
078 *
079 * @param <T> The bean type being created.
080 */
081public class BeanCreateMethodFinder<T> {
082
083   private Class<T> beanType;
084   private final Class<?> resourceClass;
085   private final Object resource;
086   private final BeanStore beanStore;
087
088   private MethodInfo method;
089   private Object[] args;
090
091   private Supplier<T> def = ()->null;
092
093   BeanCreateMethodFinder(Class<T> beanType, Object resource, BeanStore beanStore) {
094      this.beanType = Utils.assertArgNotNull("beanType", beanType);
095      this.resource = Utils.assertArgNotNull("resource", resource);
096      this.resourceClass = resource.getClass();
097      this.beanStore = BeanStore.of(beanStore, resource);
098   }
099
100   BeanCreateMethodFinder(Class<T> beanType, Class<?> resourceClass, BeanStore beanStore) {
101      this.beanType = Utils.assertArgNotNull("beanType", beanType);
102      this.resource = null;
103      this.resourceClass = Utils.assertArgNotNull("resourceClass", resourceClass);
104      this.beanStore = BeanStore.of(beanStore);
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      if (method == null) {
146         method = ClassInfo.of(resourceClass).getPublicMethod(
147            x -> x.isNotDeprecated()
148            && x.hasReturnType(beanType)
149            && x.hasNoAnnotation(BeanIgnore.class)
150            && filter.test(x)
151            && beanStore.hasAllParams(x)
152            && (x.isStatic() || resource != null)
153         );
154         if (method != null)
155            args = beanStore.getParams(method);
156      }
157      return this;
158   }
159
160   /**
161    * Shortcut for calling <c>find(<jv>x</jv> -&gt; <jv>x</jv>.hasName(<jv>methodName</jv>))</c>.
162    *
163    * @param methodName The method name to match.
164    * @return This object.
165    */
166   public BeanCreateMethodFinder<T> find(String methodName) {
167      return find(x -> x.hasName(methodName));
168   }
169
170   /**
171    * Identical to {@link #find(Predicate)} but named for fluent-style calls.
172    *
173    * @param filter The predicate to apply.
174    * @return This object.
175    */
176   public BeanCreateMethodFinder<T> thenFind(Predicate<MethodInfo> filter) {
177      return find(filter);
178   }
179
180   /**
181    * Identical to {@link #find(Predicate)} but named for fluent-style calls.
182    *
183    * @param methodName The method name to match.
184    * @return This object.
185    */
186   public BeanCreateMethodFinder<T> thenFind(String methodName) {
187      return find(methodName);
188   }
189
190   /**
191    * A default value to return if no matching methods were found.
192    *
193    * @param def The default value.  Can be <jk>null</jk>.
194    * @return This object.
195    */
196   public BeanCreateMethodFinder<T> withDefault(T def) {
197      return withDefault(()->def);
198   }
199
200   /**
201    * A default value to return if no matching methods were found.
202    *
203    * @param def The default value.
204    * @return This object.
205    */
206   public BeanCreateMethodFinder<T> withDefault(Supplier<T> def) {
207      Utils.assertArgNotNull("def", def);
208      this.def = def;
209      return this;
210   }
211
212   /**
213    * Executes the matched method and returns the result.
214    *
215    * @return The object returned by the method invocation, or the default value if method was not found.
216    * @throws ExecutableException If method invocation threw an exception.
217    */
218   @SuppressWarnings("unchecked")
219   public T run() throws ExecutableException {
220      if (method != null)
221         return (T)method.invoke(resource, args);
222      return def.get();
223   }
224
225   /**
226    * Same as {@link #run()} but also executes a consumer if the returned value was not <jk>null</jk>.
227    *
228    * @param consumer The consumer of the response.
229    * @return The object returned by the method invocation, or the default value if method was not found, or {@link Optional#empty()}.
230    * @throws ExecutableException If method invocation threw an exception.
231    */
232   public T run(Consumer<? super T> consumer) throws ExecutableException {
233      T t = run();
234      if (t != null)
235         consumer.accept(t);
236      return t;
237   }
238}