blob: 8a4ae1184646cb9a3d3ba63c26a4c5b569c66680 [file] [log] [blame]
/**
* <copyright>
*
* Copyright (c) 2005, 2008 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
*
* </copyright>
*
* $Id: ValidationVisitor.java,v 1.4 2008/02/04 17:17:31 cdamus 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<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>
implements Visitor<Boolean, C, O, P, EL, PM, S, COA, SSA, CT> {
private Environment<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> env = null;
private UMLReflection<PK, C, O, P, EL, PM, S, COA, SSA, CT> uml = null;
/**
* 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 <code>null</code>)
*
* @return a validation visitor instance for the specified environment
*/
public static <PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>
Visitor<Boolean, C, O, P, EL, PM, S, COA, SSA, CT> getInstance(
Environment<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> environment) {
if (environment == null) {
throw new NullPointerException();
}
return new ValidationVisitor<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>(environment);
}
/**
* Initializes me to validate expressions in the specified environment.
*
* @param environment the environment
*
* @since 1.2
*/
protected ValidationVisitor(
Environment<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> 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<C, O> oc) {
OCLExpression<C> source = oc.getSource();
O oper = oc.getReferredOperation();
int opcode = oc.getOperationCode();
List<OCLExpression<C>> 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<C> 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 (sourceType instanceof PredefinedType) {
if (opcode != OCLStandardLibraryUtil.getOperationCode(operName)) {
String message = OCLMessages.bind(
OCLMessages.IllegalOpcode_ERROR_,
operName);
return validatorError(oc, message, "visitOperationCallExp");//$NON-NLS-1$
}
resultType = OCLStandardLibraryUtil.getResultTypeOf(
oc, env, sourceType, opcode, args);
if (resultType == null) {
// maybe this operation was an "extra" contribution by a
// custom environment implementation
resultType = uml.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 = OCLStandardLibraryUtil.getResultTypeOf(
oc, env, sourceType, opcode, args);
if (resultType == null) {
resultType = uml.getOCLType(oper);
}
} else {
// user-defined operation
resultType = uml.getOCLType(oper);
}
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<C, EL> 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<C, PM> v) {
// get the referred variable name
Variable<C, PM> 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<C, P> pc) {
P property = pc.getReferredProperty();
OCLExpression<C> 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<OCLExpression<C>> qualifiers = pc.getQualifier();
if (!qualifiers.isEmpty()) {
// navigation qualifiers must conform to expected qualifier types
List<P> 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<P> eiter = expectedQualifierTypes.iterator();
Iterator<OCLExpression<C>> qiter = qualifiers.iterator();
while (eiter.hasNext()) {
C expectedType = uml.getOCLType(eiter.next());
OCLExpression<C> 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<C, O> ct = (CollectionType<C, O>) refType;
refType = ct.getElementType();
}
return Boolean.valueOf(TypeUtil.exactTypeMatch(env, refType, type));
}
/**
* Callback for an AssociationClassCallExp visit. Well-formedness rules:
* <ul>
* <li>the type of the AssociationClassCallExp is the type of the
* referenced EReference</li>
* <li>the referenced EReference is an AssociationClassEnd, and its
* associationClass reference is not null</li>
* </ul>
*
* @param ae
* the association end expression
* @return Boolean -- true if validated
*/
public Boolean visitAssociationClassCallExp(AssociationClassCallExp<C, P> ae) {
C ref = ae.getReferredAssociationClass();
OCLExpression<C> 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<C, O>) 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<C, PM> vd) {
String varName = vd.getName();
if (varName == null) {
return validatorError(vd, OCLMessages.MissingNameInVariableDeclaration_ERROR_,
"visitVariableDeclaration");//$NON-NLS-1$
}
C type = vd.getType();
OCLExpression<C> 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<C> i) {
OCLExpression<C> cond = i.getCondition();
OCLExpression<C> thenexp = i.getThenExpression();
OCLExpression<C> 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<C, COA, SSA> 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<OCLExpression<C>> 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<OCLExpression<C>> argsIter =
arguments.iterator();
while (paramsIter.hasNext()) {
Object param = paramsIter.next();
OCLExpression<C> arg = argsIter.next();
if (!TypeUtil.compatibleTypeMatch(env, arg.getType(), uml.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<C> 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<C> 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<C> 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<C> 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<C> 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<C> 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<C> 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<C, PM> l) {
Variable<C, PM> vd = l.getVariable();
OCLExpression<C> 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<C, PM> ie) {
// get the variable declaration for the result
Variable<C, PM> vd = ie.getResult();
C type = ie.getType();
OCLExpression<C> body = ie.getBody();
OCLExpression<C> source = ie.getSource();
List<Variable<C, PM>> 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<C, PM> 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<C, O> ct = (CollectionType<C, O>) 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<C, PM> ie) {
C type = ie.getType();
OCLExpression<C> body = ie.getBody();
OCLExpression<C> source = ie.getSource();
List<Variable<C, PM>> 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)) {
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<C, O> sourceCT = (CollectionType<C, O>) source.getType();
@SuppressWarnings("unchecked")
CollectionType<C, O> bodyCT = (CollectionType<C, O>) 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<C, PM> 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<C, O> ct = (CollectionType<C, O>) 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<C> 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<CollectionLiteralPart<C>> parts = cl.getPart();
@SuppressWarnings("unchecked")
CollectionType<C, O> collectionType = (CollectionType<C, O>) 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<C> 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<C> item) {
return item.getItem().accept(this);
}
public Boolean visitCollectionRange(CollectionRange<C> 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<C, P> 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<TupleLiteralPart<C, P>> 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<String> names = new java.util.HashSet<String>();
// Match each property with a tuple part
for (TupleLiteralPart<C, P> 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<C, P> 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, uml.getOCLType(property), type)) {
String message = OCLMessages.bind(
OCLMessages.TuplePartType_ERROR_,
tp.getName(),
tp.eContainer().toString());
return validatorError(tp, message, "visitTupleLiteralPart");//$NON-NLS-1$
}
OCLExpression<C> 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<C, S> 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
*/
private Boolean visitFeatureCallExp(FeatureCallExp<C> 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<C> source = exp.getSource();
if (source.getType() instanceof TypeType) {
@SuppressWarnings("unchecked")
TypeType<C, ?> typeType = (TypeType<C, ?>) 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<C> 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<C> 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<C, PM> expression) {
if (expression.getContextVariable() == null) {
String message = OCLMessages.MissingContextVariable_ERROR_;
return validatorError(expression, message, "visitExpressionInOCL");//$NON-NLS-1$
}
OCLExpression<C> 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<PM> parameters = uml.getParameters(operation);
List<Variable<C, PM>> variables = expression.getParameterVariable();
if (parameters.size() != variables.size()) {
String message = OCLMessages.MismatchedParameterVariables_ERROR_;
return validatorError(constraint, message, "visitExpressionInOCL");//$NON-NLS-1$
}
Iterator<PM> iter = parameters.iterator();
for (Variable<C, PM> var : expression.getParameterVariable()) {
PM param = iter.next();
var.accept(this);
C paramType = uml.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<C, PM> resultVar = expression.getResultVariable();
C operType = null;
String stereotype = uml.getStereotype(constraint);
if (UMLReflection.BODY.equals(stereotype)
|| UMLReflection.POSTCONDITION.equals(stereotype)) {
operType = uml.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 <code>null</code>)
* @param body its body expression (not <code>null</code>)
*
* @return whether it is well-formed
*/
Boolean checkExpressionInOCL(ExpressionInOCL<C, PM> expression, CT constraint,
OCLExpression<C> body) {
String stereotype = uml.getStereotype(constraint);
List<EObject> 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) ? uml.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) ? uml.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 <tt>expression</tt>
* 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<C, PM> expression,
C constrainedClassifier, List<EObject> 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 <tt>expression</tt>
* 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
* <tt>constrainedElement</tt> 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<C, PM> expression,
Object constrainedFeature, List<EObject> 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<C, PM> 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 = uml.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 = <expr> or
// <expr> = result, where <expr> 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<C> exp = uml.getSpecification(constraint).getBodyExpression();
while (exp instanceof LetExp) {
exp = ((LetExp<C, PM>) exp).getIn();
}
OperationCallExp<C, O> body = null;
if (exp instanceof OperationCallExp) {
body = (OperationCallExp<C, O>) exp;
}
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<C> 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 <code>null</code> if none
*/
private C getContextualClassifier(ExpressionInOCL<C, PM> expression) {
Variable<C, PM> selfVar = expression.getContextVariable();
return (selfVar == null)? null : selfVar.getType();
}
/**
* Null-safe alternative to {@link ENamedElement#getName()}.
*
* @param element a named element that may be <code>null</code>
* @return the element's name, or <code>null</code> if the element is <code>null</code>
*/
String getName(Object element) {
return (element == null)? null : uml.getName(element);
}
private OCLStandardLibrary<C> getStandardLibrary() {
return env.getOCLStandardLibrary();
}
/**
* Determines whether the specified expression is a reference to the
* special <code>result</code> 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 <code>true</code> if it is the result variable;
* <code>false</code>, otherwise
*/
private boolean isResultVariable(OCLExpression<C> 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) {
Variable<C, PM> var = ((VariableExp<C, PM>) 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 <code>result</code> 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 <code>true</code> if it includes some reference to the result
* variable; <code>false</code>, otherwise
*/
private boolean findResultVariable(
OCLExpression<C> expr,
final C expectedType) {
class ResultFinder extends AbstractVisitor<
Variable<C, PM>, C, O, P, EL, PM, S, COA, SSA, CT> {
boolean found = false;
@Override
public Variable<C, PM>
visitVariableExp(VariableExp<C, PM> 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