Beispiel #1
0
 private Stub(Map<Object, Integer> variablesIndex, Tuple variablesTuple, HandleType handle) {
   super();
   this.variablesIndex = variablesIndex;
   this.variablesTuple = variablesTuple;
   this.handle = handle;
   this.constraints = CollectionsFactory.getSet(); // new HashSet<PConstraint>();
 }
Beispiel #2
0
 /** @return the new constraints enforced at this handle, that aren't yet enforced at parents */
 public Set<PConstraint> getDeltaEnforcedConstraints() {
   Set<PConstraint> result = CollectionsFactory.getSet(); // new HashSet<PConstraint>(constraints);
   if (primaryParentStub != null) result.removeAll(primaryParentStub.getAllEnforcedConstraints());
   if (secondaryParentStub != null)
     result.removeAll(secondaryParentStub.getAllEnforcedConstraints());
   return result;
 }
 private Collection<ReteNodeRecipe> getSameClassRecipes(final ReteNodeRecipe recipe) {
   Collection<ReteNodeRecipe> sameClassRecipes =
       reteContainer.network.primaryRecipesByClass.get(recipe.eClass());
   if (sameClassRecipes == null) {
     sameClassRecipes = CollectionsFactory.getSet();
     reteContainer.network.primaryRecipesByClass.put(recipe.eClass(), sameClassRecipes);
   }
   return sameClassRecipes;
 }
  /**
   * @param threads the number of threads to operate the network with; 0 means single-threaded
   *     operation, 1 starts an asynchronous thread to operate the RETE net, >1 uses multiple RETE
   *     containers.
   */
  public Network(int threads, IPatternMatcherRuntimeContext context) {
    super();
    this.threads = threads;
    this.context = context;
    this.inputConnector = new InputConnector(this);
    this.nodeFactory = new NodeFactory(context);

    containers = new ArrayList<ReteContainer>();
    firstContainer = (threads > 1) ? Options.firstFreeContainer : 0; // NOPMD
    nextContainer = firstContainer;

    if (threads > 0) {
      globalTerminationCriteria =
          CollectionsFactory.getMap(); // new HashMap<ReteContainer, Long>();
      reportedClocks = CollectionsFactory.getMap(); // new HashMap<ReteContainer, Long>();
      ReadWriteLock rwl = new ReentrantReadWriteLock();
      updateLock = rwl.readLock();
      structuralChangeLock = rwl.writeLock();
      for (int i = 0; i < threads; ++i) containers.add(new ReteContainer(this, true));
    } else containers.add(new ReteContainer(this, false));

    headContainer = containers.get(0);
  }
/**
 * Stores the internal parts of a rete network. Nodes are stored according to type and parameters.
 *
 * @author Gabor Bergmann
 */
public class NodeProvisioner {

  // boolean activeStorage = true;

  ReteContainer reteContainer;
  NodeFactory nodeFactory;
  ConnectionFactory connectionFactory;
  InputConnector inputConnector;

  // TODO as recipe?
  Map<Supplier, RemoteReceiver> remoteReceivers =
      CollectionsFactory.getMap(); // new HashMap<Supplier, RemoteReceiver>();
  Map<Address<? extends Supplier>, RemoteSupplier> remoteSuppliers =
      CollectionsFactory.getMap(); // new HashMap<Address<? extends Supplier>, RemoteSupplier>();

  /**
   * PRE: NodeFactory, ConnectionFactory must exist
   *
   * @param reteContainer the ReteNet whose interior is to be mapped.
   */
  public NodeProvisioner(ReteContainer reteContainer) {
    super();
    this.reteContainer = reteContainer;
    this.nodeFactory = reteContainer.getNodeFactory();
    this.connectionFactory = reteContainer.getConnectionFactory();
    this.inputConnector = reteContainer.getInputConnectionFactory();
  }

  public synchronized Address<? extends Node> getOrCreateNodeByRecipe(RecipeTraceInfo recipeTrace) {
    final ReteNodeRecipe recipe = recipeTrace.getRecipe();
    Address<? extends Node> result = getNodesByRecipe().get(recipe);
    if (result != null) {
      // NODE ALREADY CONSTRUCTED FOR RECIPE, only needs to add trace
      if (getRecipeTraces().add(recipeTrace)) result.getNodeCache().assignTraceInfo(recipeTrace);
    } else {
      // No node for this recipe object - but equivalent recipes still reusable
      Collection<ReteNodeRecipe> sameClassRecipes = getSameClassRecipes(recipe);
      for (ReteNodeRecipe knownRecipe : sameClassRecipes) {
        if (equivalentRecipes(recipe, knownRecipe)) {
          // FOUND EQUIVALENT RECIPE
          result = getNodesByRecipe().get(knownRecipe);
          getNodesByRecipe().put(recipe, result);
          result.getNodeCache().assignTraceInfo(recipeTrace);
          break;
        }
      }
      if (result == null) {
        // MUST INSTANTIATE NEW NODE FOR RECIPE
        final Node freshNode = instantiateNodeForRecipe(recipeTrace, recipe, sameClassRecipes);
        result = reteContainer.makeAddress(freshNode);
      }
    }
    return result;
  }

  private Set<RecipeTraceInfo> getRecipeTraces() {
    return reteContainer.network.recipeTraces;
  }

  private Node instantiateNodeForRecipe(
      RecipeTraceInfo recipeTrace,
      final ReteNodeRecipe recipe,
      Collection<ReteNodeRecipe> sameClassRecipes) {
    if (recipe instanceof IndexerRecipe) {

      // INSTANTIATE AND HOOK UP
      // (cannot delay hooking up, because parent determines indexer implementation)
      ensureParents(recipeTrace);
      final ReteNodeRecipe parentRecipe =
          recipeTrace.getParentRecipeTraces().iterator().next().getRecipe();
      final Indexer result =
          nodeFactory.createIndexer(
              reteContainer,
              (IndexerRecipe) recipe,
              asSupplier(
                  (Address<? extends Supplier>)
                      reteContainer.network.getExistingNodeByRecipe(parentRecipe)),
              recipeTrace);

      // REMEMBER
      if (Options.nodeSharingOption != Options.NodeSharingOption.NEVER) {
        getNodesByRecipe().put(recipe, reteContainer.makeAddress(result));
        sameClassRecipes.add(recipe);
      }

      return result;
    } else {

      // INSTANTIATE
      Node result = nodeFactory.createNode(reteContainer, recipe, recipeTrace);

      // REMEMBER
      if (Options.nodeSharingOption == Options.NodeSharingOption.ALL) {
        getNodesByRecipe().put(recipe, reteContainer.makeAddress(result));
        sameClassRecipes.add(recipe);
      }

      // HOOK UP
      // (recursion-tolerant due to this delayed order of initialization)
      ensureParents(recipeTrace);
      if (recipe instanceof InputRecipe) inputConnector.connectInput((InputRecipe) recipe, result);
      else connectionFactory.connectToParents(recipeTrace, result);

      return result;
    }
  }

  private Map<ReteNodeRecipe, Address<? extends Node>> getNodesByRecipe() {
    return reteContainer.network.nodesByRecipe;
  }

  private void ensureParents(RecipeTraceInfo recipeTrace) {
    for (RecipeTraceInfo parentTrace : recipeTrace.getParentRecipeTraces()) {
      getOrCreateNodeByRecipe(parentTrace);
    }
  }

  private boolean equivalentRecipes(ReteNodeRecipe recipe, ReteNodeRecipe knownRecipe) {
    // TODO reuse in more cases later, e.g. switching join node parents, etc.
    return EcoreUtil.equals(recipe, knownRecipe);
  }

  private Collection<ReteNodeRecipe> getSameClassRecipes(final ReteNodeRecipe recipe) {
    Collection<ReteNodeRecipe> sameClassRecipes =
        reteContainer.network.primaryRecipesByClass.get(recipe.eClass());
    if (sameClassRecipes == null) {
      sameClassRecipes = CollectionsFactory.getSet();
      reteContainer.network.primaryRecipesByClass.put(recipe.eClass(), sameClassRecipes);
    }
    return sameClassRecipes;
  }

  //// Remoting - TODO eliminate?

  synchronized RemoteReceiver accessRemoteReceiver(Address<? extends Supplier> address) {
    throw new UnsupportedOperationException("Multi-container Rete not supported yet");
    //        if (!reteContainer.isLocal(address))
    //            return address.getContainer().getProvisioner().accessRemoteReceiver(address);
    //        Supplier localSupplier = reteContainer.resolveLocal(address);
    //        RemoteReceiver result = remoteReceivers.get(localSupplier);
    //        if (result == null) {
    //            result = new RemoteReceiver(reteContainer);
    //            reteContainer.connect(localSupplier, result); // stateless node, no
    //                                                          // synch required
    //
    //            if (Options.nodeSharingOption != Options.NodeSharingOption.NEVER)
    //                remoteReceivers.put(localSupplier, result);
    //        }
    //        return result;
  }

  /** @pre: address is NOT local */
  synchronized RemoteSupplier accessRemoteSupplier(Address<? extends Supplier> address) {
    throw new UnsupportedOperationException("Multi-container Rete not supported yet");
    //        RemoteSupplier result = remoteSuppliers.get(address);
    //        if (result == null) {
    //            result = new RemoteSupplier(reteContainer, address.getContainer().getProvisioner()
    //                    .accessRemoteReceiver(address));
    //            // network.connectAndSynchronize(supplier, result);
    //
    //            if (Options.nodeSharingOption != Options.NodeSharingOption.NEVER)
    //                remoteSuppliers.put(address, result);
    //        }
    //        return result;
  }

  /** The powerful method for accessing any (supplier) Address as a local supplier. */
  public Supplier asSupplier(Address<? extends Supplier> address) {
    if (!reteContainer.isLocal(address)) return accessRemoteSupplier(address);
    else return reteContainer.resolveLocal(address);
  }

  // local version
  // TODO remove?
  public synchronized ProjectionIndexer accessProjectionIndexer(
      RecipeTraceInfo supplierTrace, TupleMask mask) {
    final org.eclipse.incquery.runtime.rete.recipes.ProjectionIndexerRecipe
        projectionIndexerRecipe = projectionIndexerRecipe(supplierTrace, mask);
    final UserRequestTrace indexerTrace =
        new UserRequestTrace(projectionIndexerRecipe, supplierTrace);
    final Address<? extends Node> address = getOrCreateNodeByRecipe(indexerTrace);
    return (ProjectionIndexer) reteContainer.resolveLocal(address);
  }
  // local version
  public synchronized ProjectionIndexer accessProjectionIndexerOnetime(
      RecipeTraceInfo supplierTrace, TupleMask mask) {
    if (Options.nodeSharingOption != Options.NodeSharingOption.NEVER)
      return accessProjectionIndexer(supplierTrace, mask);

    final Address<? extends Node> supplierAddress = getOrCreateNodeByRecipe(supplierTrace);
    Supplier supplier = (Supplier) reteContainer.resolveLocal(supplierAddress);

    reteContainer.flushUpdates();
    OnetimeIndexer result = new OnetimeIndexer(reteContainer, mask);
    reteContainer.sendConstructionUpdates(
        result, Direction.INSERT, reteContainer.pullContents(supplier));
    reteContainer.flushUpdates();

    return result;
  }

  // local, read-only version
  public synchronized ProjectionIndexer peekProjectionIndexer(
      RecipeTraceInfo supplierTrace, TupleMask mask) {
    final Address<? extends Node> address =
        getNodesByRecipe().get(projectionIndexerRecipe(supplierTrace, mask));
    return address == null ? null : (ProjectionIndexer) reteContainer.resolveLocal(address);
  }

  private org.eclipse.incquery.runtime.rete.recipes.ProjectionIndexerRecipe projectionIndexerRecipe(
      RecipeTraceInfo parentTrace, TupleMask mask) {
    return RecipesHelper.projectionIndexerRecipe(
        parentTrace.getRecipe(), RecipesHelper.mask(mask.sourceWidth, mask.indices));
  }

  //    public synchronized Address<? extends Supplier> accessValueBinderFilterNode(
  //            Address<? extends Supplier> supplierAddress, int bindingIndex, Object bindingValue)
  // {
  //        Supplier supplier = asSupplier(supplierAddress);
  //        Object[] paramsArray = { supplier.getNodeId(), bindingIndex, bindingValue };
  //        Tuple params = new FlatTuple(paramsArray);
  //        ValueBinderFilterNode result = valueBinderFilters.get(params);
  //        if (result == null) {
  //            result = new ValueBinderFilterNode(reteContainer, bindingIndex, bindingValue);
  //            reteContainer.connect(supplier, result); // stateless node, no synch
  //                                                     // required
  //
  //            if (Options.nodeSharingOption == Options.NodeSharingOption.ALL)
  //                valueBinderFilters.put(params, result);
  //        }
  //        return reteContainer.makeAddress(result);
  //    }

  /**
   * Returns a copy of the given indexer that is an active node by itself (created if does not
   * exist). (Convention: attached with same mask to a transparent node that is attached to parent
   * node.) Node is created if it does not exist yet.
   *
   * @return an identical but active indexer
   */
  // TODO rethink traceability
  RecipeTraceInfo accessActiveIndexer(RecipeTraceInfo inactiveIndexerRecipeTrace) {
    final RecipeTraceInfo parentRecipeTrace =
        inactiveIndexerRecipeTrace.getParentRecipeTraces().iterator().next();
    final org.eclipse.incquery.runtime.rete.recipes.ProjectionIndexerRecipe inactiveIndexerRecipe =
        (org.eclipse.incquery.runtime.rete.recipes.ProjectionIndexerRecipe)
            inactiveIndexerRecipeTrace.getRecipe();

    final TransparentRecipe transparentRecipe = RecipesFactory.eINSTANCE.createTransparentRecipe();
    transparentRecipe.setParent(parentRecipeTrace.getRecipe());
    final ActiveNodeConflictTrace transparentRecipeTrace =
        new ActiveNodeConflictTrace(
            transparentRecipe, parentRecipeTrace, inactiveIndexerRecipeTrace);

    final org.eclipse.incquery.runtime.rete.recipes.ProjectionIndexerRecipe activeIndexerRecipe =
        RecipesFactory.eINSTANCE.createProjectionIndexerRecipe();
    activeIndexerRecipe.setParent(transparentRecipe);
    activeIndexerRecipe.setMask(inactiveIndexerRecipe.getMask());
    final ActiveNodeConflictTrace activeIndexerRecipeTrace =
        new ActiveNodeConflictTrace(
            activeIndexerRecipe, transparentRecipeTrace, inactiveIndexerRecipeTrace);

    return activeIndexerRecipeTrace;
  }

  //	/**
  //	 * @param parent
  //	 * @return
  //	 */
  //	private TransparentNode accessTransparentNodeInternal(Supplier parent) {
  //		nodeFactory.
  //		return null;
  //	}

  // public synchronized void registerSpecializedProjectionIndexer(Node node, ProjectionIndexer
  // indexer) {
  // if (Options.nodeSharingOption != Options.NodeSharingOption.NEVER) {
  // Object[] paramsArray = { node.getNodeId(), indexer.getMask() };
  // Tuple params = new FlatTuple(paramsArray);
  // projectionIndexers.put(params, indexer);
  // }
  // }

}
/** @author Gabor Bergmann */
public class Network {
  int threads;

  protected ArrayList<ReteContainer> containers;
  ReteContainer headContainer;
  private int firstContainer = 0;
  private int nextContainer = 0;

  // the following fields exist only if threads > 0
  protected Map<ReteContainer, Long> globalTerminationCriteria = null;
  protected Map<ReteContainer, Long> reportedClocks = null;
  protected Lock updateLock = null; // grab during normal update operations
  protected Lock structuralChangeLock = null; // grab if the network structure is to
  // be changed

  // Knowledge of the outside world
  protected IPatternMatcherRuntimeContext context;
  protected NodeFactory nodeFactory;
  protected InputConnector inputConnector;

  // Node and recipe administration
  // incl. addresses for existing nodes by recipe (where available)
  // Maintained by NodeProvisioner of each container
  Map<ReteNodeRecipe, Address<? extends Node>> nodesByRecipe = CollectionsFactory.getMap();
  /** if EcoreUtil.equals(recipe1, recipe2), only one of them will be included here */
  Map<EClass, Collection<ReteNodeRecipe>> primaryRecipesByClass = CollectionsFactory.getMap();

  Set<RecipeTraceInfo> recipeTraces = CollectionsFactory.getSet();
  /** @throws IllegalStateException if no node has been constructed for the recipe */
  public synchronized Address<? extends Node> getExistingNodeByRecipe(ReteNodeRecipe recipe) {
    final Address<? extends Node> node = nodesByRecipe.get(recipe);
    if (node == null)
      throw new IllegalStateException(
          String.format("Rete node for recipe %s not constructed yet.", recipe));
    return node;
  }
  /** @return null if no node has been constructed for the recipe */
  public synchronized Address<? extends Node> getNodeByRecipeIfExists(ReteNodeRecipe recipe) {
    final Address<? extends Node> node = nodesByRecipe.get(recipe);
    return node;
  }

  /**
   * @param threads the number of threads to operate the network with; 0 means single-threaded
   *     operation, 1 starts an asynchronous thread to operate the RETE net, >1 uses multiple RETE
   *     containers.
   */
  public Network(int threads, IPatternMatcherRuntimeContext context) {
    super();
    this.threads = threads;
    this.context = context;
    this.inputConnector = new InputConnector(this);
    this.nodeFactory = new NodeFactory(context);

    containers = new ArrayList<ReteContainer>();
    firstContainer = (threads > 1) ? Options.firstFreeContainer : 0; // NOPMD
    nextContainer = firstContainer;

    if (threads > 0) {
      globalTerminationCriteria =
          CollectionsFactory.getMap(); // new HashMap<ReteContainer, Long>();
      reportedClocks = CollectionsFactory.getMap(); // new HashMap<ReteContainer, Long>();
      ReadWriteLock rwl = new ReentrantReadWriteLock();
      updateLock = rwl.readLock();
      structuralChangeLock = rwl.writeLock();
      for (int i = 0; i < threads; ++i) containers.add(new ReteContainer(this, true));
    } else containers.add(new ReteContainer(this, false));

    headContainer = containers.get(0);
  }

  /** Kills this Network along with all containers and message consumption cycles. */
  public void kill() {
    for (ReteContainer container : containers) {
      container.kill();
    }
    containers.clear();
  }

  /**
   * Returns the head container, that is guaranteed to reside in the same JVM as the Network object.
   *
   * @return
   */
  public ReteContainer getHeadContainer() {
    return headContainer;
  }

  /**
   * Returns the next container in round-robin fashion. Configurable not to yield head container.
   */
  public ReteContainer getNextContainer() {
    if (nextContainer >= containers.size()) nextContainer = firstContainer;
    return containers.get(nextContainer++);
  }

  /**
   * Internal message delivery method.
   *
   * @pre threads > 0
   */
  private void sendUpdate(
      Address<? extends Receiver> receiver, Direction direction, Tuple updateElement) {
    ReteContainer affectedContainer = receiver.getContainer();
    synchronized (globalTerminationCriteria) {
      long newCriterion =
          affectedContainer.sendUpdateToLocalAddress(receiver, direction, updateElement);
      terminationCriterion(affectedContainer, newCriterion);
    }
  }

  /**
   * Internal message delivery method for single-threaded operation
   *
   * @pre threads == 0
   */
  private void sendUpdateSingleThreaded(
      Address<? extends Receiver> receiver, Direction direction, Tuple updateElement) {
    ReteContainer affectedContainer = receiver.getContainer();
    affectedContainer.sendUpdateToLocalAddressSingleThreaded(receiver, direction, updateElement);
  }

  /**
   * Internal message delivery method.
   *
   * @pre threads > 0
   */
  private void sendUpdates(
      Address<? extends Receiver> receiver, Direction direction, Collection<Tuple> updateElements) {
    if (updateElements.isEmpty()) return;
    ReteContainer affectedContainer = receiver.getContainer();
    synchronized (globalTerminationCriteria) {
      long newCriterion =
          affectedContainer.sendUpdatesToLocalAddress(receiver, direction, updateElements);
      terminationCriterion(affectedContainer, newCriterion);
    }
  }

  /**
   * Sends an update message to the receiver node, indicating a newly found or lost partial
   * matching. The node may reside in any of the containers associated with this network. To be
   * called from a user thread during normal operation, NOT during construction.
   *
   * @return the value of the target container's clock at the time when the message was accepted
   *     into its message queue
   */
  public void sendExternalUpdate(
      Address<? extends Receiver> receiver, Direction direction, Tuple updateElement) {
    if (threads > 0) {
      try {
        updateLock.lock();
        sendUpdate(receiver, direction, updateElement);
      } finally {
        updateLock.unlock();
      }
    } else {
      sendUpdateSingleThreaded(receiver, direction, updateElement);
      // getHeadContainer().
    }
  }

  /**
   * Sends an update message to the receiver node, indicating a newly found or lost partial
   * matching. The node may reside in any of the containers associated with this network. To be
   * called from a user thread during construction.
   *
   * @pre: structuralChangeLock MUST be grabbed by the sequence (but not necessarily this thread, as
   *     the sequence may span through network calls, that's why it's not enforced here )
   * @return the value of the target container's clock at the time when the message was accepted
   *     into its message queue
   */
  public void sendConstructionUpdate(
      Address<? extends Receiver> receiver, Direction direction, Tuple updateElement) {
    // structuralChangeLock.lock();
    if (threads > 0) sendUpdate(receiver, direction, updateElement);
    else
      receiver
          .getContainer()
          .sendUpdateToLocalAddressSingleThreaded(receiver, direction, updateElement);
    // structuralChangeLock.unlock();
  }

  /**
   * Sends multiple update messages atomically to the receiver node, indicating a newly found or
   * lost partial matching. The node may reside in any of the containers associated with this
   * network. To be called from a user thread during construction.
   *
   * @pre: structuralChangeLock MUST be grabbed by the sequence (but not necessarily this thread, as
   *     the sequence may span through network calls, that's why it's not enforced here )
   * @return the value of the target container's clock at the time when the message was accepted
   *     into its message queue
   */
  public void sendConstructionUpdates(
      Address<? extends Receiver> receiver, Direction direction, Collection<Tuple> updateElements) {
    // structuralChangeLock.lock();
    if (threads > 0) sendUpdates(receiver, direction, updateElements);
    else
      receiver
          .getContainer()
          .sendUpdatesToLocalAddressSingleThreaded(receiver, direction, updateElements);
    // structuralChangeLock.unlock();
  }

  /**
   * Establishes connection between a supplier and a receiver node, regardless which container they
   * are in. Not to be called remotely, because this method enforces the structural lock.
   *
   * @param supplier
   * @param receiver
   * @param synchronise indicates whether the receiver should be synchronised to the current
   *     contents of the supplier
   */
  public void connectRemoteNodes(
      Address<? extends Supplier> supplier,
      Address<? extends Receiver> receiver,
      boolean synchronise) {
    try {
      if (threads > 0) structuralChangeLock.lock();
      receiver.getContainer().connectRemoteNodes(supplier, receiver, synchronise);
    } finally {
      if (threads > 0) structuralChangeLock.unlock();
    }
  }

  /**
   * Severs connection between a supplier and a receiver node, regardless which container they are
   * in. Not to be called remotely, because this method enforces the structural lock.
   *
   * @param supplier
   * @param receiver
   * @param desynchronise indicates whether the current contents of the supplier should be
   *     subtracted from the receiver
   */
  public void disconnectRemoteNodes(
      Address<? extends Supplier> supplier,
      Address<? extends Receiver> receiver,
      boolean desynchronise) {
    try {
      if (threads > 0) structuralChangeLock.lock();
      receiver.getContainer().disconnectRemoteNodes(supplier, receiver, desynchronise);
    } finally {
      if (threads > 0) structuralChangeLock.unlock();
    }
  }

  /**
   * Containers use this method to report whenever they run out of messages in their queue.
   *
   * <p>To be called from the thread of the reporting container.
   *
   * @pre threads > 0.
   * @param reportingContainer the container reporting the emptiness of its message queue.
   * @param clock the value of the container's clock when reporting.
   * @param localTerminationCriteria the latest clock values this container has received from other
   *     containers since the last time it reported termination.
   */
  void reportLocalUpdateTermination(
      ReteContainer reportingContainer,
      long clock,
      Map<ReteContainer, Long> localTerminationCriteria) {
    synchronized (globalTerminationCriteria) {
      for (Entry<ReteContainer, Long> criterion : localTerminationCriteria.entrySet()) {
        terminationCriterion(criterion.getKey(), criterion.getValue());
      }

      reportedClocks.put(reportingContainer, clock);
      Long criterion = globalTerminationCriteria.get(reportingContainer);
      if (criterion != null && criterion < clock)
        globalTerminationCriteria.remove(reportingContainer);

      if (globalTerminationCriteria.isEmpty()) globalTerminationCriteria.notifyAll();
    }
  }

  /** @pre threads > 0 */
  private void terminationCriterion(ReteContainer affectedContainer, long newCriterion) {
    synchronized (globalTerminationCriteria) {
      Long oldCriterion = globalTerminationCriteria.get(affectedContainer);
      Long oldClock = reportedClocks.get(affectedContainer);
      long relevantClock = oldClock == null ? 0 : oldClock;
      if ((relevantClock <= newCriterion)
          && (oldCriterion == null || oldCriterion < newCriterion)) {
        globalTerminationCriteria.put(affectedContainer, newCriterion);
      }
    }
  }

  /**
   * Waits until all rete update operations are settled in all containers. Returns immediately, if
   * no updates are pending.
   *
   * <p>To be called from any user thread.
   */
  public void waitForReteTermination() {
    if (threads > 0) {
      synchronized (globalTerminationCriteria) {
        while (!globalTerminationCriteria.isEmpty()) {
          try {
            globalTerminationCriteria.wait();
          } catch (InterruptedException e) {

          }
        }
      }
    } else headContainer.messageConsumptionSingleThreaded();
  }

  /**
   * Waits to execute action until all rete update operations are settled in all containers. Runs
   * action and returns immediately, if no updates are pending. The given action is guaranteed to be
   * run when the terminated state still persists.
   *
   * @param action the action to be run when reaching the steady-state.
   *     <p>To be called from any user thread.
   */
  public void waitForReteTermination(Runnable action) {
    if (threads > 0) {
      synchronized (globalTerminationCriteria) {
        while (!globalTerminationCriteria.isEmpty()) {
          try {
            globalTerminationCriteria.wait();
          } catch (InterruptedException e) {

          }
        }
        action.run();
      }
    } else {
      headContainer.messageConsumptionSingleThreaded();
      action.run();
    }
  }

  /** @return the structuralChangeLock */
  public Lock getStructuralChangeLock() {
    return structuralChangeLock;
  }

  public IPatternMatcherRuntimeContext getContext() {
    return context;
  }

  public NodeFactory getNodeFactory() {
    return nodeFactory;
  }
  /** @return */
  public InputConnector getInputConnector() {
    return inputConnector;
  }
}