/*******************************************************************************
* Copyright (c) 2008-2010 SAP AG 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:
* SAP AG - initial API and implementation
******************************************************************************/
package com.sap.furcas.modeladaptation.emf.adaptation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EEnum;
import org.eclipse.emf.ecore.EEnumLiteral;
import org.eclipse.emf.ecore.EModelElement;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.ocl.ecore.opposites.DefaultOppositeEndFinder;
import org.eclipse.ocl.ecore.opposites.OppositeEndFinder;
import sun.reflect.generics.reflectiveObjects.NotImplementedException;
import com.sap.furcas.runtime.common.exceptions.DeferredActionResolvingException;
import com.sap.furcas.runtime.common.exceptions.DeferredModelElementCreationException;
import com.sap.furcas.runtime.common.exceptions.MetaModelLookupException;
import com.sap.furcas.runtime.common.exceptions.ModelAdapterException;
import com.sap.furcas.runtime.common.exceptions.ReferenceSettingException;
import com.sap.furcas.runtime.common.interfaces.IMetaModelLookup;
import com.sap.furcas.runtime.common.interfaces.IModelElementProxy;
import com.sap.furcas.runtime.common.interfaces.ResolvedNameAndReferenceBean;
import com.sap.furcas.runtime.common.util.MessageUtil;
import com.sap.furcas.runtime.common.util.TCSSpecificOCLEvaluator;
import com.sap.furcas.runtime.parser.PartitionAssignmentHandlerBase;
/**
* Functionality delegate for {@link EMFModelAdapter}, implementing the actual functionality.
*
* It assumes all input validations have already been performed by the model adapter.
*/
public class EMFModelAdapterDelegate {
private final Collection<StructureTypeMockObject> structureTypeMocks = new ArrayList<StructureTypeMockObject>();
private final Map<Object, Object> mock2ModelElementMap = new HashMap<Object, Object>();
private final PartitionAssignmentHandlerBase partitioningHandler;
private final HashSet<URI> explicitQueryScope;
private final EcoreModelElementFinder modelLookup;
private final IMetaModelLookup<EObject> metamodelLookup;
private final TCSSpecificOCLEvaluator oclEvaluator;
private final OppositeEndFinder oppositeEndFinder;
/**
* @see EMFModelAdapter for any API documentation.
*/
public EMFModelAdapterDelegate(ResourceSet resourceSet, PartitionAssignmentHandlerBase partitioningHandler, IMetaModelLookup<EObject> metamodelLookup, Set<URI> additionalQueryScope, TCSSpecificOCLEvaluator oclEvaluator, OppositeEndFinder oppositeEndFinder) {
this.metamodelLookup = metamodelLookup;
this.partitioningHandler = partitioningHandler;
this.oclEvaluator = oclEvaluator;
this.oppositeEndFinder = oppositeEndFinder;
// The model lookup needs to know about the element created by this delegate
// This is only a partial scope as it does neither include or resources in the resourceSet,
// nor all resources within the workspace
explicitQueryScope = new HashSet<URI>();
explicitQueryScope.addAll(additionalQueryScope);
modelLookup = new EcoreModelElementFinder(resourceSet, explicitQueryScope, metamodelLookup);
}
public Object createElement(List<String> qualifiedTypeName) throws ModelAdapterException {
EModelElement modelElement = findClassifierByName(qualifiedTypeName);
if (modelElement instanceof EClass) {
EClass metaClass = (EClass) modelElement;
if (metaClass.isAbstract()) {
throw new ModelAdapterException("Cannot instantiate EClass " + MessageUtil.asModelName(qualifiedTypeName)
+ " as it is abstract.");
}
EObject created = EcoreUtil.create(metaClass);
assignToPartition(created);
return created;
} else if (modelElement instanceof EDataType) {
EDataType structype = (EDataType) modelElement;
StructureTypeMockObject mock = new StructureTypeMockObject(structype);
structureTypeMocks.add(mock); // save deferred action for later
return mock;
} else {
throw new ModelAdapterException("Unsupported model element type. Cannot instantiate EModelElement "
+ MessageUtil.asModelName(qualifiedTypeName));
}
}
private void assignToPartition(EObject created) {
partitioningHandler.assignToDefaultPartition(created);
// for the EcoreModelElementFinder to work we need to know which resources hold
// model elements created by this adapter.
explicitQueryScope.add(created.eResource().getURI());
}
public EEnumLiteral createEnumLiteral(List<String> qualifiedEnumTypeName, String enumLiteralName) throws ModelAdapterException {
EModelElement modelElement = findClassifierByName(qualifiedEnumTypeName);
if (modelElement instanceof EEnum) {
EEnumLiteral enumLiteral = ((EEnum) modelElement).getEEnumLiteral(enumLiteralName);
if (enumLiteral == null) {
throw new ModelAdapterException(MessageUtil.asModelName(qualifiedEnumTypeName) + " does not have a liter named " + enumLiteralName);
}
return enumLiteral;
} else {
throw new ModelAdapterException("Unsupported model element type. Cannot instantiate EModelElement "
+ MessageUtil.asModelName(qualifiedEnumTypeName));
}
}
private EClassifier findClassifierByName(List<String> qualifiedTypeName) throws ModelAdapterException {
try {
ResolvedNameAndReferenceBean<EObject> result = metamodelLookup.resolveReference(qualifiedTypeName);
if (result != null) {
return (EClassifier) result.getReference();
} else {
throw new ModelAdapterException("Could not find an EClassifier named " + MessageUtil.asModelName(qualifiedTypeName)
+ " within the metamodels " + MessageUtil.asMetaModelNames(metamodelLookup.getMetaModelURIs()) + " .");
}
} catch (MetaModelLookupException e) {
throw new ModelAdapterException("Failed to find EClassifier named " + MessageUtil.asModelName(qualifiedTypeName), e);
}
}
public Object get(EObject modelElement, String propertyName) throws ModelAdapterException {
try {
EStructuralFeature feature = getFeatureByName(modelElement.eClass(), propertyName);
return modelElement.eGet(feature);
} catch (ModelAdapterException e) {
// look for hidden opposite
EStructuralFeature hiddenOpposite = getHiddenOppositeFeatureByName(modelElement.eClass(), propertyName);
if (hiddenOpposite == null) {
throw new ModelAdapterException("No such feature \"" + propertyName + "\" defined for " + modelElement.eClass());
}
return oppositeEndFinder.navigateOppositePropertyWithBackwardScope((EReference) hiddenOpposite, modelElement);
}
}
@SuppressWarnings("unchecked")
public void set(EObject modelElement, String propertyName, Object value, int index) throws ModelAdapterException {
if (value instanceof StructureTypeMockObject) {
value = getOrCreateRealObjectForMock((StructureTypeMockObject) value);
}
EStructuralFeature feat = getFeatureByName(modelElement.eClass(), propertyName);
if (feat.getUpperBound() == 1 || !feat.isOrdered()) {
// single value or unordered means we don't care about the index
set(modelElement, propertyName, value);
} else {
List<Object> multiProperty = (List<Object>) modelElement.eGet(feat);
if (index <= multiProperty.size() && index >= 0) {
if (feat.getUpperBound() < 0 || multiProperty.size() < feat.getUpperBound()) {
if (value instanceof Collection) {
multiProperty.addAll(index, (Collection<? extends Object>) value);
} else {
multiProperty.add(index, value);
}
} else {
throw new ModelAdapterException("Cannot add value, property " + propertyName
+ " has an upper multiplicity of " + feat.getUpperBound());
}
} else {
throw new ModelAdapterException("Cannot add value to property " + propertyName + " with index " + index
+ ", when the property has " + multiProperty.size() + " elements.");
}
}
removeExplicitResourceAssignmentIfAddedToContainment(modelElement, feat, value);
}
@SuppressWarnings("unchecked")
public void set(EObject modelElement, String propertyName, Object value) throws ModelAdapterException {
if (value instanceof StructureTypeMockObject) {
value = getOrCreateRealObjectForMock((StructureTypeMockObject) value);
}
EStructuralFeature feat = getFeatureByName(modelElement.eClass(), propertyName);
if (!feat.isMany()) {
if (value instanceof Collection) {
if (((Collection<?>) value).size() != 1) {
throw new ModelAdapterException("Upper bound for feature " + propertyName + " of " + modelElement.eClass() + " is 1. " +
"Unable to set " + ((Collection<?>) value).size() + " elements.");
}
value = ((Collection<?>) value).iterator().next();
}
Object originalValue = modelElement.eGet(feat);
if (valueHasChanged(value, originalValue)) {
modelElement.eSet(feat, value);
}
} else {
Collection<Object> multiProperty = (Collection<Object>) modelElement.eGet(feat);
if (feat.getUpperBound() < 0 || multiProperty.size() < feat.getUpperBound()) {
if (value instanceof Collection) {
multiProperty.addAll((Collection<Object>) value);
} else {
multiProperty.add(value);
}
} else {
throw new ModelAdapterException("Cannot add value, property " + propertyName + " has an upper multiplicity of "
+ feat.getUpperBound());
}
}
removeExplicitResourceAssignmentIfAddedToContainment(modelElement, feat, value);
}
/**
* Break the existing resource assignment of the child if the parent has the same resource. We want the child
* to be located directly under the parent. It should not stay a top-level element in the resource.
*
* The check for the same resource is needed to allow custom partitioning.
*/
private void removeExplicitResourceAssignmentIfAddedToContainment(EObject modelElement, EStructuralFeature feat, Object value) {
if (! (feat instanceof EReference)) {
return;
}
if (value instanceof Collection<?>) {
for (Object o : (Collection<?>) value) {
removeExplicitResourceAssignmentIfAddedToContainment(modelElement, feat, o);
}
} else if (value instanceof EObject) {
EObject parent = null;
EObject child = null;
if (((EReference) feat).isContainment()) {
parent = modelElement;
child = (EObject) value;
} else if (((EReference) feat).isContainer()) {
parent = (EObject) value;
child = modelElement;
} else {
return;
}
if (parent.eResource().equals(child.eResource())) {
parent.eResource().getContents().remove(child);
}
}
}
private boolean valueHasChanged(Object value, Object originalValue) {
return (value == null && originalValue != null) || (value != null && originalValue == null) ||
!(originalValue != null && originalValue.equals(value));
}
@SuppressWarnings("unchecked")
public void unset(EObject modelElement, String propertyName, Object value) throws ModelAdapterException {
EStructuralFeature feat = getFeatureByName(modelElement.eClass(), propertyName);
if (feat.getUpperBound() == 1) {
modelElement.eUnset(feat);
} else {
Collection<Object> multiProperty = (Collection<Object>) modelElement.eGet(feat);
if (multiProperty.size() > feat.getLowerBound()) {
if (value instanceof Collection) {
multiProperty.removeAll((Collection<? extends Object>) value);
} else {
multiProperty.remove(value);
}
} else {
throw new ModelAdapterException("Cannot add value, property " + propertyName + " has an lower multiplicity of "
+ feat.getLowerBound());
}
}
}
public Collection<?> evaluateOCLQuery(EObject sourceModelElement, Object keyValue,
String oclQuery, EObject contextObject) throws ModelAdapterException, ReferenceSettingException {
return oclEvaluator.findElementsWithOCLQuery(sourceModelElement, keyValue, oclQuery, contextObject);
}
public Object setReferenceWithOCLQuery(EObject sourceModelElement, String referencePropertyName, Object keyValue, String oclQuery,
EObject contextObject, EObject foreachObject) throws ModelAdapterException, ReferenceSettingException {
Collection<?> result = oclEvaluator.findElementsWithOCLQuery(sourceModelElement, keyValue, oclQuery, contextObject, foreachObject);
if (result.size() > 0) {
set(sourceModelElement, referencePropertyName, result);
return result;
} else {
// we return null instead of the empty collection to be consistent with the other setReference methods
return null;
}
}
/**
* @param contextObject might also be a collection of contextObjects!
*/
public Object setReferenceWithLookup(EObject sourceModelElement, String referencePropertyName, List<String> targetType,
String targetKeyName, Object targetKeyValue, Object contextObject) throws ModelAdapterException,
ReferenceSettingException {
Object result = null;
if (contextObject == null) {
// find target class with given property, assumes it is unique with
// that key and value
result = modelLookup.findEObjectOfTypeWithProperty(targetType, targetKeyName, targetKeyValue);
} else if (targetKeyName == null) {
// this is the case where the lookIn is actually the searched model
// element and no further element
// needs to be resolved. This occurs e.g. in LookupProperty
result = contextObject;
} else {
// need to investigate contextObject more.
EClassifier referenceType = getMetaType(targetType);
List<EObject> contents = null;
// look into immediate contents of context and look for appropriate
// object, and make sure it is unique
if (contextObject instanceof Collection<?>) {
for (Object obj : ((Collection<?>) contextObject)) {
if (obj instanceof EObject) {
contents = getContainedElements((EObject) obj);
} else {
throw new ReferenceSettingException("Expected context objext either to be of type "
+ "Collection<EObject>or EObject but got:" + obj.getClass());
}
}
} else if (contextObject instanceof EObject) {
contents = getContainedElements((EObject) contextObject);
} else {
throw new ReferenceSettingException("Expected context objext either to be of type "
+ "Collection<EObject>or EObject but got:" + contextObject.getClass());
}
for (EObject loopCandidateModelElement : contents) {
if (instanceOf(loopCandidateModelElement, referenceType)) {
Object candidateFeatureValue = loopCandidateModelElement.eGet(
getFeatureByName(loopCandidateModelElement.eClass(), targetKeyName));
if (candidateFeatureValue != null && candidateFeatureValue.equals(targetKeyValue)) {
if (result == null) {
// TODO what if there is more than one result? maybe use collection and add all results
result = loopCandidateModelElement;
} else {
throw new ReferenceSettingException("More than one candidate found within " + contextObject
+ " of type " + MessageUtil.asModelName(targetType) + " with feature " + targetKeyName
+ " equals " + targetKeyValue);
}
}
}
}
}
if (result != null) {
set(sourceModelElement, referencePropertyName, result);
}
return result;
}
private List<EObject> getContainedElements(EObject contextObject) {
// TODO here all composite contents of the given contextobject have to be resolved somehow.
// maybe this can be done using an mql query?
// TODO this needs to be extended by those associations that do not have
// a reference but are also defined as composite associations
List<EObject> allContents = new ArrayList<EObject>();
for (EModelElement element : getContentsIncludingInherited(contextObject.eClass())) {
if (element instanceof EReference && ((EReference) element).isContainment()) {
Object result = contextObject.eGet((EStructuralFeature) element);
if (result instanceof Collection<?>) {
for (Object o : (Collection<?>) result) {
allContents.add((EObject) o);
}
} else {
allContents.add((EObject) result);
}
}
}
return allContents;
}
private List<EModelElement> getContentsIncludingInherited(EModelElement contextObject) {
List<EModelElement> result = null;
if (contextObject instanceof EClass) {
result = new ArrayList<EModelElement>(((EClass) contextObject).getEAllStructuralFeatures());
} else if (contextObject instanceof EPackage) {
result = new ArrayList<EModelElement>(((EPackage) contextObject).getEClassifiers());
}
return result;
}
public Collection<Object> queryElement(List<String> typeName, Map<String, List<Object>> attributes) throws ModelAdapterException {
Map<String, Object> singleValuedAttributesMap = new HashMap<String, Object>();
Map<String, EObject> partitionableReferenceValuedAttributesMap = new HashMap<String, EObject>();
Set<String> featureNames = attributes.keySet();
for (String featureName : featureNames) {
List<Object> value = attributes.get(featureName);
if (value != null && value.size() > 0) {
if (value.size() > 1) {
throw new RuntimeException("Resolution by multi-valued attributes not possible.");
// multiValuedAttributesMap.put(featureName, value);
} else {
Object valueitem = value.get(0);
if (valueitem instanceof IModelElementProxy) {
valueitem = ((IModelElementProxy) valueitem).getRealObject();
}
if (valueitem instanceof EObject) {
partitionableReferenceValuedAttributesMap.put(featureName, (EObject) valueitem);
} else {
// TODO validate attributes map (primitives or list of
// primitives of suitable type in map)
singleValuedAttributesMap.put(featureName, valueitem);
}
}
}
}
return modelLookup.findEObjectsOfTypeWithProperties(typeName, partitionableReferenceValuedAttributesMap, singleValuedAttributesMap);
}
private EStructuralFeature getFeatureByName(EClass eClass, String propertyName) throws ModelAdapterException {
EStructuralFeature feature = eClass.getEStructuralFeature(propertyName);
if (feature == null) {
throw new ModelAdapterException("No such feature \"" + propertyName + "\" defined for " + eClass);
}
return feature;
}
private EReference getHiddenOppositeFeatureByName(EClass eClass, String propertyName) throws ModelAdapterException {
ArrayList<EReference> hiddenOppositeEnds = new ArrayList<EReference>();
// no need to fiddle with query2-based opposite end finder; we're only searching metamodels here
DefaultOppositeEndFinder.getInstance().findOppositeEnds(eClass, propertyName, hiddenOppositeEnds);
if (hiddenOppositeEnds.size() == 1) {
return hiddenOppositeEnds.iterator().next();
} else if (hiddenOppositeEnds.size() > 1) {
throw new ModelAdapterException("Ambigous hidden opposite ends named " + propertyName + " for " + eClass);
} else {
return null; // no hidden opposite end found;
}
}
public boolean hasDeferredActions() {
return (structureTypeMocks != null && structureTypeMocks.size() > 0);
}
public Map<Object, Object> performAllDeferredActions() throws DeferredActionResolvingException {
List<DeferredModelElementCreationException> exceptions = new ArrayList<DeferredModelElementCreationException>();
for (StructureTypeMockObject mock : structureTypeMocks) {
if (mock2ModelElementMap.containsKey(mock)) {
continue; // mock already resolved earlier, e.g. in set()
}
try {
Object realModelElement = createStructureTypeFromMock(mock);
mock2ModelElementMap.put(mock, realModelElement);
} catch (ModelAdapterException e) {
exceptions.add(new DeferredModelElementCreationException("Unable to create structure type from mock " + mock, e));
}
}
if (exceptions.size() > 0) {
throw new DeferredActionResolvingException(exceptions.size() + " deferred actions failed", exceptions);
}
return mock2ModelElementMap;
}
/**
* Finally performs the delayed instantiation of the structured type, using the
* values cached in the given mock object.
*
* @throws ModelAdapterException if e.g. required fields are not set.
*/
private Object createStructureTypeFromMock(StructureTypeMockObject mock) throws ModelAdapterException {
// TODO: when implementing, do not forget to assign the created structure type to
// the transient parsing resource so that it can be found by query2
throw new NotImplementedException();
}
private Object getOrCreateRealObjectForMock(StructureTypeMockObject mock) throws ModelAdapterException {
Object value = null;
// set value to the real object
if (mock2ModelElementMap.containsKey(mock)) {
value = mock2ModelElementMap.get(mock);
} else {
value = createStructureTypeFromMock(mock);
}
mock2ModelElementMap.put(mock, value);
return value;
}
public boolean instanceOf(EObject modelElement, EClassifier metaType) {
return metaType.isInstance(modelElement);
}
public EClass getMetaType(List<String> typeName) throws ModelAdapterException {
try {
ResolvedNameAndReferenceBean<EObject> result = metamodelLookup.resolveReference(typeName);
if (result != null && result.getReference() instanceof EClass) {
return (EClass) result.getReference();
} else {
throw new ModelAdapterException("Could not resolve any EClass for " + typeName);
}
} catch (MetaModelLookupException e) {
throw new ModelAdapterException("Failed to resolve the EClass for " + typeName, e);
}
}
}