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