001package io.avaje.inject;
002
003import io.avaje.inject.spi.Module;
004import io.avaje.lang.NonNullApi;
005
006import java.lang.reflect.Type;
007import java.util.function.Consumer;
008
009/**
010 * Build a bean scope with options for shutdown hook and supplying external dependencies.
011 * <p>
012 * We can provide external dependencies that are then used in wiring the components.
013 * </p>
014 *
015 * <pre>{@code
016 *
017 *   // external dependencies
018 *   Pump pump = ...
019 *
020 *   BeanScope scope = BeanScope.builder()
021 *     .bean(pump)
022 *     .build();
023 *
024 *   CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class);
025 *   coffeeMaker.makeIt();
026 *
027 * }</pre>
028 */
029@NonNullApi
030public interface BeanScopeBuilder {
031
032  /**
033   * Create the bean scope registering a shutdown hook (defaults to false, no shutdown hook).
034   * <p>
035   * With {@code withShutdownHook(true)} a shutdown hook will be registered with the Runtime
036   * and executed when the JVM initiates a shutdown. This then will run the {@code preDestroy}
037   * lifecycle methods.
038   * </p>
039   * <pre>{@code
040   *
041   *   // automatically closed via try with resources
042   *
043   *   BeanScope scope = BeanScope.builder()
044   *     .shutdownHook(true)
045   *     .build());
046   *
047   *   // on JVM shutdown the preDestroy lifecycle methods are executed
048   *
049   * }</pre>
050   *
051   * @return This BeanScopeBuilder
052   */
053  BeanScopeBuilder shutdownHook(boolean shutdownHook);
054
055  /**
056   * Deprecated - migrate to shutdownHook().
057   */
058  @Deprecated
059  default BeanScopeBuilder withShutdownHook(boolean shutdownHook) {
060    return shutdownHook(shutdownHook);
061  }
062
063  /**
064   * Specify the modules to include in dependency injection.
065   * <p>
066   * Only beans related to the module are included in the BeanScope that is built.
067   * <p>
068   * When we do not explicitly specify modules then all modules that are not "custom scoped"
069   * are found and used via service loading.
070   *
071   * <pre>{@code
072   *
073   *   BeanScope scope = BeanScope.builder()
074   *     .modules(new CustomModule())
075   *     .build());
076   *
077   *   CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class);
078   *   coffeeMaker.makeIt();
079   *
080   * }</pre>
081   *
082   * @param modules The modules that we want to include in dependency injection.
083   * @return This BeanScopeBuilder
084   */
085  BeanScopeBuilder modules(Module... modules);
086
087  /**
088   * Deprecated - migrate to modules()
089   */
090  @Deprecated
091  default BeanScopeBuilder withModules(Module... modules) {
092    return modules(modules);
093  }
094
095  /**
096   * Supply a bean to the scope that will be used instead of any
097   * similar bean in the scope.
098   * <p>
099   * This is typically expected to be used in tests and the bean
100   * supplied is typically a test double or mock.
101   * </p>
102   *
103   * <pre>{@code
104   *
105   *   // external dependencies
106   *   Pump pump = ...
107   *   Grinder grinder = ...
108   *
109   *   BeanScope scope = BeanScope.builder()
110   *     .beans(pump, grinder)
111   *     .build();
112   *
113   *   CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class);
114   *   coffeeMaker.makeIt();
115   *
116   * }</pre>
117   *
118   * @param beans Externally provided beans used when injecting a dependency
119   *              for the bean or the interface(s) it implements
120   * @return This BeanScopeBuilder
121   */
122  BeanScopeBuilder beans(Object... beans);
123
124  /**
125   * Deprecated - migrate to beans().
126   */
127  @Deprecated
128  default BeanScopeBuilder withBeans(Object... beans) {
129    return beans(beans);
130  }
131
132  /**
133   * Add a supplied bean instance with the given injection type (typically an interface type).
134   *
135   * <pre>{@code
136   *
137   *   Pump externalDependency = ...
138   *
139   *   try (BeanScope scope = BeanScope.builder()
140   *     .bean(Pump.class, externalDependency)
141   *     .build()) {
142   *
143   *     CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class);
144   *     coffeeMaker.makeIt();
145   *
146   *     Pump pump = scope.get(Pump.class);
147   *     assertThat(pump).isSameAs(externalDependency);
148   *   }
149   *
150   * }</pre>
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 bean(Class<D> type, D bean);
156
157  /**
158   * Deprecated - migrate to bean().
159   */
160  @Deprecated
161  default <D> BeanScopeBuilder withBean(Class<D> type, D bean) {
162    return bean(type, bean);
163  }
164
165  /**
166   * Add a supplied bean instance with the given name and injection type.
167   *
168   * @param name The name qualifier
169   * @param type The dependency injection type this bean is target for
170   * @param bean The supplied bean instance to use for injection
171   */
172  <D> BeanScopeBuilder bean(String name, Class<D> type, D bean);
173
174  /**
175   * Deprecated - migrate to bean().
176   */
177  @Deprecated
178  default <D> BeanScopeBuilder withBean(String name, Class<D> type, D bean) {
179    return bean(name, type, bean);
180  }
181
182  /**
183   * Add a supplied bean instance with the given name and generic type.
184   *
185   * @param name The name qualifier
186   * @param type The dependency injection type this bean is target for
187   * @param bean The supplied bean instance to use for injection
188   */
189  <D> BeanScopeBuilder bean(String name, Type type, D bean);
190
191  /**
192   * Deprecated - migrate to bean().
193   */
194  @Deprecated
195  default <D> BeanScopeBuilder withBean(String name, Type type, D bean) {
196    return bean(name, type, bean);
197  }
198
199  /**
200   * Add a supplied bean instance with a generic type.
201   *
202   * @param type The dependency injection type this bean is target for
203   * @param bean The supplied bean instance to use for injection
204   */
205  <D> BeanScopeBuilder bean(Type type, D bean);
206
207  /**
208   * Deprecated - migrate to bean().
209   */
210  @Deprecated
211  default <D> BeanScopeBuilder withBean(Type type, D bean) {
212    return bean(type, bean);
213  }
214
215  /**
216   * Use the given BeanScope as the parent. This becomes an additional
217   * source of beans that can be wired and accessed in this scope.
218   *
219   * @param parent The BeanScope that acts as the parent
220   */
221  BeanScopeBuilder parent(BeanScope parent);
222
223  /**
224   * Deprecated - migrate to parent().
225   */
226  @Deprecated
227  default BeanScopeBuilder withParent(BeanScope parent) {
228    return parent(parent);
229  }
230
231  /**
232   * Use the given BeanScope as the parent additionally specifying if beans
233   * added will effectively override beans that exist in the parent scope.
234   * <p>
235   * By default, child scopes will override a bean that exists in a parent scope.
236   * For testing purposes, parentOverride=false is used such that bean provided
237   * in parent test scopes are used (unless we mock() or spy() them).
238   * <p>
239   * See TestBeanScope in avaje-inject-test which has helper methods to build
240   * BeanScopes for testing with the "Global test scope" as a parent scope.
241   *
242   * @param parent         The BeanScope that acts as the parent
243   * @param parentOverride When false do not add beans that already exist on the parent.
244   *                       When true add beans regardless of whether they exist in the parent scope.
245   */
246  BeanScopeBuilder parent(BeanScope parent, boolean parentOverride);
247
248  /**
249   * Deprecated - migrate to parent().
250   */
251  @Deprecated
252  default BeanScopeBuilder withParent(BeanScope parent, boolean parentOverride) {
253    return parent(parent, parentOverride);
254  }
255
256  /**
257   * Extend the builder to support testing using mockito with
258   * <code>withMock()</code> and <code>withSpy()</code> methods.
259   *
260   * @return The builder with extra testing support for mockito mocks and spies
261   */
262  BeanScopeBuilder.ForTesting forTesting();
263
264  /**
265   * Build and return the bean scope.
266   * <p>
267   * The BeanScope is effectively immutable in that all components are created
268   * and all PostConstruct lifecycle methods have been invoked.
269   * <p>
270   * The beanScope effectively contains eager singletons.
271   *
272   * @return The BeanScope
273   */
274  BeanScope build();
275
276  /**
277   * Extends the building with testing specific support for mocks and spies.
278   */
279  interface ForTesting extends BeanScopeBuilder {
280
281    /**
282     * Use a mockito mock when injecting this bean type.
283     *
284     * <pre>{@code
285     *
286     *   try (BeanScope scope = BeanScope.builder()
287     *     .forTesting()
288     *     .mock(Pump.class)
289     *     .mock(Grinder.class)
290     *     .build()) {
291     *
292     *
293     *     CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class);
294     *     coffeeMaker.makeIt();
295     *
296     *     // this is a mockito mock
297     *     Grinder grinder = scope.get(Grinder.class);
298     *     verify(grinder).grindBeans();
299     *   }
300     *
301     * }</pre>
302     */
303    BeanScopeBuilder.ForTesting mock(Class<?> type);
304
305    /**
306     * Deprecated - migrate to mock().
307     */
308    @Deprecated
309    default BeanScopeBuilder.ForTesting withMock(Class<?> type) {
310      return  mock(type);
311    }
312
313    /**
314     * Register as a Mockito mock with a qualifier name.
315     *
316     * <pre>{@code
317     *
318     *   try (BeanScope scope = BeanScope.builder()
319     *     .forTesting()
320     *     .mock(Store.class, "red")
321     *     .mock(Store.class, "blue")
322     *     .build()) {
323     *
324     *     ...
325     *   }
326     *
327     * }</pre>
328     */
329    BeanScopeBuilder.ForTesting mock(Class<?> type, String name);
330
331    /**
332     * Deprecated - migrate to mock().
333     */
334    @Deprecated
335    default BeanScopeBuilder.ForTesting withMock(Class<?> type, String name) {
336      return mock(type, name);
337    }
338
339    /**
340     * Use a mockito mock when injecting this bean type additionally
341     * running setup on the mock instance.
342     *
343     * <pre>{@code
344     *
345     *   try (BeanScope scope = BeanScope.builder()
346     *     .forTesting()
347     *     .mock(Pump.class)
348     *     .mock(Grinder.class, grinder -> {
349     *
350     *       // setup the mock
351     *       when(grinder.grindBeans()).thenReturn("stub response");
352     *     })
353     *     .build()) {
354     *
355     *
356     *     CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class);
357     *     coffeeMaker.makeIt();
358     *
359     *     // this is a mockito mock
360     *     Grinder grinder = scope.get(Grinder.class);
361     *     verify(grinder).grindBeans();
362     *   }
363     *
364     * }</pre>
365     */
366    <D> BeanScopeBuilder.ForTesting mock(Class<D> type, Consumer<D> consumer);
367
368    /**
369     * Deprecated - migrate to mock().
370     */
371    @Deprecated
372    default <D> BeanScopeBuilder.ForTesting withMock(Class<D> type, Consumer<D> consumer) {
373      return mock(type, consumer);
374    }
375
376    /**
377     * Use a mockito spy when injecting this bean type.
378     *
379     * <pre>{@code
380     *
381     *   try (BeanScope scope = BeanScope.builder()
382     *     .forTesting()
383     *     .spy(Pump.class)
384     *     .build()) {
385     *
386     *     // setup spy here ...
387     *     Pump pump = scope.get(Pump.class);
388     *     doNothing().when(pump).pumpSteam();
389     *
390     *     // act
391     *     CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class);
392     *     coffeeMaker.makeIt();
393     *
394     *     verify(pump).pumpWater();
395     *     verify(pump).pumpSteam();
396     *   }
397     *
398     * }</pre>
399     */
400    BeanScopeBuilder.ForTesting spy(Class<?> type);
401
402    /**
403     * Deprecated - migrate to spy().
404     */
405    @Deprecated
406    default BeanScopeBuilder.ForTesting withSpy(Class<?> type) {
407      return spy(type);
408    }
409
410    /**
411     * Register a Mockito spy with a qualifier name.
412     *
413     * <pre>{@code
414     *
415     *   try (BeanScope scope = BeanScope.builder()
416     *     .forTesting()
417     *     .spy(Store.class, "red")
418     *     .spy(Store.class, "blue")
419     *     .build()) {
420     *
421     *     ...
422     *   }
423     *
424     * }</pre>
425     */
426    BeanScopeBuilder.ForTesting spy(Class<?> type, String name);
427
428    /**
429     * Deprecated - migrate to spy().
430     */
431    @Deprecated
432    default BeanScopeBuilder.ForTesting withSpy(Class<?> type, String name) {
433      return spy(type, name);
434    }
435
436    /**
437     * Use a mockito spy when injecting this bean type additionally
438     * running setup on the spy instance.
439     *
440     * <pre>{@code
441     *
442     *   try (BeanScope scope = BeanScope.builder()
443     *     .forTesting()
444     *     .spy(Pump.class, pump -> {
445     *       // setup the spy
446     *       doNothing().when(pump).pumpWater();
447     *     })
448     *     .build()) {
449     *
450     *     // or setup here ...
451     *     Pump pump = scope.get(Pump.class);
452     *     doNothing().when(pump).pumpSteam();
453     *
454     *     // act
455     *     CoffeeMaker coffeeMaker = scope.get(CoffeeMaker.class);
456     *     coffeeMaker.makeIt();
457     *
458     *     verify(pump).pumpWater();
459     *     verify(pump).pumpSteam();
460     *   }
461     *
462     * }</pre>
463     */
464    <D> BeanScopeBuilder.ForTesting spy(Class<D> type, Consumer<D> consumer);
465
466    /**
467     * Deprecated - migrate to spy().
468     */
469    @Deprecated
470    default <D> BeanScopeBuilder.ForTesting withSpy(Class<D> type, Consumer<D> consumer) {
471      return spy(type, consumer);
472    }
473
474  }
475}