001package io.avaje.inject;
002
003import java.lang.annotation.Annotation;
004import java.lang.reflect.Type;
005import java.util.List;
006import java.util.Map;
007import java.util.Optional;
008
009import org.jspecify.annotations.Nullable;
010
011/**
012 * Holds beans created by dependency injection.
013 * <p>
014 * The beans have singleton scope, support lifecycle methods for postConstruct and
015 * preDestroy and are created (wired) via dependency injection.
016 * </p>
017 *
018 * <h3>Create a BeanScope</h3>
019 * <p>
020 * We can programmatically create a BeanScope via {@code BeanScope.builder()}.
021 * </p>
022 * <pre>{@code
023 *
024 *   // create a BeanScope ...
025 *
026 *   try (BeanScope scope = BeanScope.builder()
027 *     .build()) {
028 *
029 *     CoffeeMaker coffeeMaker = context.get(CoffeeMaker.class);
030 *     coffeeMaker.makeIt()
031 *   }
032 *
033 * }</pre>
034 *
035 * <h3>External dependencies</h3>
036 * <p>
037 * We can supporting external dependencies when creating the BeanScope.
038 * Use the {@link io.avaje.inject.External @External} annotation.
039 * <p>
040 * For example, given we have Pump as an externally provided dependency.
041 *
042 * <pre>{@code
043 *
044 *   class CoffeeMaker {
045 *     // tell the annotation processor Pump is provided externally at runtime
046 *     // otherwise it thinks we have a missing dependency
047 *     @External Pump pump;
048 *   }
049 * }</pre>
050 * <p>
051 * When building the BeanScope, the dependency must be provided manually via
052 * {@link BeanScopeBuilder#bean(Class, Object)}.
053 *
054 * <pre>{@code
055 *
056 *   // provide external dependencies ...
057 *   Pump pump = ...
058 *
059 *   try (BeanScope scope = BeanScope.builder()
060 *     .bean(Pump.class, pump)
061 *     .build()) {
062 *
063 *     CoffeeMaker coffeeMaker = context.get(CoffeeMaker.class);
064 *     coffeeMaker.makeIt()
065 *   }
066 *
067 * }</pre>
068 */
069public interface BeanScope extends AutoCloseable {
070
071  /**
072   * Build a bean scope with options for shutdown hook and supplying external dependencies.
073   * <p>
074   * We can optionally:
075   * <ul>
076   *   <li>Provide external dependencies</li>
077   *   <li>Specify a parent BeanScope</li>
078   *   <li>Specify specific modules to wire</li>
079   *   <li>Specify to include a shutdown hook (to fire preDestroy lifecycle methods)</li>
080   *   <li>Use {@code forTesting()} to specify mocks and spies to use when wiring tests</li>
081   * </ul>
082   *
083   * <pre>{@code
084   *
085   *   // create a BeanScope ...
086   *
087   *   try (BeanScope scope = BeanScope.builder()
088   *     .build()) {
089   *
090   *     CoffeeMaker coffeeMaker = context.get(CoffeeMaker.class);
091   *     coffeeMaker.makeIt()
092   *   }
093   *
094   * }</pre>
095   */
096  static BeanScopeBuilder builder() {
097    return new DBeanScopeBuilder();
098  }
099
100  /**
101   * Return a single bean given the type.
102   *
103   * <pre>{@code
104   *
105   *   CoffeeMaker coffeeMaker = beanScope.get(CoffeeMaker.class);
106   *   coffeeMaker.brew();
107   *
108   * }</pre>
109   *
110   * @param type an interface or bean type
111   * @throws java.util.NoSuchElementException When no matching bean is found
112   */
113  <T> T get(Class<T> type);
114
115  /**
116   * Return a single bean given the type and name.
117   *
118   * <pre>{@code
119   *
120   *   Heater heater = beanScope.get(Heater.class, "electric");
121   *   heater.heat();
122   *
123   * }</pre>
124   *
125   * @param type an interface or bean type
126   * @param name the name qualifier of a specific bean
127   * @throws java.util.NoSuchElementException When no matching bean is found
128   */
129  <T> T get(Class<T> type, @Nullable String name);
130
131  /**
132   * Return a single bean given the full generic type.
133   *
134   * @param type The generic type
135   * @throws java.util.NoSuchElementException When no matching bean is found
136   */
137  default <T> T get(Type type) {
138    return get(type, null);
139  }
140
141  /**
142   * Return a single bean given the full generic type and name.
143   *
144   * @param type The generic type
145   * @param name the name qualifier of a specific bean
146   * @throws java.util.NoSuchElementException When no matching bean is found
147   */
148  <T> T get(Type type, @Nullable String name);
149
150  /**
151   * Optionally return a single bean given the type and empty if it is not found.
152   *
153   * @param type an interface or bean type
154   */
155  <T> Optional<T> getOptional(Class<T> type);
156
157  /**
158   * Optionally return a single bean given the type and name and empty if it is not found.
159   *
160   * @param type an interface or bean type
161   * @param name the name qualifier of a specific bean
162   */
163  <T> Optional<T> getOptional(Type type, @Nullable String name);
164
165  /**
166   * Return the list of beans that have an annotation. The annotation must have a @Retention policy of RUNTIME
167   *
168   * <pre>{@code
169   *
170   *   // e.g. register all controllers with web a framework
171   *   // .. where Controller is an annotation on the beans
172   *
173   *   List<Object> controllers = beanScope.listByAnnotation(Controller.class);
174   *
175   * }</pre>
176   *
177   * @param annotation An annotation class.
178   */
179  List<Object> listByAnnotation(Class<? extends Annotation> annotation);
180
181  /**
182   * Return the list of beans for a given type.
183   *
184   * <pre>{@code
185   *
186   *   // e.g. register all routes for a web framework
187   *
188   *   List<WebRoute> routes = beanScope.list(WebRoute.class);
189   *
190   * }</pre>
191   *
192   * @param type The type of beans to return.
193   */
194  <T> List<T> list(Class<T> type);
195
196  /**
197   * Return the list of beans that implement the given type.
198   */
199  <T> List<T> list(Type type);
200
201  /**
202   * Return the list of beans that implement the interface sorting by priority.
203   */
204  <T> List<T> listByPriority(Class<T> type);
205
206  /**
207   * Return the beans that implement the interface sorting by the priority annotation used.
208   * <p>
209   * The priority annotation will typically be either <code>javax.annotation.Priority</code>
210   * or <code>jakarta.annotation.Priority</code>.
211   *
212   * @param type     The interface type of the beans to return
213   * @param priority The priority annotation used to sort the beans
214   */
215  <T> List<T> listByPriority(Class<T> type, Class<? extends Annotation> priority);
216
217  /**
218   * Return the beans for this type mapped by their qualifier name.
219   * <p>
220   * Beans with no qualifier name get a generated unique key to use instead.
221   */
222  <T> Map<String, T> map(Type type);
223
224  /**
225   * Return all the bean entries from the scope.
226   * <p>
227   * The bean entries include entries from the parent scope if it has one.
228   *
229   * @return All bean entries from the scope.
230   */
231  List<BeanEntry> all();
232
233  /**
234   * Return true if the bean scope contains the given type.
235   */
236  boolean contains(Type type);
237
238  /**
239   * Return true if the bean scope contains the given type.
240   */
241  boolean contains(String type);
242
243  /**
244   * Close the scope firing any <code>@PreDestroy</code> lifecycle methods.
245   */
246  @Override
247  void close();
248}