package com.sap.furcas.ide.editor.contentassist; import java.util.ArrayList; import java.util.EmptyStackException; import java.util.List; import java.util.Map; import java.util.SortedMap; import java.util.Stack; import java.util.TreeMap; import org.antlr.runtime.RecognitionException; import org.antlr.runtime.Token; import org.eclipse.core.runtime.Assert; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.ResourceSet; import com.sap.furcas.metamodel.FURCAS.TCS.Alternative; import com.sap.furcas.metamodel.FURCAS.TCS.Block; import com.sap.furcas.metamodel.FURCAS.TCS.ClassTemplate; import com.sap.furcas.metamodel.FURCAS.TCS.ConditionalElement; import com.sap.furcas.metamodel.FURCAS.TCS.ContextTemplate; import com.sap.furcas.metamodel.FURCAS.TCS.FunctionCall; import com.sap.furcas.metamodel.FURCAS.TCS.Keyword; import com.sap.furcas.metamodel.FURCAS.TCS.LiteralRef; import com.sap.furcas.metamodel.FURCAS.TCS.OperatorTemplate; import com.sap.furcas.metamodel.FURCAS.TCS.Property; import com.sap.furcas.metamodel.FURCAS.TCS.SeparatorPArg; import com.sap.furcas.metamodel.FURCAS.TCS.Sequence; import com.sap.furcas.metamodel.FURCAS.TCS.SequenceElement; import com.sap.furcas.metamodel.FURCAS.TCS.SequenceInAlternative; import com.sap.furcas.metamodel.FURCAS.TCS.TCSFactory; import com.sap.furcas.metamodel.FURCAS.TCS.Template; import com.sap.furcas.runtime.parser.IParsingObserver; import com.sap.furcas.runtime.parser.impl.DelayedReference; import com.sap.furcas.runtime.tcs.PropertyArgumentUtil; import com.sap.furcas.runtime.tcs.TcsDebugUtil; import com.sap.furcas.runtime.tcs.TcsUtil; /** * Parsing Observer, that gathers all information necessary for ContentAssist. * The result produced by this observier is a stack of {@link CtsContentAssistContext} * * @author Philipp Meier * */ public class CtsContentAssistParsingHandler implements IParsingObserver { private final ResourceSet resourceSet; /** * set to true after an error is found */ private Boolean foundError = false; /** * contains a mapping of qualifiedName + Mode to ClassTemplate of all * ClassTemplates contained in the ConcreteSyntax passed to the constructor */ private final Map<List<String>, Map<String, ClassTemplate>> classTemplateMap; /** * contains a mapping of qualifiedName to OperatorTemplate of all * OperatorTemplates contained in the ConcreteSyntax passed to the * constructor */ private final Map<List<String>, OperatorTemplate> operatorTemplateMap; private final SortedMap<TextPosition, CtsContentAssistContext> positionMap = new TreeMap<TextPosition, CtsContentAssistContext>( new TextPositionComparator()); /** * for unit test setup only! */ public SortedMap<TextPosition, CtsContentAssistContext> getPositionMap() { return positionMap; } private final Stack<Sequence> currentSequenceStack = new Stack<Sequence>(); private final Stack<SequenceElement> currentSequenceElementStack = new Stack<SequenceElement>(); private final Stack<FunctionCall> currentParentFunctionCallStack = new Stack<FunctionCall>(); private final Stack<Property> currentParentPropertyStack = new Stack<Property>(); private final Stack<Template> currentParentTemplateStack = new Stack<Template>(); private boolean currentIsOperator = false; private static final int loglevel = 0; // 0 = no log, 1 = errorsonly, 2 = all public CtsContentAssistParsingHandler(ResourceSet resourceSet, Map<List<String>, Map<String, ClassTemplate>> classTemplateMap, Map<List<String>, OperatorTemplate> operatorTemplateMap) { this.resourceSet = resourceSet; this.classTemplateMap = classTemplateMap; this.operatorTemplateMap = operatorTemplateMap; } @Override public void reset() { foundError = false; positionMap.clear(); currentSequenceStack.clear(); currentSequenceElementStack.clear(); currentParentFunctionCallStack.clear(); currentParentPropertyStack.clear(); currentParentTemplateStack.clear(); } public Map<List<String>, Map<String, ClassTemplate>> getClassTemplateMap() { return classTemplateMap; } /** * Can be called after parsing is complete to check for a valid finish * state. */ public void assertValidFinishState() { Assert.isTrue(currentSequenceStack.isEmpty(), "currentSequenceStack still contains " + currentSequenceStack.size() + " elements"); Assert.isTrue(currentSequenceElementStack.isEmpty(), "currentSequenceElementStack still contains " + currentSequenceElementStack.size() + " elements"); Assert.isTrue(currentParentFunctionCallStack.isEmpty(), "currentParentFunctionCallStack still contains " + currentParentFunctionCallStack.size() + " elements"); Assert.isTrue(currentParentPropertyStack.isEmpty(), "currentParentPropertyStack still contains " + currentParentPropertyStack.size() + " elements"); Assert.isTrue(currentParentTemplateStack.isEmpty(), "currentParentTemplateStack still contains " + currentParentTemplateStack.size() + " elements"); } public CtsContentAssistContext getFloorContext(int line, int charPositionInLine) { return getFloorContext(new TextPosition(line, charPositionInLine)); } public CtsContentAssistContext getFloorContext(TextPosition position) { if (positionMap.containsKey(position)) { return positionMap.get(position); } // return nearest entry before position SortedMap<TextPosition, CtsContentAssistContext> headMap = positionMap.headMap(position); if (headMap.size() != 0) { return headMap.get(headMap.lastKey()); } return null; } private void resolveNextSequenceElement() { try { SequenceElement currentSequenceElement = currentSequenceElementStack.pop(); if (currentSequenceElement == null) { currentSequenceElement = TcsUtil.getFirstSequenceElement(currentSequenceStack.peek()); } else { currentSequenceElement = TcsUtil.getNextSequenceElement(currentSequenceElement); } currentSequenceElementStack.push(currentSequenceElement); } catch (EmptyStackException e) { currentSequenceElementStack.push(null); logError("could not resolve sequence element, stack empty"); } } private void pushSequenceIfNecessary() { SequenceElement currentSequenceElement = currentSequenceElementStack.peek(); if (currentSequenceElement instanceof FunctionCall) { FunctionCall call = (FunctionCall) currentSequenceElement; logInfo("push function call"); currentParentFunctionCallStack.push(call); pushNonEmptySequence(call.getCalledFunction().getFunctionSequence()); } if (currentSequenceElement instanceof Block) { Block block = (Block) currentSequenceElement; pushNonEmptySequence(block.getBlockSequence()); } // ConditionalElement and Alternative are pushed in the // notifyEnterSequenceAlternative() } /** * Only push non-empty Sequences, as empty Sequences will never trigger * notifyExitSequenceElement(), and thus never get popped * * @param seq * Sequence to push */ private void pushNonEmptySequence(Sequence seq) { if (seq == null) { logInfo("no push of null sequence"); return; } if (seq.getElements() == null) { logInfo("no push of sequence with null elements"); return; } if (seq.getElements().size() > 0) { logInfo("push sequence"); currentSequenceStack.push(seq); currentSequenceElementStack.push(null); } else { logInfo("no push of empty sequence"); } } private void popSequenceIfNecessary() { try { SequenceElement currentSequenceElement = currentSequenceElementStack.peek(); if (TcsUtil.isLastSequenceElement(currentSequenceElement)) { logInfo("pop Sequence"); Sequence currentSequence = currentSequenceStack.pop(); currentSequenceElementStack.pop(); if (currentSequence.getFunctionContainer() != null) { logInfo("pop parentFunctionCall"); currentParentFunctionCallStack.pop(); } } } catch (EmptyStackException e) { logError("tried to pop empty stack"); } } @Override public void notifyEnterRule(String templateURI) { logInfo("notifyEnterRule " + templateURI); Template template = (Template) resourceSet.getEObject(URI.createURI(templateURI), true); if (template instanceof ContextTemplate) { logInfo("push parentTemplate"); currentParentTemplateStack.push(template); pushNonEmptySequence(((ContextTemplate) template).getTemplateSequence()); } else { logError("resolved no Template " + templateURI); } } @Override public void notifyEnterSequenceAlternative(int choice) { logInfo("notifyEnterSequenceAlternative " + choice); SequenceElement currentSequenceElement = currentSequenceElementStack.peek(); if (currentSequenceElement instanceof ConditionalElement) { ConditionalElement cond = (ConditionalElement) currentSequenceElement; if (choice == 0) { pushNonEmptySequence(cond.getThenSequence()); } else { pushNonEmptySequence(cond.getElseSequence()); } } if (currentSequenceElement instanceof Alternative) { Alternative alt = (Alternative) currentSequenceElement; // TODO why is Alternative.getSequences() a collection? Should be // ordered. List<SequenceInAlternative> sequences = new ArrayList<SequenceInAlternative>(alt.getSequences()); if (choice < sequences.size()) { pushNonEmptySequence(sequences.get(choice)); } else { logError("invalid choice " + choice + " for " + TcsDebugUtil.prettyPrint(alt)); } } } @Override public void notifyEnterSequenceElement() { resolveNextSequenceElement(); SequenceElement currentSequenceElement = currentSequenceElementStack.peek(); if (currentSequenceElement != null) { logInfo("notifyEnterSequenceElement " + TcsDebugUtil.prettyPrint(currentSequenceElement)); if (currentSequenceElement instanceof Property) { Property prop = (Property) currentSequenceElement; if (isParentProperty(prop)) { // further class templates belong to this Property, until it // is left logInfo("push parent property " + TcsUtil.getPropertyName(prop.getPropertyReference())); currentParentPropertyStack.push(prop); } if (TcsUtil.isEnumeration(prop)) { // create dummy sequence and push it onto the sequence // stack. // this is necessary, as EnumerationKinds have no sequence, // but reference // literals directly // TODO: once EnumerationKinds have a Sequence, push that // Sequence directly pushDummySequence("ENUMERATION_VALUE"); } } pushSequenceIfNecessary(); } else { logError("resolved null SequenceElement"); } } @Override public void notifyEnterSequenceElement(String sequenceElementURI) { notifyEnterSequenceElement(); } @Override public void notifyErrorInRule(RecognitionException re) { logInfo("notifyErrorInRule " + re); foundError = true; // mark last created context as error context if (getLastContext() != null) { getLastContext().setErrorContext(true); } } private CtsContentAssistContext getLastContext() { if (positionMap.size() > 0) { return positionMap.get(positionMap.lastKey()); } return null; } @Override public void notifyExitRule() { logInfo("notifyExitRule"); try { logInfo("pop parentTemplate"); currentParentTemplateStack.pop(); } catch (EmptyStackException e) { logError("tried to pop empty stack"); } // remaining pop of stacks is handled in // notifyExitSequenceElement() } @Override public void notifyExitSequenceAlternative() { logInfo("notifyExitSequenceAlternative"); // do nothing, as pop of stacks is handled in // notifyExitSequenceElement() } @Override public void notifyExitSequenceElement() { try { SequenceElement top = currentSequenceElementStack.peek(); logInfo("notifyExitSequenceElement " + TcsDebugUtil.prettyPrint(top)); if (top instanceof Property) { Property prop = (Property) top; if (isParentProperty(prop)) { // no more class templates that belong to this Property logInfo("pop parent property " + TcsUtil.getPropertyName(prop.getPropertyReference())); currentParentPropertyStack.pop(); } } popSequenceIfNecessary(); } catch (EmptyStackException e) { System.err.println("sequence element stack empty on notifyExitSequenceElement"); } } boolean isParentProperty(Property p) { return !CtsContentAssistUtil.isAtomic(p, classTemplateMap) || PropertyArgumentUtil.containsSeparatorArg(p) || TcsUtil.isEnumeration(p); } @Override public void notifyCommittedModelElementCreation(Object newModelElement) { logInfo("notifyModelElementResolved " + newModelElement); // do nothing } @Override public void notifyModelElementResolvedOutOfContext(Object modelElement, Object contextModelElement, Token referenceLocation, DelayedReference reference) { logInfo("notifyModelElementResolvedOutOfContext " + modelElement + " " + contextModelElement + " " + referenceLocation); // TODO check if action is needed } @Override public void notifyTokenConsume(Token token) { if (token != null) { logInfo("notifyTokenConsume " + token.getText() + " [" + token.getLine() + ", " + token.getCharPositionInLine() + "]"); if (token.getText() == null) { // special case for eos token return; } if (foundError) { logInfo("skipping context creation"); return; } try { CtsContentAssistContext context = createContextFromToken(token); addContextToPositionMap(context); } catch (EmptyStackException e) { System.err.println("could not create context, sequence element stack is empty"); } } else { logError("consumed null token"); } } private void addContextToPositionMap(CtsContentAssistContext context) { positionMap.put( new TextPosition(CtsContentAssistUtil.getLine(context.getToken()), CtsContentAssistUtil .getCharPositionInLine(context.getToken())), context); } private CtsContentAssistContext createContextFromToken(Token token) { CtsContentAssistContext context = new CtsContentAssistContext(); context.setToken(token); context.setSequenceElement(currentSequenceElementStack.peek()); context.setParentFunctionCallStack(CtsContentAssistUtil.duplicateFunctionCallStack(currentParentFunctionCallStack)); context.setParentPropertyStack(CtsContentAssistUtil.duplicatePropertyStack(currentParentPropertyStack)); context.setParentTemplateStack(CtsContentAssistUtil.duplicateTemplateStack(currentParentTemplateStack)); context.setOperator(currentIsOperator); // reset, as only the first token is the operator currentIsOperator = false; return context; } @Override public void notifyTokenConsumeWithError(Token token) { logInfo("notifyTokenConsumeWithError " + token); if (foundError) { logInfo("skipping context creation"); return; } foundError = true; if (token != null) { if (token.getText() == null) { // seems to be an unlexed token. // fix location. text is fixed in parsing handler where document // text is available if (getLastContext() != null) { Token lastContextToken = getLastContext().getToken(); if (lastContextToken != null && lastContextToken.getText() != null) { token.setLine(lastContextToken.getLine()); token.setCharPositionInLine(lastContextToken.getCharPositionInLine() + lastContextToken.getText().length()); } } } CtsContentAssistContext context = createContextFromToken(token); context.setErrorContext(true); addContextToPositionMap(context); } } @Override public void notifyCommitModelElementFailed() { logInfo("notifyModelElementResolutionFailed"); } /** * @param string */ @SuppressWarnings("unused") private void logInfo(String string) { if (loglevel > 1) { System.out.println(string); } } /** * @param string */ @SuppressWarnings("unused") private void logError(String string) { if (loglevel > 0) { System.out.println("ERROR: " + string); } } @Override public void notifyEnterSeparatorSequence() { logInfo("notifyEnterSeparatorSequence"); try { Property prop = currentParentPropertyStack.peek(); if (prop != null) { SeparatorPArg sepArg = PropertyArgumentUtil.getSeparatorPArg(prop); if (sepArg != null) { pushNonEmptySequence(sepArg.getSeparatorSequence()); } else { logError("notifyEnterSeparatorSequence called, but parentProperty has no SeparatorParg"); } } else { logError("notifyEnterSeparatorSequence called, but parentProperty is null"); } } catch (EmptyStackException e) { logError("tried to peek empty stack"); } } @Override public void notifyExitSeparatorSequence() { logInfo("notifyExitSeparatorSequence"); // do nothing, as pop of stacks is handled in // notifyExitSequenceElement() } @Override public void notifyEnterOperatorSequence(String operator, int arity, boolean isUnaryPostfix) { logInfo("notifyEnterOperatorSequence operator: " + operator + " arity: " + arity + " isUnaryPostfix: " + isUnaryPostfix); currentIsOperator = true; // create dummy sequence and push it onto the sequence stack. // this is necessary, as Operator has no sequence, but references a // literal directly // TODO: once Operator has a Sequence, push that Sequence directly pushDummySequence(operator); } void pushDummySequence(String dummyKeywordName) { TCSFactory c = TCSFactory.eINSTANCE; Sequence dummy = c.createSequence(); LiteralRef litRef = c.createLiteralRef(); Keyword keyword = c.createKeyword(); keyword.setValue(dummyKeywordName); litRef.setReferredLiteral(keyword); dummy.getElements().add(litRef); pushNonEmptySequence(dummy); } @Override public void notifyExitOperatorSequence() { logInfo("notifyExitOperatorSequence"); // do nothing, as pop of stacks is handled in // notifyExitSequenceElement() } @Override public void notifyEnterOperatoredBrackettedSequence() { logInfo("notifyEnterOperatoredBrackettedSequence"); } @Override public void notifyExitOperatoredBrackettedSequence() { logInfo("notifyExitOperatoredBrackettedSequence"); } @Override public void notifyElementAddedToContext(Object element) { logInfo("notifyElementAddedToContext"); } @Override public void notifyDelayedReferenceCreated(DelayedReference ref) { logInfo("notifyDelayedReferenceCreated"); } @Override public void notifyEnterInjectorAction() { logInfo("notifyEnterInjectorAction"); } @Override public void notifyExitInjectorAction() { logInfo("notifyExitInjectorAction"); } }