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.commons.inject;
018
019import static java.util.stream.Collectors.*;
020import static org.apache.juneau.commons.utils.CollectionUtils.*;
021import static org.apache.juneau.commons.utils.Shorts.*;
022
023import java.util.*;
024import java.util.function.*;
025
026import org.apache.juneau.commons.reflect.*;
027
028/**
029 * Spring-bean-like interface for looking up beans by type and name.
030 *
031 * <p>
032 * This interface provides a minimal contract for bean lookup operations, similar to Spring's
033 * <a class="doclink" href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/beans/factory/BeanFactory.html">BeanFactory</a> interface.
034 *
035 * <p>
036 * Implementations of this interface can be backed by various sources:
037 * <ul>
038 *    <li>Spring's <a class="doclink" href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ApplicationContext.html">ApplicationContext</a>
039 *    <li>Custom in-memory stores
040 *    <li>Other dependency injection frameworks
041 * </ul>
042 *
043 * <h5 class='section'>Example:</h5>
044 * <p class='bjava'>
045 *    BeanStore <jv>beanStore</jv> = ...;
046 *
047 *    <jc>// Get bean by type</jc>
048 *    Optional&lt;MyService&gt; <jv>service</jv> = <jv>beanStore</jv>.getBean(MyService.<jk>class</jk>);
049 *
050 *    <jc>// Get bean by name</jc>
051 *    Optional&lt;Object&gt; <jv>bean</jv> = <jv>beanStore</jv>.getBean(<js>"myBean"</js>);
052 *
053 *    <jc>// Get bean by type and name</jc>
054 *    Optional&lt;MyService&gt; <jv>namedService</jv> = <jv>beanStore</jv>.getBean(MyService.<jk>class</jk>, <js>"primary"</js>);
055 *
056 *    <jc>// Get all beans of a type</jc>
057 *    Map&lt;String, MyService&gt; <jv>allServices</jv> = <jv>beanStore</jv>.getBeansOfType(MyService.<jk>class</jk>);
058 * </p>
059 *
060 * <h5 class='section'>See Also:</h5><ul>
061 *    <li class='link'><a class="doclink" href="https://docs.spring.io/spring-framework/reference/core/beans.html">Spring Framework - The IoC Container</a>
062 * </ul>
063 */
064public interface BeanStore {
065
066   /**
067    * Returns a bean of the specified type.
068    *
069    * <p>
070    * If multiple beans of the specified type exist, the behavior is implementation-dependent.
071    * Typically, implementations will return the primary bean or throw an exception.
072    *
073    * @param <T> The bean type.
074    * @param beanType The bean type.
075    * @return The bean, or {@link Optional#empty()} if no bean of the specified type exists.
076    */
077   <T> Optional<T> getBean(Class<T> beanType);
078
079   /**
080    * Returns a bean of the specified type and name.
081    *
082    * @param <T> The bean type.
083    * @param beanType The bean type.
084    * @param name The bean name.  Can be <jk>null</jk> for unnamed beans.
085    * @return The bean, or {@link Optional#empty()} if no bean of the specified type and name exists.
086    */
087   <T> Optional<T> getBean(Class<T> beanType, String name);
088
089   /**
090    * Returns all beans of the specified type, keyed by bean name.
091    *
092    * <p>
093    * The map keys are the bean names, and the values are the bean instances.
094    * If no beans of the specified type exist, returns an empty map.
095    *
096    * @param <T> The bean type.
097    * @param beanType The bean type.
098    * @return A map of bean names to bean instances.  Never <jk>null</jk>.
099    */
100   <T> Map<String,T> getBeansOfType(Class<T> beanType);
101
102   /**
103    * Returns <jk>true</jk> if this store contains at least one bean of the specified type.
104    *
105    * @param beanType The bean type.
106    * @return <jk>true</jk> if this store contains at least one bean of the specified type.
107    */
108   boolean hasBean(Class<?> beanType);
109
110   /**
111    * Returns <jk>true</jk> if this store contains a bean of the specified type and name.
112    *
113    * @param beanType The bean type.
114    * @param name The bean name.  Can be <jk>null</jk> for unnamed beans.
115    * @return <jk>true</jk> if this store contains a bean of the specified type and name.
116    */
117   boolean hasBean(Class<?> beanType, String name);
118
119   /**
120    * Convenience shortcut for instantiating a bean of the specified type using this store.
121    *
122    * <p>
123    * Equivalent to:
124    * <p class='bjava'>
125    *    BeanInstantiator.<jsm>of</jsm>(<jv>type</jv>, <jk>this</jk>).run()
126    * </p>
127    *
128    * <p>
129    * Use this when you don't need any of {@link BeanInstantiator}'s configuration options
130    * (subtype, factory-method names, builder hooks, etc.).  For more control over instantiation,
131    * use {@link BeanInstantiator#of(Class, BeanStore)} directly.
132    *
133    * <h5 class='section'>Example:</h5>
134    * <p class='bjava'>
135    *    <jc>// Instantiate a bean using this store for parameter resolution</jc>
136    *    MyService <jv>service</jv> = <jv>beanStore</jv>.instantiate(MyService.<jk>class</jk>);
137    * </p>
138    *
139    * @param <T> The bean type.
140    * @param type The bean type to instantiate.  Must not be <jk>null</jk>.
141    * @return The instantiated bean.
142    */
143   default <T> T instantiate(Class<T> type) {
144      return BeanInstantiator.of(type, this).run();
145   }
146
147   /**
148    * Returns the supplier for an unnamed bean of the specified type.
149    *
150    * <p>
151    * If no supplier is found in this store, searches the parent store recursively.
152    *
153    * @param <T> The bean type.
154    * @param beanType The bean type.
155    * @return The supplier, or {@link Optional#empty()} if no supplier of the specified type exists.
156    */
157   <T> Optional<Supplier<T>> getBeanSupplier(Class<T> beanType);
158
159   /**
160    * Returns the supplier for a named bean of the specified type.
161    *
162    * <p>
163    * If no supplier with the specified name is found in this store, searches the parent store recursively.
164    *
165    * @param <T> The bean type.
166    * @param beanType The bean type.
167    * @param name The bean name.  Can be <jk>null</jk> for unnamed beans.
168    * @return The supplier, or {@link Optional#empty()} if no supplier of the specified type and name exists.
169    */
170   <T> Optional<Supplier<T>> getBeanSupplier(Class<T> beanType, String name);
171
172   /**
173    * Finds and invokes a factory method that produces a bean of type <c>beanType</c>.
174    *
175    * <p>
176    * Scans the methods of the resource class identified by <c>onClassOrObject</c>:
177    * <ul>
178    *    <li>If <c>onClassOrObject</c> is a {@link Class}, only <jk>static</jk> methods are eligible and
179    *       the instance parameter passed to the invoked method is <jk>null</jk>.
180    *    <li>If <c>onClassOrObject</c> is any other object, both instance and static methods on its class are
181    *       eligible, and the object itself is passed as the receiver.
182    * </ul>
183    *
184    * <p>
185    * The search has two passes.  The first pass considers public methods only.  If it finds nothing, a second pass
186    * considers <em>non-public</em> methods (protected, package-private or private) across the class hierarchy,
187    * child class first, but only those that carry {@link Bean @Bean} (declared directly or inherited from a method
188    * they override; see {@link BeanAnnotation#find(MethodInfo)}).  A non-public method without {@link Bean @Bean} is
189    * never invoked, even when <c>filter</c> is <jk>null</jk>.  Non-public methods are made accessible before invocation.
190    *
191    * <p>
192    * The first method satisfying all of the following is invoked:
193    * <ol>
194    *    <li>Not deprecated.
195    *    <li>Return type is assignment-compatible with <c>beanType</c>.
196    *    <li>Accepted by <c>filter</c> (if non-<jk>null</jk>; otherwise any method qualifies).
197    *    <li>All parameters can be resolved from this store plus any <c>extraBeans</c>.
198    * </ol>
199    *
200    * <p>
201    * The default implementation returns {@link Optional#empty()}.
202    * Override in concrete stores (see {@link BasicBeanStore}) to enable factory-method scanning.
203    *
204    * <h5 class='section'>Example:</h5>
205    * <p class='bjava'>
206    *    <jc>// Filter only</jc>
207    *    <jv>beanStore</jv>.createBeanFromMethod(RichLogger.<jk>class</jk>, <jv>resource</jv>,
208    *       <jv>m</jv> -&gt; <jv>m</jv>.hasAnnotation(Bean.<jk>class</jk>))
209    *       .ifPresent(<jv>creator</jv>::impl);
210    *
211    *    <jc>// Filter + extra bean not yet in the store</jc>
212    *    <jv>beanStore</jv>.createBeanFromMethod(EncoderSet.<jk>class</jk>, <jv>resource</jv>,
213    *       <jv>m</jv> -&gt; <jv>m</jv>.hasAnnotation(Bean.<jk>class</jk>), <jv>builder</jv>)
214    *       .ifPresent(<jv>x</jv> -&gt; <jv>builder</jv>.impl(<jv>x</jv>));
215    *
216    *    <jc>// No filter, no extra beans</jc>
217    *    <jv>beanStore</jv>.createBeanFromMethod(RichLogger.<jk>class</jk>, <jv>resource</jv>);
218    * </p>
219    *
220    * @param <T> The bean type.
221    * @param beanType The type of bean to create.  Must not be <jk>null</jk>.
222    * @param onClassOrObject The object instance or {@link Class} whose methods are searched.
223    *    Must not be <jk>null</jk>.
224    * @param filter Optional predicate restricting which methods are eligible.  Can be <jk>null</jk> (any method qualifies).
225    * @param extraBeans Optional bean instances visible to parameter resolution for this call only.
226    *    These are <em>not</em> registered in the store.
227    * @return The created bean wrapped in an {@link Optional}, or {@link Optional#empty()} if no matching
228    *    factory method was found.
229    * @throws BeanCreationException If a matching method was found but threw an exception during invocation.
230    * @see BeanInstantiator BeanInstantiator — for instantiating a bean from its own constructors, builders, or factory methods
231    */
232   default <T> Optional<T> createBeanFromMethod(Class<T> beanType, Object onClassOrObject, Predicate<MethodInfo> filter, Object... extraBeans) {
233      return oe();
234   }
235
236   /**
237    * Invokes one specific factory method to produce a bean of type <c>beanType</c>.
238    *
239    * <p>
240    * Unlike {@link #createBeanFromMethod(Class, Object, Predicate, Object...)}, no search is performed:
241    * the caller supplies the {@link MethodInfo} it has already chosen.  Visibility is not a gate:
242    * public, protected, package-private and private methods are all invoked (non-public methods are made accessible first).
243    * Deprecation is not checked.
244    *
245    * <p>
246    * If <c>onClassOrObject</c> is a {@link Class}, only <jk>static</jk> methods can be invoked.  Otherwise the object
247    * is used as the receiver for instance methods.
248    *
249    * <p>
250    * Returns {@link Optional#empty()} when the method is not eligible: it returns <jk>void</jk>, it is a bridge or
251    * synthetic method, it is an instance method and <c>onClassOrObject</c> is a {@link Class}, or one of its parameters
252    * cannot be resolved from this store plus <c>extraBeans</c>.
253    *
254    * <h5 class='section'>Example:</h5>
255    * <p class='bjava'>
256    *    <jc>// Invoke one specific @Bean factory method, instead of searching by return type.</jc>
257    *    MethodInfo <jv>m</jv> = ClassInfo.<jsm>of</jsm>(MyResource.<jk>class</jk>).getDeclaredMethod(<jv>x</jv> -&gt; <jv>x</jv>.hasName(<js>"cacheIndicator"</js>)).get();
258    *    Optional&lt;HealthIndicator&gt; <jv>cache</jv> = <jv>beanStore</jv>.invokeBeanMethod(HealthIndicator.<jk>class</jk>, <jv>m</jv>, <jv>resource</jv>);
259    * </p>
260    *
261    * @param <T> The bean type.
262    * @param beanType The expected bean type.  Must be assignable from the method's return type.  Must not be <jk>null</jk>.
263    * @param method The factory method to invoke.  Must not be <jk>null</jk>.
264    * @param onClassOrObject The receiver object, or the {@link Class} for static methods.  Must not be <jk>null</jk>.
265    *    The method's declaring class must be assignable from this object's class (or this class).
266    * @param extraBeans Optional bean instances visible to parameter resolution for this call only.
267    * @return The created bean, or {@link Optional#empty()} if the method is not eligible.
268    * @throws IllegalArgumentException If an argument is <jk>null</jk>, <c>beanType</c> is not assignable from the
269    *    method's return type, or the method does not belong to the target's class hierarchy.
270    * @throws BeanCreationException If the method cannot be made accessible or threw an exception during invocation.
271    */
272   default <T> Optional<T> invokeBeanMethod(Class<T> beanType, MethodInfo method, Object onClassOrObject, Object... extraBeans) {
273      reqnn("beanType", beanType);
274      reqnn("method", method);
275      reqnn("onClassOrObject", onClassOrObject);
276      var returnType = method.getReturnType();
277      if (returnType.is(void.class) || method.isBridge() || method.isSynthetic())
278         return Optional.empty();
279      req(ClassInfo.of(beanType).getWrapperIfPrimitive().inner().isAssignableFrom(returnType.getWrapperIfPrimitive().inner()),
280         "Bean type [%s] is not assignable from the return type [%s] of method [%s].", beanType.getSimpleName(), returnType.getNameSimple(), method.getLabel());
281      var isClass = onClassOrObject instanceof Class;
282      Class<?> target = isClass ? (Class<?>)onClassOrObject : onClassOrObject.getClass();
283      req(method.inner().getDeclaringClass().isAssignableFrom(target),
284         "Method [%s] is not declared on the hierarchy of [%s].", method.getLabel(), target.getSimpleName());
285      if (method.isNotStatic() && isClass)
286         return Optional.empty();
287      if (! method.canResolveAllParameters(this, extraBeans))
288         return Optional.empty();
289      return Optional.ofNullable(BeanMethodInvoker.invoke(this, beanType, method, method.isStatic() ? null : onClassOrObject, extraBeans));
290   }
291
292   /**
293    * Convenience overload of {@link #createBeanFromMethod(Class, Object, Predicate, Object...)} with no filter and no extra beans.
294    *
295    * @param <T> The bean type.
296    * @param beanType The type of bean to create.  Must not be <jk>null</jk>.
297    * @param onClassOrObject The object instance or {@link Class} whose methods are searched.
298    *    Must not be <jk>null</jk>.
299    * @return The created bean wrapped in an {@link Optional}, or {@link Optional#empty()} if no matching
300    *    factory method was found.
301    * @throws BeanCreationException If a matching method was found but threw an exception during invocation.
302    */
303   default <T> Optional<T> createBeanFromMethod(Class<T> beanType, Object onClassOrObject) {
304      return createBeanFromMethod(beanType, onClassOrObject, null);
305   }
306
307   /**
308    * Returns the implementation class registered for the specified bean type, if any.
309    *
310    * <p>
311    * Type bindings let callers register a default implementation class to be instantiated when an
312    * actual bean of <c>beanType</c> is needed.  Bindings are inherited through the parent chain.
313    *
314    * <p>
315    * The default implementation returns {@link Optional#empty()} (no binding).  Override in concrete
316    * stores (see {@link BasicBeanStore}) to enable type binding.
317    *
318    * @param <T> The bean type.
319    * @param beanType The bean type whose implementation class is being looked up.
320    * @return The implementation class, or {@link Optional#empty()} if no binding exists.
321    */
322   default <T> Optional<Class<? extends T>> getBeanType(Class<T> beanType) {
323      return oe();
324   }
325
326   /**
327    * Returns <jk>true</jk> if this store can satisfy every parameter of the specified executable.
328    *
329    * <p>
330    * Skips:
331    * <ul>
332    *    <li>The first parameter when it is an instance of <c>outer</c> (inner-class outer reference).
333    *    <li>Parameters typed as {@link Optional} (always considered satisfied).
334    * </ul>
335    *
336    * @param executable The constructor or method to inspect.  Must not be <jk>null</jk>.
337    * @param outer The outer object to use for inner-class param resolution.  Can be <jk>null</jk> (the outer-instance skip rule is then never applied).
338    * @return <jk>true</jk> if every required parameter can be resolved from this store.
339    */
340   default boolean hasAllParams(ExecutableInfo executable, Object outer) {
341      for (var i = 0; i < executable.getParameterCount(); i++) {
342         var pi = executable.getParameter(i);
343         var pt = pi.getParameterType();
344         if ((i == 0 && nn(outer) && pt.isInstance(outer)) || pt.is(Optional.class))
345            continue;
346         var beanQualifier = pi.getResolvedQualifier();
347         var ptc = pt.inner();
348         if ((beanQualifier == null && ! hasBean(ptc)) || (nn(beanQualifier) && ! hasBean(ptc, beanQualifier)))
349            return false;
350      }
351      return true;
352   }
353
354   /**
355    * Resolves every parameter of the specified executable from this store.
356    *
357    * <p>
358    * For {@link Optional}-typed parameters the wrapped {@link Optional} is returned directly; for all
359    * others the bean instance (or <jk>null</jk> when missing) is returned.  See {@link #hasAllParams}
360    * for the full skip rules.
361    *
362    * @param executable The constructor or method to resolve parameters for.  Must not be <jk>null</jk>.
363    * @param outer The outer object to use for inner-class param resolution.  Can be <jk>null</jk> (the outer-instance skip rule is then never applied).
364    * @return An array of resolved parameter values, one per parameter of <c>executable</c>.
365    */
366   default Object[] getParams(ExecutableInfo executable, Object outer) {
367      var o = new Object[executable.getParameterCount()];
368      for (var i = 0; i < executable.getParameterCount(); i++) {
369         var pi = executable.getParameter(i);
370         var pt = pi.getParameterType();
371         if (i == 0 && nn(outer) && pt.isInstance(outer)) {
372            o[i] = outer;
373         } else {
374            var beanQualifier = pi.getResolvedQualifier();
375            var ptc = pt.unwrap(Optional.class).inner();
376            var o2 = beanQualifier == null ? getBean(ptc) : getBean(ptc, beanQualifier);
377            o[i] = pt.is(Optional.class) ? o2 : o2.orElse(null);
378         }
379      }
380      return o;
381   }
382
383   /**
384    * Returns a comma-delimited list of parameter type names that cannot be resolved from this store.
385    *
386    * <p>
387    * Intended for diagnostic error messages — see {@link #hasAllParams} for the exact match rules.
388    * Skipped parameters (outer-instance match, {@link Optional}) never appear in the result.
389    *
390    * @param executable The constructor or method to inspect.  Must not be <jk>null</jk>.
391    * @param outer The outer object to use for inner-class param resolution.  Can be <jk>null</jk> (the outer-instance skip rule is then never applied).
392    * @return A comma-delimited list of missing parameter type names, or <jk>null</jk> when none are missing.
393    */
394   default String getMissingParams(ExecutableInfo executable, Object outer) {
395      var params = executable.getParameters();
396      List<String> l = list();
397      for (var i = 0; i < params.size(); i++) {
398         var pi = params.get(i);
399         var pt = pi.getParameterType();
400         if ((i == 0 && nn(outer) && pt.isInstance(outer)) || pt.is(Optional.class))
401            continue;
402         var beanName = pi.getResolvedQualifier();
403         var ptc = pt.inner();
404         if (beanName == null && ! hasBean(ptc))
405            l.add(pt.getNameSimple());
406         if (nn(beanName) && ! hasBean(ptc, beanName))
407            l.add(pt.getNameSimple() + '@' + beanName);
408      }
409      return l.isEmpty() ? null : l.stream().sorted().collect(joining(","));
410   }
411}
412