1 package org
.argeo
.api
.register
;
3 import java
.util
.ArrayList
;
4 import java
.util
.Collections
;
5 import java
.util
.HashMap
;
6 import java
.util
.HashSet
;
10 import java
.util
.concurrent
.CompletableFuture
;
11 import java
.util
.concurrent
.CompletionStage
;
12 import java
.util
.function
.Consumer
;
13 import java
.util
.function
.Supplier
;
16 * A wrapper for an object, whose dependencies and life cycle can be managed.
18 public class Component
<I
> implements Supplier
<I
>, Comparable
<Component
<?
>> {
20 private final I instance
;
22 private final Runnable init
;
23 private final Runnable close
;
25 private final Map
<Class
<?
super I
>, PublishedType
<?
super I
>> types
;
26 private final Set
<Dependency
<?
>> dependencies
;
27 private final Map
<String
, Object
> properties
;
29 private CompletableFuture
<Void
> activationStarted
= null;
30 private CompletableFuture
<Void
> activated
= null;
32 private CompletableFuture
<Void
> deactivationStarted
= null;
33 private CompletableFuture
<Void
> deactivated
= null;
36 private Set
<Dependency
<?
>> dependants
= new HashSet
<>();
38 private RankingKey rankingKey
;
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;
45 assert dependencies
!= null;
46 assert classes
!= null;
48 this.instance
= instance
;
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
));
60 this.types
= Collections
.unmodifiableMap(types
);
63 this.dependencies
= Collections
.unmodifiableSet(new HashSet
<>(dependencies
));
64 for (Dependency
<?
> dependency
: this.dependencies
) {
65 dependency
.setDependantComponent(this);
68 // deactivated by default
69 deactivated
= CompletableFuture
.completedFuture(null);
70 deactivationStarted
= CompletableFuture
.completedFuture(null);
72 // TODO check whether context is active, so that we start right away
73 prepareNextActivation();
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
);
82 private void prepareNextActivation() {
83 activationStarted
= new CompletableFuture
<Void
>();
84 activated
= activationStarted
//
85 .thenComposeAsync(this::dependenciesActivated
) //
86 .thenRun(this.init
) //
87 .thenRun(() -> prepareNextDeactivation());
90 private void prepareNextDeactivation() {
91 deactivationStarted
= new CompletableFuture
<Void
>();
92 deactivated
= deactivationStarted
//
93 .thenComposeAsync(this::dependantsDeactivated
) //
94 .thenRun(this.close
) //
95 .thenRun(() -> prepareNextActivation());
98 CompletableFuture
<Void
> getActivated() {
102 CompletableFuture
<Void
> getDeactivated() {
106 void startActivating() {
107 if (activated
.isDone() || activationStarted
.isDone())
109 activationStarted
.complete(null);
112 void startDeactivating() {
113 if (deactivated
.isDone() || deactivationStarted
.isDone())
115 deactivationStarted
.complete(null);
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
);
125 return CompletableFuture
.allOf(constraints
.toArray(new CompletableFuture
[constraints
.size()]));
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
);
135 CompletableFuture
<Void
> dependantsDeactivated
= CompletableFuture
136 .allOf(constraints
.toArray(new CompletableFuture
[constraints
.size()]));
137 return dependantsDeactivated
;
141 void addDependant(Dependency
<?
> dependant
) {
142 dependants
.add(dependant
);
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
);
157 public <T
> boolean isPublishedType(Class
<T
> clss
) {
158 return types
.containsKey(clss
);
161 public Map
<String
, Object
> getProperties() {
166 public int compareTo(Component
<?
> o
) {
167 return rankingKey
.compareTo(rankingKey
);
171 public int hashCode() {
172 Long serviceId
= (Long
) properties
.get(RankingKey
.SERVICE_ID
);
173 if (serviceId
!= null)
174 return serviceId
.intValue();
176 return super.hashCode();
180 public String
toString() {
181 List
<String
> classes
= new ArrayList
<>();
182 for (Class
<?
> clss
: types
.keySet()) {
183 classes
.add(clss
.getName());
185 return "Component " + classes
+ " " + properties
+ "";
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
;
193 private CompletableFuture
<T
> value
;
195 public PublishedType(Component
<?
extends T
> component
, Class
<T
> clss
) {
197 this.component
= component
;
198 value
= CompletableFuture
.completedFuture((T
) component
.instance
);
201 public Component
<?
> getPublisher() {
205 public Class
<T
> getType() {
209 public CompletionStage
<T
> getValue() {
210 return value
.minimalCompletionStage();
214 /** Builds a {@link Component}. */
215 public static class Builder
<I
> implements Supplier
<I
> {
216 private final I instance
;
218 private Runnable init
;
219 private Runnable close
;
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
<>();
225 public Builder(I instance
) {
226 this.instance
= instance
;
229 public Component
<I
> build(ComponentRegister register
) {
231 if (types
.isEmpty()) {
232 types
.add(getInstanceClass());
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
);
250 public Builder
<I
> addType(Class
<?
super I
> clss
) {
255 public Builder
<I
> addActivation(Runnable init
) {
256 if (this.init
!= null)
257 throw new IllegalArgumentException("init method is already set");
262 public Builder
<I
> addDeactivation(Runnable close
) {
263 if (this.close
!= null)
264 throw new IllegalArgumentException("close method is already set");
269 public <D
> Builder
<I
> addDependency(PublishedType
<D
> type
, Consumer
<D
> set
, Consumer
<D
> unset
) {
270 dependencies
.add(new Dependency
<D
>(type
, set
, unset
));
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
);
285 @SuppressWarnings("unchecked")
286 private Class
<I
> getInstanceClass() {
287 return (Class
<I
>) instance
.getClass();
292 static class Dependency
<D
> {
293 private PublishedType
<D
> type
;
294 private Consumer
<D
> set
;
295 private Consumer
<D
> unset
;
298 Component
<?
> dependantComponent
;
299 CompletableFuture
<Void
> setStage
;
300 CompletableFuture
<Void
> unsetStage
;
302 public Dependency(PublishedType
<D
> types
, Consumer
<D
> set
, Consumer
<D
> unset
) {
305 this.set
= set
!= null ? set
: t
-> {
307 this.unset
= unset
!= null ? unset
: t
-> {
312 void setDependantComponent(Component
<?
> component
) {
313 this.dependantComponent
= component
;
316 CompletableFuture
<Void
> publisherActivated() {
317 return type
.getPublisher().activated
.copy();
320 CompletableFuture
<Void
> dependantDeactivated() {
321 return dependantComponent
.deactivated
.copy();
324 CompletableFuture
<Void
> set(Void v
) {
325 return type
.value
.thenAccept(set
);
328 CompletableFuture
<Void
> unset(Void v
) {
329 return type
.value
.thenAccept(unset
);