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 }