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