org.eclipse.ocl.internal.helper.OCLSyntaxHelper Maven / Gradle / Ivy
/**
*
*
* Copyright (c) 2002, 2009 IBM Corporation, Zeligsoft Inc., 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:
* IBM - Initial API and implementation
* E.D.Willink - Refactoring to support extensibility and flexible error handling
* Zeligsoft - Bug 207365
* E.D.Willink - Bug 259818
*
*
*
* $Id: OCLSyntaxHelper.java,v 1.19 2010/12/15 17:33:43 ewillink Exp $
*/
package org.eclipse.ocl.internal.helper;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import lpg.runtime.IPrsStream;
import lpg.runtime.IToken;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.ocl.Environment;
import org.eclipse.ocl.EnvironmentFactory;
import org.eclipse.ocl.ParserException;
import org.eclipse.ocl.SemanticException;
import org.eclipse.ocl.cst.ClassifierContextDeclCS;
import org.eclipse.ocl.cst.InvCS;
import org.eclipse.ocl.cst.InvOrDefCS;
import org.eclipse.ocl.cst.OCLExpressionCS;
import org.eclipse.ocl.cst.PackageDeclarationCS;
import org.eclipse.ocl.expressions.AssociationClassCallExp;
import org.eclipse.ocl.expressions.BooleanLiteralExp;
import org.eclipse.ocl.expressions.CollectionItem;
import org.eclipse.ocl.expressions.CollectionLiteralExp;
import org.eclipse.ocl.expressions.CollectionRange;
import org.eclipse.ocl.expressions.EnumLiteralExp;
import org.eclipse.ocl.expressions.IfExp;
import org.eclipse.ocl.expressions.IntegerLiteralExp;
import org.eclipse.ocl.expressions.InvalidLiteralExp;
import org.eclipse.ocl.expressions.IterateExp;
import org.eclipse.ocl.expressions.IteratorExp;
import org.eclipse.ocl.expressions.LetExp;
import org.eclipse.ocl.expressions.MessageExp;
import org.eclipse.ocl.expressions.NullLiteralExp;
import org.eclipse.ocl.expressions.OCLExpression;
import org.eclipse.ocl.expressions.OperationCallExp;
import org.eclipse.ocl.expressions.PropertyCallExp;
import org.eclipse.ocl.expressions.RealLiteralExp;
import org.eclipse.ocl.expressions.StateExp;
import org.eclipse.ocl.expressions.StringLiteralExp;
import org.eclipse.ocl.expressions.TupleLiteralExp;
import org.eclipse.ocl.expressions.TupleLiteralPart;
import org.eclipse.ocl.expressions.TypeExp;
import org.eclipse.ocl.expressions.UnlimitedNaturalLiteralExp;
import org.eclipse.ocl.expressions.UnspecifiedValueExp;
import org.eclipse.ocl.expressions.Variable;
import org.eclipse.ocl.expressions.VariableExp;
import org.eclipse.ocl.helper.Choice;
import org.eclipse.ocl.helper.ChoiceKind;
import org.eclipse.ocl.helper.ConstraintKind;
import org.eclipse.ocl.lpg.ProblemHandler;
import org.eclipse.ocl.parser.AbstractOCLAnalyzer;
import org.eclipse.ocl.parser.OCLAnalyzer;
import org.eclipse.ocl.parser.OCLParsersym;
import org.eclipse.ocl.types.CollectionType;
import org.eclipse.ocl.types.OCLStandardLibrary;
import org.eclipse.ocl.util.OCLUtil;
import org.eclipse.ocl.util.ObjectUtil;
import org.eclipse.ocl.util.TypeUtil;
import org.eclipse.ocl.util.UnicodeSupport;
import org.eclipse.ocl.utilities.ExpressionInOCL;
import org.eclipse.ocl.utilities.PredefinedType;
import org.eclipse.ocl.utilities.UMLReflection;
import org.eclipse.ocl.utilities.Visitor;
/**
* Engine for computation of possible syntax completions at a point in the
* an OCL expression.
*
* @author Yasser Lulu
* @author Christian W. Damus (cdamus)
*/
public class OCLSyntaxHelper
implements org.eclipse.ocl.helper.OCLSyntaxHelper {
// codes indicating the token before the cursor when completion invoked
private static final int NONE = -1;
private static final int DOT = 0;
private static final int ARROW = 1;
private static final int DOUBLE_COLON = 2;
private static final int CARET = 3;
private static final int OCL_IS_IN_STATE = 4;
private static final Set INFIX_OPERATORS;
static {
INFIX_OPERATORS = new java.util.HashSet();
INFIX_OPERATORS.add(PredefinedType.MINUS_NAME);
INFIX_OPERATORS.add(PredefinedType.PLUS_NAME);
INFIX_OPERATORS.add(PredefinedType.DIVIDE_NAME);
INFIX_OPERATORS.add(PredefinedType.TIMES_NAME);
INFIX_OPERATORS.add(PredefinedType.LESS_THAN_NAME);
INFIX_OPERATORS.add(PredefinedType.LESS_THAN_EQUAL_NAME);
INFIX_OPERATORS.add(PredefinedType.GREATER_THAN_NAME);
INFIX_OPERATORS.add(PredefinedType.GREATER_THAN_EQUAL_NAME);
INFIX_OPERATORS.add(PredefinedType.EQUAL_NAME);
INFIX_OPERATORS.add(PredefinedType.NOT_EQUAL_NAME);
}
private static final Set ANY_TYPE_OPERATIONS;
static {
ANY_TYPE_OPERATIONS = new java.util.HashSet();
ANY_TYPE_OPERATIONS.add(PredefinedType.EQUAL_NAME);
ANY_TYPE_OPERATIONS.add(PredefinedType.NOT_EQUAL_NAME);
ANY_TYPE_OPERATIONS.add(PredefinedType.OCL_AS_TYPE_NAME);
ANY_TYPE_OPERATIONS.add(PredefinedType.OCL_IS_KIND_OF_NAME);
ANY_TYPE_OPERATIONS.add(PredefinedType.OCL_IS_TYPE_OF_NAME);
ANY_TYPE_OPERATIONS.add(PredefinedType.OCL_IS_UNDEFINED_NAME);
ANY_TYPE_OPERATIONS.add(PredefinedType.OCL_IS_INVALID_NAME);
ANY_TYPE_OPERATIONS.add(PredefinedType.OCL_IS_NEW_NAME);
ANY_TYPE_OPERATIONS.add(PredefinedType.OCL_IS_IN_STATE_NAME);
ANY_TYPE_OPERATIONS.add(PredefinedType.LESS_THAN_NAME);
ANY_TYPE_OPERATIONS.add(PredefinedType.GREATER_THAN_NAME);
ANY_TYPE_OPERATIONS.add(PredefinedType.LESS_THAN_EQUAL_NAME);
ANY_TYPE_OPERATIONS.add(PredefinedType.GREATER_THAN_EQUAL_NAME);
}
private int syntaxHelpStringSuffix;
private Environment environment;
private OCLStandardLibrary stdlib;
private UMLReflection uml;
public OCLSyntaxHelper(Environment env) {
environment = env;
stdlib = env.getOCLStandardLibrary();
uml = env.getUMLReflection();
}
/**
* return collection applicable operations (syntax help)
* @return List
*/
private List getCollectionChoices(C ct) {
List result = new ArrayList();
result.addAll(getOperationChoices(ct));
return result;
}
/**
* return OclAny applicable operations (syntax help)
*
* @return a list of {@link Choice}s
*/
private List getAnyChoices() {
return getOperationChoices(stdlib.getOclAny());
}
/**
* Creates a list of choices representing the operations of the specified
* owner type.
*
* @param owner the operation owner type
*
* @return the operation {@link Choice}s
*/
private List getOperationChoices(C owner) {
List result = getOperationChoices(owner, TypeUtil.getOperations(environment, owner));
if (owner instanceof CollectionType, ?>) {
@SuppressWarnings("unchecked")
CollectionType collType = (CollectionType) owner;
result.addAll(getOperationChoices(owner, collType.oclIterators()));
}
return result;
}
private List getOperationChoices(C owner, List operations) {
List result = new java.util.ArrayList(operations.size());
for (O operation : operations) {
if ((syntaxHelpStringSuffix == CARET) || isQuery(operation)) {
operation = TypeUtil.resolveGenericSignature(
environment, owner, operation);
Choice choice = new ChoiceImpl(
uml.getName(operation),
getOperationDescription(operation),
ChoiceKind.OPERATION,
operation);
ObjectUtil.dispose(operation);
result.add(choice);
}
}
return result;
}
private List getReceptionChoices(C owner) {
List signals = uml.getSignals(owner);
List result = new java.util.ArrayList(signals.size());
for (C signal : signals) {
Choice choice = new ChoiceImpl(
uml.getName(signal),
getSignalDescription(signal),
ChoiceKind.SIGNAL,
signal);
result.add(choice);
}
return result;
}
/**
* Determines whether an operation is a query, if this is a user-model
* syntax helper.
*
* @param operation an operation
* @return true
if either we are a metamodel helper or
* the user-model operation is a query; false
, otherwise
*/
private boolean isQuery(O operation) {
return uml.isQuery(operation);
}
/**
* Constructs the string description of the specified operation.
*
* @param operation an operation
*
* @return its string description
*/
private String getOperationDescription(O operation) {
StringBuffer result = new StringBuffer();
result.append(uml.getName(operation));
result.append('(');
for (Iterator iter = uml.getParameters(operation).iterator(); iter.hasNext();) {
PM next = iter.next();
result.append(uml.getName(next));
if (TypeUtil.resolveType(environment, uml.getOCLType(next)) != null) {
result.append(": "); //$NON-NLS-1$
result.append(getDescription(next));
}
if (iter.hasNext()) {
result.append(", "); //$NON-NLS-1$
}
}
C operType = TypeUtil.resolveType(environment, uml.getOCLType(operation));
if (operType == null) {
result.append(')');
} else {
result.append(") : "); //$NON-NLS-1$
result.append(uml.getName(operType));
}
return result.toString();
}
/**
* Constructs the string description of the specified signal.
*
* @param signal a signal
*
* @return its string description
*/
private String getSignalDescription(C signal) {
StringBuffer result = new StringBuffer();
// not translatable; as indicated by the OCL spec
result.append("<> "); //$NON-NLS-1$
result.append(uml.getName(signal));
result.append('(');
for (Iterator iter = uml.getAttributes(signal).iterator();
iter.hasNext();) {
P next = iter.next();
result.append(uml.getName(next));
if (TypeUtil.resolveType(environment, uml.getOCLType(next)) != null) {
result.append(": "); //$NON-NLS-1$
result.append(getDescription(next));
}
if (iter.hasNext()) {
result.append(", "); //$NON-NLS-1$
}
}
result.append(')');
return result.toString();
}
protected class ASTVisitor
implements Visitor, C, O, P, EL, PM, S, COA, SSA, CT> {
private final int completionPosition;
private final String text;
private final ConstraintKind constraintType;
/**
* Initializes me with the position at which we are trying to find
* completions and the text that we are completing.
*
* @param text the text to complete
* @param position the completion position
* @param constraintType the type of constraint that we are completing
*/
protected ASTVisitor(String text, int position, ConstraintKind constraintType) {
this.text = text;
completionPosition = position;
this.constraintType = constraintType;
}
protected ConstraintKind getConstraintType() {
return constraintType;
}
public List visitOperationCallExp(OperationCallExp exp) {
if (exp.getEndPosition() == completionPosition) {
// we may be looking at a binary operation (such as "=" or "and")
// in which case we need to see whether the last argument
// is actually what needs to be completed
List> args = exp.getArgument();
if (!args.isEmpty()) {
OCLExpression last = args.get(args.size() - 1);
if (last.getEndPosition() == completionPosition) {
// complete this argument, instead
return last.accept(this);
}
}
}
return getChoices(exp, constraintType);
}
public List visitVariableExp(VariableExp variableexp) {
return getChoices(variableexp, constraintType);
}
public List visitPropertyCallExp(PropertyCallExp propertycallexp) {
return getChoices(propertycallexp, constraintType);
}
public List visitAssociationClassCallExp(AssociationClassCallExp exp) {
return getChoices(exp, constraintType);
}
public List visitVariable(Variable variabledeclaration) {
return Collections.emptyList();
}
public List visitIfExp(IfExp exp) {
int lastCharPos = UnicodeSupport.shiftCodePointOffsetBy(
text, exp.getEndPosition(), -1);
if (text.charAt(lastCharPos) == ')') { // known BMP code point
return getChoices(exp, constraintType);
}
return Collections.emptyList();
}
public List visitTypeExp(TypeExp typeExp) {
return getOperationChoices(typeExp.getType());
}
public List visitMessageExp(MessageExp m) {
return getChoices(m, constraintType);
}
public List visitUnspecifiedValueExp(
UnspecifiedValueExp unspecifiedvalueexp) {
return getChoices(unspecifiedvalueexp, constraintType);
}
public List visitIntegerLiteralExp(IntegerLiteralExp exp) {
return getChoices(exp, constraintType);
}
public List visitUnlimitedNaturalLiteralExp(
UnlimitedNaturalLiteralExp exp) {
return getChoices(exp, constraintType);
}
public List visitRealLiteralExp(RealLiteralExp exp) {
return getChoices(exp, constraintType);
}
public List visitStringLiteralExp(StringLiteralExp exp) {
return getChoices(exp, constraintType);
}
public List visitBooleanLiteralExp(BooleanLiteralExp exp) {
return getChoices(exp, constraintType);
}
public List visitNullLiteralExp(NullLiteralExp il) {
return getAnyChoices();
}
public List visitInvalidLiteralExp(InvalidLiteralExp il) {
return getAnyChoices();
}
public List visitTupleLiteralExp(TupleLiteralExp tupleliteralexp) {
return getChoices(tupleliteralexp, constraintType);
}
public List visitTupleLiteralPart(TupleLiteralPart tp) {
return Collections.emptyList();
}
public List visitLetExp(LetExp letexp) {
return getChoices(letexp.getType(), constraintType);
}
public List visitEnumLiteralExp(EnumLiteralExp enumliteralexp) {
return getChoices(enumliteralexp, constraintType);
}
public List visitStateExp(StateExp s) {
return getChoices(s, constraintType);
}
public List visitCollectionLiteralExp(CollectionLiteralExp exp) {
return getChoices(exp, constraintType);
}
public List visitCollectionItem(CollectionItem item) {
return item.getItem().accept(this);
}
public List visitCollectionRange(CollectionRange range) {
return range.getLast().accept(this);
}
public List visitIteratorExp(IteratorExp exp) {
if (exp.getEndPosition() == completionPosition) {
// return completion on the entire expression
return getChoices(exp.getType(), constraintType);
}
// otherwise, we are completing something within the expression body
return exp.getBody().accept(this);
}
public List visitIterateExp(IterateExp exp) {
if (exp.getEndPosition() == completionPosition) {
// the result type of an iterate expression is the type of the
// accumulator variable
return getChoices(exp.getType(), constraintType);
}
// otherwise, we are completing something within the expression body
return exp.getBody().accept(this);
}
public List visitExpressionInOCL(
ExpressionInOCL expression) {
return Collections.emptyList();
}
public List visitConstraint(CT constraint) {
return Collections.emptyList();
}
} //ASTVisitor class
/**
* returns the choices list of structural features for the passed eclass
* @param eClass the eclass to get features from
* @return List oclchoices list for structural features
*/
protected List getPropertyChoices(C eClass) {
List result = new ArrayList();
Set properties = new HashSet
(TypeUtil.getAttributes(environment, eClass));
for (P property : properties) {
result.add(createChoice(
uml.getName(property),
getDescription(property),
ChoiceKind.PROPERTY,
property));
// handle association class ends
C assocClass = uml.getAssociationClass(property);
if (assocClass != null) {
String name = uml.getName(assocClass);
if (name != null) {
name = initialLower(name);
Choice choice = createChoice(
name,
uml.getName(assocClass),
ChoiceKind.ASSOCIATION_CLASS,
assocClass);
// don't repeat in case we have multiple ends of this
// association class
if (!result.contains(choice)) {
result.add(choice);
}
}
}
}
return result;
}
protected Choice createChoice(String name, String description, ChoiceKind kind, Object element) {
return new ChoiceImpl(name, description, kind, element);
}
/**
* Gets the name of a named elem
ent with its initial character
* in lower case.
*
* @param elem a named element
* @return the element's name, with an initial lower case letter
*/
public static String initialLower(String name) {
StringBuffer result = new StringBuffer(name);
if (result.length() > 0) {
UnicodeSupport.setCodePointAt(
result,
0,
UnicodeSupport.toLowerCase(UnicodeSupport.codePointAt(result, 0)));
}
return result.toString();
}
/**
* returns the syntax help choices applicable for the passed OCLExpression
*
* @param expression the AST node to check and return relevant syntax
* help choices for.
* @param constraintType the type of constraint that we are completing
*
* @return syntax help choices for user (list of {@link Choice}s;
* could be empty
*/
protected List getChoices(OCLExpression expression, ConstraintKind constraintType) {
return getChoices(expression.getType(), constraintType);
}
private List getChoices(C type, ConstraintKind constraintType) {
List rawChoices;
if (type instanceof CollectionType, ?>) {
@SuppressWarnings("unchecked")
CollectionType ct = (CollectionType) type;
if (syntaxHelpStringSuffix == ARROW) {
rawChoices = getCollectionChoices(type);
} else if (syntaxHelpStringSuffix == DOT) {
// get features of element type (flattened)
C elementType = ct.getElementType();
while (elementType instanceof CollectionType, ?>) {
elementType = ct.getElementType();
}
return getChoices(ct.getElementType(), constraintType);
} else {
return Collections.emptyList();
}
} else {
if (syntaxHelpStringSuffix == ARROW) {
@SuppressWarnings("unchecked")
C setType = (C) environment.getOCLFactory().createSetType(type);
return getChoices(setType, constraintType);
} else if (syntaxHelpStringSuffix == DOT) {
rawChoices = getPropertyChoices(type);
rawChoices.addAll(getOperationChoices(type));
} else if (syntaxHelpStringSuffix == CARET) {
rawChoices = getOperationChoices(type);
rawChoices.addAll(getReceptionChoices(type));
} else {
return Collections.emptyList();
}
}
return filter(rawChoices, constraintType);
}
/**
* Filters the specified choices to remove choices not valid in the
* specified type of constraint.
*
* @param choices the choices to filter
* @param constraintType the type of constraint that we are completing
*
* @return the filtered choices
*/
private List filter(List choices, ConstraintKind constraintType) {
List result = choices;
for (Iterator iter = choices.iterator(); iter.hasNext();) {
Choice next = iter.next();
switch (constraintType) {
case INVARIANT:
case PRECONDITION:
case BODYCONDITION:
// only postconditions may include oclIsNew()
if (PredefinedType.OCL_IS_NEW_NAME.equals(next.getName())) {
iter.remove();
break;
}
// intentional fall-through
default:
if (INFIX_OPERATORS.contains(next.getName())) {
iter.remove();
} else if (syntaxHelpStringSuffix == CARET) {
if (ANY_TYPE_OPERATIONS.contains(next.getName())) {
iter.remove();
}
}
break;
}
}
return result;
}
/**
* Returns the valid subcontents of a path string that has double-colons in
* it.
*
* @param path the (partial) path name
* @param env the current OCL environment
*
* @throws Exception if anything at all goes awry
*
* @return a list of {@link Choice}s, possibly empty
*/
private List getPathChoices(List path) throws Exception {
if (!path.isEmpty()) {
PK ePackage = environment.lookupPackage(path);
if (ePackage != null) {
return getPackageChoices(ePackage);
} else {
//could it be an enum?
C type = environment.lookupClassifier(path);
if (uml.isEnumeration(type)) {
return getEnumerationChoices(type);
}
}
}
return Collections.emptyList();
}
/**
* Returns the valid subcontents of a path string that has double-colons in
* it, interpreted as a state of the specified owner classifier.
*
* @param owner the owner of the state expression to complete
* @param pathPrefix the path prefix to look up completions for
* @param env the current OCL environment
*
* @throws Exception if anything at all goes awry
*
* @return a list of {@link Choice}s, possibly empty
*/
private List getStateChoices(C owner, List pathPrefix) throws Exception {
List states = environment.getStates(owner, pathPrefix);
List result;
if (states.isEmpty()) {
result = Collections.emptyList();
} else {
Set choices = new java.util.LinkedHashSet();
for (S next : states) {
choices.add(new ChoiceImpl(
uml.getName(next),
uml.getName(stdlib.getState()), // == "State"
ChoiceKind.STATE,
next));
}
result = new java.util.ArrayList(choices);
}
return result;
}
private List getEnumerationChoices(C type) {
List result = new ArrayList();
for (EL literal : uml.getEnumerationLiterals(type)) {
result.add(new ChoiceImpl(
uml.getName(literal),
getDescription(literal),
ChoiceKind.ENUMERATION_LITERAL,
literal));
}
return result;
}
protected Environment getEnvironment() {
return environment;
}
/**
* builds and returns a list of Choice that represent the directly
* contained classifiers.
*
* @param ePackage
* teh package to explore and return appropriate classifiers
* @return List the list of Choice object to return to the caller.
*/
private List getPackageChoices(PK ePackage) {
Set choices = new java.util.LinkedHashSet(); // retain order
for (PK nested : uml.getNestedPackages(ePackage)) {
choices.add(new ChoiceImpl(
uml.getName(nested),
getDescription(nested),
ChoiceKind.PACKAGE,
nested));
}
for (C classifier : uml.getClassifiers(ePackage)) {
choices.add(new ChoiceImpl(
uml.getName(classifier),
getDescription(classifier),
ChoiceKind.TYPE,
classifier));
}
return new ArrayList(choices);
}
private List getVariableChoices(
Environment env,
String txt, ConstraintKind constraintType) {
Set choices = new java.util.LinkedHashSet(); // retain order
// add features of the context classifier. If the context is an
// operation, then the parameters were already added to the
// environment
int oldSuffix = syntaxHelpStringSuffix;
syntaxHelpStringSuffix = DOT;
choices.addAll(getChoices(env.getSelfVariable().getType(), constraintType));
syntaxHelpStringSuffix = oldSuffix;
try {
List tokens = tokenize(txt);
getVariables(env, txt, tokens.listIterator(tokens.size()));
} catch (Exception e) {
// maybe we found a few variables. Ignore the exception
}
for (Variable var : env.getVariables()) {
choices.add(new ChoiceImpl(
var.getName(),
uml.getName(var.getType()),
ChoiceKind.VARIABLE,
var));
}
return filter(new ArrayList(choices), constraintType);
}
/**
* Gets choices for completion of partial names starting at a specified
* position after a ".", "->", or "::".
*
* @param text the text to complete
* @param env the OCL environment
* @param constraintType the kind of constraint for which to get partial
* name suggestions
* @param position the position at which the partial name starts
*
* @return the appropriate choices
*/
private List getPartialNameChoices(String text,
ConstraintKind constraintType, int position) {
// get raw choices
List result = getSyntaxHelp(constraintType, text.substring(0, position));
// filter out choices that don't start with the partial text
String partial = text.substring(position).trim();
int length = partial.length();
for (Iterator iter = result.iterator(); iter.hasNext();) {
Choice next = iter.next();
String name = next.getName();
if ((name == null)
|| !name.regionMatches(true, 0, partial, 0, length)) {
iter.remove();
}
}
return result;
}
/**
* Gets the token at the specified index in the list of tokens
* represented by the given text. Token indices count from zero, with
* negative indices counting backwards from the last token (-1 is the last,
* -2 the next-to-last, etc.).
*
* @param text the text to tokenize
* @param tokenIndex the token index to look at (negative indices count
* backwards from the end)
*
* @return the token at the index, or null
* if there is no token at the specified index
*/
private IToken tokenAt(String text, int tokenIndex) {
IToken result = null;
List tokens = tokenize(text);
if (tokenIndex < 0) {
tokenIndex = tokens.size() + tokenIndex;
}
if ((tokenIndex >= 0) && (tokenIndex < tokens.size())) {
result = tokens.get(tokenIndex);
}
return result;
}
/**
* Converts the specified OCL text to a list of {@link IToken}s.
*
* @param text the text to tokenize
* @return the corresponding {@link OCLToken}s
*/
private List tokenize(String text) {
OCLAnalyzer analyzer =
environment.getFactory().createOCLAnalyzer(environment, text);
return tokenize(analyzer);
}
private List tokenize(OCLAnalyzer analyzer) {
IToken token = null;
List result = new ArrayList();
IPrsStream parser = analyzer.getAbstractParser().getIPrsStream();
for (;;) {
try {
token = parser.getIToken(parser.getToken());
if (token.getKind() == OCLParsersym.TK_EOF_TOKEN) {
break;
} else {
result.add(token);
}
} catch (Exception e) {
// got as many as we can. Ignore the exception
break;
}
}
return result;
}
/**
* Gets the most appropriate description available of the specified
* named element.
*
* @param namedElement a named element presented to the user as a choice
* @return the most appropriate description for the element
*/
protected String getDescription(Object namedElement) {
return uml.getDescription(namedElement);
}
/**
* Gets syntax help choices for the specified context
* classifier or operation.
*
* @param constraintType the kind of constraint for which to get completion
* suggestions
* @param txt the string we got from client that contains the ocl expression
* @param context the context classifier or operation to use for the ocl
* expression parsing
* @return a list of {@link Choice}s representing the syntax help choices
* for the user; could be empty
*/
public List getSyntaxHelp(ConstraintKind constraintType, String txt) {
OCLExpression expression;
List result;
try {
txt = txt.trim();//just to be sure
if (txt.endsWith(HelperUtil.DOT)) {
syntaxHelpStringSuffix = DOT;
int position = txt.lastIndexOf(HelperUtil.DOT); // known BMP code point
expression = getOCLExpression(environment, position, txt, constraintType);
result = expression.accept(createASTVisitor(constraintType, txt, position));
disposeAll(expression);
} else if (txt.endsWith(HelperUtil.ARROW)) {
syntaxHelpStringSuffix = ARROW;
int position = txt.lastIndexOf(HelperUtil.ARROW); // known BMP code points
expression = getOCLExpression(environment, position, txt, constraintType);
result = expression.accept(createASTVisitor(constraintType, txt, position));
disposeAll(expression);
} else if (txt.endsWith(HelperUtil.CARET)) { // known BMP code points
syntaxHelpStringSuffix = CARET;
int position;
if (txt.endsWith(HelperUtil.DOUBLE_CARET)) { // known BMP code points
position = txt.length() - 2;
} else {
position = txt.length() - 1;
}
expression = getOCLExpression(environment, position, txt, constraintType);
result = expression.accept(createASTVisitor(constraintType, txt, position));
disposeAll(expression);
} else if (txt.endsWith(HelperUtil.DOUBLE_COLON)) {
syntaxHelpStringSuffix = NONE;
int position = txt.length() - 2;
List pathName = new java.util.ArrayList();
// look backwards past the path name to see whether there is an
// "oclIsInState(" before it
OCLAnalyzer analyzer =
environment.getFactory().createOCLAnalyzer(environment, txt);
IPrsStream parser = analyzer.getAbstractParser().getIPrsStream();
List tokens = tokenize(analyzer);
ListIterator iter = tokens.listIterator(tokens.size());
while (iter.hasPrevious() && (syntaxHelpStringSuffix == NONE)) {
IToken prev = iter.previous();
switch (prev.getKind()) {
case OCLParsersym.TK_LPAREN:
if (iter.hasPrevious()) {
prev = iter.previous();
if (isOclIsInState(prev)) {
syntaxHelpStringSuffix = OCL_IS_IN_STATE;
position = prev.getStartOffset();
break;
}
}
// a different operation? This is a normal path name
syntaxHelpStringSuffix = DOUBLE_COLON;
break;
case OCLParsersym.TK_IDENTIFIER:
pathName.add(0, parser.getTokenText(prev.getTokenIndex()));
break;
case OCLParsersym.TK_COLONCOLON:
// these are part of the name
break;
default:
// did not find an "oclIsInState(". This is a normal path name
syntaxHelpStringSuffix = DOUBLE_COLON;
break;
}
}
if (syntaxHelpStringSuffix == OCL_IS_IN_STATE) {
expression = getOCLExpression(
environment,
txt.lastIndexOf(HelperUtil.DOT, position), // known BMP code point
txt, constraintType);
result = getStateChoices(expression.getType(), pathName);
disposeAll(expression);
} else {
// path choices are not affected by the variables in the operation
// namespace (e.g., parameters)
result = getPathChoices(pathName);
}
} else if (txt.endsWith("(") // known BMP code point //$NON-NLS-1$
&& isOclIsInState(tokenAt(txt, -2))) {
syntaxHelpStringSuffix = OCL_IS_IN_STATE;
expression = getOCLExpression(
environment,
txt.lastIndexOf(HelperUtil.DOT), // known BMP code point
txt, constraintType);
List empty = Collections.emptyList();
result = getStateChoices(expression.getType(), empty);
disposeAll(expression);
} else {
OCLAnalyzer parser =
environment.getFactory().createOCLAnalyzer(environment, txt);
// see whether we can complete a partial name
List tokens = tokenize(parser);
if (tokens.size() > 2) {
IToken last = tokens.get(tokens.size() - 1);
IToken prev = tokens.get(tokens.size() - 2);
if (AbstractOCLAnalyzer.isIdentifierOrKeyword(last.getKind())) {
switch (prev.getKind()) {
case OCLParsersym.TK_ARROW:
case OCLParsersym.TK_DOT:
case OCLParsersym.TK_COLONCOLON:
return getPartialNameChoices(
txt, constraintType,
prev.getEndOffset() + 1); // + 1 because end is inclusive
}
}
}
// see whether we can complete a partial path
if (tokens.size() > 1) {
List pathNames = parseTokensPathNameCS(parser, tokens);
if (!pathNames.isEmpty()) {
List choices = getPartialNameChoices(txt,
constraintType,
txt.lastIndexOf(HelperUtil.DOUBLE_COLON) + HelperUtil.DOUBLE_COLON.length());
if (!choices.isEmpty()) {
return choices;
}
}
}
if ((tokens.size() > 0) &&
(tokens.get(tokens.size() - 1).getKind() == OCLParsersym.TK_IDENTIFIER)) {
List choices = getPartialNameChoices(txt, constraintType,
tokens.get(tokens.size() - 1).getStartOffset());
if (!choices.isEmpty()) {
return choices;
}
}
// no partial names to complete: go for variables
syntaxHelpStringSuffix = NONE;
// create a copy of the environment, to embellish it without
// disrupting the caller's environment
Environment copy =
copyEnvironment(environment);
result = getVariableChoices(copy, txt, constraintType);
}
} catch (Exception e) {
// didn't work? Just try some simple variable choices, then
Environment copy =
copyEnvironment(environment);
result = getVariableChoices(copy, txt, constraintType);
}
return result;
}
protected ASTVisitor createASTVisitor(ConstraintKind constraintType,
String txt, int position) {
return new ASTVisitor(txt, position, constraintType);
}
protected boolean isOclIsInState(IToken token) {
if (token == null)
return false;
if (token.getKind() != OCLParsersym.TK_IDENTIFIER)
return false;
return PredefinedType.OCL_IS_IN_STATE_NAME.equals(token.toString());
}
/**
* Disposes not only the specified object but all of the objects
* in the content tree that contains it, from the root down.
*
* @param object an object to dispose utterly
*
* @since 1.2
*/
static void disposeAll(EObject object) {
ObjectUtil.dispose(EcoreUtil.getRootContainer(object));
}
/**
* Attempts to parse a pathname starting from the end of the
* given token list. For a pathname to be parsed by this method,
* the pathname must be of the form: IDENTIFIER ( :: IDENTIFIER )+
* The last token must be an IDENTIFIER.
*
* @param parser
* @param tokens
* @return parsed pathname
*/
private List parseTokensPathNameCS(
OCLAnalyzer analyzer,
List tokens) {
IPrsStream parser = analyzer.getAbstractParser().getIPrsStream();
ArrayList path = new ArrayList();
IToken token;
int index = tokens.size() - 1;
boolean doubleColon = false;
while (index >= 0) {
token = tokens.get(index--);
if (doubleColon && token.getKind() == OCLParsersym.TK_COLONCOLON) {
// do nothing
} else if (!doubleColon && token.getKind() == OCLParsersym.TK_IDENTIFIER) {
path.add(0, parser.getTokenText(token.getTokenIndex()));
} else {
break;
}
doubleColon = !doubleColon;
}
return path;
}
private OCLExpression getOCLExpression(
Environment env,
int index, String txt,
ConstraintKind constraintType) throws Exception {
// don't pollute the caller's environment with variables
env = copyEnvironment(env);
int start = 0;
int end = index;
String newTxt = txt.substring(start, end);
OCLAnalyzer analyzer =
environment.getFactory().createOCLAnalyzer(env, newTxt);
PackageDeclarationCS packageContext = null;
OCLExpressionCS cst = null;
// Start backing up from the end a token at a time to find the
// right-most subexpression that parses
// initialize the token list
analyzer.getLexer().getILexStream().reset();
List tokens = tokenize(analyzer);
ListIterator it = tokens.listIterator(tokens.size());
final String preamble = "context foo inv: "; //$NON-NLS-1$
final int offset = -preamble.length();
int[] balance = {0};
IToken token; // possible start token of a complete sub-expression
IToken bdry; // peek back one more looking for a boundary token
for (int i = 0; it.hasPrevious() && i < 100; i++) {
token = it.previous();
// as long as we have an unmatched right paren/bracket/brace, there
// is no point in trying to parse anything. Even if the previous
// token were the matching left, the pair cannot be parsed in
// isolation
while ((updateBalance(balance, token)) > 0 && it.hasPrevious()) {
token = it.previous();
}
boolean tryParse = true;
if (isBoundaryToken(token)) {
// can't proceed any farther left looking for a sub-expression
// to parse
break;
}
// if we're at the beginning of input, try to parse
if (it.hasPrevious()) {
// otherwise, see whether we are at a syntactic boundary
bdry = it.previous();
int oldBalance = balance[0];
// can't go farther left than a mismatched left paren/bracket/brace
if (isBoundaryToken(bdry) || (updateBalance(balance, bdry) < 0)) {
tryParse = true;
} else {
tryParse = false;
it.next(); // push this non-boundary token back
// restore the balance if it changed
balance[0] = oldBalance;
}
}
if (tryParse) {
try {
start = token.getStartOffset();
newTxt = preamble + txt.substring(start, end);
analyzer = environment.getFactory().createOCLAnalyzer(env, newTxt);
// offset the parser left by the length of our preamble text
// and right by the number of characters on the left side
// that we are ignoring
analyzer.setCharacterOffset(offset + start);
packageContext = (PackageDeclarationCS) analyzer.parseConcreteSyntax();
OCLUtil.checkForErrors(analyzer.getEnvironment().getProblemHandler());
ClassifierContextDeclCS context = (ClassifierContextDeclCS)
packageContext.getContextDecls().get(0);
EList constraints = context.getConstraints();
cst = ((InvCS) constraints.get(constraints.size()-1)).getExpressionCS();
break;
} catch (ParserException ignore) {
// continue to try another backtrack, unless we are at a
// mismatched left token, in which case we give up
if (balance[0] < 0) {
break;
}
}
}
}
if (cst != null && start >= 0) {
// populate the environment with variables up to the CST
it.next(); // in case we stepped onto a bar or in boundary token
getVariables(env, txt.substring(0, start), it);
return analyzer.parseAST(cst, constraintType);
}
return null;
}
/**
* A token marking a boundary to the right of which the right-most
* sub-expression (which we are to complete) must lie, barring parentheses,
* brackets, or braces. These boundary tokens include all infix operations
* and other tokens such as ':', ';', ',', 'let', 'in', 'if', 'then',
* 'else', 'endif'. This boundary token and anything to the left cannot
* be considered as part of the expression that we are completing.
*
* @param token a token
* @return whether it is a boundary token
*/
private boolean isBoundaryToken(IToken token) {
switch (token.getKind()) {
case OCLParsersym.TK_COLON:
case OCLParsersym.TK_COMMA:
case OCLParsersym.TK_SEMICOLON:
case OCLParsersym.TK_BAR:
case OCLParsersym.TK_in:
case OCLParsersym.TK_let:
case OCLParsersym.TK_and:
case OCLParsersym.TK_or:
case OCLParsersym.TK_xor:
case OCLParsersym.TK_implies:
case OCLParsersym.TK_endif:
case OCLParsersym.TK_then:
case OCLParsersym.TK_else:
case OCLParsersym.TK_if:
case OCLParsersym.TK_EQUAL:
case OCLParsersym.TK_NOT_EQUAL:
case OCLParsersym.TK_GREATER:
case OCLParsersym.TK_GREATER_EQUAL:
case OCLParsersym.TK_LESS:
case OCLParsersym.TK_LESS_EQUAL:
case OCLParsersym.TK_PLUS:
case OCLParsersym.TK_MINUS:
case OCLParsersym.TK_MULTIPLY:
case OCLParsersym.TK_DIVIDE:
return true;
default:
return false;
}
}
private int updateBalance(int[] balance, IToken token) {
switch (token.getKind()) {
case OCLParsersym.TK_LPAREN:
case OCLParsersym.TK_LBRACKET:
case OCLParsersym.TK_LBRACE:
balance[0]--;
break;
case OCLParsersym.TK_RPAREN:
case OCLParsersym.TK_RBRACKET:
case OCLParsersym.TK_RBRACE:
balance[0]++;
break;
}
return balance[0];
}
private void getVariables(
Environment env,
String text, ListIterator tokens) throws ParserException {
IToken token = null;
while (tokens.hasPrevious()) {
token = tokens.previous();
if (token.getKind() == OCLParsersym.TK_BAR) {
// we are looking at a nested namespace in an iterator expression.
// Parse the iterator variable declarations into our environment
int beginIndex = 0;
while (tokens.hasPrevious()) {
// search for the left parenthesis
IToken ot = tokens.previous();
if (ot.getKind() == OCLParsersym.TK_LPAREN) {
beginIndex = ot.getEndOffset() + 1;
break;
}
}
parseIterators(
env,
text.substring(beginIndex, token.getStartOffset()));
} else if (token.getKind() == OCLParsersym.TK_in) {
// we are looking at a nested namespace in a let expression.
// Parse the iterator variable declarations into our environment
int beginIndex = 0;
while (tokens.hasPrevious()) {
// search for the "let" token
IToken ot = tokens.previous();
if (ot.getKind() == OCLParsersym.TK_let) {
beginIndex = ot.getEndOffset() + 1;
break;
}
}
parseVariable(
env,
text.substring(beginIndex, token.getStartOffset()));
}
}
}
private void parseIterators(
Environment env,
String variables) throws ParserException {
int beginIndex = 0;
EnvironmentFactory environmentFactory = environment.getFactory();
OCLAnalyzer mainAnalyzer =
environmentFactory.createOCLAnalyzer(env, variables);
if (!parseVariableDeclaration(env, mainAnalyzer)) {
IPrsStream parser = mainAnalyzer.getAbstractParser().getIPrsStream();
parser.reset();
OCLAnalyzer analyzer;
String newTxt;
IToken token = parser.getIToken(parser.getToken());
while (token.getKind() != OCLParsersym.TK_EOF_TOKEN) {
if ((token.getKind() == OCLParsersym.TK_COMMA)
|| (token.getKind() == OCLParsersym.TK_SEMICOLON)) {
newTxt = variables.substring(beginIndex, token.getStartOffset());
analyzer = environmentFactory.createOCLAnalyzer(env, newTxt);
if (parseVariableDeclaration(env, analyzer)) {
beginIndex = token.getEndOffset() + 1;
// try to the end of the expression
newTxt = variables.substring(beginIndex);
analyzer = environmentFactory.createOCLAnalyzer(env, newTxt);
if (parseVariableDeclaration(env, analyzer)) {
break;
}
}
}
token = parser.getIToken(parser.getToken());
}
}
}
private void parseVariable(
Environment env,
String variables) throws ParserException {
OCLAnalyzer analyzer =
environment.getFactory().createOCLAnalyzer(env, variables);
parseVariableDeclaration(env, analyzer);
}
private boolean parseVariableDeclaration(
Environment env,
OCLAnalyzer p) {
try {
ProblemHandler problemHandler = p.getEnvironment().getProblemHandler();
problemHandler.beginParse();
p.parseVariableDeclarationCS(true);
problemHandler.endParse();
OCLUtil.checkForErrors(problemHandler);
return true;
} catch (SemanticException e) {
// ignore: this will happen when the variable has already
// been used (i.e., if a non-containing scope that is
// farther left in the input uses the same name), but in
// this case it's safe to ignore the variable because it
// couldn't be referenced anyway
return true;
} catch (ParserException ignore) {
// unable to parse variable declaration
// return false;
}
return false;
}
/**
* Copies the initial environment, so that we may change it at will.
*
* @param env the environment to copy
* @return the copy
*/
private Environment
copyEnvironment(Environment env) {
// don't actually create a "copy", but a child environment that will
// delegate unknown variables to the parent. We cannot modify the
// parent by this means
return env.getFactory().createEnvironment(env);
}
}