package com.sap.furcas.runtime.referenceresolving;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.antlr.runtime.ANTLRStringStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.Lexer;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.ocl.ParserException;
import org.eclipse.ocl.ecore.CollectionType;
import org.eclipse.ocl.ecore.OCL;
import org.eclipse.ocl.ecore.OCL.Helper;
import org.eclipse.ocl.ecore.OCLExpression;
import org.eclipse.ocl.ecore.opposites.OppositeEndFinder;
import org.eclipse.ocl.examples.impactanalyzer.ImpactAnalyzer;
import org.eclipse.ocl.examples.impactanalyzer.ImpactAnalyzerFactory;
import org.eclipse.ocl.examples.impactanalyzer.PartialEvaluator;
import org.eclipse.ocl.examples.impactanalyzer.PartialEvaluatorFactory;
import org.eclipse.ocl.examples.impactanalyzer.util.OCLFactory;
import com.sap.emf.ocl.trigger.AbstractTriggerable;
import com.sap.emf.ocl.trigger.ExpressionWithContext;
import com.sap.furcas.metamodel.FURCAS.TCS.ContextTemplate;
import com.sap.furcas.metamodel.FURCAS.TCS.ForeachPredicatePropertyInit;
import com.sap.furcas.metamodel.FURCAS.TCS.InjectorActionsBlock;
import com.sap.furcas.metamodel.FURCAS.TCS.PredicateSemantic;
import com.sap.furcas.metamodel.FURCAS.TCS.PropertyReference;
import com.sap.furcas.metamodel.FURCAS.TCS.SequenceElement;
import com.sap.furcas.metamodel.FURCAS.TCS.Template;
import com.sap.furcas.metamodel.FURCAS.textblocks.ForEachExecution;
import com.sap.furcas.metamodel.FURCAS.textblocks.TextBlock;
import com.sap.furcas.metamodel.FURCAS.textblocks.TextblocksFactory;
import com.sap.furcas.metamodel.FURCAS.textblocks.TextblocksPackage;
import com.sap.furcas.modeladaptation.emf.adaptation.EMFModelAdapter;
import com.sap.furcas.modeladaptation.emf.lookup.QueryBasedEcoreMetaModelLookUp;
import com.sap.furcas.runtime.common.interfaces.IMetaModelLookup;
import com.sap.furcas.runtime.common.util.ContextAndForeachHelper;
import com.sap.furcas.runtime.common.util.EcoreHelper;
import com.sap.furcas.runtime.common.util.FileResourceHelper;
import com.sap.furcas.runtime.common.util.TCSSpecificOCLEvaluator;
import com.sap.furcas.runtime.parser.IModelAdapter;
import com.sap.furcas.runtime.parser.IParsingObserver;
import com.sap.furcas.runtime.parser.ModelElementCreationException;
import com.sap.furcas.runtime.parser.ParserFactory;
import com.sap.furcas.runtime.parser.PartitionAssignmentHandlerBaseImpl;
import com.sap.furcas.runtime.parser.exceptions.UnknownProductionRuleException;
import com.sap.furcas.runtime.parser.impl.DefaultTextAwareModelAdapter;
import com.sap.furcas.runtime.parser.impl.DelegationParsingObserver;
import com.sap.furcas.runtime.parser.impl.ForeachParsingObserver;
import com.sap.furcas.runtime.parser.impl.ObservableInjectingParser;
import com.sap.furcas.runtime.parser.textblocks.TbParsingUtil;
import com.sap.furcas.runtime.tcs.TcsUtil;
import com.sap.furcas.runtime.textblocks.TbNavigationUtil;
import com.sap.furcas.runtime.textblocks.modifcation.TbChangeUtil;
/**
* Updates a model element property by evaluating a so-called <code>foreach</code> OCL expression. Based on the
* evaluation result, a certain number of elements of a certain type is created and assigned to the property. Of which
* types those elements are and how may there will be created depends on the parameterization of the updater.
* <p>
*
* If no further specifications are provided, for each evaluation result one element of corresponding type is created
* using the respective template with empty (default) mode.
* <p>
*
* A <code>mode</code> argument may be provided, determining the template mode to use when creating elements. The
* template resulting from this specification is stored in {@link PredicateSemantic#getAs()}.
* <p>
*
* If a sequence of <code>when/as/mode</code> combinations is provided, each element from the evaluation result is used
* as context to evaluate the <code>when</code> clauses. The <code>as</code> template of the first one evaluating to
* <code>true</code> is used to create the element. The mode specification is already factored into the template stored
* in the {@link PredicateSemantic#getAs()} result. The <code>when</code> clause is optional. If not specified, all
* elements match. This is useful when used as the last rule in a sequence, as a default rule, used if no other
* <code>when</code> clause matches and is considered accordingly here.
* <p>
*
* @author Axel Uhl (D043530)
*
*/
public class ForeachPropertyInitUpdater extends AbstractFurcasOCLBasedModelUpdater {
/**
* Used to override {@link AbstractTriggerable#getTriggerExpressionsWithContext()}
*/
private final Collection<ExpressionWithContext> triggerExpressionsWithContext;
/**
* The base expression "X" used in the <code>foreach(X ...)</code> property init
*/
private final OCLExpression baseForeachExpression;
/**
* Enables mapping the OCL expressions used in the <code>when</code> clauses of the <code>foreach</code> predicate
* to the {@link PredicateSemantic} objects representing the respective <code>when</code> clause
*/
private final Map<OCLExpression, PredicateSemantic> expressionToWhenClause;
private final ForeachPredicatePropertyInit foreachPredicatePropertyInit;
private final ParserFactory<? extends ObservableInjectingParser, ? extends Lexer> parserFactory;
private final ImpactAnalyzer impactAnalyzerForTracebackOfBaseExpression;
protected ForeachPropertyInitUpdater(ForeachPredicatePropertyInit foreachPredicatePropertyInit,
EPackage.Registry metamodelPackageRegistry,
ParserFactory<? extends ObservableInjectingParser, ? extends Lexer> parserFactory,
OppositeEndFinder oppositeEndFinder) throws ParserException {
super(foreachPredicatePropertyInit.getPropertyReference().getStrucfeature(), metamodelPackageRegistry,
oppositeEndFinder,
/* expression with context; provided later in getTriggerExpressionsWithContext() */null,
/* notifyNewContextElements */true, determineSelfKind(foreachPredicatePropertyInit.getValue()),
ContextAndForeachHelper.getContextTag(foreachPredicatePropertyInit.getValue()));
this.parserFactory = parserFactory;
triggerExpressionsWithContext = new LinkedList<ExpressionWithContext>();
this.foreachPredicatePropertyInit = foreachPredicatePropertyInit;
Helper oclHelper = createOCLHelper();
// add the base foreach expression:
baseForeachExpression = oclHelper.createQuery(ContextAndForeachHelper
.prepareOclQuery(foreachPredicatePropertyInit.getValue()));
triggerExpressionsWithContext.add(new ExpressionWithContext(baseForeachExpression,
(EClass) ContextAndForeachHelper.getParsingContext(foreachPredicatePropertyInit.getValue(),
((InjectorActionsBlock) foreachPredicatePropertyInit.eContainer()).getParentTemplate())));
if (this.foreachPredicatePropertyInit.getPredicateSemantic().isEmpty()) {
expressionToWhenClause = Collections.emptyMap();
} else {
expressionToWhenClause = new HashMap<OCLExpression, PredicateSemantic>();
}
EClassifier baseType = baseForeachExpression.getType();
if (baseType instanceof CollectionType) {
baseType = ((CollectionType) baseType).getElementType();
}
oclHelper.setContext(baseType);
for (PredicateSemantic whenClause : foreachPredicatePropertyInit.getPredicateSemantic()) {
if (whenClause.getWhen() != null) {
OCLExpression whenClausExpression = oclHelper.createQuery(whenClause.getWhen());
expressionToWhenClause.put(whenClausExpression, whenClause);
triggerExpressionsWithContext.add(new ExpressionWithContext(whenClausExpression,
(EClass) baseType));
}
}
impactAnalyzerForTracebackOfBaseExpression = ImpactAnalyzerFactory.INSTANCE.createImpactAnalyzer(baseForeachExpression,
/* notifyOnNewContextElements */ false, oppositeEndFinder, OCLFactory.getInstance());
}
private Helper createOCLHelper() throws ParserException {
return createOCLHelper(foreachPredicatePropertyInit.getValue(),
((InjectorActionsBlock) foreachPredicatePropertyInit.eContainer()).getParentTemplate(),
getOppositeEndFinder());
}
@Override
public void notify(OCLExpression expression, Collection<EObject> affectedContextObjects,
OppositeEndFinder oppositeEndFinder, Notification change) {
if (expression == baseForeachExpression) {
handleChangeOfBaseExpressionValue(affectedContextObjects, change);
} else {
PredicateSemantic whenClause = expressionToWhenClause.get(expression);
if (whenClause != null) {
handleChangeOfWhenClauseValue(whenClause, affectedContextObjects, change);
}
}
}
private void handleChangeOfBaseExpressionValue(Collection<EObject> affectedContextObjects, Notification change) {
PartialEvaluator partialEvaluator = PartialEvaluatorFactory.INSTANCE.createPartialEvaluator(change,
getOppositeEndFinder(), OCLFactory.getInstance());
OCL ocl = org.eclipse.ocl.examples.impactanalyzer.util.OCL.newInstance(getOppositeEndFinder());
for (EObject affectedContextObject : affectedContextObjects) {
Object oldValue = partialEvaluator.evaluate(affectedContextObject, baseForeachExpression);
Object newValue = ocl.evaluate(affectedContextObject, baseForeachExpression);
if (oldValue != newValue && (oldValue == null || !oldValue.equals(newValue))) {
// something changed
// the getElementsToUpdate(affectedContextObject) is necessary because the foreach
// base expression may itself use #context or #foreach
updateFeature(getElementsToUpdate(affectedContextObject), newValue);
}
}
}
/**
* The <code>change</code> event may have affected the value of the <code>whenClause</code>. The same change may
* have had other effects as well, e.g., on the other <code>when</code>-clauses of the same <code>foreach</code>
* construct.
* <p>
*
* The elements for which the <code>when</code>-clause value has changed may or may not have been results of a
* <code>foreach</code> clause. To find this out, the impact analyzer can trace back the candidate object through
* the <code>foreach</code> expression and see if there is a {@link TextBlock} documenting the execution of the
* <code>foreach</code> clause for the respective element. If not, the change is simply ignored. If yes, and if the
* <code>foreach</code> expression actually evaluates to the affected context object from
* <code>affectedContextObjects</code>, the production of the corresponding result is triggered and the resulting
* element is used to replace the old element from the {@link #getPropertyToUpdate() feature to update}.
*
* @param whenClause
* the <code>when</code>-clause whose value may have changed due to <code>change</code>
* @param affectedContextObjects
* the superset of the context objects for which the <code>when</code>-clause may have changed its value
* @param change
* the original change that may have caused a change in <code>whenClause</code>'s value when evaluated on
* <code>affectedContextObjects</code>
*/
private void handleChangeOfWhenClauseValue(PredicateSemantic whenClause,
Collection<EObject> affectedContextObjects, Notification change) {
// The context element of the when-clause is the result of the corresponding foreach element.
// We need to use the impact analyzer to trace this back to the foreach "self" context and repeat
// this as if the foreach expression itself had changed.
// We assume here that no #context nor #foreach is used in the when-clause.
// It wouldn't make much sense anyway because the when-clause should filter the foreach result.
for (EObject affectedContextObject : affectedContextObjects) {
Template newTemplateToUse = findTemplate(affectedContextObject);
// First try to see if a previous ForEachExecution is documented:
Collection<EObject> foreachExecutionsUsingSelfAsForeachElement = getOppositeEndFinder()
.navigateOppositePropertyWithBackwardScope(
TextblocksPackage.eINSTANCE.getForEachExecution_ContextElement(), affectedContextObject);
if (foreachExecutionsUsingSelfAsForeachElement != null && !foreachExecutionsUsingSelfAsForeachElement.isEmpty()) {
for (EObject eo : foreachExecutionsUsingSelfAsForeachElement) {
if (eo.eContainer() != null) { // ignore stale ForEachContext that for unknown reasons are still
// returned by opposite end finder
ForEachExecution foreachExecution = (ForEachExecution) eo;
EObject elementToUpdate = foreachExecution.getSourceModelElement();
TextBlock textBlock = (TextBlock) foreachExecution.eContainer();
ForeachPredicatePropertyInit propInit = foreachExecution.getForeachPedicatePropertyInit();
if (propInit == foreachPredicatePropertyInit) {
Template oldTemplateUsedForProduction = foreachExecution.getTemplateUsedForProduction();
// check if the template to use for producing really changed; if so, execute the one
// production rule and carefully update the existing ForEachExecution
if (newTemplateToUse != oldTemplateUsedForProduction) {
if (newTemplateToUse == null) {
// remove element and ForEachExecution record
@SuppressWarnings("unchecked")
List<EObject> l = (List<EObject>) elementToUpdate.eGet(getPropertyToUpdate());
l.remove(foreachExecution.getResultModelElement());
TbChangeUtil.delete(foreachExecution);
} else {
// the supposed change really led to a change in production rule; produce anew
EObject newObject = produceWith(newTemplateToUse, affectedContextObject, textBlock,
elementToUpdate, getOppositeEndFinder());
if (getPropertyToUpdate().isMany()) {
int position = textBlock.getForEachExecutions().indexOf(foreachExecution);
@SuppressWarnings("unchecked")
List<EObject> l = (List<EObject>) elementToUpdate.eGet(getPropertyToUpdate());
l.set(position, newObject);
} else {
elementToUpdate.eSet(getPropertyToUpdate(), newObject);
}
foreachExecution.setResultModelElement(newObject);
}
}
}
}
}
} else {
// Perhaps the foreach element changed from no matching when-clause to at least one
// matching when-clause because no ForEachExecution exists for the presumed foreach-element so far.
// The affectedContextObject is likely the foreach result. Trace back through the foreach
// expression to find out which the elements-to-update are and check if the foreach
// property init actually got executed for the element-to-update.
Collection<EObject> foreachBaseExpressionContexts = impactAnalyzerForTracebackOfBaseExpression
.getContextObjects(affectedContextObject);
for (EObject foreachBaseExpressionContext : foreachBaseExpressionContexts) {
Collection<EObject> textBlocks = getOppositeEndFinder().navigateOppositePropertyWithBackwardScope(
TextblocksPackage.eINSTANCE.getTextBlock_CorrespondingModelElements(),
foreachBaseExpressionContext);
TextBlock textBlock = (TextBlock) textBlocks.iterator().next();
if (foreachWasExecutedFor(textBlock)) {
// resolve through use of #context / #foreach in base expression to find true element to update
try {
Set<EObject> elementsToUpdate = getElementsToUpdate(foreachBaseExpressionContext);
for (EObject elementToUpdate : elementsToUpdate) {
// if the result of the when-clauses is that there's still nothing to produce,
// then nothing needs to be done
if (newTemplateToUse != null) {
// the supposed change really led to a change in production rule; produce anew
EObject newObject = produceWith(newTemplateToUse, affectedContextObject, textBlock,
elementToUpdate, getOppositeEndFinder());
int position;
if (getPropertyToUpdate().isMany()) {
Object foreachBaseExpressionResult = createOCLHelper().getOCL().evaluate(
foreachBaseExpressionContext, baseForeachExpression);
@SuppressWarnings("unchecked")
List<EObject> l = (List<EObject>) elementToUpdate.eGet(getPropertyToUpdate());
if (foreachBaseExpressionResult instanceof List<?>) {
position = ((List<?>) foreachBaseExpressionResult).indexOf(affectedContextObject);
} else {
// no list or not even a collection; append at end
position = l.size();
}
l.add(position, newObject);
} else {
elementToUpdate.eSet(getPropertyToUpdate(), newObject);
position = 0;
}
ForEachExecution foreachExecution = createForeachExecution(elementToUpdate,
affectedContextObject, newObject, newTemplateToUse);
textBlock.getForEachExecutions().add(position, foreachExecution);
}
}
} catch (ParserException e) {
// TODO this try/catch block will probably disappear when no OCL parsing is necessary in
// getElementsToUpdate
throw new RuntimeException(e);
}
}
}
}
}
}
/**
* Receives the current value of the foreach base expression and the elements on which to update the feature
* indicated by {@link #foreachPredicatePropertyInit}.{@link ForeachPredicatePropertyInit#getPropertyReference()
* getPropertyReference()}. {@link PropertyReference#getStrucfeature() getStrucfeature()}.
* <p>
*
* With the current coarse-grained replacement strategy, all {@link ForEachExecution} elements attached to the
* {@link TextBlock} documenting the creation of each of the <code>elementsToUpdate</code> are removed first. Then,
* the new elements are produced using {@link #produceElement(Object, TextBlock, ResourceSet, OppositeEndFinder)}
* operation. As a record of this, a new {@link ForEachExecution} element is created for each object creation. They
* are attached to the {@link TextBlock#getForEachExecutions()} collection of the text blocks documenting the creation
* of the <code>elementsToUpdate</code>.
*/
private void updateFeature(Set<EObject> elementsToUpdate, Object newValueOfForeachBaseExpression) {
Collection<?> foreachElements;
if (newValueOfForeachBaseExpression instanceof Collection<?>) {
foreachElements = (Collection<?>) newValueOfForeachBaseExpression;
} else {
foreachElements = Collections.singleton(newValueOfForeachBaseExpression);
}
for (EObject elementToUpdate : elementsToUpdate) {
// update the element only if a TextBlock documents its creation and indicates that the
// InjectorActionsBlock containing the foreachPredicatePropertyInit has actually been executed
// during its creation.
// We assume that the when-clauses and their evaluation results haven't changed for those
// elements to which foreach element production already applied. Such changes are handled
// by the impact analyzer for the when-clause expressions.
// We loop over the ForEachExecution elements attached to the TextBlock that documents
// the creation of elementToUpdate, filtering for those that are for foreachPredicatePropertyInit
// and having elementToUpdate as their source element (could be different in case of
// nested foreach templates). Existing ForEachExecution objects are updated; new elements
// are produced if the template of the foreach-element differ. Existing ForEachExecution
// objects can be updated with newly-produced objects. If the ForEachExecution list is
// preempted before all elements have been produced, new ForEachExecution elements are
// appended to the text block. If there are trailing not re-used ForEachExecution elements,
// they are deleted.
// from the following ForEachExecution elements select the sub-sequence for which all elements
// refer to the foreachPredicatePropertyInit
Collection<EObject> textBlocks = getOppositeEndFinder().navigateOppositePropertyWithBackwardScope(
TextblocksPackage.eINSTANCE.getTextBlock_CorrespondingModelElements(), elementToUpdate);
TextBlock textBlock = (TextBlock) textBlocks.iterator().next();
if (foreachWasExecutedFor(textBlock)) {
Iterator<ForEachExecution> foreachExecutionsIterator = textBlock.getForEachExecutions().iterator();
ForEachExecution nextForEachExecution = getNextForeachExecution(foreachExecutionsIterator, elementToUpdate);
Collection<Object> newFeatureValue = new BasicEList<Object>();
for (Object foreachElement : foreachElements) {
EObject producedElement = produceElement(foreachElement, textBlock, elementToUpdate,
getOppositeEndFinder(), nextForEachExecution);
if (producedElement != null) {
if (!(foreachPredicatePropertyInit.getPropertyReference().getStrucfeature() instanceof EReference)
|| !((EReference) foreachPredicatePropertyInit.getPropertyReference().getStrucfeature())
.isContainment()) {
// assign to elementToUpdate's Resource as a default, in case it's not added to a
// containment reference
elementToUpdate.eResource().getContents().add(producedElement);
}
newFeatureValue.add(producedElement);
if (nextForEachExecution != null) {
nextForEachExecution = getNextForeachExecution(foreachExecutionsIterator, elementToUpdate);
}
}
}
// delete trailing ForEachExecutions
while (nextForEachExecution != null) {
foreachExecutionsIterator.remove();
nextForEachExecution = getNextForeachExecution(foreachExecutionsIterator, elementToUpdate);
}
if (foreachPredicatePropertyInit.getPropertyReference().getStrucfeature().isMany()) {
elementToUpdate.eSet(foreachPredicatePropertyInit.getPropertyReference().getStrucfeature(),
newFeatureValue);
} else {
if (newFeatureValue.isEmpty()) {
elementToUpdate.eSet(foreachPredicatePropertyInit.getPropertyReference().getStrucfeature(),
null);
} else {
elementToUpdate.eSet(foreachPredicatePropertyInit.getPropertyReference().getStrucfeature(),
newFeatureValue.iterator().next()); // pick first element
}
}
}
}
}
/**
* Finds the next {@link ForEachExecution} in the collection iterated by <code>foreachExecutionIterator</code> that is
* for the same {@link #foreachPredicatePropertyInit property init} as this updater and has
* <code>elementToUpdate</code> as its {@link ForEachExecution#getSourceModelElement() source element}. If no such
* element is found, <code>null</code> is returned
*
* Postcondition: if a non-<code>null</code> result is returned, the <code>foreachExecutionIterator</code> is at that
* element so that calling {@link Iterator#remove()} removes the element just returned
*
* @param foreachExecutionIterator
* must not be <code>null</code>
*/
private ForEachExecution getNextForeachExecution(Iterator<ForEachExecution> foreachExecutionIterator, EObject elementToUpdate) {
ForEachExecution result = null;
while (foreachExecutionIterator.hasNext() && result == null) {
ForEachExecution fec = foreachExecutionIterator.next();
if (fec.getForeachPedicatePropertyInit().equals(foreachPredicatePropertyInit)) {
result = fec;
}
}
return result;
}
private ForEachExecution createForeachExecution(EObject elementToUpdate, Object foreachElement,
EObject producedElement, Template template) {
// create ForEachExecution element documenting what just happened in the TextBlocks model
ForEachExecution foreachExecution = TextblocksFactory.eINSTANCE.createForEachExecution();
foreachExecution.setForeachPedicatePropertyInit(foreachPredicatePropertyInit);
foreachExecution.setSourceModelElement(elementToUpdate);
if (foreachElement instanceof EObject) {
foreachExecution.setContextElement((EObject) foreachElement);
} else if (foreachElement instanceof String) {
foreachExecution.setContextString((String) foreachElement);
} // else it must have been a Boolean which we don't record
foreachExecution.setTemplateUsedForProduction(template);
foreachExecution.setResultModelElement(producedElement);
return foreachExecution;
}
/**
* Tries to find the execution of {@link #foreachPredicatePropertyInit}.
* {@link ForeachPredicatePropertyInit#getInjectorActionsBlock() getInjectorActionsBlock()} documented inside
* <code>textBlock</code>.
*/
private boolean foreachWasExecutedFor(TextBlock textBlock) {
return TcsUtil.wasExecuted((ContextTemplate) textBlock.getType(),
textBlock.getParentAltChoices(), getSequenceElement());
}
/**
* Determines the template to use to produce the element for the given <code>foreachElement</code> which is a result
* object from the foreach-expression's evaluation result. If a <code>null</code> <code>forEachExecution</code> is
* passed, a new element is produced and a new {@link ForEachExecution} will be constructed and appended to the
* <code>textBlock</code>'s {@link TextBlock#getForEachExecutions() foreach contexts}, documenting this rule
* execution.
* <p>
*
* If a non-<code>null</code> <code>forEachExecution</code> is passed, two cases are possible. If the template that
* would be used now for production is the same as the one pointed to by the {@link ForEachExecution} and the same
* foreach result was used, no production is necessary. The {@link ForEachExecution} as well as its element can be
* re-used. The {@link ForEachExecution#getResultModelElement() result element} of the {@link ForEachExecution} is
* used as this method's result in this case.
* <p>
*
* If the template to use for production differs from the one in the <code>forEachExecution</code> or the production
* was carried out for a different foreach result, the production is executed and the {@link ForEachExecution} is
* updated with the results.
*
* @return the element produced or <code>null</code> if no matching template was found, e.g., because no
* "when"-clause matched the element
*/
private EObject produceElement(Object foreachElement, TextBlock textBlock, EObject elementToUpdate,
OppositeEndFinder oppositeEndFinder, ForEachExecution forEachExecution) {
Template template = null; // null means "don't produce"
if (foreachElement instanceof Boolean) {
if ((Boolean) foreachElement) {
template = foreachPredicatePropertyInit.getPredicateSemantic().iterator().next().getAs();
}
} else if (foreachElement instanceof EObject) {
template = findTemplate((EObject) foreachElement);
} else {
// for other non-EObject, non-Boolean types produce using the template of the single when/as
template = foreachPredicatePropertyInit.getPredicateSemantic().iterator().next().getAs();
}
EObject result = null;
if (template != null) {
if (forEachExecution == null || forEachExecution.getTemplateUsedForProduction() != template ||
forEachExecution.getContextElement() != foreachElement) {
result = produceWith(template, foreachElement, textBlock, elementToUpdate, oppositeEndFinder);
if (forEachExecution == null) {
ForEachExecution newForEachExecution = createForeachExecution(elementToUpdate, foreachElement, result, template);
// no concurrent modification exception can occur here because forEachExecution is null
// which avoids further calls to the iterator's next() operation
textBlock.getForEachExecutions().add(newForEachExecution);
} else {
forEachExecution.setTemplateUsedForProduction(template);
forEachExecution.setResultModelElement(result);
if (foreachElement instanceof EObject) {
forEachExecution.setContextElement((EObject) foreachElement);
} else if (foreachElement instanceof String) {
forEachExecution.setContextString((String) foreachElement);
}
}
} else {
// re-use result object from ForEachExecution:
result = forEachExecution.getResultModelElement();
}
}
return result;
}
/**
* Executes the <code>template</code>'s parse rule. The
* {@link ObservableInjectingParser#setCurrentForeachElement(Object) foreach element} on the parser can easily be
* set to <code>foreachElement</code>. Tricky is establishing the #context stack.
*
* @param textBlock
* required to construct a context stack if necessary and to initialize the lexer's token stream
* @return the element produced
*/
private EObject produceWith(Template template, Object foreachElement, TextBlock textBlock, EObject elementToUpdate,
OppositeEndFinder oppositeEndFinder) {
ResourceSet resourceSet = elementToUpdate.eResource().getResourceSet();
Resource transientResource = EcoreHelper.createTransientParsingResource(resourceSet, "http://furcas.org/foreach/reevaluation/");
try {
String ruleName = parserFactory.getRuleNameFinder().getRuleName(template, /*mode*/ null);
String content = TbNavigationUtil.getUltraRoot(textBlock).getCachedString();
Lexer lexer = parserFactory.createLexer(new ANTLRStringStream(content));
// Construct scope that includes the additionalQueryScope as well as all dirty
// resources. The latter includes the transient parsing resource used by the incremental parser.
HashSet<URI> scope = new HashSet<URI>(parserFactory.getAdditionalQueryScope());
scope.addAll(FileResourceHelper.getResourceSetAsScope(resourceSet));
IMetaModelLookup<EObject> metamodelLookup = new QueryBasedEcoreMetaModelLookUp(resourceSet,
parserFactory.getMetamodelURIs());
IModelAdapter modelAdapter = new DefaultTextAwareModelAdapter(new EMFModelAdapter(resourceSet,
new PartitionAssignmentHandlerBaseImpl(transientResource), metamodelLookup, scope,
new TCSSpecificOCLEvaluator(oppositeEndFinder), oppositeEndFinder));
ObservableInjectingParser parser = parserFactory.createParser(new CommonTokenStream(lexer), modelAdapter);
DelegationParsingObserver delegator = new DelegationParsingObserver();
IParsingObserver originalObserver = parser.getObserver();
if (originalObserver != null) {
delegator.addParsingObserver(originalObserver);
}
delegator.addParsingObserver(new ForeachParsingObserver(textBlock));
parser.setObserver(delegator);
// invoke the parser to execute the template
Method methodToCall = parser.getClass().getMethod(ruleName);
// parser.reset();
if (!Modifier.isFinal(methodToCall.getModifiers())) {
throw new UnknownProductionRuleException(ruleName + " is not a production rule in generated Parser.");
}
parser.setCurrentForeachElement(foreachElement);
TbParsingUtil.constructContext(textBlock, parser);
// TODO How can we be sure that the model is in a valid state when the parser is called?
// Simply injecting (modifying) the domain model through the parser might lead to data loss
EObject parseReturn = (EObject) methodToCall.invoke(parser);
if (parseReturn == null) {
throw new ModelElementCreationException("Unable to create model element using parse rule " + ruleName
+ ". Parse errors: " + parser.getInjector().getErrorList());
}
parser.setDelayedReferencesAfterParsing(); // TODO instead of using DelayedReference stuff, migrate to model updaters
return parseReturn;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
transientResource.getContents().clear();
resourceSet.getResources().remove(transientResource);
}
}
private TextBlock getRootBlock(TextBlock textBlock) {
TextBlock result = textBlock;
while (result.eContainer() != null && result.eContainer() instanceof TextBlock) {
result = (TextBlock) result.eContainer();
}
return result;
}
/**
* Determines for an {@link EObject} result of the foreach base expression which template to use to produce the
* element. If there is no {@link PredicateSemantic} associated, use the <code>foreachElement</code>'s type and the
* {@link ForeachPredicatePropertyInit#getMode() global mode} to decide which template to use (using
* {@link TcsUtil#getTemplateMode(Template)}). If no template is found, a {@link RuntimeException} is thrown.
*/
private Template findTemplate(EObject foreachElement) {
Template result = null;
if (foreachPredicatePropertyInit.getPredicateSemantic().isEmpty()) {
String mode = foreachPredicatePropertyInit.getMode();
result = TcsUtil.findTemplate(foreachElement.eClass(), mode, /* TODO partition scope */null);
} else {
OCL ocl = org.eclipse.ocl.examples.impactanalyzer.util.OCL.newInstance(getOppositeEndFinder());
Helper oclHelper = ocl.createOCLHelper();
oclHelper.setContext(foreachElement.eClass());
for (PredicateSemantic whenClause : foreachPredicatePropertyInit.getPredicateSemantic()) {
if (whenClause.getWhen() != null) {
OCLExpression when;
// TODO this try/catch will probably disappear when we're compiling the OCL ASTs into the FURCAS mapping
try {
// TODO cache compiled expressions; caution: foreachElement.eClass() may be more specific than real context type; obtain context type from foreach expression's type
// TODO use opposite direction of expressionToWhenClause, perhaps requiring another map?
when = oclHelper.createQuery(whenClause.getWhen());
} catch (ParserException e) {
throw new RuntimeException(e);
}
Object whenResult = ocl.evaluate(foreachElement, when);
if (ocl.getEnvironment().getOCLStandardLibrary().getInvalid() != whenResult) {
Boolean match = (Boolean) whenResult;
if (match) {
result = whenClause.getAs();
break;
}
}
} else {
// no when clause means a match
result = whenClause.getAs();
break;
}
}
}
return result;
}
@Override
public Collection<ExpressionWithContext> getTriggerExpressionsWithContext() {
return triggerExpressionsWithContext;
}
@Override
protected SequenceElement getSequenceElement() {
return foreachPredicatePropertyInit.getInjectorActionsBlock();
}
}