/******************************************************************************* * Copyright (c) 2004-2011 Abel Hegedus 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: * Abel Hegedus - initial API and implementation *******************************************************************************/ package org.eclipse.incquery.querybasedfeatures.runtime; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.notify.Notifier; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.InternalEObject; import org.eclipse.emf.ecore.impl.ENotificationImpl; import org.eclipse.emf.ecore.util.EcoreEList; import org.eclipse.incquery.runtime.api.IMatchProcessor; import org.eclipse.incquery.runtime.api.IPatternMatch; import org.eclipse.incquery.runtime.api.IncQueryEngine; import org.eclipse.incquery.runtime.api.IncQueryMatcher; import org.eclipse.incquery.runtime.exception.IncQueryException; import org.eclipse.incquery.runtime.extensibility.MatcherFactoryRegistry; import org.eclipse.incquery.runtime.rete.misc.DeltaMonitor; /** * @author Abel Hegedus * * FIXME write AggregateHandler if any EDataType should be allowed TODO notifications could be static final? to * ensure message ordering * */ @SuppressWarnings("rawtypes") public class QueryBasedFeatureHandler implements IQueryBasedFeatureHandler { /** * @author Abel Hegedus * */ private final class DerivedFeatureWipeCallback implements Runnable { @SuppressWarnings("unchecked") @Override public void run() { String patternName = matcher.getPatternName(); try { matcher = (IncQueryMatcher<IPatternMatch>) MatcherFactoryRegistry.getMatcherFactory(patternName) .getMatcher(matcher.getEngine()); } catch (IncQueryException e) { matcher.getEngine().getLogger() .error("[IncqueryFeatureHandler] Exception during wipe callback: " + e.getMessage(), e); } dm = matcher.newDeltaMonitor(false); } } /** * @author Abel Hegedus * */ private final class DerivedFeatureCallback implements Runnable { @Override public void run() { // after each model update, check the delta monitor // FIXME should be: after each complete transaction, check the delta monitor try { updateMemory.clear(); dm.matchFoundEvents.removeAll(processNewMatches(dm.matchFoundEvents)); dm.matchLostEvents.removeAll(processLostMatches(dm.matchLostEvents)); checkUnhandledNewMatch(); sendNextNotfication(); } catch (IncQueryException e) { matcher.getEngine().getLogger() .error("[IncqueryFeatureHandler] Exception during update: " + e.getMessage(), e); } } } private IncQueryMatcher<IPatternMatch> matcher; private DeltaMonitor<IPatternMatch> dm; private Runnable processMatchesRunnable; // private final InternalEObject source; private final EStructuralFeature feature; private String sourceParamName; private String targetParamName; private QueryBasedFeatureKind kind; private final Map<InternalEObject, Object> updateMemory = new HashMap<InternalEObject, Object>(); private final Map<InternalEObject, Integer> counterMemory = new HashMap<InternalEObject, Integer>(); private final Map<InternalEObject, Object> singleRefMemory = new HashMap<InternalEObject, Object>(); private boolean keepCache = true; private boolean initialized = false; private final List<ENotificationImpl> notifications = new ArrayList<ENotificationImpl>(); /* * could use EObjectEList or similar to have notifications handled by EMF, but notification sending must be delayed * in order to avoid infinite notification loop */ private final Map<InternalEObject, List<Object>> manyRefMemory = new HashMap<InternalEObject, List<Object>>(); private Runnable processWipeRunnable; /** * */ public QueryBasedFeatureHandler(EStructuralFeature feature) { // this.source = source; this.feature = feature; if (feature.isMany()) { kind = QueryBasedFeatureKind.MANY_REFERENCE; } else { kind = QueryBasedFeatureKind.SINGLE_REFERENCE; } // initialize(matcher, sourceParamName, targetParamName); } @SuppressWarnings("unchecked") protected void initialize(final IncQueryMatcher matcher, String sourceParamName, String targetParamName) { if (initialized) { IncQueryEngine.getDefaultLogger().error("[IncqueryFeatureHandler] Feature already initialized!"); return; } initialized = true; this.matcher = matcher; this.sourceParamName = sourceParamName; this.targetParamName = targetParamName; if (matcher.getPositionOfParameter(sourceParamName) == null) { matcher.getEngine().getLogger() .error("[IncqueryFeatureHandler] Source parameter " + sourceParamName + " not found!"); } if (targetParamName != null && matcher.getPositionOfParameter(targetParamName) == null) { matcher.getEngine().getLogger() .error("[IncqueryFeatureHandler] Target parameter " + targetParamName + " not found!"); } if ((targetParamName == null) != (kind == QueryBasedFeatureKind.COUNTER)) { matcher.getEngine().getLogger() .error("[IncqueryFeatureHandler] Invalid configuration (no targetParamName needed for Counter)!"); } // IPatternMatch partialMatch = matcher.newEmptyMatch(); // partialMatch.set(sourceParamName, source); this.dm = matcher.newDeltaMonitor(true); this.processMatchesRunnable = new DerivedFeatureCallback(); this.processWipeRunnable = new DerivedFeatureWipeCallback(); } private void sendNextNotfication() { while (!notifications.isEmpty()) { ENotificationImpl remove = notifications.remove(0); // matcher.getEngine().getLogger().logError(this + " : " +remove.toString()); ((Notifier) remove.getNotifier()).eNotify(remove); } } /** * * @param source * @param feature * @param matcher * @param sourceParamName */ public QueryBasedFeatureHandler(EStructuralFeature feature, QueryBasedFeatureKind kind) { this(feature); this.kind = kind; if (kind == QueryBasedFeatureKind.SUM && !(feature instanceof EAttribute)) { IncQueryEngine.getDefaultLogger().error( "[IncqueryFeatureHandler] Invalid configuration (Aggregate can be used only with EAttribute)!"); } } /** * */ public QueryBasedFeatureHandler(EStructuralFeature feature, QueryBasedFeatureKind kind, boolean keepCache) { this(feature, kind); this.keepCache = keepCache; } /** * Call this once to start handling callbacks. */ protected void startMonitoring() { matcher.addCallbackAfterUpdates(processMatchesRunnable); matcher.addCallbackAfterWipes(processWipeRunnable); processMatchesRunnable.run(); } @Override public Object getValue(Object source) { switch (kind) { case SUM: // fall-through case COUNTER: return getIntValue(source); case SINGLE_REFERENCE: return getSingleReferenceValue(source); case MANY_REFERENCE: return getManyReferenceValue(source); case ITERATION: return getValueIteration(source); } return null; } @Override public int getIntValue(Object source) { Integer result = counterMemory.get(source); if (result == null) { result = 0; } return result; } @Override public Object getSingleReferenceValue(Object source) { if (keepCache) { return singleRefMemory.get(source); } else { if (!initialized) { return null; } IPatternMatch match = matcher.newEmptyMatch(); match.set(sourceParamName, source); if (matcher.countMatches(match) > 1) { matcher.getEngine() .getLogger() .warn("[IncqueryFeatureHandler] Single reference derived feature has multiple possible values, returning one arbitrary value"); } IPatternMatch patternMatch = matcher.getOneArbitraryMatch(match); if (patternMatch != null) { return patternMatch.get(targetParamName); } else { return null; } } } @Override public List<?> getManyReferenceValue(Object source) { if (keepCache) { List<Object> values = manyRefMemory.get(source); if (values == null) { values = new ArrayList<Object>(); } return values; } else { final List<Object> values = new ArrayList<Object>(); if (!initialized) { return values; } IPatternMatch match = matcher.newEmptyMatch(); match.set(sourceParamName, source); matcher.forEachMatch(match, new IMatchProcessor<IPatternMatch>() { @Override public void process(IPatternMatch match) { values.add(match.get(targetParamName)); } }); return values;// matcher.getAllValues(targetParamName, match); } } /** * @param singleRefMemory * the singleRefMemory to set */ private void setSingleRefMemory(InternalEObject source, Object singleRefMemory) { if (keepCache) { this.singleRefMemory.put(source, singleRefMemory); } } private void addToManyRefMemory(InternalEObject source, Object added) { if (keepCache) { List<Object> values = manyRefMemory.get(source); if (values == null) { values = new ArrayList<Object>(); manyRefMemory.put(source, values); } values.add(added); } } private void removeFromManyRefMemory(InternalEObject source, Object removed) { if (keepCache) { List<Object> values = manyRefMemory.get(source); if (values == null) { matcher.getEngine() .getLogger() .error("[IncqueryFeatureHandler] Space-time continuum breached (should never happen): removing from list that doesn't exist"); } values.remove(removed); } } @Override public EList getManyReferenceValueAsEList(Object source) { Collection<?> values = getManyReferenceValue(source); if (values.size() > 0) { return new EcoreEList.UnmodifiableEList((InternalEObject) source, feature, values.size(), values.toArray()); } else { return new EcoreEList.UnmodifiableEList((InternalEObject) source, feature, 0, null); } } private Collection<IPatternMatch> processNewMatches(Collection<IPatternMatch> signatures) throws IncQueryException { List<IPatternMatch> processed = new ArrayList<IPatternMatch>(); for (IPatternMatch signature : signatures) { if (kind == QueryBasedFeatureKind.ITERATION) { ENotificationImpl notification = newMatchIteration(signature); if (notification != null) { notifications.add(notification); } } else { Object target = signature.get(targetParamName); InternalEObject source = (InternalEObject) signature.get(sourceParamName); if (target != null) { if (kind == QueryBasedFeatureKind.SUM) { increaseCounter(source, (Integer) target); } else { if (feature.isMany()) { notifications.add(new ENotificationImpl(source, Notification.ADD, feature, null, target)); addToManyRefMemory(source, target); } else { if (updateMemory.get(source) != null) { matcher.getEngine() .getLogger() .error("[IncqueryFeatureHandler] Space-time continuum breached (should never happen): update memory already set for given source"); } else { // must handle later (either in lost matches or after that) updateMemory.put(source, target); } } } } else if (kind == QueryBasedFeatureKind.COUNTER) { increaseCounter(source, 1); } } processed.add(signature); } return processed; } /** * @throws CoreException */ private void increaseCounter(InternalEObject source, int delta) throws IncQueryException { Integer value = getIntValue(source); if (value <= Integer.MAX_VALUE - delta) { int tempMemory = value + delta; notifications.add(new ENotificationImpl(source, Notification.SET, feature, getIntValue(source), tempMemory)); counterMemory.put(source, tempMemory); } else { throw new IncQueryException(String.format( "The counter of %s for feature %s reached the maximum value of int!", source, feature), "Counter reached maximum value of int"); } } private Collection<IPatternMatch> processLostMatches(Collection<IPatternMatch> signatures) throws IncQueryException { List<IPatternMatch> processed = new ArrayList<IPatternMatch>(); for (IPatternMatch signature : signatures) { if (kind == QueryBasedFeatureKind.ITERATION) { ENotificationImpl notification = lostMatchIteration(signature); if (notification != null) { notifications.add(notification); } } else { Object target = signature.get(targetParamName); InternalEObject source = (InternalEObject) signature.get(sourceParamName); if (target != null) { if (kind == QueryBasedFeatureKind.SUM) { decreaseCounter(source, (Integer) target); } else { if (feature.isMany()) { notifications .add(new ENotificationImpl(source, Notification.REMOVE, feature, target, null)); removeFromManyRefMemory(source, target); } else { Object updateValue = updateMemory.get(source); if (updateValue != null) { notifications.add(new ENotificationImpl(source, Notification.SET, feature, target, updateValue)); setSingleRefMemory(source, updateValue); updateMemory.remove(source); } else { notifications .add(new ENotificationImpl(source, Notification.SET, feature, target, null)); setSingleRefMemory(source, null); } } } } else { if (kind == QueryBasedFeatureKind.COUNTER) { decreaseCounter(source, 1); } } } processed.add(signature); } return processed; } /** * Called each time when a new match is found for Iteration kind * * @param signature * @return notification to be sent, if one is necessary */ protected ENotificationImpl newMatchIteration(IPatternMatch signature) { throw new UnsupportedOperationException("Iteration derived feature handlers must override newMatchIteration"); } /** * Called each time when a match is lost for Iteration kind * * @param signature * @return notification to be sent, if one is necessary */ protected ENotificationImpl lostMatchIteration(IPatternMatch signature) { throw new UnsupportedOperationException("Iteration derived feature handlers must override oldMatchIteration"); } @Override public Object getValueIteration(Object source) { throw new UnsupportedOperationException("Iteration derived feature handlers must override getValueIteration"); } /** * @throws CoreException */ private void decreaseCounter(InternalEObject source, int delta) throws IncQueryException { Integer value = counterMemory.get(source); if (value == null) { matcher.getEngine() .getLogger() .error("[IncqueryFeatureHandler] Space-time continuum breached (should never happen): decreasing a counter with no previous value"); } else if (value >= delta) { int tempMemory = value - delta; int oldValue = value; notifications.add(new ENotificationImpl(source, Notification.SET, feature, oldValue, tempMemory)); counterMemory.put(source, tempMemory); } else { throw new IncQueryException(String.format("The counter of %s for feature %s cannot go below zero!", source, feature), "Counter cannot go below zero"); } } private void checkUnhandledNewMatch() { if (!updateMemory.isEmpty()) { for (InternalEObject source : updateMemory.keySet()) { notifications.add(new ENotificationImpl(source, Notification.SET, feature, null, updateMemory .get(source))); setSingleRefMemory(source, updateMemory.get(source)); } updateMemory.clear(); } } }