View Javadoc
1   package com.guinetik.hexafun.hexa;
2   
3   import com.guinetik.hexafun.HexaApp;
4   import java.util.HashMap;
5   import java.util.Map;
6   import java.util.function.Function;
7   
8   /**
9    * Builder for creating HexaApp instances with use cases, ports, and adapters.
10   *
11   * <p>Supports fluent DSL with implicit closure - each useCase() call
12   * automatically closes the previous one.
13   *
14   * <p>Example:
15   * <pre class="language-java">{@code
16   * HexaFun.dsl()
17   *     .withPort(TaskRepository.class, new InMemoryTaskRepository())
18   *     .withAdapter(TO_DTO, task -> new TaskDTO(task.id(), task.title()))
19   *     .useCase(Keys.CREATE)
20   *         .validate(validator)
21   *         .handle(handler)
22   *     .useCase(Keys.DELETE)
23   *         .handle(deleteHandler)
24   *     .build();
25   * }</pre>
26   */
27  public class UseCaseBuilder {
28  
29      private final Map<String, UseCase<?, ?>> useCases = new HashMap<>();
30      private final Map<Class<?>, Object> ports = new HashMap<>();
31      private final Map<String, Function<?, ?>> adapters = new HashMap<>();
32  
33      // Pending registration - committed on next useCase() or build()
34      private String pendingName;
35      private UseCase<?, ?> pendingUseCase;
36  
37      /**
38       * Register a port (output adapter) by its type.
39       * Ports are registered when build() is called.
40       *
41       * <p>Example:
42       * <pre class="language-java">{@code
43       * HexaFun.dsl()
44       *     .withPort(TaskRepository.class, new InMemoryTaskRepository())
45       *     .withPort(EmailService.class, new SmtpEmailService())
46       *     .useCase(...)
47       *     .build();
48       * }</pre>
49       *
50       * @param type The interface/class type to register
51       * @param impl The implementation instance
52       * @param <T> The port type
53       * @return This builder for chaining
54       */
55      public <T> UseCaseBuilder withPort(Class<T> type, T impl) {
56          ports.put(type, impl);
57          return this;
58      }
59  
60      /**
61       * Register an adapter with a type-safe key.
62       * Adapters transform data from one type to another.
63       *
64       * <p>Example:
65       * <pre class="language-java">{@code
66       * HexaFun.dsl()
67       *     .withAdapter(TO_INVENTORY, req -> new InventoryCheck(req.itemId()))
68       *     .withAdapter(TO_PAYMENT, req -> new PaymentRequest(req.total()))
69       *     .useCase(...)
70       *     .build();
71       * }</pre>
72       *
73       * @param key The type-safe adapter key
74       * @param adapter The transformation function
75       * @param <From> The source type
76       * @param <To> The target type
77       * @return This builder for chaining
78       */
79      public <From, To> UseCaseBuilder withAdapter(
80          AdapterKey<From, To> key,
81          Function<From, To> adapter
82      ) {
83          adapters.put(key.name(), adapter);
84          return this;
85      }
86  
87      /**
88       * Start defining a use case with a type-safe key.
89       * Implicitly closes any previous use case definition.
90       *
91       * @param key The type-safe key for this use case
92       * @param <I> The input type of the use case
93       * @param <O> The output type of the use case
94       * @return A new UseCaseInputStep for chaining
95       */
96      public <I, O> UseCaseInputStep<I, O> useCase(UseCaseKey<I, O> key) {
97          commitPending();
98          return new UseCaseInputStep<>(key.name(), this);
99      }
100 
101     /**
102      * Stage a use case for registration. Will be committed on next useCase() or build().
103      */
104     <I, O> void stage(String name, UseCase<I, O> useCase) {
105         commitPending();
106         this.pendingName = name;
107         this.pendingUseCase = useCase;
108     }
109 
110     /**
111      * Commit pending use case to the registry.
112      */
113     private void commitPending() {
114         if (pendingName != null && pendingUseCase != null) {
115             useCases.put(pendingName, pendingUseCase);
116             pendingName = null;
117             pendingUseCase = null;
118         }
119     }
120 
121     /**
122      * Build a HexaApp with all the registered use cases and ports.
123      * @return A new HexaApp instance
124      */
125     public HexaApp build() {
126         commitPending();
127 
128         HexaApp app = HexaApp.create();
129 
130         // Register ports
131         for (Map.Entry<Class<?>, Object> entry : ports.entrySet()) {
132             registerPort(app, entry.getKey(), entry.getValue());
133         }
134 
135         // Register adapters
136         for (Map.Entry<String, Function<?, ?>> entry : adapters.entrySet()) {
137             registerAdapter(app, entry.getKey(), entry.getValue());
138         }
139 
140         // Register use cases
141         for (Map.Entry<String, UseCase<?, ?>> entry : useCases.entrySet()) {
142             registerUseCase(app, entry.getKey(), entry.getValue());
143         }
144 
145         return app;
146     }
147 
148     @SuppressWarnings({ "unchecked", "rawtypes" })
149     private void registerPort(HexaApp app, Class type, Object impl) {
150         app.port(type, impl);
151     }
152 
153     @SuppressWarnings({ "unchecked", "rawtypes" })
154     private void registerAdapter(HexaApp app, String name, Function adapter) {
155         app.withAdapter(name, adapter);
156     }
157 
158     @SuppressWarnings({ "unchecked", "rawtypes" })
159     private void registerUseCase(HexaApp app, String name, UseCase useCase) {
160         app.withUseCase(name, useCase);
161     }
162 }