001package io.avaje.inject;
002
003import io.avaje.inject.spi.Module;
004
005import java.lang.reflect.Type;
006import java.util.function.Consumer;
007
008/**
009 * Build a bean scope with options for shutdown hook and supplying external dependencies.
010 * <p>
011 * We can provide external dependencies that are then used in wiring the components.
012 * </p>
013 *
014 * <pre>{@code
015 *
016 *   // external dependencies
017 *   Pump pump = ...
018 *
019 *   BeanScope scope = BeanScope.newBuilder()
020 *     .withBean(pump)
021 *     .build();
022 *
023 *   CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class);
024 *   coffeeMaker.makeIt();
025 *
026 * }</pre>
027 */
028public interface BeanScopeBuilder {
029
030  /**
031   * Create the bean scope registering a shutdown hook (defaults to false, no shutdown hook).
032   * <p>
033   * With {@code withShutdownHook(true)} a shutdown hook will be registered with the Runtime
034   * and executed when the JVM initiates a shutdown. This then will run the {@code preDestroy}
035   * lifecycle methods.
036   * </p>
037   * <pre>{@code
038   *
039   *   // automatically closed via try with resources
040   *
041   *   BeanScope scope = BeanScope.newBuilder()
042   *     .withShutdownHook(true)
043   *     .build());
044   *
045   *   // on JVM shutdown the preDestroy lifecycle methods are executed
046   *
047   * }</pre>
048   *
049   * @return This BeanScopeBuilder
050   */
051  BeanScopeBuilder withShutdownHook(boolean shutdownHook);
052
053  /**
054   * Specify the modules to include in dependency injection.
055   * <p>
056   * Only beans related to the module are included in the BeanScope that is built.
057   * <p>
058   * When we do not explicitly specify modules then all modules that are not "custom scoped"
059   * are found and used via service loading.
060   *
061   * <pre>{@code
062   *
063   *   BeanScope scope = BeanScope.newBuilder()
064   *     .withModules(new CustomModule())
065   *     .build());
066   *
067   *   CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class);
068   *   coffeeMaker.makeIt();
069   *
070   * }</pre>
071   *
072   * @param modules The modules that we want to include in dependency injection.
073   * @return This BeanScopeBuilder
074   */
075  BeanScopeBuilder withModules(Module... modules);
076
077  /**
078   * Supply a bean to the scope that will be used instead of any
079   * similar bean in the scope.
080   * <p>
081   * This is typically expected to be used in tests and the bean
082   * supplied is typically a test double or mock.
083   * </p>
084   *
085   * <pre>{@code
086   *
087   *   // external dependencies
088   *   Pump pump = ...
089   *   Grinder grinder = ...
090   *
091   *   BeanScope scope = BeanScope.newBuilder()
092   *     .withBeans(pump, grinder)
093   *     .build();
094   *
095   *   CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class);
096   *   coffeeMaker.makeIt();
097   *
098   * }</pre>
099   *
100   * @param beans Externally provided beans used when injecting a dependency
101   *              for the bean or the interface(s) it implements
102   * @return This BeanScopeBuilder
103   */
104  BeanScopeBuilder withBeans(Object... beans);
105
106  /**
107   * Add a supplied bean instance with the given injection type (typically an interface type).
108   *
109   * <pre>{@code
110   *
111   *   Pump externalDependency = ...
112   *
113   *   try (BeanScope scope = BeanScope.newBuilder()
114   *     .withBean(Pump.class, externalDependency)
115   *     .build()) {
116   *
117   *     CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class);
118   *     coffeeMaker.makeIt();
119   *
120   *     Pump pump = scope.get(Pump.class);
121   *     assertThat(pump).isSameAs(externalDependency);
122   *   }
123   *
124   * }</pre>
125   *
126   * @param type The dependency injection type this bean is target for
127   * @param bean The supplied bean instance to use for injection
128   */
129  <D> BeanScopeBuilder withBean(Class<D> type, D bean);
130
131  /**
132   * Add a supplied bean instance with the given name and injection type.
133   *
134   * @param name The name qualifier
135   * @param type The dependency injection type this bean is target for
136   * @param bean The supplied bean instance to use for injection
137   */
138  <D> BeanScopeBuilder withBean(String name, Class<D> type, D bean);
139
140  /**
141   * Add a supplied bean instance with the given name and generic type.
142   *
143   * @param name The name qualifier
144   * @param type The dependency injection type this bean is target for
145   * @param bean The supplied bean instance to use for injection
146   */
147  <D> BeanScopeBuilder withBean(String name, Type type, D bean);
148
149  /**
150   * Add a supplied bean instance with a generic type.
151   *
152   * @param type The dependency injection type this bean is target for
153   * @param bean The supplied bean instance to use for injection
154   */
155  <D> BeanScopeBuilder withBean(Type type, D bean);
156
157  /**
158   * Use the given BeanScope as the parent. This becomes an additional
159   * source of beans that can be wired and accessed in this scope.
160   *
161   * @param parent The BeanScope that acts as the parent
162   */
163  BeanScopeBuilder withParent(BeanScope parent);
164
165  /**
166   * Extend the builder to support testing using mockito with
167   * <code>withMock()</code> and <code>withSpy()</code> methods.
168   *
169   * @return The builder with extra testing support for mockito mocks and spies
170   */
171  BeanScopeBuilder.ForTesting forTesting();
172
173  /**
174   * Build and return the bean scope.
175   * <p>
176   * The BeanScope is effectively immutable in that all components are created
177   * and all PostConstruct lifecycle methods have been invoked.
178   * <p>
179   * The beanScope effectively contains eager singletons.
180   *
181   * @return The BeanScope
182   */
183  BeanScope build();
184
185  /**
186   * Extends the building with testing specific support for mocks and spies.
187   */
188  interface ForTesting extends BeanScopeBuilder {
189
190    /**
191     * Use a mockito mock when injecting this bean type.
192     *
193     * <pre>{@code
194     *
195     *   try (BeanScope scope = BeanScope.newBuilder()
196     *     .forTesting()
197     *     .withMock(Pump.class)
198     *     .withMock(Grinder.class)
199     *     .build()) {
200     *
201     *
202     *     CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class);
203     *     coffeeMaker.makeIt();
204     *
205     *     // this is a mockito mock
206     *     Grinder grinder = scope.get(Grinder.class);
207     *     verify(grinder).grindBeans();
208     *   }
209     *
210     * }</pre>
211     */
212    BeanScopeBuilder.ForTesting withMock(Class<?> type);
213
214    /**
215     * Register as a Mockito mock with a qualifier name.
216     *
217     * <pre>{@code
218     *
219     *   try (BeanScope scope = BeanScope.newBuilder()
220     *     .forTesting()
221     *     .withMock(Store.class, "red")
222     *     .withMock(Store.class, "blue")
223     *     .build()) {
224     *
225     *     ...
226     *   }
227     *
228     * }</pre>
229     */
230    BeanScopeBuilder.ForTesting withMock(Class<?> type, String name);
231
232    /**
233     * Use a mockito mock when injecting this bean type additionally
234     * running setup on the mock instance.
235     *
236     * <pre>{@code
237     *
238     *   try (BeanScope scope = BeanScope.newBuilder()
239     *     .forTesting()
240     *     .withMock(Pump.class)
241     *     .withMock(Grinder.class, grinder -> {
242     *
243     *       // setup the mock
244     *       when(grinder.grindBeans()).thenReturn("stub response");
245     *     })
246     *     .build()) {
247     *
248     *
249     *     CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class);
250     *     coffeeMaker.makeIt();
251     *
252     *     // this is a mockito mock
253     *     Grinder grinder = scope.get(Grinder.class);
254     *     verify(grinder).grindBeans();
255     *   }
256     *
257     * }</pre>
258     */
259    <D> BeanScopeBuilder.ForTesting withMock(Class<D> type, Consumer<D> consumer);
260
261    /**
262     * Use a mockito spy when injecting this bean type.
263     *
264     * <pre>{@code
265     *
266     *   try (BeanScope scope = BeanScope.newBuilder()
267     *     .forTesting()
268     *     .withSpy(Pump.class)
269     *     .build()) {
270     *
271     *     // setup spy here ...
272     *     Pump pump = scope.get(Pump.class);
273     *     doNothing().when(pump).pumpSteam();
274     *
275     *     // act
276     *     CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class);
277     *     coffeeMaker.makeIt();
278     *
279     *     verify(pump).pumpWater();
280     *     verify(pump).pumpSteam();
281     *   }
282     *
283     * }</pre>
284     */
285    BeanScopeBuilder.ForTesting withSpy(Class<?> type);
286
287    /**
288     * Register a Mockito spy with a qualifier name.
289     *
290     * <pre>{@code
291     *
292     *   try (BeanScope scope = BeanScope.newBuilder()
293     *     .forTesting()
294     *     .withSpy(Store.class, "red")
295     *     .withSpy(Store.class, "blue")
296     *     .build()) {
297     *
298     *     ...
299     *   }
300     *
301     * }</pre>
302     */
303    BeanScopeBuilder.ForTesting withSpy(Class<?> type, String name);
304
305    /**
306     * Use a mockito spy when injecting this bean type additionally
307     * running setup on the spy instance.
308     *
309     * <pre>{@code
310     *
311     *   try (BeanScope scope = BeanScope.newBuilder()
312     *     .forTesting()
313     *     .withSpy(Pump.class, pump -> {
314     *       // setup the spy
315     *       doNothing().when(pump).pumpWater();
316     *     })
317     *     .build()) {
318     *
319     *     // or setup here ...
320     *     Pump pump = scope.get(Pump.class);
321     *     doNothing().when(pump).pumpSteam();
322     *
323     *     // act
324     *     CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class);
325     *     coffeeMaker.makeIt();
326     *
327     *     verify(pump).pumpWater();
328     *     verify(pump).pumpSteam();
329     *   }
330     *
331     * }</pre>
332     */
333    <D> BeanScopeBuilder.ForTesting withSpy(Class<D> type, Consumer<D> consumer);
334
335  }
336}