001package io.avaje.inject;
002
003import java.lang.annotation.Annotation;
004import java.lang.reflect.Type;
005import java.util.List;
006
007/**
008 * Holds beans created by dependency injection.
009 * <p>
010 * The beans have singleton scope, support lifecycle methods for postConstruct and
011 * preDestroy and are created (wired) via dependency injection.
012 * </p>
013 *
014 * <h3>Create a BeanScope</h3>
015 * <p>
016 * We can programmatically create a BeanScope via {@code BeanScope.newBuilder()}.
017 * </p>
018 * <pre>{@code
019 *
020 *   // create a BeanScope ...
021 *
022 *   try (BeanScope scope = BeanScope.newBuilder()
023 *     .build()) {
024 *
025 *     CoffeeMaker coffeeMaker = context.get(CoffeeMaker.class);
026 *     coffeeMaker.makeIt()
027 *   }
028 *
029 * }</pre>
030 *
031 * <h3>External dependencies</h3>
032 * <p>
033 * We can supporting external dependencies when creating the BeanScope. We need to do 2 things.
034 * we need to specify these via
035 * </p>
036 * <ul>
037 *   <li>
038 *       1. Specify the external dependency via {@code @InjectModule(requires=...)}.
039 *       Otherwise at compile time the annotation processor detects it as a missing dependency and we can't compile.
040 *   </li>
041 *   <li>
042 *       2. Provide the dependency when creating the BeanScope
043 *   </li>
044 * </ul>
045 * <p>
046 * For example, given we have Pump as an externally provided dependency.
047 *
048 * <pre>{@code
049 *
050 *   // tell the annotation processor Pump is provided externally
051 *   // otherwise it thinks we have a missing dependency
052 *
053 *   @InjectModule(requires=Pump.class)
054 *
055 * }</pre>
056 * <p>
057 * When we build the BeanScope provide the dependency via {@link BeanScopeBuilder#withBean(Class, Object)}.
058 *
059 * <pre>{@code
060 *
061 *   // provide external dependencies ...
062 *   Pump pump = ...
063 *
064 *   try (BeanScope scope = BeanScope.newBuilder()
065 *     .withBean(Pump.class, pump)
066 *     .build()) {
067 *
068 *     CoffeeMaker coffeeMaker = context.get(CoffeeMaker.class);
069 *     coffeeMaker.makeIt()
070 *   }
071 *
072 * }</pre>
073 */
074public interface BeanScope extends AutoCloseable {
075
076  /**
077   * Build a bean scope with options for shutdown hook and supplying external dependencies.
078   * <p>
079   * We can optionally:
080   * <ul>
081   *   <li>Provide external dependencies</li>
082   *   <li>Specify a parent BeanScope</li>
083   *   <li>Specify specific modules to wire</li>
084   *   <li>Specify to include a shutdown hook (to fire preDestroy lifecycle methods)</li>
085   *   <li>Use {@code forTesting()} to specify mocks and spies to use when wiring tests</li>
086   * </ul>
087   *
088   * <pre>{@code
089   *
090   *   // create a BeanScope ...
091   *
092   *   try (BeanScope scope = BeanScope.newBuilder()
093   *     .build()) {
094   *
095   *     CoffeeMaker coffeeMaker = context.get(CoffeeMaker.class);
096   *     coffeeMaker.makeIt()
097   *   }
098   *
099   * }</pre>
100   */
101  static BeanScopeBuilder newBuilder() {
102    return new DBeanScopeBuilder();
103  }
104
105  /**
106   * Return a single bean given the type.
107   *
108   * <pre>{@code
109   *
110   *   CoffeeMaker coffeeMaker = beanScope.get(CoffeeMaker.class);
111   *   coffeeMaker.brew();
112   *
113   * }</pre>
114   *
115   * @param type an interface or bean type
116   */
117  <T> T get(Class<T> type);
118
119  /**
120   * Return a single bean given the type and name.
121   *
122   * <pre>{@code
123   *
124   *   Heater heater = beanScope.get(Heater.class, "electric");
125   *   heater.heat();
126   *
127   * }</pre>
128   *
129   * @param type an interface or bean type
130   * @param name the name qualifier of a specific bean
131   */
132  <T> T get(Class<T> type, String name);
133
134  /**
135   * Return a single bean given the generic type and name.
136   *
137   * @param type The generic type
138   * @param name the name qualifier of a specific bean
139   */
140  <T> T get(Type type, String name);
141
142  /**
143   * Return the list of beans that have an annotation.
144   *
145   * <pre>{@code
146   *
147   *   // e.g. register all controllers with web a framework
148   *   // .. where Controller is an annotation on the beans
149   *
150   *   List<Object> controllers = beanScope.listByAnnotation(Controller.class);
151   *
152   * }</pre>
153   *
154   * @param annotation An annotation class.
155   */
156  List<Object> listByAnnotation(Class<?> annotation);
157
158  /**
159   * Return the list of beans that implement the interface.
160   *
161   * <pre>{@code
162   *
163   *   // e.g. register all routes for a web framework
164   *
165   *   List<WebRoute> routes = beanScope.list(WebRoute.class);
166   *
167   * }</pre>
168   *
169   * @param interfaceType An interface class.
170   */
171  <T> List<T> list(Class<T> interfaceType);
172
173  /**
174   * Return the list of beans that implement the interface sorting by priority.
175   */
176  <T> List<T> listByPriority(Class<T> interfaceType);
177
178  /**
179   * Return the beans that implement the interface sorting by the priority annotation used.
180   * <p>
181   * The priority annotation will typically be either <code>javax.annotation.Priority</code>
182   * or <code>jakarta.annotation.Priority</code>.
183   *
184   * @param interfaceType The interface type of the beans to return
185   * @param priority      The priority annotation used to sort the beans
186   */
187  <T> List<T> listByPriority(Class<T> interfaceType, Class<? extends Annotation> priority);
188
189  /**
190   * Return all the bean entries from the scope.
191   * <p>
192   * The bean entries include entries from the parent scope if it has one.
193   *
194   * @return All bean entries from the scope.
195   */
196  List<BeanEntry> all();
197
198  /**
199   * Close the scope firing any <code>@PreDestroy</code> lifecycle methods.
200   */
201  void close();
202}