/*******************************************************************************
* Copyright (c) 2004-2008 Gabor Bergmann and Daniel Varro
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Gabor Bergmann - initial API and implementation
*******************************************************************************/
package org.eclipse.incquery.runtime.rete.boundary;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Set;
import org.eclipse.incquery.runtime.rete.collections.CollectionsFactory;
import org.eclipse.incquery.runtime.rete.index.MemoryIdentityIndexer;
import org.eclipse.incquery.runtime.rete.index.MemoryNullIndexer;
import org.eclipse.incquery.runtime.rete.index.ProjectionIndexer;
import org.eclipse.incquery.runtime.rete.matcher.ReteEngine;
import org.eclipse.incquery.runtime.rete.network.Direction;
import org.eclipse.incquery.runtime.rete.network.Receiver;
import org.eclipse.incquery.runtime.rete.network.ReteContainer;
import org.eclipse.incquery.runtime.rete.remote.Address;
import org.eclipse.incquery.runtime.rete.single.SingleInputNode;
import org.eclipse.incquery.runtime.rete.tuple.Tuple;
import org.eclipse.incquery.runtime.rete.tuple.TupleMask;
import org.eclipse.incquery.runtime.rete.tuple.TupleMemory;
import org.eclipse.incquery.runtime.rete.util.Options;
/**
* @author Gabor Bergmann
*
* Permits the traversal of update notifications if a given function (with variables mapped to given positions
* in the Tuple) equals a right-hand-side (also mapped to a given position). If right-hand-side is omitted, the
* function is the predicate itself and should evaluate to true.
*
* The predicate is reevaluated on the Tuple each time an element affected by the term experiences a move, name
* or value change. Furthermore, it is also reevaluated if any ASMfunctions called at the previous evaluation
* are changed at the positions that were used.
*
* Uses unwrapped tuples. In distributed environments, AbstractPredicateEvaluatorNodes should always be built on
* the head container if they need single-threaded access to the environment.
*
*/
public class PredicateEvaluatorNode extends SingleInputNode {
protected ReteEngine<?> engine;
protected ReteBoundary<?> boundary;
protected Integer rhsIndex;
protected int[] affectedIndices;
protected Set<Tuple> outgoing;
protected MemoryNullIndexer memoryNullIndexer;
protected MemoryIdentityIndexer memoryIdentityIndexer;
protected Map<Object, Collection<Tuple>> elementOccurences;
protected Map<Tuple, Set<Tuple>> invoker2traces;
protected Map<Tuple, Set<Tuple>> trace2invokers;
protected Address<ASMFunctionTraceNotifierNode> asmFunctionTraceNotifier;
protected Address<ElementChangeNotifierNode> elementChangeNotifier;
protected AbstractEvaluator evaluator;
private final int tupleWidth;
private final TupleMask nullMask;
private final TupleMask identityMask;
/**
* @param rhsIndex
* the index of the element in the Tuple that should equals the result of the evaluation; if null, the
* right-hand-side will be the Boolean true.
* @param variableIndices
* maps variable names to values.
*/
public PredicateEvaluatorNode(ReteEngine<?> engine, ReteContainer container, Integer rhsIndex,
int[] affectedIndices, int tupleWidth, AbstractEvaluator evaluator) {
super(container);
this.engine = engine;
this.boundary = engine.getBoundary();
this.rhsIndex = rhsIndex;
this.affectedIndices = affectedIndices;
this.tupleWidth = tupleWidth;
this.evaluator = evaluator;
this.elementOccurences = CollectionsFactory.getMap();//new HashMap<Object, Collection<Tuple>>();
this.outgoing = CollectionsFactory.getSet();//new HashSet<Tuple>();
this.invoker2traces = CollectionsFactory.getMap();//new HashMap<Tuple, Set<Tuple>>();
this.trace2invokers = CollectionsFactory.getMap();//new HashMap<Tuple, Set<Tuple>>();
// extractASMFunctions();
this.asmFunctionTraceNotifier = Address.of(new ASMFunctionTraceNotifierNode(reteContainer));
this.elementChangeNotifier = Address.of(new ElementChangeNotifierNode(reteContainer));
nullMask = TupleMask.linear(0, tupleWidth);
identityMask = TupleMask.identity(tupleWidth);
// if (Options.employTrivialIndexers) {
// memoryNullIndexer = new MemoryNullIndexer(reteContainer, tupleWidth, outgoing, this, this);
// reteContainer.getLibrary().registerSpecializedProjectionIndexer(this, memoryNullIndexer);
// memoryIdentityIndexer = new MemoryIdentityIndexer(reteContainer, tupleWidth, outgoing, this, this);
// reteContainer.getLibrary().registerSpecializedProjectionIndexer(this, memoryIdentityIndexer);
// }
}
@Override
public ProjectionIndexer constructIndex(TupleMask mask) {
if (Options.employTrivialIndexers) {
if (nullMask.equals(mask))
return getNullIndexer();
if (identityMask.equals(mask))
return getIdentityIndexer();
}
return super.constructIndex(mask);
}
@Override
public void pullInto(Collection<Tuple> collector) {
for (Tuple ps : outgoing)
collector.add(boundary.wrapTuple(ps));
}
@Override
public void update(Direction direction, Tuple wrappers) {
Tuple updateElement = boundary.unwrapTuple(wrappers);
updateOccurences(direction, updateElement);
if (direction == Direction.REVOKE) {
if (outgoing.remove(updateElement)) {
clearTraces(updateElement);
propagateUpdate(Direction.REVOKE, wrappers);
}
} else /* (direction == Direction.INSERT) */
{
check(updateElement);
}
}
protected void notifyASMFunctionValueChanged(Tuple trace) {
// System.out.println("TEN notified");
Set<Tuple> invokers = trace2invokers.get(trace);
if (invokers != null) {
LinkedList<Tuple> copy = new LinkedList<Tuple>(invokers);
for (Tuple ps : copy)
check(ps);
}
}
protected void notifyElementChange(Object element) {
for (Tuple ps : elementOccurences.get(element))
check(ps);
}
protected void updateOccurences(Direction direction, Tuple ps) {
for (Integer i : affectedIndices) {
Object element = ps.get(i);
// if (element instanceof IModelElement) {
updateElementOccurence(direction, ps, element);
// }
}
}
protected void updateElementOccurence(Direction direction, Tuple ps, Object element) {
Collection<Tuple> occurences;
if (direction == Direction.INSERT) {
occurences = elementOccurences.get(element);
boolean change = occurences == null;
if (change) {
occurences = new TupleMemory();
elementOccurences.put(element, occurences);
engine.getManipulationListener().registerSensitiveTerm(element, this);
}
occurences.add(ps);
} else // REVOKE
{
occurences = elementOccurences.get(element);
occurences.remove(ps);
boolean change = occurences.isEmpty();
if (change) {
elementOccurences.remove(element);
engine.getManipulationListener().unregisterSensitiveTerm(element, this);
}
}
}
protected void check(Tuple ps) {
boolean result = evaluateExpression(ps);
if (result) /* expression evaluates to true */
{
if (outgoing.add(ps))
propagateUpdate(Direction.INSERT, boundary.wrapTuple(ps));
} else /* expression evaluates to false */
{
if (outgoing.remove(ps))
propagateUpdate(Direction.REVOKE, boundary.wrapTuple(ps));
}
}
protected boolean evaluateExpression(Tuple ps) {
Object termResult = evaluateTerm(ps);
if (rhsIndex != null) {
Object rightHandSide = ps.get(rhsIndex);
return rightHandSide.equals(termResult);
} else {
if (Boolean.FALSE.equals(termResult))
return false;
else if (Boolean.TRUE.equals(termResult))
return true;
engine.getContext()
.logWarning(
String.format(
"The incremental pattern matcher encountered a type compatibility problem during check() evaluation over variables %s: expression evaluated to type %s instead of java.lang.Boolean. (Developer note: result was %s in %s)",
prettyPrintTuple(ps), termResult == null ? null : termResult.getClass().getName(),
termResult, this));
return false;
}
}
public Object evaluateTerm(Tuple ps) {
// clearing ASMfunction traces
clearTraces(ps);
// actual evaluation
Object result = null;
try {
result = evaluator.evaluate(ps);
} catch (Throwable e) { // NOPMD
if (e instanceof Error)
throw (Error) e;
engine.getContext()
.logWarning(
String.format(
"The incremental pattern matcher encountered an error during %s evaluation over variables %s. Error message: %s. (Developer note: %s in %s)",
rhsIndex == null ? "check()" : "eval()", prettyPrintTuple(ps), e.getMessage(), e
.getClass().getSimpleName(), this), e);
// engine.logEvaluatorException(e);
result = Boolean.FALSE;
}
// saving ASMFunction traces
saveTraces(ps, evaluator.getTraces());
return result;
}
protected String prettyPrintTuple(Tuple ps) {
return ps.toString();
}
protected void clearTraces(Tuple invoker) {
Set<Tuple> traces = invoker2traces.get(invoker);
if (traces != null) {
invoker2traces.remove(invoker);
for (Tuple trace : traces) {
Set<Tuple> invokers = trace2invokers.get(trace);
invokers.remove(invoker);
if (invokers.isEmpty()) {
trace2invokers.remove(trace);
engine.geTraceListener().unregisterSensitiveTrace(trace, this);
}
}
}
}
protected void saveTraces(Tuple invoker, Set<Tuple> traces) {
if (traces != null && !traces.isEmpty()) {
invoker2traces.put(invoker, traces);
for (Tuple trace : traces) {
Set<Tuple> invokers = trace2invokers.get(trace);
if (invokers == null) {
invokers = CollectionsFactory.getSet();//new HashSet<Tuple>();
trace2invokers.put(trace, invokers);
engine.geTraceListener().registerSensitiveTrace(trace, this);
}
invokers.add(invoker);
}
}
}
@Override
protected void propagateUpdate(Direction direction, Tuple updateElement) {
super.propagateUpdate(direction, updateElement);
if (memoryIdentityIndexer != null)
memoryIdentityIndexer.propagate(direction, updateElement);
if (memoryNullIndexer != null)
memoryNullIndexer.propagate(direction, updateElement);
}
/**
* @return the asmFunctionTraceNotifier
*/
public Address<? extends Receiver> getAsmFunctionTraceNotifier() {
return asmFunctionTraceNotifier;
}
/**
* @return the elementChangeNotifier
*/
public Address<? extends Receiver> getElementChangeNotifier() {
return elementChangeNotifier;
}
/**
* @return the engine
*/
public ReteEngine<?> getEngine() {
return engine;
}
public MemoryNullIndexer getNullIndexer() {
if (memoryNullIndexer == null)
memoryNullIndexer = new MemoryNullIndexer(reteContainer, tupleWidth, outgoing, this, this);
return memoryNullIndexer;
}
public MemoryIdentityIndexer getIdentityIndexer() {
if (memoryIdentityIndexer == null)
memoryIdentityIndexer = new MemoryIdentityIndexer(reteContainer, tupleWidth, outgoing, this, this);
return memoryIdentityIndexer;
}
class ASMFunctionTraceNotifierNode extends SingleInputNode {
public ASMFunctionTraceNotifierNode(ReteContainer reteContainer) {
super(reteContainer);
}
@Override
public void pullInto(Collection<Tuple> collector) {
}
@Override
public void update(Direction direction, Tuple updateElement) {
notifyASMFunctionValueChanged(updateElement);
}
}
class ElementChangeNotifierNode extends SingleInputNode {
public ElementChangeNotifierNode(ReteContainer reteContainer) {
super(reteContainer);
}
@Override
public void pullInto(Collection<Tuple> collector) {
}
@Override
public void update(Direction direction, Tuple updateElement) {
notifyElementChange(updateElement.get(0));
}
}
}