Massive package refactoring
[lgpl/argeo-commons.git] / org.argeo.api.register / src / org / argeo / api / register / Component.java
diff --git a/org.argeo.api.register/src/org/argeo/api/register/Component.java b/org.argeo.api.register/src/org/argeo/api/register/Component.java
new file mode 100644 (file)
index 0000000..f4676d9
--- /dev/null
@@ -0,0 +1,333 @@
+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);
+               }
+
+       }
+}