/*
* Copyright (c) 2005, 2008 Sven Efftinge 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:
* Sven Efftinge - Initial API and implementation
*/
package org.eclipse.gmf.internal.xpand.expression.codeassist;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import lpg.runtime.IToken;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EOperation;
import org.eclipse.emf.ecore.EParameter;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.gmf.internal.xpand.BuiltinMetaModel;
import org.eclipse.gmf.internal.xpand.editor.Activator;
import org.eclipse.gmf.internal.xpand.model.ExecutionContext;
import org.eclipse.gmf.internal.xpand.model.Variable;
import org.eclipse.gmf.internal.xpand.parser.XpandParsersym;
import org.eclipse.gmf.internal.xpand.xtend.ast.GenericExtension;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.ocl.ecore.CollectionType;
import org.eclipse.ocl.lpg.AbstractFormattingHelper;
import org.eclipse.ocl.utilities.PredefinedType;
@SuppressWarnings("restriction")
public class ExpressionProposalComputer implements ProposalComputer {
private ExecutionContext executionContext;
private final ProposalFactory proposalFactory;
private final ExpressionSimpleAnalyzer exprAnalyzer;
public ExpressionProposalComputer(ProposalFactory factory) {
assert factory != null;
proposalFactory = factory;
ExpressionSimpleAnalyzer cached = Activator.findState(ExpressionSimpleAnalyzer.class);
if (cached == null) {
Activator.putState(ExpressionSimpleAnalyzer.class, cached = new ExpressionSimpleAnalyzer());
}
exprAnalyzer = cached;
}
/**
* @param ctx
* @return
*/
public List<ICompletionProposal> computeProposals(final String txt, final ExecutionContext context) {
try {
final String[] s = exprAnalyzer.computePrefixAndTargetExpression(txt);
final String prefix = s[0];
final String expressionString = s[1];
this.executionContext = context;
final List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
if ((prefix.length() > 0) && (expressionString == null)) {
proposals.addAll(new TypeProposalComputer(proposalFactory).computeProposals(txt, executionContext));
}
EClassifier targetType = null;
if (expressionString != null) {
/*
* FIXME new ExpressionHelper(expressionString).analyze(executionContext, issues);
*
final Set<AnalysationIssue> issues = new HashSet<AnalysationIssue>();
targetType = null;
if (targetType == null) {
return Collections.emptyList();
}
*/
// HACK: FALL-THROUGH
}
if (targetType == null) {
// variables
for (org.eclipse.ocl.expressions.Variable<EClassifier, EParameter> v : executionContext.getOCLEnvironment().getVariables()) {
String varName = v.getName();
if (varName.toLowerCase().startsWith(prefix.toLowerCase())) {
EClassifier t = v.getType();
String typeName = AbstractFormattingHelper.INSTANCE.formatType(t);
proposals.add(proposalFactory.createVariableProposal(varName, typeName, prefix));
}
}
// members and extensions on this
final Variable v = executionContext.getImplicitVariable();
if (v != null) {
targetType = v.getType();
proposals.addAll(getAllMemberProposals(targetType, prefix));
}
final Set<? extends GenericExtension> exts = executionContext.getAllExtensions();
for (GenericExtension extension : exts) {
if (extension.getName().toLowerCase().startsWith(prefix.toLowerCase())) {
proposals.add(proposalFactory.createExtensionProposal(extension, prefix));
}
}
} else {
// members and extensions on targetType
proposals.addAll(getAllMemberProposals(targetType, prefix));
}
return proposals;
} finally {
executionContext = null;
}
}
/**
* @param targetType
* @param prefix
* @param context
* @param ctx
* @param factory
* @return
*/
private List<ICompletionProposal> getAllMemberProposals(EClassifier targetType, final String prefix) {
if (targetType == null) {
return Collections.emptyList();
}
final List<ICompletionProposal> result = new ArrayList<ICompletionProposal>();
result.addAll(internalGetAllMemberProposals(targetType, prefix, false));
if (targetType instanceof CollectionType) {
result.addAll(getAllCollectionOperations(prefix));
targetType = ((CollectionType) targetType).getElementType();
result.addAll(internalGetAllMemberProposals(targetType, prefix, true));
}
return result;
}
private List<ICompletionProposal> internalGetAllMemberProposals(EClassifier targetType, String prefix, boolean onCollection) {
final List<ICompletionProposal> result = new LinkedList<ICompletionProposal>();
final String prefixLowerCase = prefix.toLowerCase();
for (EStructuralFeature f : getAllFeatures(targetType, executionContext)) {
if (f.getName().toLowerCase().startsWith(prefixLowerCase)) {
result.add(proposalFactory.createPropertyProposal(f, prefix, onCollection));
}
}
for (EOperation op : getAllOperation(targetType, executionContext)) {
if (op.getName().toLowerCase().startsWith(prefixLowerCase) && Character.isJavaIdentifierStart(op.getName().charAt(0))) {
result.add(proposalFactory.createOperationProposal(op, prefix, onCollection));
}
}
for (GenericExtension e : executionContext.getAllExtensions()) {
if (e.getName().toLowerCase().startsWith(prefixLowerCase) && (e.getParameterTypes().size() >= 1) &&
BuiltinMetaModel.isAssignableFrom(executionContext, e.getParameterTypes().get(0), targetType)) {
result.add(proposalFactory.createExtensionOnMemberPositionProposal(e, prefix, onCollection));
}
}
return result;
}
private static List<EStructuralFeature> getAllFeatures(EClassifier targetType, ExecutionContext ctx) {
List<EStructuralFeature> r2 = ctx.getOCLEnvironment().getTypeResolver().getAdditionalAttributes(targetType);
if (targetType instanceof EClass) {
List<EStructuralFeature> r1 = ((EClass) targetType).getEAllStructuralFeatures();
ArrayList<EStructuralFeature> rv = new ArrayList<EStructuralFeature>(r1.size() + r2.size());
rv.addAll(r1);
rv.addAll(r2);
return rv;
}
return r2;
}
private static List<EOperation> getAllOperation(EClassifier targetType, ExecutionContext ctx) {
List<EOperation> r2 = ctx.getOCLEnvironment().getTypeResolver().getAdditionalOperations(targetType);
if (targetType instanceof PredefinedType<?>) {
@SuppressWarnings("unchecked")
PredefinedType<EOperation> t = (PredefinedType<EOperation>) targetType;
List<EOperation> r1 = t.oclOperations();
ArrayList<EOperation> rv = new ArrayList<EOperation>(r1.size() + r2.size());
rv.addAll(r1);
rv.addAll(r2);
return rv;
} else if (targetType instanceof EClass) {
List<EOperation> r1 = ((EClass) targetType).getEAllOperations();
ArrayList<EOperation> rv = new ArrayList<EOperation>(r1.size() + r2.size());
rv.addAll(r1);
rv.addAll(r2);
return rv;
}
return r2;
}
// XXX could use XpandParsersym.orderedTerminalSymbols[TK_xx] to get
// values like 'select', 'reject', etc
private List<ICompletionProposal> getAllCollectionOperations(final String prefix) {
final List<ICompletionProposal> result = new ArrayList<ICompletionProposal>();
final String marked = "expression-with-e";
String s = "select(e|" + marked + ")";
if (s.startsWith(prefix)) {
result.add(proposalFactory.createCollectionSpecificOperationProposal(s, s, prefix, s.indexOf(marked), marked.length()));
}
s = "reject(e|" + marked + ")";
if (s.startsWith(prefix)) {
result.add(proposalFactory.createCollectionSpecificOperationProposal(s, s, prefix, s.indexOf(marked), marked.length()));
}
s = "collect(e|" + marked + ")";
if (s.startsWith(prefix)) {
result.add(proposalFactory.createCollectionSpecificOperationProposal(s, s, prefix, s.indexOf(marked), marked.length()));
}
s = "exists(e|" + marked + ")";
if (s.startsWith(prefix)) {
result.add(proposalFactory.createCollectionSpecificOperationProposal(s, s, prefix, s.indexOf(marked), marked.length()));
}
s = "notExists(e|" + marked + ")";
if (s.startsWith(prefix)) {
result.add(proposalFactory.createCollectionSpecificOperationProposal(s, s, prefix, s.indexOf(marked), marked.length()));
}
s = "forAll(e|" + marked + ")";
if (s.startsWith(prefix)) {
result.add(proposalFactory.createCollectionSpecificOperationProposal(s, s, prefix, s.indexOf(marked), marked.length()));
}
s = "[EClassifier]";
if (s.startsWith(prefix)) {
result.add(proposalFactory.createCollectionSpecificOperationProposal(s, s, prefix, s.indexOf("EClassifier"), "EClassifier".length()));
}
return result;
}
/**
* is public only for testing purposes
*/
public static class ExpressionSimpleAnalyzer {
private final Set<Integer> operators = new HashSet<Integer>();
{
operators.add(XpandParsersym.TK_and);
operators.add(XpandParsersym.TK_DIVIDE);
operators.add(XpandParsersym.TK_DOT);
operators.add(XpandParsersym.TK_EQUAL);
operators.add(XpandParsersym.TK_GREATER_EQUAL);
operators.add(XpandParsersym.TK_GREATER);
operators.add(XpandParsersym.TK_LESS_EQUAL);
operators.add(XpandParsersym.TK_LESS);
operators.add(XpandParsersym.TK_MINUS);
operators.add(XpandParsersym.TK_MULTIPLY);
operators.add(XpandParsersym.TK_NOT_EQUAL);
operators.add(XpandParsersym.TK_not);
operators.add(XpandParsersym.TK_or);
operators.add(XpandParsersym.TK_PLUS);
operators.add(XpandParsersym.TK_ARROW);
}
private final Set<Integer> stopper = new HashSet<Integer>();
{
stopper.add(XpandParsersym.TK_LPAREN);
stopper.add(XpandParsersym.TK_COLON);
stopper.add(XpandParsersym.TK_QUESTIONMARK);
stopper.add(XpandParsersym.TK_BAR);
stopper.add(XpandParsersym.TK_LBRACE);
stopper.add(XpandParsersym.TK_COMMA);
}
private final Set<Integer> methodNames = new HashSet<Integer>();
{
// TODO: commented out due to the latest changed in OCL parser. Corresponding code have to be reviewed.
/*
methodNames.add(XpandParsersym.TK_IDENTIFIER);
methodNames.add(XpandParsersym.TK_collect);
methodNames.add(XpandParsersym.TK_exists);
methodNames.add(XpandParsersym.TK_forAll);
methodNames.add(XpandParsersym.TK_reject);
methodNames.add(XpandParsersym.TK_select);
*/
// methodNames.add(XpandParsersym.TK_typeSelect);
// TODO: commented out due to the latest changed in OCL parser. Corresponding code have to be reviewed.
/*
methodNames.add(XpandParsersym.TK_closure);
methodNames.add(XpandParsersym.TK_any);
methodNames.add(XpandParsersym.TK_one);
methodNames.add(XpandParsersym.TK_collectNested);
methodNames.add(XpandParsersym.TK_sortedBy);
methodNames.add(XpandParsersym.TK_isUnique);
methodNames.add(XpandParsersym.TK_iterate);
*/
// TODO: commented out due to the latest changed in OCL parser. Corresponding code have to be reviewed.
/* methodNames.add(XpandParsersym.TK_oclIsKindOf);
methodNames.add(XpandParsersym.TK_oclIsTypeOf);
methodNames.add(XpandParsersym.TK_oclAsType);
methodNames.add(XpandParsersym.TK_oclIsNew);
methodNames.add(XpandParsersym.TK_oclIsUndefined);
methodNames.add(XpandParsersym.TK_oclIsInvalid);
methodNames.add(XpandParsersym.TK_oclIsInState);
methodNames.add(XpandParsersym.TK_allInstances);*/
}
private final Set<Integer> operands = new HashSet<Integer>();
{
operands.add(XpandParsersym.TK_IDENTIFIER);
// operands.add(XpandParsersym.TK_collect);
// operands.add(XpandParsersym.TK_exists);
operands.add(XpandParsersym.TK_false);
// operands.add(XpandParsersym.TK_forAll);
operands.add(XpandParsersym.TK_null);
// operands.add(XpandParsersym.TK_reject);
// operands.add(XpandParsersym.TK_select);
operands.add(XpandParsersym.TK_true);
operands.add(XpandParsersym.TK_self);
// operands.add(XpandParsersym.TK_typeSelect);
operands.add(XpandParsersym.TK_INTEGER_LITERAL);
operands.add(XpandParsersym.TK_REAL_LITERAL);
operands.add(XpandParsersym.TK_STRING_LITERAL);
}
private final Map<Integer, Integer> blockTokens = new HashMap<Integer, Integer>();
{
blockTokens.put(XpandParsersym.TK_LPAREN, XpandParsersym.TK_RPAREN);
blockTokens.put(XpandParsersym.TK_LBRACE, XpandParsersym.TK_RBRACE); // XXX braces as block tokens?
}
/**
* is public only for testing purposes
*/
public String[] computePrefixAndTargetExpression(final String str) {
final ReverseScanner scanner = new ReverseScanner(str);
String prefix = "";
IToken t = scanner.previousToken();
if (t != null) {
// prefix consists of identifier parts
if (!Character.isWhitespace(str.charAt(str.length() - 1))) {
if (Character.isJavaIdentifierStart(t.toString().charAt(0))) {
prefix = t.toString();
t = scanner.previousToken(); // go to operator
}
}
final int exprEnd = scanner.getOffset();
// if t is a dot there is a target expression
if (t != null && (t.getKind() == XpandParsersym.TK_DOT || t.getKind() == XpandParsersym.TK_ARROW)) {
boolean lastWasOperator = true;
boolean stop = false;
while (!stop && (t = scanner.previousToken()) != null) {
if (isOperand(t)) {
if (lastWasOperator) {
lastWasOperator = false;
} else { // two operands in sequence -> stopper!
scanner.nextToken();
stop = true;
}
} else if (t.getKind() == XpandParsersym.TK_DOT || t.getKind() == XpandParsersym.TK_ARROW) {
if (!lastWasOperator) {
lastWasOperator = true;
} else {
// errorneous expression
return new String[] { prefix, null };
}
} else if (isBlockCloser(t) && lastWasOperator) {
lastWasOperator = false;
final Stack<IToken> s = new Stack<IToken>();
s.push(t);
while (!s.isEmpty()) {
final IToken temp = scanner.previousToken();
if (temp == null) {
return new String[] { prefix, null };
}
if (temp.getKind() == t.getKind()) {
s.push(temp);
} else if (isOpposite(temp, t)) {
s.pop();
}
}
if (t.getKind() == XpandParsersym.TK_RPAREN) {
// we have an unambigous syntax here
// a.method(with.param)
// but also
// anIdentifier
// (another.parenthesized.expressions)
final IToken temp = scanner.previousToken();
if (!isMethodName(temp)) {
scanner.nextToken();
}
}
} else {
scanner.nextToken(); // go one forward
stop = true;
}
}
return new String[] { prefix, str.substring(scanner.getOffset(), exprEnd).trim() };
}
}
return new String[] { prefix, null };
}
private boolean isMethodName(final IToken temp) {
return methodNames.contains(temp.getKind());
}
private boolean isOpposite(final IToken left, final IToken right) {
final Integer temp = blockTokens.get(left.getKind());
return (temp != null) && (right.getKind() == temp.intValue());
}
private boolean isBlockCloser(final IToken t) {
return blockTokens.values().contains(t.getKind());
}
private boolean isOperand(final IToken t) {
return operands.contains(t.getKind());
}
}
}