blob: c82971391bbe21c35263a341713066982f985b68 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005,2013 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
* Zeligsoft - Bug 253252
* Radek Dvorak - Bugs 261128, 265066
* E.D.Willink - Bug 297541
* Axel Uhl (SAP AG) - Bug 342644
* Christian W. Damus (CEA LIST) - Bug 416373
*******************************************************************************/
package org.eclipse.ocl;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.ecore.EAnnotation;
import org.eclipse.emf.ecore.EModelElement;
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.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.OCLPlugin;
import org.eclipse.ocl.internal.OCLStatusCodes;
import org.eclipse.ocl.internal.evaluation.CachedTypeChecker;
import org.eclipse.ocl.internal.evaluation.IterationTemplate;
import org.eclipse.ocl.internal.evaluation.IterationTemplateAny;
import org.eclipse.ocl.internal.evaluation.IterationTemplateClosure;
import org.eclipse.ocl.internal.evaluation.IterationTemplateCollect;
import org.eclipse.ocl.internal.evaluation.IterationTemplateCollectNested;
import org.eclipse.ocl.internal.evaluation.IterationTemplateExists;
import org.eclipse.ocl.internal.evaluation.IterationTemplateForAll;
import org.eclipse.ocl.internal.evaluation.IterationTemplateIsUnique;
import org.eclipse.ocl.internal.evaluation.IterationTemplateOne;
import org.eclipse.ocl.internal.evaluation.IterationTemplateReject;
import org.eclipse.ocl.internal.evaluation.IterationTemplateSelect;
import org.eclipse.ocl.internal.evaluation.IterationTemplateSortedBy;
import org.eclipse.ocl.internal.l10n.OCLMessages;
import org.eclipse.ocl.options.EvaluationOptions;
import org.eclipse.ocl.parser.AbstractOCLAnalyzer;
import org.eclipse.ocl.types.AnyType;
import org.eclipse.ocl.types.BagType;
import org.eclipse.ocl.types.CollectionType;
import org.eclipse.ocl.types.InvalidType;
import org.eclipse.ocl.types.OrderedSetType;
import org.eclipse.ocl.types.PrimitiveType;
import org.eclipse.ocl.types.SequenceType;
import org.eclipse.ocl.types.SetType;
import org.eclipse.ocl.types.VoidType;
import org.eclipse.ocl.util.CollectionUtil;
import org.eclipse.ocl.util.OCLStandardLibraryUtil;
import org.eclipse.ocl.util.OCLUtil;
import org.eclipse.ocl.util.ObjectUtil;
import org.eclipse.ocl.util.UnicodeSupport;
import org.eclipse.ocl.utilities.PredefinedType;
/**
* An evaluation visitor implementation for OCL expressions.
*
* @author Tim Klinger (tklinger)
* @author Christian W. Damus (cdamus)
*
* @since 1.3
*/
public class EvaluationVisitorImpl<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E>
extends AbstractEvaluationVisitor<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> {
private static final Integer UNLIMITED = Integer.valueOf(UnlimitedNaturalLiteralExp.UNLIMITED);
private static int tempCounter = 0;
private static final String DELIMS = " \t\n\r\f"; //$NON-NLS-1$
// This is the same as HashMap's default initial capacity
private static final int DEFAULT_REGEX_CACHE_LIMIT = 16;
// this is the same as HashMap's default load factor
private static final float DEFAULT_REGEX_CACHE_LOAD_FACTOR = 0.75f;
private static List<String> tokenize(String sourceString, String delims, boolean returnDelims) {
StringTokenizer tokenizer = new StringTokenizer(sourceString, delims, returnDelims);
List<String> results = new ArrayList<String>();
while (tokenizer.hasMoreTokens()) {
results.add(tokenizer.nextToken());
}
return results;
}
private EvaluationEnvironment.Enumerations<EL> enumerations;
/**
* Cache supporting dynamic operation lookup.
*/
private final TypeChecker.Cached<C, O, P> cachedTypeChecker;
/**
* Lazily-created cache of reusable regex pattern matchers to avoid
* repeatedly parsing the same regexes.
*/
private Map<String, Matcher> regexMatchers;
/**
* Constructor
*
* @param env
* an evaluation environment (map of variable names to values)
* @param extentMap
* a map of classes to their instance lists
*/
@SuppressWarnings("unchecked")
public EvaluationVisitorImpl(
Environment<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> env,
EvaluationEnvironment<C, O, P, CLS, E> evalEnv,
Map<? extends CLS, ? extends Set<? extends E>> extentMap) {
super(env, evalEnv, extentMap);
enumerations = OCLUtil.getAdapter(evalEnv, EvaluationEnvironment.Enumerations.class);
boolean dynamicDispatch = EvaluationOptions.getValue(evalEnv, EvaluationOptions.DYNAMIC_DISPATCH);
if (dynamicDispatch) {
cachedTypeChecker = createTypeChecker();
}
else {
cachedTypeChecker = null;
}
}
/**
* Create the TypeChecker used to facilitate dynamic dispatch.
*
* The default implementation attempts to re-use an analysis type checker, creating
* an evaluation one if no analysis one available.
*
* @since 3.2
*/
protected TypeChecker.Cached<C,O,P> createTypeChecker() {
Environment<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> environment = getEnvironment();
@SuppressWarnings("unchecked")
TypeChecker<C, O, P> typeChecker = OCLUtil.getAdapter(environment, TypeChecker.class);
if (typeChecker instanceof TypeChecker.Cached<?,?,?>) {
return (TypeChecker.Cached<C,O,P>)typeChecker;
}
else {
return new CachedTypeChecker<C, O, P, PM>(environment);
}
}
private boolean isBooleanOperation(int opCode) {
return opCode == PredefinedType.AND ||
opCode == PredefinedType.OR ||
opCode == PredefinedType.NOT ||
opCode == PredefinedType.XOR ||
opCode == PredefinedType.IMPLIES;
}
/**
*
* Callback for an OperationCallExp visit.
*
*/
@Override
public Object visitOperationCallExp(OperationCallExp<C, O> oc) {
// check if source type is primitive and handle the
// primitive ops "inline". Otherwise use java reflection
// to invoke the operation (there is currently no means
// to do this directly in EMF).
// Note: Generally the result of an operation invocation on the
// undefined
// object or with an undefined argument is undefined except in the
// following
// cases prescribed by the spec (p. 2-10, sec. 2.4.11)
// 1. true || <anything> is true
// 2. false && <anything> is false
// 3. false implies <anything> is true
// 4. if <condition> <something1> else <something2> has the value
// dictated
// by the condition regardless of the other value.
// all irrespective of the order of the arguments.
OCLExpression<C> source = oc.getSource();
C sourceType = source.getType();
O oper = oc.getReferredOperation();
int opCode = oc.getOperationCode();
List<OCLExpression<C>> args = oc.getArgument();
int numArgs = args.size();
// evaluate source
Object sourceVal = safeVisitExpression(source);
// resolve operation dynamically, before attempting to get any body since
// built-in operations may be overloaded by Java/OCL functions with bodies.
if (cachedTypeChecker != null) {
C dynamicSourceType = getEvaluationEnvironment().getType(sourceVal);
oper = cachedTypeChecker.getDynamicOperation(dynamicSourceType, oper);
if (oper == null) { // Ambiguous overload
return getInvalid();
}
}
OCLExpression<C> body = getOperationBody(oper);
if ((body != null) || opCode <= 0 /* not a pre-defined operation */
|| getEvaluationEnvironment().overrides(oper, opCode)) {
// delegate evaluation to the evaluation environment
// evaluate args
Object[] evalArgs = new Object[numArgs];
int i = 0;
for (Iterator<OCLExpression<C>> it = args.iterator(); it.hasNext(); i++) {
OCLExpression<C> arg = it.next();
evalArgs[i] = safeVisitExpression(arg);
}
// ask the environment to evaluate
try {
Object result;
if (body != null) {
// if source is undefined, result is OclInvalid
if (isUndefined(sourceVal)) {
return getInvalid();
}
result = call(oper, body, sourceVal, evalArgs);
} else {
// handle <, <=, >, and >= operators (only needed for UML)
if (opCode <= 0) {
opCode = inferOperationCode(oper, opCode);
}
result = getEvaluationEnvironment().callOperation(
oper, opCode, sourceVal, evalArgs);
}
return result;
} catch (EvaluationHaltedException e) {
// evaluation stopped on demand, propagate father
throw e;
} catch (UnsupportedOperationException ignore) {
// let the EvaluationVisitor do its thing
} catch (Exception e) {
OCLPlugin
.catching(getClass(), "visitOperationCallExp", e);//$NON-NLS-1$
OCLPlugin.log(
Diagnostic.ERROR,
OCLStatusCodes.IGNORED_EXCEPTION_WARNING,
OCLMessages.bind(
OCLMessages.ErrorMessage_ERROR_,
"visitOperationCallExp", //$NON-NLS-1$
e.getLocalizedMessage()),
e);
return getInvalid();
}
}
// inline primitive and collection operation evaluation for increased
// efficiency
// We handle equals and notEquals separately since they require type
// checking
// The semantics for equality are as follows:
//
// Define primtive(type) := type in {Boolean, String, Integer, Double,
// Void}
//
// For the expression x = y, let t1 = runtimeType(x1), t2 =
// runtimeType(x2)
//
// if primitive(t1) or primitive(t2) then
// we use the java semantics for the corresponding built-in primitive
// types EXCEPT for
// the following cases:
// (1) when one or the type is Void, the result is true just when both x
// and y are undefined.
// (2) when the t1 and t2 are non-conformant (for example t1 = String,
// t2 = Integer) then
// the result is false.
//
// For example,
// "1 = 1.0" evaluates to true (unlike "(new Integer(1)).equals(new
// Double(1.0))" which evalutes to false).
// "1 = 'x'" evalutes to false
// "(1/0) = 1" evaluates to false
// "(1/0) = (1/0)" evaluates to true
//
// otherwise, for non-primitive types, we use the "equals" method to
// determine equality, which is, by default,
// object identity.
//
// The semantics for inequality are dual.
//
if (opCode == PredefinedType.EQUAL) {
if (sourceVal == getInvalid()) {
return getInvalid();
}
// evaluate argument
OCLExpression<C> arg = args.get(0);
Object argVal = safeVisitExpression(arg);
if (argVal == getInvalid()) {
return argVal;
}
if (sourceVal instanceof Number) {
// coerce to Long or Double, if possible, for comparison
sourceVal = higherPrecisionNumber((Number) sourceVal);
}
if (argVal instanceof Number) {
// coerce to Long or Double, if possible, for comparison
argVal = higherPrecisionNumber((Number) argVal);
}
return Boolean.valueOf(ObjectUtil.equal(sourceVal, argVal));
}
else if (opCode == PredefinedType.NOT_EQUAL) {
if (sourceVal == getInvalid()) {
return getInvalid();
}
// notEquals
// evaluate argument
OCLExpression<C> arg = args.get(0);
Object argVal = safeVisitExpression(arg);
if (argVal == getInvalid()) {
return argVal;
}
if (sourceVal instanceof Number) {
// coerce to Long or Double, if possible, for comparison
sourceVal = higherPrecisionNumber((Number) sourceVal);
}
if (argVal instanceof Number) {
// coerce to Long or Double, if possible, for comparison
argVal = higherPrecisionNumber((Number) argVal);
}
return Boolean.valueOf(!ObjectUtil.equal(sourceVal, argVal));
}
else if (opCode == PredefinedType.TO_STRING) {
if (sourceVal == null) {
return "null"; //$NON-NLS-1$
}
else if (sourceVal == getInvalid()) {
return "invalid"; //$NON-NLS-1$
}
else if (sourceType == getUnlimitedNatural()) {
return "*"; //$NON-NLS-1$
}
else {
return sourceVal.toString();
}
}
// AnyType::oclAsSet()
else if (opCode == PredefinedType.OCL_AS_SET) {
if (sourceVal == getInvalid()) {
return sourceVal;
}
Set<Object> resultSet = CollectionUtil.createNewSet();
if (sourceVal != null) {
resultSet.add(sourceVal);
}
return resultSet;
}
if (sourceType instanceof PrimitiveType<?>
|| sourceType instanceof CollectionType<?, ?>
|| getUMLReflection().isEnumeration(sourceType)
|| getUMLReflection().isDataType(sourceType)
|| (sourceType instanceof VoidType<?>) || (sourceType instanceof InvalidType<?>)) {
if (numArgs == 0) {
//
// unary operations:
//
// if source is undefined and the operation is not
// undefined, then this expression is invalid
if (isUndefined(sourceVal)
&& opCode != PredefinedType.OCL_IS_UNDEFINED
&& opCode != PredefinedType.OCL_IS_INVALID) {
return getInvalid();
}
// evaluate this operation
switch (opCode) {
case PredefinedType.MINUS:
// Integer::minus()
// -* doesn't exist, so evaluate to invalid
if (sourceType == getUnlimitedNatural() && UNLIMITED.equals(sourceVal)) {
return getInvalid();
}
if (sourceVal instanceof Integer) {
int intVal = (Integer)sourceVal;
if (intVal == Integer.MIN_VALUE) {
return Long.valueOf(-(long)intVal);
}
else {
return Integer.valueOf(-intVal);
}
} else if (sourceVal instanceof Long) {
long longVal = (Long)sourceVal;
if (longVal == -(long)Integer.MIN_VALUE) {
return Integer.valueOf(Integer.MIN_VALUE);
}
else {
return Long.valueOf(-longVal);
}
}
// Double::minus()
return - (Double) sourceVal;
case PredefinedType.ABS:
if (sourceVal instanceof Integer) {
int sourceInt = (Integer) sourceVal;
if (sourceType == getUnlimitedNatural()) {
// the unlimited value has no absolute
if (sourceInt == UnlimitedNaturalLiteralExp.UNLIMITED) {
return getInvalid();
}
}
// Integer::abs()
if (sourceInt == Integer.MIN_VALUE) {
return Long.valueOf(-(long)Integer.MIN_VALUE);
}
else {
return Math.abs(sourceInt);
}
} else if (sourceVal instanceof Long) {
long sourceInt = (Long) sourceVal;
if (sourceType == getUnlimitedNatural()) {
// the unlimited value has no absolute
if (sourceInt == UnlimitedNaturalLiteralExp.UNLIMITED) {
return getInvalid();
}
}
// Integer::abs()
return Math.abs(sourceInt);
}
// Real::abs()
return Math.abs((Double) sourceVal);
case PredefinedType.CHARACTERS:
String sourceString = (String)sourceVal;
List<String> results = new ArrayList<String>(sourceString.length());
for (int i = 0; i < sourceString.length(); i++) {
results.add(sourceString.substring(i, i+1));
}
return results;
case PredefinedType.FLOOR:
if (sourceVal instanceof Double) {
// Real::floor()
return (int) Math.floor((Double) sourceVal);
}
if (sourceType == getUnlimitedNatural()) {
long sourceInt = (Long) higherPrecisionNumber((Number) sourceVal);
// the unlimited value has no floor
if (sourceInt == UnlimitedNaturalLiteralExp.UNLIMITED) {
return getInvalid();
}
}
// Integer::floor()
return sourceVal;
case PredefinedType.ROUND:
if (sourceVal instanceof Double) {
// Real::round()
return (int) Math.round((Double) sourceVal);
}
if (sourceType == getUnlimitedNatural()) {
long sourceInt = (Long) higherPrecisionNumber((Number) sourceVal);
// the unlimited value can't be rounded
if (sourceInt == UnlimitedNaturalLiteralExp.UNLIMITED) {
return getInvalid();
}
}
// Integer::round()
return sourceVal;
case PredefinedType.NOT:
return (((Boolean) sourceVal).booleanValue()) ? Boolean.FALSE
: Boolean.TRUE;
case PredefinedType.OCL_IS_UNDEFINED:
// OclAny::oclIsUndefined()
return isUndefined(sourceVal)?
Boolean.TRUE : Boolean.FALSE;
case PredefinedType.OCL_IS_INVALID:
// OclAny::oclIsInvalid()
return (sourceVal == getInvalid())?
Boolean.TRUE : Boolean.FALSE;
case PredefinedType.SIZE:
if (sourceType == getString()) {
// String::size()
return new Integer(((String) sourceVal).length());
} else if (sourceType instanceof CollectionType<?, ?>) {
return new Integer(((Collection<?>) sourceVal).size());
}
case PredefinedType.TO_BOOLEAN:
// String::toInteger()
return "true".equals(sourceVal); //$NON-NLS-1$
case PredefinedType.TO_INTEGER:
// String::toInteger()
return Integer.valueOf((String) sourceVal);
case PredefinedType.TO_REAL:
// String::toReal()
return Double.valueOf((String) sourceVal);
case PredefinedType.TO_LOWER:
case PredefinedType.TO_LOWER_CASE:
// String::toLower()
return UnicodeSupport.toLowerCase((String) sourceVal);
case PredefinedType.TO_UPPER:
case PredefinedType.TO_UPPER_CASE:
// String::toUpper()
return UnicodeSupport.toUpperCase((String) sourceVal);
case PredefinedType.TOKENIZE:
return tokenize((String) sourceVal, DELIMS, false);
case PredefinedType.TRIM:
return ((String) sourceVal).trim();
case PredefinedType.IS_EMPTY:
// Collection::isEmpty()
return Boolean.valueOf(((Collection<?>) sourceVal)
.isEmpty());
case PredefinedType.NOT_EMPTY:
// Collection::notEmpty()
return Boolean.valueOf(!((Collection<?>) sourceVal)
.isEmpty());
case PredefinedType.SUM:
// Collection::sum()
Number num = (Number) CollectionUtil.sum((Collection<?>) sourceVal);
if (num == null) {
// empty collection
@SuppressWarnings("unchecked")
CollectionType<C, O> numCollType = (CollectionType<C, O>) sourceType;
C numType = numCollType.getElementType();
if (numType == getReal()) {
num = new Double(0.0);
} else if (numType == getInteger()) {
num = new Integer(0);
}
}
return num;
case PredefinedType.FLATTEN:
// Set, Bag, Sequence, OrderedSet::flatten()
return CollectionUtil.flatten((Collection<?>) sourceVal);
case PredefinedType.AS_SET:
// Set, Bag, Sequence, OrderedSet::asSet()
return CollectionUtil.asSet((Collection<?>) sourceVal);
case PredefinedType.AS_BAG:
// Set, Bag, Sequence, OrderedSet::asBag()
return CollectionUtil.asBag((Collection<?>) sourceVal);
case PredefinedType.AS_ORDERED_SET:
// Set, Bag, Sequence, OrderedSet::asOrderedSet()
return CollectionUtil.asOrderedSet((Collection<?>) sourceVal);
case PredefinedType.AS_SEQUENCE:
// Set, Bag, Sequence, OrderedSet::asSequence)
return CollectionUtil.asSequence((Collection<?>) sourceVal);
case PredefinedType.FIRST:
// OrderedSet::first()
if (((Collection<?>) sourceVal).isEmpty()) {
return getInvalid();
}
return CollectionUtil.first((Collection<?>) sourceVal);
case PredefinedType.LAST:
// OrderedSet::last()
if (((Collection<?>) sourceVal).isEmpty()) {
return getInvalid();
}
return CollectionUtil.last((Collection<?>) sourceVal);
case PredefinedType.MAX:
// Collection::sum()
return CollectionUtil.max((Collection<?>) sourceVal);
case PredefinedType.MIN:
// Collection::sum()
return CollectionUtil.min((Collection<?>) sourceVal);
} // end of unary operation switch
} else if (numArgs == 1) {
//
// binary operations:
//
// evaluate argument
OCLExpression<C> arg = args.get(0);
// get argument type
C argType = arg.getType();
if (isUndefined(sourceVal)) {
switch (opCode) {
case PredefinedType.OCL_IS_TYPE_OF:
case PredefinedType.OCL_IS_KIND_OF:
case PredefinedType.OCL_AS_TYPE:
case PredefinedType.AND:
case PredefinedType.OR:
case PredefinedType.XOR:
case PredefinedType.IMPLIES:
if (isLaxNullHandling()) {
break;
} else {
return getInvalid();
}
default:
return getInvalid();
}
}
// AnyType::oclIsTypeOf(OclType)
if (opCode == PredefinedType.OCL_IS_TYPE_OF) {
Object targetType = arg.accept(getVisitor());
// UnlimitedNatural is represented as Integer, so checking sourceVal's type
// doesn't work. Therefore, UnlimitedNatural needs to be handled here.
if (sourceType == getUnlimitedNatural()) {
return targetType == getUnlimitedNatural();
}
Boolean result = oclIsTypeOf(sourceVal, targetType);
if (result == null) {
return getInvalid();
} else {
return result;
}
} else if (opCode == PredefinedType.OCL_IS_KIND_OF) {
// no special check for Integer representation of UnlimitedNatural necessary
// because UnlimitedNatural is subtype of Integer
Object targetType = arg.accept(getVisitor());
// UnlimitedNatural is represented as Integer, so checking sourceVal's type
// doesn't work. Therefore, UnlimitedNatural needs to be handled here.
if (sourceType == getUnlimitedNatural() && targetType == getUnlimitedNatural()) {
return true; // other combinations properly handled since checked with Integer
}
Boolean result = oclIsKindOf(sourceVal, targetType);
if (result == null) {
return getInvalid();
} else {
return result;
}
} else if (opCode == PredefinedType.OCL_AS_TYPE) {
// Type conversions for the built-in, non-collection
// types are completely checked in the parser. The only
// actual work that
// needs to be done here is to convert from Any/Real to
// Integer
// and back (necessary since in OCL Integers extend
// Reals but this is not true of the java primtives).
// if the source is undefined or the conversion to
// OclVoid so is the result
if (sourceVal == null || (argType instanceof VoidType<?>)) {
return sourceVal;
}
if (sourceVal == getInvalid() || (argType instanceof InvalidType<?>)) {
return getInvalid();
}
if (sourceVal instanceof String
&& ((TypeExp<C>) arg).getReferredType() == getString()) {
return sourceVal;
} else if (sourceVal instanceof Double
&& (argType == getInteger())) {
return new Integer(((Double) sourceVal).intValue());
} else if (sourceVal instanceof Boolean
&& ((TypeExp<C>) arg).getReferredType() == getBoolean()) {
return sourceVal;
} else if (sourceVal instanceof Integer
&& (((TypeExp<C>) arg).getReferredType() == getReal())) {
if (sourceType == getUnlimitedNatural()) {
int sourceInt = (Integer) sourceVal;
// the unlimited value is invalid as Real because there
// is no positive infinity defined in the OCL Real type
if (sourceInt == UnlimitedNaturalLiteralExp.UNLIMITED) {
return getInvalid();
}
}
return new Double(((Integer) sourceVal).doubleValue());
} else if (sourceType == getUnlimitedNatural() && sourceVal.equals(UNLIMITED)
&& (((TypeExp<C>) arg).getReferredType() == getInteger())) {
// According to OCL 2.3 (10-11-42) Section 8.2.1, UnlimitedNatural value
// * is an invalid Integer.
return getInvalid();
} else if (((TypeExp<C>) arg).getReferredType() instanceof AnyType<?>) {
return sourceVal;
} else if ((sourceType == getUnlimitedNatural() && ((TypeExp<C>) arg).getReferredType() == getUnlimitedNatural()) ||
oclIsKindOf(sourceVal, ((TypeExp<C>) arg).getReferredType())) {
return sourceVal;
} else {
return getInvalid();
}
}
// evaluate arg, unless we have a boolean operation
Object argVal = null;
if (!isBooleanOperation(opCode)) {
argVal = safeVisitExpression(arg);
if (argVal == getInvalid()) {
return argVal; // an invalid argument leads to invalid operation call value
} // unless a boolean operation doesn't evaluate the arg
}
if (sourceVal instanceof Number) {
if (argVal == null) {
// one-arg numeric operation is invalid for null / undefined arg
return getInvalid();
}
// we have a numeric operation. Promote to high precision
sourceVal = higherPrecisionNumber((Number) sourceVal);
if (argVal instanceof Number) {
argVal = higherPrecisionNumber((Number) argVal);
}
}
if (sourceVal instanceof Long && argVal instanceof Long) {
//
// source and single arg are both integers
//
long sourceInt = (Long) sourceVal;
long argInt = (Long) argVal;
boolean sourceUnlimited =
sourceType == getUnlimitedNatural()
&& sourceInt == UnlimitedNaturalLiteralExp.UNLIMITED;
boolean argUnlimited =
argType == getUnlimitedNatural()
&& argInt == UnlimitedNaturalLiteralExp.UNLIMITED;
if (sourceUnlimited && argUnlimited) {
switch (opCode) {
case PredefinedType.LESS_THAN:
case PredefinedType.GREATER_THAN:
// See section 11.5.5 of 10-11-42 in OCL 2.3
return Boolean.FALSE;
case PredefinedType.GREATER_THAN_EQUAL:
case PredefinedType.LESS_THAN_EQUAL:
// See section 11.5.5 of 10-11-42 in OCL 2.3
return Boolean.TRUE;
default:
// cannot do arithmetic on the unlimited value
return getInvalid();
}
} else if (sourceUnlimited || argUnlimited) {
switch (opCode) {
case PredefinedType.LESS_THAN:
case PredefinedType.LESS_THAN_EQUAL:
return argUnlimited;
case PredefinedType.GREATER_THAN:
case PredefinedType.GREATER_THAN_EQUAL:
return sourceUnlimited;
default:
// cannot do arithmetic on the unlimited value
return getInvalid();
}
}
switch (opCode) {
// Integer::plus(Integer)
case PredefinedType.PLUS:
return coerceNumber(sourceInt + argInt);
// Integer::minus(Integer)
case PredefinedType.MINUS:
return coerceNumber(sourceInt - argInt);
// Integer::times(Integer)
case PredefinedType.TIMES:
return coerceNumber(sourceInt * argInt);
// Integer::divide(Integer)
case PredefinedType.DIVIDE: {
// denominator of 0 means undefined
double num = sourceInt;
double denom = argInt;
return (denom == 0.0) ? getInvalid() : num / denom;
}
// Integer::div(Integer)
case PredefinedType.DIV:
// denominator of 0 means undefined
return (argInt == 0) ? getInvalid() :
coerceNumber(sourceInt / argInt);
// Integer::mod(Integer)
case PredefinedType.MOD:
return coerceNumber(sourceInt % argInt);
// Integer::max(Integer)
case PredefinedType.MAX:
return coerceNumber(Math.max(sourceInt, argInt));
// Integer::min(Integer)
case PredefinedType.MIN:
return coerceNumber(Math.min(sourceInt, argInt));
// Integer::lessThan(Integer)
case PredefinedType.LESS_THAN:
return sourceInt < argInt;
// Integer::greaterThan(Integer)
case PredefinedType.GREATER_THAN:
return sourceInt > argInt;
// Integer::lessThanEqual(Integer)
case PredefinedType.LESS_THAN_EQUAL:
return sourceInt <= argInt;
// Integer::greaterThanEqual(Integer)
case PredefinedType.GREATER_THAN_EQUAL:
return sourceInt >= argInt;
default: {
String message = OCLMessages.bind(
OCLMessages.UnknownOperation_ERROR_,
getName(oper));
RuntimeException error = new RuntimeException(message);
OCLPlugin.throwing(getClass(),
"visitOperationCallExp", error);//$NON-NLS-1$
throw error;
}
}
} else if (sourceVal instanceof Long
&& argVal instanceof Double) {
//
// source is an integer and single arg is a real
//
long sourceInt = (Long) sourceVal;
double argReal = (Double) argVal;
if (sourceType == getUnlimitedNatural()) {
if (sourceInt == UnlimitedNaturalLiteralExp.UNLIMITED) {
switch (opCode) {
case PredefinedType.LESS_THAN:
// unlimited is not less than or equal to
// any Real value
return Boolean.FALSE;
case PredefinedType.GREATER_THAN:
case PredefinedType.GREATER_THAN_EQUAL:
// unlimited is greater than
// every Real value
return Boolean.TRUE;
default:
// cannot do arithmetic on the unlimited value
return getInvalid();
}
}
}
switch (opCode) {
// Integer::plus(Real)
case PredefinedType.PLUS:
return coerceNumber(sourceInt + argReal);
// Integer::minus(Real)
case PredefinedType.MINUS:
return coerceNumber(sourceInt - argReal);
// Integer::times(Real)
case PredefinedType.TIMES:
return coerceNumber(sourceInt * argReal);
// Integer::divide(Real)
case PredefinedType.DIVIDE:
// denominator of 0 results in undefined
return (argReal == 0.0) ? getInvalid() : sourceInt / argReal;
// Integer::max(Real)
case PredefinedType.MAX:
return coerceNumber(Math.max(sourceInt, argReal));
// Integer::min(Real)
case PredefinedType.MIN:
return coerceNumber(Math.min(sourceInt, argReal));
// Integer::lessThan(Real)
case PredefinedType.LESS_THAN:
return sourceInt < argReal;
// Integer::greaterThan(Real)
case PredefinedType.GREATER_THAN:
return sourceInt > argReal;
// Integer::lessThanEqual(Real)
case PredefinedType.LESS_THAN_EQUAL:
return sourceInt <= argReal;
// Integer::greaterThanEqual(Real)
case PredefinedType.GREATER_THAN_EQUAL:
return sourceInt >= argReal;
default: {
String message = OCLMessages.bind(
OCLMessages.UnknownOperation_ERROR_,
getName(oper));
RuntimeException error = new RuntimeException(message);
OCLPlugin.throwing(getClass(),
"visitOperationCallExp", error);//$NON-NLS-1$
throw error;
}
}
}
else if (sourceVal instanceof Double
&& argVal instanceof Long) {
double sourceReal = (Double) sourceVal;
long argInt = (Long) argVal;
//
// source is a real and single arg is an integer
//
if (argType == getUnlimitedNatural()) {
if (argInt == UnlimitedNaturalLiteralExp.UNLIMITED) {
switch (opCode) {
case PredefinedType.LESS_THAN:
// unlimited is greater than
// every Real value
return Boolean.TRUE;
case PredefinedType.GREATER_THAN:
case PredefinedType.GREATER_THAN_EQUAL:
// unlimited is not less than or equal to
// any Real value
return Boolean.FALSE;
default:
// cannot do arithmetic on the unlimited value
return getInvalid();
}
}
}
// for these arithmetic operations, don't need to coerce
// the result to any other precision because OCL Reals are
// represented as Doubles, anyway
switch (opCode) {
// Real::plus(Integer)
case PredefinedType.PLUS:
return sourceReal + argInt;
// Real::minus(Integer)
case PredefinedType.MINUS:
return sourceReal - argInt;
// Real::times(Integer)
case PredefinedType.TIMES:
return sourceReal * argInt;
// Real::divide(Integer)
case PredefinedType.DIVIDE:
// denominator of 0 results in undefined
return (argInt == 0) ? getInvalid() : sourceReal / argInt;
// Real::max(Integer)
case PredefinedType.MAX:
return Math.max(sourceReal, argInt);
// Real::min(Integer)
case PredefinedType.MIN:
return Math.min(sourceReal, argInt);
// Real::lessThan(Integer)
case PredefinedType.LESS_THAN:
return sourceReal < argInt;
// Real::greaterThan(Integer)
case PredefinedType.GREATER_THAN:
return sourceReal > argInt;
// Real::lessThanEqual(Integer)
case PredefinedType.LESS_THAN_EQUAL:
return sourceReal <= argInt;
// Real::greaterThanEqual(Integer)
case PredefinedType.GREATER_THAN_EQUAL:
return sourceReal >= argInt;
default: {
String message = OCLMessages.bind(
OCLMessages.UnknownOperation_ERROR_,
getName(oper));
RuntimeException error = new RuntimeException(message);
OCLPlugin.throwing(getClass(),
"visitOperationCallExp", error);//$NON-NLS-1$
throw error;
}
}
} else if (sourceVal instanceof Double
&& argVal instanceof Double) {
double sourceReal = (Double) sourceVal;
double argReal = (Double) argVal;
//
// source is a real and single arg is a real
//
switch (opCode) {
// Real::plus(Real)
case PredefinedType.PLUS:
return sourceReal + argReal;
// Real::minus(Real)
case PredefinedType.MINUS:
return sourceReal - argReal;
// Real::times(Real)
case PredefinedType.TIMES:
return sourceReal * argReal;
// Real::divide(Real)
case PredefinedType.DIVIDE:
// denominator of 0 results in undefined
return (argReal == 0.0) ? getInvalid() : sourceReal / argReal;
// Real::max(Real)
case PredefinedType.MAX:
return Math.max(sourceReal, argReal);
// Real::min(Real)
case PredefinedType.MIN:
return Math.min(sourceReal, argReal);
// Real::lessThan(Real)
case PredefinedType.LESS_THAN:
return sourceReal < argReal;
// Real::greaterThan(Real)
case PredefinedType.GREATER_THAN:
return sourceReal > argReal;
// Real::lessThanEqual(Real)
case PredefinedType.LESS_THAN_EQUAL:
return sourceReal <= argReal;
// Real::greaterThanEqual(Real)
case PredefinedType.GREATER_THAN_EQUAL:
return sourceReal >= argReal;
default: {
String message = OCLMessages.bind(
OCLMessages.UnknownOperation_ERROR_,
getName(oper));
RuntimeException error = new RuntimeException(message);
OCLPlugin.throwing(getClass(),
"visitOperationCallExp", error);//$NON-NLS-1$
throw error;
}
}
} else if (sourceVal instanceof Boolean || isBooleanOperation(opCode)) {
// the logic with an undefined value is basic 3-valued
// logic:
// null represents the undefined value
// boolean source and single boolean arg
switch (opCode) {
// Boolean::or(Boolean)
case PredefinedType.OR:
if (Boolean.TRUE.equals(sourceVal)) {
return Boolean.TRUE;
}
// must evaluate the argument now
argVal = arg.accept(getVisitor());
if (Boolean.TRUE.equals(argVal)) {
return Boolean.TRUE;
}
if (isUndefined(sourceVal) || isUndefined(argVal)) {
return getInvalid();
}
return Boolean.FALSE;
// Boolean::xor(Boolean)
case PredefinedType.XOR:
// XOR does not have a short-circuit
argVal = arg.accept(getVisitor());
if (isUndefined(sourceVal) || isUndefined(argVal)) {
return getInvalid();
}
return (argVal == null) ? sourceVal
: (((Boolean) sourceVal).booleanValue()
^ ((Boolean) argVal).booleanValue() ? Boolean.TRUE
: Boolean.FALSE);
// Boolean::and(Boolean)
case PredefinedType.AND:
if (Boolean.FALSE.equals(sourceVal)) {
return Boolean.FALSE;
}
// must evaluate the argument now
argVal = arg.accept(getVisitor());
if (Boolean.FALSE.equals(argVal)) {
return Boolean.FALSE;
}
if (isUndefined(sourceVal) || isUndefined(argVal)) {
return getInvalid();
}
return Boolean.TRUE;
// Boolean::implies
case PredefinedType.IMPLIES:
if (Boolean.FALSE.equals(sourceVal)) {
return Boolean.TRUE;
}
// must evaluate the argument now
argVal = arg.accept(getVisitor());
if (Boolean.TRUE.equals(argVal)) {
return Boolean.TRUE;
}
if (isUndefined(sourceVal) || isUndefined(argVal)) {
return getInvalid();
}
return argVal;
default: {
String message = OCLMessages.bind(
OCLMessages.UnknownOperation_ERROR_,
getName(oper));
RuntimeException error = new RuntimeException(message);
OCLPlugin.throwing(getClass(),
"visitOperationCallExp", error);//$NON-NLS-1$
throw error;
}
}
}
else if (sourceVal instanceof String) {
if (isUndefined(argVal)) {
return getInvalid();
}
switch (opCode) {
// String::concat(String)
case PredefinedType.CONCAT:
case PredefinedType.PLUS:
return ((String) sourceVal).concat((String) argVal);
// Handle < (lessThan)
case PredefinedType.LESS_THAN:
return Boolean.valueOf(((String) sourceVal).compareTo((String) argVal) < 0);
// Handle <= (lessThanEqual)
case PredefinedType.LESS_THAN_EQUAL:
return Boolean.valueOf(((String) sourceVal).compareTo((String) argVal) <= 0);
// Handle > (greaterThan)
case PredefinedType.GREATER_THAN:
return Boolean.valueOf(((String) sourceVal).compareTo((String) argVal) > 0);
// Handle > (greaterThanEqual)
case PredefinedType.GREATER_THAN_EQUAL:
return Boolean.valueOf(((String) sourceVal).compareTo((String) argVal) >= 0);
case PredefinedType.AT:
if (!(argVal instanceof Integer)) {
return getInvalid();
}
return String.valueOf(((String) sourceVal).substring((Integer) argVal-1, (Integer) argVal));
case PredefinedType.ENDS_WITH:
return Boolean.valueOf(((String) sourceVal).endsWith((String) argVal));
case PredefinedType.EQUALS_IGNORE_CASE:
return Boolean.valueOf(((String) sourceVal).equalsIgnoreCase((String) argVal));
case PredefinedType.INDEX_OF:
return Integer.valueOf(1 + ((String) sourceVal).indexOf((String) argVal));
case PredefinedType.LAST_INDEX_OF:
return Integer.valueOf(1 + ((String) sourceVal).lastIndexOf((String) argVal));
case PredefinedType.MATCHES:
return Boolean.valueOf(getRegexMatcher((String) argVal, (String) sourceVal).matches());
case PredefinedType.STARTS_WITH:
return Boolean.valueOf(((String) sourceVal).startsWith((String) argVal));
case PredefinedType.TOKENIZE:
return tokenize((String) sourceVal, (String) argVal, false);
default: {
String message = OCLMessages.bind(
OCLMessages.UnknownOperation_ERROR_,
getName(oper));
RuntimeException error = new RuntimeException(message);
OCLPlugin.throwing(getClass(),
"visitOperationCallExp", error);//$NON-NLS-1$
throw error;
}
}
} else if (sourceVal instanceof Collection<?>) {
@SuppressWarnings("unchecked")
Collection<Object> sourceColl = (Collection<Object>) sourceVal;
// bug 183144: inputting OclInvalid should result in OclInvalid
if (argVal == getInvalid()) {
return argVal;
}
switch (opCode) {
case PredefinedType.INCLUDES:
// Collection::includes(T)
return CollectionUtil.includes(sourceColl,
argVal) ? Boolean.TRUE : Boolean.FALSE;
case PredefinedType.EXCLUDES:
// Collection::excludes(T)
return CollectionUtil.excludes(sourceColl,
argVal) ? Boolean.TRUE : Boolean.FALSE;
case PredefinedType.COUNT:
// Collection::count(T)
return new Integer(CollectionUtil.count(
sourceColl, argVal));
case PredefinedType.INCLUDES_ALL:
// Collection::includesAll(T)
if (argVal == null) {
return getInvalid();
}
return CollectionUtil.includesAll(sourceColl,
(Collection<?>) argVal) ? Boolean.TRUE
: Boolean.FALSE;
case PredefinedType.EXCLUDES_ALL:
// Collection::excludesAll(T)
if (argVal == null) {
return getInvalid();
}
return CollectionUtil.excludesAll(sourceColl,
(Collection<?>) argVal) ? Boolean.TRUE
: Boolean.FALSE;
case PredefinedType.PRODUCT: {
// Collection::product(Collection(T2))
if (argVal == null) {
return getInvalid();
}
@SuppressWarnings("unchecked")
CollectionType<C, O> collType = (CollectionType<C, O>) oc.getType();
return CollectionUtil.product(
getEvaluationEnvironment(),
getEnvironment(),
sourceColl,
(Collection<?>) argVal,
collType.getElementType());
}
case PredefinedType.UNION: {
// Set, Bag::union(Set, Bag)
if (argVal == null) {
return getInvalid();
}
Collection<?> argColl = (Collection<?>) argVal;
return CollectionUtil.union(sourceColl, argColl);
}
case PredefinedType.INTERSECTION: {
// Set, Bag::intersection(Set, Bag)
if (argVal == null) {
return getInvalid();
}
Collection<?> argColl = (Collection<?>) argVal;
return CollectionUtil.intersection(sourceColl,
argColl);
}
case PredefinedType.MINUS:
// Set::minus(Set)
if (argVal == null) {
return getInvalid();
}
return CollectionUtil.minus((Set<?>) sourceColl,
(Set<?>) argVal);
case PredefinedType.INCLUDING:
// Set, Bag, Sequence::including(T)
return CollectionUtil.including(sourceColl,
argVal);
case PredefinedType.EXCLUDING:
// Set, Bag, Sequence::excluding(T)
return CollectionUtil.excluding(sourceColl,
argVal);
case PredefinedType.SYMMETRIC_DIFFERENCE:
// Set::symmetricDifference(Set)
if (argVal == null) {
return getInvalid();
}
return CollectionUtil.symmetricDifference(
(Set<?>) sourceColl, (Set<?>) argVal);
case PredefinedType.APPEND:
// OrderedSet, Sequence::append(T)
return CollectionUtil.append(sourceColl, argVal);
case PredefinedType.PREPEND:
// OrderedSet, Sequence::prepend(T)
return CollectionUtil.prepend(sourceColl,
argVal);
case PredefinedType.AT: {
// OrderedSet, Sequence::at(Integer)
if (!(argVal instanceof Integer)) {
return getInvalid();
}
int indexVal = ((Integer) argVal).intValue();
return CollectionUtil.at(sourceColl, indexVal);
}
case PredefinedType.INDEX_OF:
// OrderedSet, Sequence::indexOf(T)
Object indexOf = CollectionUtil.indexOf(sourceColl,
argVal);
if (indexOf == null) {
// according to OCL spec, precondition is violated, resulting in invalid
indexOf = getInvalid();
}
return indexOf;
case PredefinedType.SELECT_BY_KIND: {
// Collection::selectByKind(OclType)
Collection<Object> newElements = CollectionUtil.createNewCollectionOfSameKind(sourceColl);
for (Iterator<?> it = sourceColl.iterator(); it.hasNext();) {
Object object = it.next();
if ((object != null) && oclIsKindOf(object, argVal)) {
newElements.add(object);
}
}
return newElements;
}
case PredefinedType.SELECT_BY_TYPE: {
// Collection::selectByType(OclType)
Collection<Object> newElements = CollectionUtil.createNewCollectionOfSameKind(sourceColl);
for (Iterator<?> it = sourceColl.iterator(); it.hasNext();) {
Object object = it.next();
if (oclIsTypeOf(object, argVal)) {
newElements.add(object);
}
}
return newElements;
}
} // end of collection type switch
} else if (sourceVal instanceof Comparable<?>) {
// Handle < (lessThan)
if (opCode == PredefinedType.LESS_THAN) {
@SuppressWarnings("unchecked")
Comparable<Object> comp = (Comparable<Object>) sourceVal;
return Boolean.valueOf(comp.compareTo(argVal) < 0);
}
// Handle <= (lessThanEqual)
else if (opCode == PredefinedType.LESS_THAN_EQUAL) {
@SuppressWarnings("unchecked")
Comparable<Object> comp = (Comparable<Object>) sourceVal;
return Boolean.valueOf(comp.compareTo(argVal) <= 0);
}
// Handle > (greaterThan)
else if (opCode == PredefinedType.GREATER_THAN) {
@SuppressWarnings("unchecked")
Comparable<Object> comp = (Comparable<Object>) sourceVal;
return Boolean.valueOf(comp.compareTo(argVal) > 0);
}
// Handle > (greaterThanEqual)
else if (opCode == PredefinedType.GREATER_THAN_EQUAL) {
@SuppressWarnings("unchecked")
Comparable<Object> comp = (Comparable<Object>) sourceVal;
return Boolean.valueOf(comp.compareTo(argVal) >= 0);
}
}
} else {
//
// ternary operations
//
// check if undefined
if (isUndefined(sourceVal)) {
return getInvalid();
}
// evaluate arg1
Object arg1 = args.get(0).accept(getVisitor());
// check if invalid
if (arg1 == getInvalid()) {
return getInvalid();
}
// evaluate arg2
Object arg2 = args.get(1).accept(getVisitor());
// check if invalid
if (arg2 == getInvalid()) {
return getInvalid();
}
if (sourceVal instanceof String) {
// just one ternary string operation
// String::substring(Integer, Integer)
// index orgin 1 for OCL
if (isUndefined(arg1) || isUndefined(arg2)) {
return getInvalid();
}
String sourceString = (String) sourceVal;
switch (opCode) {
case PredefinedType.REPLACE_ALL:
return getRegexMatcher((String) arg1, sourceString).replaceAll((String) arg2);
case PredefinedType.REPLACE_FIRST:
return getRegexMatcher((String) arg1, sourceString).replaceFirst((String) arg2);
case PredefinedType.SUBSTITUTE_ALL:
return sourceString.replace((String) arg1, (String) arg2);
case PredefinedType.SUBSTITUTE_FIRST:
String oldSubstring = (String) arg1;
int index = sourceString.indexOf((String) arg1);
if (index >= 0) {
return sourceString.substring(0, index) + (String) arg2 + sourceString.substring(index + oldSubstring.length(), sourceString.length());
}
else {
return getInvalid();
}
case PredefinedType.SUBSTRING:
int lower = ((Integer) arg1).intValue();
int upper = ((Integer) arg2).intValue();
if (!(1 <= lower &&
lower <= upper &&
upper <= ((String) sourceVal).length())) {
return getInvalid();
}
return sourceString.substring(lower-1, upper);
case PredefinedType.TOKENIZE:
return tokenize(sourceString, (String) arg1, (Boolean) arg2);
}
} else if (sourceVal instanceof Collection<?>) {
@SuppressWarnings("unchecked")
Collection<Object> sourceColl = (Collection<Object>) sourceVal;
if (opCode == PredefinedType.INSERT_AT) {
if (isUndefined(arg1)) {
return getInvalid();
}
// OrderedSet, Sequence::insertAt(Integer, T)
int index = ((Integer) arg1).intValue();
return CollectionUtil.insertAt(sourceColl, index,
arg2);
} else if (opCode == PredefinedType.SUB_ORDERED_SET) {
if (isUndefined(arg1) || isUndefined(arg2)) {
return getInvalid();
}
// OrderedSet, Sequence::subOrderedSet(Integer, Integer)
int lower = ((Integer) arg1).intValue();
int upper = ((Integer) arg2).intValue();
return CollectionUtil.subOrderedSet(sourceColl,
lower, upper);
} else if (opCode == PredefinedType.SUB_SEQUENCE) {
if (isUndefined(arg1) || isUndefined(arg2)) {
return getInvalid();
}
// Sequence::subSequence(Integer, Integer)
int lower = ((Integer) arg1).intValue();
int upper = ((Integer) arg2).intValue();
return CollectionUtil.subSequence(sourceColl,
lower, upper);
}
}
}
} else {
// Handle allInstances
if (opCode == PredefinedType.ALL_INSTANCES) {
// can assume classifier type, otherwise the expression would
// not have parsed (or validated)
@SuppressWarnings("unchecked")
C classifier = (C) sourceVal;
if (getUMLReflection().isEnumeration(classifier)) {
// the instances are the literals
return new java.util.HashSet<EL>(
getUMLReflection().getEnumerationLiterals(classifier));
} else if (sourceVal instanceof VoidType<?>) {
// OclVoid has a single instance: null
Set<Object> result = new java.util.HashSet<Object>();
result.add(null);
return result;
} else if (getUMLReflection().isClass(classifier)) {
return getExtentMap().get(sourceVal);
} else {
// other types do not have numerable instances
return Collections.EMPTY_SET;
}
}
if (opCode == PredefinedType.OCL_IS_UNDEFINED) {
return isUndefined(sourceVal)?
Boolean.TRUE : Boolean.FALSE;
}
if (opCode == PredefinedType.OCL_IS_INVALID) {
return (sourceVal == getInvalid())?
Boolean.TRUE : Boolean.FALSE;
}
// result is invalid if source is undefined
if (isUndefined(sourceVal)) {
switch (opCode) {
case PredefinedType.OCL_IS_TYPE_OF:
case PredefinedType.OCL_IS_KIND_OF:
case PredefinedType.OCL_AS_TYPE:
if (isLaxNullHandling()) {
break;
} else {
return getInvalid();
}
default:
return getInvalid();
}
}
// Handle type check and conversion:
// AnyType::oclIsTypeOf(OclType)
if (opCode == PredefinedType.OCL_IS_TYPE_OF) {
OCLExpression<C> arg = args.get(0);
Boolean result = oclIsTypeOf(sourceVal, arg.accept(getVisitor()));
if (result == null) {
return getInvalid();
} else {
return result;
}
}
// AnyType::oclIsKindOf(OclType)
else if (opCode == PredefinedType.OCL_IS_KIND_OF) {
OCLExpression<C> arg = args.get(0);
Boolean result = oclIsKindOf(sourceVal, arg.accept(getVisitor()));
if (result == null) {
return getInvalid();
} else {
return result;
}
}
// AnyType::oclAsType(OclType)
else if (opCode == PredefinedType.OCL_AS_TYPE) {
// Check if the source object type is really
// conformant to the arg type. Note that
// it is not possible to do this check 100%
// at parse time because we only have the
// declared type of the source to check
// against the arg type and it may happen
// that the declared type is not conformant
// but a subtype of it is. For example,
// if there are four types A, B, C, and D;
// B is subtype of both A and C; D is a subtype of A;
// and x is a variable of type A; then it is impossible
// to know at parse time whether x.oclAsType(C)
// is a valid conversion. If x is an object of
// type B then it is; if x is an object of type D
// then it isn't; and this cannot be determined
// until runtime.
OCLExpression<C> arg = args.get(0);
@SuppressWarnings("unchecked")
C type = (C) arg.accept(getVisitor());
if (Boolean.TRUE.equals(oclIsKindOf(sourceVal, type))) {
return sourceVal;
} else {
return getInvalid();
}
}
// Handle < (lessThan)
else if ((opCode == PredefinedType.LESS_THAN) && (sourceVal instanceof Comparable<?>)) {
@SuppressWarnings("unchecked")
Comparable<Object> compContext = (Comparable<Object>) sourceVal;
OCLExpression<C> arg = args.get(0);
@SuppressWarnings("unchecked")
Comparable<Object> evalArg = (Comparable<Object>) arg.accept(getVisitor());
return Boolean.valueOf(compContext.compareTo(evalArg) < 0);
}
// Handle <= (lessThanEqual)
else if ((opCode == PredefinedType.LESS_THAN_EQUAL) && (sourceVal instanceof Comparable<?>)) {
@SuppressWarnings("unchecked")
Comparable<Object> compContext = (Comparable<Object>) sourceVal;
OCLExpression<C> arg = args.get(0);
@SuppressWarnings("unchecked")
Comparable<Object> evalArg = (Comparable<Object>) arg.accept(getVisitor());
return Boolean.valueOf(compContext.compareTo(evalArg) <= 0);
}
// Handle > (greaterThan)
else if ((opCode == PredefinedType.GREATER_THAN) && (sourceVal instanceof Comparable<?>)) {
@SuppressWarnings("unchecked")
Comparable<Object> compContext = (Comparable<Object>) sourceVal;
OCLExpression<C> arg = args.get(0);
Comparable<?> evalArg = (Comparable<?>) arg.accept(getVisitor());
return Boolean.valueOf(compContext.compareTo(evalArg) > 0);
}
// Handle > (greaterThanEqual)
else if ((opCode == PredefinedType.GREATER_THAN_EQUAL) && (sourceVal instanceof Comparable<?>)) {
@SuppressWarnings("unchecked")
Comparable<Object> compContext = (Comparable<Object>) sourceVal;
OCLExpression<C> arg = args.get(0);
@SuppressWarnings("unchecked")
Comparable<Object> evalArg = (Comparable<Object>) arg.accept(getVisitor());
return Boolean.valueOf(compContext.compareTo(evalArg) >= 0);
}
//
// unknown operation (shouldn't have gotten this far if we
// successfully parsed and/or validated the expression
//
return getInvalid();
}
return getInvalid();
}
/**
* Infers a standard operation code from the name of a user-defined
* operation. This applies for cases where a standard operation is not
* defined by the OCL Standard Library, but is implemented nonetheless by
* the interpreter.
*
* @param operation the operation
* @param opcode the original operation code from the AST
* @return the appropriate operation code, or the original <tt>opcode</tt>
* if there is no matching standard operation
*/
private int inferOperationCode(O operation, int opcode) {
int result = opcode;
String opName = getName(operation);
if (PredefinedType.LESS_THAN_NAME.equals(opName)) {
result = PredefinedType.LESS_THAN;
} else if (PredefinedType.GREATER_THAN_NAME.equals(opName)) {
result = PredefinedType.GREATER_THAN;
} else if (PredefinedType.LESS_THAN_EQUAL_NAME.equals(opName)) {
result = PredefinedType.LESS_THAN_EQUAL;
} else if (PredefinedType.GREATER_THAN_EQUAL_NAME.equals(opName)) {
result = PredefinedType.GREATER_THAN_EQUAL;
}
return result;
}
/**
* Callback for an IterateExp visit.
*/
@Override
public Object visitIterateExp(IterateExp<C, PM> ie) {
// get the variable declaration for the result
Variable<C, PM> vd = ie.getResult();
String resultName = (String) vd.accept(getVisitor());
try {
// get the list of ocl iterators
List<Variable<C, PM>> iterators = ie.getIterator();
// evaluate the source collection
Object sourceValue = ie.getSource().accept(getVisitor());
// value of iteration expression is undefined if the source is
// null or OclInvalid
if (isUndefined(sourceValue)) {
return getInvalid();
}
Collection<?> coll = (Collection<?>) sourceValue;
// get the body expression
OCLExpression<C> body = ie.getBody();
// construct an iteration template to evaluate the iterator
IterationTemplate<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> is =
IterationTemplate.getInstance(getVisitor());
// evaluate
return is.evaluate(coll, iterators, body, resultName);
} finally {
// remove result variable from environment
getEvaluationEnvironment().remove(resultName);
}
}
/**
* Callback for an IteratorExp visit.
*/
@Override
public Object visitIteratorExp(IteratorExp<C, PM> ie) {
C sourceType = ie.getSource().getType();
if (sourceType instanceof PredefinedType<?>) {
Object sourceValue = ie.getSource().accept(getVisitor());
// value of iteration expression is undefined if the source is
// null or OclInvalid
if (isUndefined(sourceValue)) {
return getInvalid();
}
Collection<?> sourceCollection = (Collection<?>) sourceValue;
switch (OCLStandardLibraryUtil.getOperationCode(ie.getName())) {
case PredefinedType.EXISTS:
return evaluateExistsIterator(ie, sourceCollection);
case PredefinedType.FOR_ALL:
return evaluateForAllIterator(ie, sourceCollection);
case PredefinedType.SELECT:
return evaluateSelectIterator(ie, sourceCollection);
case PredefinedType.REJECT:
return evaluateRejectIterator(ie, sourceCollection);
case PredefinedType.COLLECT:
return evaluateCollectIterator(ie, sourceCollection);
case PredefinedType.COLLECT_NESTED:
return evaluateCollectNestedIterator(ie, sourceCollection);
case PredefinedType.ONE:
return evaluateOneIterator(ie, sourceCollection);
case PredefinedType.ANY:
return evaluateAnyIterator(ie, sourceCollection);
case PredefinedType.SORTED_BY:
return evaluateSortedByIterator(ie, sourceCollection);
case PredefinedType.IS_UNIQUE:
return evaluateIsUnique(ie, sourceCollection);
case PredefinedType.CLOSURE:
return evaluateClosure(ie, sourceCollection);
}
}
String message = OCLMessages.bind(
OCLMessages.IteratorNotImpl_ERROR_, ie.getName());
UnsupportedOperationException ex = new UnsupportedOperationException(
message);
OCLPlugin.throwing(getClass(), "visitIteratorExp", ex);//$NON-NLS-1$
throw ex;
}
private static synchronized String generateName() {
return "__result__" + tempCounter++;//$NON-NLS-1$
}
private Object evaluateExistsIterator(IteratorExp<C, PM> ie, Collection<?> coll) {
// get the list of ocl iterators
List<Variable<C, PM>> iterators = ie.getIterator();
// get the body expression
OCLExpression<C> body = ie.getBody();
// get an iteration template to evaluate the iterator
IterationTemplate<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> is =
IterationTemplateExists.getInstance(getVisitor());
// generate a name for the result variable and add it to the environment
String resultName = generateName();
getEvaluationEnvironment().add(resultName, Boolean.FALSE);
try {
// evaluate
return is.evaluate(coll, iterators, body, resultName);
} finally {
// remove the result variable from the environment
getEvaluationEnvironment().remove(resultName);
}
}
private Object evaluateForAllIterator(IteratorExp<C, PM> ie, Collection<?> coll) {
// get the list of ocl iterators
List<Variable<C, PM>> iterators = ie.getIterator();
// int numIters = iterators.size();
// get the body expression
OCLExpression<C> body = ie.getBody();
// get an iteration template to evaluate the iterator
IterationTemplate<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> is =
IterationTemplateForAll.getInstance(getVisitor());
// generate a name for the result variable and add it to the environment
String resultName = generateName();
getEvaluationEnvironment().add(resultName, Boolean.TRUE);
try {
// evaluate
return is.evaluate(coll, iterators, body, resultName);
} finally {
// remove result name from the environment
getEvaluationEnvironment().remove(resultName);
}
}
private Object evaluateCollectNestedIterator(IteratorExp<C, PM> ie, Collection<?> coll) {
// get the list of ocl iterators
List<Variable<C, PM>> iterators = ie.getIterator();
// int numIters = iterators.size();
// get the body expression
OCLExpression<C> body = ie.getBody();
// get initial result value based on the source type
@SuppressWarnings("unchecked")
CollectionType<C, O> collType = (CollectionType<C, O>) ie.getSource().getType();
Object initResultVal = null;
if (collType instanceof SetType<?, ?> || collType instanceof BagType<?, ?>) {
// collection on a Bag or a Set yields a Bag
initResultVal = CollectionUtil.createNewBag();
} else {
// Sequence or Ordered Set yields a Sequence
initResultVal = CollectionUtil.createNewSequence();
}
// get an iteration template to evaluate the iterator
IterationTemplate<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> is =
IterationTemplateCollectNested.getInstance(getVisitor());
// generate a name for the result variable and add it to the environment
String resultName = generateName();
getEvaluationEnvironment().add(resultName, initResultVal);
try {
// evaluate
return is.evaluate(coll, iterators, body, resultName);
} finally {
// remove result name from environment
getEvaluationEnvironment().remove(resultName);
}
}
private Object evaluateCollectIterator(IteratorExp<C, PM> ie, Collection<?> coll) {
// get the list of ocl iterators
List<Variable<C, PM>> iterators = ie.getIterator();
// int numIters = iterators.size();
// get the body expression
OCLExpression<C> body = ie.getBody();
// get initial result value based on the source type
@SuppressWarnings("unchecked")
CollectionType<C, O> collType = (CollectionType<C, O>) ie.getSource().getType();
Object initResultVal = null;
if (collType instanceof SetType<?, ?> || collType instanceof BagType<?, ?>) {
// collection on a Bag or a Set yields a Bag
initResultVal = CollectionUtil.createNewBag();
} else {
// Sequence or Ordered Set yields a Sequence
initResultVal = CollectionUtil.createNewSequence();
}
// get an iteration template to evaluate the iterator
IterationTemplate<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> is =
IterationTemplateCollect.getInstance(getVisitor());
// generate a name for the result variable and add it to the environment
String resultName = generateName();
getEvaluationEnvironment().add(resultName, initResultVal);
try {
// evaluate
return is.evaluate(coll, iterators, body, resultName);
} finally {
// remove result name from environment
getEvaluationEnvironment().remove(resultName);
}
}
private Object evaluateSelectIterator(IteratorExp<C, PM> ie, Collection<?> coll) {
// get the list of ocl iterators
List<Variable<C, PM>> iterators = ie.getIterator();
// int numIters = iterators.size();
// get the body expression
OCLExpression<C> body = ie.getBody();
// get initial result value based on the source type
@SuppressWarnings("unchecked")
CollectionType<C, O> collType = (CollectionType<C, O>) ie.getSource().getType();
Object initResultVal = null;
if (collType instanceof SetType<?, ?>) {
// Set
initResultVal = CollectionUtil.createNewSet();
} else if (collType instanceof BagType<?, ?>) {
// Bag
initResultVal = CollectionUtil.createNewBag();
} else if (collType instanceof SequenceType<?, ?>) {
// Sequence
initResultVal = CollectionUtil.createNewSequence();
} else {
// OrderedSet
initResultVal = CollectionUtil.createNewOrderedSet();
}
// get an iteration template to evaluate the iterator
IterationTemplate<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> is =
IterationTemplateSelect.getInstance(getVisitor());
// generate a name for the result variable and add it to the environment
String resultName = generateName();
getEvaluationEnvironment().add(resultName, initResultVal);
try {
// evaluate
return is.evaluate(coll, iterators, body, resultName);
} finally {
// remove result name from environment
getEvaluationEnvironment().remove(resultName);
}
}
private Object evaluateRejectIterator(IteratorExp<C, PM> ie, Collection<?> coll) {
// get the list of ocl iterators
List<Variable<C, PM>> iterators = ie.getIterator();
// int numIters = iterators.size();
// get the body expression
OCLExpression<C> body = ie.getBody();
// get initial result value based on the source type
@SuppressWarnings("unchecked")
CollectionType<C, O> collType = (CollectionType<C, O>) ie.getSource().getType();
Object initResultVal = null;
if (collType instanceof SetType<?, ?>) {
// Set
initResultVal = CollectionUtil.createNewSet();
} else if (collType instanceof BagType<?, ?>) {
// Bag
initResultVal = CollectionUtil.createNewBag();
} else if (collType instanceof SequenceType<?, ?>) {
// Sequence
initResultVal = CollectionUtil.createNewSequence();
} else {
// OrderedSet
initResultVal = CollectionUtil.createNewOrderedSet();
}
// get an iteration template to evaluate the iterator
IterationTemplate<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> is =
IterationTemplateReject.getInstance(getVisitor());
// generate a name for the result variable and add it to the environment
String resultName = generateName();
getEvaluationEnvironment().add(resultName, initResultVal);
try {
// evaluate
return is.evaluate(coll, iterators, body, resultName);
} finally {
// remove result name from environment
getEvaluationEnvironment().remove(resultName);
}
}
private Object evaluateOneIterator(IteratorExp<C, PM> ie, Collection<?> coll) {
// get the list of ocl iterators
List<Variable<C, PM>> iterators = ie.getIterator();
// int numIters = iterators.size();
// get the body expression
OCLExpression<C> body = ie.getBody();
// get an iteration template to evaluate the iterator
IterationTemplate<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> is =
IterationTemplateOne.getInstance(getVisitor());
// generate a name for the result variable and add it to the environment
String resultName = generateName();
getEvaluationEnvironment().add(resultName, Boolean.FALSE);
try {
// evaluate
return is.evaluate(coll, iterators, body, resultName);
} finally {
// remove result name from environment
getEvaluationEnvironment().remove(resultName);
}
}
private Object evaluateAnyIterator(IteratorExp<C, PM> ie, Collection<?> coll) {
// get the list of ocl iterators
List<Variable<C, PM>> iterators = ie.getIterator();
// get the body expression
OCLExpression<C> body = ie.getBody();
// get an iteration template to evaluate the iterator
IterationTemplate<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> is =
IterationTemplateAny.getInstance(getVisitor());
// generate a name for the result variable and add it to the environment
String resultName = generateName();
EvaluationEnvironment<C, O, P, CLS, E> evaluationEnvironment = getEvaluationEnvironment();
boolean anyLessIsInvalid = EvaluationOptions.getValue(evaluationEnvironment, EvaluationOptions.ANY_LESS_IS_INVALID);
evaluationEnvironment.add(resultName, anyLessIsInvalid ? getInvalid() : null);
try {
// evaluate
return is.evaluate(coll, iterators, body, resultName);
} finally {
// remove result name from environment
evaluationEnvironment.remove(resultName);
}
}
private Object evaluateSortedByIterator(IteratorExp<C, PM> ie, Collection<?> coll) {
// get the list of ocl iterators
List<Variable<C, PM>> iterators = ie.getIterator();
// int numIters = iterators.size();
// get the body expression
OCLExpression<C> body = ie.getBody();
// get an iteration template to evaluate the iterator
IterationTemplate<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> is =
IterationTemplateSortedBy.getInstance(getVisitor());
// generate a name for the result variable and add it to the environment
String resultName = generateName();
final Map<Object, Comparable<Object>> map =
new HashMap<Object, Comparable<Object>>();
getEvaluationEnvironment().add(resultName, map);
try {
// evaluate
// TODO: find an efficient way to do this.
Object evaluationResult = is.evaluate(coll, iterators, body, resultName);
if (evaluationResult == getInvalid()) {
// handle the OclInvalid result
return evaluationResult;
}
is.evaluate(coll, iterators, body, resultName);
} finally {
// remove result name from environment
getEvaluationEnvironment().remove(resultName);
}
// sort the source collection based on the natural ordering of the
// body expression evaluations
List<Object> result = new ArrayList<Object>(coll);
Collections.sort(result, getComparatorForSortedBy(map, ie));
// create result
// type is Sequence if source is a sequence or a Bag,
// SortedSet if source is a SortedSet or a Set
C collType = ie.getSource().getType();
if (collType instanceof SetType<?, ?> || collType instanceof OrderedSetType<?, ?>) {
return CollectionUtil.createNewOrderedSet(result);
} else {
return CollectionUtil.createNewSequence(result);
}
}
private Comparator<Object> getComparatorForSortedBy(
final Map<Object, Comparable<Object>> map, IteratorExp<C, PM> ie) {
// special case: UnlimitedNatural::UNLIMITED is greater than
// everything except for itself
if (ie.getBody().getType() == getUnlimitedNatural()) {
return new Comparator<Object>() {
public int compare(Object o1, Object o2) {
Comparable<Object> b1 = map.get(o1);
Comparable<Object> b2 = map.get(o2);
return (b1.equals(UNLIMITED) ?
b2.equals(UNLIMITED) ?
0 : // both are UNLIMITED
1 : // b1 is UNLIMITED, b2 not, so b1>b2
b2.equals(UNLIMITED) ?
-1 : // b2 is UNLIMITED, b1 not, so b1 < b2
b1.compareTo(b2));
}
};
} else {
return new Comparator<Object>() {
public int compare(Object o1, Object o2) {
Comparable<Object> b1 = map.get(o1);
Comparable<Object> b2 = map.get(o2);
return (b1.compareTo(b2));
}
};
}
}
private Object evaluateIsUnique(IteratorExp<C, PM> ie, Collection<?> coll) {
// get the list of ocl iterators
List<Variable<C, PM>> iterators = ie.getIterator();
// get the body expression
OCLExpression<C> body = ie.getBody();
// get an iteration template to evaluate the iterator
IterationTemplate<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> is =
IterationTemplateIsUnique.getInstance(getVisitor());
// generate a name for the result variable and add it to the environment
String resultName = generateName();
getEvaluationEnvironment().add(resultName, new HashSet<Object>());
try {
// evaluate
is.evaluate(coll, iterators, body, resultName);
} finally {
// remove result name from environment
getEvaluationEnvironment().remove(resultName);
}
return is.isDone() ? Boolean.FALSE : Boolean.TRUE;
}
private Object evaluateClosure(IteratorExp<C, PM> ie, Collection<?> coll) {
// get the list of ocl iterators
List<Variable<C, PM>> iterators = ie.getIterator();
// get the body expression
OCLExpression<C> body = ie.getBody();
C type = ie.getType();
// create initial result value
Object initResultVal = type instanceof OrderedSetType<?,?> ? CollectionUtil.createNewOrderedSet() : CollectionUtil.createNewSet();
// get an iteration template to evaluate the iterator
IterationTemplate<PK, C, O, P, EL, PM, S, COA, SSA, CT, CLS, E> template =
IterationTemplateClosure.getInstance(getVisitor(), body);
// generate a name for the result variable and add it to the environment
String resultName = generateName();
getEvaluationEnvironment().add(resultName, initResultVal);
try {
// evaluate
return template.evaluate(coll, iterators, body, resultName);
} finally {
// remove result name from environment
getEvaluationEnvironment().remove(resultName);
}
}
/**
* Callback for an EnumLiteralExp visit. Get the referred enum literal and
* return it as an Integer.
*
* @param el
* the enumeration literal expresion
* @return the enumeration literal as an Integer
*/
@Override
public Object visitEnumLiteralExp(EnumLiteralExp<C, EL> el) {
return (enumerations == null) ? el.getReferredEnumLiteral()
: enumerations.getValue(el.getReferredEnumLiteral());
}
/**
* Callback for a VariableExp visit.
*
* @param v
* the variable expression
* @return the value of the variable
*/
@Override
public Object visitVariableExp(VariableExp<C, PM> v) {
// get the referred variable name
Variable<C, PM> vd = v.getReferredVariable();
String varName = vd.getName();
// evaluate the variable in the current environment
return getEvaluationEnvironment().getValueOf(varName);
}
/**
* Callback for a PropertyCallExp visit. Evaluates the source of the
* expression and then reflectively gets the value of the property on the
* result. For example, in "self.foo", "self" is the source and would be
* evaluated first, then the value of the property "foo" would be accessed
* on that object.
*/
@Override
public Object visitPropertyCallExp(PropertyCallExp<C, P> pc) {
P property = pc.getReferredProperty();
OCLExpression<C> source = pc.getSource();
// evaluate source
Object context = source.accept(getVisitor());
// if source is undefined, result is OclInvalid
if (isUndefined(context)) {
return getInvalid();
}
OCLExpression<C> derivation = getPropertyBody(property);
if (derivation != null) {
// this is an additional property
return navigate(property, derivation, context);
}
List<Object> qualifiers;
if (pc.getQualifier().isEmpty()) {
qualifiers = Collections.emptyList();
} else {
// handle qualified association navigation
qualifiers = new java.util.ArrayList<Object>();
for (OCLExpression<C> q : pc.getQualifier()) {
qualifiers.add(q.accept(getVisitor()));
}
}
Object result = getEvaluationEnvironment().navigateProperty(property, qualifiers, context);
if ((pc.getType() instanceof CollectionType<?, ?>) && !(result instanceof Collection<?>)) {
// this was an XSD "unspecified multiplicity". Now that we know what
// the multiplicity is, we can coerce it to a collection value
@SuppressWarnings("unchecked")
CollectionKind kind = ((CollectionType<C, O>) pc.getType()).getKind();
Collection<Object> collection = CollectionUtil.createNewCollection(kind);
collection.add(result);
result = collection;
}
return result;
}
/**
* Callback for an AssociationClassCallExp visit. Evaluates the source of the
* expression and then reflectively gets the value of the reference on the
* result. For example, in "self.foo", "self" is the source and would be
* evaluated first, then the value of the reference "foo" would be derived
* on that object.
*/
@Override
public Object visitAssociationClassCallExp(AssociationClassCallExp<C, P> ae) {
Object context = ae.getSource().accept(getVisitor());
if (isUndefined(context)) {
return getInvalid();
}
// evaluate attribute on source value
return getEvaluationEnvironment().navigateAssociationClass(
ae.getReferredAssociationClass(),
ae.getNavigationSource(),
context);
}
/**
* Callback for a VariableDeclaration visit.
*/
@Override
public Object visitVariable(Variable<C, PM> vd) {
// add the variable to the environment, initialized to
// its initial expression (if it has one). return the name
// of the variable.
String varName = vd.getName();
OCLExpression<C> initExp = vd.getInitExpression();
Object initVal = null;
if (initExp != null) {
// if an unpropagated runtime exception is thrown, assign invalid to
// variable, allowing an oclIsInvalid() to detect it later
initVal = safeVisitExpression(initExp);
}
getEvaluationEnvironment().add(varName, initVal);
return varName;
}
/**
* Callback for an IfExp visit.
*/
@Override
public Object visitIfExp(IfExp<C> ie) {
// get condition
OCLExpression<C> condition = ie.getCondition();
// evaluate condition
Object condVal = condition.accept(getVisitor());
if (isUndefined(condVal)) {
return getInvalid();
}
Boolean condValBool = (Boolean) condVal;
if (condValBool.booleanValue()) {
return ie.getThenExpression().accept(getVisitor());
}
return ie.getElseExpression().accept(getVisitor());
}
/**
* Callback for a TypeExp visiy.
*/
@Override
public Object visitTypeExp(TypeExp<C> t) {
return t.getReferredType();
}
@Override
public Object visitStateExp(StateExp<C, S> s) {
return s.getReferredState();
}
@Override
public Object visitMessageExp(MessageExp<C, COA, SSA> m) {
throw new UnsupportedOperationException("evaluation of MessageExp"); //$NON-NLS-1$
}
/**
* Callback for an UnspecifiedValueExp visit.
*/
@Override
public Object visitUnspecifiedValueExp(UnspecifiedValueExp<C> uv) {
// TODO: return a "random instance of the type of the expression"
throw new UnsupportedOperationException("evaluation of UnspecifiedValueExp"); //$NON-NLS-1$
}
/**
* Callback for an IntegerLiteralExp visit.
*
* @return the value of the integer literal as a java.lang.Integer.
*/
@Override
public Object visitIntegerLiteralExp(IntegerLiteralExp<C> il) {
return coerceNumber(il.getLongSymbol());
}
/**
* Callback for an UnlimitedNaturalLiteralExp visit.
*
* @return the value of the natural literal as a java.lang.Integer.
*/
@Override
public Object visitUnlimitedNaturalLiteralExp(
UnlimitedNaturalLiteralExp<C> literalExp) {
return literalExp.getIntegerSymbol();
}
/**
* Callback for a RealLiteralExp visit.
*
* @return the value of the real literal as a java.lang.Double.
*/
@Override
public Object visitRealLiteralExp(RealLiteralExp<C> rl) {
return rl.getRealSymbol();
}
/**
* Callback for a StringLiteralExp visit.
*
* @return the value of the string literal as a java.lang.String.
*/
@Override
public Object visitStringLiteralExp(StringLiteralExp<C> sl) {
return sl.getStringSymbol();
}
/**
* Callback for a BooleanLiteralExp visit.
*
* @return the value of the boolean literal as a java.lang.Boolean.
*/
@Override
public Object visitBooleanLiteralExp(BooleanLiteralExp<C> bl) {
return bl.getBooleanSymbol();
}
@Override
public Object visitInvalidLiteralExp(InvalidLiteralExp<C> il) {
// just make up some object to take the place of the OclInvalid literal
return getInvalid();
}
@Override
public Object visitNullLiteralExp(NullLiteralExp<C> il) {
// the single OclVoid instance is equivalent to Java null
return null;
}
/**
* Callback for LetExp visit.
*/
@Override
public Object visitLetExp(LetExp<C, PM> l) {
// get variable decl for let variable
Variable<C, PM> vd = l.getVariable();
String name = (String) vd.accept(getVisitor());
try {
// evaluate the "in" part of the let
OCLExpression<C> inExp = l.getIn();
// return the value of the "in"
return inExp.accept(getVisitor());
} finally {
// remove the variable-init expression binding from the environment
getEvaluationEnvironment().remove(name);
}
}
/**
* Callback for a CollectionLiteralExp visit.
*/
@Override
public Object visitCollectionLiteralExp(CollectionLiteralExp<C> cl) {
// construct the appropriate collection from the parts
// based on the collection kind.
CollectionKind kind = cl.getKind();
List<CollectionLiteralPart<C>> parts = cl.getPart();
Collection<Object> result = CollectionUtil.createNewCollection(kind);
if ((kind == CollectionKind.SEQUENCE_LITERAL) && cl.isSimpleRange()) {
// literal is of the form: Sequence{first..last}.
// construct a list with a lazy iterator for it.
CollectionRange<C> collRange = (CollectionRange<C>) parts.get(0);
OCLExpression<C> first = collRange.getFirst();
OCLExpression<C> last = collRange.getLast();
// evaluate first value
Integer firstVal = (Integer) first.accept(getVisitor());
if (firstVal == null) {
result.add(null);
return result;
}
// evaluate last value
Integer lastVal = (Integer) last.accept(getVisitor());
if (lastVal == null) {
result.add(null);
return result;
}
int firstInt = firstVal.intValue();
int lastInt = lastVal.intValue();
if (firstInt > lastInt) {
return result;
}
// construct a lazy integer list for the range
return new IntegerRangeList(firstInt, lastInt);
} else {
// not a sequence or not a simple range
for (CollectionLiteralPart<C> part : parts) {
if (part instanceof CollectionItem<?>) {
// CollectionItem part
CollectionItem<C> item = (CollectionItem<C>) part;
OCLExpression<C> itemExp = item.getItem();
Object itemVal = itemExp.accept(getVisitor());
if (itemVal == getInvalid()) {
return getInvalid(); // can't have an invalid element in a collection
}
// add it to the result set, even if null, except it's the only item in a Set
// literal; otherwise, the implicit set conversion of an undefined value
// would return false for isEmpty(). See also Section 7.5.3 in the OCL 2.3
// specification (10-11-42) on implicit set conversion by the -> operator
if (itemVal != null || parts.size() > 1 || !isImplicitSetConversion(cl)) {
result.add(itemVal);
}
} else {
// Collection range
CollectionRange<C> range = (CollectionRange<C>) part;
OCLExpression<C> first = range.getFirst();
OCLExpression<C> last = range.getLast();
// evaluate first value
Integer firstVal = (Integer) first.accept(getVisitor());
Integer lastVal = (Integer) last.accept(getVisitor());
if (!((firstVal == null) || (lastVal == null))) {
// TODO: enhance IntegerRangeList to support multiple ranges
// add values between first and last inclusive
int firstInt = firstVal.intValue();
int lastInt = lastVal.intValue();
for (int i = firstInt; i <= lastInt; i++) {
result.add(new Integer(i));
}
}
} // end of collection range
} // end of parts iterator
} // end of not-simple range case
return result;
} // end of Set, OrderedSet, Bag Literals
private boolean isImplicitSetConversion(CollectionLiteralExp<C> cl) {
boolean result = false;
if (cl instanceof EModelElement) {
EAnnotation implicitSetConversionAnnotation = ((EModelElement) cl)
.getEAnnotation(AbstractOCLAnalyzer.OCL_ANNOTATIONS_URI);
if (implicitSetConversionAnnotation != null) {
String implicitSetConversionDetail = implicitSetConversionAnnotation
.getDetails().get(AbstractOCLAnalyzer.IMPLICIT_SET_CONVERSION);
if (implicitSetConversionDetail != null &&
Boolean.valueOf(implicitSetConversionDetail)) {
result = true;
}
}
}
return result;
}
// private static inner class for lazy lists over an integer range
private static final class IntegerRangeList
extends AbstractList<Integer> {
// public IntegerRangeList() {
// super();
// }
public IntegerRangeList(int first, int last) {
super();
this.first = first;
this.last = last;
}
// public int getFirst() {
// return first;
// }
// public int getLast() {
// return last;
// }
@Override
public int size() {
return last - first + 1;
}
@Override
public Integer get(int index) {
if (index < 0 || index >= size()) {
String message = OCLMessages.bind(
OCLMessages.IndexOutOfRange_ERROR_,
new Object[] {
Integer.toString(index),
Integer.toString(first),
Integer.toString(last)});
IllegalArgumentException error = new IllegalArgumentException(
message);
OCLPlugin.throwing(getClass(), "get", error);//$NON-NLS-1$
throw error;
}
return new Integer(first + index);
}
@Override
public Iterator<Integer> iterator() {
// local iterator class that provides
// hasNext() and next() methods appropriate
// for this range set
class IntegerRangeIterator
implements Iterator<Integer> {
public IntegerRangeIterator() {
curr = first;
initialized = false;
}
public Integer next() {
if (!initialized) {
curr = first - 1;
initialized = true;
}
if (hasNext()) {
return new Integer(++curr);
}
throw new NoSuchElementException();
}
public boolean hasNext() {
return (curr < last) || !initialized;
}
public void remove() {
throw new UnsupportedOperationException();
}
private int curr;
private boolean initialized;
}
return new IntegerRangeIterator();
}
private int first;
private int last;
}
/**
* Callback for a TupleLiteralExp visit.
*
* @param tl
* tuple literal expression
* @return String
*/
@Override
public Object visitTupleLiteralExp(TupleLiteralExp<C, P> tl) {
C type = tl.getType();
List<TupleLiteralPart<C, P>> tp = tl.getPart();
Map<P, Object> propertyValues = new HashMap<P, Object>();
for (TupleLiteralPart<C, P> part : tp) {
// Set the tuple field with the value of the init expression
propertyValues.put(part.getAttribute(), part.accept(getVisitor()));
}
return getEvaluationEnvironment().createTuple(type, propertyValues);
}
@Override
public Object visitTupleLiteralPart(TupleLiteralPart<C, P> tp) {
return tp.getValue().accept(getVisitor());
}
/**
* Obtains a cached matcher for the given {@code regex} initialized to a
* string to match.
*
* @param regex
* a regular expression to get from (or create in) the cache
* @param stringToMatch
* the search string with which to (re-)initialize the matcher
*
* @return the cached matcher; never {@code null} (failure to parse the
* regex raises an exception)
*
* @see #createRegexCache()
*
* @since 3.4
*/
protected Matcher getRegexMatcher(String regex, String stringToMatch) {
if (regexMatchers == null) {
regexMatchers = createRegexCache();
}
Matcher result = regexMatchers.get(regex);
if (result == null) {
result = Pattern.compile(regex).matcher(stringToMatch);
regexMatchers.put(regex, result);
} else {
result.reset(stringToMatch);
}
return result;
}
/**
* Creates (on demand) the regular-expression matcher cache. The default
* implementation creates an access-ordered LRU cache with a limit of 16
* entries. Subclasses may override to create a map with whatever different
* performance characteristics may be required.
*
* @return the new regular-expression matcher cache
*
* @see #getRegexMatcher(String, String)
*
* @since 3.4
*/
protected Map<String, Matcher> createRegexCache() {
return new java.util.LinkedHashMap<String, Matcher>(
DEFAULT_REGEX_CACHE_LIMIT, DEFAULT_REGEX_CACHE_LOAD_FACTOR, true) {
private static final long serialVersionUID = 1L;
@Override
protected boolean removeEldestEntry(
Map.Entry<String, Matcher> eldest) {
return size() > DEFAULT_REGEX_CACHE_LIMIT;
}
};
}
} //EvaluationVisitorImpl