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