##// END OF EJS Templates
WIP Variants model
cin -
r39:fdd8b56c7024 default
parent child
Show More
@@ -1,8 +1,9
1 1 {
2 2 "java.configuration.updateBuildConfiguration": "automatic",
3 3 "java.compile.nullAnalysis.mode": "automatic",
4 4 "cSpell.words": [
5 5 "implab",
6 "materializer",
6 7 "rawtypes"
7 8 ]
8 9 } No newline at end of file
@@ -1,12 +1,42
1 1 # AGENTS.md
2 2
3 3 ## ΠŸΡ€ΠΎΠ΅ΠΊΡ‚Π½Ρ‹Π΅ договорСнности
4 4
5 5 ### ΠŸΡƒΠ±Π»ΠΈΡ‡Π½ΠΎΠ΅ API Π±ΠΈΠ±Π»ΠΈΠΎΡ‚Π΅ΠΊ
6 6
7 7 - ΠŸΡ€Π΅Π΄ΠΏΠΎΡ‡Ρ‚ΠΈΡ‚Π΅Π»Π΅Π½ `non-null` ΠΏΠΎΠ΄Ρ…ΠΎΠ΄.
8 8 - Π’Π°ΠΌ, Π³Π΄Π΅ Π·Π½Π°Ρ‡Π΅Π½ΠΈΠ΅ ΠΆΠΈΠ²Π΅Ρ‚ Π² Gradle Provider API, возвращаСтся `Provider<T>` (Π½Π΅ `null`).
9 9 - Π’Π°ΠΌ, Π³Π΄Π΅ lookup синхронный, возвращаСтся `Optional<T>` (Π½Π΅ `null`).
10 10 - `find*` рассматриваСтся ΠΊΠ°ΠΊ синоним legacy `get*` (поиск Π±Π΅Π· `fail-fast`).
11 11 - `require*` это `find*` + `fail-fast` с понятной ошибкой Π² мСстС Π²Ρ‹Π·ΠΎΠ²Π°.
12 12 - Для Π½ΠΎΠ²ΠΎΠ³ΠΎ API ΠΏΡ€Π΅Π΄ΠΏΠΎΡ‡Ρ‚ΠΈΡ‚Π΅Π»ΡŒΠ½Ρ‹ Ρ„ΠΎΡ€ΠΌΡ‹ `find/require`; Π½ΠΎΠ²Ρ‹Π΅ `get*` ΠΏΠΎ возмоТности Π½Π΅ Π΄ΠΎΠ±Π°Π²Π»ΡΡ‚ΡŒ.
13
14 ## Identity-first modeling
15
16 Prefer an **identity-first** split between:
17
18 - **identity objects** used for discovery and selection
19 - **stateful/materialized objects** obtained through separate API calls
20
21 ### Rules
22
23 - Objects intended for replayable observation (`all(...)`, similar collection APIs) should be **identity objects**.
24 - Identity objects must be:
25 - cheap to create
26 - effectively immutable
27 - limited to identity and cheap selection metadata
28 - Heavy, computed, provider-based, or runtime-bound state must be resolved separately.
29 - Aggregate content should be accessed through dedicated lookup/materialization APIs.
30 - Eager observation of identity is acceptable.
31 - Eager observation of computed state is not.
32
33 ### Do not
34
35 - Do not store heavy computed state inside identity objects.
36 - Do not store runtime references to foreign domains inside identity objects.
37 - Do not use custom events when replayable identity registries plus on-demand lookup are sufficient.
38
39 ### Rule of thumb
40
41 **Identity objects contain selection metadata.
42 Aggregate content is obtained separately, on demand.**
@@ -1,34 +1,35
1 1 package org.implab.gradle.common.core.lang;
2 2
3 3 import java.util.Objects;
4 4 import java.util.function.Supplier;
5 5
6 6 public class LazyValue<T> implements Supplier<T> {
7 7 private volatile T value;
8 private volatile boolean initialized = false;
8 9
9 10 private final Supplier<T> innerSupplier;
10 11
11 12 public LazyValue(Supplier<T> supplier) {
12 13 this.innerSupplier = supplier;
13 14 }
14 15
15 16 @Override
16 17 public T get() {
17 18 var v = value;
18 if (v != null) {
19 if (initialized) {
19 20 return v;
20 21 }
21 22
22 23 synchronized (this) {
23 24 v = value;
24 25 if (v == null) {
25 26 v = Objects.requireNonNull(
26 27 innerSupplier.get(),
27 28 "LazyValue supplier returned null");
28 29 value = v;
29 30 }
30 31 return v;
31 32 }
32 33 }
33 34
34 35 }
@@ -1,65 +1,70
1 1 package org.implab.gradle.common.core.lang;
2 2
3 3 import java.util.regex.Pattern;
4 4
5 5 import org.eclipse.jdt.annotation.NonNullByDefault;
6 6 import org.gradle.api.provider.Provider;
7 7
8 8 @NonNullByDefault
9 9 public class Strings {
10 10
11 11 private static final Pattern firstLetter = Pattern.compile("^\\w");
12 12
13 13 private static final Pattern INVALID_NAME_CHAR = Pattern.compile("[^A-Za-z0-9_.-]");
14 14
15 15 public static String capitalize(String string) {
16 16 return string == null ? null
17 17 : string.length() == 0 ? string
18 18 : firstLetter.matcher(string).replaceFirst(m -> m.group().toUpperCase());
19 19 }
20 20
21 21 public static String toCamelCase(String name) {
22 22 if (name == null || name.isEmpty())
23 23 return name;
24 24 StringBuilder out = new StringBuilder(name.length());
25 25 boolean up = false;
26 26 boolean first = true;
27 27 for (int i = 0; i < name.length(); i++) {
28 28 char c = name.charAt(i);
29 29 switch (c) {
30 30 case '-', '_', ' ', '.' -> up = true;
31 31 default -> {
32 32 out.append(
33 33 first ? Character.toLowerCase(c)
34 34 : up ? Character.toUpperCase(c): c);
35 35 up = false;
36 36 first = false;
37 37 }
38 38 }
39 39 }
40 40 return out.toString();
41 41 }
42 42
43 43 public static void argumentNotNullOrEmpty(String value, String argumentName) {
44 44 if (value == null || value.length() == 0)
45 45 throw new IllegalArgumentException(String.format("Argument %s can't be null or empty", argumentName));
46 46 }
47 47
48 48 public static void argumentNotNullOrBlank(String value, String argumentName) {
49 49 if (value == null || value.trim().length() == 0)
50 50 throw new IllegalArgumentException(String.format("Argument %s can't be null or blank", argumentName));
51 51 }
52 52
53 public static String requireNonBlank(String value) {
54 argumentNotNullOrBlank(value, "value");
55 return value;
56 }
57
53 58 public static String sanitizeName(String value) {
54 59 return INVALID_NAME_CHAR.matcher(value).replaceAll("_");
55 60 }
56 61
57 62 public static String asString(Object value) {
58 63 if (value == null)
59 64 return null;
60 65 if (value instanceof Provider<?> provider)
61 66 return asString(provider.get());
62 67 else
63 68 return value.toString();
64 69 }
65 70 }
@@ -1,17 +1,15
1 1 package org.implab.gradle.variants;
2 2
3 3 import org.gradle.api.Plugin;
4 4 import org.gradle.api.Project;
5 5 import org.implab.gradle.variants.model.VariantsExtension;
6 6
7 7 public abstract class VariantsPlugin implements Plugin<Project> {
8 8 @Override
9 9 public void apply(Project target) {
10 var extension = target.getExtensions().create("variants", VariantsExtension.class);
11 10
12 target.afterEvaluate(project -> {
13
14 });
11 target.getExtensions().create("variants", VariantsExtension.class);
15 12
16 13 }
14
17 15 }
@@ -1,35 +1,8
1 1 package org.implab.gradle.variants.model;
2 2
3 import org.gradle.api.Named;
4 import org.gradle.api.provider.SetProperty;
5
6 /**
7 * Binds a role to a set of layers inside a particular variant.
3 /** A binding between a role and a layer inside a specific variant.
8 4 *
9 * The binding name is the role name, e.g. "production", "test", "tool".
10 */
11 public interface RoleBinding extends Named {
12
13 /**
14 * Layer names participating in this (variant, role) selection.
15 *
16 * Core model keeps names here deliberately:
17 * source/materialization semantics live elsewhere.
5 * @see {@link VariantDefinition} for the context of this binding.
18 6 */
19 SetProperty<String> getLayerNames();
20
21 /**
22 * Adds one layer to this binding.
23 */
24 void layer(String name);
25
26 /**
27 * Adds several layers to this binding.
28 */
29 void layers(String... names);
30
31 /**
32 * Adds several layers to this binding.
33 */
34 void layers(Iterable<String> names);
7 public record RoleLayerBinding(String name, String layerName) {
35 8 } No newline at end of file
@@ -1,44 +1,12
1 1 package org.implab.gradle.variants.model;
2 2
3 import org.gradle.api.Action;
4 3 import org.gradle.api.Named;
5 import org.gradle.api.NamedDomainObjectContainer;
6 import org.implab.gradle.common.core.lang.Closures;
7
8 import groovy.lang.Closure;
9 4
10 5 /**
11 6 * A named variant, e.g. "browser", "electron".
12 7 *
13 8 * A variant does not "have a role" directly.
14 9 * It owns a set of role bindings.
15 10 */
16 11 public interface Variant extends Named {
17
18 /**
19 * Role bindings declared inside this variant.
20 *
21 * The binding name is the role name.
22 */
23 NamedDomainObjectContainer<RoleBinding> getRoleBindings();
24
25 /**
26 * Creates or returns an existing role binding and configures it.
27 */
28 default RoleBinding role(String name) {
29 return getRoleBindings().maybeCreate(name);
30 }
31
32 /**
33 * Creates or returns an existing role binding and configures it.
34 */
35 default RoleBinding role(String name, Action<? super RoleBinding> action) {
36 var role = role(name);
37 action.execute(role);
38 return role;
39 }
40
41 default RoleBinding role(String name, Closure<?> closure) {
42 return role(name, Closures.action(closure));
43 }
44 12 } No newline at end of file
@@ -1,59 +1,65
1 1 package org.implab.gradle.variants.model;
2 2
3 3 import org.gradle.api.Action;
4 4 import org.gradle.api.NamedDomainObjectContainer;
5 5 import org.implab.gradle.common.core.lang.Closures;
6 6
7 7 import groovy.lang.Closure;
8 8
9 9 /**
10 10 * Root extension:
11 11 *
12 12 * variants {
13 13 * layers { ... }
14 14 * roles { ... }
15 15 *
16 16 * variant("browser") {
17 17 * role("production") {
18 18 * layers("main", "generated", "mainRjs")
19 19 * }
20 20 * }
21 21 * }
22 22 */
23 23 public interface VariantsExtension {
24 24
25 25 /**
26 26 * Domain of layers.
27 27 */
28 28 NamedDomainObjectContainer<Layer> getLayers();
29 29
30 30 /**
31 31 * Domain of roles.
32 32 */
33 33 NamedDomainObjectContainer<Role> getRoles();
34 34
35 35 /**
36 * Domain of variants.
37 */
38 NamedDomainObjectContainer<Variant> getVariants();
39
40 /**
36 41 * Declared variants.
37 42 */
38 NamedDomainObjectContainer<Variant> getVariantDefinitions();
43 NamedDomainObjectContainer<VariantDefinition> getVariantDefinitions();
39 44
40 45 /**
41 46 * Creates or returns an existing variant and configures it.
42 47 */
43 default Variant variant(String name) {
48 default VariantDefinition variant(String name) {
49
44 50 return getVariantDefinitions().maybeCreate(name);
45 51 }
46 52
47 53 /**
48 54 * Creates or returns an existing variant and configures it.
49 55 */
50 default Variant variant(String name, Action<? super Variant> action) {
56 default VariantDefinition variant(String name, Action<? super VariantDefinition> action) {
51 57 var variant = variant(name);
52 58 action.execute(variant);
53 59 return variant;
54 60 }
55 61
56 default Variant variant(String name, Closure<?> closure) {
62 default VariantDefinition variant(String name, Closure<?> closure) {
57 63 return variant(name, Closures.action(closure));
58 64 }
59 65 } No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now