| @@ -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 | import org.gradle.api.provider.Provider; |
|
13 | import org.gradle.api.provider.Provider; | |
| 14 | import org.gradle.api.tasks.TaskProvider; |
|
14 | import org.gradle.api.tasks.TaskProvider; | |
| 15 | import org.implab.gradle.common.core.lang.Deferred; |
|
15 | import org.implab.gradle.common.core.lang.Deferred; | |
| 16 |
import org.implab.gradle. |
|
16 | import org.implab.gradle.common.core.lang.ReplayableQueue; | |
| 17 | import org.implab.gradle.variants.artifacts.ArtifactAssemblies; |
|
17 | import org.implab.gradle.variants.artifacts.ArtifactAssemblies; | |
| 18 | import org.implab.gradle.variants.artifacts.ArtifactAssembly; |
|
18 | import org.implab.gradle.variants.artifacts.ArtifactAssembly; | |
| 19 | import org.implab.gradle.variants.artifacts.ArtifactSlot; |
|
19 | import org.implab.gradle.variants.artifacts.ArtifactSlot; | |
| @@ -15,14 +15,16 import org.gradle.api.artifacts.Configur | |||||
| 15 | import org.gradle.api.model.ObjectFactory; |
|
15 | import org.gradle.api.model.ObjectFactory; | |
| 16 | import org.gradle.api.provider.Property; |
|
16 | import org.gradle.api.provider.Property; | |
| 17 | import org.implab.gradle.internal.IdentityContainerFactory; |
|
17 | import org.implab.gradle.internal.IdentityContainerFactory; | |
| 18 |
import org.implab.gradle. |
|
18 | import org.implab.gradle.common.core.lang.ReplayableQueue; | |
| 19 | import org.implab.gradle.variants.artifacts.OutgoingVariant; |
|
19 | import org.implab.gradle.variants.artifacts.OutgoingVariant; | |
| 20 | import org.implab.gradle.variants.artifacts.Slot; |
|
20 | import org.implab.gradle.variants.artifacts.Slot; | |
| 21 | import org.implab.gradle.variants.core.Variant; |
|
21 | import org.implab.gradle.variants.core.Variant; | |
| 22 |
|
22 | |||
| 23 | /** |
|
23 | /** | |
| 24 | * Реестр исходящих вариантов. Связывает исходящие конфигурации с вариантами |
|
24 | * Registry of variant-level outgoing models. | |
| 25 | * сборки. Связь устанавливается 1:1. |
|
25 | * | |
|
|
26 | * <p>Each declared outgoing model owns one lazy Gradle consumable configuration | |||
|
|
27 | * and one live slot identity container. | |||
| 26 | */ |
|
28 | */ | |
| 27 | @NonNullByDefault |
|
29 | @NonNullByDefault | |
| 28 | public class OutgoingRegistry { |
|
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 | public void configureEach(Consumer<? super OutgoingVariant> action) { |
|
75 | public void configureEach(Consumer<? super OutgoingVariant> action) { | |
| 74 | outgoingVariants.forEach(action); |
|
76 | outgoingVariants.forEach(action); | |
| @@ -2,10 +2,8 package org.implab.gradle.variants.sourc | |||||
| 2 |
|
2 | |||
| 3 | import java.text.MessageFormat; |
|
3 | import java.text.MessageFormat; | |
| 4 | import java.util.LinkedHashMap; |
|
4 | import java.util.LinkedHashMap; | |
| 5 | import java.util.LinkedList; |
|
|||
| 6 | import java.util.List; |
|
5 | import java.util.List; | |
| 7 | import java.util.Map; |
|
6 | import java.util.Map; | |
| 8 | import java.util.function.Consumer; |
|
|||
| 9 | import java.util.function.Supplier; |
|
7 | import java.util.function.Supplier; | |
| 10 | import java.util.stream.Collectors; |
|
8 | import java.util.stream.Collectors; | |
| 11 |
|
9 | |||
| @@ -14,6 +12,7 import org.gradle.api.Action; | |||||
| 14 | import org.gradle.api.Named; |
|
12 | import org.gradle.api.Named; | |
| 15 | import org.gradle.api.logging.Logger; |
|
13 | import org.gradle.api.logging.Logger; | |
| 16 | import org.gradle.api.logging.Logging; |
|
14 | import org.gradle.api.logging.Logging; | |
|
|
15 | import org.implab.gradle.common.core.lang.ReplayableQueue; | |||
| 17 | import org.implab.gradle.variants.core.Layer; |
|
16 | import org.implab.gradle.variants.core.Layer; | |
| 18 | import org.implab.gradle.variants.core.Variant; |
|
17 | import org.implab.gradle.variants.core.Variant; | |
| 19 | import org.implab.gradle.variants.sources.CompileUnit; |
|
18 | import org.implab.gradle.variants.sources.CompileUnit; | |
| @@ -57,7 +56,7 public class SourceSetConfigurationRegis | |||||
| 57 | sourcesByUnit.computeIfAbsent(unit, key -> new ReplayableQueue<>()), |
|
56 | sourcesByUnit.computeIfAbsent(unit, key -> new ReplayableQueue<>()), | |
| 58 | action, |
|
57 | action, | |
| 59 | MessageFormat.format( |
|
58 | MessageFormat.format( | |
| 60 | "Source set for [variant={0}, layer={1}] already materialed", |
|
59 | "Source set for [variant={0}, layer={1}] already materialized", | |
| 61 | unit.variant().getName(), |
|
60 | unit.variant().getName(), | |
| 62 | unit.layer().getName())); |
|
61 | unit.layer().getName())); | |
| 63 | } |
|
62 | } | |
| @@ -92,23 +91,4 public class SourceSetConfigurationRegis | |||||
| 92 | sourcesByLayer.computeIfAbsent(unit.layer(), key -> new ReplayableQueue<>()).add(sourceSet); |
|
91 | sourcesByLayer.computeIfAbsent(unit.layer(), key -> new ReplayableQueue<>()).add(sourceSet); | |
| 93 | sourcesByUnit.computeIfAbsent(unit, key -> new ReplayableQueue<>()).add(sourceSet); |
|
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 | } |
|
94 | } | |
| 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 | } |
|
|||
| 1 | NO CONTENT: file was removed |
|
NO CONTENT: file was removed |
General Comments 0
You need to be logged in to leave comments.
Login now
