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