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 java.util.stream.Collectors.*;
016import static org.apache.juneau.Visibility.*;
017import static org.apache.juneau.internal.CollectionUtils.*;
018
019import java.util.*;
020import java.util.function.*;
021
022import org.apache.juneau.*;
023import org.apache.juneau.annotation.*;
024import org.apache.juneau.reflect.*;
025
026/**
027 * Utility class for creating beans through constructors, creator methods, and builders.
028 *
029 * <p>
030 * Uses a {@link BeanStore} to find available ways to construct beans via injection of beans from the store.
031 *
032 * <p>
033 * This class is instantiated through the following method:
034 * <ul class='javatree'>
035 *    <li class='jc'>{@link BeanStore}
036 *       <ul class='javatreec'>
037 *          <li class='jm'>{@link BeanStore#createBean(Class)}
038 *       </ul>
039 *    </li>
040 * </ul>
041 *
042 * <h5 class='section'>Example:</h5>
043 * <p class='bjava'>
044 *    <jc>// Construct and throw a RuntimeException using a bean store.</jc>
045 *    <jk>throw</jk> BeanStore
046 *       .<jsm>create</jsm>()
047 *       .build()
048 *       .addBean(Throwable.<jk>class</jk>, <jv>cause</jv>)
049 *       .addBean(String.<jk>class</jk>, <jv>msg</jv>)
050 *       .addBean(Object[].<jk>class</jk>, <jv>args</jv>)
051 *       .createBean(RuntimeException.<jk>class</jk>)
052 *       .run();
053 * </p>
054 *
055 * <p>
056 * Looks for the following methods for creating a bean:
057 * <ol class='spaced-list'>
058 *    <li>Looks for a singleton no-arg method of the form:
059 *       <p class='bjava'>
060 *    <jk>public static</jk> MyClass <jsm>getInstance</jsm>();
061 *       </p>
062 *       <ul>
063 *          <li>Deprecated and {@link BeanIgnore @BeanIgnore-annotated} methods are ignored.
064 *    </ul>
065 *    <li>Looks for a static creator method of the form:
066 *       <p class='bjava'>
067 *    <jk>public static</jk> MyClass <jsm>create</jsm>(<ja>&lt;args&gt;</ja>);
068 *       </p>
069 *       <ul>
070 *          <li>All arguments except {@link Optional} and {@link List} parameters must have beans available in the store.
071 *          <li>If multiple methods are found, the one with the most matching parameters is used.
072 *          <li>Deprecated and {@link BeanIgnore @BeanIgnore-annotated} methods are ignored.
073 *       </ul>
074 *    <li>Looks for a public constructor of the form:
075 *       <p class='bjava'>
076 *    <jk>public</jk> MyClass(<ja>&lt;args&gt;</ja>);
077 *       </p>
078 *       <ul>
079 *          <li>All arguments except {@link Optional} and {@link List} parameters must have beans available in the store.
080 *          <li>If multiple methods are found, the one with the most matching parameters is used.
081 *          <li>Deprecated and {@link BeanIgnore @BeanIgnore-annotated} methods are ignored.
082 *    </ul>
083 *    <li>Looks for a protected constructor of the form:
084 *       <p class='bjava'>
085 *    <jk>protected</jk> MyClass(<ja>&lt;args&gt;</ja>);
086 *       </p>
087 *       <ul>
088 *          <li>All arguments except {@link Optional} and {@link List} parameters must have beans available in the store.
089 *          <li>If multiple methods are found, the one with the most matching parameters is used.
090 *          <li>Deprecated and {@link BeanIgnore @BeanIgnore-annotated} methods are ignored.
091 *    </ul>
092 *    <li>Looks for a static no-arg create method that returns a builder object that can be passed in to a protected constructor.
093 *       <p class='bjava'>
094 *    <jk>public static</jk> MyClass.Builder <jsm>create</jsm>();
095 *
096 *    <jk>protected</jk> MyClass(MyClass.Builder <jv>builder</jv>);
097 *       </p>
098 *       <ul>
099 *          <li>Deprecated and {@link BeanIgnore @BeanIgnore-annotated} methods are ignored.
100 *    </ul>
101 * </ol>
102 *
103 * <h5 class='section'>Notes:</h5><ul>
104 *    <li class='note'>The {@link #builder(Class,Object)} method can be used to set an existing initialized builder object to pass to a constructor.
105 *    <li class='note'>An existing initialized builder can be set using the {@link #builder(Class,Object)} method.
106 * </ul>
107 *
108 * <h5 class='section'>See Also:</h5><ul>
109 *    <li class='jc'>{@link BeanStore}
110 * </ul>
111 *
112 * @param <T> The bean type being created.
113 */
114public class BeanCreator<T> {
115
116   //-----------------------------------------------------------------------------------------------------------------
117   // Static
118   //-----------------------------------------------------------------------------------------------------------------
119
120   /**
121    * Shortcut for calling <c>BeanStore.INSTANCE.createBean(beanType)</c>.
122    *
123    * @param <T> The bean type to create.
124    * @param beanType The bean type to create.
125    * @return A new creator.
126    */
127   public static <T> BeanCreator<T> of(Class<T> beanType) {
128      return BeanStore.INSTANCE.createBean(beanType);
129   }
130
131   //-----------------------------------------------------------------------------------------------------------------
132   // Instance
133   //-----------------------------------------------------------------------------------------------------------------
134
135   private final BeanStore store;
136   private ClassInfo type;
137   private Object builder;
138   private T impl;
139   private boolean silent;
140
141   /**
142    * Constructor.
143    *
144    * @param type The bean type being created.
145    * @param store The bean store creating this creator.
146    */
147   protected BeanCreator(Class<T> type, BeanStore store) {
148      this.type = ClassInfo.of(type);
149      this.store = BeanStore.of(store, store.outer.orElse(null));
150   }
151
152   /**
153    * Allows you to specify a subclass of the specified bean type to create.
154    *
155    * @param value The value for this setting.
156    * @return This object.
157    */
158   public BeanCreator<T> type(Class<?> value) {
159      type = ClassInfo.of(value);
160      return this;
161   }
162
163   /**
164    * Allows you to specify a subclass of the specified bean type to create.
165    *
166    * @param value The value for this setting.
167    * @return This object.
168    */
169   public BeanCreator<T> type(ClassInfo value) {
170      return type(value == null ? null : value.inner());
171   }
172
173   /**
174    * Allows you to specify a specific instance for the build method to return.
175    *
176    * @param value The value for this setting.
177    * @return This object.
178    */
179   public BeanCreator<T> impl(T value) {
180      impl = value;
181      return this;
182   }
183
184   /**
185    * Adds an argument to this creator.
186    *
187    * @param <T2> The parameter type.
188    * @param beanType The parameter type.
189    * @param bean The parameter value.
190    * @return This object.
191    */
192   public <T2> BeanCreator<T> arg(Class<T2> beanType, T2 bean) {
193      store.add(beanType, bean);
194      return this;
195   }
196
197   /**
198    * Suppresses throwing of {@link ExecutableException ExecutableExceptions} from the {@link #run()} method when
199    * a form of creation cannot be found.
200    *
201    * @return This object.
202    */
203   public BeanCreator<T> silent() {
204      silent = true;
205      return this;
206   }
207
208   /**
209    * Specifies a builder object for the bean type.
210    *
211    * <h5 class='section'>Notes:</h5><ul>
212    *    <li class='note'>When specified, we don't look for a static creator method.
213    * </ul>
214    *
215    * @param <B> The class type of the builder.
216    * @param type The class type of the builder.
217    * @param value The value for this setting.
218    * @return This object.
219    */
220   @SuppressWarnings("unchecked")
221   public <B> BeanCreator<T> builder(Class<B> type, B value) {
222      builder = value;
223      Class<?> t = value.getClass();
224      do {
225         store.add((Class<T>)t, (T)value);
226         t = t.getSuperclass();
227      } while(t != null && ! t.equals(type));
228      return this;
229   }
230
231   /**
232    * Same as {@link #run()} but returns the alternate value if a method of creation could not be found.
233    *
234    * @param other The other bean to use.
235    * @return Either the created or other bean.
236    */
237   public T orElse(T other) {
238      return execute().orElse(other);
239   }
240
241   /**
242    * Same as {@link #run()} but returns the value wrapped in an {@link Optional}.
243    *
244    * @return A new bean wrapped in an {@link Optional}.
245    */
246   public Optional<T> execute() {
247      return optional(silent().run());
248   }
249
250   /**
251    * Creates the bean.
252    *
253    * @return A new bean.
254    * @throws ExecutableException if bean could not be created and {@link #silent()} was not enabled.
255    */
256   public T run() {
257
258      if (impl != null)
259         return impl;
260
261      if (type == null)
262         return null;
263
264      Value<String> found = Value.empty();
265
266      // Look for getInstance(Builder).
267      if (builder != null) {
268         MethodInfo m = type.getPublicMethod(
269            x -> x.isStatic()
270            && x.isNotDeprecated()
271            && x.hasNumParams(1)
272            && x.getParam(0).canAccept(builder)
273            && x.hasReturnType(type)
274            && x.hasNoAnnotation(BeanIgnore.class)
275            && x.hasName("getInstance")
276         );
277         if (m != null)
278            return m.invoke(null, builder);
279      }
280
281      // Look for getInstance().
282      if (builder == null) {
283         MethodInfo m = type.getPublicMethod(
284            x -> x.isStatic()
285            && x.isNotDeprecated()
286            && x.hasNoParams()
287            && x.hasReturnType(type)
288            && x.hasNoAnnotation(BeanIgnore.class)
289            && x.hasName("getInstance")
290         );
291         if (m != null)
292            return m.invoke(null);
293      }
294
295      if (builder == null) {
296         // Look for static creator methods.
297
298         Match<MethodInfo> match = new Match<>();
299
300         // Look for static creator method.
301         type.forEachPublicMethod(x -> isStaticCreateMethod(x), x -> {
302            found.set("STATIC_CREATOR");
303            if (hasAllParams(x))
304               match.add(x);
305         });
306
307         if (match.isPresent())
308            return match.get().invoke(null, getParams(match.get()));
309      }
310
311      if (type.isInterface()) {
312         if (silent)
313            return null;
314         throw new ExecutableException("Could not instantiate class {0}: {1}.", type.getName(), "Class is an interface");
315      }
316
317      if (type.isAbstract()) {
318         if (silent)
319            return null;
320         throw new ExecutableException("Could not instantiate class {0}: {1}.", type.getName(), "Class is abstract");
321      }
322
323      // Look for public constructor.
324      Match<ConstructorInfo> constructorMatch = new Match<>();
325      type.forEachPublicConstructor(x -> true, x -> {
326         found.setIfEmpty("PUBLIC_CONSTRUCTOR");
327         if (hasAllParams(x))
328            constructorMatch.add(x);
329      });
330
331      // Look for protected constructor.
332      if (! constructorMatch.isPresent()) {
333         type.forEachDeclaredConstructor(x -> x.isProtected(), x -> {
334            found.setIfEmpty("PROTECTED_CONSTRUCTOR");
335            if (hasAllParams(x))
336               constructorMatch.add(x);
337         });
338      }
339
340      // Execute.
341      if (constructorMatch.isPresent())
342         return constructorMatch.get().invoke(getParams(constructorMatch.get()));
343
344      if (builder == null) {
345         // Look for static-builder/protected-constructor pair.
346         Value<T> value = Value.empty();
347         type.forEachDeclaredConstructor(x -> x.hasNumParams(1) && x.isVisible(PROTECTED), x -> {
348            Class<?> pt = x.getParam(0).getParameterType().inner();
349            MethodInfo m = type.getPublicMethod(y -> isStaticCreateMethod(y, pt));
350            if (m != null) {
351               Object builder = m.invoke(null);
352               value.set(x.accessible().invoke(builder));
353            }
354         });
355         if (value.isPresent())
356            return value.get();
357      }
358
359      if (silent)
360         return null;
361
362      String msg = null;
363      if (found.isEmpty()) {
364         msg = "No public/protected constructors found";
365      } else if (found.get().equals("STATIC_CREATOR")) {
366         msg = "Static creator found but could not find prerequisites: " + type.getPublicMethods().stream().filter(x -> isStaticCreateMethod(x)).map(x -> getMissingParams(x)).sorted().collect(joining(" or "));
367      } else if (found.get().equals("PUBLIC_CONSTRUCTOR")) {
368         msg = "Public constructor found but could not find prerequisites: " + type.getPublicConstructors().stream().map(x -> getMissingParams(x)).sorted().collect(joining(" or "));
369      } else {
370         msg = "Protected constructor found but could not find prerequisites: " + type.getDeclaredConstructors().stream().filter(x -> x.isProtected()).map(x -> getMissingParams(x)).sorted().collect(joining(" or "));
371      }
372      throw new ExecutableException("Could not instantiate class {0}: {1}.", type.getName(), msg);
373   }
374
375   /**
376    * Converts this creator into a supplier.
377    *
378    * @return A supplier that returns the results of the {@link #run()} method.
379    */
380   public Supplier<T> supplier() {
381      return ()->run();
382   }
383
384   private boolean isStaticCreateMethod(MethodInfo m) {
385      return isStaticCreateMethod(m, type.inner());
386   }
387
388   private boolean isStaticCreateMethod(MethodInfo m, Class<?> type) {
389      return m.isStatic()
390         && m.isNotDeprecated()
391         && m.hasReturnType(type)
392         && m.hasNoAnnotation(BeanIgnore.class)
393         && m.hasName("create");
394   }
395
396   //-----------------------------------------------------------------------------------------------------------------
397   // Helpers
398   //-----------------------------------------------------------------------------------------------------------------
399
400   static class Match<T extends ExecutableInfo> {
401      T executable = null;
402      int numMatches = -1;
403
404      @SuppressWarnings("unchecked")
405      void add(T ei) {
406         if (ei.getParamCount() > numMatches) {
407            numMatches = ei.getParamCount();
408            executable = (T)ei.accessible();
409         }
410      }
411
412      boolean isPresent() {
413         return executable != null;
414      }
415
416      T get() {
417         return executable;
418      }
419   }
420
421   private boolean hasAllParams(ExecutableInfo ei) {
422      return store.hasAllParams(ei);
423   }
424
425   private Object[] getParams(ExecutableInfo ei) {
426      return store.getParams(ei);
427   }
428
429   private String getMissingParams(ExecutableInfo ei) {
430      return store.getMissingParams(ei);
431   }
432}