| /******************************************************************************* |
| * Copyright (c) 2005, 2015, 2018 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v2.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v20.html |
| * |
| * Contributors: |
| * IBM - Initial API and implementation |
| * C.Damus - 291365 |
| * E.D.Willink - 322159 |
| * Adolfo Sanchez-Barbudo Herrera (Open Canarias) - Bug 333032 |
| * Axel Uhl (SAP AG) - Bug 342644 |
| *******************************************************************************/ |
| |
| package org.eclipse.ocl.ecore; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.emf.common.util.BasicEList; |
| import org.eclipse.emf.common.util.ECollections; |
| import org.eclipse.emf.common.util.EList; |
| import org.eclipse.emf.common.util.Enumerator; |
| import org.eclipse.emf.ecore.EClass; |
| import org.eclipse.emf.ecore.EClassifier; |
| import org.eclipse.emf.ecore.EEnumLiteral; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EOperation; |
| import org.eclipse.emf.ecore.EPackage; |
| import org.eclipse.emf.ecore.EParameter; |
| import org.eclipse.emf.ecore.EReference; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| import org.eclipse.emf.ecore.ETypedElement; |
| import org.eclipse.emf.ecore.InternalEObject; |
| import org.eclipse.ocl.AbstractEvaluationEnvironment; |
| import org.eclipse.ocl.EvaluationEnvironment; |
| import org.eclipse.ocl.LazyExtentMap; |
| import org.eclipse.ocl.ecore.delegate.InvocationBehavior; |
| import org.eclipse.ocl.ecore.internal.OCLEcorePlugin; |
| import org.eclipse.ocl.ecore.internal.OCLStandardLibraryImpl; |
| import org.eclipse.ocl.ecore.internal.OCLStatusCodes; |
| import org.eclipse.ocl.ecore.internal.UMLReflectionImpl; |
| import org.eclipse.ocl.ecore.opposites.OppositeEndFinder; |
| import org.eclipse.ocl.expressions.CollectionKind; |
| import org.eclipse.ocl.internal.l10n.OCLMessages; |
| import org.eclipse.ocl.types.CollectionType; |
| import org.eclipse.ocl.types.TupleType; |
| import org.eclipse.ocl.util.CollectionUtil; |
| import org.eclipse.ocl.util.OCLStandardLibraryUtil; |
| import org.eclipse.ocl.util.ObjectUtil; |
| import org.eclipse.ocl.util.Tuple; |
| import org.eclipse.ocl.util.UnicodeSupport; |
| import org.eclipse.ocl.utilities.PredefinedType; |
| |
| /** |
| * Implementation of the {@link EvaluationEnvironment} for evaluation of OCL |
| * expressions on instances of Ecore models (i.e., on M0 models). |
| * |
| * @author Tim Klinger (tklinger) |
| * @author Christian W. Damus (cdamus) |
| */ |
| public class EcoreEvaluationEnvironment |
| extends |
| AbstractEvaluationEnvironment<EClassifier, EOperation, EStructuralFeature, EClass, EObject> |
| implements EvaluationEnvironment.Enumerations<EEnumLiteral>, |
| EvaluationEnvironmentWithHiddenOpposites { |
| |
| private boolean mustCheckOperationReflectionConsistency = true; |
| |
| private final EcoreEnvironmentFactory factory; |
| |
| private final OppositeEndFinder oppositeEndFinder; |
| |
| /** |
| * Initializes me. |
| * |
| * @deprecated A root evaluation environment should be created through the correspondent {@link EcoreEnvironmentFactory} |
| */ |
| public EcoreEvaluationEnvironment() { |
| this((EcoreEnvironmentFactory)null); |
| } |
| |
| /** |
| * Initializes me. |
| * @since 3.1 |
| */ |
| public EcoreEvaluationEnvironment(EcoreEnvironmentFactory factory) { |
| super(); |
| this.factory = factory; |
| if (factory != null) { |
| this.oppositeEndFinder = factory.getOppositeEndFinder(); |
| } else { |
| this.oppositeEndFinder = null; |
| } |
| } |
| |
| /** |
| * Initializes me with my parent evaluation environment (nesting scope). |
| * |
| * @param parent |
| * my parent (nesting scope); must not be <code>null</code> |
| */ |
| public EcoreEvaluationEnvironment( |
| EvaluationEnvironment<EClassifier, EOperation, EStructuralFeature, EClass, EObject> parent) { |
| super(parent); |
| EcoreEvaluationEnvironment ecoreParent = (EcoreEvaluationEnvironment) parent; |
| this.factory = ecoreParent.factory; |
| this.oppositeEndFinder = ecoreParent.oppositeEndFinder; |
| } |
| |
| @Override |
| public Object callOperation(EOperation operation, int opcode, |
| Object source, Object[] args) |
| throws IllegalArgumentException { |
| |
| // FIXME: Pull up so that UML environment can benefit. |
| if (InvocationBehavior.INSTANCE.appliesTo(operation)) { |
| EList<Object> arguments = (args.length == 0) |
| ? ECollections.emptyEList() |
| : new BasicEList.UnmodifiableEList<Object>(args.length, args); |
| |
| try { |
| Object result; |
| // Fix for Bug 322159 |
| Exception checkFailure; |
| if (mustCheckOperationReflectionConsistency |
| && ((checkFailure = checkOperationReflectionConsistency(source)) != null)) { |
| EClass operationClass = operation.getEContainingClass(); |
| EPackage operationPackage = operationClass.getEPackage(); |
| OCLEcorePlugin |
| .warning( |
| OCLStatusCodes.USERMODELSUPPORT_NO_OPERATION_REFLECTION, |
| OCLMessages.bind( |
| OCLMessages.NoOperationReflection_WARNING_, |
| operationPackage.getName() |
| + "::" + operationClass.getName()), //$NON-NLS-1$ |
| checkFailure); |
| result = ((EOperation.Internal) operation) |
| .getInvocationDelegate().dynamicInvoke( |
| (InternalEObject) source, arguments); |
| } else { |
| result = ((EObject) source).eInvoke(operation, arguments); |
| } |
| return coerceValue(operation, result, true); |
| } catch (InvocationTargetException e) { |
| throw new IllegalArgumentException(e); |
| } |
| } |
| |
| // TODO: WBN to pull up createValue to the superclass as a pass-thru |
| // so that subclasses don't have to override callOperation |
| return coerceValue(operation, |
| super.callOperation(operation, opcode, source, args), true); |
| } |
| |
| // implements the inherited specification |
| @Override |
| protected Method getJavaMethodFor(EOperation operation, Object receiver) { |
| Method result = null; |
| |
| // in the case of infix operators, we need to replace the name with |
| // a valid Java name. We will choose the legacy OCL parser names |
| // which some clients already depend on |
| String operName = operation.getName(); |
| int opcode = OCLStandardLibraryUtil.getOperationCode(operName); |
| switch (opcode) { |
| case PredefinedType.PLUS : |
| operName = "plus"; //$NON-NLS-1$ |
| break; |
| case PredefinedType.MINUS : |
| operName = "minus"; //$NON-NLS-1$ |
| break; |
| case PredefinedType.TIMES : |
| operName = "times"; //$NON-NLS-1$ |
| break; |
| case PredefinedType.DIVIDE : |
| operName = "divide"; //$NON-NLS-1$ |
| break; |
| case PredefinedType.LESS_THAN : |
| operName = "lessThan"; //$NON-NLS-1$ |
| break; |
| case PredefinedType.LESS_THAN_EQUAL : |
| operName = "lessThanEqual"; //$NON-NLS-1$ |
| break; |
| case PredefinedType.GREATER_THAN : |
| operName = "greaterThan"; //$NON-NLS-1$ |
| break; |
| case PredefinedType.GREATER_THAN_EQUAL : |
| operName = "greaterThanEqual"; //$NON-NLS-1$ |
| break; |
| } |
| |
| // get containing class for the operation |
| EClass container = operation.getEContainingClass(); |
| |
| // get the corresponding java instance class |
| Class<?> containerClass = container.getInstanceClass(); |
| |
| // get the parameter types as java classes |
| EList<EParameter> parms = operation.getEParameters(); |
| Class<?>[] javaParms = new Class[parms.size()]; |
| for (int i = 0, n = parms.size(); i < n; i++) { |
| EParameter parm = parms.get(i); |
| |
| if (parm.isMany()) { |
| javaParms[i] = EList.class; // TODO: EList could be suppressed |
| } else { |
| javaParms[i] = parm.getEType().getInstanceClass(); |
| } |
| } |
| |
| // lookup the method on the java class |
| try { |
| result = containerClass.getMethod(operName, javaParms); |
| } catch (NoSuchMethodException e) { |
| // do nothing |
| } |
| |
| return result; |
| } |
| |
| // implements the inherited specification |
| @Override |
| protected Object getInvalidResult() { |
| return OCLStandardLibraryImpl.INVALID; |
| } |
| |
| // implements the inherited specification |
| public Object navigateProperty(EStructuralFeature property, |
| List<?> qualifiers, Object target) |
| throws IllegalArgumentException { |
| |
| if (target instanceof EObject) { |
| EObject etarget = (EObject) target; |
| |
| if (etarget.eClass().getEAllStructuralFeatures().contains(property)) { |
| EClassifier oclType = UMLReflectionImpl.INSTANCE.getOCLType(property); |
| if (oclType instanceof VoidType) { |
| // then the only instance is null; using eGet would |
| // cause a ClassCastException because VoidTypeImpl |
| // is neither an EClass nor an EDataType. |
| return null; |
| } |
| return coerceValue(property, etarget.eGet(property), true); |
| } |
| } else if (target instanceof Tuple<?, ?>) { |
| @SuppressWarnings("unchecked") |
| Tuple<EOperation, EStructuralFeature> tuple = (Tuple<EOperation, EStructuralFeature>) target; |
| |
| if (tuple.getTupleType().oclProperties().contains(property)) { |
| return tuple.getValue(property); |
| } |
| } |
| |
| throw new IllegalArgumentException(); |
| } |
| |
| /** |
| * Obtains the collection kind appropriate for representing the values of |
| * the specified typed element. |
| * |
| * @param element |
| * a typed element (property, operation, etc.) |
| * |
| * @return the collection kind appropriate to the multiplicity, orderedness, |
| * and uniqueness of the element, or <code>null</code> if it is not |
| * many |
| * @since 3.2 |
| */ |
| protected static CollectionKind getCollectionKind(ETypedElement element) { |
| EClassifier oclType = UMLReflectionImpl.INSTANCE.getOCLType(element); |
| |
| CollectionKind result = null; |
| |
| if (oclType instanceof CollectionType<?, ?>) { |
| result = ((CollectionType<?, ?>) oclType).getKind(); |
| ObjectUtil.dispose(oclType); // we created this object |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Coerces the value of the specified typed element into the appropriate |
| * representation, derived from the supplied <code>value</code> template. |
| * The <code>value</code> is coerced to the appropriate collection kind for |
| * this element (or scalar if not multi-valued). The original value may |
| * either be used as is where possible or, optionally, copied into the new |
| * collection (if multi-valued). |
| * |
| * @param element |
| * a typed element (property, operation, etc.) |
| * @param value |
| * the computed value of the element |
| * @param copy |
| * whether to copy the specified value into the resulting |
| * collection/scalar value |
| * |
| * @return the value, in the appropriate OCL collection type or scalar form |
| * as required |
| * |
| * @see #getCollectionKind(ETypedElement) |
| * @since 3.2 |
| */ |
| protected Object coerceValue(ETypedElement element, Object value, boolean copy) { |
| CollectionKind kind = getCollectionKind(element); |
| |
| if (kind != null) { |
| if (value instanceof Collection<?>) { |
| return copy |
| ? CollectionUtil.createNewCollection(kind, |
| (Collection<?>) value) |
| : value; |
| } else { |
| Collection<Object> result = CollectionUtil |
| .createNewCollection(kind); |
| result.add(value); |
| return result; |
| } |
| } else { |
| if (value instanceof Collection<?>) { |
| Collection<?> collection = (Collection<?>) value; |
| return collection.isEmpty() |
| ? null |
| : collection.iterator().next(); |
| } else { |
| return value; |
| } |
| } |
| } |
| |
| // implements the inherited specification |
| public Object navigateAssociationClass(EClassifier associationClass, |
| EStructuralFeature navigationSource, Object target) |
| throws IllegalArgumentException { |
| |
| if (target instanceof EObject) { |
| EObject etarget = (EObject) target; |
| |
| EReference ref = getAssociationClassReference(etarget, |
| (EClass) associationClass); |
| |
| if (etarget.eClass().getEAllStructuralFeatures().contains(ref)) { |
| return etarget.eGet(ref); |
| } |
| } |
| |
| throw new IllegalArgumentException(); |
| } |
| |
| /** |
| * Retrieves the reference feature in the specified context object that |
| * references the specified association class. |
| * |
| * @param context |
| * the context object |
| * @param associationClass |
| * the association class that it references |
| * @return the reference in question |
| */ |
| private EReference getAssociationClassReference(EObject context, |
| EClass associationClass) { |
| EReference result = null; |
| |
| StringBuffer nameBuf = new StringBuffer(associationClass.getName()); |
| UnicodeSupport.setCodePointAt(nameBuf, 0, |
| UnicodeSupport.toLowerCase(UnicodeSupport.codePointAt(nameBuf, 0))); |
| String name = nameBuf.toString(); |
| |
| for (EReference next : context.eClass().getEAllReferences()) { |
| if (name.equals(next.getName()) |
| && (associationClass == next.getEReferenceType())) { |
| result = next; |
| break; |
| } |
| } |
| |
| return result; |
| } |
| |
| // implements the inherited specification |
| public Tuple<EOperation, EStructuralFeature> createTuple(EClassifier type, |
| Map<EStructuralFeature, Object> values) { |
| |
| @SuppressWarnings("unchecked") |
| TupleType<EOperation, EStructuralFeature> tupleType = (TupleType<EOperation, EStructuralFeature>) type; |
| |
| return new AbstractTuple<EOperation, EStructuralFeature>(tupleType, values) |
| { |
| @Override |
| protected String getName(EStructuralFeature part) { |
| return part.getName(); |
| } |
| }; |
| } |
| |
| // implements the inherited specification |
| public Map<EClass, Set<EObject>> createExtentMap(Object object) { |
| if (object instanceof EObject) { |
| return new LazyExtentMap<EClass, EObject>((EObject) object) { |
| |
| // implements the inherited specification |
| @Override |
| protected boolean isInstance(EClass cls, EObject element) { |
| return cls.isInstance(element); |
| } |
| }; |
| } |
| |
| return Collections.emptyMap(); |
| } |
| |
| // implements the inherited specification |
| public boolean isKindOf(Object object, EClassifier classifier) { |
| // special case for Integer/UnlimitedNatural and Real which |
| // are not related types in java but are in OCL |
| if ((object.getClass() == Integer.class) |
| && (classifier.getInstanceClass() == Double.class)) { |
| return Boolean.TRUE; |
| } else if (classifier instanceof AnyType) { |
| return Boolean.TRUE; |
| } |
| |
| return classifier.isInstance(object); |
| } |
| |
| // implements the inherited specification |
| public boolean isTypeOf(Object object, EClassifier classifier) { |
| if (classifier instanceof EClass && object instanceof EObject) { |
| return ((EObject) object).eClass() == classifier; |
| } else if (!(object instanceof EObject) |
| && !(classifier instanceof EClass)) { |
| return object.getClass() == classifier.getInstanceClass(); |
| } |
| |
| return false; |
| } |
| |
| // implements the inherited specification |
| public EClassifier getType(Object object) { |
| return EcoreEnvironmentFactory.oclType(object); |
| } |
| |
| /** |
| * Ecore implementation of the enumeration literal value. |
| * |
| * @since 1.2 |
| */ |
| public Enumerator getValue(EEnumLiteral enumerationLiteral) { |
| return enumerationLiteral.getInstance(); |
| } |
| |
| /** |
| * Configure the check that an incorrect usage of "Operation Reflection" |
| * false in a genmodel has omitted the eInvoke override. Disabling the |
| * check eliminates the overhead of using Java Reflection to perform |
| * run-time code checking but incurs the risk of mysterious evaluation |
| * malfunctions if the genmodel is wrong. |
| * <p> |
| * The check is enabled by default. |
| * |
| * @param checkDisabled true to disable the check |
| * |
| * @since 3.1 |
| */ |
| public void setOperationReflectionCheckDisabled(boolean checkDisabled) { |
| mustCheckOperationReflectionConsistency = !checkDisabled; |
| } |
| |
| /** |
| * Check if the provided source object has support for a delegated invocation |
| * of an OCL expression body. |
| * <p> |
| * Missing support is the result of a failure to change the default genmodel |
| * 'Operation Reflection' setting to true. |
| * <p> |
| * Lack of support is tested by checking for a missing override of |
| * {@link org.eclipse.emf.ecore.impl.EOperationImpl#eInvoke(int, EList)}. |
| * |
| * @param source the object to check |
| * |
| * @since 3.1 |
| * @noextend This interface may be removed in future releases if a better |
| * workaround for Bug 322159 is found. |
| */ |
| protected Exception checkOperationReflectionConsistency(Object source) { |
| |
| try { |
| source.getClass().getDeclaredMethod( |
| "eInvoke", int.class, EList.class); //$NON-NLS-1$ |
| return null; |
| } catch (NoSuchMethodException e) { |
| return e; |
| } catch (SecurityException e) { |
| throw new IllegalArgumentException(e); |
| } |
| } |
| |
| /** |
| * @since 3.1 |
| */ |
| public Object navigateOppositeProperty(EReference property, Object target) throws IllegalArgumentException { |
| Object result = null; |
| if (property.isContainment()) { |
| EObject resultCandidate = ((EObject) target).eContainer(); |
| if (resultCandidate != null) { |
| // first check if the container is assignment-compatible to the property's owning type: |
| if (property.getEContainingClass().isInstance(resultCandidate)) { |
| Object propertyValue = resultCandidate.eGet(property); |
| if (propertyValue == target |
| || (propertyValue instanceof Collection<?> && ((Collection<?>) propertyValue).contains(target))) { |
| // important to create a copy because, e.g., the partial evaluator may modify the resulting collection |
| result = CollectionUtil.createNewBag(Collections.singleton(resultCandidate)); |
| } |
| } |
| } |
| } else { |
| if (oppositeEndFinder != null) { |
| result = oppositeEndFinder.navigateOppositePropertyWithForwardScope(property, (EObject) target); |
| } |
| } |
| return result; |
| } |
| |
| } |