]> git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.enterprise/src/org/argeo/util/register/Component.java
Start working around components
[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.Arrays;
4 import java.util.Collections;
5 import java.util.HashMap;
6 import java.util.HashSet;
7 import java.util.IdentityHashMap;
8 import java.util.Map;
9 import java.util.Set;
10 import java.util.concurrent.CompletableFuture;
11 import java.util.concurrent.atomic.AtomicBoolean;
12 import java.util.function.Consumer;
13 import java.util.function.Predicate;
14
15 public class Component {
16 private final static AtomicBoolean started = new AtomicBoolean(false);
17 private final static IdentityHashMap<Object, Component> components = new IdentityHashMap<>();
18
19 private static synchronized void registerComponent(Component component) {
20 if (started.get()) // TODO make it rellay dynamic
21 throw new IllegalStateException("Already activated");
22 if (components.containsKey(component.instance))
23 throw new IllegalArgumentException("Already registered as component");
24 components.put(component.instance, component);
25 }
26
27 static synchronized Component get(Object instance) {
28 if (!components.containsKey(instance))
29 throw new IllegalArgumentException("Not registered as component");
30 return components.get(instance);
31 }
32
33 public synchronized static void activate() {
34 if (started.get())
35 throw new IllegalStateException("Already activated");
36 for (Component component : components.values()) {
37 component.activationStarted.complete(null);
38 }
39 started.set(true);
40 }
41
42 public synchronized static void deactivate() {
43 if (!started.get())
44 throw new IllegalStateException("Not activated");
45 for (Component component : components.values()) {
46 component.deactivationStarted.complete(null);
47 }
48 started.set(false);
49 }
50
51 private final Object instance;
52
53 private Runnable init;
54 private Runnable close;
55
56 private final Map<Class<?>, PublishedType<?>> types;
57 private final Set<Dependency<?>> dependencies;
58
59 private CompletableFuture<Void> activationStarted = new CompletableFuture<Void>();
60 private CompletableFuture<Void> activated = new CompletableFuture<Void>();
61
62 private CompletableFuture<Void> deactivationStarted = new CompletableFuture<Void>();
63 private CompletableFuture<Void> deactivated = new CompletableFuture<Void>();
64
65 private Set<Dependency<?>> dependants = new HashSet<>();
66
67 Component(Object instance, Runnable init, Runnable close, Set<Dependency<?>> dependencies, Set<Class<?>> classes) {
68 assert instance != null;
69 assert init != null;
70 assert close != null;
71 assert dependencies != null;
72 assert classes != null;
73
74 this.instance = instance;
75 this.init = init;
76 this.close = close;
77
78 // types
79 Map<Class<?>, PublishedType<?>> types = new HashMap<>(classes.size());
80 for (Class<?> clss : classes) {
81 if (!clss.isAssignableFrom(instance.getClass()))
82 throw new IllegalArgumentException(
83 "Type " + clss.getName() + " is not compatible with " + instance.getClass().getName());
84 types.put(clss, new PublishedType<>(clss));
85 }
86 this.types = Collections.unmodifiableMap(types);
87
88 // dependencies
89 this.dependencies = Collections.unmodifiableSet(new HashSet<>(dependencies));
90 for (Dependency<?> dependency : this.dependencies) {
91 dependency.setDependantComponent(this);
92 }
93
94 // future activation
95 activated = activationStarted //
96 .thenCompose(this::dependenciesActivated) //
97 .thenRun(this.init);
98
99 // future deactivation
100 deactivated = deactivationStarted //
101 .thenCompose(this::dependantsDeactivated) //
102 .thenRun(this.close);
103
104 registerComponent(this);
105 }
106
107 CompletableFuture<Void> dependenciesActivated(Void v) {
108 Set<CompletableFuture<?>> constraints = new HashSet<>(this.dependencies.size());
109 for (Dependency<?> dependency : this.dependencies) {
110 CompletableFuture<Void> dependencyActivated = dependency.getPublisher().activated //
111 .thenCompose(dependency::set);
112 constraints.add(dependencyActivated);
113 }
114 return CompletableFuture.allOf(constraints.toArray(new CompletableFuture[constraints.size()]));
115 }
116
117 CompletableFuture<Void> dependantsDeactivated(Void v) {
118 Set<CompletableFuture<?>> constraints = new HashSet<>(this.dependants.size());
119 for (Dependency<?> dependant : this.dependants) {
120 CompletableFuture<Void> dependantDeactivated = dependant.getDependantComponent().deactivated //
121 .thenCompose(dependant::unset);
122 constraints.add(dependantDeactivated);
123 }
124 CompletableFuture<Void> dependantsDeactivated = CompletableFuture
125 .allOf(constraints.toArray(new CompletableFuture[constraints.size()]));
126 return dependantsDeactivated;
127
128 }
129
130 void addDependant(Dependency<?> dependant) {
131 dependants.add(dependant);
132 }
133
134 public <T> PublishedType<T> getType(Class<T> clss) {
135 if (!types.containsKey(clss))
136 throw new IllegalArgumentException(clss.getName() + " is not a type published by this component");
137 return (PublishedType<T>) types.get(clss);
138 }
139
140 public class PublishedType<T> {
141 private Class<T> clss;
142
143 private CompletableFuture<T> value;
144
145 public PublishedType(Class<T> clss) {
146 this.clss = clss;
147
148 value = CompletableFuture.completedFuture((T) Component.this.instance);
149 }
150
151 Component getPublisher() {
152 return Component.this;
153 }
154
155 Class<T> getType() {
156 return clss;
157 }
158 }
159
160 public static class Builder<I> {
161 private final I instance;
162
163 private Runnable init;
164 private Runnable close;
165
166 private Set<Dependency<?>> dependencies = new HashSet<>();
167 private Set<Class<?>> types = new HashSet<>();
168
169 public Builder(I instance) {
170 this.instance = instance;
171 }
172
173 public Component build() {
174 if (types.isEmpty()) {
175 types.add(instance.getClass());
176 }
177
178 if (init == null)
179 init = () -> {
180 };
181 if (close == null)
182 close = () -> {
183 };
184
185 Component component = new Component(instance, init, close, dependencies, types);
186 for (Dependency<?> dependency : dependencies) {
187 dependency.type.getPublisher().addDependant(dependency);
188 }
189 return component;
190 }
191
192 public Builder<I> addType(Class<?>... classes) {
193 types.addAll(Arrays.asList(classes));
194 return this;
195 }
196
197 public Builder<I> addInit(Runnable init) {
198 if (this.init != null)
199 throw new IllegalArgumentException("init method is already set");
200 this.init = init;
201 return this;
202 }
203
204 public Builder<I> addClose(Runnable close) {
205 if (this.close != null)
206 throw new IllegalArgumentException("close method is already set");
207 this.close = close;
208 return this;
209 }
210
211 public <D> Builder<I> addDependency(PublishedType<D> type, Predicate<?> filter, Consumer<D> set,
212 Consumer<D> unset) {
213 dependencies.add(new Dependency<D>(type, filter, set, unset));
214 return this;
215 }
216
217 public I get() {
218 return instance;
219 }
220
221 }
222
223 static class Dependency<D> {
224 private PublishedType<D> type;
225 private Predicate<?> filter;
226 private Consumer<D> set;
227 private Consumer<D> unset;
228
229 // live
230 Component dependantComponent;
231 CompletableFuture<Void> setStage;
232 CompletableFuture<Void> unsetStage;
233
234 public Dependency(PublishedType<D> types, Predicate<?> filter, Consumer<D> set, Consumer<D> unset) {
235 super();
236 this.type = types;
237 this.filter = filter;
238 this.set = set;
239 this.unset = unset != null ? unset : (v) -> set.accept(null);
240 }
241
242 // live
243 void setDependantComponent(Component component) {
244 this.dependantComponent = component;
245 }
246
247 Component getPublisher() {
248 return type.getPublisher();
249 }
250
251 Component getDependantComponent() {
252 return dependantComponent;
253 }
254
255 CompletableFuture<Void> set(Void v) {
256 return type.value.thenAccept(set);
257 }
258
259 CompletableFuture<Void> unset(Void v) {
260 return type.value.thenAccept(unset);
261 }
262
263 }
264 }
265