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