package org.argeo.util.register;
-import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.IdentityHashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
-import java.util.function.Predicate;
-
-public class Component {
- private final static AtomicBoolean started = new AtomicBoolean(false);
- private final static IdentityHashMap<Object, Component> components = new IdentityHashMap<>();
-
- private static synchronized void registerComponent(Component component) {
- if (started.get()) // TODO make it rellay dynamic
- throw new IllegalStateException("Already activated");
- if (components.containsKey(component.instance))
- throw new IllegalArgumentException("Already registered as component");
- components.put(component.instance, component);
- }
-
- static synchronized Component get(Object instance) {
- if (!components.containsKey(instance))
- throw new IllegalArgumentException("Not registered as component");
- return components.get(instance);
- }
- public synchronized static void activate() {
- if (started.get())
- throw new IllegalStateException("Already activated");
- for (Component component : components.values()) {
- component.activationStarted.complete(null);
- }
- started.set(true);
- }
-
- public synchronized static void deactivate() {
- if (!started.get())
- throw new IllegalStateException("Not activated");
- for (Component component : components.values()) {
- component.deactivationStarted.complete(null);
- }
- started.set(false);
- }
+/**
+ * A wrapper for an object, whose dependencies and life cycle can be managed.
+ */
+public class Component<I> {
- private final Object instance;
+ private final I instance;
- private Runnable init;
- private Runnable close;
+ private final Runnable init;
+ private final Runnable close;
- private final Map<Class<?>, PublishedType<?>> types;
+ private final Map<Class<? super I>, PublishedType<? super I>> types;
private final Set<Dependency<?>> dependencies;
- private CompletableFuture<Void> activationStarted = new CompletableFuture<Void>();
- private CompletableFuture<Void> activated = new CompletableFuture<Void>();
+ private CompletableFuture<Void> activationStarted = null;
+ private CompletableFuture<Void> activated = null;
- private CompletableFuture<Void> deactivationStarted = new CompletableFuture<Void>();
- private CompletableFuture<Void> deactivated = new CompletableFuture<Void>();
+ private CompletableFuture<Void> deactivationStarted = null;
+ private CompletableFuture<Void> deactivated = null;
private Set<Dependency<?>> dependants = new HashSet<>();
- Component(Object instance, Runnable init, Runnable close, Set<Dependency<?>> dependencies, Set<Class<?>> classes) {
+ Component(I instance, Runnable init, Runnable close, Set<Dependency<?>> dependencies,
+ Set<Class<? super I>> classes) {
assert instance != null;
assert init != null;
assert close != null;
this.close = close;
// types
- Map<Class<?>, PublishedType<?>> types = new HashMap<>(classes.size());
- for (Class<?> clss : classes) {
+ Map<Class<? super I>, PublishedType<? super I>> types = new HashMap<>(classes.size());
+ for (Class<? super I> clss : classes) {
if (!clss.isAssignableFrom(instance.getClass()))
throw new IllegalArgumentException(
"Type " + clss.getName() + " is not compatible with " + instance.getClass().getName());
- types.put(clss, new PublishedType<>(clss));
+ types.put(clss, new PublishedType<>(this, clss));
}
this.types = Collections.unmodifiableMap(types);
dependency.setDependantComponent(this);
}
- // future activation
+ // deactivated by default
+ deactivated = CompletableFuture.completedFuture(null);
+ deactivationStarted = CompletableFuture.completedFuture(null);
+
+ // TODO check whether context is active, so that we start right away
+ prepareNextActivation();
+
+ StaticRegister.registerComponent(this);
+ }
+
+ private void prepareNextActivation() {
+ activationStarted = new CompletableFuture<Void>();
activated = activationStarted //
- .thenCompose(this::dependenciesActivated) //
- .thenRun(this.init);
+ .thenComposeAsync(this::dependenciesActivated) //
+ .thenRun(this.init) //
+ .thenRun(() -> prepareNextDeactivation());
+ }
- // future deactivation
+ private void prepareNextDeactivation() {
+ deactivationStarted = new CompletableFuture<Void>();
deactivated = deactivationStarted //
- .thenCompose(this::dependantsDeactivated) //
- .thenRun(this.close);
+ .thenComposeAsync(this::dependantsDeactivated) //
+ .thenRun(this.close) //
+ .thenRun(() -> prepareNextActivation());
+ }
- registerComponent(this);
+ public CompletableFuture<Void> getActivated() {
+ return activated;
+ }
+
+ public CompletableFuture<Void> getDeactivated() {
+ return deactivated;
+ }
+
+ void startActivating() {
+ if (activated.isDone() || activationStarted.isDone())
+ return;
+ activationStarted.complete(null);
+ }
+
+ void startDeactivating() {
+ if (deactivated.isDone() || deactivationStarted.isDone())
+ return;
+ deactivationStarted.complete(null);
}
CompletableFuture<Void> dependenciesActivated(Void v) {
dependants.add(dependant);
}
- public <T> PublishedType<T> getType(Class<T> clss) {
+ I getInstance() {
+ return instance;
+ }
+
+ @SuppressWarnings("unchecked")
+ <T> PublishedType<T> getType(Class<T> clss) {
if (!types.containsKey(clss))
throw new IllegalArgumentException(clss.getName() + " is not a type published by this component");
return (PublishedType<T>) types.get(clss);
}
- public class PublishedType<T> {
+ public static class PublishedType<T> {
+ private Component<? extends T> component;
private Class<T> clss;
private CompletableFuture<T> value;
- public PublishedType(Class<T> clss) {
+ public PublishedType(Component<? extends T> component, Class<T> clss) {
this.clss = clss;
-
- value = CompletableFuture.completedFuture((T) Component.this.instance);
+ this.component = component;
+ value = CompletableFuture.completedFuture((T) component.instance);
}
- Component getPublisher() {
- return Component.this;
+ Component<?> getPublisher() {
+ return component;
}
Class<T> getType() {
private Runnable close;
private Set<Dependency<?>> dependencies = new HashSet<>();
- private Set<Class<?>> types = new HashSet<>();
+ private Set<Class<? super I>> types = new HashSet<>();
public Builder(I instance) {
this.instance = instance;
}
- public Component build() {
+ public Component<I> build() {
+ // default values
if (types.isEmpty()) {
- types.add(instance.getClass());
+ types.add(getInstanceClass());
}
if (init == null)
close = () -> {
};
- Component component = new Component(instance, init, close, dependencies, types);
+ // instantiation
+ Component<I> component = new Component<I>(instance, init, close, dependencies, types);
for (Dependency<?> dependency : dependencies) {
dependency.type.getPublisher().addDependant(dependency);
}
return component;
}
- public Builder<I> addType(Class<?>... classes) {
- types.addAll(Arrays.asList(classes));
+ public Builder<I> addType(Class<? super I> clss) {
+ types.add(clss);
return this;
}
return this;
}
- public <D> Builder<I> addDependency(PublishedType<D> type, Predicate<?> filter, Consumer<D> set,
- Consumer<D> unset) {
- dependencies.add(new Dependency<D>(type, filter, set, unset));
+ public <D> Builder<I> addDependency(PublishedType<D> type, Consumer<D> set, Consumer<D> unset) {
+ dependencies.add(new Dependency<D>(type, set, unset));
return this;
}
return instance;
}
+ @SuppressWarnings("unchecked")
+ private Class<I> getInstanceClass() {
+ return (Class<I>) instance.getClass();
+ }
+
}
static class Dependency<D> {
private PublishedType<D> type;
- private Predicate<?> filter;
private Consumer<D> set;
private Consumer<D> unset;
// live
- Component dependantComponent;
+ Component<?> dependantComponent;
CompletableFuture<Void> setStage;
CompletableFuture<Void> unsetStage;
- public Dependency(PublishedType<D> types, Predicate<?> filter, Consumer<D> set, Consumer<D> unset) {
+ public Dependency(PublishedType<D> types, Consumer<D> set, Consumer<D> unset) {
super();
this.type = types;
- this.filter = filter;
this.set = set;
this.unset = unset != null ? unset : (v) -> set.accept(null);
}
// live
- void setDependantComponent(Component component) {
+ void setDependantComponent(Component<?> component) {
this.dependantComponent = component;
}
- Component getPublisher() {
+ Component<?> getPublisher() {
return type.getPublisher();
}
- Component getDependantComponent() {
+ Component<?> getDependantComponent() {
return dependantComponent;
}
}
}
-
--- /dev/null
+package org.argeo.util.register;
+
+import java.util.HashSet;
+import java.util.IdentityHashMap;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/** A minimal component register. */
+public class StaticRegister {
+ private final static AtomicBoolean started = new AtomicBoolean(false);
+ private final static IdentityHashMap<Object, Component<?>> components = new IdentityHashMap<>();
+
+ static synchronized void registerComponent(Component<?> component) {
+ if (started.get()) // TODO make it really dynamic
+ throw new IllegalStateException("Already activated");
+ if (components.containsKey(component.getInstance()))
+ throw new IllegalArgumentException("Already registered as component");
+ components.put(component.getInstance(), component);
+ }
+
+ static synchronized Component<?> get(Object instance) {
+ if (!components.containsKey(instance))
+ throw new IllegalArgumentException("Not registered as component");
+ return components.get(instance);
+ }
+
+ synchronized static void activate() {
+ if (started.get())
+ throw new IllegalStateException("Already activated");
+ Set<CompletableFuture<?>> constraints = new HashSet<>();
+ for (Component<?> component : components.values()) {
+ component.startActivating();
+ constraints.add(component.getActivated());
+ }
+
+ // wait
+ try {
+ CompletableFuture.allOf(constraints.toArray(new CompletableFuture[0])).thenRun(() -> started.set(true))
+ .get();
+ } catch (ExecutionException | InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ synchronized static void deactivate() {
+ if (!started.get())
+ throw new IllegalStateException("Not activated");
+ Set<CompletableFuture<?>> constraints = new HashSet<>();
+ for (Component<?> component : components.values()) {
+ component.startDeactivating();
+ constraints.add(component.getDeactivated());
+ }
+
+ // wait
+ try {
+ CompletableFuture.allOf(constraints.toArray(new CompletableFuture[0])).thenRun(() -> started.set(false))
+ .get();
+ } catch (ExecutionException | InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}