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