package org.argeo.util.register; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; /** * A wrapper for an object, whose dependencies and life cycle can be managed. */ public class Component { private final I instance; private final Runnable init; private final Runnable close; private final Map, PublishedType> types; private final Set> dependencies; private CompletableFuture activationStarted = null; private CompletableFuture activated = null; private CompletableFuture deactivationStarted = null; private CompletableFuture deactivated = null; private Set> dependants = new HashSet<>(); Component(Consumer> register, I instance, Runnable init, Runnable close, Set> dependencies, Set> classes) { 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, PublishedType> types = new HashMap<>(classes.size()); for (Class 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(); register.accept(this); } private void prepareNextActivation() { activationStarted = new CompletableFuture(); activated = activationStarted // .thenComposeAsync(this::dependenciesActivated) // .thenRun(this.init) // .thenRun(() -> prepareNextDeactivation()); } private void prepareNextDeactivation() { deactivationStarted = new CompletableFuture(); deactivated = deactivationStarted // .thenComposeAsync(this::dependantsDeactivated) // .thenRun(this.close) // .thenRun(() -> prepareNextActivation()); } public CompletableFuture getActivated() { return activated; } public CompletableFuture 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 dependenciesActivated(Void v) { Set> constraints = new HashSet<>(this.dependencies.size()); for (Dependency dependency : this.dependencies) { CompletableFuture dependencyActivated = dependency.publisherActivated() // .thenCompose(dependency::set); constraints.add(dependencyActivated); } return CompletableFuture.allOf(constraints.toArray(new CompletableFuture[constraints.size()])); } CompletableFuture dependantsDeactivated(Void v) { Set> constraints = new HashSet<>(this.dependants.size()); for (Dependency dependant : this.dependants) { CompletableFuture dependantDeactivated = dependant.dependantDeactivated() // .thenCompose(dependant::unset); constraints.add(dependantDeactivated); } CompletableFuture dependantsDeactivated = CompletableFuture .allOf(constraints.toArray(new CompletableFuture[constraints.size()])); return dependantsDeactivated; } void addDependant(Dependency dependant) { dependants.add(dependant); } I getInstance() { return instance; } @SuppressWarnings("unchecked") PublishedType getType(Class clss) { if (!types.containsKey(clss)) throw new IllegalArgumentException(clss.getName() + " is not a type published by this component"); return (PublishedType) types.get(clss); } boolean isPublishedType(Class clss) { return types.containsKey(clss); } public static class PublishedType { private Component component; private Class clss; // private CompletableFuture> publisherAvailable; private CompletableFuture value; public PublishedType(Component component, Class clss) { this.clss = clss; this.component = component; value = CompletableFuture.completedFuture((T) component.instance); // value = publisherAvailable.thenApply((c) -> c.getInstance()); } Component getPublisher() { return component; } // CompletableFuture> publisherAvailable() { // return publisherAvailable; // } Class getType() { return clss; } } public static class Builder { private final I instance; private Runnable init; private Runnable close; private Set> dependencies = new HashSet<>(); private Set> types = new HashSet<>(); public Builder(I instance) { this.instance = instance; } public Component build(Consumer> register) { // default values if (types.isEmpty()) { types.add(getInstanceClass()); } if (init == null) init = () -> { }; if (close == null) close = () -> { }; // instantiation Component component = new Component(register, instance, init, close, dependencies, types); for (Dependency dependency : dependencies) { dependency.type.getPublisher().addDependant(dependency); } return component; } public Builder addType(Class clss) { types.add(clss); return this; } public Builder addInit(Runnable init) { if (this.init != null) throw new IllegalArgumentException("init method is already set"); this.init = init; return this; } public Builder addClose(Runnable close) { if (this.close != null) throw new IllegalArgumentException("close method is already set"); this.close = close; return this; } public Builder addDependency(PublishedType type, Consumer set, Consumer unset) { dependencies.add(new Dependency(type, set, unset)); return this; } public I get() { return instance; } @SuppressWarnings("unchecked") private Class getInstanceClass() { return (Class) instance.getClass(); } } static class Dependency { private PublishedType type; private Consumer set; private Consumer unset; // live Component dependantComponent; CompletableFuture setStage; CompletableFuture unsetStage; public Dependency(PublishedType types, Consumer set, Consumer unset) { super(); this.type = types; this.set = set; this.unset = unset != null ? unset : (v) -> set.accept(null); } // live void setDependantComponent(Component component) { this.dependantComponent = component; } CompletableFuture publisherActivated() { return type.getPublisher().activated.copy(); } CompletableFuture dependantDeactivated() { return dependantComponent.deactivated.copy(); } CompletableFuture set(Void v) { return type.value.thenAccept(set); } CompletableFuture unset(Void v) { return type.value.thenAccept(unset); } } }