All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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 element 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); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy