--- /dev/null
+package org.argeo.api.register;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * A wrapper for an object, whose dependencies and life cycle can be managed.
+ */
+public class Component<I> implements Supplier<I>, Comparable<Component<?>> {
+
+ private final I instance;
+
+ private final Runnable init;
+ private final Runnable close;
+
+ private final Map<Class<? super I>, PublishedType<? super I>> types;
+ private final Set<Dependency<?>> dependencies;
+ private final Map<String, Object> properties;
+
+ private CompletableFuture<Void> activationStarted = null;
+ private CompletableFuture<Void> activated = null;
+
+ private CompletableFuture<Void> deactivationStarted = null;
+ private CompletableFuture<Void> deactivated = null;
+
+ // internal
+ private Set<Dependency<?>> dependants = new HashSet<>();
+
+ private RankingKey rankingKey;
+
+ Component(ComponentRegister register, I instance, Runnable init, Runnable close, Set<Dependency<?>> dependencies,
+ Set<Class<? super I>> classes, Map<String, Object> properties) {
+ assert instance != null;
+ assert init != null;
+ assert close != null;
+ assert dependencies != null;
+ assert classes != null;
+
+ this.instance = instance;
+ this.init = init;
+ this.close = close;
+
+ // types
+ 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<>(this, clss));
+ }
+ this.types = Collections.unmodifiableMap(types);
+
+ // dependencies
+ this.dependencies = Collections.unmodifiableSet(new HashSet<>(dependencies));
+ for (Dependency<?> dependency : this.dependencies) {
+ dependency.setDependantComponent(this);
+ }
+
+ // deactivated by default
+ deactivated = CompletableFuture.completedFuture(null);
+ deactivationStarted = CompletableFuture.completedFuture(null);
+
+ // TODO check whether context is active, so that we start right away
+ prepareNextActivation();
+
+ long serviceId = register.register(this);
+ Map<String, Object> props = new HashMap<>(properties);
+ props.put(RankingKey.SERVICE_ID, serviceId);
+ this.properties = Collections.unmodifiableMap(props);
+ rankingKey = new RankingKey(properties);
+ }
+
+ private void prepareNextActivation() {
+ activationStarted = new CompletableFuture<Void>();
+ activated = activationStarted //
+ .thenComposeAsync(this::dependenciesActivated) //
+ .thenRun(this.init) //
+ .thenRun(() -> prepareNextDeactivation());
+ }
+
+ private void prepareNextDeactivation() {
+ deactivationStarted = new CompletableFuture<Void>();
+ deactivated = deactivationStarted //
+ .thenComposeAsync(this::dependantsDeactivated) //
+ .thenRun(this.close) //
+ .thenRun(() -> prepareNextActivation());
+ }
+
+ CompletableFuture<Void> getActivated() {
+ return activated;
+ }
+
+ 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) {
+ Set<CompletableFuture<?>> constraints = new HashSet<>(this.dependencies.size());
+ for (Dependency<?> dependency : this.dependencies) {
+ CompletableFuture<Void> dependencyActivated = dependency.publisherActivated() //
+ .thenCompose(dependency::set);
+ constraints.add(dependencyActivated);
+ }
+ return CompletableFuture.allOf(constraints.toArray(new CompletableFuture[constraints.size()]));
+ }
+
+ CompletableFuture<Void> dependantsDeactivated(Void v) {
+ Set<CompletableFuture<?>> constraints = new HashSet<>(this.dependants.size());
+ for (Dependency<?> dependant : this.dependants) {
+ CompletableFuture<Void> dependantDeactivated = dependant.dependantDeactivated() //
+ .thenCompose(dependant::unset);
+ constraints.add(dependantDeactivated);
+ }
+ CompletableFuture<Void> dependantsDeactivated = CompletableFuture
+ .allOf(constraints.toArray(new CompletableFuture[constraints.size()]));
+ return dependantsDeactivated;
+
+ }
+
+ void addDependant(Dependency<?> dependant) {
+ dependants.add(dependant);
+ }
+
+ @Override
+ public I get() {
+ return instance;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <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 <T> boolean isPublishedType(Class<T> clss) {
+ return types.containsKey(clss);
+ }
+
+ public Map<String, Object> getProperties() {
+ return properties;
+ }
+
+ @Override
+ public int compareTo(Component<?> o) {
+ return rankingKey.compareTo(rankingKey);
+ }
+
+ @Override
+ public int hashCode() {
+ Long serviceId = (Long) properties.get(RankingKey.SERVICE_ID);
+ if (serviceId != null)
+ return serviceId.intValue();
+ else
+ return super.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ List<String> classes = new ArrayList<>();
+ for (Class<?> clss : types.keySet()) {
+ classes.add(clss.getName());
+ }
+ return "Component " + classes + " " + properties + "";
+ }
+
+ /** A type which has been explicitly exposed by a component. */
+ public static class PublishedType<T> {
+ private Component<? extends T> component;
+ private Class<T> clss;
+
+ private CompletableFuture<T> value;
+
+ public PublishedType(Component<? extends T> component, Class<T> clss) {
+ this.clss = clss;
+ this.component = component;
+ value = CompletableFuture.completedFuture((T) component.instance);
+ }
+
+ public Component<?> getPublisher() {
+ return component;
+ }
+
+ public Class<T> getType() {
+ return clss;
+ }
+
+ public CompletionStage<T> getValue() {
+ return value.minimalCompletionStage();
+ }
+ }
+
+ /** Builds a {@link Component}. */
+ public static class Builder<I> implements Supplier<I> {
+ private final I instance;
+
+ private Runnable init;
+ private Runnable close;
+
+ private Set<Dependency<?>> dependencies = new HashSet<>();
+ private Set<Class<? super I>> types = new HashSet<>();
+ private final Map<String, Object> properties = new HashMap<>();
+
+ public Builder(I instance) {
+ this.instance = instance;
+ }
+
+ public Component<I> build(ComponentRegister register) {
+ // default values
+ if (types.isEmpty()) {
+ types.add(getInstanceClass());
+ }
+
+ if (init == null)
+ init = () -> {
+ };
+ if (close == null)
+ close = () -> {
+ };
+
+ // instantiation
+ Component<I> component = new Component<I>(register, instance, init, close, dependencies, types, properties);
+ for (Dependency<?> dependency : dependencies) {
+ dependency.type.getPublisher().addDependant(dependency);
+ }
+ return component;
+ }
+
+ public Builder<I> addType(Class<? super I> clss) {
+ types.add(clss);
+ return this;
+ }
+
+ public Builder<I> addActivation(Runnable init) {
+ if (this.init != null)
+ throw new IllegalArgumentException("init method is already set");
+ this.init = init;
+ return this;
+ }
+
+ public Builder<I> addDeactivation(Runnable close) {
+ if (this.close != null)
+ throw new IllegalArgumentException("close method is already set");
+ this.close = close;
+ return this;
+ }
+
+ public <D> Builder<I> addDependency(PublishedType<D> type, Consumer<D> set, Consumer<D> unset) {
+ dependencies.add(new Dependency<D>(type, set, unset));
+ return this;
+ }
+
+ public void addProperty(String key, Object value) {
+ if (properties.containsKey(key))
+ throw new IllegalStateException("Key " + key + " is already set.");
+ properties.put(key, value);
+ }
+
+ @Override
+ public I get() {
+ return instance;
+ }
+
+ @SuppressWarnings("unchecked")
+ private Class<I> getInstanceClass() {
+ return (Class<I>) instance.getClass();
+ }
+
+ }
+
+ static class Dependency<D> {
+ private PublishedType<D> type;
+ private Consumer<D> set;
+ private Consumer<D> unset;
+
+ // live
+ Component<?> dependantComponent;
+ CompletableFuture<Void> setStage;
+ CompletableFuture<Void> unsetStage;
+
+ public Dependency(PublishedType<D> types, Consumer<D> set, Consumer<D> unset) {
+ super();
+ this.type = types;
+ this.set = set != null ? set : t -> {
+ };
+ this.unset = unset != null ? unset : t -> {
+ };
+ }
+
+ // live
+ void setDependantComponent(Component<?> component) {
+ this.dependantComponent = component;
+ }
+
+ CompletableFuture<Void> publisherActivated() {
+ return type.getPublisher().activated.copy();
+ }
+
+ CompletableFuture<Void> dependantDeactivated() {
+ return dependantComponent.deactivated.copy();
+ }
+
+ CompletableFuture<Void> set(Void v) {
+ return type.value.thenAccept(set);
+ }
+
+ CompletableFuture<Void> unset(Void v) {
+ return type.value.thenAccept(unset);
+ }
+
+ }
+}