]>
git.argeo.org Git - lgpl/argeo-commons.git/blob - org.argeo.util/src/org/argeo/util/register/Component.java
1 package org
.argeo
.util
.register
;
3 import java
.util
.Collections
;
4 import java
.util
.HashMap
;
5 import java
.util
.HashSet
;
8 import java
.util
.concurrent
.CompletableFuture
;
9 import java
.util
.function
.Consumer
;
12 * A wrapper for an object, whose dependencies and life cycle can be managed.
14 public class Component
<I
> {
16 private final I instance
;
18 private final Runnable init
;
19 private final Runnable close
;
21 private final Map
<Class
<?
super I
>, PublishedType
<?
super I
>> types
;
22 private final Set
<Dependency
<?
>> dependencies
;
24 private CompletableFuture
<Void
> activationStarted
= null;
25 private CompletableFuture
<Void
> activated
= null;
27 private CompletableFuture
<Void
> deactivationStarted
= null;
28 private CompletableFuture
<Void
> deactivated
= null;
30 private Set
<Dependency
<?
>> dependants
= new HashSet
<>();
32 Component(Consumer
<Component
<?
>> register
, I instance
, Runnable init
, Runnable close
,
33 Set
<Dependency
<?
>> dependencies
, Set
<Class
<?
super I
>> classes
) {
34 assert instance
!= null;
37 assert dependencies
!= null;
38 assert classes
!= null;
40 this.instance
= instance
;
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
));
52 this.types
= Collections
.unmodifiableMap(types
);
55 this.dependencies
= Collections
.unmodifiableSet(new HashSet
<>(dependencies
));
56 for (Dependency
<?
> dependency
: this.dependencies
) {
57 dependency
.setDependantComponent(this);
60 // deactivated by default
61 deactivated
= CompletableFuture
.completedFuture(null);
62 deactivationStarted
= CompletableFuture
.completedFuture(null);
64 // TODO check whether context is active, so that we start right away
65 prepareNextActivation();
67 register
.accept(this);
70 private void prepareNextActivation() {
71 activationStarted
= new CompletableFuture
<Void
>();
72 activated
= activationStarted
//
73 .thenComposeAsync(this::dependenciesActivated
) //
74 .thenRun(this.init
) //
75 .thenRun(() -> prepareNextDeactivation());
78 private void prepareNextDeactivation() {
79 deactivationStarted
= new CompletableFuture
<Void
>();
80 deactivated
= deactivationStarted
//
81 .thenComposeAsync(this::dependantsDeactivated
) //
82 .thenRun(this.close
) //
83 .thenRun(() -> prepareNextActivation());
86 public CompletableFuture
<Void
> getActivated() {
90 public CompletableFuture
<Void
> getDeactivated() {
94 void startActivating() {
95 if (activated
.isDone() || activationStarted
.isDone())
97 activationStarted
.complete(null);
100 void startDeactivating() {
101 if (deactivated
.isDone() || deactivationStarted
.isDone())
103 deactivationStarted
.complete(null);
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
.publisherActivated() //
110 .thenCompose(dependency
::set
);
111 constraints
.add(dependencyActivated
);
113 return CompletableFuture
.allOf(constraints
.toArray(new CompletableFuture
[constraints
.size()]));
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
.dependantDeactivated() //
120 .thenCompose(dependant
::unset
);
121 constraints
.add(dependantDeactivated
);
123 CompletableFuture
<Void
> dependantsDeactivated
= CompletableFuture
124 .allOf(constraints
.toArray(new CompletableFuture
[constraints
.size()]));
125 return dependantsDeactivated
;
129 void addDependant(Dependency
<?
> dependant
) {
130 dependants
.add(dependant
);
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
);
144 <T
> boolean isPublishedType(Class
<T
> clss
) {
145 return types
.containsKey(clss
);
148 public static class PublishedType
<T
> {
149 private Component
<?
extends T
> component
;
150 private Class
<T
> clss
;
152 // private CompletableFuture<Component<? extends T>> publisherAvailable;
153 private CompletableFuture
<T
> value
;
155 public PublishedType(Component
<?
extends T
> component
, Class
<T
> clss
) {
157 this.component
= component
;
158 value
= CompletableFuture
.completedFuture((T
) component
.instance
);
159 // value = publisherAvailable.thenApply((c) -> c.getInstance());
162 Component
<?
> getPublisher() {
166 // CompletableFuture<Component<? extends T>> publisherAvailable() {
167 // return publisherAvailable;
175 public static class Builder
<I
> {
176 private final I instance
;
178 private Runnable init
;
179 private Runnable close
;
181 private Set
<Dependency
<?
>> dependencies
= new HashSet
<>();
182 private Set
<Class
<?
super I
>> types
= new HashSet
<>();
184 public Builder(I instance
) {
185 this.instance
= instance
;
188 public Component
<I
> build(Consumer
<Component
<?
>> register
) {
190 if (types
.isEmpty()) {
191 types
.add(getInstanceClass());
202 Component
<I
> component
= new Component
<I
>(register
, instance
, init
, close
, dependencies
, types
);
203 for (Dependency
<?
> dependency
: dependencies
) {
204 dependency
.type
.getPublisher().addDependant(dependency
);
209 public Builder
<I
> addType(Class
<?
super I
> clss
) {
214 public Builder
<I
> addInit(Runnable init
) {
215 if (this.init
!= null)
216 throw new IllegalArgumentException("init method is already set");
221 public Builder
<I
> addClose(Runnable close
) {
222 if (this.close
!= null)
223 throw new IllegalArgumentException("close method is already set");
228 public <D
> Builder
<I
> addDependency(PublishedType
<D
> type
, Consumer
<D
> set
, Consumer
<D
> unset
) {
229 dependencies
.add(new Dependency
<D
>(type
, set
, unset
));
237 @SuppressWarnings("unchecked")
238 private Class
<I
> getInstanceClass() {
239 return (Class
<I
>) instance
.getClass();
244 static class Dependency
<D
> {
245 private PublishedType
<D
> type
;
246 private Consumer
<D
> set
;
247 private Consumer
<D
> unset
;
250 Component
<?
> dependantComponent
;
251 CompletableFuture
<Void
> setStage
;
252 CompletableFuture
<Void
> unsetStage
;
254 public Dependency(PublishedType
<D
> types
, Consumer
<D
> set
, Consumer
<D
> unset
) {
258 this.unset
= unset
!= null ? unset
: (v
) -> set
.accept(null);
262 void setDependantComponent(Component
<?
> component
) {
263 this.dependantComponent
= component
;
266 CompletableFuture
<Void
> publisherActivated() {
267 return type
.getPublisher().activated
.copy();
270 CompletableFuture
<Void
> dependantDeactivated() {
271 return dependantComponent
.deactivated
.copy();
274 CompletableFuture
<Void
> set(Void v
) {
275 return type
.value
.thenAccept(set
);
278 CompletableFuture
<Void
> unset(Void v
) {
279 return type
.value
.thenAccept(unset
);