| /******************************************************************************* |
| * Copyright (c) 2005, 2018 IBM Corporation, Zeligsoft Inc., and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v2.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v20.html |
| * |
| * Contributors: |
| * IBM - Initial API and implementation |
| * Zeligsoft - Bug 247079 |
| * Bas Elzinga - Bug 259630 |
| *******************************************************************************/ |
| |
| package org.eclipse.ocl.uml; |
| |
| 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.EList; |
| import org.eclipse.emf.ecore.EClass; |
| import org.eclipse.emf.ecore.EClassifier; |
| import org.eclipse.emf.ecore.EEnum; |
| import org.eclipse.emf.ecore.EEnumLiteral; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EPackage; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| import org.eclipse.emf.ecore.EcoreFactory; |
| import org.eclipse.ocl.AbstractEvaluationEnvironment; |
| import org.eclipse.ocl.Environment; |
| import org.eclipse.ocl.EvaluationEnvironment; |
| import org.eclipse.ocl.LazyExtentMap; |
| import org.eclipse.ocl.expressions.CollectionKind; |
| import org.eclipse.ocl.types.TupleType; |
| import org.eclipse.ocl.uml.internal.OCLStandardLibraryImpl; |
| import org.eclipse.ocl.uml.options.EvaluationMode; |
| import org.eclipse.ocl.uml.options.UMLEvaluationOptions; |
| import org.eclipse.ocl.uml.util.OCLUMLUtil; |
| import org.eclipse.ocl.util.CollectionUtil; |
| import org.eclipse.ocl.util.OCLStandardLibraryUtil; |
| import org.eclipse.ocl.util.Tuple; |
| import org.eclipse.ocl.utilities.PredefinedType; |
| import org.eclipse.uml2.common.util.UML2Util; |
| import org.eclipse.uml2.uml.Association; |
| import org.eclipse.uml2.uml.AssociationClass; |
| import org.eclipse.uml2.uml.Class; |
| import org.eclipse.uml2.uml.Classifier; |
| import org.eclipse.uml2.uml.Element; |
| import org.eclipse.uml2.uml.Enumeration; |
| import org.eclipse.uml2.uml.EnumerationLiteral; |
| import org.eclipse.uml2.uml.Feature; |
| import org.eclipse.uml2.uml.InstanceSpecification; |
| import org.eclipse.uml2.uml.InstanceValue; |
| import org.eclipse.uml2.uml.LiteralBoolean; |
| import org.eclipse.uml2.uml.LiteralInteger; |
| import org.eclipse.uml2.uml.LiteralNull; |
| import org.eclipse.uml2.uml.LiteralString; |
| import org.eclipse.uml2.uml.LiteralUnlimitedNatural; |
| import org.eclipse.uml2.uml.MultiplicityElement; |
| import org.eclipse.uml2.uml.NamedElement; |
| import org.eclipse.uml2.uml.Operation; |
| import org.eclipse.uml2.uml.Package; |
| import org.eclipse.uml2.uml.Parameter; |
| import org.eclipse.uml2.uml.ParameterDirectionKind; |
| import org.eclipse.uml2.uml.Profile; |
| import org.eclipse.uml2.uml.ProfileApplication; |
| import org.eclipse.uml2.uml.Property; |
| import org.eclipse.uml2.uml.Slot; |
| import org.eclipse.uml2.uml.Stereotype; |
| import org.eclipse.uml2.uml.StructuralFeature; |
| import org.eclipse.uml2.uml.UMLPackage; |
| import org.eclipse.uml2.uml.ValueSpecification; |
| import org.eclipse.uml2.uml.util.UMLSwitch; |
| import org.eclipse.uml2.uml.util.UMLUtil; |
| |
| /** |
| * Implementation of the {@link EvaluationEnvironment} for evaluation of OCL |
| * expressions on instances of UML models (i.e., on M0 models). |
| * |
| * @author Christian W. Damus (cdamus) |
| */ |
| public class UMLEvaluationEnvironment |
| extends |
| AbstractEvaluationEnvironment<Classifier, Operation, Property, Class, EObject> |
| implements EvaluationEnvironment.Enumerations<EnumerationLiteral> { |
| |
| private static final EPackage CACHE_MISS = EcoreFactory.eINSTANCE |
| .createEPackage(); |
| |
| private final EPackage.Registry registry; |
| |
| // cache of UML to Ecore package mappings |
| private final Map<Package, EPackage> packageMap = new java.util.HashMap<Package, EPackage>(); |
| |
| private final Map<Feature, String> featureNameMap = new java.util.HashMap<Feature, String>(); |
| |
| private final UMLEnvironmentFactory factory; |
| |
| private ValueExtractor valueExtractor; |
| |
| /** |
| * Initializes me. |
| */ |
| public UMLEvaluationEnvironment(UMLEnvironmentFactory factory) { |
| this.factory = factory; |
| this.registry = factory.getEPackageRegistry(); |
| } |
| |
| /** |
| * Initializes me with my parent evaluation environment (nesting scope). |
| * |
| * @param parent |
| * my parent (nesting scope); must not be <code>null</code> |
| */ |
| public UMLEvaluationEnvironment( |
| EvaluationEnvironment<Classifier, Operation, Property, Class, EObject> parent) { |
| super(parent); |
| |
| UMLEvaluationEnvironment umlParent = (UMLEvaluationEnvironment) getParent(); |
| |
| this.factory = umlParent.factory; |
| this.registry = umlParent.getEPackageRegistry(); |
| } |
| |
| /** |
| * Obtains my package registry for looking up the Ecore definitions of UML |
| * packages. |
| * |
| * @return my package registry |
| */ |
| protected final EPackage.Registry getEPackageRegistry() { |
| return registry; |
| } |
| |
| /** |
| * Queries my effective evaluation mode which is just my assigned mode, |
| * unless I am {@link EvaluationMode#ADAPTIVE}. In that case, I derive an |
| * effective evaluation mode from my <tt>self</tt> context variable. |
| * <p> |
| * <b>Note</b> that my effective evaluation mode is inherited from my parent |
| * evaluation environment, if I have one. |
| * </p> |
| * |
| * @return my effective evaluation mode |
| * |
| * @see #getParent() |
| * @see #getEffectiveEvaluationMode(Object) |
| * |
| * @since 1.2 |
| */ |
| protected EvaluationMode getEffectiveEvaluationMode() { |
| return getEffectiveEvaluationMode(getValueOf(Environment.SELF_VARIABLE_NAME)); |
| } |
| |
| /** |
| * Queries my effective evaluation mode which is just my assigned mode, |
| * unless I am {@link EvaluationMode#ADAPTIVE}. In that case, I derive an |
| * effective evaluation mode from the specified <tt>context</tt> object. |
| * <p> |
| * <b>Note</b> that my effective evaluation mode is inherited from my parent |
| * evaluation environment, if I have one. |
| * </p> |
| * |
| * @return a context object |
| * @return my effective evaluation mode |
| * |
| * @see #getParent() |
| * |
| * @since 1.2 |
| */ |
| protected EvaluationMode getEffectiveEvaluationMode(Object context) { |
| EvaluationMode result = getValue(UMLEvaluationOptions.EVALUATION_MODE); |
| |
| if (result == EvaluationMode.ADAPTIVE) { |
| if (context instanceof InstanceSpecification) { |
| result = EvaluationMode.INSTANCE_MODEL; |
| } else { |
| result = EvaluationMode.RUNTIME_OBJECTS; |
| } |
| } |
| |
| return result; |
| } |
| |
| @Override |
| public Object callOperation(Operation operation, int opcode, Object source, |
| Object[] args) |
| throws IllegalArgumentException { |
| |
| // 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(Operation operation, Object receiver) { |
| Method result = null; |
| |
| String operName = getEcoreOperationName(operation); |
| |
| // 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 |
| 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 |
| Classifier container = (Classifier) operation.getOwner(); |
| |
| // get the corresponding Ecore classifier |
| EClassifier eclassifier = getEClassifier(container, receiver); |
| |
| // get the corresponding java instance class |
| java.lang.Class<?> containerClass = eclassifier.getInstanceClass(); |
| |
| // get the parameter types as java classes |
| EList<Parameter> parms = operation.getOwnedParameters(); |
| EList<java.lang.Class<?>> javaParms = new BasicEList<java.lang.Class<?>>( |
| parms.size()); |
| for (int i = 0, n = parms.size(); i < n; i++) { |
| Parameter parm = parms.get(i); |
| |
| if (parm.getDirection() != ParameterDirectionKind.RETURN_LITERAL) { |
| if (parm.isMultivalued()) { |
| javaParms.add(EList.class); // TODO: EList could be |
| // suppressed |
| } else { |
| eclassifier = getEClassifier((Classifier) parm.getType(), |
| receiver); |
| javaParms.add(eclassifier.getInstanceClass()); |
| } |
| } |
| } |
| |
| // lookup the method on the java class |
| try { |
| result = containerClass.getMethod(operName, javaParms |
| .toArray(new java.lang.Class<?>[javaParms.size()])); |
| } catch (NoSuchMethodException e) { |
| // do nothing |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Gets the name of the specified operation, accounting for the possibility |
| * that it is aliased using an Ecore stereotype. The resulting name is |
| * cached for fast repeated access. |
| * |
| * @param operation an operation |
| * @return the name of the corresponding Ecore operation |
| */ |
| private String getEcoreOperationName(Operation operation) { |
| String result = featureNameMap.get(operation); |
| |
| if (result == null) { |
| result = operation.getName(); |
| |
| Stereotype stereo = getAppliedEcoreStereotype(operation, |
| UMLUtil.STEREOTYPE__E_OPERATION); |
| if (stereo != null) { |
| // look for an <<eOperation>> alias name |
| String alias = (String) operation.getValue(stereo, |
| UMLUtil.TAG_DEFINITION__OPERATION_NAME); |
| if ((alias != null) && (alias.length() > 0)) { |
| result = alias; |
| } |
| } |
| |
| featureNameMap.put(operation, result); |
| } |
| |
| return result; |
| } |
| |
| private Stereotype getAppliedEcoreStereotype(Element element, |
| String name) { |
| return element.getAppliedStereotype("Ecore" //$NON-NLS-1$ |
| + NamedElement.SEPARATOR + name); |
| } |
| |
| // implements the inherited specification |
| @Override |
| protected Object getInvalidResult() { |
| return OCLStandardLibraryImpl.INVALID; |
| } |
| |
| // implements the inherited specification |
| public Object navigateProperty(Property property, List<?> qualifiers, |
| Object source) |
| throws IllegalArgumentException { |
| |
| switch (getEffectiveEvaluationMode()) { |
| case INSTANCE_MODEL: |
| InstanceSpecification instance = (InstanceSpecification) source; |
| |
| // in the case that the association owns the property and the |
| // source is an instance of that association, then we are navigating |
| // an end from the association, itself, so this is not a |
| // non-navigable property scenario |
| Association association = property.getOwningAssociation(); |
| if ((association != null) && !isInstance(association, instance)) { |
| // non-navigable property. Qualifiers don't apply |
| return navigateNonNavigableProperty(property, association, |
| instance); |
| } |
| |
| // TODO: In case there is no slot, look for an init constraint |
| // and a default value, in that order |
| for (Slot slot : instance.getSlots()) { |
| if (slot.getDefiningFeature() == property) { |
| if (!qualifiers.isEmpty()) { |
| return findValueQualifiedBy(instance, property, |
| qualifiers); |
| } |
| |
| return getValue(slot); |
| } |
| } |
| |
| // look for a slot on a property that redefines the property |
| // we are looking for |
| for (Slot slot : instance.getSlots()) { |
| if (redefines(slot.getDefiningFeature(), property)) { |
| if (!qualifiers.isEmpty()) { |
| return findValueQualifiedBy(instance, property, |
| qualifiers); |
| } |
| |
| return getValue(slot); |
| } |
| } |
| |
| // an instance needs not have a slot for every feature |
| return isMultivaluedSlot(instance, property) ? CollectionUtil |
| .createNewCollection(getCollectionKind(property)) |
| : null; |
| case RUNTIME_OBJECTS: |
| if (source instanceof EObject) { |
| // TODO: In case the property is unset, look for an init constraint |
| // and a default value, in that order |
| EObject esource = (EObject) source; |
| |
| EStructuralFeature feature = esource.eClass() |
| .getEStructuralFeature(getEcoreAttributeName(property)); |
| |
| if (feature != null) { |
| Object result = esource.eGet(feature); |
| |
| return coerceValue(property, result, true); |
| } else { |
| // must be a non-navigable property? |
| Property otherEnd = property.getOtherEnd(); |
| if (otherEnd != null) { |
| EClass eclass = null; |
| |
| Element owner = otherEnd.getOwner(); |
| if ((owner instanceof Classifier) && (owner != otherEnd.getAssociation())) { |
| eclass = (EClass) OCLUMLUtil.getEClassifier( |
| (Classifier) owner, source, registry); |
| } |
| |
| if (eclass != null) { |
| EStructuralFeature eEnd = eclass |
| .getEStructuralFeature(getEcoreAttributeName(otherEnd)); |
| |
| if (eEnd != null) { |
| Collection<Object> result = createCollection(property); |
| |
| // maybe eEnd is a containment reference |
| if (esource.eContainmentFeature() == eEnd) { |
| result.add(esource.eContainer()); |
| } else { |
| for (EStructuralFeature.Setting setting : UML2Util |
| .getNonNavigableInverseReferences(esource)) { |
| if (setting.getEStructuralFeature() == eEnd) { |
| result.add(setting.getEObject()); |
| } |
| } |
| } |
| |
| return coerceValue(property, result, false); |
| } |
| } |
| } |
| } |
| } else if (source instanceof Tuple<?, ?>) { |
| @SuppressWarnings("unchecked") |
| Tuple<Operation, Property> tuple = (Tuple<Operation, Property>) source; |
| |
| if (tuple.getTupleType().oclProperties().contains(property)) { |
| return tuple.getValue(property); |
| } |
| } |
| } |
| |
| throw new IllegalArgumentException( |
| "no such property: " + property.getName()); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Gets the name of the specified attribute, accounting for the possibility |
| * that it is aliased using an Ecore stereotype. The resulting name is |
| * cached for fast repeated access. |
| * |
| * @param attribute an attribute |
| * @return the name of the corresponding Ecore attribute |
| */ |
| private String getEcoreAttributeName(Property attribute) { |
| String result = featureNameMap.get(attribute); |
| |
| if (result == null) { |
| Stereotype stereo = getAppliedEcoreStereotype(attribute, |
| UMLUtil.STEREOTYPE__E_ATTRIBUTE); |
| String aliasAttribute = UMLUtil.TAG_DEFINITION__ATTRIBUTE_NAME; |
| if (stereo == null) { |
| stereo = getAppliedEcoreStereotype(attribute, |
| UMLUtil.STEREOTYPE__E_REFERENCE); |
| aliasAttribute = UMLUtil.TAG_DEFINITION__REFERENCE_NAME; |
| } |
| if (stereo != null) { |
| // look for an <<eAttribute>> or <<eReference>> alias name |
| String alias = (String) attribute.getValue(stereo, |
| aliasAttribute); |
| if ((alias != null) && (alias.length() > 0)) { |
| result = alias; |
| } |
| } |
| |
| if (result == null) { |
| result = UMLUtil.getValidJavaIdentifier(attribute.getName()); |
| } |
| |
| featureNameMap.put(attribute, result); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Obtains the collection kind appropriate for representing the values of |
| * the specified feature. |
| * |
| * @param feature |
| * a feature |
| * |
| * @return the collection kind appropriate to the multiplicity, orderedness, |
| * and uniqueness of the feature, or <code>null</code> if it is |
| * not many |
| */ |
| private static CollectionKind getCollectionKind(Feature feature) { |
| MultiplicityElement element = null; |
| |
| if (feature instanceof MultiplicityElement) { |
| element = (MultiplicityElement) feature; |
| } else if (feature instanceof Operation) { |
| element = ((Operation) feature).getReturnResult(); |
| } |
| |
| if (element != null) { |
| return element.isMultivalued() ? CollectionKind.getKind(element |
| .isOrdered(), element.isUnique()) |
| : null; |
| } |
| |
| return null; // void operation is implicitly scalar |
| } |
| |
| /** |
| * Creates a collection value for the specified feature. The collection type |
| * is arbitrary if the feature is not multi-valued (but in a context where a |
| * collection is needed for computation). |
| * |
| * @param feature |
| * a feature (property, operation, etc.) |
| * @return a collection to store its value |
| */ |
| private Collection<Object> createCollection(Feature feature) { |
| CollectionKind kind = getCollectionKind(feature); |
| |
| if (kind != null) { |
| return CollectionUtil.createNewCollection(kind); |
| } else { |
| // doesn't matter the collection type |
| return new BasicEList.FastCompare<Object>(); |
| } |
| } |
| |
| /** |
| * Coerces the value of the specified feature 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 feature (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 feature |
| * a feature (property, operation, etc.) |
| * @param value |
| * the computed value of the property |
| * @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(Feature) |
| */ |
| private Object coerceValue(Feature feature, Object value, boolean copy) { |
| CollectionKind kind = getCollectionKind(feature); |
| |
| 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; |
| } |
| } |
| } |
| |
| /** |
| * Navigates a non-navigable property in an instance specification context. |
| * |
| * @param property |
| * the non-navigable property to navigate |
| * @param association |
| * the association of which the property is a member |
| * @param instance |
| * the instance specification from which we are navigating the |
| * property |
| * |
| * @return the property value |
| */ |
| private Object navigateNonNavigableProperty(Property property, |
| Association association, InstanceSpecification instance) { |
| Collection<InstanceSpecification> result = CollectionUtil |
| .createNewSet(); |
| |
| Property otherEnd = property.getOtherEnd(); |
| |
| for (Slot slot : getSlotsReferencing(instance)) { |
| // TODO: Handle redefinition of the other end |
| if (slot.getDefiningFeature() == otherEnd) { |
| InstanceSpecification referencer = slot.getOwningInstance(); |
| |
| if (referencer != null) { |
| if (isInstance(association, referencer)) { |
| // get value of the other end. Multiplicity of an |
| // association end is always 1 from the association's |
| // perspective |
| Slot otherSlot = getSlot(referencer, property); |
| if (otherSlot != null) { |
| ValueSpecification value = otherSlot.getValues() |
| .isEmpty() ? null |
| : otherSlot.getValues().get(0); |
| if ((value != null) |
| && (value instanceof InstanceValue)) { |
| result.add(((InstanceValue) value) |
| .getInstance()); |
| } |
| } |
| } else { |
| // navigable opposite |
| result.add(referencer); |
| } |
| } |
| } |
| } |
| |
| return coerceValue(property, result, true); |
| } |
| |
| /** |
| * Queries whether the specified property redefines (recursively) another |
| * property. |
| * |
| * @param feature |
| * a property |
| * @param redefined |
| * a property that (we hope) it redefines |
| * |
| * @return <code>true</code> if the feature redefines the redefined |
| * property (or some property that, recursively, redefines it); |
| * <code>false</code>, otherwise |
| */ |
| private boolean redefines(StructuralFeature feature, Property redefined) { |
| if (feature == redefined) { |
| // base case |
| return true; |
| } |
| |
| if (feature instanceof Property) { |
| Property property = (Property) feature; |
| |
| for (Property next : property.getRedefinedProperties()) { |
| if (redefines(next, redefined)) { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Gets the Java value in an instance's slot. |
| * |
| * @param slot |
| * a slot |
| * @return the value in the slot, as a Java object (not a |
| * {@link ValueSpecification}) |
| */ |
| private Object getValue(Slot slot) { |
| List<ValueSpecification> values = slot.getValues(); |
| |
| if (isMultivaluedSlot(slot)) { |
| StructuralFeature feature = slot.getDefiningFeature(); |
| |
| // create a collection of the kind appropriate to the feature. |
| // If the feature is ordered, then the order of the values in |
| // the slot is preserved |
| return getValueExtractor() |
| .extractValues( |
| values, |
| CollectionKind.getKind(feature.isOrdered(), feature |
| .isUnique())); |
| } else { |
| return values.isEmpty() ? null |
| : getValueExtractor().extractValue(values.get(0)); |
| } |
| } |
| |
| /** |
| * Queries whether an instance's slot is multivalued. This depends on the |
| * owner, because a member-end slot of an association class link has |
| * multiplicity 1 regardless of the multiplicity of the slot's defining |
| * property. |
| * |
| * @param slot |
| * a slot |
| * @return whether the slot is multi-valued from the perspective of its |
| * owner's type |
| */ |
| private boolean isMultivaluedSlot(Slot slot) { |
| return isMultivaluedSlot(slot.getOwningInstance(), (Property) slot |
| .getDefiningFeature()); |
| } |
| |
| private boolean isMultivaluedSlot(InstanceSpecification owner, |
| Property property) { |
| Classifier classifier = owner.getClassifiers().isEmpty() ? null |
| : owner.getClassifiers().get(0); |
| |
| if (classifier instanceof Association) { |
| return (property.getAssociation() != classifier) |
| && property.isMultivalued(); |
| } |
| |
| return property.isMultivalued(); |
| } |
| |
| private ValueExtractor getValueExtractor() { |
| if (valueExtractor == null) { |
| valueExtractor = new ValueExtractor(); |
| } |
| |
| return valueExtractor; |
| } |
| |
| Object findValueQualifiedBy(InstanceSpecification source, |
| Property property, List<?> qualifiers) { |
| // from amongst the links referencing this source instance, find the |
| // one that has the specified qualifier values and get its value for |
| // the property |
| Association association = property.getAssociation(); |
| Property otherEnd = property.getOtherEnd(); |
| |
| for (EStructuralFeature.Setting setting : UML2Util |
| .getNonNavigableInverseReferences(source)) { |
| |
| if (setting.getEStructuralFeature() == UMLPackage.Literals.INSTANCE_VALUE__INSTANCE) { |
| |
| InstanceValue value = (InstanceValue) setting.getEObject(); |
| if (value.getOwner() instanceof Slot) { |
| Slot slot = (Slot) value.getOwner(); |
| |
| // TODO: Account for redefinition of the member ends |
| if (slot.getDefiningFeature() == otherEnd) { |
| InstanceSpecification link = slot.getOwningInstance(); |
| |
| for (Classifier c : link.getClassifiers()) { |
| if (c.conformsTo(association)) { |
| // found a link instance of this association. |
| // Look for the qualifier values |
| if (match(link, property.getQualifiers(), |
| qualifiers)) { |
| // get the property value for this link |
| return navigateProperty(property, |
| Collections.emptyList(), link); |
| } |
| |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Queries whether the specified <code>instance</code> has slots for the |
| * given <code>properties</code> that have these <code>values</code>. |
| * |
| * @param instance |
| * an instance specification |
| * @param properties |
| * a list of properties |
| * @param values |
| * values to look for in the slots of the instance defined by the |
| * given properties |
| * |
| * @return <code>true</code> if this instance has the matching property |
| * values; <code>false</code>, otherwise |
| */ |
| private boolean match(InstanceSpecification instance, |
| List<Property> properties, List<?> values) { |
| |
| int found = 0; |
| |
| for (Slot slot : instance.getSlots()) { |
| int index = properties.indexOf(slot.getDefiningFeature()); |
| |
| if (index >= 0) { |
| Object actualValue = getValue(slot); |
| |
| if (UML2Util.safeEquals(actualValue, values.get(index))) { |
| found++; |
| } |
| } |
| } |
| |
| return found == properties.size(); |
| } |
| |
| // implements the inherited specification |
| public Object navigateAssociationClass(Classifier associationClass, |
| Property navigationSource, Object source) |
| throws IllegalArgumentException { |
| |
| switch (getEffectiveEvaluationMode()) { |
| case INSTANCE_MODEL: |
| InstanceSpecification sourceInstance = (InstanceSpecification) source; |
| |
| Collection<Property> ends; |
| if (navigationSource != null) { |
| // qualified navigation: select the links belonging to the |
| // the navigation source, i.e., where the opposite of the |
| // navigation source references the source instance |
| ends = Collections.singleton(navigationSource.getOtherEnd()); |
| } else { |
| // unqualified navigation: select links where either end is |
| // the navigation source |
| ends = ((AssociationClass) associationClass).getMemberEnds(); |
| } |
| |
| // find link instances of this association class that have a slot |
| // referencing the source instance |
| Collection<InstanceSpecification> links = CollectionUtil |
| .createNewSet(); |
| |
| for (Slot slot : getSlotsReferencing(sourceInstance)) { |
| // only consider slots that are defined by association |
| // ends, because the association class may have other |
| // properties that can reference our source instance |
| // TODO: Account for redefinition of the member ends |
| if (ends.contains(slot.getDefiningFeature())) { |
| InstanceSpecification link = slot.getOwningInstance(); |
| |
| if (isInstance(associationClass, link)) { |
| // found a link! |
| links.add(link); |
| } |
| } |
| } |
| |
| // these association class instances are the answer. If we know the |
| // navigation source and it is not many, then do not return a |
| // collection value |
| if ((navigationSource != null) && !navigationSource.isMultivalued()) { |
| return links.isEmpty() ? null |
| : links.iterator().next(); |
| } |
| |
| return links; |
| case RUNTIME_OBJECTS: |
| // UML-to-Ecore conversion does not support association classes. |
| // Fall through |
| } |
| |
| throw new IllegalArgumentException( |
| "no such association class: " + associationClass.getName()); //$NON-NLS-1$ |
| } |
| |
| private boolean isInstance(Classifier classifier, |
| InstanceSpecification instance) { |
| for (Classifier c : instance.getClassifiers()) { |
| if (c.conformsTo(classifier)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Finds the slot defined by the specified property in a given instance |
| * specification. |
| * |
| * @param instance |
| * an instance specification |
| * @param property |
| * a property of (one of) the instance's classifier(s) |
| * |
| * @return the instance's slot for this property, or <code>null</code> if |
| * no such slot is defined |
| */ |
| private Slot getSlot(InstanceSpecification instance, Property property) { |
| for (Slot slot : instance.getSlots()) { |
| // TODO: Account for property redefinition |
| if (slot.getDefiningFeature() == property) { |
| return slot; |
| } |
| } |
| |
| return null; |
| } |
| |
| /** |
| * Performs a reverse-reference lookup for slots that have |
| * <code>InstanceValue</code>s referencing the specified instance |
| * specification. |
| * |
| * @param instance |
| * an instance specification |
| * @return the slots referencing it (or an empty collection if none) |
| */ |
| private Collection<Slot> getSlotsReferencing(InstanceSpecification instance) { |
| Collection<Slot> result = CollectionUtil.createNewSet(); |
| |
| for (EStructuralFeature.Setting setting : UML2Util |
| .getNonNavigableInverseReferences(instance)) { |
| if (setting.getEStructuralFeature() == UMLPackage.Literals.INSTANCE_VALUE__INSTANCE) { |
| InstanceValue value = (InstanceValue) setting.getEObject(); |
| if (value.getOwner() instanceof Slot) { |
| result.add((Slot) value.getOwner()); |
| } |
| } |
| } |
| |
| return result; |
| } |
| |
| // implements the inherited specification |
| public Tuple<Operation, Property> createTuple(Classifier type, |
| Map<Property, Object> values) { |
| |
| @SuppressWarnings("unchecked") |
| TupleType<Operation, Property> tupleType = (TupleType<Operation, Property>) type; |
| |
| return new AbstractTuple<Operation, Property>(tupleType, values) |
| { |
| @Override |
| protected String getName(Property part) { |
| return part.getName(); |
| } |
| }; |
| } |
| |
| // implements the inherited specification |
| public Map<Class, Set<EObject>> createExtentMap(Object object) { |
| if (object instanceof EObject) { // covers both run-time EObjects and model InstanceSpecifications |
| switch (getEffectiveEvaluationMode(object)) { |
| case INSTANCE_MODEL: |
| return new LazyExtentMap<Class, EObject>((EObject) object) { |
| |
| // implements the inherited specification |
| @Override |
| protected boolean isInstance(Class cls, EObject element) { |
| boolean result = false; |
| |
| if (element instanceof InstanceSpecification) { |
| EList<Classifier> classifiers = ((InstanceSpecification) element) |
| .getClassifiers(); |
| |
| for (Classifier c : classifiers) { |
| if (c.conformsTo(cls)) { |
| result = true; |
| break; |
| } |
| } |
| } |
| |
| return result; |
| } |
| }; |
| case RUNTIME_OBJECTS: |
| return new LazyExtentMap<Class, EObject>((EObject) object) { |
| |
| // implements the inherited specification |
| @Override |
| protected boolean isInstance(Class cls, EObject element) { |
| EClassifier eclass = getEClassifier(cls, element); |
| |
| return (eclass != null) && eclass.isInstance(element); |
| } |
| }; |
| } |
| } |
| |
| return Collections.emptyMap(); |
| } |
| |
| // implements the inherited specification |
| public boolean isKindOf(Object object, Classifier classifier) { |
| switch (getEffectiveEvaluationMode()) { |
| case INSTANCE_MODEL: |
| if (object instanceof ValueSpecification) { |
| ValueSpecification value = (ValueSpecification) object; |
| |
| if (value.getType() != null) { |
| Classifier type = (Classifier) value.getType(); |
| |
| // special case for Integer/UnlimitedNatural and Real |
| // which are not related types in java but are in OCL |
| if ((type == OCLStandardLibraryImpl.INSTANCE.getInteger()) |
| && (classifier == OCLStandardLibraryImpl.INSTANCE.getReal())) { |
| return true; |
| } |
| |
| return type.conformsTo(classifier); |
| } |
| } else if (object instanceof InstanceSpecification) { |
| InstanceSpecification instance = (InstanceSpecification) object; |
| |
| for (Classifier c : instance.getClassifiers()) { |
| if (c.conformsTo(classifier)) { |
| return true; |
| } |
| } |
| } |
| break; |
| case RUNTIME_OBJECTS: |
| if (object instanceof EObject) { |
| // special case for Integer/UnlimitedNatural and Real |
| // which are not related types in java but are in OCL |
| EClassifier eclassifier = getEClassifier(classifier, object); |
| |
| if (eclassifier == null) { |
| return false; |
| } |
| |
| if ((object.getClass() == Integer.class) |
| && (eclassifier.getInstanceClass() == Double.class)) { |
| return Boolean.TRUE; |
| } |
| |
| return eclassifier.isInstance(object); |
| } |
| break; |
| } |
| |
| return false; |
| } |
| |
| // implements the inherited specification |
| public boolean isTypeOf(Object object, Classifier classifier) { |
| switch (getEffectiveEvaluationMode()) { |
| case INSTANCE_MODEL: |
| if (object instanceof ValueSpecification) { |
| ValueSpecification value = (ValueSpecification) object; |
| |
| return value.getType() == classifier; |
| } else if (object instanceof InstanceSpecification) { |
| return ((InstanceSpecification) object).getClassifiers().contains( |
| classifier); |
| } |
| break; |
| case RUNTIME_OBJECTS: |
| if (object instanceof EObject) { |
| EClassifier eclassifier = getEClassifier(classifier, object); |
| |
| if (eclassifier == null) { |
| return false; |
| } |
| |
| if ((eclassifier instanceof EClass) && (object instanceof EObject)) { |
| return ((EObject) object).eClass() == eclassifier; |
| } else if (!(object instanceof EObject) |
| && !(eclassifier instanceof EClass)) { |
| return object.getClass() == eclassifier.getInstanceClass(); |
| } |
| } |
| break; |
| } |
| |
| return false; |
| } |
| |
| public Classifier getType(Object object) { |
| return factory.getClassifier(object); |
| } |
| |
| /** |
| * Looks up the Ecore definition of the specified UML package. |
| * |
| * @param pkg |
| * a UML package |
| * @return its Ecore definition, or <code>null</code> if not found |
| */ |
| protected EPackage getEPackage(Package pkg) { |
| EPackage result = packageMap.get(pkg); |
| |
| if (result == null) { |
| result = OCLUMLUtil.getEPackage(pkg, registry); |
| |
| if (result == null) { |
| result = CACHE_MISS; |
| } |
| |
| packageMap.put(pkg, result); |
| } |
| |
| return (result == CACHE_MISS) ? null |
| : result; |
| } |
| |
| /** |
| * Looks up the Ecore definition of the specified UML classifier, using the |
| * specified <code>element</code> as a context for finding profile |
| * applications in the case that the classifier is a stereotype or some |
| * other type in a {@link Profile}. Finding the Ecore definition of a |
| * profile type requires finding the actual applied version of the profile. |
| * |
| * @param umlClassifier |
| * a UML classifier |
| * @param element |
| * an element in the context of which the OCL evaluation is being |
| * performed |
| * @return the corresponding Ecore classifier, or <code>null</code> if not |
| * found |
| */ |
| protected EClassifier getEClassifier(Classifier umlClassifier, |
| Object element) { |
| EClassifier result = null; |
| Package umlPackage = umlClassifier.getPackage(); |
| EPackage ecorePackage = null; |
| |
| if (umlPackage instanceof Profile) { |
| // use the element to get the most appropriate profile definition |
| Profile profile = (Profile) umlPackage; |
| |
| if (element instanceof Element) { |
| Element umlElement = (Element) element; |
| |
| Package nesting = umlElement.getNearestPackage(); |
| while (nesting != null) { |
| ProfileApplication appl = nesting |
| .getProfileApplication(profile); |
| if (appl != null) { |
| ecorePackage = appl.getAppliedDefinition(); |
| break; |
| } |
| |
| nesting = (nesting.getOwner() == null) |
| ? null |
| : nesting.getOwner().getNearestPackage(); |
| } |
| } |
| |
| if (ecorePackage == null) { |
| // assume the latest definition of the profile (if any) |
| ecorePackage = profile.getDefinition(); |
| } |
| } else if (umlPackage != null) { |
| ecorePackage = getEPackage(umlPackage); |
| } |
| |
| if (ecorePackage != null) { |
| result = ecorePackage.getEClassifier(UML2Util |
| .getValidJavaIdentifier(umlClassifier.getName())); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Implements the interface by finding the corresponding <tt>Enumerator</tt> |
| * value in a generated (or dynamic) EMF implementation, else the same |
| * enumeration literal (supporting the instance-specification model case). |
| * |
| * @since 1.2 |
| */ |
| public Object getValue(EnumerationLiteral enumerationLiteral) { |
| if (getEffectiveEvaluationMode() == EvaluationMode.RUNTIME_OBJECTS) { |
| Object context = getValueOf(Environment.SELF_VARIABLE_NAME); |
| |
| // if we're in an instance-specification world (model of instances) |
| // then we use the models of enumeration literals (M1 level), not |
| // the run-time instances (M0 level) |
| Enumeration umlEnum = enumerationLiteral.getEnumeration(); |
| EClassifier eType = OCLUMLUtil.getEClassifier(umlEnum, context, |
| getEPackageRegistry()); |
| |
| if (eType instanceof EEnum) { |
| EEnumLiteral eLiteral = ((EEnum) eType).getELiterals().get( |
| umlEnum.getOwnedLiterals().indexOf(enumerationLiteral)); |
| |
| if (eLiteral != null) { |
| return eLiteral.getInstance(); |
| } |
| } |
| } |
| |
| return enumerationLiteral; |
| } |
| |
| /** |
| * A converter of UML value specifications to OCL (Java) values. |
| * |
| * @author Christian W. Damus (cdamus) |
| */ |
| class ValueExtractor |
| extends UMLSwitch<Object> { |
| |
| @Override |
| public Object caseLiteralBoolean(LiteralBoolean object) { |
| return Boolean.valueOf(object.booleanValue()); |
| } |
| |
| @Override |
| public Object caseLiteralInteger(LiteralInteger object) { |
| return Integer.valueOf(object.integerValue()); |
| } |
| |
| @Override |
| public Object caseLiteralNull(LiteralNull object) { |
| return null; |
| } |
| |
| @Override |
| public Object caseLiteralString(LiteralString object) { |
| return object.stringValue(); |
| } |
| |
| @Override |
| public Object caseLiteralUnlimitedNatural(LiteralUnlimitedNatural object) { |
| return Integer.valueOf(object.unlimitedValue()); |
| } |
| |
| @Override |
| public Object caseInstanceValue(InstanceValue object) { |
| return object.getInstance(); |
| } |
| |
| @Override |
| public Object caseValueSpecification(ValueSpecification object) { |
| // the default case is a value specification that we don't |
| // understand |
| return OCLStandardLibraryImpl.INVALID; |
| } |
| |
| /** |
| * Converts the specified collection of UML values to a collection of |
| * OCL values. |
| * |
| * @param values |
| * UML values |
| * @return the corresponding OCL values |
| */ |
| Collection<?> extractValues( |
| Collection<? extends ValueSpecification> values, |
| CollectionKind collectionKind) { |
| Collection<Object> result = CollectionUtil |
| .createNewCollection(collectionKind); |
| |
| for (ValueSpecification value : values) { |
| result.add(extractValue(value)); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Converts the specified UML values to an OCL value. |
| * |
| * @param values |
| * a UML value |
| * @return the corresponding OCL value |
| */ |
| Object extractValue(ValueSpecification value) { |
| return doSwitch(value); |
| } |
| } |
| } |