package com.sap.mi.textual.parsing.textblocks.reference;
import java.io.InvalidObjectException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Vector;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.emf.common.command.CommandStack;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.resource.ResourceSet;
import com.sap.furcas.metamodel.FURCAS.TCS.ConcreteSyntax;
import com.sap.furcas.metamodel.FURCAS.textblocks.DocumentNode;
import com.sap.furcas.metamodel.FURCAS.textblocks.LexedToken;
import com.sap.furcas.metamodel.FURCAS.textblocks.TextBlock;
import com.sap.furcas.runtime.common.exceptions.ModelAdapterException;
import com.sap.furcas.runtime.parser.ModelElementCreationException;
import com.sap.furcas.runtime.parser.impl.DelayedReference;
import com.sap.furcas.runtime.parser.impl.ModelInjector;
import com.sap.furcas.runtime.parser.impl.ObservableInjectingParser;
import com.sap.furcas.runtime.parser.textblocks.LexedTokenWrapper;
import com.sap.furcas.runtime.parser.textblocks.LocalContextBuilder;
import com.sap.furcas.runtime.parser.textblocks.TextBlocksAwareModelAdapter;
import com.sap.furcas.runtime.parser.textblocks.observer.ParserTextBlocksHandler;
import com.sap.furcas.runtime.textblocks.TbUtil;
/**
* This class is responsible to resolve {@link DelayedReference}s. This includes references
* that could not instantly be resolved during parsing and references that need to be re-evaluated
* because they might potentially change due to recent model changes.
* <p>
* The queue of the resolver is filled by the various {@link IAExpressionInvalidationChangeListener}s.
* </p>
*/
public class ReferenceResolver {
private final class ReferenceReevaluationCommand extends Command {
private final DelayedReference ref;
private ReferenceReevaluationCommand(DelayedReference ref) {
super(ref.getConnection(), "Re-evaluate unresolved Reference: " + ref);
this.ref = ref;
}
@Override
public boolean canExecute() {
return true;
}
@Override
public void doExecute() {
EPackage outermostPackage = AdapterEcoreHelper.getOutermostPackageThroughClusteredImports(getConnection(),
(EObject) ref.getModelElement());
reEvaluateUnresolvedRef(getConnection(), outermostPackage, ref, (TextBlock) ref.getTextBlock());
}
@Override
public Collection<PartitionOperation> getAffectedPartitions() {
PRI pri = ((EObject) ref.getModelElement()).get___Partition().getPri();
PartitionOperation editOperation = new PartitionOperation(PartitionOperation.Operation.EDIT, pri);
return Collections.singleton(editOperation);
}
}
/**
* Synchronized access to this collection on this object (the
* {@link ReferenceResolver} instance) because the repetition of reference
* resolution is affected by it.
*/
private final Collection<DelayedReference> unresolvedReferences = new Vector<DelayedReference>();
private final IncrementalReferenceEvaluationRegistry registry;
private final GlobalDelayedReferenceResolver notifier;
public ReferenceResolver(GlobalDelayedReferenceResolver globalDelayedReferenceResolver,
IncrementalReferenceEvaluationRegistry registry) {
this.notifier = globalDelayedReferenceResolver;
this.registry = registry;
}
/**
* Attempt to resolve all queued unresolved references. References that
* could not be resolved remain queued.
* <p>
* This method is thread safe: One thread at a time is responsible to resolve
* references. Multiple threads however are allowed to queue new references,
* while a resolve run is in progress. Those references will be picked up
* automatically.
* </p>
*/
public synchronized void resolveReferences(IProgressMonitor monitor) {
synchronized (unresolvedReferences) {
if (unresolvedReferences.isEmpty()) {
return; // nothing to do, exit "early"
}
}
boolean referencesAddedFromOutsideOrOneResolved;
do {
referencesAddedFromOutsideOrOneResolved = false;
Collection<DelayedReference> failedReferences = new ArrayList<DelayedReference>();
Collection<DelayedReference> workingCopy = new ArrayList<DelayedReference>();
synchronized (unresolvedReferences) {
workingCopy.addAll(unresolvedReferences);
unresolvedReferences.clear();
}
monitor.beginTask("Reevaluating OCL References...", workingCopy.size());
for (Entry<ResourceSet, Collection<DelayedReference>> referencesPerConnection : splitPerConnection(workingCopy).entrySet()) {
CommandStack cmdStack = referencesPerConnection.getKey().getCommandStack();
if (cmdStack.canRedo()) {
// the current connection is in the process of undoing commands
// we may not issue reference resolving commands at this point, because
// this would clear the redo stack and make it impossible to ever
// redo the commands.
failedReferences.addAll(referencesPerConnection.getValue());
Activator.logInfo("Deferring Reevaluation due to ongoing undo action");
continue;
}
cmdStack.openGroup("Re-evaluate unresolved References");
try {
for (DelayedReference ref : referencesPerConnection.getValue()) {
try {
Command cmd = new ReferenceReevaluationCommand(ref);
cmd.execute();
referencesAddedFromOutsideOrOneResolved = true;
// no exception means successfully resolved
// FIXME the comment above is high likely wrong!
} catch (InvalidConnectionException ex) {
Activator.logWarning("Could not re-resolve reference: " + ref + ". Connection: "
+ ref.getConnection() + " is not alive anymore! Reference is ignored and removed.");
} catch (InvalidObjectException ex) {
Activator.logWarning("Could not re-resolve reference: " + ref + ". Element: " + ref.getModelElement()
+ " is not alive anymore! Reference is ignored and removed.");
} catch (PartitionEditingNotPossibleException ex) {
Activator.logWarning("Could not re-resolve reference: " + ref + ". Partition: " + ex.getPri()
+ " is locked by connection " + ref.getConnection() + "! Will try again later");
failedReferences.add(ref);
} catch (Exception ex) {
Activator.logError(ex, " Could not re-resolve reference: " + ref
+ ". Reference is ignored and removed.");
}
monitor.worked(1);
}
} finally {
cmdStack.closeGroup();
}
}
monitor.done();
synchronized (unresolvedReferences) {
referencesAddedFromOutsideOrOneResolved = referencesAddedFromOutsideOrOneResolved || !hasEmptyQueue();
// will try to resolve deferred references again later;
// re-queue. Note that this
// doesn't force another run; if nothing changed, we still
// wouldn't be able to resolve
queueNewUnresolvedReferences(failedReferences);
}
} while (referencesAddedFromOutsideOrOneResolved);
}
private Map<ResourceSet, Collection<DelayedReference>> splitPerConnection(Collection<DelayedReference> workingCopy) {
Map<ResourceSet, Collection<DelayedReference>> referencesPerConnection = new HashMap<ResourceSet, Collection<DelayedReference>>();
for (DelayedReference ref : workingCopy) {
if (!referencesPerConnection.containsKey(ref.getConnection())) {
referencesPerConnection.put(ref.getConnection(), new ArrayList<DelayedReference>());
}
referencesPerConnection.get(ref.getConnection()).add(ref);
}
return referencesPerConnection;
}
private void reEvaluateUnresolvedRef(Connection conn, RefPackage outermostPackage, DelayedReference unresolvedRef,
TextBlock contextTextBlock) {
LocalContextBuilder localContextBuilder = new LocalContextBuilder();
try {
// ensure that the model element uses the current connection
ensureUsageOfConnection(unresolvedRef, conn);
LexedTokenWrapper token = (LexedTokenWrapper) unresolvedRef.getToken();
DocumentNode referringDocumentNode = null;
ModelInjector modelInjector = constructModelInjector(conn, outermostPackage);
ObservableInjectingParser parser = null;
LexedToken tokenInCurrentConnection = null;
if (token != null) {
AbstractToken modelElementToken = token.getWrappedToken();
tokenInCurrentConnection = (LexedToken) conn.getElement(modelElementToken.get___Mri());
if (tokenInCurrentConnection == null || unresolvedRef.getModelElement() == null) {
registry.removeRegistration(unresolvedRef);
return;
}
if (tokenInCurrentConnection.getParentBlock().getType() == null) {
Activator.logWarning("Ignoring unresolved reference due to a broken mapping: " + unresolvedRef);
return;
}
ConcreteSyntax cs = tokenInCurrentConnection.getParentBlock().getType().getParseRule().getConcretesyntax();
parser = registry.getParser(cs);
((ParserTextBlocksHandler) parser.getObserver()).setConnection(conn);
TbUtil.constructContext(tokenInCurrentConnection, localContextBuilder);
referringDocumentNode = tokenInCurrentConnection;
// also rebuild the context for the parser,
// as it may be used e.g. in foreach predicate references
if (unresolvedRef.getType() == DelayedReference.TYPE_SEMANTIC_PREDICATE) {
parser.reset();
TbUtil.constructContext(tokenInCurrentConnection, parser);
}
if (!localContextBuilder.getContextStack().isEmpty()) {
unresolvedRef.setContextElement(localContextBuilder.getContextStack().peek());
}
} else if (contextTextBlock != null) {
TbUtil.constructContext(contextTextBlock, localContextBuilder);
referringDocumentNode = contextTextBlock;
if (!localContextBuilder.getContextStack().isEmpty()) {
unresolvedRef.setContextElement(localContextBuilder.getContextStack().peek());
}
ConcreteSyntax cs = contextTextBlock.getType().getParseRule().getConcretesyntax();
parser = registry.getParser(cs);
// also rebuild the context for the parser,
// as it may be used e.g. in foreach predicate references
if (unresolvedRef.getType() == DelayedReference.TYPE_SEMANTIC_PREDICATE) {
parser.reset();
TbUtil.constructContext(contextTextBlock, parser);
((ParserTextBlocksHandler) parser.getObserver()).setConnection(conn);
parser.setInjector(modelInjector);
}
// TODO Reconstruct textual representation from TextBlock and create TokenStream from it.#
// This TokenStream can then be used upon reevaluation started from the IA to
// determine the values used in the disambiguation queries.
} else {
registry.removeRegistration(unresolvedRef);
return;
}
boolean resolved = false;
try {
if (unresolvedRef.getType() == DelayedReference.TYPE_SEMANTIC_PREDICATE || !(unresolvedRef.getOclQuery() != null && unresolvedRef.getType() != DelayedReference.CONTEXT_LOOKUP)) {
resolved = modelInjector.resolveReference(unresolvedRef, localContextBuilder.getContextManager(), parser);
} else {
Collection<?> existingValueCollection = findCurrentlySetElements(unresolvedRef, modelInjector, tokenInCurrentConnection);
Collection<?> resultCollection = findNewElementsToSet(unresolvedRef);
for (Object valueElement : existingValueCollection) {
if (!resultCollection.contains(valueElement)) {
modelInjector.unset(unresolvedRef.getModelElement(), unresolvedRef.getPropertyName(), valueElement);
if (referringDocumentNode.getReferencedElements().contains(valueElement)) {
referringDocumentNode.getReferencedElements().remove(valueElement);
}
notifier.notifyReferenceUnset(unresolvedRef, valueElement);
}
}
for (Object result : resultCollection) {
if (!existingValueCollection.contains(result) && result != null) {
modelInjector.set(unresolvedRef.getModelElement(), unresolvedRef.getPropertyName(), result);
resolved = true;
unresolvedRef.setRealValue(result);
notifier.notifyReferenceSet(unresolvedRef, result);
}
}
}
} catch (ModelAdapterException e) {
Activator.logWarning(e);
// TODO check if we can ignore this?
} catch (ModelElementCreationException e) {
throw new RuntimeException(e);
}
if (resolved) {
if (unresolvedRef.getType() == DelayedReference.TYPE_SEMANTIC_PREDICATE) {
// to be able to incrementally re evaluate the reference
// later
// we need to setup a link between the textblock and the
// template used in the ref
Collection<Template> templates = ((ParserTextBlocksHandler) parser.getObserver()).getCurrentTbProxy().getAdditionalTemplates();
for (Template template : templates) {
if (!((TextBlock) unresolvedRef.getTextBlock()).getAdditionalTemplates().contains(template)) {
((TextBlock) unresolvedRef.getTextBlock()).getAdditionalTemplates().add(template);
}
}
RefObject value = (RefObject) unresolvedRef.getRealValue();
if (!referringDocumentNode.getCorrespondingModelElements().contains(value)) {
referringDocumentNode.getCorrespondingModelElements().add(value);
}
// TbUtil.addForEachContext(unresolvedRef.getTextBlock(),
// (RefObject) unresolvedRef.getModelElement(),
// (RefObject) unresolvedRef.getCurrentForeachElement(),
// (ForeachPredicatePropertyInit)
// unresolvedRef.getQueryElement(),
// (RefObject) unresolvedRef.getRealValue(), conn);
parser.setDelayedReferencesAfterParsing();
} else {
if (unresolvedRef.getRealValue() instanceof RefObject) {
if (!referringDocumentNode.getReferencedElements().contains(unresolvedRef.getRealValue())) {
referringDocumentNode.getReferencedElements().add((RefObject) unresolvedRef.getRealValue());
}
}
}
if (!unresolvedRef.isGenericReference()) {
registry.removeRegistration(unresolvedRef);
}
}
} catch (InvalidConnectionException ice) {
ice.printStackTrace();
}
}
/**
* Ensures that the {@link DelayedReference unresolvedRef} carries the
* element using the correct connection given by <code>conn</code>.
*
* @param unresolvedRef
* the {@link DelayedReference} for which the elements should be
* checked.
* @param conn
* the {@link Connection} where the elements should be used from.
*/
private void ensureUsageOfConnection(DelayedReference unresolvedRef, ResourceSet conn) {
EObject elementInCurrentConnection = (EObject) conn.getElement(((EObject) unresolvedRef.getModelElement()).get___Mri());
if (elementInCurrentConnection == null) {
throw new RuntimeException("Element: " + unresolvedRef.getModelElement() + " is not available in connection: " + conn);
} else {
unresolvedRef.setModelElement(elementInCurrentConnection);
}
Object elementInOldConnection = unresolvedRef.getContextElement();
if (elementInOldConnection != null) {
elementInCurrentConnection = (EObject) conn.getElement(((EObject) elementInOldConnection).get___Mri());
if (elementInCurrentConnection == null) {
throw new RuntimeException("Element: " + elementInOldConnection + " is not available in connection: " + conn);
} else {
unresolvedRef.setContextElement(elementInCurrentConnection);
}
}
elementInOldConnection = unresolvedRef.getCurrentForeachElement();
if (elementInOldConnection != null) {
elementInCurrentConnection = (EObject) conn.getElement(((EObject) elementInOldConnection).get___Mri());
if (elementInCurrentConnection == null) {
throw new RuntimeException("Element: " + elementInOldConnection + " is not available in connection: " + conn);
} else {
unresolvedRef.setCurrentForeachElement(elementInCurrentConnection);
}
}
elementInOldConnection = unresolvedRef.getQueryElement();
if (elementInOldConnection != null) {
elementInCurrentConnection = (EObject) conn.getElement(((EObject) elementInOldConnection).get___Mri());
if (elementInCurrentConnection == null) {
throw new RuntimeException("Element: " + elementInOldConnection + " is not available in connection: " + conn);
} else {
unresolvedRef.setQueryElement(elementInCurrentConnection);
}
}
elementInOldConnection = unresolvedRef.getTextBlock();
if (elementInOldConnection != null) {
elementInCurrentConnection = (EObject) conn.getElement(((EObject) elementInOldConnection).get___Mri());
if (elementInCurrentConnection == null) {
throw new RuntimeException("Element: " + elementInOldConnection + " is not available in connection: " + conn);
} else {
unresolvedRef.setTextBlock(elementInCurrentConnection);
}
}
}
/*package*/ ModelInjector constructModelInjector(ResourceSet connection, RefPackage outermostPackage) {
// tokenNames only needed for parse error reporting regarding keyword
// issues; not needed here
ModelInjector mi = new ModelInjector(/* tonekNames */null);
// Use global scope (null) for now
TextBlocksAwareModelAdapter ma = new TextBlocksAwareModelAdapter(new MOINModelAdapter(outermostPackage, connection, /* partitionScope */
null, /* criScope */null));
mi.setModelAdapter(ma);
return mi;
}
void queueNewUnresolvedReferences(Collection<DelayedReference> unresolvedIAReferences) {
synchronized (unresolvedReferences) {
unresolvedReferences.addAll(unresolvedIAReferences);
}
}
public boolean hasEmptyQueue() {
synchronized (unresolvedReferences) {
return unresolvedReferences.isEmpty();
}
}
/**
* Clears all currently deferred references.
*
* This should <b>ONLY</b> be used in <b>tests</b>.
*/
public void clearUnresolvedReferences() {
synchronized (this) {
synchronized (unresolvedReferences) {
unresolvedReferences.clear();
}
}
}
}