##// END OF EJS Templates
WIP variant sources model
cin -
r40:924d9107c025 default
parent child
Show More
@@ -0,0 +1,29
1 package org.implab.gradle.common.core.lang;
2
3 import java.util.LinkedList;
4 import java.util.List;
5 import java.util.function.Consumer;
6
7 public final class Deferred<T> {
8 private final List<Consumer<T>> listeners = new LinkedList<>();
9 private T value;
10 private boolean resolved = false;
11
12 public void resolve(T value) {
13 if (resolved) {
14 throw new IllegalStateException("Already resolved");
15 }
16 this.value = value;
17 this.resolved = true;
18 listeners.forEach(listener -> listener.accept(value));
19 listeners.clear();
20 }
21
22 public void whenResolved(Consumer<T> listener) {
23 if (resolved) {
24 listener.accept(value);
25 } else {
26 listeners.add(listener);
27 }
28 }
29 }
@@ -0,0 +1,346
1 # Identity-First Model: Preserving Lazy Semantics Without Custom Events
2
3 In build and configuration systems, the same tension appears again and again:
4
5 * you want an **observable model** that consumers can subscribe to using something like `all(...)`
6 * you do **not** want that subscription to destroy laziness
7 * and you would prefer to avoid introducing a custom event bus, event ordering, and a separate lifecycle model
8
9 A practical way to solve this is to **separate identity from computed state**.
10
11 ## Core idea
12
13 The idea is simple:
14
15 * one object is responsible only for **identity** and minimal selection metadata
16 * the actual aggregate content is obtained through **separate API calls**
17 * those API calls may be lazy, expensive, cached, provider-based, or computed on demand
18
19 So instead of one β€œfat” object, we get two layers.
20
21 ### 1. Identity layer
22
23 Lightweight objects that:
24
25 * are cheap to create
26 * are effectively immutable
27 * are safe to observe eagerly
28 * work well as keys and subscription points
29
30 ### 2. State / aggregate access layer
31
32 Separate APIs that:
33
34 * resolve or compute content from identity
35 * do heavy work only when needed
36 * preserve lazy semantics where that actually matters
37
38 This is especially useful when a collection must be replayable, but should not drag expensive materialization along with it.
39
40 ---
41
42 ## The problem this solves
43
44 Consider a typical Gradle-like scenario.
45
46 An adapter wants to subscribe to a collection:
47
48 ```java
49 projections.getProjections().all(projection -> {
50 ...
51 });
52 ```
53
54 If `projection` is a heavy object that already contains:
55
56 * computed bindings
57 * providers of source sets
58 * derived state
59 * partially materialized objects
60
61 then `all(...)` starts forcing things that were supposed to stay lazy.
62
63 Typical symptoms:
64
65 * lazy semantics are weakened or broken
66 * coupling increases
67 * plugin application order starts to matter
68 * custom events begin to look tempting: `onCreated`, `onResolved`, `onMaterialized`
69
70 The problem is not that `all(...)` is bad.
71 The problem is that **too much meaning has been packed into the observable object**.
72
73 ---
74
75 ## The solution: observe identity, not state
76
77 If the observable collection contains only identity objects, the picture changes.
78
79 For example:
80
81 ```java
82 public interface SourceSetProjection extends Named {
83 }
84 ```
85
86 This object contains only identity:
87
88 * `name`
89 * perhaps a small amount of selection metadata
90 * but not heavy aggregate state
91
92 Now this subscription:
93
94 ```java
95 projections.getProjections().all(projection -> {
96 ...
97 });
98 ```
99
100 is no longer dangerous.
101 It eagerly materializes only cheap keys, not the full aggregate graph.
102
103 The actual content is requested separately:
104
105 ```java
106 Set<VariantLayerBinding> bindings = projections.getBindings(projection);
107 NamedDomainObjectProvider<GenericSourceSet> sourceSet =
108 materializer.getSourceSet(projection.getName());
109 ```
110
111 Those calls may remain:
112
113 * lazy
114 * computed
115 * cached
116 * tied to runtime lifecycle
117 * delegated to a dedicated materializer
118
119 ---
120
121 ## Example
122
123 ### A problematic design
124
125 Suppose we define projection like this:
126
127 ```java
128 public interface SourceSetProjection extends Named {
129 Set<VariantLayerBinding> getBindings();
130 NamedDomainObjectProvider<GenericSourceSet> getSourceSet();
131 }
132 ```
133
134 This is problematic because `SourceSetProjection` is no longer just identity. It is already close to an aggregate.
135
136 It mixes:
137
138 * symbolic identity
139 * relation data
140 * runtime references into a foreign domain
141
142 Subscribing via `all(...)` now risks pulling in much more than intended.
143
144 The type says β€œprojection”, but internally it already carries half the system.
145
146 ---
147
148 ### A cleaner design
149
150 Split responsibilities instead:
151
152 ```java
153 public interface SourceSetProjection extends Named {
154 }
155 ```
156
157 ```java
158 public interface SourceSetProjections {
159 NamedDomainObjectCollection<SourceSetProjection> getProjections();
160 Set<VariantLayerBinding> getBindings(String sourceSetName);
161 }
162 ```
163
164 ```java
165 public interface SourceSetMaterializer {
166 NamedDomainObjectProvider<GenericSourceSet> getSourceSet(String sourceSetName);
167 }
168 ```
169
170 Now the adapter flow looks like this:
171
172 ```java
173 projections.getProjections().all(projection -> {
174 Set<VariantLayerBinding> bindings =
175 projections.getBindings(projection.getName());
176
177 NamedDomainObjectProvider<GenericSourceSet> sourceSet =
178 materializer.getSourceSet(projection.getName());
179
180 // apply adapter-specific policy
181 });
182 ```
183
184 What changed:
185
186 * replayable subscription is preserved
187 * eager observation is acceptable because `SourceSetProjection` is cheap
188 * expensive and computed state has moved to separate APIs
189 * materialization remains under the control of a single owner
190
191 ---
192
193 ## Why this is often better than events
194
195 When identity and state are mixed together, people quickly start inventing events:
196
197 * `projectionCreated`
198 * `projectionResolved`
199 * `sourceSetAvailable`
200 * `sourceSetMaterialized`
201
202 That usually happens because it becomes important to know **when exactly** an object is β€œready enough”.
203
204 If the observable object contains only identity, and heavy state is obtained separately, then many of those events become unnecessary.
205
206 The architecture becomes calmer:
207
208 * an **identity registry**
209 * a **lookup API** for relations
210 * a **lazy materialization API** for heavy objects
211
212 Instead of saying:
213
214 > β€œWhen this object becomes sufficiently ready, I will react.”
215
216 you can say:
217
218 > β€œI can observe identity immediately, and ask for the expensive state only when I actually need it.”
219
220 This is easier to reason about, easier to test, and usually easier to evolve.
221
222 ---
223
224 ## What should live inside an identity object
225
226 An identity object does not have to be completely empty.
227 It may carry **selection metadata**, as long as that metadata is:
228
229 * cheap
230 * stable
231 * not expensive to initialize
232 * not turning the object into an aggregate
233
234 Typical examples:
235
236 * `id`
237 * `name`
238 * `kind`
239 * `type`
240 * domain key
241
242 What should usually stay out:
243
244 * computed aggregate content
245 * runtime references to foreign domains
246 * lazy providers of heavy objects
247 * derived state that can trigger premature materialization
248
249 A useful rule of thumb:
250
251 **An identity object contains selection metadata; aggregate content is obtained separately.**
252
253 ---
254
255 ## Why this works well with `all(...)`
256
257 `all(...)` weakens laziness only if the observed objects are themselves heavy or stateful.
258
259 If the observed objects are:
260
261 * cheap
262 * identity-only
263 * effectively immutable
264
265 then eager observation is usually acceptable.
266
267 So the real principle is:
268
269 **Eager observation of identity is often harmless.
270 Eager observation of computed state is not.**
271
272 That is why `all(...)` can be perfectly fine for collections of:
273
274 * `Variant`
275 * `Layer`
276 * `Role`
277 * `SourceSetProjection`
278
279 as long as those objects stay on the identity side of the boundary.
280
281 ---
282
283 ## Where this principle is especially useful
284
285 This approach is particularly effective when:
286
287 * there is replayable observation via `all(...)`
288 * identity objects are cheap and stable
289 * aggregate content may be expensive
290 * symbolic model and runtime model should remain separate
291 * you want to avoid building a custom event system
292
293 For Gradle-like models, this is often a very natural fit.
294
295 ---
296
297 ## When it is unnecessary
298
299 If an object is:
300
301 * small
302 * cheap
303 * and already fully represents its useful content
304
305 then splitting identity and state may be overengineering.
306
307 So this is not a universal rule. It is a tool to use when there is real tension between:
308
309 * key
310 * state
311 * computation
312 * runtime reference
313
314 ---
315
316 ## Practical conclusion
317
318 The principle can be summarized like this:
319
320 1. **Identity objects** hold only identity and cheap selection metadata.
321 2. **Aggregate content** is not stored inside them, but retrieved through separate API calls.
322 3. Those API calls may perform:
323
324 * lazy resolution
325 * caching
326 * heavy computation
327 * materialization on demand
328 4. This makes it possible to use replayable mechanisms such as `all(...)` without destroying laziness where laziness actually matters.
329
330 This is how you can combine:
331
332 * simple observation
333 * a clean model
334 * no custom event bus
335 * lazy materialization of heavy state
336
337 ---
338
339 # Short design note version
340
341 A concise version of the same principle:
342
343 > Use identity objects as cheap, observable keys.
344 > Keep expensive or computed aggregate content out of them.
345 > Resolve that content through separate APIs on demand.
346 > This allows replayable observation (`all(...)`) without forcing premature materialization, and often removes the need for a custom event model.
@@ -0,0 +1,306
1 # Identity-first model: ΠΊΠ°ΠΊ ΡΠΎΡ…Ρ€Π°Π½ΠΈΡ‚ΡŒ lazy semantics Π±Π΅Π· своих событий
2
3 Π’ систСмах ΠΊΠΎΠ½Ρ„ΠΈΠ³ΡƒΡ€Π°Ρ†ΠΈΠΈ ΠΈ сборки ΠΏΠΎΡ‡Ρ‚ΠΈ всСгда Π²ΠΎΠ·Π½ΠΈΠΊΠ°Π΅Ρ‚ ΠΎΠ΄Π½ΠΎ ΠΈ Ρ‚ΠΎ ΠΆΠ΅ напряТСниС:
4
5 * хочСтся ΠΈΠΌΠ΅Ρ‚ΡŒ **Π½Π°Π±Π»ΡŽΠ΄Π°Π΅ΠΌΡƒΡŽ модСль**, Π½Π° ΠΊΠΎΡ‚ΠΎΡ€ΡƒΡŽ ΠΌΠΎΠΆΠ½ΠΎ ΠΏΠΎΠ΄ΠΏΠΈΡΠ°Ρ‚ΡŒΡΡ Ρ‡Π΅Ρ€Π΅Π· Ρ‡Ρ‚ΠΎ-Ρ‚ΠΎ Π²Ρ€ΠΎΠ΄Π΅ `all(...)`
6 * Π½ΠΎ Π½Π΅ хочСтся, Ρ‡Ρ‚ΠΎΠ±Ρ‹ такая подписка **Π»ΠΎΠΌΠ°Π»Π° Π»Π΅Π½ΠΈΠ²ΠΎΡΡ‚ΡŒ**
7 * Π΅Ρ‰Ρ‘ мСньшС хочСтся Ρ‚Π°Ρ‰ΠΈΡ‚ΡŒ Π² Π°Ρ€Ρ…ΠΈΡ‚Π΅ΠΊΡ‚ΡƒΡ€Ρƒ ΡΠΎΠ±ΡΡ‚Π²Π΅Π½Π½ΡƒΡŽ ΡˆΠΈΠ½Ρƒ событий, порядок подписки ΠΈ ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹ΠΉ lifecycle
8
9 Один ΠΈΠ· Ρ€Π°Π±ΠΎΡ‡ΠΈΡ… способов Ρ€Π΅ΡˆΠΈΡ‚ΡŒ это β€” **Ρ€Π°Π·Π΄Π΅Π»ΠΈΡ‚ΡŒ identity ΠΈ вычисляСмоС состояниС**.
10
11 ## Π‘ΡƒΡ‚ΡŒ ΠΏΡ€ΠΈΠ½Ρ†ΠΈΠΏΠ°
12
13 ИдСя простая:
14
15 * ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹ΠΉ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ ΠΎΡ‚Π²Π΅Ρ‡Π°Π΅Ρ‚ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π·Π° **identity** ΠΈ ΠΌΠΈΠ½ΠΈΠΌΠ°Π»ΡŒΠ½Ρ‹Π΅ ΠΌΠ΅Ρ‚Π°Π΄Π°Π½Π½Ρ‹Π΅ Π²Ρ‹Π±ΠΎΡ€Π°
16 * всё ΡΠΎΠ΄Π΅Ρ€ΠΆΠ°Ρ‚Π΅Π»ΡŒΠ½ΠΎΠ΅ состояниС Π°Π³Ρ€Π΅Π³Π°Ρ‚Π° получаСтся **ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹ΠΌΠΈ Π²Ρ‹Π·ΠΎΠ²Π°ΠΌΠΈ API**
17 * эти Π²Ρ‹Π·ΠΎΠ²Ρ‹ ΡƒΠΆΠ΅ ΠΌΠΎΠ³ΡƒΡ‚ Π±Ρ‹Ρ‚ΡŒ lazy, Π΄ΠΎΡ€ΠΎΠ³ΠΈΠΌΠΈ, вычисляСмыми, ΠΊΡΡˆΠΈΡ€ΡƒΠ΅ΠΌΡ‹ΠΌΠΈ, ΠΏΡ€ΠΎΠ²Π°ΠΉΠ΄Π΅Ρ€Π½Ρ‹ΠΌΠΈ β€” ΠΊΠ°ΠΊΠΈΠΌΠΈ ΡƒΠ³ΠΎΠ΄Π½ΠΎ
18
19 Π’ΠΎ Π΅ΡΡ‚ΡŒ вмСсто ΠΎΠ΄Π½ΠΎΠ³ΠΎ β€œΡ‚ΠΎΠ»ΡΡ‚ΠΎΠ³ΠΎβ€ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Π° ΠΌΡ‹ ΠΏΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ Π΄Π²Π° слоя:
20
21 ### 1. Identity layer
22
23 Π›Ρ‘Π³ΠΊΠΈΠ΅ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Ρ‹, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅:
24
25 * Π΄Ρ‘ΡˆΠ΅Π²ΠΎ ΡΠΎΠ·Π΄Π°ΡŽΡ‚ΡΡ
26 * Π½Π΅ ΠΌΡƒΡ‚ΠΈΡ€ΡƒΡŽΡ‚
27 * бСзопасно Π½Π°Π±Π»ΡŽΠ΄Π°ΡŽΡ‚ΡΡ eagerly
28 * годятся ΠΊΠ°ΠΊ ΠΊΠ»ΡŽΡ‡ΠΈ ΠΈ Ρ‚ΠΎΡ‡ΠΊΠΈ подписки
29
30 ### 2. State / aggregate access layer
31
32 ΠžΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹Π΅ API, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹Π΅:
33
34 * ΠΏΠΎ identity находят ΠΈΠ»ΠΈ Π²Ρ‹Ρ‡ΠΈΡΠ»ΡΡŽΡ‚ содСрТимоС
35 * Π΄Π΅Π»Π°ΡŽΡ‚ heavy work Ρ‚ΠΎΠ»ΡŒΠΊΠΎ ΠΏΠΎ Ρ‚Ρ€Π΅Π±ΠΎΠ²Π°Π½ΠΈΡŽ
36 * ΠΌΠΎΠ³ΡƒΡ‚ ΡΠΎΡ…Ρ€Π°Π½ΡΡ‚ΡŒ Π»Π΅Π½ΠΈΠ²ΡƒΡŽ сСмантику
37
38 Π­Ρ‚ΠΎ особСнно ΠΏΠΎΠ»Π΅Π·Π½ΠΎ Ρ‚Π°ΠΌ, Π³Π΄Π΅ коллСкция Π΄ΠΎΠ»ΠΆΠ½Π° Π±Ρ‹Ρ‚ΡŒ replayable, Π½ΠΎ ΠΏΡ€ΠΈ этом Π½Π΅ Π΄ΠΎΠ»ΠΆΠ½Π° Ρ‚Π°Ρ‰ΠΈΡ‚ΡŒ Π·Π° собой Π΄ΠΎΡ€ΠΎΠ³ΡƒΡŽ ΠΌΠ°Ρ‚Π΅Ρ€ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΡŽ.
39
40 ---
41
42 ## ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΠ°, ΠΊΠΎΡ‚ΠΎΡ€ΡƒΡŽ это Ρ€Π΅ΡˆΠ°Π΅Ρ‚
43
44 Рассмотрим Ρ‚ΠΈΠΏΠΈΡ‡Π½ΡƒΡŽ ΡΠΈΡ‚ΡƒΠ°Ρ†ΠΈΡŽ Π² Gradle-ΠΏΠΎΠ΄ΠΎΠ±Π½ΠΎΠΉ ΠΌΠΎΠ΄Π΅Π»ΠΈ.
45
46 Π•ΡΡ‚ΡŒ коллСкция ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ΠΎΠ², Π½Π° ΠΊΠΎΡ‚ΠΎΡ€ΡƒΡŽ Π°Π΄Π°ΠΏΡ‚Π΅Ρ€ Ρ…ΠΎΡ‡Π΅Ρ‚ ΠΏΠΎΠ΄ΠΏΠΈΡΠ°Ρ‚ΡŒΡΡ:
47
48 ```java
49 projections.getProjections().all(projection -> {
50 ...
51 });
52 ```
53
54 Если `projection` β€” это тяТёлый ΠΎΠ±ΡŠΠ΅ΠΊΡ‚, Π²Π½ΡƒΡ‚Ρ€ΠΈ ΠΊΠΎΡ‚ΠΎΡ€ΠΎΠ³ΠΎ ΡƒΠΆΠ΅ Π»Π΅ΠΆΠ°Ρ‚:
55
56 * вычислСнныС bindings
57 * ΠΏΡ€ΠΎΠ²Π°ΠΉΠ΄Π΅Ρ€Ρ‹ Π½Π° source sets
58 * derived state
59 * ΠΏΠΎΠ»Ρƒ-ΠΌΠ°Ρ‚Π΅Ρ€ΠΈΠ°Π»ΠΈΠ·ΠΎΠ²Π°Π½Π½Ρ‹Π΅ сущности
60
61 Ρ‚ΠΎ `all(...)` Π½Π°Ρ‡ΠΈΠ½Π°Π΅Ρ‚ Ρ€Π°Π½ΠΎ Ρ€Π°ΡΠΊΡ€Ρ‹Π²Π°Ρ‚ΡŒ Ρ‚ΠΎ, Ρ‡Ρ‚ΠΎ Ρ…ΠΎΡ‚Π΅Π»ΠΎΡΡŒ ΠΎΡΡ‚Π°Π²ΠΈΡ‚ΡŒ Π»Π΅Π½ΠΈΠ²Ρ‹ΠΌ.
62
63 ΠŸΠΎΡΠ²Π»ΡΡŽΡ‚ΡΡ симптомы:
64
65 * ломаСтся lazy semantics
66 * растёт ΡΠ²ΡΠ·Π½ΠΎΡΡ‚ΡŒ
67 * становится Π²Π°ΠΆΠ΅Π½ порядок примСнСния ΠΏΠ»Π°Π³ΠΈΠ½ΠΎΠ²
68 * хочСтся Π·Π°Π²ΠΎΠ΄ΠΈΡ‚ΡŒ собствСнныС события: `onCreated`, `onResolved`, `onMaterialized`
69
70 АрхитСктура Π½Π°Ρ‡ΠΈΠ½Π°Π΅Ρ‚ ΡΠΊΡ€ΠΈΠΏΠ΅Ρ‚ΡŒ Π½Π΅ ΠΏΠΎΡ‚ΠΎΠΌΡƒ, Ρ‡Ρ‚ΠΎ `all(...)` ΠΏΠ»ΠΎΡ…, Π° ΠΏΠΎΡ‚ΠΎΠΌΡƒ Ρ‡Ρ‚ΠΎ **слишком ΠΌΠ½ΠΎΠ³ΠΎ смысла засунуто Π² Π½Π°Π±Π»ΡŽΠ΄Π°Π΅ΠΌΡ‹ΠΉ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚**.
71
72 ---
73
74 ## РСшСниС: Π½Π°Π±Π»ΡŽΠ΄Π°Ρ‚ΡŒ identity, Π° Π½Π΅ состояниС
75
76 Если ΡΠ΄Π΅Π»Π°Ρ‚ΡŒ Π½Π°Π±Π»ΡŽΠ΄Π°Π΅ΠΌΡ‹ΠΌΠΈ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ identity objects, ΠΊΠ°Ρ€Ρ‚ΠΈΠ½Π° мСняСтся.
77
78 НапримСр:
79
80 ```java
81 public interface SourceSetProjection extends Named {
82 }
83 ```
84
85 Π’Π°ΠΊΠΎΠΉ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ содСрТит Ρ‚ΠΎΠ»ΡŒΠΊΠΎ identity:
86
87 * `name`
88 * Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎ, Π΅Ρ‰Ρ‘ ΠΏΠ°Ρ€Ρƒ ΠΌΠ΅Ρ‚Π°Π΄Π°Π½Π½Ρ‹Ρ… Π²Ρ‹Π±ΠΎΡ€Π°
89 * Π½ΠΎ Π½Π΅ тяТёлоС содСрТимоС
90
91 Вогда подписка:
92
93 ```java
94 projections.getProjections().all(projection -> {
95 ...
96 });
97 ```
98
99 большС Π½Π΅ ΡΡ‚Ρ€Π°ΡˆΠ½Π°.
100 Она eagerly materializes Ρ‚ΠΎΠ»ΡŒΠΊΠΎ Π΄Π΅ΡˆΡ‘Π²Ρ‹Π΅ ΠΊΠ»ΡŽΡ‡ΠΈ, Π° Π½Π΅ вСсь Π°Π³Ρ€Π΅Π³Π°Ρ‚Π½Ρ‹ΠΉ Π³Ρ€Π°Ρ„.
101
102 Π‘ΠΎΠ΄Π΅Ρ€ΠΆΠΈΠΌΠΎΠ΅ Π·Π°ΠΏΡ€Π°ΡˆΠΈΠ²Π°Π΅Ρ‚ΡΡ ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½ΠΎ:
103
104 ```java
105 Set<VariantLayerBinding> bindings = projections.getBindings(projection);
106 NamedDomainObjectProvider<GenericSourceSet> sourceSet =
107 materializer.getSourceSet(projection.getName());
108 ```
109
110 И Π²ΠΎΡ‚ эти Π²Ρ‹Π·ΠΎΠ²Ρ‹ ΡƒΠΆΠ΅ ΠΌΠΎΠ³ΡƒΡ‚ Π±Ρ‹Ρ‚ΡŒ:
111
112 * lazy
113 * вычисляСмыми
114 * ΠΊΡΡˆΠΈΡ€ΡƒΠ΅ΠΌΡ‹ΠΌΠΈ
115 * привязанными ΠΊ runtime lifecycle
116
117 ---
118
119 ## ΠŸΡ€ΠΈΠΌΠ΅Ρ€
120
121 ### НСудачный Π²Π°Ρ€ΠΈΠ°Π½Ρ‚
122
123 ΠŸΡ€Π΅Π΄ΡΡ‚Π°Π²ΠΈΠΌ Ρ‚Π°ΠΊΠΎΠΉ интСрфСйс:
124
125 ```java
126 public interface SourceSetProjection extends Named {
127 Set<VariantLayerBinding> getBindings();
128 NamedDomainObjectProvider<GenericSourceSet> getSourceSet();
129 }
130 ```
131
132 ΠŸΡ€ΠΎΠ±Π»Π΅ΠΌΡ‹ Ρ‚ΡƒΡ‚ сразу Π²ΠΈΠ΄Π½Ρ‹:
133
134 * `SourceSetProjection` ΡƒΠΆΠ΅ Π½Π΅ identity object, Π° ΠΏΠΎΡ‡Ρ‚ΠΈ Π°Π³Ρ€Π΅Π³Π°Ρ‚
135 * Π²Π½ΡƒΡ‚Ρ€ΠΈ ΡΠΌΠ΅ΡˆΠ°Π½Ρ‹:
136
137 * symbolic identity
138 * relation data
139 * runtime reference Π² Ρ‡ΡƒΠΆΠΎΠΉ Π΄ΠΎΠΌΠ΅Π½
140 * подписка Ρ‡Π΅Ρ€Π΅Π· `all(...)` Π½Π°Ρ‡ΠΈΠ½Π°Π΅Ρ‚ Ρ‚Π°Ρ‰ΠΈΡ‚ΡŒ Π·Π° собой большС, Ρ‡Π΅ΠΌ Ρ…ΠΎΡ‚Π΅Π»ΠΎΡΡŒ Π±Ρ‹
141
142 На словах ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ называСтся β€œprojection”, Π° ΠΏΠΎ Ρ„Π°ΠΊΡ‚Ρƒ Π²Π½ΡƒΡ‚Ρ€ΠΈ Ρƒ Π½Π΅Π³ΠΎ ΡƒΠΆΠ΅ полсистСмы.
143
144 ---
145
146 ### Π‘ΠΎΠ»Π΅Π΅ ΡƒΠ΄Π°Ρ‡Π½Ρ‹ΠΉ Π²Π°Ρ€ΠΈΠ°Π½Ρ‚
147
148 РаздСляСм ΠΎΡ‚Π²Π΅Ρ‚ΡΡ‚Π²Π΅Π½Π½ΠΎΡΡ‚ΡŒ:
149
150 ```java
151 public interface SourceSetProjection extends Named {
152 }
153 ```
154
155 ```java
156 public interface SourceSetProjections {
157 NamedDomainObjectCollection<SourceSetProjection> getProjections();
158 Set<VariantLayerBinding> getBindings(String sourceSetName);
159 }
160 ```
161
162 ```java
163 public interface SourceSetMaterializer {
164 NamedDomainObjectProvider<GenericSourceSet> getSourceSet(String sourceSetName);
165 }
166 ```
167
168 Π’Π΅ΠΏΠ΅Ρ€ΡŒ сцСнарий Π°Π΄Π°ΠΏΡ‚Π΅Ρ€Π° выглядит Ρ‚Π°ΠΊ:
169
170 ```java
171 projections.getProjections().all(projection -> {
172 Set<VariantLayerBinding> bindings =
173 projections.getBindings(projection.getName());
174
175 NamedDomainObjectProvider<GenericSourceSet> sourceSet =
176 materializer.getSourceSet(projection.getName());
177
178 // apply adapter-specific policy
179 });
180 ```
181
182 Π§Ρ‚ΠΎ измСнилось:
183
184 * replayable подписка ΡΠΎΡ…Ρ€Π°Π½ΠΈΠ»Π°ΡΡŒ
185 * eager materialization допустима, ΠΏΠΎΡ‚ΠΎΠΌΡƒ Ρ‡Ρ‚ΠΎ `SourceSetProjection` Π΄Π΅ΡˆΡ‘Π²Ρ‹ΠΉ
186 * Π΄ΠΎΡ€ΠΎΠ³ΠΎΠ΅ ΠΈ вычисляСмоС состояниС ΡƒΡˆΠ»ΠΎ Π² ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹Π΅ API
187 * materialization остаётся ΠΏΠΎΠ΄ ΠΊΠΎΠ½Ρ‚Ρ€ΠΎΠ»Π΅ΠΌ ΠΎΠ΄Π½ΠΎΠ³ΠΎ Π²Π»Π°Π΄Π΅Π»ΡŒΡ†Π°
188
189 ---
190
191 ## ΠŸΠΎΡ‡Π΅ΠΌΡƒ это Π»ΡƒΡ‡ΡˆΠ΅ событий
192
193 Когда Π² ΠΌΠΎΠ΄Π΅Π»ΠΈ ΡΠΌΠ΅ΡˆΠ°Π½Ρ‹ identity ΠΈ состояниС, ΠΎΡ‡Π΅Π½ΡŒ быстро хочСтся ΠΈΠ·ΠΎΠ±Ρ€Π΅Ρ‚Π°Ρ‚ΡŒ события:
194
195 * `projectionCreated`
196 * `projectionResolved`
197 * `sourceSetAvailable`
198 * `sourceSetMaterialized`
199
200 ΠŸΠΎΡ‚ΠΎΠΌΡƒ Ρ‡Ρ‚ΠΎ Π² ΠΊΠ°ΠΊΠΎΠΉ-Ρ‚ΠΎ ΠΌΠΎΠΌΠ΅Π½Ρ‚ становится Π²Π°ΠΆΠ½ΠΎ, **ΠΊΠΎΠ³Π΄Π° ΠΈΠΌΠ΅Π½Π½ΠΎ** ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ ΡƒΠΆΠ΅ β€œΠ΄ΠΎΡΡ‚Π°Ρ‚ΠΎΡ‡Π½ΠΎ готов”.
201
202 Если ΠΆΠ΅ Π½Π°Π±Π»ΡŽΠ΄Π°Π΅ΠΌΡ‹ΠΉ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ содСрТит Ρ‚ΠΎΠ»ΡŒΠΊΠΎ identity, Π° всё тяТёлоС получаСтся ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹ΠΌΠΈ Π²Ρ‹Π·ΠΎΠ²Π°ΠΌΠΈ, событийная модСль часто Π²ΠΎΠΎΠ±Ρ‰Π΅ Π½Π΅ Π½ΡƒΠΆΠ½Π°.
203
204 ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅Ρ‚ΡΡ Π±ΠΎΠ»Π΅Π΅ спокойная схСма:
205
206 * Π΅ΡΡ‚ΡŒ **identity registry**
207 * Π΅ΡΡ‚ΡŒ **lookup API** для связСй
208 * Π΅ΡΡ‚ΡŒ **lazy materialization API** для тяТёлых сущностСй
209
210 Π’ΠΎ Π΅ΡΡ‚ΡŒ вмСсто событий:
211
212 > β€œΠΊΠΎΠ³Π΄Π° ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ станСт достаточно Π³ΠΎΡ‚ΠΎΠ², я Ρ‡Ρ‚ΠΎ-Ρ‚ΠΎ ΡΠ΄Π΅Π»Π°ΡŽβ€
213
214 получаСтся ΠΎΠ±Ρ‹Ρ‡Π½Ρ‹ΠΉ ΠΈ понятный flow:
215
216 > β€œΡ Π²ΠΈΠΆΡƒ identity, Π° Π½ΡƒΠΆΠ½ΠΎΠ΅ состояниС ΡΠΏΡ€ΠΎΡˆΡƒ ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½ΠΎ, ΠΊΠΎΠ³Π΄Π° ΠΎΠ½ΠΎ ΠΌΠ½Π΅ Π΄Π΅ΠΉΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ понадобится”
217
218 Π­Ρ‚ΠΎ ΠΏΡ€ΠΎΡ‰Π΅ ΠΈ для reasoning, ΠΈ для тСстирования, ΠΈ для ΡΠ²ΠΎΠ»ΡŽΡ†ΠΈΠΈ API.
219
220 ---
221
222 ## Π§Ρ‚ΠΎ ΠΈΠΌΠ΅Π½Π½ΠΎ Π΄ΠΎΠ»ΠΆΠ½ΠΎ ΠΆΠΈΡ‚ΡŒ Π² identity object
223
224 Identity object Π½Π΅ обязан Π±Ρ‹Ρ‚ΡŒ β€œΠ°Π±ΡΠΎΠ»ΡŽΡ‚Π½ΠΎ пустым”.
225 Он ΠΌΠΎΠΆΠ΅Ρ‚ ΡΠΎΠ΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ **ΠΌΠ΅Ρ‚Π°Π΄Π°Π½Π½Ρ‹Π΅ Π²Ρ‹Π±ΠΎΡ€Π°**, Ссли ΠΎΠ½ΠΈ:
226
227 * Π΄Π΅ΡˆΠ΅Π²Ρ‹
228 * ΡΡ‚Π°Π±ΠΈΠ»ΡŒΠ½Ρ‹
229 * Π½Π΅ Ρ‚Ρ€Π΅Π±ΡƒΡŽΡ‚ тяТёлой ΠΈΠ½ΠΈΡ†ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΠΈ
230 * Π½Π΅ ΠΏΡ€Π΅Π²Ρ€Π°Ρ‰Π°ΡŽΡ‚ ΠΎΠ±ΡŠΠ΅ΠΊΡ‚ Π² Π°Π³Ρ€Π΅Π³Π°Ρ‚
231
232 НапримСр:
233
234 * `id`
235 * `name`
236 * `kind`
237 * `domain key`
238 * maybe `type`
239
240 Но ΠΎΠ½ Π½Π΅ Π΄ΠΎΠ»ΠΆΠ΅Π½ ΡΠΎΠ΄Π΅Ρ€ΠΆΠ°Ρ‚ΡŒ:
241
242 * вычисляСмоС содСрТимоС Π°Π³Ρ€Π΅Π³Π°Ρ‚Π°
243 * ссылки Π²Ρ€Π΅ΠΌΠ΅Π½ΠΈ выполнСния Π½Π° Ρ‡ΡƒΠΆΠΈΠ΅ Π΄ΠΎΠΌΠ΅Π½Ρ‹
244 * lazy providers Π½Π° heavy objects
245 * derived state, ΠΊΠΎΡ‚ΠΎΡ€Ρ‹ΠΉ ΠΌΠΎΠΆΠ΅Ρ‚ ΠΏΡ€ΠΎΠ²ΠΎΡ†ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ Ρ€Π°Π½Π½ΡŽΡŽ ΠΌΠ°Ρ‚Π΅Ρ€ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΡŽ
246
247 Π₯ΠΎΡ€ΠΎΡˆΠ°Ρ практичСская Ρ„ΠΎΡ€ΠΌΡƒΠ»Π°:
248
249 **identity object contains selection metadata; aggregate content is obtained separately.**
250
251 ---
252
253 ## Когда этот ΠΏΡ€ΠΈΠ½Ρ†ΠΈΠΏ особСнно ΠΏΠΎΠ»Π΅Π·Π΅Π½
254
255 Он особСнно Ρ…ΠΎΡ€ΠΎΡˆ, Ссли:
256
257 * Π΅ΡΡ‚ΡŒ replayable наблюдСниС Ρ‡Π΅Ρ€Π΅Π· `all(...)`
258 * identity-ΠΎΠ±ΡŠΠ΅ΠΊΡ‚Ρ‹ Π΄Π΅ΡˆΡ‘Π²Ρ‹Π΅ ΠΈ ΠΏΠΎΡ‡Ρ‚ΠΈ immutable
259 * содСрТимоС Π°Π³Ρ€Π΅Π³Π°Ρ‚Π° ΠΌΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ Π΄ΠΎΡ€ΠΎΠ³ΠΈΠΌ
260 * Π½ΡƒΠΆΠ΅Π½ clean split ΠΌΠ΅ΠΆΠ΄Ρƒ symbolic model ΠΈ runtime model
261 * хочСтся ΠΈΠ·Π±Π΅ΠΆΠ°Ρ‚ΡŒ собствСнной событийной ΡˆΠΈΠ½Ρ‹
262
263 Для Gradle-ΠΏΠΎΠ΄ΠΎΠ±Π½Ρ‹Ρ… ΠΌΠΎΠ΄Π΅Π»Π΅ΠΉ это Π²ΠΎΠΎΠ±Ρ‰Π΅ ΠΎΡ‡Π΅Π½ΡŒ СстСствСнный ΠΏΡ€ΠΈΡ‘ΠΌ.
264
265 ---
266
267 ## Когда ΠΎΠ½ Π½Π΅ Π½ΡƒΠΆΠ΅Π½
268
269 Если ΠΎΠ±ΡŠΠ΅ΠΊΡ‚:
270
271 * малСнький
272 * Π΄Π΅ΡˆΡ‘Π²Ρ‹ΠΉ
273 * ΡƒΠΆΠ΅ сам ΠΏΠΎ сСбС ΠΈ Π΅ΡΡ‚ΡŒ всё Π΅Π³ΠΎ содСрТимоС
274
275 Ρ‚ΠΎ Ρ€Π°Π·Π΄Π΅Π»Π΅Π½ΠΈΠ΅ Π½Π° identity ΠΈ ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹Π΅ lookup API ΠΌΠΎΠΆΠ΅Ρ‚ Π±Ρ‹Ρ‚ΡŒ лишним.
276
277 Π’ΠΎ Π΅ΡΡ‚ΡŒ этот ΠΏΡ€ΠΈΠ½Ρ†ΠΈΠΏ ΠΏΠΎΠ»Π΅Π·Π΅Π½ Π½Π΅ ΠΊΠ°ΠΊ Π΄ΠΎΠ³ΠΌΠ°, Π° ΠΊΠ°ΠΊ инструмСнт.
278 Π•Π³ΠΎ стоит ΠΏΡ€ΠΈΠΌΠ΅Π½ΡΡ‚ΡŒ Ρ‚Π°ΠΌ, Π³Π΄Π΅ Ρ€Π΅Π°Π»ΡŒΠ½ΠΎ Π΅ΡΡ‚ΡŒ риск смСшСния:
279
280 * ΠΊΠ»ΡŽΡ‡Π°
281 * состояния
282 * вычислСния
283 * runtime reference
284
285 ---
286
287 ## ΠŸΡ€Π°ΠΊΡ‚ΠΈΡ‡Π΅ΡΠΊΠΈΠΉ ΠΈΡ‚ΠΎΠ³
288
289 Если ΡΡ„ΠΎΡ€ΠΌΡƒΠ»ΠΈΡ€ΠΎΠ²Π°Ρ‚ΡŒ ΠΊΠΎΡ€ΠΎΡ‚ΠΊΠΎ, Ρ‚ΠΎ ΠΏΡ€ΠΈΠ½Ρ†ΠΈΠΏ Ρ‚Π°ΠΊΠΎΠΉ:
290
291 1. **Identity objects** содСрТат Ρ‚ΠΎΠ»ΡŒΠΊΠΎ identity ΠΈ Π΄Π΅ΡˆΡ‘Π²Ρ‹Π΅ ΠΌΠ΅Ρ‚Π°Π΄Π°Π½Π½Ρ‹Π΅ Π²Ρ‹Π±ΠΎΡ€Π°.
292 2. **АгрСгатноС содСрТимоС** Π½Π΅ хранится Π²Π½ΡƒΡ‚Ρ€ΠΈ Π½ΠΈΡ…, Π° получаСтся ΠΎΡ‚Π΄Π΅Π»ΡŒΠ½Ρ‹ΠΌΠΈ API-Π²Ρ‹Π·ΠΎΠ²Π°ΠΌΠΈ.
293 3. Π­Ρ‚ΠΈ API ΡƒΠΆΠ΅ ΠΌΠΎΠ³ΡƒΡ‚ Π²Ρ‹ΠΏΠΎΠ»Π½ΡΡ‚ΡŒ:
294
295 * lazy resolution
296 * ΠΊΡΡˆΠΈΡ€ΠΎΠ²Π°Π½ΠΈΠ΅
297 * heavy computation
298 * materialization
299 4. Благодаря этому ΠΌΠΎΠΆΠ½ΠΎ бСзопасно ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ replayable ΠΌΠ΅Ρ…Π°Π½ΠΈΠ·ΠΌΡ‹ Π²Ρ€ΠΎΠ΄Π΅ `all(...)`, Π½Π΅ Ρ€Π°Π·Ρ€ΡƒΡˆΠ°Ρ Π»Π΅Π½ΠΈΠ²ΡƒΡŽ сСмантику Ρ‚Π°ΠΌ, Π³Π΄Π΅ ΠΎΠ½Π° Π΄Π΅ΠΉΡΡ‚Π²ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎ Π²Π°ΠΆΠ½Π°.
300
301 ИмСнно Ρ‚Π°ΠΊ удаётся ΡΠΎΠ²ΠΌΠ΅ΡΡ‚ΠΈΡ‚ΡŒ:
302
303 * простоС наблюдСниС
304 * Ρ‡ΠΈΡΡ‚ΡƒΡŽ модСль
305 * отсутствиС собствСнных событий
306 * Π»Π΅Π½ΠΈΠ²ΡƒΡŽ ΠΌΠ°Ρ‚Π΅Ρ€ΠΈΠ°Π»ΠΈΠ·Π°Ρ†ΠΈΡŽ тяТёлого состояния
@@ -0,0 +1,185
1 package org.implab.gradle.variants;
2
3 import java.util.ArrayList;
4 import java.util.HashMap;
5 import java.util.HashSet;
6 import java.util.List;
7 import java.util.Map;
8 import java.util.Set;
9 import java.util.function.Consumer;
10 import java.util.stream.Stream;
11
12 import org.eclipse.jdt.annotation.NonNullByDefault;
13 import org.gradle.api.Action;
14 import org.gradle.api.NamedDomainObjectCollection;
15 import org.gradle.api.NamedDomainObjectContainer;
16 import org.gradle.api.NamedDomainObjectProvider;
17 import org.gradle.api.Plugin;
18 import org.gradle.api.Project;
19 import org.implab.gradle.common.core.lang.Deferred;
20 import org.implab.gradle.common.core.lang.Strings;
21 import org.implab.gradle.common.sources.GenericSourceSet;
22 import org.implab.gradle.common.sources.SourcesPlugin;
23 import org.implab.gradle.variants.model.Layer;
24 import org.implab.gradle.variants.model.Variant;
25 import org.implab.gradle.variants.model.VariantsExtension;
26 import org.implab.gradle.variants.sources.LayerProjectionRule;
27 import org.implab.gradle.variants.sources.SourceSetMaterializer;
28 import org.implab.gradle.variants.sources.SourceSetProjection;
29 import org.implab.gradle.variants.sources.SourceSetProjections;
30 import org.implab.gradle.variants.sources.VariantLayerBinding;
31 import org.implab.gradle.variants.sources.VariantSourcesContext;
32 import org.implab.gradle.variants.sources.VariantSourcesExtension;
33
34 @NonNullByDefault
35 public abstract class VariantSourcesPlugin implements Plugin<Project> {
36 @Override
37 public void apply(Project target) {
38 // Apply the main VariantsPlugin to ensure the core variant model is available.
39 target.getPlugins().apply(VariantsPlugin.class);
40 target.getPlugins().apply(SourcesPlugin.class);
41 // Access the VariantsExtension to configure variant sources.
42 var variantsExtension = target.getExtensions().getByType(VariantsExtension.class);
43 var objectFactory = target.getObjects();
44
45 var sources = SourcesPlugin.getSourcesExtension(target);
46
47 var deferred = new Deferred<VariantSourcesContext>();
48 var layerProjectionRules = objectFactory.domainObjectContainer(LayerProjectionRule.class);
49
50 var variantSourcesExtension = new VariantSourcesExtension() {
51 @Override
52 public NamedDomainObjectContainer<LayerProjectionRule> getLayerRules() {
53 return layerProjectionRules;
54 }
55
56 @Override
57 public void whenFinalized(Action<? super VariantSourcesContext> action) {
58 deferred.whenResolved(action::execute);
59 }
60 };
61 target.getExtensions().add(VariantSourcesExtension.class, "variantSources", variantSourcesExtension);
62
63 // create convention to automatically create layer projection rules for each
64 // variant layer
65 variantsExtension.getLayers().all(layer -> {
66 // Automatically create a layer projection rule for each variant layer.
67 variantSourcesExtension.layer(layer.getName(), rule -> {
68 // Configure the source set name pattern based on the layer name.
69 rule.getSourceSetNamePattern()
70 .convention("{variant}{layerCapitalized}")
71 .finalizeValueOnRead();
72 });
73 });
74
75 var projections = objectFactory.domainObjectContainer(SourceSetProjection.class);
76
77 Map<String, Set<VariantLayerBinding>> projectionBindings = new HashMap<>();
78
79 var sourceSetProjections = new SourceSetProjections() {
80 @Override
81 public NamedDomainObjectCollection<SourceSetProjection> getProjections() {
82 return projections;
83 }
84
85 @Override
86 public Set<VariantLayerBinding> getBindings(String sourceSetName) {
87 return projectionBindings.getOrDefault(sourceSetName, Set.of());
88 }
89
90 @Override
91 public Set<VariantLayerBinding> getBindings(SourceSetProjection projection) {
92 return getBindings(projection.getName());
93 }
94 };
95
96 Set<String> materializedSourceSets = new HashSet<>();
97
98 var materializer = new SourceSetMaterializer() {
99 @Override
100 public NamedDomainObjectProvider<GenericSourceSet> getSourceSet(String sourceSetName) {
101 return materializedSourceSets.add(sourceSetName)
102 ? sources.register(sourceSetName)
103 : sources.named(sourceSetName);
104 }
105 };
106
107 var bindings = new VariantBindings();
108
109 target.afterEvaluate(t -> {
110 // Once the project is evaluated, resolve the deferred context and finalize the
111 // sources configuration.
112 variantsExtension.getLayers().all(bindings::addLayer);
113 variantsExtension.getVariants().all(bindings::addVariant);
114
115 variantsExtension.getLayers().all(layer -> {
116 // For each layer, apply the projection rules to generate source set projections.
117
118 var rule = layerProjectionRules.maybeCreate(layer.getName());
119 var pattern = rule.getSourceSetNamePattern().getOrElse("{variant}{layerCapitalized}");
120 // Generate source set names based on the pattern and variant/layer information.
121 // This is a simplified example; real implementation would need to consider
122 // all variants and layers.
123 var sourceSetName = pattern.replace("{layer}", layer.getName())
124 .replace("{variant}", "main") // Placeholder for actual variant name
125 .replace("{layerCapitalized}", Strings.capitalize(layer.getName()));
126
127 var projection = objectFactory.newInstance(SourceSetProjection.class, sourceSetName);
128 projections.add(projection);
129 // Bind the projection to the corresponding variant layer.
130 projectionBindings.computeIfAbsent(sourceSetName, k -> new HashSet<>())
131 .add(new VariantLayerBinding(layer.getName(), projection));
132 });
133
134 var context = new VariantSourcesContext() {
135
136 @Override
137 public SourceSetProjections getProjections() {
138 return sourceSetProjections;
139 }
140
141 @Override
142 public SourceSetMaterializer getMaterializer() {
143 return materializer;
144 }
145
146 // Implementation of the context that provides access to variant and layer
147 // information.
148 };
149 deferred.resolve(context);
150 });
151 }
152
153 class VariantBindings {
154 private final Set<Layer> layers = new HashSet<>();
155 private final Set<Variant> variants = new HashSet<>();
156
157 private final List<Consumer<? super VariantLayerBinding>> listeners = new ArrayList<>();
158
159 void addLayer(Layer layer) {
160 layers.add(layer);
161 variants.stream()
162 .map(variant -> VariantLayerBinding.of(variant, layer))
163 .forEach(this::notifyBindingAdded);
164 }
165
166 void addVariant(Variant variant) {
167 variants.add(variant);
168 layers.stream()
169 .map(layer -> VariantLayerBinding.of(variant, layer))
170 .forEach(this::notifyBindingAdded);
171 }
172
173 void whenBindingAdded(Consumer<? super VariantLayerBinding> listener) {
174 layers.stream()
175 .flatMap(layer -> variants.stream().map(variant -> VariantLayerBinding.of(variant, layer)))
176 .forEach(listener);
177 listeners.add(listener);
178 }
179
180 private void notifyBindingAdded(VariantLayerBinding binding) {
181 listeners.forEach(listener -> listener.accept(binding));
182 }
183 }
184
185 }
@@ -0,0 +1,70
1 package org.implab.gradle.variants.model;
2
3 import java.util.HashSet;
4 import java.util.Set;
5 import java.util.function.Consumer;
6 import java.util.stream.Stream;
7
8 import org.gradle.api.Action;
9 import org.gradle.api.Named;
10 import org.gradle.api.provider.SetProperty;
11 import org.implab.gradle.common.core.lang.Closures;
12 import org.implab.gradle.common.core.lang.Strings;
13
14 import groovy.lang.Closure;
15
16 public interface VariantDefinition extends Named {
17 /**
18 * Role bindings declared inside this variant.
19 *
20 * The binding pair of role and layer names.
21 */
22 SetProperty<RoleLayerBinding> getRoleBindings();
23
24 /**
25 * Creates or returns an existing role binding and configures it.
26 */
27 default void role(String name, Action<? super RoleSpec> action) {
28 var spec = new RoleSpec(name);
29 action.execute(spec);
30 spec.accept(getRoleBindings()::add);
31 }
32
33 default void role(String name, Closure<?> closure) {
34 role(name, Closures.action(closure));
35 }
36
37 default void finalizeVariant() {
38 getRoleBindings().finalizeValue();
39 }
40
41 public static class RoleSpec {
42 private final String name;
43 private final Set<String> layerNames;
44
45 public RoleSpec(String name) {
46 this.name = name;
47 this.layerNames = new HashSet<>();
48 }
49
50 public String getName() {
51 return name;
52 }
53
54 public Set<String> getLayerNames() {
55 return layerNames;
56 }
57
58 public void layers(String name, String... extraNames) {
59 Stream.concat(Stream.of(name), Stream.of(extraNames))
60 .map(Strings::requireNonBlank)
61 .forEach(this.layerNames::add);
62 }
63
64 void accept(Consumer<? super RoleLayerBinding> consumer) {
65 layerNames.stream()
66 .map(layerName -> new RoleLayerBinding(name, layerName))
67 .forEach(consumer);
68 }
69 }
70 }
@@ -0,0 +1,26
1 package org.implab.gradle.variants.sources;
2
3 import org.gradle.api.Action;
4 import org.gradle.api.Named;
5 import org.gradle.api.provider.Property;
6
7 /**
8 * Projection rule for a layer.
9 */
10 public interface LayerProjectionRule extends Named {
11
12 /**
13 * Pattern used to calculate the source set name.
14 * Examples:
15 * "{layer}"
16 * "{variant}{layerCapitalized}"
17 */
18 Property<String> getSourceSetNamePattern();
19
20 /**
21 * Optional hook for future extension.
22 */
23 default void configure(Action<? super LayerProjectionRule> action) {
24 action.execute(this);
25 }
26 } No newline at end of file
@@ -0,0 +1,15
1 package org.implab.gradle.variants.sources;
2
3 import org.gradle.api.NamedDomainObjectProvider;
4 import org.implab.gradle.common.sources.GenericSourceSet;
5
6 /**
7 * Materializes symbolic source set names into actual GenericSourceSet instances.
8 */
9 public interface SourceSetMaterializer {
10 NamedDomainObjectProvider<GenericSourceSet> getSourceSet(String sourceSetName);
11
12 default NamedDomainObjectProvider<GenericSourceSet> getSourceSet(SourceSetProjection projection) {
13 return getSourceSet(projection.getName());
14 }
15 } No newline at end of file
@@ -0,0 +1,10
1 package org.implab.gradle.variants.sources;
2
3 import org.gradle.api.Named;
4
5 /**
6 * Represents a projected source set. This is an identity object and doesn't contain any state.
7 * The name of the projection is used as the source set name by the {@link SourceSetMaterializer}.
8 */
9 public interface SourceSetProjection extends Named {
10 }
@@ -0,0 +1,28
1 package org.implab.gradle.variants.sources;
2
3 import java.util.Set;
4
5 import org.gradle.api.NamedDomainObjectCollection;
6
7 /**
8 * Registry of symbolic source set names produced by sources projection.
9 *
10 * Identity in this registry is the GenericSourceSet name.
11 */
12 public interface SourceSetProjections {
13
14 /**
15 * Returns all source set projections. This is a separate
16 */
17 NamedDomainObjectCollection<SourceSetProjection> getProjections();
18
19 /**
20 * Returns all logical bindings projected into the given source set name.
21 */
22 Set<VariantLayerBinding> getBindings(String sourceSetName);
23
24 /**
25 * Returns all logical bindings projected into the given source set name.
26 */
27 Set<VariantLayerBinding> getBindings(SourceSetProjection projection);
28 } No newline at end of file
@@ -0,0 +1,27
1 package org.implab.gradle.variants.sources;
2
3 import org.implab.gradle.variants.model.Layer;
4 import org.implab.gradle.variants.model.Variant;
5
6 /**
7 * Logical usage of a layer inside a variant.
8 * Identity: (variantName, layerName)
9 */
10 public interface VariantLayerBinding {
11 Variant getVariant();
12 Layer getLayer();
13
14 public static VariantLayerBinding of(Variant variant, Layer layer) {
15 return new VariantLayerBinding() {
16 @Override
17 public Variant getVariant() {
18 return variant;
19 }
20
21 @Override
22 public Layer getLayer() {
23 return layer;
24 }
25 };
26 }
27 } No newline at end of file
@@ -0,0 +1,7
1 package org.implab.gradle.variants.sources;
2
3 public interface VariantSourcesContext {
4 SourceSetProjections getProjections();
5
6 SourceSetMaterializer getMaterializer();
7 }
@@ -0,0 +1,37
1 package org.implab.gradle.variants.sources;
2
3 import org.gradle.api.Action;
4 import org.gradle.api.NamedDomainObjectContainer;
5 import org.implab.gradle.common.core.lang.Closures;
6
7 import groovy.lang.Closure;
8
9 public interface VariantSourcesExtension {
10
11 /**
12 * Projection rules keyed by layer name.
13 */
14 NamedDomainObjectContainer<LayerProjectionRule> getLayerRules();
15
16 /**
17 * Creates or returns an existing layer rule and configures it.
18 */
19 default LayerProjectionRule layerRule(String name) {
20 return getLayerRules().maybeCreate(name);
21 }
22
23 /**
24 * Creates or returns an existing layer rule and configures it.
25 */
26 default LayerProjectionRule layer(String name, Action<? super LayerProjectionRule> action) {
27 LayerProjectionRule rule = layerRule(name);
28 action.execute(rule);
29 return rule;
30 }
31
32 void whenFinalized(Action<? super VariantSourcesContext> action);
33
34 default void whenFinalized(Closure<?> closure) {
35 whenFinalized(Closures.action(closure));
36 }
37 } No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now