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 implements Supplier, Comparable> { private final I instance; private final Runnable init; private final Runnable close; private final Map, PublishedType> types; private final Set> dependencies; private final Map properties; private CompletableFuture activationStarted = null; private CompletableFuture activated = null; private CompletableFuture deactivationStarted = null; private CompletableFuture deactivated = null; // internal private Set> dependants = new HashSet<>(); private RankingKey rankingKey; Component(ComponentRegister register, I instance, Runnable init, Runnable close, Set> dependencies, Set> classes, Map 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, 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(); long serviceId = register.register(this); Map 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(); 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()); } CompletableFuture getActivated() { return activated; } 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); } @Override public I get() { return instance; } @SuppressWarnings("unchecked") public 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); } public boolean isPublishedType(Class clss) { return types.containsKey(clss); } public Map 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 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 { private Component component; private Class clss; private CompletableFuture value; public PublishedType(Component component, Class clss) { this.clss = clss; this.component = component; value = CompletableFuture.completedFuture((T) component.instance); } public Component getPublisher() { return component; } public Class getType() { return clss; } public CompletionStage getValue() { return value.minimalCompletionStage(); } } /** Builds a {@link Component}. */ public static class Builder implements Supplier { private final I instance; private Runnable init; private Runnable close; private Set> dependencies = new HashSet<>(); private Set> types = new HashSet<>(); private final Map properties = new HashMap<>(); public Builder(I instance) { this.instance = instance; } public Component build(ComponentRegister 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, properties); for (Dependency dependency : dependencies) { dependency.type.getPublisher().addDependant(dependency); } return component; } public Builder addType(Class clss) { types.add(clss); return this; } public Builder addActivation(Runnable init) { if (this.init != null) throw new IllegalArgumentException("init method is already set"); this.init = init; return this; } public Builder addDeactivation(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 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 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 != null ? set : t -> { }; this.unset = unset != null ? unset : t -> { }; } // 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); } } }