/*******************************************************************************
* Copyright (c) 2010-2012, Tamas Szabo, Abel Hegedus, Istvan Rath 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:
* Tamas Szabo, Abel Hegedus - initial API and implementation
*******************************************************************************/
package org.eclipse.incquery.runtime.triggerengine.api;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.incquery.patternlanguage.patternLanguage.Pattern;
import org.eclipse.incquery.runtime.api.IMatchProcessor;
import org.eclipse.incquery.runtime.api.IMatchUpdateListener;
import org.eclipse.incquery.runtime.api.IPatternMatch;
import org.eclipse.incquery.runtime.api.IncQueryMatcher;
import org.eclipse.incquery.runtime.triggerengine.notification.ActivationNotificationProvider;
import org.eclipse.incquery.runtime.triggerengine.notification.AttributeMonitor;
import org.eclipse.incquery.runtime.triggerengine.notification.IActivationNotificationListener;
import org.eclipse.incquery.runtime.triggerengine.notification.IAttributeMonitorListener;
/**
* A {@link AbstractRule} defines a transformation step in the context of the AbstractRule Engine. Each rule is assigned
* a precondition (Left Hand Side - LHS) which is an EMF-IncQuery pattern and a postcondition (Right Hand Side - RHS)
* which is an {@link IMatchProcessor} instance.
*
* <p>
* The {@link AbstractRule} keeps track of its activations and they can be queried and executed at some point in time.
*
* @author Tamas Szabo
*
* @param <MatchType>
* the type of the pattern match
*/
public abstract class AbstractRule<MatchType extends IPatternMatch> implements IAttributeMonitorListener<MatchType>,
IMatchUpdateListener<MatchType>, IRule<MatchType> {
private IMatchProcessor<MatchType> afterAppearanceJob;
private IMatchProcessor<MatchType> afterDisappearanceJob;
private IMatchProcessor<MatchType> afterModificationJob;
protected IAgenda agenda;
protected boolean upgradedStateUsed;
protected boolean disappearedStateUsed;
protected IncQueryMatcher<MatchType> matcher;
protected Map<ActivationState, Map<MatchType, Activation<MatchType>>> stateMap;
protected AttributeMonitor<MatchType> attributeMonitor;
protected ActivationNotificationProvider activationProvider;
public AbstractRule(IAgenda agenda, IncQueryMatcher<MatchType> matcher, boolean upgradedStateUsed,
boolean disappearedStateUsed) {
this.agenda = agenda;
this.matcher = matcher;
this.upgradedStateUsed = upgradedStateUsed;
this.disappearedStateUsed = disappearedStateUsed;
this.stateMap = new HashMap<ActivationState, Map<MatchType, Activation<MatchType>>>();
this.stateMap.put(ActivationState.APPEARED, new HashMap<MatchType, Activation<MatchType>>());
this.stateMap.put(ActivationState.DISAPPEARED, new HashMap<MatchType, Activation<MatchType>>());
this.stateMap.put(ActivationState.UPDATED, new HashMap<MatchType, Activation<MatchType>>());
this.activationProvider = new ActivationNotificationProvider() {
@Override
protected void listenerAdded(IActivationNotificationListener listener, boolean fireNow) {
if (fireNow) {
for (Activation<MatchType> activation : getActivations()) {
listener.activationAppeared(activation);
}
}
}
};
}
@Override
public Pattern getPattern() {
return matcher.getPattern();
}
/**
* This method is called when the activation for the rule is fired. Subtypes may use this to step the state machine
* of the activation.
*
* @param activation
* the activation that was fired
*/
public abstract void activationFired(Activation<MatchType> activation);
@Override
public IAgenda getAgenda() {
return agenda;
}
@Override
public List<Activation<MatchType>> getActivations() {
List<Activation<MatchType>> activations = new ArrayList<Activation<MatchType>>();
for (ActivationState as : stateMap.keySet()) {
for (MatchType match : stateMap.get(as).keySet()) {
Activation<MatchType> a = stateMap.get(as).get(match);
if (!a.isFired()) {
activations.add(a);
}
}
}
return activations;
}
/**
* This method is only used when upgradedStateUsed flag is true
*
* @param match
*/
protected void processMatchModification(MatchType match) {
Map<MatchType, Activation<MatchType>> updatedMap = stateMap.get(ActivationState.UPDATED);
Map<MatchType, Activation<MatchType>> appearedMap = stateMap.get(ActivationState.APPEARED);
Activation<MatchType> activation = updatedMap.get(match);
// if this method is called the activation associated to the match must be in either appeared or upgraded state
if (activation != null) {
// upgraded state is not changed
activation.setFired(false);
activationProvider.notifyActivationAppearance(activation);
} else {
activation = appearedMap.get(match);
if (activation != null && activation.isFired()) {
// changing activation state from appeared to upgraded
appearedMap.remove(match);
activation.setFired(false);
activation.setState(ActivationState.UPDATED);
updatedMap.put(match, activation);
activationProvider.notifyActivationAppearance(activation);
}
}
}
protected void processMatchAppearance(MatchType match) {
Map<MatchType, Activation<MatchType>> disappearedMap = stateMap.get(ActivationState.DISAPPEARED);
Map<MatchType, Activation<MatchType>> appearedMap = stateMap.get(ActivationState.APPEARED);
// activation can be in not exists or disappeared state
Activation<MatchType> activation = disappearedMap.get(match);
if (activation != null) {
// if disappearedStateUsed flag is false no activation will be inserted into disappearedMap
disappearedMap.remove(match);
activation.setFired(true);
activation.setState(ActivationState.APPEARED);
appearedMap.put(match, activation);
activationProvider.notifyActivationDisappearance(activation);
} else {
activation = createActivation(match);
appearedMap.put(match, activation);
if (upgradedStateUsed) {
attributeMonitor.registerFor(match);
}
activationProvider.notifyActivationAppearance(activation);
}
}
protected abstract Activation<MatchType> createActivation(MatchType match);
protected void processMatchDisappearance(MatchType match) {
Map<MatchType, Activation<MatchType>> disappearedMap = stateMap.get(ActivationState.DISAPPEARED);
Map<MatchType, Activation<MatchType>> appearedMap = stateMap.get(ActivationState.APPEARED);
Map<MatchType, Activation<MatchType>> updatedMap = stateMap.get(ActivationState.UPDATED);
// activation can be in appeared or updated state
Activation<MatchType> activation = appearedMap.get(match);
if (activation != null) {
// changing activation state from appeared to disappeared if it was fired
if (activation.isFired() && disappearedStateUsed) {
appearedMap.remove(match);
activation.setFired(false);
activation.setState(ActivationState.DISAPPEARED);
disappearedMap.put(match, activation);
activationProvider.notifyActivationAppearance(activation);
} else {
appearedMap.remove(match);
// unregistering change listener from the affected observable values
attributeMonitor.unregisterFor(match);
activationProvider.notifyActivationDisappearance(activation);
}
} else {
// changing activation state from updated to disappeared if it was fired
activation = updatedMap.get(match);
activation.setFired(false);
updatedMap.remove(match);
activation.setState(ActivationState.DISAPPEARED);
disappearedMap.put(match, activation);
activationProvider.notifyActivationAppearance(activation);
}
}
@Override
public void notifyAppearance(MatchType match) {
processMatchAppearance(match);
}
@Override
public void notifyDisappearance(MatchType match) {
processMatchDisappearance(match);
}
@Override
public void notifyUpdate(MatchType match) {
processMatchModification(match);
}
@Override
public boolean addActivationNotificationListener(IActivationNotificationListener listener, boolean fireNow) {
return activationProvider.addActivationNotificationListener(listener, fireNow);
}
@Override
public boolean removeActivationNotificationListener(IActivationNotificationListener listener) {
return activationProvider.removeActivationNotificationListener(listener);
}
@Override
public void setStateChangeProcessor(ActivationState newState, IMatchProcessor<MatchType> processor) {
switch (newState) {
case APPEARED:
afterAppearanceJob = processor;
break;
case DISAPPEARED:
afterDisappearanceJob = processor;
break;
case UPDATED:
afterModificationJob = processor;
break;
}
}
@Override
public IMatchProcessor<MatchType> getStateChangeProcessor(ActivationState newState) {
switch (newState) {
case APPEARED:
return afterAppearanceJob;
case DISAPPEARED:
return afterDisappearanceJob;
case UPDATED:
return afterModificationJob;
default:
return null;
}
}
}