/*******************************************************************************
* Copyright (c) 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.lookup;
import java.util.ArrayList;
import java.util.List;
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.EGenericType;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.ETypeParameter;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.ocl.ecore.opposites.OppositeEndFinder;
import com.sap.furcas.runtime.common.exceptions.MetaModelLookupException;
import com.sap.furcas.runtime.common.interfaces.IMetaModelLookup;
import com.sap.furcas.runtime.common.interfaces.MultiplicityBean;
import com.sap.furcas.runtime.common.interfaces.ResolvedNameAndReferenceBean;
import com.sap.furcas.runtime.common.util.EcoreHelper;
import com.sap.furcas.runtime.common.util.MessageUtil;
/**
* Abstract implementation of {@link IMetaModelLookup} allowing clients to inspect ecore metamodels.
*
* Subclasses must implement mechanisms to search for EClassifiers by a given qualified or
* unqualified name.
*/
public abstract class AbstractEcoreMetaModelLookup implements IMetaModelLookup<EObject> {
/**
* Returns an {@link OppositeEndFinder} that can be used to used to find
* opposite ends within the target metamodel.
*/
protected abstract OppositeEndFinder getOppositeEndFinder();
/**
* Look up the type by its name
*/
protected final EClassifier findClassifier(ResolvedNameAndReferenceBean<EObject> reference) throws MetaModelLookupException {
if (reference != null) {
if (reference.getReference() instanceof EClassifier) {
EClassifier classi = (EClassifier) reference.getReference();
return classi;
}
return findClassifiersByQualifiedName(reference.getNames());
}
return null;
}
protected abstract EClassifier findClassifiersByQualifiedName(List<String> names) throws MetaModelLookupException;
protected abstract List<EClassifier> findClassifiersByUnqualifiedName(String typeName) throws MetaModelLookupException;
@Override
public boolean isAbstract(ResolvedNameAndReferenceBean<EObject> referedElement) throws MetaModelLookupException {
EClassifier classi = findClassifier(referedElement);
if (classi instanceof EClass) {
return ((EClass) classi).isAbstract();
} else {
return false;
}
}
@Override
public ResolvedNameAndReferenceBean<EObject> getFeatureClassReference(ResolvedNameAndReferenceBean<EObject> reference,
String featureName) throws MetaModelLookupException {
EClassifier resultType = null;
if (reference != null) {
EStructuralFeature feat = getEStructuralFeature(reference, featureName);
if(feat == null) {
//could be hidden opposite
feat = getHiddenOpposite(reference, featureName);
}
if (feat != null) {
if(feat.getEGenericType() != null) {
resultType = getEType(reference.getReference(), feat);
} else {
EClassifier eType = feat.getEType();
if (eType != null && eType.eIsProxy()) {
EClassifier resolved = (EClassifier) EcoreUtil.resolve(eType, feat);
if (resolved == eType) {
throw new MetaModelLookupException("Could not resolve proxy for classifier: " + eType);
}
resultType = resolved;
} else {
resultType = eType;
}
}
if (resultType == null) {
throw new MetaModelLookupException("Inconsistent metamodel: No type specified for for " + MessageUtil.asModelName(reference.getNames())+ "." + featureName);
}
}
}
return createBean(resultType);
}
/**
* Return the specialised value of feature.getEType() resolving any type parameters
* from the specialised type of the sourceObject of the feature.
*
* @param sourceObject
* @param feature
* @return
*/
public static EClassifier getEType(EObject sourceObject, EStructuralFeature feature) {
EGenericType targetGenericType = feature.getEGenericType();
ETypeParameter targetTypeParameter = targetGenericType.getETypeParameter();
if ((targetTypeParameter != null) && (sourceObject != null)) {
EClass sourceGenericType = feature.getEContainingClass();
EObject typeParameterContainer = targetTypeParameter.eContainer();
EClass sourceClass = (EClass) sourceObject;
EList<EGenericType> allSourceGenericSuperTypes = sourceClass.getEAllGenericSuperTypes();
for (EGenericType sourceGenericSuperType : allSourceGenericSuperTypes) {
if (sourceGenericSuperType.getERawType() == typeParameterContainer) {
EList<EGenericType> sourceTypeArguments = sourceGenericSuperType.getETypeArguments();
int i = sourceGenericType.getETypeParameters().indexOf(targetTypeParameter);
if ((0 <= i) && (i < sourceTypeArguments.size())) {
EGenericType sourceTypeArgument = sourceTypeArguments.get(i);
return sourceTypeArgument.getERawType();
}
}
}
}
return targetGenericType.getERawType();
}
private EStructuralFeature getHiddenOpposite(ResolvedNameAndReferenceBean<EObject> reference, String featureName) throws MetaModelLookupException {
List<EReference> ends = new ArrayList<EReference>();
getOppositeEndFinder().findOppositeEnds((EClassifier) reference.getReference(), featureName, ends);
if(ends.size() > 1) {
throw new MetaModelLookupException("More than one oppositeEnd found for: " + reference.getNames() + ":" + featureName);
} else if(ends.size() == 0) {
return null;
} else {
return ends.iterator().next();
}
}
private EStructuralFeature getEStructuralFeature(ResolvedNameAndReferenceBean<EObject> reference, String featureName) throws MetaModelLookupException {
EStructuralFeature returnFeature = null;
EClassifier typeClass = findClassifier(reference);
if (typeClass != null && typeClass instanceof EClass) {
returnFeature = ((EClass) typeClass).getEStructuralFeature(featureName);
}
return returnFeature;
}
/*
* (non-Javadoc)
*
* @see com.sap.mi.textual.interfaces.IMetaModelLookup#getMultiplicity(java.lang.String, java.lang.String)
*/
@Override
public MultiplicityBean getMultiplicity(ResolvedNameAndReferenceBean<EObject> referedElement, String featureName)
throws MetaModelLookupException {
String[] path = featureName.split("\\.");
ResolvedNameAndReferenceBean<EObject> referedElementPart = referedElement;
MultiplicityBean bean = null;
for (String featureNamePart : path) {
EStructuralFeature feature = getEStructuralFeature(referedElementPart, featureNamePart);
if (feature != null) {
bean = new MultiplicityBean();
bean.setLowerBound(feature.getLowerBound());
bean.setUpperBound(feature.getUpperBound());
referedElementPart = createBean(feature.getEType());
}
}
return bean;
}
@Override
public List<ResolvedNameAndReferenceBean<EObject>> qualifyName(String typeName) throws MetaModelLookupException {
// Find all Classifiers of this name
// return all qualified names for these Classifiers
List<ResolvedNameAndReferenceBean<EObject>> names = new ArrayList<ResolvedNameAndReferenceBean<EObject>>();
List<EClassifier> classifiers = findClassifiersByUnqualifiedName(typeName);
for (EClassifier classifier : classifiers) {
names.add(createBean(classifier));
}
return names;
}
protected ResolvedNameAndReferenceBean<EObject> createBean(EClassifier metaClass) {
if (metaClass == null) {
return null;
} else {
return new ResolvedNameAndReferenceBean<EObject>(EcoreHelper.getQualifiedName(metaClass), metaClass);
}
}
@Override
public List<String> getEnumLiterals(ResolvedNameAndReferenceBean<EObject> reference) throws MetaModelLookupException {
EClassifier classifier = findClassifier(reference);
if (!(classifier instanceof EEnum)) {
throw new MetaModelLookupException("The given name (" + reference.getNames() + ") does not resolve to an EnumerationType");
}
EEnum enumeration = (EEnum) classifier;
List<String> literals = new ArrayList<String>(enumeration.getELiterals().size());
for (EEnumLiteral literal : enumeration.getELiterals()) {
literals.add(literal.getLiteral());
}
return literals;
}
@Override
public boolean isSubTypeOf(ResolvedNameAndReferenceBean<EObject> subType, ResolvedNameAndReferenceBean<EObject> superType) throws MetaModelLookupException {
EClassifier supertypeClass = findClassifier(superType);
EClassifier subtypeClass = findClassifier(subType);
if (supertypeClass == null || subtypeClass == null) {
return false;
}
if (subtypeClass.equals(supertypeClass)) {
return true;
}
List<EClass> superList = ((EClass) subtypeClass).getEAllSuperTypes();
for (EClass generalizableElement : superList) {
if (generalizableElement.equals(supertypeClass)) {
return true;
}
}
return false;
}
@Override
public ResolvedNameAndReferenceBean<EObject> resolveReference(List<String> names) throws MetaModelLookupException {
EClassifier classifier = findClassifiersByQualifiedName(names);
return createBean(classifier);
}
@Override
public ResolvedNameAndReferenceBean<EObject> resolveReferenceName(EObject reference) {
if (reference instanceof EClassifier) {
return createBean((EClassifier) reference);
} else {
throw new IllegalArgumentException("Expected Classifier, not " + reference.getClass());
}
}
}