]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.enterprise/src/org/argeo/util/register/Component.java
Refine first component implementation
[lgpl/argeo-commons.git] / org.argeo.enterprise / 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(I instance, Runnable init, Runnable close, Set<Dependency<?>> dependencies,
33 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 StaticRegister.registerComponent(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.getPublisher().activated //
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.getDependantComponent().deactivated //
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 public static class PublishedType<T> {
145 private Component<? extends T> component;
146 private Class<T> clss;
147
148 private CompletableFuture<T> value;
149
150 public PublishedType(Component<? extends T> component, Class<T> clss) {
151 this.clss = clss;
152 this.component = component;
153 value = CompletableFuture.completedFuture((T) component.instance);
154 }
155
156 Component<?> getPublisher() {
157 return component;
158 }
159
160 Class<T> getType() {
161 return clss;
162 }
163 }
164
165 public static class Builder<I> {
166 private final I instance;
167
168 private Runnable init;
169 private Runnable close;
170
171 private Set<Dependency<?>> dependencies = new HashSet<>();
172 private Set<Class<? super I>> types = new HashSet<>();
173
174 public Builder(I instance) {
175 this.instance = instance;
176 }
177
178 public Component<I> build() {
179 // default values
180 if (types.isEmpty()) {
181 types.add(getInstanceClass());
182 }
183
184 if (init == null)
185 init = () -> {
186 };
187 if (close == null)
188 close = () -> {
189 };
190
191 // instantiation
192 Component<I> component = new Component<I>(instance, init, close, dependencies, types);
193 for (Dependency<?> dependency : dependencies) {
194 dependency.type.getPublisher().addDependant(dependency);
195 }
196 return component;
197 }
198
199 public Builder<I> addType(Class<? super I> clss) {
200 types.add(clss);
201 return this;
202 }
203
204 public Builder<I> addInit(Runnable init) {
205 if (this.init != null)
206 throw new IllegalArgumentException("init method is already set");
207 this.init = init;
208 return this;
209 }
210
211 public Builder<I> addClose(Runnable close) {
212 if (this.close != null)
213 throw new IllegalArgumentException("close method is already set");
214 this.close = close;
215 return this;
216 }
217
218 public <D> Builder<I> addDependency(PublishedType<D> type, Consumer<D> set, Consumer<D> unset) {
219 dependencies.add(new Dependency<D>(type, set, unset));
220 return this;
221 }
222
223 public I get() {
224 return instance;
225 }
226
227 @SuppressWarnings("unchecked")
228 private Class<I> getInstanceClass() {
229 return (Class<I>) instance.getClass();
230 }
231
232 }
233
234 static class Dependency<D> {
235 private PublishedType<D> type;
236 private Consumer<D> set;
237 private Consumer<D> unset;
238
239 // live
240 Component<?> dependantComponent;
241 CompletableFuture<Void> setStage;
242 CompletableFuture<Void> unsetStage;
243
244 public Dependency(PublishedType<D> types, Consumer<D> set, Consumer<D> unset) {
245 super();
246 this.type = types;
247 this.set = set;
248 this.unset = unset != null ? unset : (v) -> set.accept(null);
249 }
250
251 // live
252 void setDependantComponent(Component<?> component) {
253 this.dependantComponent = component;
254 }
255
256 Component<?> getPublisher() {
257 return type.getPublisher();
258 }
259
260 Component<?> getDependantComponent() {
261 return dependantComponent;
262 }
263
264 CompletableFuture<Void> set(Void v) {
265 return type.value.thenAccept(set);
266 }
267
268 CompletableFuture<Void> unset(Void v) {
269 return type.value.thenAccept(unset);
270 }
271
272 }
273 }