/******************************************************************************* * Copyright (c) 2008, 2015 Borland Software 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: * Borland Software Corporation - initial API and implementation * Christopher Gerking - bugs 392153, 425069 * Alex Paperno - bugs 400720, 415029 * Christine Gerpheide - bug 432969 *******************************************************************************/ package org.eclipse.m2m.internal.qvt.oml.ast.env; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.runtime.IStatus; import org.eclipse.emf.common.util.DelegatingEList; import org.eclipse.emf.common.util.ECollections; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EOperation; import org.eclipse.emf.ecore.EReference; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.ecore.ETypedElement; import org.eclipse.emf.ecore.impl.DynamicEObjectImpl; import org.eclipse.emf.ecore.util.FeatureMapUtil; import org.eclipse.m2m.internal.qvt.oml.NLS; import org.eclipse.m2m.internal.qvt.oml.QvtPlugin; import org.eclipse.m2m.internal.qvt.oml.ast.parser.IntermediateClassFactory; import org.eclipse.m2m.internal.qvt.oml.ast.parser.QvtOperationalParserUtil; import org.eclipse.m2m.internal.qvt.oml.ast.parser.QvtOperationalTypesUtil; import org.eclipse.m2m.internal.qvt.oml.ast.parser.QvtOperationalUtil; import org.eclipse.m2m.internal.qvt.oml.evaluator.EvaluationMessages; import org.eclipse.m2m.internal.qvt.oml.evaluator.EvaluationUtil; import org.eclipse.m2m.internal.qvt.oml.evaluator.IntermediatePropertyModelAdapter; import org.eclipse.m2m.internal.qvt.oml.evaluator.ModelInstance; import org.eclipse.m2m.internal.qvt.oml.evaluator.ModuleInstance; import org.eclipse.m2m.internal.qvt.oml.evaluator.NumberConversions; import org.eclipse.m2m.internal.qvt.oml.evaluator.QVTEvaluationOptions; import org.eclipse.m2m.internal.qvt.oml.evaluator.QVTStackTraceElement; import org.eclipse.m2m.internal.qvt.oml.evaluator.QvtGenericVisitorDecorator; import org.eclipse.m2m.internal.qvt.oml.evaluator.QvtRuntimeException; import org.eclipse.m2m.internal.qvt.oml.evaluator.QvtStackTraceBuilder; import org.eclipse.m2m.internal.qvt.oml.evaluator.ThisInstanceResolver; import org.eclipse.m2m.internal.qvt.oml.evaluator.TransformationInstance; import org.eclipse.m2m.internal.qvt.oml.evaluator.TransformationInstance.InternalTransformation; import org.eclipse.m2m.internal.qvt.oml.expressions.ContextualProperty; import org.eclipse.m2m.internal.qvt.oml.expressions.DirectionKind; import org.eclipse.m2m.internal.qvt.oml.expressions.ImperativeOperation; import org.eclipse.m2m.internal.qvt.oml.expressions.ModelParameter; import org.eclipse.m2m.internal.qvt.oml.expressions.Module; import org.eclipse.m2m.internal.qvt.oml.expressions.OperationalTransformation; import org.eclipse.m2m.internal.qvt.oml.library.EObjectEStructuralFeaturePair; import org.eclipse.m2m.internal.qvt.oml.stdlib.CallHandler; import org.eclipse.m2m.internal.qvt.oml.stdlib.QVTUMLReflection; import org.eclipse.m2m.internal.qvt.oml.trace.Trace; import org.eclipse.m2m.internal.qvt.oml.trace.TraceFactory; import org.eclipse.m2m.qvt.oml.ecore.ImperativeOCL.DictionaryType; import org.eclipse.m2m.qvt.oml.ecore.ImperativeOCL.ListType; import org.eclipse.m2m.qvt.oml.util.Dictionary; import org.eclipse.m2m.qvt.oml.util.IContext; import org.eclipse.m2m.qvt.oml.util.MutableList; import org.eclipse.m2m.qvt.oml.util.Utils; import org.eclipse.ocl.ecore.EcoreEnvironmentFactory; import org.eclipse.ocl.ecore.EcoreEvaluationEnvironment; import org.eclipse.ocl.ecore.EcorePackage; import org.eclipse.ocl.expressions.CollectionKind; import org.eclipse.ocl.types.AnyType; import org.eclipse.ocl.types.CollectionType; import org.eclipse.ocl.util.CollectionUtil; import org.eclipse.ocl.util.Tuple; public class QvtOperationalEvaluationEnv extends EcoreEvaluationEnvironment { protected QvtOperationalEvaluationEnv(IContext context, QvtOperationalEvaluationEnv parent) { super(parent == null ? new EcoreEvaluationEnvironment((EcoreEnvironmentFactory)null) : parent); if(parent == null) { myRootEnv = this; myInternal = new RootInternal(context); myStackDepth = 1; } else { myRootEnv = parent.myRootEnv; myInternal = new Internal(); myStackDepth = parent.myStackDepth + 1; } myBindings = new HashMap<String, Object>(); myOperationArgs = new ArrayList<Object>(); myExtents = new ArrayList<ModelParameterExtent>(); } public QvtOperationalEvaluationEnv getRoot() { return myRootEnv; } public int getDepth() { return myStackDepth; } public ModuleInstance getThisOfType(Module module) { ThisInstanceResolver thisResolver = internalEnv().getThisResolver(); assert thisResolver != null; return thisResolver.getThisInstanceOf(module); } @Override public <T> T getAdapter(Class<T> adapterType) { if(InternalEvaluationEnv.class == adapterType) { return adapterType.cast(internalEnv()); } return super.getAdapter(adapterType); } private Internal internalEnv() { return myInternal; } @Override public Map<EClass, Set<EObject>> createExtentMap(Object object) { return new QVTExtentMap(this); } public List<Object> getOperationArgs() { return myOperationArgs; } public void setOperationSelf(Object source) { myOperationSelf = source; } public Object getOperationSelf() { return myOperationSelf; } public IContext getContext() { return internalEnv().getContext(); } public void addModelExtent(ModelParameterExtent extent) { internalEnv().addModelExtent(extent); } @SuppressWarnings("restriction") public QVTUMLReflection getUMLReflection() { QvtOperationalEvaluationEnv parent = getParent(); if(parent != null) { return parent.getUMLReflection(); } if (fQVUMLReflection == null) { fQVUMLReflection = new QVTUMLReflection(org.eclipse.ocl.ecore.internal.UMLReflectionImpl.INSTANCE); } return fQVUMLReflection; } public void cleanup() { internalEnv().cleanup(); clear(); if (getParent() == null) { if (fQVUMLReflection != null) { fQVUMLReflection.close(); } QvtOperationalStdLibrary.INSTANCE.getEnvironment().close(); } } @Override public boolean overrides(EOperation operation, int opcode) { if (CallHandler.Access.hasHandler(operation)) { return true; } // Remark: Prone to cause SOE if running in a deep stack as // the super implementation calls recursively upto the root // evaluation environment return false;//super.overrides(operation, opcode); } @Override public Object callOperation(EOperation operation, int opcode, Object source, Object[] args) throws IllegalArgumentException { CallHandler callHandler = CallHandler.Access.getHandler(operation); if(callHandler != null) { if(source == null || source == getInvalidResult()) { return getInvalidResult(); } Module targetModule = QvtOperationalParserUtil.getOwningModule(operation); ModuleInstance targetModuleInstance = getThisOfType(targetModule); assert targetModuleInstance != null; return callHandler.invoke(targetModuleInstance, source, args, this); } return super.callOperation(operation, opcode, source, args); } @SuppressWarnings("unchecked") @Override public Object navigateProperty(EStructuralFeature property, List<?> qualifiers, Object target) throws IllegalArgumentException { if(target instanceof ModuleInstance) { ModuleInstance moduleTarget = (ModuleInstance) target; EClassifier owningClassifier = getUMLReflection().getOwningClassifier(property); if (owningClassifier instanceof Module) { target = moduleTarget.getThisInstanceOf((Module) owningClassifier); } else { target = moduleTarget.getThisInstanceOf(moduleTarget.getModule()); } } EStructuralFeature resolvedProperty = property; if (property instanceof ContextualProperty) { IntermediatePropertyModelAdapter.ShadowEntry shadow = IntermediatePropertyModelAdapter.getPropertyHolder( property.getEContainingClass(), (ContextualProperty)property, target); target = shadow.getPropertyRuntimeOwner(target, this); resolvedProperty = shadow.getProperty(); } // FIXME - workaround for an issue of multiple tuple type instances, possibly coming from // imported modules. The super impl. looks for the property by feature instance, do it // by name here to avoid lookup failure, IllegalArgExc... if(target instanceof Tuple<?, ?>) { if (target instanceof EObject) { EObject etarget = (EObject) target; resolvedProperty = etarget.eClass().getEStructuralFeature(property.getName()); if(resolvedProperty == null) { return null; } } else { resolvedProperty = null; for (EStructuralFeature feature : ((Tuple<EOperation, EStructuralFeature>) target).getTupleType().oclProperties()) { if (property.getName().equals(feature.getName())) { resolvedProperty = feature; break; } } if(resolvedProperty == null) { return null; } } } try { return super.navigateProperty(resolvedProperty, qualifiers, target); } catch (IllegalArgumentException e) { internalEnv().throwQVTException( new QvtRuntimeException("Unknown property '" + property.getName() + "'", e)); //$NON-NLS-1$ //$NON-NLS-2$ return getInvalidResult(); } } @Override public QvtOperationalEvaluationEnv getParent() { return super.getParent() instanceof QvtOperationalEvaluationEnv ? (QvtOperationalEvaluationEnv) super.getParent() : null; } /** * Returns the value associated with the supplied name * * @param name * the name whose value is to be returned * @return the value associated with the name */ @Override public Object getValueOf(String name) { Object result = myBindings.get(name); if(result instanceof TypedBinding) { return ((TypedBinding)result).value; } return result; } /** * Gets the type bound to the variable of the given name. * * @param the * name of the variable * @return the type the referenced variable previously bound by * {@link #add(String, Object, EClassifier)} or <code>null</code> * if none is available. * @see #add(String, Object, EClassifier) */ public EClassifier getTypeOf(String name) { Object result = myBindings.get(name); if(result instanceof TypedBinding) { return ((TypedBinding)result).type; } return null; } /** * Test whether the give object is OclInvalid retrieved from this * environment. * * @param value * the object to test * @return <code>true</code> if the passed object is * <code>OclInvalid</code> from this environment; * <code>false</code> otherwise */ // FIXME - rename to is isInvalid public boolean isOclInvalid(Object value) { return getInvalidResult() == value; } public void copyVariableValueFrom(QvtOperationalEvaluationEnv fromEnv, String varName, String targetVarName) { Object sourceValue = fromEnv.getValueOf(varName); this.replace(targetVarName, sourceValue); } /** * Replaces the current value of the supplied name with the supplied value. * * @param name * the name * @param value * the new value */ @Override public void replace(String name, Object value) { myBindings.put(name, value); } /** * Replaces the current value of the supplied name with the supplied value * and type. * * @param name * the name * @param declaredType * the type of the value known at declaration time * @param value * the new value */ public void replace(String name, Object value, EClassifier declaredType) { if(declaredType != null) { replace(name, new TypedBinding(value, declaredType)); } else { replace(name, value); } } /** * Adds the supplied name and value binding to the environment * * @param name * the name to add * @param value * the associated binding */ @Override public void add(String name, Object value) { if(QvtOperationalEnv.THIS.equals(name)) { Object thisValue = value; if(thisValue != null && thisValue.getClass() == TypedBinding.class) { thisValue = ((TypedBinding)thisValue).value; } if(thisValue instanceof ModuleInstance) { internalEnv().setThisResolver((ModuleInstance) thisValue); } } if (myBindings.containsKey(name)) { String message = NLS.bind("The name: ({0}) already has a binding: ({1})", name, myBindings.get(name)); //$NON-NLS-1$ throw new IllegalArgumentException(message); } myBindings.put(name, value); } /** * Adds the supplied name and typed value binding to the environment * * @param name * the name to add * @param declaredType * the type of the value known at declaration time * @param value * the associated binding */ public void add(String name, Object value, EClassifier declaredType) { if(declaredType != null) { add(name, new TypedBinding(value, declaredType)); } else { add(name, value); } } /** * Removes the supplied name and binding from the environment (if it exists) * and returns it. * * @param name * the name to remove * @return the value associated with the removed name */ @Override public Object remove(String name) { Object result = myBindings.remove(name); if(result instanceof TypedBinding) { return ((TypedBinding)result).value; } return result; } /** * Clears the environment of variables. */ @Override public void clear() { myBindings.clear(); } /** * Returns a string representation of the bindings */ @Override public String toString() { return myBindings.toString(); } public Set<String> getNames() { return myBindings.keySet(); } @Override public boolean isKindOf(Object object, EClassifier classifier) { if (classifier instanceof AnyType<?>) { // [Spec 11.2.1] All types in the UML model and the primitive types in the OCL standard library // comply with the type OclAny. // OclAny behaves as a supertype for all the types except for the OCL pre-defined collection types. // OclAny is itself an instance of the metatype AnyType. return false == object instanceof Collection<?>; } else if(classifier == QvtOperationalStdLibrary.INSTANCE.getElementType()) { if(object instanceof EObject) { return QVTUMLReflection.isUserModelElement(((EObject)object).eClass()); } } else if(object instanceof DynamicEObjectImpl) { for (EClass objType : ((EObject) object).eClass().getEAllSuperTypes()) { if (objType.getEPackage() == classifier.getEPackage() && objType.getClassifierID() == classifier.getClassifierID()) return true; } } if (classifier instanceof CollectionType<?, ?> && object instanceof java.util.Collection<?>) { if (!classifier.isInstance(object)) { if (classifier.eClass().getEPackage() != EcorePackage.eINSTANCE || classifier.eClass().getClassifierID() != EcorePackage.eINSTANCE.getCollectionType().getClassifierID()) { return false; } } if (((java.util.Collection<?>) object).isEmpty()) { return true; } return isKindOf(((java.util.Collection<?>) object).iterator().next(), ((org.eclipse.ocl.ecore.CollectionType) classifier).getElementType()); } return super.isKindOf(object, classifier); } @Override public EClassifier getType(Object object) { if (object == null) { return QvtOperationalStdLibrary.INSTANCE.getOCLStdLib().getOclVoid(); } return super.getType(object); } @Override protected Object coerceValue(ETypedElement element, Object value, boolean copy) { EClassifier oclType = getUMLReflection().getOCLType(element); if (value instanceof MutableList<?> || value instanceof Dictionary<?,?>) { // avoid coercion of mutable collections return value; } else if (oclType instanceof AnyType<?>) { // avoid coercion of collection to single element in case of OclAny type (bug 386115) if (value instanceof Collection<?>) { return copy ? CollectionUtil.createNewCollection((Collection<?>) value) : value; } } else if (oclType instanceof ListType) { if (value instanceof Collection<?>) { // enable coercion of arbitrary collection values in case of Collection type return copy ? Utils.createList((Collection<?>) value) : value; } else { // coerce 'null' to empty Set in case of Collection type return copy ? Utils.createList(value != null ? Collections.singletonList(value) : Collections.emptyList()) : value; } } else if (oclType instanceof DictionaryType) { if (value instanceof Collection<?>) { // enable coercion of arbitrary collection values in case of Collection type return copy ? Utils.createDictionary((Collection<?>) value) : value; } else { // coerce 'null' to empty Set in case of Collection type return copy ? Utils.createDictionary(value != null ? Collections.singletonList(value) : Collections.emptyList()) : value; } } else if (oclType instanceof CollectionType<?, ?>) { @SuppressWarnings("unchecked") CollectionType<EClassifier, EOperation> collectionType = (CollectionType<EClassifier, EOperation>) oclType; if (collectionType.getKind() == CollectionKind.COLLECTION_LITERAL) { if (value instanceof Collection<?>) { // enable coercion of arbitrary collection values in case of Collection type return copy ? CollectionUtil.createNewCollection((Collection<?>) value) : value; } else { // coerce 'null' to empty Set in case of Collection type return copy ? CollectionUtil.createNewSet(value != null ? Collections.singletonList(value) : Collections.emptyList()) : value; } } else if (value == null) { // coerce 'null' to empty collection return copy ? EvaluationUtil.createNewCollection(collectionType) : value; } } return super.coerceValue(element, value, copy); } public EObject createInstance(EClassifier type, ModelParameter extent) { if (!QvtOperationalUtil.isInstantiable(type)) { internalEnv().throwQVTException( new QvtRuntimeException("Cannot instantiate type " + QvtOperationalParserUtil.safeGetQualifiedName(getUMLReflection(), type, ""))); //$NON-NLS-1$ //$NON-NLS-2$ } EObject newObject = type.getEPackage().getEFactoryInstance().create((EClass) type); if (newObject == null) { return null; } putInstanceToExtent(newObject, extent); return newObject; } public void putInstanceToExtent(EObject eObj, ModelParameter extent) { TransformationInstance mainTransfInstance = internalEnv().getCurrentTransformation(); if(mainTransfInstance == null) { assert extent == null; // not running in a transformation, ignore extent return; } ModelParameterExtent targetExtent; if(extent == null) { targetExtent = getDefaultInstantiationExtent(eObj.eClass()); } else { OperationalTransformation targetTransf = (OperationalTransformation)extent.eContainer(); assert targetTransf != null; TransformationInstance targetThis = mainTransfInstance; if(mainTransfInstance.getTransformation() != targetTransf) { targetThis = (TransformationInstance)mainTransfInstance.getThisInstanceOf(targetTransf); } ModelInstance model = targetThis.getModel(extent); assert model != null; targetExtent = model.getExtent(); } if (isReadonlyGuardEnabled() && targetExtent.isReadonly()) { internalEnv().throwQVTException(new QvtRuntimeException( NLS.bind(EvaluationMessages.ExtendedOclEvaluatorVisitorImpl_ReadOnlyInputModel, extent.getName() + " : " + QvtOperationalTypesUtil.getTypeFullName(extent.getEType())))); //$NON-NLS-1$ } targetExtent.addObject(eObj); } public ModelParameterExtent getDefaultInstantiationExtent(EClassifier type) { TransformationInstance mainTransfInstance = internalEnv().getCurrentTransformation(); if(mainTransfInstance != null) { if(IntermediateClassFactory.isIntermediateClass(type)) { InternalTransformation internTransf = mainTransfInstance.getAdapter(InternalTransformation.class); ModelInstance intermExtent = internTransf.getIntermediateExtent(); if(intermExtent != null) { return intermExtent.getExtent(); } } else if(QVTUMLReflection.isUserModelElement(type)) { EList<ModelParameter> modelParameters = mainTransfInstance.getTransformation().getModelParameter(); ModelParameter modelParam = QvtOperationalModuleEnv.findModelParameter(type, DirectionKind.OUT, modelParameters); ModelInstance model = mainTransfInstance.getModel(modelParam); if (model != null) { return model.getExtent(); } } } return internalEnv().getUnboundExtent(); } /** * Appends new content to the elements of the specified <code>collection</code>. * Mutates the given <code>collection</code> if is mutable, otherwise creates a new collection of the same type and contents. * * @param collection the collection to be appended to, never <code>null</code> * @param newContent the new content to be appended to the <code>collection</code>, may be an element that conforms to the given <code>classifier</code>, a collection of conforming elements, or <code>null</code> (never <code>invalid</code>) * @param classifier the classifier that the collection elements must conform to * @return the resulting collection including the appended content */ @SuppressWarnings("unchecked") private Collection<Object> append(Collection<Object> collection, Object newContent, EClassifier classifier) { if (collection == null || isOclInvalid(newContent)) { throw new IllegalArgumentException(); }; Collection<Object> newCollection; if (collection instanceof MutableList<?> || collection instanceof Dictionary<?,?>) { newCollection = collection; } else { newCollection = CollectionUtil.createNewCollection(collection); } Object convertedContent = ensureTypeCompatibility(newContent, classifier.getInstanceClass()); if (convertedContent == null || isKindOf(convertedContent, classifier)) { newCollection.add(convertedContent); } else if (newContent instanceof Collection<?>) { for (Object element : (Collection<Object>) newContent) { newCollection.add(ensureTypeCompatibility(element, classifier.getInstanceClass())); } } return newCollection; } /** * Assigns a RHS <code>exprValue</code> to a LHS variable or property. * * @param classifier the LHS classifier of the assignment * @param oldValue the old value of the LHS variable or property * @param exprValue the RHS value to be assigned * @param isReset in case of a collection assignment, indicates whether the current LHS elements should be discarded or not * @return the new LHS value resulting from the assignment */ @SuppressWarnings("unchecked") public Object assign(EClassifier classifier, Object oldValue, Object exprValue, boolean isReset) { Object newValue; if (classifier instanceof CollectionType<?, ?> && !isOclInvalid(exprValue)) { CollectionType<EClassifier, EOperation> collectionType = (CollectionType<EClassifier, EOperation>) classifier; Collection<Object> newCollection = null; if (isReset) { if (exprValue instanceof MutableList || exprValue instanceof Dictionary) { newCollection = (Collection<Object>) exprValue; } else { newCollection = EvaluationUtil.createNewCollection(collectionType); if (newCollection == null) { if (exprValue instanceof Collection) { newCollection = EvaluationUtil.createNewCollectionOfSameKind((Collection<Object>) exprValue); } else { newCollection = CollectionUtil.createNewSet(); } } if (exprValue instanceof Collection) { for (Object element : (Collection<Object>) exprValue) { newCollection.add(ensureTypeCompatibility(element, collectionType.getElementType().getInstanceClass())); } } else { if (exprValue != null) { newCollection.add(ensureTypeCompatibility(exprValue, collectionType.getElementType().getInstanceClass())); } } } } else { Collection<Object> oldCollection; if (oldValue instanceof Collection<?>) { oldCollection = (Collection<Object>) oldValue; } else { oldCollection = EvaluationUtil.createNewCollection(collectionType); if (oldCollection == null) { oldCollection = CollectionUtil.createNewSet(); } } newCollection = append(oldCollection, exprValue, collectionType.getElementType()); } newValue = newCollection; } else { newValue = exprValue; } return EvaluationUtil.doImplicitListCoercion(classifier, newValue); } @SuppressWarnings("unchecked") public void callSetter(EObject target, EStructuralFeature eStructuralFeature, Object exprValue, boolean valueIsUndefined, boolean isReset) { if (getInvalidResult() == target) { // call performed on OclInvalid, can not continue return; } EObject owner = target; if (target instanceof ModuleInstance) { ModuleInstance moduleTarget = (ModuleInstance) target; owner = moduleTarget.getThisInstanceOf(moduleTarget.getModule()); } if (eStructuralFeature instanceof ContextualProperty) { IntermediatePropertyModelAdapter.ShadowEntry shadow = IntermediatePropertyModelAdapter.getPropertyHolder( eStructuralFeature.getEContainingClass(), (ContextualProperty) eStructuralFeature, owner); owner = shadow.getPropertyRuntimeOwner(owner, this); eStructuralFeature = shadow.getProperty(); } if (isReadonlyGuardEnabled()) { checkReadonlyGuard(eStructuralFeature, exprValue, owner); } final Object currentValue = owner.eGet(eStructuralFeature); EClassifier oclType = getUMLReflection().getOCLType(eStructuralFeature); Object newValue = assign(oclType, currentValue, exprValue, isReset); final Class<?> expectedClass = eStructuralFeature.getEType().getInstanceClass(); if (FeatureMapUtil.isMany(owner, eStructuralFeature)) { EList<Object> containerList = (EList<Object>) currentValue; if (valueIsUndefined) { if (isReset) { containerList.clear(); } } else { Collection<?> coll = (Collection<?>) newValue; List<Object> newList; if (!EvaluationUtil.canContainNull(containerList)) { newList = new ArrayList<Object>(coll.size()); for (Object o : coll) { if (o != null) { newList.add(o); } } } else { newList = newValue instanceof List ? (List<Object>) newValue : new ArrayList<Object>(coll); } @SuppressWarnings("serial") EList<Object> delegatingList = new DelegatingEList<Object>() { protected List<Object> delegateList() { return (List<Object>) currentValue; } @Override public void add(int index, Object object) { super.add(index, ensureTypeCompatibility(object, expectedClass)); } @Override public boolean add(Object object) { return super.add(ensureTypeCompatibility(object, expectedClass)); } }; // guard against incorrect behavior of ELists implementation. // Inspired by https://bugs.eclipse.org/bugs/show_bug.cgi?id=465283 try { ECollections.setEList(delegatingList, newList); } catch (RuntimeException e) { QvtPlugin.warning(IStatus.OK, NLS.bind(EvaluationMessages.ContentMergeForMultivaluedFeatureFailed, eStructuralFeature.getName()), e); containerList.clear(); containerList.addAll(newList); } } } else { if (isOclInvalid(newValue) || (newValue == null && !acceptsNullValue(expectedClass))) { if (isReset) { owner.eUnset(eStructuralFeature); } } else { if (newValue instanceof Collection && (eStructuralFeature.getUpperBound() == ETypedElement.UNSPECIFIED_MULTIPLICITY)) { for (Object element : (Collection<Object>) newValue) { if (element != null) { newValue = element; break; } } } owner.eSet(eStructuralFeature, ensureTypeCompatibility(newValue, expectedClass)); } } } private boolean isReadonlyGuardEnabled() { return getContext().getSessionData().getValue(QVTEvaluationOptions.FLAG_READONLY_GUARD_ENABLED) == Boolean.TRUE; } public int getMaxStackDepth() { return getContext().getSessionData().getValue(QVTEvaluationOptions.EVALUATION_MAX_STACK_DEPTH); } public List<Class<? extends QvtGenericVisitorDecorator>> getVisitorDecoratorClasses() { return getContext().getSessionData().getValue(QVTEvaluationOptions.VISITOR_DECORATORS); } private void checkReadonlyGuard(EStructuralFeature eStructuralFeature, Object exprValue, EObject owner) { ModelParameter violatedReadonlyParam = ModelParameterExtent.getReadonlyModelParameter(owner); if(violatedReadonlyParam == null && eStructuralFeature instanceof EReference) { EReference eReference = (EReference) eStructuralFeature; if (eReference.isContainment()) { if (exprValue instanceof EObject) { violatedReadonlyParam = ModelParameterExtent.getReadonlyModelParameter((EObject) exprValue); } else if (exprValue instanceof Collection<?>) { for (Object element : (Collection<?>) exprValue) { if (element instanceof EObject) { violatedReadonlyParam = ModelParameterExtent.getReadonlyModelParameter((EObject) exprValue); if(violatedReadonlyParam != null) { break; } } } } } } if(violatedReadonlyParam != null) { internalEnv().throwQVTException(new QvtRuntimeException( NLS.bind(EvaluationMessages.ExtendedOclEvaluatorVisitorImpl_ReadOnlyInputModel, violatedReadonlyParam.getName() + " : " + QvtOperationalTypesUtil.getTypeFullName(violatedReadonlyParam.getEType())))); //$NON-NLS-1$ } } // private boolean isMany(EObject ownerObj, EStructuralFeature eStructuralFeature) { // if (eStructuralFeature.isMany()) { // return true; // } // if (eStructuralFeature.getLowerBound() == 0 && eStructuralFeature.getUpperBound() == ETypedElement.UNSPECIFIED_MULTIPLICITY) { // return ownerObj.eGet(eStructuralFeature) instanceof List; // } // return false; // } /** * Ensures that the value has a type compatible with the expected type, * converting it if necessary. * * @param value * the value (may be <code>null</code>) * @param expectedType * the expected type (may be <code>null</code>) * @return the converted value */ private Object ensureTypeCompatibility(Object value, Class<?> expectedType) { if (expectedType != null) { // perform the type conversion only the expected type is available return NumberConversions.convertNumber(value, expectedType); } return value; } /** * Returns <code>true</code> if <code>null</code> is a valid value of * the type. * * @param type * the type (may be <code>null</code>) * @return <code>true</code> if <code>null</code> is a valid value of * the type */ private boolean acceptsNullValue(Class<?> type) { if (type == null) { return true; } return !type.isPrimitive(); } public QvtOperationalEvaluationEnv cloneEvaluationEnv() { QvtOperationalEvaluationEnv env = new QvtOperationalEvaluationEnv(getContext(), getParent()); return copyEnv(env); } // just running under the root transformation execution environment, // so cut me off the whole execution stack hierarchy public QvtOperationalEvaluationEnv createDeferredExecutionEnvironment() { QvtOperationalEvaluationEnv parent = (getRoot() == this) ? parent = null : getRoot(); QvtOperationalEvaluationEnv result = new QvtOperationalEvaluationEnv(getContext(), parent); return copyEnv(result); } protected QvtOperationalEvaluationEnv copyEnv(QvtOperationalEvaluationEnv env) { env.myInternal = internalEnv().clone(); env.myOperationArgs.addAll(myOperationArgs); env.myOperationSelf = myOperationSelf; env.myOperation = myOperation; env.myBindings.putAll(myBindings); return env; } /** * Sets the operation being currently executed. */ public void setOperation(ImperativeOperation myOperation) { this.myOperation = myOperation; } /** * Gets the operation being currently executed. * * @return the operation of <code>null</code> if no operation context is * available */ public ImperativeOperation getOperation() { return myOperation; } /** * The root evaluation environment, refers to <code>this</code> if this is the root environment */ private QvtOperationalEvaluationEnv myRootEnv; private Internal myInternal; private ImperativeOperation myOperation; private final List<Object> myOperationArgs; private Object myOperationSelf; private final Map<String, Object> myBindings; private final int myStackDepth; private final List<ModelParameterExtent> myExtents; private QVTUMLReflection fQVUMLReflection; private static class TypedBinding { final Object value; final EClassifier type; private TypedBinding(Object value, EClassifier type) { this.value = value; this.type = type; } @Override public String toString() { StringBuilder buf = new StringBuilder(); if(type != null) { buf.append(type).append(" : "); //$NON-NLS-1$ } buf.append(value); return buf.toString(); } } private class RootInternal extends Internal { private IContext myContext; private List<Runnable> myDeferredTasks; private EObjectEStructuralFeaturePair myLastAssignLvalue; private ModelParameterExtent myUnboundExtent; private TransformationInstance myThisTransformation; private boolean myIsDeferredExecution; private Trace myTraces; RootInternal(IContext context) { assert context != null; myContext = context; myIsDeferredExecution = false; myTraces = TraceFactory.eINSTANCE.createTrace(); } RootInternal(RootInternal another) { super(another); myLastAssignLvalue = another.myLastAssignLvalue; myDeferredTasks = another.myDeferredTasks; myContext = another.myContext; myUnboundExtent = another.myUnboundExtent; myThisTransformation = another.myThisTransformation; myIsDeferredExecution = another.myIsDeferredExecution; } @Override IContext getContext() { return myContext; } @Override public TransformationInstance getCurrentTransformation() { return myThisTransformation; } @Override public void setThisResolver(ThisInstanceResolver thisResolver) { if(thisResolver instanceof TransformationInstance) { myThisTransformation = (TransformationInstance) thisResolver; } super.setThisResolver(thisResolver); } @Override public Internal clone() { return new RootInternal(this); } @Override public ModelParameterExtent getUnboundExtent() { if(myUnboundExtent == null) { myUnboundExtent = new ModelParameterExtent(); } return myUnboundExtent; } @Override public void addDeferredTask(Runnable task) { if (myDeferredTasks == null) { myDeferredTasks = new ArrayList<Runnable>(); } myDeferredTasks.add(task); } @Override public EObjectEStructuralFeaturePair getLastAssignmentLvalueEval() { return myLastAssignLvalue; } @Override public void setLastAssignmentLvalueEval(EObjectEStructuralFeaturePair lvalue) { myLastAssignLvalue = lvalue; } @Override public void processDeferredTasks() { if (myDeferredTasks != null) { try { myIsDeferredExecution = true; // make me re-entrant in case of errorenous call to #addDeferredTask() // from running the task => concurrent modification exception // This error condition should be handled elsewhere List<Runnable> tasksCopy = new ArrayList<Runnable>(myDeferredTasks); for (Runnable task : tasksCopy) { task.run(); } } finally { myIsDeferredExecution = false; } } } @Override public boolean isDeferredExecution() { return myIsDeferredExecution; } @Override public Trace getTraces() { return myTraces; } @Override public void setTraces(Trace trace) { myTraces = trace; } } private class Internal implements InternalEvaluationEnv { private ThisInstanceResolver myThisResolver; private EObject myCurrentIP; Internal() { } Internal(Internal another) { myThisResolver = another.myThisResolver; myCurrentIP = another.myCurrentIP; } IContext getContext() { return getRoot().internalEnv().getContext(); } @Override public Internal clone() { return new Internal(this); } public TransformationInstance getCurrentTransformation() { return getRoot().internalEnv().getCurrentTransformation(); } public ModuleInstance getCurrentModule() { return (myThisResolver instanceof ModuleInstance) ? (ModuleInstance)myThisResolver : null; } public ModelParameterExtent getUnboundExtent() { return getRoot().internalEnv().getUnboundExtent(); } public void addModelExtent(ModelParameterExtent extent) { getRoot().myExtents.add(extent); } public void cleanup() { for (ModelParameterExtent extent : getRoot().myExtents) { extent.cleanup(); } } public void setThisResolver(ThisInstanceResolver thisResolver) { this.myThisResolver = thisResolver; } public ThisInstanceResolver getThisResolver() { return myThisResolver; } public Object getInvalid() { return getInvalidResult(); } public EObjectEStructuralFeaturePair getLastAssignmentLvalueEval() { return getRoot().internalEnv().getLastAssignmentLvalueEval(); } public void setLastAssignmentLvalueEval(EObjectEStructuralFeaturePair lvalue) { getRoot().internalEnv().setLastAssignmentLvalueEval(lvalue); } public void processDeferredTasks() { getRoot().internalEnv().processDeferredTasks(); } public boolean isDeferredExecution() { return getRoot().internalEnv().isDeferredExecution(); } public void addDeferredTask(Runnable task) { getRoot().internalEnv().addDeferredTask(task); } public void setTraces(Trace trace) { getRoot().internalEnv().setTraces(trace); } public Trace getTraces() { return getRoot().internalEnv().getTraces(); } public EObject setCurrentIP(EObject currentIPObject) { EObject prevValue = myCurrentIP; myCurrentIP = currentIPObject; return prevValue; } public EObject getCurrentIP() { return myCurrentIP; } public void throwQVTException(QvtRuntimeException exception) throws QvtRuntimeException { try { exception.setStackQvtTrace(getStackTraceElements()); } catch (Exception e) { QvtPlugin.error("Failed to build QVT stack trace", e); //$NON-NLS-1$ } throw exception; } public List<QVTStackTraceElement> getStackTraceElements() { return new QvtStackTraceBuilder(QvtOperationalEvaluationEnv.this).buildStackTrace(); } } }