]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.util/src/org/argeo/util/register/Component.java
Use latest release of Argeo Maven parent
[lgpl/argeo-commons.git] / org.argeo.util / src / org / argeo / util / register / Component.java
1 package org.argeo.util.register;
2
3 import java.util.Collections;
4 import java.util.HashMap;
5 import java.util.HashSet;
6 import java.util.Map;
7 import java.util.Set;
8 import java.util.concurrent.CompletableFuture;
9 import java.util.function.Consumer;
10
11 /**
12 * A wrapper for an object, whose dependencies and life cycle can be managed.
13 */
14 public class Component<I> {
15
16 private final I instance;
17
18 private final Runnable init;
19 private final Runnable close;
20
21 private final Map<Class<? super I>, PublishedType<? super I>> types;
22 private final Set<Dependency<?>> dependencies;
23
24 private CompletableFuture<Void> activationStarted = null;
25 private CompletableFuture<Void> activated = null;
26
27 private CompletableFuture<Void> deactivationStarted = null;
28 private CompletableFuture<Void> deactivated = null;
29
30 private Set<Dependency<?>> dependants = new HashSet<>();
31
32 Component(Consumer<Component<?>> register, I instance, Runnable init, Runnable close,
33 Set<Dependency<?>> dependencies, Set<Class<? super I>> classes) {
34 assert instance != null;
35 assert init != null;
36 assert close != null;
37 assert dependencies != null;
38 assert classes != null;
39
40 this.instance = instance;
41 this.init = init;
42 this.close = close;
43
44 // types
45 Map<Class<? super I>, PublishedType<? super I>> types = new HashMap<>(classes.size());
46 for (Class<? super I> clss : classes) {
47 // if (!clss.isAssignableFrom(instance.getClass()))
48 // throw new IllegalArgumentException(
49 // "Type " + clss.getName() + " is not compatible with " + instance.getClass().getName());
50 types.put(clss, new PublishedType<>(this, clss));
51 }
52 this.types = Collections.unmodifiableMap(types);
53
54 // dependencies
55 this.dependencies = Collections.unmodifiableSet(new HashSet<>(dependencies));
56 for (Dependency<?> dependency : this.dependencies) {
57 dependency.setDependantComponent(this);
58 }
59
60 // deactivated by default
61 deactivated = CompletableFuture.completedFuture(null);
62 deactivationStarted = CompletableFuture.completedFuture(null);
63
64 // TODO check whether context is active, so that we start right away
65 prepareNextActivation();
66
67 register.accept(this);
68 }
69
70 private void prepareNextActivation() {
71 activationStarted = new CompletableFuture<Void>();
72 activated = activationStarted //
73 .thenComposeAsync(this::dependenciesActivated) //
74 .thenRun(this.init) //
75 .thenRun(() -> prepareNextDeactivation());
76 }
77
78 private void prepareNextDeactivation() {
79 deactivationStarted = new CompletableFuture<Void>();
80 deactivated = deactivationStarted //
81 .thenComposeAsync(this::dependantsDeactivated) //
82 .thenRun(this.close) //
83 .thenRun(() -> prepareNextActivation());
84 }
85
86 public CompletableFuture<Void> getActivated() {
87 return activated;
88 }
89
90 public CompletableFuture<Void> getDeactivated() {
91 return deactivated;
92 }
93
94 void startActivating() {
95 if (activated.isDone() || activationStarted.isDone())
96 return;
97 activationStarted.complete(null);
98 }
99
100 void startDeactivating() {
101 if (deactivated.isDone() || deactivationStarted.isDone())
102 return;
103 deactivationStarted.complete(null);
104 }
105
106 CompletableFuture<Void> dependenciesActivated(Void v) {
107 Set<CompletableFuture<?>> constraints = new HashSet<>(this.dependencies.size());
108 for (Dependency<?> dependency : this.dependencies) {
109 CompletableFuture<Void> dependencyActivated = dependency.publisherActivated() //
110 .thenCompose(dependency::set);
111 constraints.add(dependencyActivated);
112 }
113 return CompletableFuture.allOf(constraints.toArray(new CompletableFuture[constraints.size()]));
114 }
115
116 CompletableFuture<Void> dependantsDeactivated(Void v) {
117 Set<CompletableFuture<?>> constraints = new HashSet<>(this.dependants.size());
118 for (Dependency<?> dependant : this.dependants) {
119 CompletableFuture<Void> dependantDeactivated = dependant.dependantDeactivated() //
120 .thenCompose(dependant::unset);
121 constraints.add(dependantDeactivated);
122 }
123 CompletableFuture<Void> dependantsDeactivated = CompletableFuture
124 .allOf(constraints.toArray(new CompletableFuture[constraints.size()]));
125 return dependantsDeactivated;
126
127 }
128
129 void addDependant(Dependency<?> dependant) {
130 dependants.add(dependant);
131 }
132
133 I getInstance() {
134 return instance;
135 }
136
137 @SuppressWarnings("unchecked")
138 <T> PublishedType<T> getType(Class<T> clss) {
139 if (!types.containsKey(clss))
140 throw new IllegalArgumentException(clss.getName() + " is not a type published by this component");
141 return (PublishedType<T>) types.get(clss);
142 }
143
144 <T> boolean isPublishedType(Class<T> clss) {
145 return types.containsKey(clss);
146 }
147
148 public static class PublishedType<T> {
149 private Component<? extends T> component;
150 private Class<T> clss;
151
152 // private CompletableFuture<Component<? extends T>> publisherAvailable;
153 private CompletableFuture<T> value;
154
155 public PublishedType(Component<? extends T> component, Class<T> clss) {
156 this.clss = clss;
157 this.component = component;
158 value = CompletableFuture.completedFuture((T) component.instance);
159 // value = publisherAvailable.thenApply((c) -> c.getInstance());
160 }
161
162 Component<?> getPublisher() {
163 return component;
164 }
165
166 // CompletableFuture<Component<? extends T>> publisherAvailable() {
167 // return publisherAvailable;
168 // }
169
170 Class<T> getType() {
171 return clss;
172 }
173 }
174
175 public static class Builder<I> {
176 private final I instance;
177
178 private Runnable init;
179 private Runnable close;
180
181 private Set<Dependency<?>> dependencies = new HashSet<>();
182 private Set<Class<? super I>> types = new HashSet<>();
183
184 public Builder(I instance) {
185 this.instance = instance;
186 }
187
188 public Component<I> build(Consumer<Component<?>> register) {
189 // default values
190 if (types.isEmpty()) {
191 types.add(getInstanceClass());
192 }
193
194 if (init == null)
195 init = () -> {
196 };
197 if (close == null)
198 close = () -> {
199 };
200
201 // instantiation
202 Component<I> component = new Component<I>(register, instance, init, close, dependencies, types);
203 for (Dependency<?> dependency : dependencies) {
204 dependency.type.getPublisher().addDependant(dependency);
205 }
206 return component;
207 }
208
209 public Builder<I> addType(Class<? super I> clss) {
210 types.add(clss);
211 return this;
212 }
213
214 public Builder<I> addInit(Runnable init) {
215 if (this.init != null)
216 throw new IllegalArgumentException("init method is already set");
217 this.init = init;
218 return this;
219 }
220
221 public Builder<I> addClose(Runnable close) {
222 if (this.close != null)
223 throw new IllegalArgumentException("close method is already set");
224 this.close = close;
225 return this;
226 }
227
228 public <D> Builder<I> addDependency(PublishedType<D> type, Consumer<D> set, Consumer<D> unset) {
229 dependencies.add(new Dependency<D>(type, set, unset));
230 return this;
231 }
232
233 public I get() {
234 return instance;
235 }
236
237 @SuppressWarnings("unchecked")
238 private Class<I> getInstanceClass() {
239 return (Class<I>) instance.getClass();
240 }
241
242 }
243
244 static class Dependency<D> {
245 private PublishedType<D> type;
246 private Consumer<D> set;
247 private Consumer<D> unset;
248
249 // live
250 Component<?> dependantComponent;
251 CompletableFuture<Void> setStage;
252 CompletableFuture<Void> unsetStage;
253
254 public Dependency(PublishedType<D> types, Consumer<D> set, Consumer<D> unset) {
255 super();
256 this.type = types;
257 this.set = set;
258 this.unset = unset != null ? unset : (v) -> set.accept(null);
259 }
260
261 // live
262 void setDependantComponent(Component<?> component) {
263 this.dependantComponent = component;
264 }
265
266 CompletableFuture<Void> publisherActivated() {
267 return type.getPublisher().activated.copy();
268 }
269
270 CompletableFuture<Void> dependantDeactivated() {
271 return dependantComponent.deactivated.copy();
272 }
273
274 CompletableFuture<Void> set(Void v) {
275 return type.value.thenAccept(set);
276 }
277
278 CompletableFuture<Void> unset(Void v) {
279 return type.value.thenAccept(unset);
280 }
281
282 }
283 }