| @@ -0,0 +1,89 | |||
|
|
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 | import org.eclipse.jdt.annotation.NonNullByDefault; | |
|
|
8 | ||
|
|
9 | /** | |
|
|
10 | * Replayable append-only notification queue. | |
|
|
11 | * | |
|
|
12 | * <p> | |
|
|
13 | * The queue stores values in insertion order and supports replaying all values | |
|
|
14 | * that were already added when a consumer is registered. The same consumer is | |
|
|
15 | * also retained and invoked for every value added later. | |
|
|
16 | * | |
|
|
17 | * <p> | |
|
|
18 | * Values are committed before dispatch. If a consumer throws while a value is | |
|
|
19 | * being added, the value remains recorded in the queue and will be visible to | |
|
|
20 | * later consumers through {@link #forEach(Consumer)} and {@link #values()}. | |
|
|
21 | * | |
|
|
22 | * <p> | |
|
|
23 | * Reentrant replay is intentionally not supported. Consumers must not call | |
|
|
24 | * {@link #add(Object)} or {@link #forEach(Consumer)} on the same queue while | |
|
|
25 | * they are being invoked by that queue. Such nested calls fail fast with | |
|
|
26 | * {@link IllegalStateException}. This keeps ordering predictable and avoids | |
|
|
27 | * recursive event-loop semantics. | |
|
|
28 | * | |
|
|
29 | * @param <T> value type | |
|
|
30 | */ | |
|
|
31 | @NonNullByDefault | |
|
|
32 | public class ReplayableQueue<T> { | |
|
|
33 | private final List<Consumer<? super T>> consumers = new LinkedList<>(); | |
|
|
34 | private final List<T> values = new LinkedList<>(); | |
|
|
35 | private boolean replaying = false; | |
|
|
36 | ||
|
|
37 | /** | |
|
|
38 | * Adds a value and dispatches it to all registered consumers. | |
|
|
39 | * | |
|
|
40 | * <p> | |
|
|
41 | * The value is recorded before consumers are invoked. If dispatch fails, the | |
|
|
42 | * value still belongs to the queue. | |
|
|
43 | * | |
|
|
44 | * @param value value to add | |
|
|
45 | */ | |
|
|
46 | public void add(T value) { | |
|
|
47 | safeInvoke(value, v -> { | |
|
|
48 | values.add(v); | |
|
|
49 | consumers.forEach(consumer -> consumer.accept(v)); | |
|
|
50 | }); | |
|
|
51 | } | |
|
|
52 | ||
|
|
53 | /** | |
|
|
54 | * Returns an immutable snapshot of values recorded so far. | |
|
|
55 | * | |
|
|
56 | * @return current values in insertion order | |
|
|
57 | */ | |
|
|
58 | public List<T> values() { | |
|
|
59 | return List.copyOf(values); | |
|
|
60 | } | |
|
|
61 | ||
|
|
62 | /** | |
|
|
63 | * Replays all recorded values to the consumer and registers it for future | |
|
|
64 | * values. | |
|
|
65 | * | |
|
|
66 | * <p> | |
|
|
67 | * The consumer is registered only after replaying existing values succeeds. | |
|
|
68 | * | |
|
|
69 | * @param consumer consumer to replay and retain | |
|
|
70 | */ | |
|
|
71 | public void forEach(Consumer<? super T> consumer) { | |
|
|
72 | safeInvoke(values, v -> { | |
|
|
73 | v.forEach(consumer); | |
|
|
74 | consumers.add(consumer); | |
|
|
75 | }); | |
|
|
76 | } | |
|
|
77 | ||
|
|
78 | private <X> void safeInvoke(X value, Consumer<? super X> consumer) { | |
|
|
79 | if (replaying) | |
|
|
80 | throw new IllegalStateException("Reentrant replay is not supported: replay is in progress"); | |
|
|
81 | try { | |
|
|
82 | replaying = true; | |
|
|
83 | consumer.accept(value); | |
|
|
84 | } finally { | |
|
|
85 | replaying = false; | |
|
|
86 | } | |
|
|
87 | } | |
|
|
88 | ||
|
|
89 | } | |
| @@ -0,0 +1,59 | |||
|
|
1 | package org.implab.gradle.common.core.lang; | |
|
|
2 | ||
|
|
3 | import static org.junit.jupiter.api.Assertions.assertEquals; | |
|
|
4 | import static org.junit.jupiter.api.Assertions.assertThrows; | |
|
|
5 | import static org.junit.jupiter.api.Assertions.assertTrue; | |
|
|
6 | ||
|
|
7 | import java.util.ArrayList; | |
|
|
8 | import java.util.List; | |
|
|
9 | ||
|
|
10 | import org.junit.jupiter.api.Test; | |
|
|
11 | ||
|
|
12 | class ReplayableQueueTest { | |
|
|
13 | @Test | |
|
|
14 | void replaysExistingValuesAndReceivesFutureValues() { | |
|
|
15 | var queue = new ReplayableQueue<String>(); | |
|
|
16 | var seen = new ArrayList<String>(); | |
|
|
17 | ||
|
|
18 | queue.add("one"); | |
|
|
19 | queue.add("two"); | |
|
|
20 | queue.forEach(seen::add); | |
|
|
21 | queue.add("three"); | |
|
|
22 | ||
|
|
23 | assertEquals(List.of("one", "two", "three"), seen); | |
|
|
24 | assertEquals(List.of("one", "two", "three"), queue.values()); | |
|
|
25 | } | |
|
|
26 | ||
|
|
27 | @Test | |
|
|
28 | void commitsValueBeforeDispatchingConsumers() { | |
|
|
29 | var queue = new ReplayableQueue<String>(); | |
|
|
30 | ||
|
|
31 | queue.forEach(value -> { | |
|
|
32 | throw new IllegalStateException("boom"); | |
|
|
33 | }); | |
|
|
34 | ||
|
|
35 | assertThrows(IllegalStateException.class, () -> queue.add("one")); | |
|
|
36 | assertEquals(List.of("one"), queue.values()); | |
|
|
37 | } | |
|
|
38 | ||
|
|
39 | @Test | |
|
|
40 | void rejectsReentrantAdd() { | |
|
|
41 | var queue = new ReplayableQueue<String>(); | |
|
|
42 | ||
|
|
43 | queue.forEach(value -> queue.add("nested")); | |
|
|
44 | ||
|
|
45 | var ex = assertThrows(IllegalStateException.class, () -> queue.add("one")); | |
|
|
46 | assertTrue(ex.getMessage().contains("Reentrant replay is not supported")); | |
|
|
47 | } | |
|
|
48 | ||
|
|
49 | @Test | |
|
|
50 | void rejectsReentrantForEach() { | |
|
|
51 | var queue = new ReplayableQueue<String>(); | |
|
|
52 | queue.add("one"); | |
|
|
53 | ||
|
|
54 | var ex = assertThrows(IllegalStateException.class, () -> queue.forEach(value -> queue.forEach(nested -> { | |
|
|
55 | }))); | |
|
|
56 | ||
|
|
57 | assertTrue(ex.getMessage().contains("Reentrant replay is not supported")); | |
|
|
58 | } | |
|
|
59 | } | |
| @@ -13,7 +13,7 import org.gradle.api.file.FileSystemLoc | |||
|
|
13 | 13 | import org.gradle.api.provider.Provider; |
|
|
14 | 14 | import org.gradle.api.tasks.TaskProvider; |
|
|
15 | 15 | import org.implab.gradle.common.core.lang.Deferred; |
|
|
16 |
import org.implab.gradle. |
|
|
|
16 | import org.implab.gradle.common.core.lang.ReplayableQueue; | |
|
|
17 | 17 | import org.implab.gradle.variants.artifacts.ArtifactAssemblies; |
|
|
18 | 18 | import org.implab.gradle.variants.artifacts.ArtifactAssembly; |
|
|
19 | 19 | import org.implab.gradle.variants.artifacts.ArtifactSlot; |
| @@ -15,14 +15,16 import org.gradle.api.artifacts.Configur | |||
|
|
15 | 15 | import org.gradle.api.model.ObjectFactory; |
|
|
16 | 16 | import org.gradle.api.provider.Property; |
|
|
17 | 17 | import org.implab.gradle.internal.IdentityContainerFactory; |
|
|
18 |
import org.implab.gradle. |
|
|
|
18 | import org.implab.gradle.common.core.lang.ReplayableQueue; | |
|
|
19 | 19 | import org.implab.gradle.variants.artifacts.OutgoingVariant; |
|
|
20 | 20 | import org.implab.gradle.variants.artifacts.Slot; |
|
|
21 | 21 | import org.implab.gradle.variants.core.Variant; |
|
|
22 | 22 | |
|
|
23 | 23 | /** |
|
|
24 | * Реестр исходящих вариантов. Связывает исходящие конфигурации с вариантами | |
|
|
25 | * сборки. Связь устанавливается 1:1. | |
|
|
24 | * Registry of variant-level outgoing models. | |
|
|
25 | * | |
|
|
26 | * <p>Each declared outgoing model owns one lazy Gradle consumable configuration | |
|
|
27 | * and one live slot identity container. | |
|
|
26 | 28 | */ |
|
|
27 | 29 | @NonNullByDefault |
|
|
28 | 30 | public class OutgoingRegistry { |
| @@ -66,9 +68,9 public class OutgoingRegistry { | |||
|
|
66 | 68 | } |
|
|
67 | 69 | |
|
|
68 | 70 | /** |
|
|
69 |
* Replayable hook |
|
|
|
71 | * Registers a replayable hook for outgoing variant declarations. | |
|
|
70 | 72 | * |
|
|
71 | * @param action | |
|
|
73 | * @param action outgoing variant action | |
|
|
72 | 74 | */ |
|
|
73 | 75 | public void configureEach(Consumer<? super OutgoingVariant> action) { |
|
|
74 | 76 | outgoingVariants.forEach(action); |
| @@ -2,10 +2,8 package org.implab.gradle.variants.sourc | |||
|
|
2 | 2 | |
|
|
3 | 3 | import java.text.MessageFormat; |
|
|
4 | 4 | import java.util.LinkedHashMap; |
|
|
5 | import java.util.LinkedList; | |
|
|
6 | 5 | import java.util.List; |
|
|
7 | 6 | import java.util.Map; |
|
|
8 | import java.util.function.Consumer; | |
|
|
9 | 7 | import java.util.function.Supplier; |
|
|
10 | 8 | import java.util.stream.Collectors; |
|
|
11 | 9 | |
| @@ -14,6 +12,7 import org.gradle.api.Action; | |||
|
|
14 | 12 | import org.gradle.api.Named; |
|
|
15 | 13 | import org.gradle.api.logging.Logger; |
|
|
16 | 14 | import org.gradle.api.logging.Logging; |
|
|
15 | import org.implab.gradle.common.core.lang.ReplayableQueue; | |
|
|
17 | 16 | import org.implab.gradle.variants.core.Layer; |
|
|
18 | 17 | import org.implab.gradle.variants.core.Variant; |
|
|
19 | 18 | import org.implab.gradle.variants.sources.CompileUnit; |
| @@ -57,7 +56,7 public class SourceSetConfigurationRegis | |||
|
|
57 | 56 | sourcesByUnit.computeIfAbsent(unit, key -> new ReplayableQueue<>()), |
|
|
58 | 57 | action, |
|
|
59 | 58 | MessageFormat.format( |
|
|
60 | "Source set for [variant={0}, layer={1}] already materialed", | |
|
|
59 | "Source set for [variant={0}, layer={1}] already materialized", | |
|
|
61 | 60 | unit.variant().getName(), |
|
|
62 | 61 | unit.layer().getName())); |
|
|
63 | 62 | } |
| @@ -92,23 +91,4 public class SourceSetConfigurationRegis | |||
|
|
92 | 91 | sourcesByLayer.computeIfAbsent(unit.layer(), key -> new ReplayableQueue<>()).add(sourceSet); |
|
|
93 | 92 | sourcesByUnit.computeIfAbsent(unit, key -> new ReplayableQueue<>()).add(sourceSet); |
|
|
94 | 93 | } |
|
|
95 | ||
|
|
96 | class ReplayableQueue<T> { | |
|
|
97 | private final List<Consumer<? super T>> consumers = new LinkedList<>(); | |
|
|
98 | private final List<T> values = new LinkedList<>(); | |
|
|
99 | ||
|
|
100 | public void add(T value) { | |
|
|
101 | consumers.forEach(consumer -> consumer.accept(value)); | |
|
|
102 | values.add(value); | |
|
|
103 | } | |
|
|
104 | ||
|
|
105 | List<T> values() { | |
|
|
106 | return List.copyOf(values); | |
|
|
107 | } | |
|
|
108 | ||
|
|
109 | public void forEach(Consumer<? super T> consumer) { | |
|
|
110 | values.forEach(consumer); | |
|
|
111 | consumers.add(consumer); | |
|
|
112 | } | |
|
|
113 | } | |
|
|
114 | 94 | } |
|
|
1 | NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now
