package net.iplantevin.ql.gui.main; import net.iplantevin.ql.evaluation.EvaluationVisitor; import net.iplantevin.ql.evaluation.Value; import net.iplantevin.ql.gui.formcomponents.AbstractFormComponent; import net.iplantevin.ql.gui.formcomponents.AbstractWidgetContainer; import org.antlr.v4.runtime.misc.OrderedHashSet; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.Executors; /** * A FormEventManager manages subscription of (Abstract)FormComponents to events * on variables (identifiers) in the form. It also manages the queue of re-evaluations * that have to be performed, in a sequential way. This class uses antlr4's * "OrderedHashSet", Set implementation that maintains the order in which * elements were added. * * @author Ivan */ public class FormEventManager { private final Map<String, Set<AbstractFormComponent>> subscriptions; private final Executor executor; private final EvaluationVisitor evaluator; /** * Constructor that stores a reference to the evaluator used by the creator. * (The evaluator is needed to store updated values.) * * @param evaluator the EvaluationVisitor object to use. */ public FormEventManager(EvaluationVisitor evaluator) { subscriptions = new HashMap<String, Set<AbstractFormComponent>>(); executor = Executors.newSingleThreadExecutor(); this.evaluator = evaluator; } /** * Subscribes given AbstractFormComponent to all identifiers it wants to * receive updates from. * * @param formComponent the AbstractFormComponent to subscribe. * @param ids the set of identifiers the given component wants to be * subscribed to. */ public void subscribe(AbstractFormComponent formComponent, Set<String> ids) { for (String identifier : ids) { subscribeComponentToIdentifier(formComponent, identifier); } } /** * Subscribes given component to given identifier. If there are no subscriptions * yet for this identifier, an entry in the subscriptions map is created. * * @param formComponent the component to subscribe. * @param identifier the identifier the given component should be * subscribed to. */ private void subscribeComponentToIdentifier(AbstractFormComponent formComponent, String identifier) { if (hasSubscriptionsForIdentifier(identifier)) { subscriptions.get(identifier).add(formComponent); } else { HashSet<AbstractFormComponent> subscribedComponents = new OrderedHashSet<AbstractFormComponent>(); subscribedComponents.add(formComponent); subscriptions.put(identifier, subscribedComponents); } } /** * Adds a (re-)evalution to the queue of the Executor as a Runnable. The * Runnable will send the reEvaluate() message to all AbstractFormComponents * subscribed to the given identifier. The updated value is furthermore * only put in the EvaluationVisitor when the Runnable is executed. * * @param source * @param value the new value for given identifier. */ public void scheduleEvaluation(final AbstractWidgetContainer source, final Value value) { executor.execute(new Runnable() { public void run() { String identifier = source.getIdentifier(); if (hasSubscriptionsForIdentifier(identifier)) { evaluator.storeValue(identifier, value); for (AbstractFormComponent component : subscriptions.get(identifier)) { reEvaluateComponent(component, source); } } } }); } private void reEvaluateComponent(AbstractFormComponent component, AbstractWidgetContainer source) { // Source must not trigger re-evaluation on itself. if (component == source) { return; } component.reEvaluate(); } private boolean hasSubscriptionsForIdentifier(String identifier) { return subscriptions.containsKey(identifier); } }