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

org.eclipse.ocl.parser.ValidationVisitor Maven / Gradle / Ivy

/**
 * 
 *
 * Copyright (c) 2005, 2010 IBM Corporation 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, 297541
 *   Zeligsoft - Bugs 243079, 179990
 *   Borland - Bug 179990
 *
 * 
 *
 * $Id: ValidationVisitor.java,v 1.14 2010/12/15 17:33:43 ewillink Exp $
 */

package org.eclipse.ocl.parser;

import java.util.Iterator;
import java.util.List;
import java.util.Set;

import org.eclipse.emf.ecore.ENamedElement;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.ocl.Environment;
import org.eclipse.ocl.expressions.AssociationClassCallExp;
import org.eclipse.ocl.expressions.BooleanLiteralExp;
import org.eclipse.ocl.expressions.CollectionItem;
import org.eclipse.ocl.expressions.CollectionKind;
import org.eclipse.ocl.expressions.CollectionLiteralExp;
import org.eclipse.ocl.expressions.CollectionLiteralPart;
import org.eclipse.ocl.expressions.CollectionRange;
import org.eclipse.ocl.expressions.EnumLiteralExp;
import org.eclipse.ocl.expressions.FeatureCallExp;
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.internal.l10n.OCLMessages;
import org.eclipse.ocl.lpg.BasicEnvironment;
import org.eclipse.ocl.lpg.ProblemHandler;
import org.eclipse.ocl.options.ProblemOption;
import org.eclipse.ocl.types.BagType;
import org.eclipse.ocl.types.CollectionType;
import org.eclipse.ocl.types.InvalidType;
import org.eclipse.ocl.types.OCLStandardLibrary;
import org.eclipse.ocl.types.OrderedSetType;
import org.eclipse.ocl.types.SequenceType;
import org.eclipse.ocl.types.SetType;
import org.eclipse.ocl.types.TupleType;
import org.eclipse.ocl.types.TypeType;
import org.eclipse.ocl.types.VoidType;
import org.eclipse.ocl.util.OCLStandardLibraryUtil;
import org.eclipse.ocl.util.OCLUtil;
import org.eclipse.ocl.util.TypeUtil;
import org.eclipse.ocl.utilities.AbstractVisitor;
import org.eclipse.ocl.utilities.ExpressionInOCL;
import org.eclipse.ocl.utilities.PredefinedType;
import org.eclipse.ocl.utilities.UMLReflection;
import org.eclipse.ocl.utilities.UtilitiesPackage;
import org.eclipse.ocl.utilities.Visitor;

/**
 * Checks the well-formedness rules for the expressions package
 * 
 * @author Edith Schonberg (edith)
 * @author Christian W. Damus (cdamus)
 */
public class ValidationVisitor
	implements Visitor {
	
	/**
	 * @since 3.1
	 */
	protected final Environment env;
	
    /**
	 * @since 3.1
	 */
    protected final UMLReflection uml;
	
	/**
	 * Obtains an instance of the validation visitor that validates against the
	 * specified environment, which presumably was used in parsing the OCL in
	 * the first place.
	 * 
	 * @param environment an OCL environment (must no be null)
	 * 
	 * @return a validation visitor instance for the specified environment
	 */
	public static 
	Visitor getInstance(
		Environment environment) {
		return environment.getFactory().createValidationVisitor(environment);
	}

	/**
	 * Initializes me to validate expressions in the specified environment.
	 * 
	 * @param environment the environment
	 * 
	 * @since 3.1
	 */
	public ValidationVisitor(
			Environment environment) {
		
		super();
		
		this.env = environment;
        this.uml = environment.getUMLReflection();
	}

	/**
	 * Pass a problemMessage generated for a problemObject at some validationContext to
	 * env.getErrorHandler().
	 * 
	 * @param problemObject The object being validated, may be null if unknown
	 * @param problemMessage The problem with problemObject
	 * @param validationContext Optional context of the validator, may be null
	 * 
	 * @return TRUE always, since a validation error has been reported.
	 */
	protected Boolean validatorError(Object problemObject, String problemMessage, String problemContext) {
		OCLUtil.getAdapter(env, BasicEnvironment.class).validatorError(problemMessage,
			problemContext, problemObject);
		return Boolean.TRUE;
	}

	/**
	 * Callback for an OperationCallExp visit.
	 * 
	 * Well-formedness rule: All of the arguments must conform to the parameters
	 * of the referred operation. There must be exactly as many arguments as the
	 * referred operation has parameters.
	 * 
	 * @param oc
	 *            the operation call expression
	 * @return Boolean -- true if validated
	 */
	public Boolean visitOperationCallExp(OperationCallExp oc) {

		OCLExpression source = oc.getSource();
		O oper = oc.getReferredOperation();
		int opcode = oc.getOperationCode();
		List> args = oc.getArgument();

		if (oper == null) {
			String message = OCLMessages.bind(
					OCLMessages.NullOperation_ERROR_,
					oc.toString());
			return validatorError(oc, message, "visitOperationCallExp");//$NON-NLS-1$
		}

		if (source == null) {
			String message = OCLMessages.bind(
					OCLMessages.NullSourceOperation_ERROR_,
					oc.toString());
			return validatorError(oc, message, "visitOperationCallExp");//$NON-NLS-1$
		}

		C sourceType = source.getType();
		String operName = getName(oper);

		for (OCLExpression expr : args) {
			expr.accept(this);
		}
		
		if (visitFeatureCallExp(oc)) {
            return Boolean.TRUE;
        }
		
		if (opcode == PredefinedType.OCL_IS_NEW) {
			// oclIsNew() may only be used in postcondition constraints
			if (!env.isInPostcondition(oc)) {
				return validatorError(oc, OCLMessages.OCLIsNewInPostcondition_ERROR_, "visitOperationCallExp");//$NON-NLS-1$
			}
		}
		
		source.accept(this);

		// Check argument conformance.
		O oper1 = env.lookupOperation(sourceType,
			operName, args);
		if (oper1 != oper) {
			String message = OCLMessages.bind(
					OCLMessages.IllegalOperation_ERROR_,
					oc.toString());
			return validatorError(oc, message, "visitOperationCallExp");//$NON-NLS-1$
		}
		
		if (!uml.isQuery(oper)) {
			String message = OCLMessages.bind(
					OCLMessages.NonQueryOperation_ERROR_,
					getName(oper));
			return validatorError(oc, message, "visitOperationCallExp");//$NON-NLS-1$
		}
		
		C resultType;

		if (TypeUtil.isStandardLibraryFeature(env, sourceType, oper)) {
			if (opcode != OCLStandardLibraryUtil.getOperationCode(operName)) {
				String message = OCLMessages.bind(
						OCLMessages.IllegalOpcode_ERROR_,
						operName);
				return validatorError(oc, message, "visitOperationCallExp");//$NON-NLS-1$
			}
			
			resultType = TypeUtil
				.getResultType(oc, env, sourceType, oper, args);
			
			if (resultType == null) {
				// maybe this operation was an "extra" contribution by a
				//    custom environment implementation
				resultType = getOCLType(oper);
			}
		} else if (TypeUtil.isOclAnyOperation(env, oper)) {
			// source is an EClass, an enumeration, or a user data type and
			//   operation is defined by OclAny (not the source, itself)
			if (opcode != OCLStandardLibraryUtil.getOclAnyOperationCode(operName)) {
				String message = OCLMessages.bind(
						OCLMessages.IllegalOpcode_ERROR_,
						operName);
				return validatorError(oc, message, "visitOperationCallExp");//$NON-NLS-1$
			}
			
			resultType = TypeUtil
				.getResultType(oc, env, sourceType, oper, args);
			
			if (resultType == null) {
				resultType = getOCLType(oper);
			}
		} else {
			// user-defined operation
			resultType = TypeUtil
				.getResultType(oc, env, sourceType, oper, args);
		}
		
		if (!TypeUtil.exactTypeMatch(env, resultType, oc.getType())) {
			String message = OCLMessages.bind(
					OCLMessages.TypeConformanceOperation_ERROR_,
					oc.getType().toString());
			return validatorError(oc, message, "visitOperationCallExp");//$NON-NLS-1$
		}
		
		if ((opcode == PredefinedType.TO_LOWER) || (opcode == PredefinedType.TO_UPPER)) {
			// check settings for using non-standard closure iterator
			ProblemHandler.Severity sev = ProblemHandler.Severity.OK;
			BasicEnvironment benv = OCLUtil.getAdapter(env, BasicEnvironment.class);
			
			if (benv != null) {
				sev = benv.getValue(ProblemOption.STRING_CASE_CONVERSION);
				if ((sev != null) && (sev != ProblemHandler.Severity.OK)) {
	                benv.problem(
	                        sev,
	                        ProblemHandler.Phase.VALIDATOR,
	                        OCLMessages.bind(
	                                OCLMessages.NonStd_Operation_,
	                                (opcode == PredefinedType.TO_LOWER) ? "String::toLower()" //$NON-NLS-1$
	                                    : "String::toUpper()"), "operationCallExp", //$NON-NLS-1$ //$NON-NLS-2$
	                        oc);
	            }
			}
		}
		
		return Boolean.TRUE;
	}

	/**
	 * Callback for an EnumLiteralExp visit. Well-formedness rule: The type of
	 * an enum Literal expression is the type of the referred literal.
	 * 
	 * @param el
	 *            the enumeration literal expresion
	 * @return Boolean -- true if validated
	 */
	public Boolean visitEnumLiteralExp(EnumLiteralExp el) {
		EL l = el.getReferredEnumLiteral();
		C type = el.getType();
		if (!uml.isEnumeration(type) || uml.getEnumeration(l) != type) {
			String message = OCLMessages.bind(
					OCLMessages.IllegalEnumLiteral_ERROR_,
					el.toString());
			return validatorError(el, message, "visitEnumLiteralExp");//$NON-NLS-1$
		}
		return Boolean.TRUE;
	}

	/**
	 * Callback for a VariableExp visit. Well-formedness rule: The type of a
	 * VariableExp is the type of the Variable to which it refers.
	 * 
	 * @param v
	 *            the variable expression
	 * @return Boolean -- true if validated
	 */
	public Boolean visitVariableExp(VariableExp v) {
		// get the referred variable name
		Variable vd = v.getReferredVariable();

		if (vd == null || v.getType() == null || vd.getName() == null
			|| vd.getType() == null) {
			String message = OCLMessages.bind(
					OCLMessages.IncompleteVariableExp_ERROR_,
					v.toString());
			return validatorError(v, message, "visitVariableExp");//$NON-NLS-1$
		}
		vd.accept(this);
		if (!TypeUtil.exactTypeMatch(env, vd.getType(), v.getType())) {
			String message = OCLMessages.bind(
					OCLMessages.VariableTypeMismatch_ERROR_,
					vd.getName());
			return validatorError(v, message, "visitVariableExp");//$NON-NLS-1$
		}
		return Boolean.TRUE;
	}

	/**
	 * Callback for an PropertyCallExp visit. Well-formedness rule: The
	 * type of the PropertyCallExp is the type of the referred
	 * EStructuralFeature.
	 * 
	 * @param pc the property call expression
	 * @return Boolean -- true if validated
	 */
	public Boolean visitPropertyCallExp(PropertyCallExp pc) {
		P property = pc.getReferredProperty();
		OCLExpression source = pc.getSource();
		C type = pc.getType();

		if (property == null) {
			String message = OCLMessages.bind(
					OCLMessages.NullProperty_ERROR_,
					pc.toString());
			return validatorError(pc, message, "visitPropertyCallExp");//$NON-NLS-1$
		}
		
		if (source == null) {
			String message = OCLMessages.bind(
					OCLMessages.NullNavigationSource_ERROR_,
					pc.toString());
			return validatorError(pc, message, "visitPropertyCallExp");//$NON-NLS-1$
		}
		if (type == null) {
			String message = OCLMessages.bind(
					OCLMessages.NullNavigationType_ERROR_,
					pc.toString());
			return validatorError(pc, message, "visitPropertyCallExp");//$NON-NLS-1$
		}
		
		List> qualifiers = pc.getQualifier();
		if (!qualifiers.isEmpty()) {
			// navigation qualifiers must conform to expected qualifier types
			List

expectedQualifierTypes = uml.getQualifiers(property); if (expectedQualifierTypes.size() != qualifiers.size()) { String message = OCLMessages.bind( OCLMessages.MismatchedQualifiers_ERROR_, pc.toString()); return validatorError(pc, message, "visitPropertyCallExp");//$NON-NLS-1$ } else { Iterator

eiter = expectedQualifierTypes.iterator(); Iterator> qiter = qualifiers.iterator(); while (eiter.hasNext()) { C expectedType = getOCLType(eiter.next()); OCLExpression qualifier = qiter.next(); C qualifierType = qualifier.getType(); if ((TypeUtil.getRelationship(env, qualifierType, expectedType) & UMLReflection.SUBTYPE) == 0) { String message = OCLMessages.bind( OCLMessages.MismatchedQualifiers_ERROR_, pc.toString()); return validatorError(pc, message, "visitPropertyCallExp");//$NON-NLS-1$ } } } } if (visitFeatureCallExp(pc)) { return Boolean.TRUE; } source.accept(this); C refType = TypeUtil.getPropertyType(env, source.getType(), property); if (!pc.getQualifier().isEmpty() && (refType instanceof CollectionType)) { // qualifying the navigation results in a non-collection // type @SuppressWarnings("unchecked") CollectionType ct = (CollectionType) refType; refType = ct.getElementType(); } return Boolean.valueOf(TypeUtil.exactTypeMatch(env, refType, type)); } /** * Callback for an AssociationClassCallExp visit. Well-formedness rules: *

    *
  • the type of the AssociationClassCallExp is the type of the * referenced EReference
  • *
  • the referenced EReference is an AssociationClassEnd, and its * associationClass reference is not null
  • *
* * @param ae * the association end expression * @return Boolean -- true if validated */ public Boolean visitAssociationClassCallExp(AssociationClassCallExp ae) { C ref = ae.getReferredAssociationClass(); OCLExpression source = ae.getSource(); C type = ae.getType(); if (ref == null) { String message = OCLMessages.bind( OCLMessages.MissingAssociationClass_ERROR_, ae.toString()); return validatorError(ae, message, "visitAssociationClassCallExp");//$NON-NLS-1$ } if (source == null) { String message = OCLMessages.bind( OCLMessages.NullNavigationSource_ERROR_, ae.toString()); return validatorError(ae, message, "visitAssociationClassCallExp");//$NON-NLS-1$ } C sourceType = source.getType(); if (type == null) { String message = OCLMessages.bind( OCLMessages.NullNavigationType_ERROR_, ae.toString()); return validatorError(ae, message, "visitAssociationClassCallExp");//$NON-NLS-1$ } if (type instanceof CollectionType) { @SuppressWarnings("unchecked") C elementType = ((CollectionType) type).getElementType(); type = elementType; } if (ae.getNavigationSource() != null) { // navigation source must be an end of the association class P end = ae.getNavigationSource(); if (ref != uml.getAssociationClass(end) || (end != env.lookupProperty(sourceType, getName(end)))) { String message = OCLMessages.bind( OCLMessages.AssociationClassQualifierType_ERROR_, ae.toString()); return validatorError(ae, message, "visitAssociationClassCallExp");//$NON-NLS-1$ } } if (visitFeatureCallExp(ae)) { return Boolean.TRUE; } source.accept(this); return Boolean.valueOf(TypeUtil.exactTypeMatch(env, ref, type)); } /** * Callback for a VariableDeclaration visit. Well-formedness rule: The type * of the initExpression must conform to the type of the declared variable. * * @param vd -- * variable declaration * @return Boolean -- true if validated */ public Boolean visitVariable(Variable vd) { String varName = vd.getName(); if (varName == null) { return validatorError(vd, OCLMessages.MissingNameInVariableDeclaration_ERROR_, "visitVariableDeclaration");//$NON-NLS-1$ } C type = vd.getType(); OCLExpression init = vd.getInitExpression(); if (init != null) { init.accept(this); if (!TypeUtil.compatibleTypeMatch(env, init.getType(), type)) { String message = OCLMessages.bind( OCLMessages.TypeConformanceInit_ERROR_, varName); return validatorError(vd, message, "visitVariableDeclaration");//$NON-NLS-1$ } } return Boolean.TRUE; } /** * Callback for an IfExp visit. Well-formedness Rule: The type of the * condition must be Boolean. The type of the if expression is the common * supertype of the then and else * * @param i - * if expression * @return Boolean -- true if validated */ public Boolean visitIfExp(IfExp i) { OCLExpression cond = i.getCondition(); OCLExpression thenexp = i.getThenExpression(); OCLExpression elseexp = i.getElseExpression(); if (cond == null || thenexp == null || elseexp == null) { String message = OCLMessages.bind( OCLMessages.IncompleteIfExp_ERROR_, i.toString()); return validatorError(i, message, "visitIfExp");//$NON-NLS-1$ } cond.accept(this); thenexp.accept(this); elseexp.accept(this); if (cond.getType() != getStandardLibrary().getBoolean()) { String message = OCLMessages.bind( OCLMessages.NonBooleanIfExp_ERROR_, cond.toString()); return validatorError(i, message, "visitIfExp");//$NON-NLS-1$ } C thenelsetype = TypeUtil.commonSuperType( null, env, thenexp.getType(), elseexp.getType()); if (thenelsetype == null) { return Boolean.TRUE; } if (!TypeUtil.exactTypeMatch(env, i.getType(), thenelsetype)) { String message = OCLMessages.bind( OCLMessages.TypeConformanceIfExp_ERROR_, i.toString()); return validatorError(i, message, "visitIfExp");//$NON-NLS-1$ } return Boolean.TRUE; } public Boolean visitMessageExp(MessageExp m) { if (m.getTarget() == null) { String message = OCLMessages.bind( OCLMessages.MissingMessageTarget_ERROR_, m.toString()); return validatorError(m, message, "visitMessageExp");//$NON-NLS-1$ } m.getTarget().accept(this); if (m.getCalledOperation() == null && m.getSentSignal() == null) { String message = OCLMessages.UnrecognizedMessageType_ERROR_; return validatorError(m, message, "visitMessageExp");//$NON-NLS-1$ } if (m.getCalledOperation() != null && m.getSentSignal() != null) { String message = OCLMessages.AmbiguousMessageType_ERROR_; return validatorError(m, message, "visitMessageExp");//$NON-NLS-1$ } List parameters; if (m.getCalledOperation() != null) { O operation = uml.getOperation(m.getCalledOperation()); if (operation == null) { String message = OCLMessages.bind( OCLMessages.MissingOperationInCallAction_ERROR_, m.toString()); return validatorError(m, message, "visitMessageExp");//$NON-NLS-1$ } parameters = uml.getParameters(operation); } else { C signal = uml.getSignal(m.getSentSignal()); if (signal == null) { String message = OCLMessages.bind( OCLMessages.MissingSignalInCallAction_ERROR_, m.toString()); return validatorError(m, message, "visitMessageExp");//$NON-NLS-1$ } parameters = uml.getAttributes(signal); } List> arguments = m.getArgument(); if (arguments.size() != parameters.size()) { String message = OCLMessages.bind(OCLMessages.MessageArgumentCount_ERROR_, getName(m.getType())); return validatorError(m, message, "visitMessageExp");//$NON-NLS-1$ } // check type conformance against operators Iterator paramsIter = parameters.iterator(); Iterator> argsIter = arguments.iterator(); while (paramsIter.hasNext()) { Object param = paramsIter.next(); OCLExpression arg = argsIter.next(); if (!TypeUtil.compatibleTypeMatch(env, arg.getType(), getOCLType(param))) { String message = OCLMessages.bind(OCLMessages.MessageArgConformance_ERROR_, getName(param), arg.toString()); return validatorError(m, message, "visitMessageExp");//$NON-NLS-1$ } // validate the argument arg.accept(this); } return Boolean.TRUE; } /** * Callback for an UnspecifiedValueExp visit. * * @param uv -- * unspecified value expression * @return Boolean -- true if validated */ public Boolean visitUnspecifiedValueExp(UnspecifiedValueExp uv) { // unspecified values need not declare a type (it can be OclVoid). // The only restriction is that they can only be used in message expressions if (!(uv.eContainer() instanceof MessageExp)) { String message = OCLMessages.bind( OCLMessages.IllegalUnspecifiedValueExp_ERROR_, uv.toString()); return validatorError(uv, message, "visitUnspecifiedValueExp");//$NON-NLS-1$ } return Boolean.TRUE; } /** * Callback for a TypeExp visit. */ public Boolean visitTypeExp(TypeExp t) { if (!(t.getType() instanceof TypeType)) { String message = OCLMessages.bind(OCLMessages.TypeConformanceTypeExp_ERROR_, getName(t.getType())); return validatorError(t, message, "visitTypeExp");//$NON-NLS-1$ } if (t.getReferredType() == null) { String message = OCLMessages.bind( OCLMessages.TypeExpMissingType_ERROR_, t.toString()); return validatorError(t, message, "visitTypeExp");//$NON-NLS-1$ } return Boolean.TRUE; } /** * Callback for an IntegerLiteralExp visit. Well-formedness rule: The type * of an integer Literal expression is the type Integer * * @param il - * integer literal expression * @return Boolean -- true if validated */ public Boolean visitIntegerLiteralExp(IntegerLiteralExp il) { if (il.getType() != getStandardLibrary().getInteger()) { String message = OCLMessages.TypeConformanceIntegerLiteral_ERROR_; return validatorError(il, message, "visitIntegerLiteralExp");//$NON-NLS-1$ } return Boolean.TRUE; } /** * Callback for an UnlimitedNaturalLiteralExp visit. Well-formedness rule: The type * of an unlimited natural Literal expression is the type UnlimitedNatural * * @param unl - * unlimited literal expression * @return Boolean -- true if validated */ public Boolean visitUnlimitedNaturalLiteralExp(UnlimitedNaturalLiteralExp unl) { if (unl.getType() != getStandardLibrary().getUnlimitedNatural()) { String message = OCLMessages.TypeConformanceUnlimitedNaturalLiteral_ERROR_; return validatorError(unl, message, "visitUnlimitedNaturalLiteralExp");//$NON-NLS-1$ } return Boolean.TRUE; } /** * Callback for a RealLiteralExp visit. Well-formedness rule: The type of a * real literal expression is the type Real. * * @param rl -- * real literal expression * @return Boolean -- true if validated */ public Boolean visitRealLiteralExp(RealLiteralExp rl) { if (rl.getType() != getStandardLibrary().getReal()) { String message = OCLMessages.TypeConformanceRealLiteral_ERROR_; return validatorError(rl, message, "visitRealLiteralExp");//$NON-NLS-1$ } return Boolean.TRUE; } /** * Callback for a StringLiteralExp visit. Well-formedness rule: The type of * a string literal expression is the type of the string. * * @param sl -- * string literal expression * @return Boolean -- true if validated */ public Boolean visitStringLiteralExp(StringLiteralExp sl) { if (sl.getType() != getStandardLibrary().getString()) { String message = OCLMessages.TypeConformanceStringLiteral_ERROR_; return validatorError(sl, message, "visitStringLiteralExp");//$NON-NLS-1$ } return Boolean.TRUE; } /** * Callback for a BooleanLiteralExp visit. Well-formedness rule: The type of * a Boolean Literal expression is the type of the boolean. * * @param bl - * boolean literal expression * @return Boolean - true if validated */ public Boolean visitBooleanLiteralExp(BooleanLiteralExp bl) { if (bl.getType() != getStandardLibrary().getBoolean()) { String message = OCLMessages.TypeConformanceBooleanLiteral_ERROR_; return validatorError(bl, message, "visitBooleanLiteralExp");//$NON-NLS-1$ } return Boolean.TRUE; } /** * Callback for LetExp visit. Well-formedness rule: The type of the Let * expression is the type of the in expression. * * @param l -- * let expression * @return Boolean -- true if validated */ public Boolean visitLetExp(LetExp l) { Variable vd = l.getVariable(); OCLExpression in = l.getIn(); C type = l.getType(); if (vd == null || in == null || type == null) { String message = OCLMessages.bind( OCLMessages.IncompleteLetExp_ERROR_, l.toString()); return validatorError(l, message, "visitLetExp");//$NON-NLS-1$ } vd.accept(this); in.accept(this); if (!TypeUtil.exactTypeMatch(env, type, in.getType())) { String message = OCLMessages.bind( OCLMessages.TypeConformanceLetExp_ERROR_, type, in.getType()); return validatorError(l, message, "visitLetExp");//$NON-NLS-1$ } return Boolean.TRUE; } /** * * Callback for an IterateExp visit. *Well-formedness rule: The type of the * iterate is the type of the result variable. The type of the body * expression must conform to the declared type of the result variable. *A * result variable must have an init expression. *The type of a source * expression must be a collection. *The loop variable has no init * expression. *The type of the iterator variable must be the type of the * elements of the *source collection. * * @param ie - * iterate expression * @return Boolean -- true if validated */ public Boolean visitIterateExp(IterateExp ie) { // get the variable declaration for the result Variable vd = ie.getResult(); C type = ie.getType(); OCLExpression body = ie.getBody(); OCLExpression source = ie.getSource(); List> iterators = ie.getIterator(); if (vd == null || type == null || source == null || body == null || iterators.isEmpty()) { String message = OCLMessages.bind( OCLMessages.IncompleteIterateExp_ERROR_, ie.toString()); return validatorError(ie, message, "visitIterateExp");//$NON-NLS-1$ } // Validate all of the iterate parts source.accept(this); vd.accept(this); body.accept(this); if (vd.getInitExpression() == null) { String message = OCLMessages.bind( OCLMessages.MissingInitIterateExp_ERROR_, ie.toString()); return validatorError(ie, message, "visitIterateExp");//$NON-NLS-1$ } if (!TypeUtil.exactTypeMatch(env, type, vd.getType())) { String message = OCLMessages.bind( OCLMessages.TypeConformanceIterateExp_ERROR_, ie.toString()); return validatorError(ie, message, "visitIterateExp");//$NON-NLS-1$ } if (!TypeUtil.compatibleTypeMatch(env, body.getType(), vd.getType())) { String message = OCLMessages.bind( OCLMessages.TypeConformanceIterateExpBody_ERROR_, ie.toString()); return validatorError(ie, message, "visitIterateExp");//$NON-NLS-1$ } C sourceType = source.getType(); if (!(sourceType instanceof CollectionType)) { String message = OCLMessages.bind( OCLMessages.IteratorSource_ERROR_, ie.toString()); return validatorError(ie, message, "visitIterateExp");//$NON-NLS-1$ } // validate the number of iterator variables if (iterators.size() > 1) { String message = OCLMessages.bind( OCLMessages.TooManyIteratorVariables_ERROR_, ie.getName()); return validatorError(ie, message, "visitIteratorExp");//$NON-NLS-1$ } for (Variable loopiter : iterators) { // Validate the iterator expressions loopiter.accept(this); if (loopiter.getInitExpression() != null) { String message = OCLMessages.bind( OCLMessages.IterateExpLoopVarInit_ERROR_, ie.toString()); return validatorError(ie, message, "visitIterateExp");//$NON-NLS-1$ } @SuppressWarnings("unchecked") CollectionType ct = (CollectionType) sourceType; if (!TypeUtil.exactTypeMatch(env, loopiter.getType(), ct.getElementType())) { String message = OCLMessages.bind( OCLMessages.TypeConformanceIterateExpLoopVar_ERROR_, ie.toString()); return validatorError(ie, message, "visitIterateExp");//$NON-NLS-1$ } } return Boolean.TRUE; } /** * Callback for an IteratorExp visit. Well-formedness rule: If the iterator * is "forall", "isUnique", "any", "one", or "exists", the type of the * iterator must be Boolean. The result type of the collect operation on a * sequence type is a sequence; the result type of collect on any other type * is a bag. The select and reject iterators have the same type as its * source. They type of the body of the select, reject, forall, exists must * be boolean. The type of a source expression must be a collection. The * loop variable has no init expression. The type of the iterator variable * must be the type of the elements of the source collection. * * @param ie -- * iterator expression * @return Boolean -- true if validated */ public Boolean visitIteratorExp(IteratorExp ie) { C type = ie.getType(); OCLExpression body = ie.getBody(); OCLExpression source = ie.getSource(); List> iterators = ie.getIterator(); String name = ie.getName(); if (type == null || name == null || source == null || body == null || iterators.isEmpty()) { String message = OCLMessages.bind( OCLMessages.IncompleteIteratorExp_ERROR_, ie.toString()); return validatorError(ie, message, "visitIteratorExp");//$NON-NLS-1$ } int opcode = 0; if (source.getType() instanceof PredefinedType) { opcode = OCLStandardLibraryUtil.getOperationCode(name); } // Validate all of the iterate parts source.accept(this); body.accept(this); switch (opcode) { case PredefinedType.FOR_ALL: case PredefinedType.EXISTS: case PredefinedType.IS_UNIQUE: if (type != getStandardLibrary().getBoolean()) { String message = OCLMessages.bind( OCLMessages.TypeConformanceIteratorResult_ERROR_, ie.toString()); return validatorError(ie, message, "visitIteratorExp");//$NON-NLS-1$ } } if (opcode == PredefinedType.COLLECT) { if (source.getType() instanceof SequenceType || source.getType() instanceof OrderedSetType) { if (!(type instanceof SequenceType)) { String message = OCLMessages.bind( OCLMessages.TypeConformanceCollectSequence_ERROR_, ie.toString()); return validatorError(ie, message, "visitIteratorExp");//$NON-NLS-1$ } } else if (!(type instanceof BagType)) { String message = OCLMessages.bind( OCLMessages.TypeConformanceCollectBag_ERROR_, ie.toString()); return validatorError(ie, message, "visitIteratorExp");//$NON-NLS-1$ } } switch (opcode) { case PredefinedType.SELECT: case PredefinedType.REJECT: if (!TypeUtil.exactTypeMatch(env, type, source.getType())) { String message = OCLMessages.bind( OCLMessages.TypeConformanceSelectReject_ERROR_, ie.toString()); return validatorError(ie, message, "visitIteratorExp");//$NON-NLS-1$ } } switch (opcode) { case PredefinedType.SELECT: case PredefinedType.REJECT: case PredefinedType.FOR_ALL: case PredefinedType.ANY: case PredefinedType.EXISTS: case PredefinedType.ONE: if (body.getType() != getStandardLibrary().getBoolean()) { String message = OCLMessages.bind( OCLMessages.TypeConformanceIteratorBodyBoolean_ERROR_, ie.toString()); return validatorError(ie, message, "visitIteratorExp");//$NON-NLS-1$ } } C sourceType = source.getType(); if (!(sourceType instanceof CollectionType)) { String message = OCLMessages.bind( OCLMessages.IteratorSource_ERROR_, ie.toString()); return validatorError(ie, message, "visitIteratorExp");//$NON-NLS-1$ } if (opcode == PredefinedType.CLOSURE) { // check settings for using non-standard closure iterator ProblemHandler.Severity sev = ProblemHandler.Severity.OK; BasicEnvironment benv = OCLUtil.getAdapter(env, BasicEnvironment.class); if (benv != null) { sev = benv.getValue(ProblemOption.CLOSURE_ITERATOR); if ((sev != null) && (sev != ProblemHandler.Severity.OK)) { benv.problem(sev, ProblemHandler.Phase.VALIDATOR, OCLMessages .bind(OCLMessages.NonStd_Iterator_, PredefinedType.CLOSURE_NAME), "iteratorExp", ie); //$NON-NLS-1$ } } if (!(type instanceof SetType) && !(type instanceof OrderedSetType)) { String message = OCLMessages.bind( OCLMessages.TypeConformanceClosure_ERROR_, ie.toString()); return validatorError(ie, message, "visitIteratorExp");//$NON-NLS-1$ } // recursive reference must be to a type conforming // to the source, otherwise it isn't recursive // checked above that the source is a collection type @SuppressWarnings("unchecked") CollectionType sourceCT = (CollectionType) source.getType(); @SuppressWarnings("unchecked") CollectionType bodyCT = (CollectionType) type; C sourceElementType = sourceCT.getElementType(); C bodyType = bodyCT.getElementType(); if (!TypeUtil.compatibleTypeMatch(env, bodyType, sourceElementType)) { String message = OCLMessages.bind( OCLMessages.ElementTypeConformanceClosure_ERROR_, getName(bodyType), getName(sourceElementType)); return validatorError(ie, message, "visitIteratorExp");//$NON-NLS-1$ } } if (opcode == PredefinedType.SORTED_BY) { // the body type must be comparable (in OCL terms, it must // define the '<' operation) if (!uml.isComparable(body.getType())) { // FIXME: Should be more specifically about the sortedBy iterator String message = OCLMessages.bind( OCLMessages.OperationNotFound_ERROR_, PredefinedType.LESS_THAN_NAME, getName(body.getType())); return validatorError(ie, message, "visitIteratorExp");//$NON-NLS-1$ } } // validate the number of iterators switch (opcode) { case PredefinedType.FOR_ALL: case PredefinedType.EXISTS: if (iterators.size() > 2) { String message = OCLMessages.bind( OCLMessages.TooManyIteratorVariables_ERROR_, ie.getName()); return validatorError(ie, message, "visitIteratorExp");//$NON-NLS-1$ } break; default: if (iterators.size() > 1) { String message = OCLMessages.bind( OCLMessages.TooManyIteratorVariables_ERROR_, ie.getName()); return validatorError(ie, message, "visitIteratorExp");//$NON-NLS-1$ } } for (Variable loopiter : iterators) { // Validate the iterator expressions loopiter.accept(this); if (loopiter.getInitExpression() != null) { String message = OCLMessages.bind( OCLMessages.IterateExpLoopVarInit_ERROR_, ie.toString()); return validatorError(ie, message, "visitIteratorExp");//$NON-NLS-1$ } @SuppressWarnings("unchecked") CollectionType ct = (CollectionType) sourceType; if (!TypeUtil.exactTypeMatch(env, loopiter.getType(), ct.getElementType())) { String message = OCLMessages.bind( OCLMessages.TypeConformanceIteratorExpLoopVar_ERROR_, ie.toString()); return validatorError(ie, message, "visitIteratorExp");//$NON-NLS-1$ } } return Boolean.TRUE; } /** * Callback for a CollectionLiteralExp visit. Well-formedness rule: The type * of a collection literal expression is determined by the collection kind * selection, and the common supertype of all elements. The empty collection * has a Classifier as element type. * * @param cl -- * collection literal expression * @return Boolean -- true if validated */ public Boolean visitCollectionLiteralExp(CollectionLiteralExp cl) { CollectionKind kind = cl.getKind(); C type = cl.getType(); if (!(type instanceof CollectionType)) { String message = OCLMessages.bind( OCLMessages.TypeConformanceCollectionLiteralExp_ERROR_, cl.toString()); return validatorError(cl, message, "visitCollectionLiteralExp");//$NON-NLS-1$ } switch (kind) { case SET_LITERAL: if (!(type instanceof SetType)) { String message = OCLMessages.bind( OCLMessages.TypeConformanceSetLiteral_ERROR_, cl.toString()); return validatorError(cl, message, "visitCollectionLiteralExp");//$NON-NLS-1$ } break; case ORDERED_SET_LITERAL: if (!(type instanceof OrderedSetType)) { String message = OCLMessages.bind( OCLMessages.TypeConformanceOrderedSetLiteral_ERROR_, cl.toString()); return validatorError(cl, message, "visitCollectionLiteralExp");//$NON-NLS-1$ } break; case BAG_LITERAL: if (!(type instanceof BagType)) { String message = OCLMessages.bind( OCLMessages.TypeConformanceBagLiteral_ERROR_, cl.toString()); return validatorError(cl, message, "visitCollectionLiteralExp");//$NON-NLS-1$ } break; default: if ((kind != CollectionKind.SEQUENCE_LITERAL) || !(type instanceof SequenceType)) { String message = OCLMessages.bind( OCLMessages.TypeConformanceSequenceLiteral_ERROR_, cl.toString()); return validatorError(cl, message, "visitCollectionLiteralExp");//$NON-NLS-1$ } break; } List> parts = cl.getPart(); @SuppressWarnings("unchecked") CollectionType collectionType = (CollectionType) type; if (parts.isEmpty()) { if (!(collectionType.getElementType() instanceof VoidType)) { String message = OCLMessages.bind( OCLMessages.TypeConformanceEmptyCollection_ERROR_, cl.toString()); return validatorError(cl, message, "visitCollectionLiteralExp");//$NON-NLS-1$ } else { return Boolean.TRUE; } } C partsType = parts.get(0).getType(); for (CollectionLiteralPart part : parts) { part.accept(this); partsType = TypeUtil.commonSuperType(null, env, partsType, part.getType()); if (partsType == null) { return Boolean.TRUE; } } if (!TypeUtil.exactTypeMatch(env, partsType, collectionType.getElementType())) { String message = OCLMessages.bind( OCLMessages.TypeConformanceCollectionElementType_ERROR_, cl.toString()); return validatorError(cl, message, "visitCollectionLiteralExp");//$NON-NLS-1$ } return Boolean.TRUE; } public Boolean visitCollectionItem(CollectionItem item) { return item.getItem().accept(this); } public Boolean visitCollectionRange(CollectionRange range) { return range.getFirst().accept(this) && range.getLast().accept(this); } /** * Callback for a TupleLiteralExp visit. * * Well-formedness rule: The type of a tuple literal is a TupleType the * specified parts All tuple literal expression parts must have unique * names. The type of each attribute in a tuple literal part must match the * type of the initialization expression. * * @param tl * tuple literal expression * @return Boolean */ public Boolean visitTupleLiteralExp(TupleLiteralExp tl) { C type = tl.getType(); if (!(type instanceof TupleType)) { String message = OCLMessages.bind( OCLMessages.TypeConformanceTupleLiteralExp_ERROR_, tl.toString()); return validatorError(tl, message, "visitTupleLiteralExp");//$NON-NLS-1$ } // The fields of the tuple are the properties of the EClass. List> tp = tl.getPart(); if (tp.size() != uml.getAttributes(type).size()) { String message = OCLMessages.bind( OCLMessages.TypeConformanceTupleLiteralExpParts_ERROR_, tl.toString()); return validatorError(tl, message, "visitTupleLiteralExp");//$NON-NLS-1$ } Set names = new java.util.HashSet(); // Match each property with a tuple part for (TupleLiteralPart part : tl.getPart()) { String name = part.getName(); P property = env.lookupProperty(type, name); if (property == null) { String message = OCLMessages.bind( OCLMessages.TupleLiteralExpressionPart_ERROR_, name, tl.toString()); return validatorError(tl, message, "visitTupleLiteralExp");//$NON-NLS-1$ } // Validate each TupleLiteralPart in the tuple literal // At the same time, check for unique names if (!names.add(name)) { String message = OCLMessages.bind( OCLMessages.TupleDuplicateName_ERROR_, name, tl.toString()); return validatorError(tl, message, "visitTupleLiteralExp");//$NON-NLS-1$ } part.accept(this); } return Boolean.TRUE; } public Boolean visitTupleLiteralPart(TupleLiteralPart tp) { P property = tp.getAttribute(); if (property == null) { String message = OCLMessages.bind( OCLMessages.MissingPropertyInTupleLiteralPart_ERROR_, tp.getName(), tp.eContainer().toString()); return validatorError(tp, message, "visitTupleLiteralPart");//$NON-NLS-1$ } C type = tp.getType(); if (type == null) { String message = OCLMessages.bind( OCLMessages.MissingTypeInTupleLiteralPart_ERROR_, tp.getName(), tp.eContainer().toString()); return validatorError(tp, message, "visitTupleLiteralPart");//$NON-NLS-1$ } // convert property type to OCL type because it may be an Ecore primitive // such as EIntegerObject if (!TypeUtil.exactTypeMatch(env, getOCLType(property), type)) { String message = OCLMessages.bind( OCLMessages.TuplePartType_ERROR_, tp.getName(), tp.eContainer().toString()); return validatorError(tp, message, "visitTupleLiteralPart");//$NON-NLS-1$ } OCLExpression value = tp.getValue(); if (value != null) { value.accept(this); if (!TypeUtil.compatibleTypeMatch(env, value.getType(), type)) { String message = OCLMessages.TypeConformanceTuplePartValue_ERROR_; return validatorError(tp, message, "visitTupleLiteralPart");//$NON-NLS-1$ } } return Boolean.TRUE; } public Boolean visitStateExp(StateExp s) { Object state = s.getReferredState(); if (state == null) { String message = OCLMessages.bind( OCLMessages.MissingStateInStateExp_ERROR_, s.toString()); return validatorError(s, message, "visitStateExp");//$NON-NLS-1$ } return Boolean.TRUE; } /** * Applies well-formedness rules for model property calls in general. * This includes checking that "@pre" notation is only used in a * postcondition constraint. * * @param exp the model property call expression to validate * * @Return true if validation must terminate due to an error * @since 3.1 */ public Boolean visitFeatureCallExp(FeatureCallExp exp) { if (exp.isMarkedPre()) { // check for a postcondition constraint if (!env.isInPostcondition(exp)) { String message = OCLMessages.AtPreInPostcondition_ERROR_; return validatorError(exp, message, "visitFeatureCallExp");//$NON-NLS-1$ } } // check for static access to non-static features if (exp.getSource() != null) { OCLExpression source = exp.getSource(); if (source.getType() instanceof TypeType) { @SuppressWarnings("unchecked") TypeType typeType = (TypeType) source.getType(); Object feature = null; if (exp instanceof OperationCallExp) { feature = ((OperationCallExp) exp).getReferredOperation(); // operation must either be defined by the TypeType // (e.g., allInstances()) or be a static operation of // the referred classifier if (!(typeType.oclOperations().contains(feature) || isStatic(feature))) { String message = OCLMessages.bind( OCLMessages.NonStaticOperation_ERROR_, getName(feature)); return validatorError(exp, message, "visitFeatureCallExp");//$NON-NLS-1$ } } else if (exp instanceof PropertyCallExp) { feature = ((PropertyCallExp) exp).getReferredProperty(); // property must be a static attribute of // the referred classifier if (!isStatic(feature)) { String message = OCLMessages.bind( OCLMessages.NonStaticAttribute_ERROR_, getName(feature)); return validatorError(exp, message, "visitFeatureCallExp");//$NON-NLS-1$ } } } } return Boolean.FALSE; } private boolean isStatic(Object feature) { return (uml != null) && uml.isStatic(feature); } public Boolean visitInvalidLiteralExp(InvalidLiteralExp il) { if (!(il.getType() instanceof InvalidType)) { String message = OCLMessages.TypeConformanceInvalidLiteral_ERROR_; return validatorError(il, message, "visitInvalidLiteralExp");//$NON-NLS-1$ } return Boolean.TRUE; } public Boolean visitNullLiteralExp(NullLiteralExp il) { if (!(il.getType() instanceof VoidType)) { String message = OCLMessages.TypeConformanceNullLiteral_ERROR_; return validatorError(il, message, "visitNullLiteralExp");//$NON-NLS-1$ } return Boolean.TRUE; } public Boolean visitExpressionInOCL(ExpressionInOCL expression) { if (expression.getContextVariable() == null) { String message = OCLMessages.MissingContextVariable_ERROR_; return validatorError(expression, message, "visitExpressionInOCL");//$NON-NLS-1$ } OCLExpression body = expression.getBodyExpression(); if (body == null) { // won't be able to do anything else useful with this expression String message = OCLMessages.MissingBodyExpression_ERROR_; return validatorError(expression, message, "visitExpressionInOCL");//$NON-NLS-1$ } CT constraint = uml.getConstraint(expression); if (constraint != null) { O operation = getConstrainedOperation(uml.getConstrainedElements(constraint)); if (operation == null) { if (!UMLReflection.DEFINITION.equals(uml.getStereotype(constraint))) { if (!expression.getParameterVariable().isEmpty()) { String message = OCLMessages.ExtraneousParameterVariables_ERROR_; return validatorError(constraint, message, "visitExpressionInOCL");//$NON-NLS-1$ } if (expression.getResultVariable() != null) { String message = OCLMessages.ExtraneousResultVariable_ERROR_; return validatorError(constraint, message, "visitExpressionInOCL");//$NON-NLS-1$ } } } else { List parameters = uml.getParameters(operation); List> variables = expression.getParameterVariable(); if (parameters.size() != variables.size()) { String message = OCLMessages.MismatchedParameterVariables_ERROR_; return validatorError(constraint, message, "visitExpressionInOCL");//$NON-NLS-1$ } Iterator iter = parameters.iterator(); for (Variable var : expression.getParameterVariable()) { PM param = iter.next(); var.accept(this); C paramType = getOCLType(param); if (paramType != null) { if (!TypeUtil.exactTypeMatch(env, paramType, var.getType())) { String message = OCLMessages.MismatchedParameterVariables_ERROR_; return validatorError(constraint, message, "visitExpressionInOCL");//$NON-NLS-1$ } } } // we need to validate the result variable against the operation // result type in postconditions. In other constraints, the // result variable does not exist (in body expressions, we // allow it for now for compatibility) Variable resultVar = expression.getResultVariable(); C operType = null; String stereotype = uml.getStereotype(constraint); if (UMLReflection.BODY.equals(stereotype) || UMLReflection.POSTCONDITION.equals(stereotype)) { operType = getOCLType(operation); if (operType instanceof VoidType) { operType = null; } } if (((operType == null) != (resultVar == null)) && !UMLReflection.BODY.equals(stereotype)) { String message; if (resultVar == null) { message = OCLMessages.MissingResultVariable_ERROR_; } else { message = OCLMessages.ExtraneousResultVariable_ERROR_; } return validatorError(constraint, message, "visitExpressionInOCL");//$NON-NLS-1$ } else if (resultVar != null) { if (!TypeUtil.exactTypeMatch(env, operType, resultVar.getType())) { String message = OCLMessages.MissingResultVariable_ERROR_; return validatorError(constraint, message, "visitExpressionInOCL");//$NON-NLS-1$ } expression.getResultVariable().accept(this); } } } Boolean wellFormed = checkExpressionInOCL(expression, constraint, body); return Boolean.TRUE.equals(body.accept(this)) && Boolean.TRUE.equals(wellFormed); } /** * Checks the well-formedness of an ExpressionInOCL, according to the * constraints defined in Chapter 12 of the OCL Specification. * * @param expression an expression * @param constraint the constraint that owns it (not null) * @param body its body expression (not null) * * @return whether it is well-formed */ Boolean checkExpressionInOCL(ExpressionInOCL expression, CT constraint, OCLExpression body) { String stereotype = uml.getStereotype(constraint); List constrainedElement = uml.getConstrainedElements(constraint); C bodyType = body.getType(); C oclBoolean = getStandardLibrary().getBoolean(); if (UMLReflection.INVARIANT.equals(stereotype)) { // if expression has one constrained element that is a classifier // then the constrained classifier is the context classifier and // the body expression is boolean-valued C constrainedClassifier = getConstrainedClassifier(constrainedElement); if (!Boolean.TRUE.equals(checkContextClassifier(expression, constrainedClassifier, constrainedElement))) { return Boolean.FALSE; } // we should always check this type conformance if (bodyType != oclBoolean) { // so must invariants, but they have a different kind of context String message = OCLMessages.bind( OCLMessages.InvariantConstraintBoolean_ERROR_, getName(constrainedClassifier)); return validatorError(constraint, message, "checkExpressionInOCL"); //$NON-NLS-1$ } } else if (UMLReflection.POSTCONDITION.equals(stereotype) || UMLReflection.PRECONDITION.equals(stereotype)) { // if the expression has one constrained element that is an operation // then the constrained element's owner is the contextual classifier // and the body expression is boolean-valued. // Note that this specifically allows an inheriting classifier to // redefine a pre- or post-condition by listing the classifier // that inherits the operation as well as the operation in the // constrainedElement reference O constrainedOperation = getConstrainedOperation(constrainedElement); if (!Boolean.TRUE.equals(checkContextFeatureClassifier(expression, constrainedOperation, constrainedElement))) { return Boolean.FALSE; } // we should always check this type conformance if (bodyType != oclBoolean) { String message = OCLMessages.bind( OCLMessages.OperationConstraintBoolean_ERROR_, getName(constrainedOperation)); return validatorError(constraint, message, "checkExpressionInOCL"); //$NON-NLS-1$ } } else if (UMLReflection.DEFINITION.equals(stereotype)) { // if expression has one constrained element that is a classifier // then the constrained element is the context classifier C constrainedClassifier = getConstrainedClassifier(constrainedElement); if (!Boolean.TRUE.equals(checkContextClassifier(expression, constrainedClassifier, constrainedElement))) { return Boolean.FALSE; } } else if (UMLReflection.INITIAL.equals(stereotype) || UMLReflection.DERIVATION.equals(stereotype)) { // if the expression has one constrained element that is a property // then the constrained element's owner is the contextual classifier // and the body expression type conforms to the property type. // Note that this specifically allows an inheriting classifier to // redefine an initial or derived value by listing the classifier // that inherits the attribute as well as the attribute in the // constrainedElement reference P constrainedProperty = getConstrainedProperty(constrainedElement); if (!Boolean.TRUE.equals(checkContextFeatureClassifier(expression, constrainedProperty, constrainedElement))) { return Boolean.FALSE; } C propertyType = (constrainedProperty != null) ? getOCLType(constrainedProperty) : getStandardLibrary().getOclVoid(); // we should always check this type conformance if (!TypeUtil.compatibleTypeMatch(env, bodyType, propertyType)) { String message = OCLMessages.bind( OCLMessages.InitOrDerConstraintConformance_ERROR_, new Object[] { getName(bodyType), getName(constrainedProperty), getName(propertyType)}); return validatorError(constraint, message, "checkExpressionInOCL"); //$NON-NLS-1$ } } else if (UMLReflection.BODY.equals(stereotype)) { // if the expression has one constrained element that is an operation // then the constrained element's owner is the contextual classifier // and the body expression type conforms to the operation type. // Note that this specifically allows an inheriting classifier to // redefine an operation body by listing the classifier // that inherits the operation as well as the operation in the // constrainedElement reference O constrainedOperation = getConstrainedOperation(constrainedElement); if (!Boolean.TRUE.equals(checkContextFeatureClassifier(expression, constrainedOperation, constrainedElement))) { return Boolean.FALSE; } C operationType = (constrainedOperation != null) ? getOCLType(constrainedOperation) : getStandardLibrary().getOclVoid(); String operationName = getName(constrainedOperation); // void operations may not have body constraints if (operationType instanceof VoidType) { String message = OCLMessages.bind( OCLMessages.BodyConditionNotAllowed_ERROR_, operationName); return validatorError(constraint, message, "checkExpressionInOCL"); //$NON-NLS-1$ } // we should always check this type conformance if ((bodyType == oclBoolean) && (operationType != oclBoolean)) { // this is a UML-style body condition constraint (the UML and // OCL specifications are contradictory) if (visitBodyConditionConstraint(constraint, operationType, operationName)) { return Boolean.TRUE; } } else { // the body expression type must conform to the operation type if (!TypeUtil.compatibleTypeMatch(env, bodyType, operationType)) { String message = OCLMessages.bind( OCLMessages.BodyConditionConformance_ERROR_, new Object[] { operationName, getName(bodyType), getName(operationType)}); return validatorError(constraint, message, "checkExpressionInOCL"); //$NON-NLS-1$ } // check that the body doesn't reference the result variable if (findResultVariable(expression.getBodyExpression(), operationType)) { String message = OCLMessages.bind( OCLMessages.BodyConditionForm_ERROR_, operationName); return validatorError(constraint, message, "checkExpressionInOCL"); //$NON-NLS-1$ } } } return Boolean.TRUE; } /** * Checks that the contextual classifier of the specified expression * is correct for a classifier-context constraint. * * @param expression an expression being validated * @param constrainedClassifier the feature context of the constraint * @param constrainedElement the constrained elements * * @return whether the context classifier is correct */ private Boolean checkContextClassifier(ExpressionInOCL expression, C constrainedClassifier, List constrainedElement) { C contextualClassifier = getContextualClassifier(expression); if (constrainedElement.size() == 1) { if (constrainedClassifier != contextualClassifier) { String message = OCLMessages.bind( OCLMessages.WrongContextClassifier_ERROR_, getName(contextualClassifier), getName(constrainedClassifier)); return validatorError(expression, message, "checkExpressionInOCL"); //$NON-NLS-1$ } } return Boolean.TRUE; } /** * Checks that the contextual classifier of the specified expression * is correct for a constraint in a feature context. * MDT OCL provides an extension in which a constraint in a * feature context may be specified in the context of a classifier that * inherits (thus does not own) the feature. In this case, the * constrainedElement list additionally includes the specializing * classifier. * * @param expression an expression being validated * @param constrainedFeature the feature context of the constraint * @param constrainedElement the constrained elements * * @return whether the context classifier is correct */ private Boolean checkContextFeatureClassifier(ExpressionInOCL expression, Object constrainedFeature, List constrainedElement) { C contextualClassifier = getContextualClassifier(expression); if ((constrainedElement.size() == 1) && (constrainedFeature != null)) { C owner = uml.getOwningClassifier(constrainedFeature); if (owner != contextualClassifier) { String message = OCLMessages.bind( OCLMessages.WrongContextClassifier_ERROR_, getName(contextualClassifier), getName(owner)); return validatorError(expression, message, "checkExpressionInOCL"); //$NON-NLS-1$ } } else if (constrainedElement.size() > 1) { // MDT OCL extension for applying constraints in the context of // a classifier that inherits the operation C constrainedClassifier = getConstrainedClassifier(constrainedElement); if ((constrainedClassifier != null) && (constrainedFeature != null)) { C owner = uml.getOwningClassifier(constrainedFeature); if (!TypeUtil.compatibleTypeMatch(env, constrainedClassifier, owner)) { String message = OCLMessages.bind( OCLMessages.WrongContextClassifier_ERROR_, getName(contextualClassifier), getName(owner)); return validatorError(expression, message, "checkExpressionInOCL"); //$NON-NLS-1$ } } } return Boolean.TRUE; } /** * Applies well-formedness rules to constraints. * * @param constraint the constraint to validate */ public Boolean visitConstraint(CT constraint) { ExpressionInOCL specification = uml.getSpecification(constraint); Boolean specificationResult = specification.accept(this); if (!Boolean.TRUE.equals(specificationResult)) { return specificationResult; } // // String stereo = uml.getStereotype(constraint); // // C bodyType = specification.getBodyExpression().getType(); // C oclBoolean = getStandardLibrary().getBoolean(); // // String classifierName = null; // // if (!uml.getConstrainedElements(constraint).isEmpty()) { // EObject constrained = uml.getConstrainedElements(constraint).get(0); // // if (uml.isOperation(constrained)) { // classifierName = getName(uml.getOwningClassifier(constrained)); // } else if (uml.isProperty(constrained)) { // classifierName = getName(uml.getOwningClassifier(constrained)); // } else if (uml.isClassifier(constrained)) { // classifierName = getName(constrained); // } // } // // if (UMLReflection.DEFINITION.equals(stereo)) { // // expression type must conform to feature type // EObject feature = null; // if (uml.getConstrainedElements(constraint).size() >= 2) { // EObject constrained = uml.getConstrainedElements(constraint).get(1); // if (uml.isOperation(constrained) || uml.isProperty(constrained)) { // feature = constrained; // } // } // // if (feature == null) { // String message = OCLMessages.bind( // OCLMessages.DefinitionConstraintFeature_ERROR_, // classifierName); // return validatorError(constraint, message, "visitConstraint"); //$NON-NLS-1$ // } // // C featureType = getOCLType(feature); // // if ((featureType == null) // || !TypeUtil.compatibleTypeMatch(env, bodyType, featureType)) { // // String message = OCLMessages.bind( // OCLMessages.DefinitionConstraintConformance_ERROR_, // getName(bodyType), // getName(featureType)); // return validatorError(constraint, message, "visitConstraint"); //$NON-NLS-1$ // } // } return Boolean.TRUE; } /** * @param constraint * @param operationType * @param operationName * * @Return true if validation must terminate due to an error */ private Boolean visitBodyConditionConstraint(CT constraint, C operationType, String operationName) { C bodyType; // the expression must be of the form result = or // = result, where is some expression whose type // conforms to the operation type. However, this expression is // allowed to be nested inside any number of lets for the user's // convenience OCLExpression exp = uml.getSpecification(constraint).getBodyExpression(); while (exp instanceof LetExp) { @SuppressWarnings("unchecked") LetExp letExp = (LetExp) exp; exp = letExp.getIn(); } OperationCallExp body = null; if (exp instanceof OperationCallExp) { @SuppressWarnings("unchecked") OperationCallExp callExp = (OperationCallExp) exp; body = callExp; } if ((body == null) || (body.getOperationCode() != PredefinedType.EQUAL) || (body.getArgument().size() != 1)) { String message = OCLMessages.bind( OCLMessages.BodyConditionForm_ERROR_, operationName); return validatorError(constraint, message, "visitBodyConditionConstraint"); //$NON-NLS-1$ } OCLExpression bodyExpr; if (isResultVariable(body.getSource(), operationType)) { bodyExpr = body.getArgument().get(0); } else if (isResultVariable(body.getArgument().get(0), operationType)) { bodyExpr = body.getSource(); } else { String message = OCLMessages.bind( OCLMessages.BodyConditionForm_ERROR_, operationName); return validatorError(constraint, message, "visitBodyConditionConstraint");//$NON-NLS-1$ } bodyType = bodyExpr.getType(); if ((TypeUtil.getRelationship(env, bodyType, operationType) & UMLReflection.SUBTYPE) == 0) { String message = OCLMessages.bind( OCLMessages.BodyConditionConformance_ERROR_, new Object[] { operationName, getName(bodyType), getName(operationType)}); return validatorError(constraint, message, "visitBodyConditionConstraint");//$NON-NLS-1$ } // one last check: does the "body" part of the condition include // the result variable? It must not if (findResultVariable(bodyExpr, operationType)) { String message = OCLMessages.bind( OCLMessages.BodyConditionForm_ERROR_, operationName); return validatorError(constraint, message, "visitBodyConditionConstraint");//$NON-NLS-1$ } return Boolean.FALSE; } /** * Obtains the constrained element that is a classifier. * * @param constrainedElement a list of constrained elements * @return the constrained operation, if any */ @SuppressWarnings("unchecked") private O getConstrainedOperation(List constrainedElement) { for (Object constrained : constrainedElement) { if (uml.isOperation(constrained)) { return (O) constrained; } } return null; } /** * Obtains the constrained element that is a classifier. * * @param constrainedElement a list of constrained elements * @return the constrained property, if any */ @SuppressWarnings("unchecked") private P getConstrainedProperty(List constrainedElement) { for (Object constrained : constrainedElement) { if (uml.isProperty(constrained)) { return (P) constrained; } } return null; } /** * Obtains the constrained element that is a classifier. * * @param constrainedElement a list of constrained elements * @return the constrained classifier, if any */ @SuppressWarnings("unchecked") private C getConstrainedClassifier(List constrainedElement) { for (Object constrained : constrainedElement) { if (uml.isClassifier(constrained)) { return (C) constrained; } } return null; } /** * Obtains the contextual classifier of an expression. * * @param expression the expression * @return its contextual classifier, or null if none */ private C getContextualClassifier(ExpressionInOCL expression) { Variable selfVar = expression.getContextVariable(); return (selfVar == null)? null : selfVar.getType(); } /** * Null-safe alternative to {@link ENamedElement#getName()}. * * @param element a named element that may be null * @return the element's name, or null if the element is null */ String getName(Object element) { return (element == null)? null : uml.getName(element); } /** * Obtains the type of a meta-element, ensuring that the result is * canonicalized with respect to the current environment (via its type * resolver). * * @param metaElement a typed meta-element * * @return the OCL type corresponding to the element's type * * @see UMLReflection#getOCLType(Object) * * @since 1.2 */ protected C getOCLType(Object metaElement) { return TypeUtil.resolveType(env, uml.getOCLType(metaElement)); } private OCLStandardLibrary getStandardLibrary() { return env.getOCLStandardLibrary(); } /** * Determines whether the specified expression is a reference to the * special result variable of an operation body constraint. * * @param expr an OCL expression * @param expectedType the expected type of the result variable (i.e., * the operation type * * @return true if it is the result variable; * false, otherwise */ private boolean isResultVariable(OCLExpression expr, C expectedType) { // the implicitly defined "result" variable always has the same type // as the operation boolean result = (expr instanceof VariableExp); if (result) { result = TypeUtil.exactTypeMatch(env, expr.getType(), expectedType); } if (result) { @SuppressWarnings("unchecked") Variable var = ((VariableExp) expr).getReferredVariable(); // the result variable is a context variable, contained in the // ExpressionInOcl::resultVariable property result = (var != null) && Environment.RESULT_VARIABLE_NAME.equals(var.getName()) && (var.eContainmentFeature() == UtilitiesPackage.Literals.EXPRESSION_IN_OCL__RESULT_VARIABLE); } return result; } /** * Queries whether the special result variable can be found * anywhere in the specified OCL expression. * * @param expr the expression to search * @param expectedType the expected type of the result variable * * @return true if it includes some reference to the result * variable; false, otherwise */ private boolean findResultVariable( OCLExpression expr, final C expectedType) { class ResultFinder extends AbstractVisitor< Variable, C, O, P, EL, PM, S, COA, SSA, CT> { boolean found = false; @Override public Variable visitVariableExp(VariableExp v) { if (isResultVariable(v, expectedType)) { found = true; return v.getReferredVariable(); } // no need to call super because this is a leaf expression return null; } } ResultFinder finder = new ResultFinder(); expr.accept(finder); return finder.found; } } // ValidationVisitorImpl




© 2015 - 2024 Weber Informatics LLC | Privacy Policy