/** * */ package com.sap.mi.textual.parsing.textblocks.reference; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.WeakHashMap; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import com.sap.furcas.metamodel.FURCAS.TCS.ContextTemplate; import com.sap.furcas.metamodel.FURCAS.TCS.InjectorAction; import com.sap.furcas.metamodel.FURCAS.TCS.InjectorActionsBlock; import com.sap.furcas.metamodel.FURCAS.TCS.Property; import com.sap.furcas.metamodel.FURCAS.TCS.Template; import com.sap.furcas.metamodel.FURCAS.textblockdefinition.TextBlockDefinition; import com.sap.furcas.metamodel.FURCAS.textblocks.AbstractToken; import com.sap.furcas.metamodel.FURCAS.textblocks.DocumentNode; import com.sap.furcas.metamodel.FURCAS.textblocks.ForEachContext; import com.sap.furcas.metamodel.FURCAS.textblocks.LexedToken; import com.sap.furcas.metamodel.FURCAS.textblocks.TextBlock; import com.sap.furcas.runtime.common.interfaces.IModelElementProxy; import com.sap.furcas.runtime.common.util.ContextAndForeachHelper; import com.sap.furcas.runtime.parser.impl.DelayedReference; import com.sap.furcas.runtime.parser.textblocks.LexedTokenWrapper; import com.sap.furcas.runtime.parser.textblocks.LocalContextBuilder; import com.sap.furcas.runtime.tcs.TcsUtil; import com.sap.furcas.runtime.textblocks.TbUtil; import com.sap.furcas.runtime.textblocks.shortprettyprint.ShortPrettyPrinter; /** * An event listener responsible to queue a {@link DelayedReference} for re-evaluation * in the {@link ReferenceResolver}. * */ public class IAExpressionInvalidationChangeListener implements UpdateListener, ChangeListener { /** * The OCL expressions used by the {@link #reference} for (re-)evaluation. * This listener will be notified about changes that may affect any of those * OCL expressions in order for the reference to be re-evaluated. */ private final OclExpressionRegistration[] registrations; private final DelayedReference reference; private final WeakHashMap<ChangeEvent, Set<MRI>> elementsImpactedByEvent = new WeakHashMap<ChangeEvent, Set<MRI>>(); private final ReferenceResolver resolver; public IAExpressionInvalidationChangeListener(ReferenceResolver resolver, DelayedReference ref, OclExpressionRegistration... registration) { this.resolver = resolver; this.registrations = registration; this.reference = ref; } @Override public void notifyUpdate(EventChain events) { try { if (!events.getEvents().isEmpty()) { ResourceSet conn = events.getEvents().iterator().next().getEventTriggerConnection(); if (reference.isGenericReference()) { // Its a generic reference not an unresolved one if (reference.getQueryElement() != null) { List<DelayedReference> newRefs = null; if (reference.getQueryElement() instanceof InjectorAction) { newRefs = filterEventsAndRegisterDelayedReferencesForInjectorAction(events.getEvents(), conn); } else if (reference.getQueryElement() instanceof Property) { if (reference.getType() == DelayedReference.CONTEXT_LOOKUP) { newRefs = filterEventsAndQueueDelayedReferencesForContextLookup(events.getEvents(), conn); } else { newRefs = filterEventsAndQueueDelayedReferencesForPropertyQuery(events.getEvents(), conn); } } if (newRefs != null && newRefs.size() > 0) { resolve(newRefs); } } } } } catch (Exception e) { Activator.logError(e, "Preparing Delayed Reference Re-Evaluation"); } } private void resolve(Collection<DelayedReference> unresolvedReferences) { if (unresolvedReferences != null && !unresolvedReferences.isEmpty()) { resolver.queueNewUnresolvedReferences(unresolvedReferences); resolver.resolveReferences(new NullProgressMonitor()); } } private List<DelayedReference> filterEventsAndRegisterDelayedReferencesForInjectorAction(List<ChangeEvent> events, Resource conn) { List<DelayedReference> newReferences = new ArrayList<DelayedReference>(); Collection<TextBlock> textBlocksInChosenAlternativeForInjectorAction = getTextBlocksInChosenAlternativeForInjectorAction(conn); if (textBlocksInChosenAlternativeForInjectorAction.size() > 0) { Set<MRI> allAffectedElements = new HashSet<MRI>(); for (ChangeEvent event : events) { // checking for // !areAllRegistrationsUnaffectedDueToPrimitiveAttributeValueComparisonWithLiteralOnly // not necessary because an entry in elementsImpactedByEvent // only appears after this check was performed // TODO this is a workaround. to properly decide this we need to // keep track of the alternative chosen at runtime of the parser Set<MRI> affectedElementsForEvent = elementsImpactedByEvent.remove(event); if (affectedElementsForEvent != null) { allAffectedElements.addAll(affectedElementsForEvent); } } if (allAffectedElements.size() > 0) { for (TextBlock textBlock : textBlocksInChosenAlternativeForInjectorAction) { Set<EObject> intersectionOfCorrespondingAndAffectedElements = filterCorrespondingOrContextElementWithAffectedElements( conn, allAffectedElements, textBlock); for (EObject ro : intersectionOfCorrespondingAndAffectedElements) { DelayedReference clonedRef = reference.clone(); clonedRef.setModelElement(ro); clonedRef.setRealValue(null); clonedRef.setTextBlock(textBlock); clonedRef.setConnection(conn); newReferences.add(clonedRef); // reference.setModelElement(null); } } } } return newReferences; } /** * We catch both, the immediate change event for each single event at the * time the change happens, and later the update event when all changes of a * command or command group are done (see {@link #notifyUpdate(EventChain)} * ). When the change occurs, the model is still in the state immediately * after that particular change. Therefore, other than in the update event, * the impact analysis can be performed truthfully and better than in the * update listener where other model changes may incorrectly influence the * results of the impact analysis. * <p> * * We record the results of the impact analysis, keyed by the model change * event and store it in a weak hash map. If the update handler is executed, * all events will be handled and removed from the map. If not, the events * will eventually disappear from the weak map due to garbage collection. */ @Override public void notify(ChangeEvent event) { try { ResourceSet conn = event.getEventTriggerConnection(); if (reference.isGenericReference()) { // Its a generic reference not an unresolved one if (reference.getQueryElement() != null) { if (reference.getQueryElement() instanceof InjectorAction) { if (!areAllRegistrationsUnaffectedDueToPrimitiveAttributeValueComparisonWithLiteralOnly(event, /* replacement for __TEMP__*/ null)) { if (getTextBlocksInChosenAlternativeForInjectorAction(conn).size() > 0) { elementsImpactedByEvent.put(event, getAffectedElements(event, conn)); } } } else { // compute affected elements because // filterEventsAndQueueDelayedReferencesForContextLookup // and filterEventsAndQueueDelayedReferencesForPropertyQuery // always need them elementsImpactedByEvent.put(event, getAffectedElements(event, conn)); } } } } catch (Exception e) { Activator.logError(e, "Calculating effected elements for change event: " + event); } } private Collection<TextBlock> getTextBlocksInChosenAlternativeForInjectorAction(ResourceSet conn) { Collection<TextBlock> result = new ArrayList<TextBlock>(); InjectorAction injectorAction = (InjectorAction) conn.getElement(((EObject) reference.getQueryElement()) .get___Mri()); if(injectorAction != null) { InjectorActionsBlock injectorActionsBlock = (InjectorActionsBlock) injectorAction.refImmediateComposite(); Template template = injectorActionsBlock.getParentTemplate(); // now find all TextBlocks referencing this template; Collection<TextBlock> tbs = getTextBlocksUsingQueryElement(conn, template); for (TextBlock textBlock : tbs) { // first check if the alternative in which the injector action // resides was // chosen during the parsing process // TODO this is a workaround. to properly decide this we need to // keep track // of the alternative chosen at runtime of the parser boolean wasInChosenAlternative = isInjectorActionInChosenAlternative(injectorActionsBlock, textBlock); if (wasInChosenAlternative) { result.add(textBlock); } } } return result; } private boolean areAllRegistrationsUnaffectedDueToPrimitiveAttributeValueComparisonWithLiteralOnly(ChangeEvent event, String replacementFor__TEMP__) { boolean allRegistrationsUnaffectedDueToPrimitiveAttributeValueComparisonWithLiteralOnly = true; for (OclExpressionRegistration registration : registrations) { if (!registration.isUnaffectedDueToPrimitiveAttributeValueComparisonWithLiteralOnly(event, replacementFor__TEMP__)) { allRegistrationsUnaffectedDueToPrimitiveAttributeValueComparisonWithLiteralOnly = false; break; } } return allRegistrationsUnaffectedDueToPrimitiveAttributeValueComparisonWithLiteralOnly; } private boolean isInjectorActionInChosenAlternative(InjectorActionsBlock injectorActionsBlock, TextBlock textBlock) { boolean wasInChosenAlternative = false; if (textBlock.getTokens().isEmpty()) { for (TextBlock tb : textBlock.getSubBlocks()) { if (tb.getSequenceElement() != null && (tb.getSequenceElement().getElementSequence().equals(injectorActionsBlock.getElementSequence()) || injectorActionsBlock .getElementSequence().refImmediateComposite() instanceof ContextTemplate)) { wasInChosenAlternative = true; break; } } } else { for (AbstractToken tok : textBlock.getTokens()) { if (tok instanceof LexedToken) { LexedToken lt = (LexedToken) tok; // check if injectoraction was in chosen alternative or // directly in in the template if (lt.getSequenceElement() != null && (lt.getSequenceElement().getElementSequence().equals(injectorActionsBlock.getElementSequence()) || injectorActionsBlock .getElementSequence().refImmediateComposite() instanceof ContextTemplate)) { wasInChosenAlternative = true; break; } } } } return wasInChosenAlternative; } private Collection<TextBlock> getTextBlocksUsingQueryElement(ResourceSet conn, Template template) { TextblockDefinitionReferencesProduction tbDefAssoc = conn .getAssociation(TextblockDefinitionReferencesProduction.ASSOCIATION_DESCRIPTOR); TextBlockDefinition def = tbDefAssoc.getTextBlockDefinition(template).iterator().next(); TextBlockType tbTypeAssoc = conn.getAssociation(TextBlockType.ASSOCIATION_DESCRIPTOR); Collection<TextBlock> tbs = new ArrayList<TextBlock>(tbTypeAssoc.getTextBlock(def)); // now find all TextBlocks that reference the template in their // "additionalTemplate" TextBlockAdditionalTemplates tbAdditionalAssoc = conn.getAssociation(TextBlockAdditionalTemplates.ASSOCIATION_DESCRIPTOR); Collection<TextBlock> additionalTbs = tbAdditionalAssoc.getTextblock(template); tbs.addAll(additionalTbs); tbs = TbUtil.filterVersionedTextBlockForNewest(tbs); return tbs; } private List<DelayedReference> filterEventsAndQueueDelayedReferencesForPropertyQuery(List<ChangeEvent> events, ResourceSet conn) { List<DelayedReference> newReferences = new ArrayList<DelayedReference>(); Collection<LexedToken> toks = getTokensUsingQueryElement(conn); Set<LexedToken> tokensForWhichToAddNewReference = new HashSet<LexedToken>(); for (ChangeEvent event : events) { Set<MRI> affectedElements = elementsImpactedByEvent.remove(event); // check for // !allRegistrationsUnaffectedDueToPrimitiveAttributeValueComparisonWithLiteralOnly // not necessary because this check is performed before affected // elements are cached in elementsImpactedByEvent if (affectedElements != null && affectedElements.size() > 0) { for (LexedToken lt : toks) { if (lt == null || lt.getParent() == null) { // dangling token. Ignoring continue; } Set<EObject> intersectionOfCorrespondingAndAffectedElements = filterCorrespondingOrContextElementWithAffectedElements( conn, affectedElements, lt.getParent()); if (intersectionOfCorrespondingAndAffectedElements.size() > 0) { tokensForWhichToAddNewReference.add(lt); } } } } for (LexedToken lt : tokensForWhichToAddNewReference) { newReferences.addAll(getReferencesToReEvaluate(conn, lt)); } return newReferences; } private List<DelayedReference> filterEventsAndQueueDelayedReferencesForContextLookup(List<ChangeEvent> events, ResourceSet conn) { Collection<LexedToken> toks = getTokensUsingQueryElement(conn); Set<LexedToken> tokensForWhichToAddNewReference = new HashSet<LexedToken>(); List<DelayedReference> newReferences = new ArrayList<DelayedReference>(); for (ChangeEvent event : events) { Set<MRI> affectedElements = elementsImpactedByEvent.remove(event); // check // for!allRegistrationsUnaffectedDueToPrimitiveAttributeValueComparisonWithLiteralOnly // not necessary because this check is performed before affected // elements are cached in elementsImpactedByEvent if (affectedElements != null && affectedElements.size() > 0) { for (LexedToken lt : toks) { Set<EObject> intersectionOfCorrespondingAndAffectedElements = filterAffectedElements(conn, affectedElements, Collections.singletonList(lt.getParentBlock())); if (intersectionOfCorrespondingAndAffectedElements.size() > 0) { tokensForWhichToAddNewReference.add(lt); } } } } for (LexedToken lt : tokensForWhichToAddNewReference) { newReferences.addAll(getReferencesToReEvaluate(conn, lt)); } return newReferences; } private Collection<? extends DelayedReference> getReferencesToReEvaluate(ResourceSet conn, LexedToken lt) { Set<EObject> result = filterCorrespondingElementsByDelayedReferenceSourceType(conn, lt.getParentBlock(), reference); List<DelayedReference> newReferences = new ArrayList<DelayedReference>(); for (EObject ro : result) { DelayedReference clonedRef = reference.clone(); clonedRef.setModelElement(ro); clonedRef.setRealValue(null); clonedRef.setResourceSet(conn); clonedRef.setToken(new LexedTokenWrapper(lt)); RefPackage outermostPackage = MoinHelper.getOutermostPackageThroughClusteredImports(conn, (RefBaseObject) clonedRef.getModelElement()); ShortPrettyPrinter shortPrettyPrinter = new ShortPrettyPrinter(resolver.constructModelInjector(conn, outermostPackage).getModelAdapter()); clonedRef.setKeyValue(shortPrettyPrinter.resynchronizeToEditableState(lt)); clonedRef.setTextBlock(lt.getParent()); String oclQuery = clonedRef.getOclQuery(); clonedRef.setOclQuery(oclQuery.replaceAll(GlobalDelayedReferenceResolver.TEMPORARY_QUERY_PARAM_REPLACEMENT, lt .getValue())); newReferences.add(clonedRef); } return newReferences; } /** * Filtes the elements in * {@link DocumentNode#getCorrespondingModelElements()} by comparing them to * the type given by the parent template of the query element of the * {@link #reference}. * * @param conn * The connection to use to resolve the template * @param node * To node from which the corresponding elements should be * filtered. * @param delayedReference * TODO * @return A type filtered set of elements by the {@link #reference}'s query * elements template. */ private Set<EObject> filterCorrespondingElementsByDelayedReferenceSourceType(ResourceSet conn, DocumentNode node, DelayedReference delayedReference) { Set<EObject> result = new HashSet<EObject>(); EObject queryElement = (EObject) conn.getElement(((EObject) delayedReference.getQueryElement()).get___Mri()); for (EObject refObject : node.getCorrespondingModelElements()) { if (refObject.refIsInstanceOf(TcsUtil.getParentTemplate(queryElement).getMetaReference(), false)) { result.add(refObject); } } return result; } private Collection<LexedToken> getTokensUsingQueryElement(ResourceSet conn) { Property property = (Property) conn.getElement(((EObject) reference.getQueryElement()).get___Mri()); // now find all TextBlocks referencing this property; LexedTokenReferenesSequenceElement lexedTokenSeqElAssoc = conn .getAssociation(LexedTokenReferenesSequenceElement.ASSOCIATION_DESCRIPTOR); Collection<LexedToken> toks = lexedTokenSeqElAssoc.getLexedtoken(property); return toks; } /** * Filters the given elements with the corresponding elements or, if the * query uses a #context reference with the context element of the given * {@link TextBlock}. * <p> * * TODO handle #foreach here too * * @param conn * the connection used to resolve the MRIs within affecedElements * @param affectedElements * Elements that were affected by * @param node * The {@link TextBlock} which's corresponding elements should be * filtered. * @return the intersection between affected elements and the corresponding * elements of the given {@link TextBlock}. */ private Set<EObject> filterCorrespondingOrContextElementWithAffectedElements(ResourceSet conn, Set<MRI> affectedElements, DocumentNode node) { Collection<EObject> correspondingModelElements = null; boolean isContext = ContextAndForeachHelper.usesContext(reference.getOclQuery()); boolean usesForEach = ContextAndForeachHelper.usesForeach(reference.getOclQuery()); Set<EObject> sourceModelElements = null; if (isContext) { LocalContextBuilder localContextBuilder = new LocalContextBuilder(); TbUtil.constructContext(node, localContextBuilder); correspondingModelElements = new ArrayList<EObject>(); if (!localContextBuilder.getContextStack().isEmpty()) { IModelElementProxy innermostContext = localContextBuilder.getContextStack().peek(); Object taggedContext = localContextBuilder.getContextManager().getTaggedContext(innermostContext, ContextAndForeachHelper.getContextTag(reference.getOclQuery())); if (taggedContext != null) { // null may occur in case the node leads to a context stack // that doesn't fit the // context stack expected by the OCL expression; // in this case, e.g., if a tag exists on the OCL // expression's #context, no context object will // be found. In this case, don't add anything to the // correspondingModelElements. EObject unwrappedContext = null; if (taggedContext instanceof IModelElementProxy) { unwrappedContext = (EObject) ((IModelElementProxy) taggedContext).getRealObject(); } else { unwrappedContext = (EObject) taggedContext; } correspondingModelElements.add(unwrappedContext); } } } else if (usesForEach) { correspondingModelElements = new ArrayList<EObject>(); sourceModelElements = new HashSet<EObject>(); Collection<ForEachContext> fec = null; if (node instanceof TextBlock) { fec = ((TextBlock) node).getForEachContext(); } else { fec = ((TextBlock) node.refImmediateComposite()).getForEachContext(); } for (ForEachContext foreachContext : fec) { if (foreachContext.getForeachPedicatePropertyInit().equals(reference.getQueryElement())) { correspondingModelElements.addAll(foreachContext.getContextElement()); sourceModelElements.add(foreachContext.getSourceModelElement()); } } } else { correspondingModelElements = new ArrayList<EObject>(node.getCorrespondingModelElements()); correspondingModelElements.addAll(node.getReferencedElements()); } Set<EObject> intersectionOfCorrespondingAndAffectedElements = filterAffectedElements(conn, affectedElements, correspondingModelElements); if (isContext) { if (intersectionOfCorrespondingAndAffectedElements.size() > 0) { Set<EObject> result = filterCorrespondingElementsByDelayedReferenceSourceType(conn, node, reference); return result; } else { return new HashSet<EObject>(0); } } else if (usesForEach) { return sourceModelElements; } else { return intersectionOfCorrespondingAndAffectedElements; } } private Set<EObject> filterAffectedElements(ResourceSet conn, Set<MRI> affectedElements, Collection<? extends EObject> correspondingModelElements) { Set<EObject> intersectionOfCorrespondingAndAffectedElements = new HashSet<EObject>(correspondingModelElements); if (intersectionOfCorrespondingAndAffectedElements.size() > 0) { List<MRI> correspondingModelElementsMris = new ArrayList<MRI>(correspondingModelElements.size()); for (EObject cme : correspondingModelElements) { correspondingModelElementsMris.add(cme.get___Mri()); } Set<MRI> intersectionOfCorrespondingAndAffectedElementsMris = new HashSet<MRI>(affectedElements); intersectionOfCorrespondingAndAffectedElementsMris.retainAll(correspondingModelElementsMris); intersectionOfCorrespondingAndAffectedElements.clear(); for (MRI mri : intersectionOfCorrespondingAndAffectedElementsMris) { intersectionOfCorrespondingAndAffectedElements.add((EObject) conn.getElement(mri)); } } return intersectionOfCorrespondingAndAffectedElements; } private Set<MRI> getAffectedElements(ChangeEvent event, ResourceSet conn) { Statistics.getInstance().setCurrentObjectForSelf(reference.getElementForSelf()); Set<MRI> affectedElements = new HashSet<MRI>(); for (OclExpressionRegistration registration : registrations) { if (event instanceof ModelChangeEvent) { affectedElements.addAll(registration.getAffectedModelElements((ModelChangeEvent) event, conn)); } } return affectedElements; } public String getDebugInfo(ResourceSet conn) { Statistics oclIaStatistics = Statistics.getInstance(); StringBuilder result = new StringBuilder(); for (OclExpressionRegistration registration : registrations) { if (registration != null) { result.append(oclIaStatistics.toString(registration, conn)); result.append('\n'); } } return result.toString(); } public String getDebugInfoAsCsv(ResourceSet conn) { Statistics oclIaStatistics = Statistics.getInstance(); StringBuilder result = new StringBuilder(); for (OclExpressionRegistration registration : registrations) { if (registration != null) { result.append(oclIaStatistics.toCsv(registration, conn)); result.append('\n'); } } return result.toString(); } }