package com.sap.furcas.runtime.referenceresolving; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EPackage; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.ocl.ParserException; 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 com.sap.emf.ocl.trigger.AbstractOCLBasedModelUpdater; 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.InjectorAction; import com.sap.furcas.metamodel.FURCAS.TCS.InjectorActionsBlock; import com.sap.furcas.metamodel.FURCAS.TCS.PredicateSemantic; import com.sap.furcas.metamodel.FURCAS.TCS.Property; import com.sap.furcas.metamodel.FURCAS.TCS.SequenceElement; import com.sap.furcas.metamodel.FURCAS.TCS.Template; import com.sap.furcas.metamodel.FURCAS.textblocks.DocumentNode; import com.sap.furcas.metamodel.FURCAS.textblocks.ForEachExecution; import com.sap.furcas.metamodel.FURCAS.textblocks.TextBlock; import com.sap.furcas.metamodel.FURCAS.textblocks.TextblocksPackage; import com.sap.furcas.runtime.common.util.ContextAndForeachHelper; import com.sap.furcas.runtime.parser.impl.ModelElementProxy; import com.sap.furcas.runtime.parser.impl.ModelUpdater; import com.sap.furcas.runtime.tcs.TcsUtil; /** * Offers some "text blocks magic" utility methods to subclasses, such as finding the text blocks and from those the * elements to which particular {@link InjectorAction}s were applied. This can be used, e.g., to filter result of the * {@link ImpactAnalyzer#getContextObjects(org.eclipse.emf.common.notify.Notification)} method further so as to only * re-apply an injector action to those elements to which the injector action was applied before. * * @author Axel Uhl (D043530) * */ public abstract class AbstractFurcasOCLBasedModelUpdater extends AbstractOCLBasedModelUpdater implements ModelUpdater { public enum SelfKind { SELF, CONTEXT, FOREACH } private final SelfKind selfKind; private final String contextTag; protected AbstractFurcasOCLBasedModelUpdater(EStructuralFeature propertyToUpdate, EPackage.Registry metamodelPackageRegistry, OppositeEndFinder oppositeEndFinder, ExpressionWithContext triggerExpression, boolean notifyOnNewContextElements, SelfKind selfKind, String contextTag) { super(propertyToUpdate, metamodelPackageRegistry, oppositeEndFinder, triggerExpression, notifyOnNewContextElements); this.selfKind = selfKind; this.contextTag = contextTag; } protected SelfKind getSelfKind() { return selfKind; } /** * This default implementation re-evaluates the {@link #triggerExpression} on each element reported as affected and * sets the {@link #getPropertyToUpdate() property} to update by this updater to the evaluation result. */ @Override public void notify(OCLExpression expression, Collection<EObject> affectedContextObjects, OppositeEndFinder oppositeEndFinder, Notification change) { OCL ocl = org.eclipse.ocl.examples.impactanalyzer.util.OCL.newInstance(oppositeEndFinder); for (EObject eo : affectedContextObjects) { Object newValue = ocl.evaluate(eo, expression); // only assign if result was not "invalid" if (ocl.getEnvironment().getOCLStandardLibrary().getInvalid() != newValue) { if (!getPropertyToUpdate().isMany() && newValue instanceof Collection) { // pick first result if it exists or leave null newValue = ((Collection<?>) newValue).isEmpty() ? null : ((Collection<?>) newValue).iterator() .next(); } for (EObject elementToUpdate : getElementsToUpdate(eo)) { elementToUpdate.eSet(getPropertyToUpdate(), newValue); } } } } protected static Helper createOCLHelper(String oclExpression, Template contextTemplate, OppositeEndFinder oppositeEndFinder) throws ParserException { Helper result = org.eclipse.ocl.examples.impactanalyzer.util.OCL.newInstance(oppositeEndFinder).createOCLHelper(); EClass parsingContext = (EClass) ContextAndForeachHelper.getParsingContext(oclExpression, contextTemplate); result.setContext(parsingContext); return result; } /** * The OCL impact analyzer determines OCL context elements for which an expression of this model updater may have * changed its value. But those elements are not necessarily the same elements on which a property is to be updated * with the new evaluation result. Instead, if <code>#context</code> or <code>#foreach</code> were used in the OCL * expression, then the element produced by some superior <code>context</code> template or a superior template's * <code>foreach</code> element is the context element of the OCL expression, and the element to be updated has to * be determined from the textblocks model which represents a record of template executions and lets us determine * the element for which <code>#context</code> or <code>#foreach</code>, respectively, identifies the element * delivered as OCL context element by the impact analyzer. * <p> * * This method uses the {@link #selfKind} enumeration to determine the strategy by which to find the element to * update. We distinguish the following cases: * * <ul> * * <li>{@link SelfKind#SELF}: <code>self</code> is the element to update</li> * * <li>{@link SelfKind#CONTEXT}: <code>self</code> is the element referred to as <code>#context</code> in the * original OCL expression (before <code>#context</code> was replaced by <code>self</code>). From the * <code>self</code> element we can determine the {@link TextBlock} and from it the {@link ContextTemplate} that * created the element. The text blocks tree also tells us which production rules were triggered from there, using * the template in which the OCL-expression injector action resides. But this is only necessary, not a sufficient * criterion. We additionally have to check if for this innermost text block the * {@link TextBlock#getParentAltChoices()} indicate that along nested alternatives the one was used that actually * contains our {@link #getSequenceElement() sequence element}. See also * {@link TcsUtil#wasExecuted(ContextTemplate, org.eclipse.emf.common.util.EList, SequenceElement)}.</li> * * <li>{@link SelfKind#FOREACH}: <code>self</code> is the element referred to as <code>#foreach</code> in the * original OCL expression, before <code>#foreach</code> was replaced by <code>self</code>. There must have been a * {@link ForEachExecution} whose {@link ForEachExecution#getContextElement()} contains <code>self</code>. Once we've * found this {@link ForEachExecution}, we can fetch its {@link ForEachExecution#getResultModelElement()}. This may be * the element to be updated, if its production was actually selected by the <code>when</code> clauses of the * <code>foreach</code> clause whose execution is described by the {@link ForEachExecution}.</li> * * </ul> * * @param self * the context for the OCL expression as identified by the OCL impact analyzer; for the straightforward * case where {@link #selfKind} is {@link SelfKind#SELF}, this is at the same time the result of this * method; otherwise, the <code>inTextBlock</code> argument is used to compute the result */ protected Set<EObject> getElementsToUpdate(EObject self) { switch (selfKind) { case SELF: return getElementsToUpdateFromSelf(self); case CONTEXT: return getElementsToUpdateFromContextElement(self); case FOREACH: try { return getElementToUpdateFromForeachElement(self); } catch (ParserException e) { throw new RuntimeException(e); } default: throw new RuntimeException("Unknown self kind: "+selfKind); } } /** * Note that this method does not check whether the foreach property init was at all applied for the * <code>self</code> object that is the potential result of the foreach expression. It just checks that a * <code>foreach</code> expression has evaluated to <code>self</code>, then determines which of the * <code>when</code> clauses matches the result and whose <code>as</code> template contains the * {@link #getSequenceElement() sequence element} whose execution trace we're searching. If such * a {@link ForEachExecution} is found, its {@link ForEachExecution#getResultModelElement() result element} * is added to this method's result. */ private Set<EObject> getElementToUpdateFromForeachElement(EObject self) throws ParserException { Set<EObject> result = new HashSet<EObject>(); OCL ocl = org.eclipse.ocl.examples.impactanalyzer.util.OCL.newInstance(getOppositeEndFinder()); // The following call may return many objects because // 1) a single template may use multiple foreach predicates whose foreach expressions produce // overlapping element sets // 2) a single foreach expression may even produce multiple occurrences of the same element, e.g., in a bag // Each result indicates one production of one new element. It may be possible that the same template // gets executed more than once for the same foreach-element. Therefore, also the same injector action // may get executed several times for the same #foreach element: once per invocation of its owning // template for the same #foreach element. Collection<EObject> foreachContextsUsingSelfAsForeachElement = getOppositeEndFinder() .navigateOppositePropertyWithBackwardScope( TextblocksPackage.eINSTANCE.getForEachExecution_ContextElement(), self); if (foreachContextsUsingSelfAsForeachElement != null) { Helper oclHelper = ocl.createOCLHelper(); Template sequenceElementsParentTemplate = getSequenceElement().getParentTemplate(); for (EObject eo : foreachContextsUsingSelfAsForeachElement) { ForEachExecution foreachContext = (ForEachExecution) eo; ForeachPredicatePropertyInit propInit = foreachContext.getForeachPedicatePropertyInit(); Template templateUsedForProduction = foreachContext.getTemplateUsedForProduction(); if (templateUsedForProduction == sequenceElementsParentTemplate) { oclHelper.setContext(self.eClass()); if (propInit.getPredicateSemantic().isEmpty()) { // no when-clause; foreach produces an element in all cases result.add(foreachContext.getResultModelElement()); } else { // now check which when-clause is chosen for the current foreach-element self for (PredicateSemantic whenClause : propInit.getPredicateSemantic()) { if (whenClause.getWhen() == null // we can use the current state and don't need to use the pre-change state // because changes in when-clause selection have to be handled separately // by the ForeachPropertyInitUpdater || (Boolean) ocl.evaluate(self, oclHelper.createQuery(whenClause.getWhen()))) { // now we know the when-clause; determine template for when-clause and check if // it contains injectorAction Template t = whenClause.getAs(); if (EcoreUtil.isAncestor(t, getSequenceElement())) { // yes, the self object led to the injector action firing because // we excluded #foreach being nested inside semantic predicates which // would be the only way to create alternatives without concrete-syntactical // disambiguation. Remember that templates called by foreach(...) must not // make concrete-syntactical contributions. result.add(foreachContext.getResultModelElement()); break; // continue with the next ForEachContext element } } } } } } } return result; } /** * Obtains the text blocks documenting the execution of {@link #getSequenceElement()}. If its * {@link TextBlock#getCorrespondingModelElements()} contains <code>element</code> then * <code>element</code> will be returned as the single element of a collection. Otherwise, * an empty collection is returned. */ private Set<EObject> getElementsToUpdateFromSelf(EObject element) { Set<TextBlock> textBlocks = getTextBlocksInWhichSequenceElementWasExecuted(element, /* #context */ false); for (TextBlock tb : textBlocks) { if (element.equals(tb.getCorrespondingModelElements().get(0))) { return Collections.singleton(element); } } return Collections.emptySet(); } /** * We know the {@link #getSequenceElement() sequence element} in which the OCL expression was used * that contains the <code>#context</code> sub-expression. We also know the <code>contextElement</code> and from it * can determine the {@link TextBlock} that documents the creation of the context element. From {@link #contextTag} * we know if/which context tag was used. We need to find a path of text blocks to a text block documenting * the execution of the template containing the {@link #getSequenceElement() sequence element} containing * the OCL expression. */ private Set<EObject> getElementsToUpdateFromContextElement(EObject contextElement) { Set<EObject> result = new HashSet<EObject>(); Set<TextBlock> textBlocks = getTextBlocksInWhichSequenceElementWasExecuted(contextElement, true); for (TextBlock tb : textBlocks) { result.add(tb.getCorrespondingModelElements().get(0)); } return result; } /** * We know the {@link #getSequenceElement() sequence element} in which the OCL expression was used that contains the * <code>#context</code> sub-expression. We also know the <code>element</code> and from it can determine the * {@link TextBlock} that documents the creation of the element. From {@link #contextTag} we know if/which context * tag was used. We need to find a path of text blocks to a text block documenting the execution of the template * containing the {@link #getSequenceElement() sequence element} containing the OCL expression. * <p> * * A special case is the use of <code>foreach</code> predicates. In this case, multiple * {@link DocumentNode#getCorrespondingModelElements() object creations} are attached to the text block for the * template in which the <codo>foreach</code> predicate occurs. No text blocks are then created for the templates * whose execution is triggered by the <code>foreach</code> predicate. Instead, all elements created transitively * through the <code>foreach</code> are accumulated in the {@link DocumentNode#getCorrespondingModelElements() * corresponding model elements} for the text block documenting the execution of the template containing the * <code>foreach</code>. In this case we have to check if the TODO ... * * @param context * pass <code>true</code> if <code>element</code> is not the element produced by the template in which * the property is to update but if <code>#context</code> was used in the OCL expression, thus referring * to the element produced by the next-outer {@link ContextTemplate}. */ protected Set<TextBlock> getTextBlocksInWhichSequenceElementWasExecuted(EObject element, boolean context) { Set<TextBlock> textBlocks = new HashSet<TextBlock>(); Collection<EObject> textBlockDocumentingCreationOfContextElement = getOppositeEndFinder() .navigateOppositePropertyWithBackwardScope( TextblocksPackage.eINSTANCE.getTextBlock_CorrespondingModelElements(), element); if (textBlockDocumentingCreationOfContextElement != null) { for (EObject eo : textBlockDocumentingCreationOfContextElement) { if (eo instanceof TextBlock) { TextBlock textBlock = (TextBlock) eo; Template template = textBlock.getType(); if (!context || (template instanceof ContextTemplate && ((ContextTemplate) template).getContextTags() != null && ((ContextTemplate) template) .getContextTags().getTags().contains(contextTag))) { // either no #context was used, or the contextTemplate has the expected tag (e.g., "context(X)" // if the usage was "#context(X)") Set<TextBlock> textBlocksForSubordinateExecutionsOfSequenceElementHoldingTheOCLExpression = getSubordinateTextBlocksLeadingTo( textBlock, getSequenceElement().getParentTemplate()); for (TextBlock tb : textBlocksForSubordinateExecutionsOfSequenceElementHoldingTheOCLExpression) { // add the first element from correspondingModelElements because that's the one // actually immediately created by the template holding the sequence element with // the OCL expression if (!tb.getCorrespondingModelElements().isEmpty() && TcsUtil.wasExecuted((ContextTemplate) tb.getType(), tb.getParentAltChoices(), getSequenceElement())) { textBlocks.add(tb); } } } } } } return textBlocks; } protected Set<TextBlock> getSubordinateTextBlocksLeadingTo(TextBlock textBlock, Template templateHoldingSequenceElement) { if (textBlock.getType() == templateHoldingSequenceElement) { return Collections.singleton(textBlock); } else { Set<TextBlock> result = new HashSet<TextBlock>(); for (DocumentNode subBlock : textBlock.getSubNodes()) { if (subBlock instanceof TextBlock) { result.addAll(getSubordinateTextBlocksLeadingTo((TextBlock) subBlock, templateHoldingSequenceElement)); } } return result; } } /** * Tells the {@link SequenceElement} (e.g., an {@link InjectorActionsBlock} or a {@link Property}) * from which this updater was created. */ protected abstract SequenceElement getSequenceElement(); /** * Determines which object will be used as <tt>self</tt> in evaluating the * OCL expression. Can either be a {@link ModelElementProxy proxy} or a * {@link RefObject}. If the OCL expression uses <tt>#context</tt>, the * {@link #getContextElement() context element} is used; otherwise the * {@link #getModelElement()} call is used. */ protected static SelfKind determineSelfKind(String oclExpression) { if (ContextAndForeachHelper.usesContext(oclExpression)) { return SelfKind.CONTEXT; } else if (ContextAndForeachHelper.usesForeach(oclExpression)) { return SelfKind.FOREACH; } else { return SelfKind.SELF; } } }