/*******************************************************************************
* Copyright (c) 2011 SAP AG and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* SAP AG - initial API and implementation
******************************************************************************/
package com.sap.furcas.ide.editor.contentassist;
import static com.sap.furcas.ide.editor.contentassist.CtsContentAssistUtil.createKeywordProposal;
import static com.sap.furcas.ide.editor.contentassist.CtsContentAssistUtil.createPropValueProposal;
import static com.sap.furcas.ide.editor.contentassist.CtsContentAssistUtil.createPropertyProposal;
import static com.sap.furcas.ide.editor.contentassist.CtsContentAssistUtil.createTemplateProposal;
import static com.sap.furcas.ide.editor.contentassist.CtsContentAssistUtil.duplicateFunctionCallStack;
import static com.sap.furcas.ide.editor.contentassist.CtsContentAssistUtil.duplicatePropertyStack;
import static com.sap.furcas.ide.editor.contentassist.CtsContentAssistUtil.getOffset;
import static com.sap.furcas.ide.editor.contentassist.CtsContentAssistUtil.isAtomic;
import static com.sap.furcas.ide.editor.contentassist.CtsContentAssistUtil.isContained;
import static com.sap.furcas.runtime.tcs.PropertyArgumentUtil.containsForcedLowerArg;
import static com.sap.furcas.runtime.tcs.PropertyArgumentUtil.containsForcedUpperArgOfOne;
import static com.sap.furcas.runtime.tcs.TcsUtil.getClassTemplates;
import static com.sap.furcas.runtime.tcs.TcsUtil.getFirstSequenceElement;
import static com.sap.furcas.runtime.tcs.TcsUtil.getMode;
import static com.sap.furcas.runtime.tcs.TcsUtil.getNextSequenceElement;
import static com.sap.furcas.runtime.tcs.TcsUtil.getParentAlternative;
import static com.sap.furcas.runtime.tcs.TcsUtil.getParentSequenceElement;
import static com.sap.furcas.runtime.tcs.TcsUtil.getParentTemplate;
import static com.sap.furcas.runtime.tcs.TcsUtil.getResourceSetFromEObject;
import static com.sap.furcas.runtime.tcs.TcsUtil.getSequence;
import static com.sap.furcas.runtime.tcs.TcsUtil.getType;
import static com.sap.furcas.runtime.tcs.TcsUtil.isMultiValued;
import static com.sap.furcas.runtime.tcs.TcsUtil.isOperatored;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import org.antlr.runtime.Token;
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.EStructuralFeature;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.query2.ResultSet;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import com.sap.furcas.metamodel.FURCAS.TCS.Alternative;
import com.sap.furcas.metamodel.FURCAS.TCS.Associativity;
import com.sap.furcas.metamodel.FURCAS.TCS.Block;
import com.sap.furcas.metamodel.FURCAS.TCS.ClassTemplate;
import com.sap.furcas.metamodel.FURCAS.TCS.ConcreteSyntax;
import com.sap.furcas.metamodel.FURCAS.TCS.ConditionalElement;
import com.sap.furcas.metamodel.FURCAS.TCS.CustomSeparator;
import com.sap.furcas.metamodel.FURCAS.TCS.EnumLiteralMapping;
import com.sap.furcas.metamodel.FURCAS.TCS.FunctionCall;
import com.sap.furcas.metamodel.FURCAS.TCS.FunctionTemplate;
import com.sap.furcas.metamodel.FURCAS.TCS.InjectorActionsBlock;
import com.sap.furcas.metamodel.FURCAS.TCS.LiteralRef;
import com.sap.furcas.metamodel.FURCAS.TCS.LookupScopePArg;
import com.sap.furcas.metamodel.FURCAS.TCS.Operator;
import com.sap.furcas.metamodel.FURCAS.TCS.OperatorList;
import com.sap.furcas.metamodel.FURCAS.TCS.OperatorTemplate;
import com.sap.furcas.metamodel.FURCAS.TCS.Priority;
import com.sap.furcas.metamodel.FURCAS.TCS.Property;
import com.sap.furcas.metamodel.FURCAS.TCS.ReferenceByPArg;
import com.sap.furcas.metamodel.FURCAS.TCS.RefersToPArg;
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.TCSFactory;
import com.sap.furcas.metamodel.FURCAS.TCS.Template;
import com.sap.furcas.metamodel.FURCAS.textblocks.AbstractToken;
import com.sap.furcas.metamodel.FURCAS.textblocks.TextBlock;
import com.sap.furcas.metamodel.FURCAS.textblocks.Version;
import com.sap.furcas.runtime.common.exceptions.MetaModelLookupException;
import com.sap.furcas.runtime.common.util.EcoreHelper;
import com.sap.furcas.runtime.common.util.TCSSpecificOCLEvaluator;
import com.sap.furcas.runtime.tcs.PropertyArgumentUtil;
import com.sap.furcas.runtime.tcs.TcsUtil;
import com.sap.furcas.runtime.textblocks.model.TextBlocksModel;
import com.sap.furcas.runtime.textblocks.modifcation.TbVersionUtil;
/**
* Used by the {@link CtsContentAssistProcessor} to calculate possible inputs after a given SequenceElement.
*
* <b>Terminology:</b>
* Atomic SequenceElement is one of: Property points to an Attribute or a Reference with the refersTo argument,
* LiteralRef.
*
* The other SequenceElements are composite in the sense, that they contain child-Sequences, and will not contain any input-tokens
* directly.
*
* CustomSeparators are ignored, as they do not contain any input-tokens and no child Sequences.
*
* @author D052602
*
* @author Philipp Meier
*
*/
public class CtsCompletionCalculator {
private static final URI TRANSIENT_PARTITION_NAME = URI.createURI("http://www.furcas.org/TcsUtilTransientPartition");
/**
* Caches LiteralRefs that are created to be returned as atomic sequence elements for each operator literal
*/
private static Map<Operator, LiteralRef> operatorToLiteralRefMap = new HashMap<Operator, LiteralRef>();
/**
* clears the TcsUtil transient partition on this connection
*
* @param c
*/
public static void clearTransientPartition(ResourceSet c) {
Resource transientPartition = c.getResource(TRANSIENT_PARTITION_NAME, false);
if (transientPartition != null) {
transientPartition.getContents().clear();
}
// also clear operatorToLiteralRefMap which would otherwise refer to
// deleted elements
operatorToLiteralRefMap.clear();
}
public static List<ICompletionProposal> createFirstPossibleProposals(ConcreteSyntax syntax,
Map<List<String>, Map<String, ClassTemplate>> classTemplateMap, ITextViewer viewer, int line, int charPositionInLine,
Token token, TextBlocksModel tbModel, TCSSpecificOCLEvaluator oclEvaluator) {
List<ICompletionProposal> results = new ArrayList<ICompletionProposal>();
for (SequenceElement atomic : getMainTemplatePossibleFirstAtomicSequenceElements(syntax, classTemplateMap)) {
results.addAll(createProposalsFromAtomicSequenceElement(syntax, classTemplateMap, atomic, viewer, line,
charPositionInLine, token, tbModel, oclEvaluator));
}
return results;
}
private static List<SequenceElement> getMainTemplatePossibleFirstAtomicSequenceElements(ConcreteSyntax syntax,
Map<List<String>, Map<String, ClassTemplate>> classTemplateMap) {
ClassTemplate main = TcsUtil.getMainClassTemplate(syntax);
Set<Template> visitedTemplates = new HashSet<Template>();
visitedTemplates.add(main);
if (main.isIsAbstract()) {
return getPossibleFirstAtomicSequenceElements((EClass) main.getMetaReference(), main.getMode(), classTemplateMap,
visitedTemplates, syntax, getResourceSetFromEObject(main));
} else {
return getPossibleFirstAtomicSequenceElements(main.getTemplateSequence(), classTemplateMap, visitedTemplates, syntax);
}
}
private static void cacheOperatorLiteral(OperatorList operatorList, Operator op) {
ResourceSet c = TcsUtil.getResourceSetFromEObject(operatorList);
Resource transientPartition = c.getResource(TRANSIENT_PARTITION_NAME, true);
LiteralRef litRef = TCSFactory.eINSTANCE.createLiteralRef();
transientPartition.getContents().add(litRef);
litRef.setReferredLiteral(op.getLiteral());
operatorToLiteralRefMap.put(op, litRef);
}
/**
* Returns a list of the first possible atomic SequenceElements of the given SequenceElement.
*
* Composite SequenceElements return the possible first atomic SequenceElements of each of their children merged into a single
* list, atomic SequenceElements return a list containing themselves.
*
* @param e
* SequenceElement to get the first possible atomic SequenceElements from
* @param visitedTemplates
* @return first possible atomic SequenceElements
*/
public static List<SequenceElement> getPossibleFirstAtomicSequenceElements(SequenceElement e,
Map<List<String>, Map<String, ClassTemplate>> classTemplateMap, Set<Template> visitedTemplates, ConcreteSyntax syntax) {
assert (visitedTemplates != null);
List<SequenceElement> results = new ArrayList<SequenceElement>();
// check for composite SequenceElements
if (e instanceof Alternative) {
Alternative alt = (Alternative) e;
if (alt.isIsMulti()) {
// isMulti Alternative is optional
results.add(null);
}
for (Sequence s : alt.getSequences()) {
addAllIfNotNull(
results,
getPossibleFirstAtomicSequenceElements(s, classTemplateMap, new HashSet<Template>(visitedTemplates),
syntax));
}
return results;
}
else if (e instanceof ConditionalElement) {
ConditionalElement cond = (ConditionalElement) e;
addAllIfNotNull(
results,
getPossibleFirstAtomicSequenceElements(cond.getThenSequence(), classTemplateMap, new HashSet<Template>(
visitedTemplates), syntax));
addAllIfNotNull(
results,
getPossibleFirstAtomicSequenceElements(cond.getElseSequence(), classTemplateMap, new HashSet<Template>(
visitedTemplates), syntax));
return results;
}
else if (e instanceof Block) {
Block block = (Block) e;
addAllIfNotNull(results,
getPossibleFirstAtomicSequenceElements(block.getBlockSequence(), classTemplateMap, visitedTemplates, syntax));
return results;
}
else if (e instanceof FunctionCall) {
FunctionCall call = (FunctionCall) e;
addAllIfNotNull(results,
getPossibleFirstAtomicSequenceElements(call.getCalledFunction(), classTemplateMap, visitedTemplates, syntax));
return results;
}
else if (e instanceof CustomSeparator) {
// CustomSeparators do not contain any SequenceElements
results.add(null);
return results;
}
else if (e instanceof InjectorActionsBlock) {
// InjectorActionsBlocks do not contain any SequenceElements
results.add(null);
return results;
}
else if (e instanceof Property) {
Property prop = (Property) e;
// if we have a multi-valued Property, add corresponding
// elements as well
if (isMultiValued(prop) && !containsForcedLowerArg(prop)) {
results.add(null);
}
if (isAtomic(prop, classTemplateMap)) {
results.add(e);
return results;
} else {
addAllIfNotNull(
results,
getPossibleFirstAtomicSequenceElements((EClass) getType(prop), getMode(prop), classTemplateMap,
visitedTemplates, syntax, getResourceSetFromEObject(prop)));
return results;
}
}
else {
// atomic SequenceElement or null
results.add(e);
return results;
}
}
/**
* Returns a list of all possible atomic SequenceElements, that directly follow the given SequenceElement, or indirectly
* follow it through SequenceElements that may have no tokens (i.e null Sequences in ConditionalElement).
*
* The parentFunctionCallMap parameter is needed to disambiguate between the different FunctionCalls that reference the
* FunctionTemplate this SequenceElement is part of. The map is needed, as functions can contain other functions.
*
* @param e
* SequenceElement after which to look
* @param parentFunctionCallMap
* maps the correct parent FunctionCall for each FunctionTemplate this SequenceElement is part of
* @return all possible SequenceElements that are directly following
*/
public static List<SequenceElement> getPossibleAtomicFollows(SequenceElement e, Stack<FunctionCall> parentFunctionCallStack,
Map<List<String>, Map<String, ClassTemplate>> classTemplateMap, Stack<Property> parentPropertyStack,
boolean isOperator, ConcreteSyntax syntax) {
// we will modify the stack, make a copy first
Stack<Property> copiedParentPropertyStack = duplicatePropertyStack(parentPropertyStack);
Stack<FunctionCall> copiedParentFunctionCallStack = duplicateFunctionCallStack(parentFunctionCallStack);
String operatorValue = null;
if (isOperator) {
// e is a literal ref referring to the operator value
LiteralRef ref = (LiteralRef) e;
operatorValue = ref.getReferredLiteral().getValue();
}
List<SequenceElement> results = new ArrayList<SequenceElement>();
// counts how often getParent() has been called
int orderOfParent = 0;
SequenceElement current = e;
while (current != null) {
if (current instanceof Property) {
Property prop = (Property) current;
SeparatorPArg sepArg = PropertyArgumentUtil.getSeparatorPArg(prop);
if (sepArg != null) {
// add separator sequence proposals as well
Sequence sepSeq = sepArg.getSeparatorSequence();
results.addAll(getPossibleFirstAtomicSequenceElements(sepSeq, classTemplateMap,
new HashSet<Template>(), syntax));
} else {
if (isMultiValued(prop) && !containsForcedUpperArgOfOne(prop)) {
// add the first elements of this property to
// the follows-set
addAllIfNotNull(results,
getPossibleFirstAtomicSequenceElements(prop, classTemplateMap, new HashSet<Template>(), syntax));
}
}
// operator handling
if (isOperatored(prop, classTemplateMap)) {
if (isOperator && orderOfParent == 1) {
// we are at an operator
// if the corresponding OperatorTemplates have a sequence, add the possible first atomic of that sequence.
// if this is not the case for at least one matching OperatorTemplate, add first elements of parent
// property
boolean otWithoutSequenceFound = false;
for (OperatorTemplate ot : findOperatorTemplatesByOperatorLiteralValue(operatorValue,
(EClass) getType(prop), syntax)) {
HashSet<Template> visitedTemplates = new HashSet<Template>();
visitedTemplates.add(ot);
if (ot.getTemplateSequence() != null) {
addAllIfNotNull(
results,
getPossibleFirstAtomicSequenceElements(ot.getTemplateSequence(), classTemplateMap,
visitedTemplates, syntax));
} else {
otWithoutSequenceFound = true;
}
}
if (otWithoutSequenceFound) {
addAllIfNotNull(
results,
getPossibleFirstAtomicSequenceElements(prop, classTemplateMap, new HashSet<Template>(),
syntax));
}
} else {
// we are before or past an operator and thus add all
// valid operators to the proposals
for (ClassTemplate ct : getClassTemplates((EClass) getType(prop), getMode(prop), classTemplateMap,
getResourceSetFromEObject(prop))) {
addAllIfNotNull(results, getOperatorsAsAtomicSequenceElements(getOperatorList(ct, syntax)));
}
}
}
}
if (current instanceof Alternative) {
Alternative alt = (Alternative) current;
if (alt.isIsMulti()) {
addAllIfNotNull(results,
getPossibleFirstAtomicSequenceElements(alt, classTemplateMap, new HashSet<Template>(), syntax));
}
}
Sequence parentSeq = current.getElementSequence();
if (parentSeq.getSeparatorContainer() != null) {
// we are in a separator sequence, add parent property to
// proposals
try {
Property parentProp = copiedParentPropertyStack.peek();
addAllIfNotNull(results, getPossibleFirstAtomicSequenceElements(parentProp, classTemplateMap,
new HashSet<Template>(), syntax));
// after separator, no other tokens are possible
return results;
} catch (Exception ex) {
// do nothing
}
}
SequenceElement next = getNextSequenceElement(current);
while (next != null) {
List<SequenceElement> nextElementResults = new ArrayList<SequenceElement>();
addAllIfNotNull(nextElementResults,
getPossibleFirstAtomicSequenceElements(next, classTemplateMap, new HashSet<Template>(), syntax));
results.addAll(nextElementResults);
if (nextElementResults.contains(null)) {
next = getNextSequenceElement(next);
} else {
// next SequenceElement is guaranteed to need a Token
return results;
}
}
current = getParentSequenceElement(current, copiedParentFunctionCallStack, copiedParentPropertyStack,
classTemplateMap);
orderOfParent++;
}
// special handling of operatored main template
// don't need to check current == null because current has been used before
if (syntax != null) {
ClassTemplate main = TcsUtil.getMainClassTemplate(syntax);
if (main.isIsOperatored()) {
if (isOperator && orderOfParent == 1) {
// we are at an operator, add first elements of parent
// property
addAllIfNotNull(
results,
getPossibleFirstAtomicSequenceElements((EClass) main.getMetaReference(), main.getMode(), classTemplateMap,
new HashSet<Template>(), syntax, getResourceSetFromEObject(main)));
// also add prefix operators
addAllIfNotNull(results, getPrefixOperatorsAsAtomicSequenceElements(getOperatorList(main, syntax)));
} else {
// we are before or past an operator and thus add all
// valid operators to the proposals
addAllIfNotNull(results, getOperatorsAsAtomicSequenceElements(getOperatorList(main, syntax)));
}
}
}
return results;
}
private static Collection<OperatorTemplate> findOperatorTemplatesByOperatorLiteralValue(String operatorValue, EClass type,
ConcreteSyntax syntax) {
List<OperatorTemplate> result = new ArrayList<OperatorTemplate>();
Collection<EClass> subTypes = TcsUtil.getAllSubtypes(type);
for (Template t : syntax.getTemplates()) {
if (t instanceof OperatorTemplate) {
OperatorTemplate ot = (OperatorTemplate) t;
for (EClass subType : subTypes) {
if (ot.getMetaReference() != null && ot.getMetaReference().equals(subType)) {
if (ot.getOperators() != null) {
for (Operator op : ot.getOperators()) {
// assumes that not two operators within the
// used
// operator list have the same literal
if (op.getLiteral().getValue().equals(operatorValue)) {
result.add(ot);
}
}
}
}
}
}
}
return result;
}
private static OperatorList getOperatorList(ClassTemplate ct, ConcreteSyntax syntax) {
OperatorList mainClassOpList = ct.getOperatorList();
if (mainClassOpList != null) {
return mainClassOpList;
}
// no operator list name specified, query operator lists of syntax
// instead
for (OperatorList opList : syntax.getOperatorLists()) {
if (opList.getName() == null) {
return opList;
}
}
return null;
}
private static List<SequenceElement> getOperatorsAsAtomicSequenceElements(OperatorList operatorList) {
if (operatorList == null) {
return null;
}
List<SequenceElement> results = new ArrayList<SequenceElement>();
for (Priority prio : operatorList.getPriorities()) {
for (Operator op : prio.getOperators()) {
if (!operatorToLiteralRefMap.containsKey(op)) {
cacheOperatorLiteral(operatorList, op);
}
results.add(operatorToLiteralRefMap.get(op));
}
}
return results;
}
private static List<SequenceElement> getPrefixOperatorsAsAtomicSequenceElements(OperatorList operatorList) {
if (operatorList == null) {
return null;
}
// prefix means arity of 1 and left associativity and not postfix
List<SequenceElement> results = new ArrayList<SequenceElement>();
for (Priority prio : operatorList.getPriorities()) {
if (prio.getAssociativity() == Associativity.LEFT) {
for (Operator op : prio.getOperators()) {
if (op.getArity() == 1 && !op.isPostfix()) {
if (!operatorToLiteralRefMap.containsKey(op)) {
cacheOperatorLiteral(operatorList, op);
}
results.add(operatorToLiteralRefMap.get(op));
}
}
}
}
return results;
}
/**
* Returns a list of the first possible atomic SequenceElements of the given Sequence.
*
* @param s
* Sequence to get the first possible atomic SequenceElements from
* @return first possible atomic SequenceElements
*/
public static List<SequenceElement> getPossibleFirstAtomicSequenceElements(Sequence s,
Map<List<String>, Map<String, ClassTemplate>> classTemplateMap, Set<Template> visitedTemplates, ConcreteSyntax syntax) {
assert (visitedTemplates != null);
SequenceElement e = getFirstSequenceElement(s);
List<SequenceElement> results = new ArrayList<SequenceElement>();
List<SequenceElement> elementResults;
do {
elementResults = getPossibleFirstAtomicSequenceElements(e, classTemplateMap, new HashSet<Template>(visitedTemplates),
syntax);
addAllIfNotNull(results, elementResults);
e = getNextSequenceElement(e);
} while (elementResults.contains(null) && e != null);
return results;
}
/**
* Returns a list of the first possible atomic SequenceElements of the given FunctionTemplate.
*
* @param t
* FunctionTemplate to get the first possible atomic SequenceElements from
* @return first possible atomic SequenceElements
*/
private static List<SequenceElement> getPossibleFirstAtomicSequenceElements(FunctionTemplate t,
Map<List<String>, Map<String, ClassTemplate>> classTemplateMap, Set<Template> visitedTemplates, ConcreteSyntax syntax) {
assert (visitedTemplates != null);
if (t != null) {
if (!visitedTemplates.contains(t)) {
visitedTemplates.add(t);
return getPossibleFirstAtomicSequenceElements(t.getFunctionSequence(), classTemplateMap, visitedTemplates, syntax);
} else {
return null;
}
}
return createListWithEntry(null);
}
private static List<SequenceElement> createListWithEntry(SequenceElement e) {
List<SequenceElement> result = new ArrayList<SequenceElement>();
result.add(e);
return result;
}
private static List<SequenceElement> getPossibleFirstAtomicSequenceElements(EClass type, String mode,
Map<List<String>, Map<String, ClassTemplate>> classTemplateMap, Set<Template> visitedTemplates,
ConcreteSyntax syntax, ResourceSet connection) {
List<SequenceElement> results = new ArrayList<SequenceElement>();
Collection<ClassTemplate> classTemplates = getClassTemplates(type, mode, classTemplateMap, connection);
classTemplates.removeAll(visitedTemplates); // reduce classTemplates to the templates which have not yet been visited
visitedTemplates.addAll(classTemplates);
for (ClassTemplate ct : classTemplates) {
if (!ct.isIsAbstract()) {
addAllIfNotNull(
results,
getPossibleFirstAtomicSequenceElements(ct.getTemplateSequence(), classTemplateMap, visitedTemplates,
syntax));
} else {
if (ct.isIsOperatored()) {
// add prefix operators
addAllIfNotNull(results, getPrefixOperatorsAsAtomicSequenceElements(getOperatorList(ct, syntax)));
}
}
}
return results;
}
public static List<ICompletionProposal> createFollowProposalsFromContext(ConcreteSyntax syntax,
CtsContentAssistContext context, Map<List<String>, Map<String, ClassTemplate>> classTemplateMap, ITextViewer viewer,
int line, int charPositionInLine, Token token, TextBlocksModel tbModel, TCSSpecificOCLEvaluator oclEvaluator) {
List<ICompletionProposal> results = new ArrayList<ICompletionProposal>();
if (context == null) {
return results;
}
for (SequenceElement atomic : getPossibleAtomicFollows(context.getSequenceElement(),
context.getParentFunctionCallStack(), classTemplateMap, context.getParentPropertyStack(), context.isOperator(),
syntax)) {
results.addAll(createProposalsFromAtomicSequenceElement(syntax, classTemplateMap, atomic, viewer, line,
charPositionInLine, token, tbModel, oclEvaluator));
}
return results;
}
private static List<ICompletionProposal> createProposalsFromAtomicSequenceElement(ConcreteSyntax syntax,
Map<List<String>, Map<String, ClassTemplate>> classTemplateMap, SequenceElement e, ITextViewer viewer, int line,
int charPositionInLine, Token token, TextBlocksModel tbModel, TCSSpecificOCLEvaluator oclEvaluator) {
List<ICompletionProposal> results = new ArrayList<ICompletionProposal>();
if (e != null && syntax != null) {
List<String> templateStrings = new ArrayList<String>();
Template parentTemplate = getParentTemplate(e, syntax);
Sequence parentSequence = getSequence(parentTemplate);
Set<SequenceElement> templateFirstAtomicSet = new HashSet<SequenceElement>(
getPossibleFirstAtomicSequenceElements(parentSequence, classTemplateMap, new HashSet<Template>(),
syntax));
if (isContained(e, templateFirstAtomicSet)) {
// add template proposals starting at the parent template
templateStrings.addAll(generateFollowTemplateProposalStrings(parentSequence, classTemplateMap, syntax,
new HashSet<Template>()));
}
Alternative parentAlternative = getParentAlternative(e);
if (parentAlternative != null) {
Set<SequenceElement> alternativeFirstAtomicSet = new HashSet<SequenceElement>(
getPossibleFirstAtomicSequenceElements(parentAlternative, classTemplateMap,
new HashSet<Template>(), syntax));
if (isContained(e, alternativeFirstAtomicSet)) {
// add template proposals starting at each alternative
for (Sequence s : parentAlternative.getSequences()) {
templateStrings.addAll(generateFollowTemplateProposalStrings(s, classTemplateMap, syntax,
new HashSet<Template>()));
}
}
}
for (String templateString : templateStrings) {
// check if we actually have a template proposal and not
// just a single keyword
if (templateString.contains(" ")) {
String displayString = templateString;
String replacementString = displayString;
results.add(createTemplateProposal(displayString, replacementString, viewer, line, charPositionInLine, token));
}
}
}
if (e instanceof LiteralRef) {
LiteralRef lit = (LiteralRef) e;
if (lit.getReferredLiteral() != null) {
String literalValue = lit.getReferredLiteral().getValue();
String displayString = literalValue;
String replacementString = displayString;
results.add(createKeywordProposal(displayString, replacementString, viewer, line, charPositionInLine, token));
}
} else if (e instanceof Property) {
Property prop = (Property) e;
assert (isAtomic(prop, classTemplateMap));
if (TcsUtil.isEnumeration(prop)) {
// propose the enum values
for (EnumLiteralMapping mapping : TcsUtil.getEnumTemplateForType(syntax, prop.getPropertyReference()
.getStrucfeature().getEType())) {
LiteralRef ref = (LiteralRef) mapping.getElement();
if (ref.getReferredLiteral() != null) {
String displayString = ref.getReferredLiteral().getValue();
String replacementString = displayString;
results.add(createPropValueProposal(displayString, replacementString, viewer, line,
charPositionInLine, token));
}
}
}
RefersToPArg refersToArg = PropertyArgumentUtil.getRefersToPArg(prop);
ReferenceByPArg referenceByArg = PropertyArgumentUtil.getReferenceByPArg(prop);
LookupScopePArg lookupScopeArg = PropertyArgumentUtil.getLookupScopePArg(prop);
if (referenceByArg != null && lookupScopeArg != null) {
// first find all elements in the lookup scope, then apply referenceBy OCL expression to
// map the individual elements into its string representation
String invert = PropertyArgumentUtil.getReferenceByAsOCL(referenceByArg);
Collection<EObject> allElementsInLookupScope = findAllElementsInLookupScope(viewer, line, charPositionInLine, tbModel,
lookupScopeArg, oclEvaluator);
for (EObject candidateInScope : allElementsInLookupScope) {
String displayString = null;
String replacementString = null;
try {
// We don't have to pass in a context or foreach element: The invert/referenceBy query
// is directly executed on the elements in the lookupScope
Object result = oclEvaluator.findElementsWithOCLQuery(candidateInScope, /*keyValue*/ null, invert,
/*context*/ null, /*foreach*/ null);
if (result instanceof Collection<?>) {
result = ((Collection<?>) result).iterator().next();
}
if (result instanceof String) {
replacementString = (String) result;
replacementString = PropertyArgumentUtil.stripPrefixPostfix(replacementString,
PropertyArgumentUtil.getPrefixPArg(prop), PropertyArgumentUtil.getPostfixPArg(prop));
displayString = replacementString + " : " + candidateInScope.eClass().getName()
+ " (by: " + invert + ")";
}
} catch (Exception e1) {
//this is not necessarily an error
//as we investigate whether the current property fits the element
//if this is not the case this caught exception may occur
}
if (displayString != null) {
results.add(createPropValueProposal(displayString, replacementString, viewer, line,
charPositionInLine, token));
}
}
}
if (refersToArg != null) {
// propose referenced feature of model elements queried by type
EStructuralFeature propFeature = TcsUtil.getStructuralFeature(prop);
if (propFeature != null) {
String featureName = PropertyArgumentUtil.getRefersToPArg(prop).getPropertyName();
List<String> propValues = queryPropertyValues(propFeature.getEType(), featureName, prop.eResource()
.getResourceSet());
for (String propValue : propValues) {
String displayString = propValue + " : " + propFeature.getEType().getName();
String replacementString = propValue;
results.add(createPropValueProposal(displayString, replacementString, viewer, line, charPositionInLine,
token));
}
}
}
if (TcsUtil.isMultiValued(prop)) {
// we have a multivalued primitive types property
// name is likely to be plural so we add Entry to make the
// proposal singular
String displayString = TcsUtil.getPropertyName(prop.getPropertyReference()) + "Entry";
String replacementString = displayString;
results.add(createPropertyProposal(displayString, replacementString, viewer, line, charPositionInLine, token));
} else {
String displayString = TcsUtil.getPropertyName(prop.getPropertyReference());
String replacementString = displayString;
results.add(createPropertyProposal(displayString, replacementString, viewer, line, charPositionInLine, token));
}
}
return results;
}
/**
* Execute the lookupScope OCL query to find all elements which can potentially be bound.
*/
@SuppressWarnings("unchecked")
private static Collection<EObject> findAllElementsInLookupScope(ITextViewer viewer, int line, int charPositionInLine,
TextBlocksModel textBlocksModel, LookupScopePArg queryArg, TCSSpecificOCLEvaluator oclEvaluator) {
TextBlock currentVersion = TbVersionUtil.getOtherVersion(textBlocksModel.getRoot(), Version.CURRENT);
if (currentVersion != null) {
textBlocksModel = new TextBlocksModel(currentVersion);
}
// currentTbModel is non-null at this point
if (textBlocksModel.getRoot() != null) {
AbstractToken floorToken = textBlocksModel.getFloorTokenInRoot(getOffset(viewer, line, charPositionInLine));
TextBlock parentBlock = floorToken.getParent();
while (parentBlock != null && parentBlock.getCorrespondingModelElements().size() < 1) {
parentBlock = parentBlock.getParent();
}
if (parentBlock != null) {
// we found a parent block with attached model
// element(s)
EObject element = parentBlock.getCorrespondingModelElements().get(0);
EObject contextElement = getContextElement(parentBlock, TcsUtil.getContextTag(queryArg.getQuery()));
EObject foreachObject = getForeachElement(TcsUtil.getContextTag(queryArg.getQuery()));
try {
return (Collection<EObject>) oclEvaluator.findElementsWithOCLQuery(element, /*keyValue*/ null,
queryArg.getQuery(), contextElement, foreachObject);
} catch (Exception e1) {
System.out.println("Error executing ocl query: " + e1.getMessage());
// do nothing, just omit proposals
}
}
}
return Collections.emptyList();
}
/**
* @param contextTag
*/
private static EObject getForeachElement(String contextTag) {
// currently the ForeachElement is only used in queries of model elements without
// syntactical elements and is thus currently irrelevant for content assist
// once this changes a new testcase needs to be created and this implementation
// can be derived from getContextElement()
// do nothing
return null;
}
private static EObject getContextElement(TextBlock parentBlock, String tag) {
TextBlock curBlock = parentBlock;
while (curBlock != null && curBlock.getCorrespondingModelElements().size() > 0) {
Template t = curBlock.getType();
if (t instanceof ClassTemplate) {
ClassTemplate ct = (ClassTemplate) t;
if (TcsUtil.matchesContext(ct, tag)) {
return curBlock.getCorrespondingModelElements().get(0);
}
}
if (t instanceof OperatorTemplate) {
OperatorTemplate ot = (OperatorTemplate) t;
if (TcsUtil.matchesContext(ot, tag)) {
return curBlock.getCorrespondingModelElements().get(0);
}
}
curBlock = curBlock.getParent();
}
return null;
}
private static List<String> queryPropertyValues(EClassifier type, String featureName, ResourceSet conn) {
// TODO limit query to a single partition or use model information from
// parsing handler instead?
String query = "select ofClass from [" + EcoreUtil.getURI(type) + "] as ofClass";
ResultSet resultSet;
try {
resultSet = EcoreHelper.executeQuery(query, EcoreHelper.getQueryContext(conn));
} catch (MetaModelLookupException e1) {
e1.printStackTrace();
return Collections.emptyList();
}
URI[] resultElements = resultSet.getUris("ofClass");
List<String> results = new ArrayList<String>();
for (URI elemURI : resultElements) {
try {
EObject element = conn.getEObject(elemURI, false);
Object featureValue = element.eGet(element.eClass().getEStructuralFeature(featureName));
if (featureValue instanceof Integer) {
results.add("" + featureValue);
}
if (featureValue instanceof String) {
results.add((String) featureValue);
}
} catch (Exception e) {
System.err.println("TcsUtil.queryPropertyValues encountered the following error: cannot read feature "
+ featureName + " from element " + elemURI.toString());
}
// TODO need to add more type checks?
}
/*
* System.out.println("TcsUtil.queryPropertyValues(" + joinNameList(type.getQualifiedName()) + ", " + featureName + ");");
* System.out.println(query); System.out.println(results);
*/
return results;
}
/**
* generate all possible template strings of this sequence
*
* @param s
* @return
*/
private static List<String> generateFollowTemplateProposalStrings(Sequence s,
Map<List<String>, Map<String, ClassTemplate>> classTemplateMap, ConcreteSyntax syntax,
HashSet<Template> visitedTemplates) {
List<String> results = new ArrayList<String>();
if (s == null) {
return results;
}
results.add("");
for (SequenceElement e : s.getElements()) {
List<String> newResults = new ArrayList<String>();
for (String result : results) {
for (String templateProposal : generateFollowTemplateProposalStrings(e, classTemplateMap, syntax,
new HashSet<Template>(visitedTemplates))) {
String newResult = result + " " + templateProposal;
newResults.add(newResult.trim());
}
}
if (!newResults.isEmpty()) {
results = newResults;
}
}
return results;
}
/**
* generate all possible template strings of this sequence element
*
* @param e
* @return
*/
private static List<String> generateFollowTemplateProposalStrings(SequenceElement e,
Map<List<String>, Map<String, ClassTemplate>> classTemplateMap, ConcreteSyntax syntax,
HashSet<Template> visitedTemplates) {
List<String> results = new ArrayList<String>();
if (e == null) {
results.add("");
return results;
}
List<SequenceElement> firstElements = getPossibleFirstAtomicSequenceElements(e, classTemplateMap,
new HashSet<Template>(), syntax);
if (!firstElements.contains(null)) {
// we have a required SequenceElement
// special handling of container sequence elements
if (e instanceof Alternative) {
Alternative alt = (Alternative) e;
for (Sequence s : alt.getSequences()) {
results.addAll(generateFollowTemplateProposalStrings(s, classTemplateMap, syntax, new HashSet<Template>(
visitedTemplates)));
}
return results;
}
if (e instanceof ConditionalElement) {
ConditionalElement cond = (ConditionalElement) e;
results.addAll(generateFollowTemplateProposalStrings(cond.getThenSequence(), classTemplateMap, syntax,
new HashSet<Template>(visitedTemplates)));
results.addAll(generateFollowTemplateProposalStrings(cond.getElseSequence(), classTemplateMap, syntax,
new HashSet<Template>(visitedTemplates)));
return results;
}
if (e instanceof Block) {
Block b = (Block) e;
return generateFollowTemplateProposalStrings(b.getBlockSequence(), classTemplateMap, syntax, visitedTemplates);
}
if (e instanceof FunctionCall) {
FunctionCall call = (FunctionCall) e;
FunctionTemplate func = call.getCalledFunction();
if (!visitedTemplates.contains(func)) {
visitedTemplates.add(func);
return generateFollowTemplateProposalStrings(func.getFunctionSequence(), classTemplateMap, syntax,
visitedTemplates);
}
}
if (e instanceof Property) {
Property p = (Property) e;
if (!isAtomic(p, classTemplateMap)) {
Collection<ClassTemplate> classTemplates = TcsUtil.getClassTemplates((EClass) TcsUtil.getType(p),
TcsUtil.getMode(p), classTemplateMap, p.eResource().getResourceSet());
classTemplates.removeAll(visitedTemplates); // reduce classTemplates to the templates which have not yet been visited
visitedTemplates.addAll(classTemplates);
for (ClassTemplate ct : classTemplates) {
HashSet<Template> subVisited = new HashSet<Template>(visitedTemplates);
results.addAll(generateFollowTemplateProposalStrings(ct.getTemplateSequence(), classTemplateMap, syntax,
subVisited));
}
return results;
}
}
// no composite
String firstElemDisplayString = "";
if (e instanceof LiteralRef) {
LiteralRef ref = (LiteralRef) e;
if (ref.getReferredLiteral() != null) {
firstElemDisplayString = ref.getReferredLiteral().getValue();
}
} else if (e instanceof Property) {
Property prop = (Property) e;
if (TcsUtil.isMultiValued(prop)) {
firstElemDisplayString = TcsUtil.getPropertyName(prop.getPropertyReference()) + "Entry";
} else {
firstElemDisplayString = TcsUtil.getPropertyName(prop.getPropertyReference());
}
}
results.add(firstElemDisplayString);
}
return results;
}
private static void addAllIfNotNull(List<SequenceElement> output, List<SequenceElement> input) {
if (input != null) {
output.addAll(input);
}
}
}