/*******************************************************************************
* 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.adaptation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
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.EObject;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.query.index.IndexFactory;
import org.eclipse.emf.query2.FromEntry;
import org.eclipse.emf.query2.FromType;
import org.eclipse.emf.query2.LocalWhereEntry;
import org.eclipse.emf.query2.Operation;
import org.eclipse.emf.query2.Query;
import org.eclipse.emf.query2.QueryContext;
import org.eclipse.emf.query2.QueryProcessor;
import org.eclipse.emf.query2.QueryProcessorFactory;
import org.eclipse.emf.query2.ResultSet;
import org.eclipse.emf.query2.SelectAlias;
import org.eclipse.emf.query2.SelectEntry;
import org.eclipse.emf.query2.WhereClause;
import org.eclipse.emf.query2.WhereEntry;
import org.eclipse.emf.query2.WhereString;
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.EcoreHelper;
import com.sap.furcas.runtime.common.util.MessageUtil;
/**
* A helper class allowing to search a given reference scope for model elements.
* Model elements are search per type and (optionally) a give set of known attribute values.
*
* This class is used by the {@link EMFModelAdapterDelegate} to search for existing model
* elements, including the ones that have just been created by the delegate.
* Finding such model elements is important when resolving non-ocl based references.
*
* @author Stephan Erb (d049157)
*/
public class EcoreModelElementFinder {
private final ResourceSet resourceSet;
private final Set<URI> referenceScope;
private final IMetaModelLookup<EObject> metamodelLookup;
private final QueryProcessor queryProcessor;
private static final String MQL_ALIAS_INSTANCE = "instance";
/**
*
* @param referenceScope a live collection. May be changed from the outside after this class
* has been instantiated.
*/
public EcoreModelElementFinder(ResourceSet resourceSet, Set<URI> referenceScope, IMetaModelLookup<EObject> metamodelLookup) {
this.resourceSet = resourceSet;
this.metamodelLookup = metamodelLookup;
this.referenceScope = referenceScope;
queryProcessor = QueryProcessorFactory.getDefault().createQueryProcessor(IndexFactory.getInstance());
}
/**
* Returns the model element uniquely identified by the combination of type, property name and
* corresponding property value. Will return null if nothing is found. If more than one element is found, this
* method will throw a ModelAdapterException.<p>
*
* This is used for lookups within a context. The search only covers the explicitly given referenceScope
* (which includes the transient resource and thus the parsing context).
*/
public EObject findEObjectOfTypeWithProperty(List<String> targetType, String targetKeyName, Object targetKeyValue) throws ModelAdapterException {
URI qName = EcoreUtil.getURI(findMetaClassOfType(targetType));
SelectEntry se = new SelectAlias(MQL_ALIAS_INSTANCE);
FromEntry fe = new FromType(MQL_ALIAS_INSTANCE, qName, false);
WhereClause clause = new WhereString(targetKeyName, Operation.EQUAL, String.valueOf(targetKeyValue));
WhereEntry we = new LocalWhereEntry(MQL_ALIAS_INSTANCE, clause);
Query mq = new Query(new SelectEntry[] { se }, new FromEntry[] { fe }, new WhereEntry[] { we });
ResultSet resultSet = null;
try {
QueryContext scopeProvider = EcoreHelper.getRestrictedQueryContext(resourceSet, referenceScope);
resultSet = queryProcessor.execute(mq, scopeProvider); // ,
} catch (IllegalArgumentException e) {
// trying to access an non-existent property is an illegal argument for query2
throw new ModelAdapterException("Cannot find element of type " + MessageUtil.asModelName(targetType)
+ " where " + targetKeyName + " is " + targetKeyValue, e);
}
if (resultSet.getSize() == 0) {
return null;
} else if (resultSet.getSize() == 1) {
URI[] uris = resultSet.getUris(MQL_ALIAS_INSTANCE);
return resourceSet.getEObject(uris[0], true);
} else {
throw new ReferenceSettingException("Expected to find 1 result for search after "
+ MessageUtil.asModelName(targetType) + " with " + targetKeyName + " = " + targetKeyValue
+ " but found " + resultSet.getSize());
}
}
/**
* Returns all instances of the given type, which have the attribute/references values
* as specified by the given maps.<p>
*
* It is searched within the referenceScope and within all resources in
* the resourceSet.
*/
public Collection<Object> findEObjectsOfTypeWithProperties(List<String> typeName,
Map<String, EObject> partitionableReferenceValuedAttributesMap,
Map<String, Object> singleAttributesMap) throws ModelAdapterException {
StringBuilder queryBuilder = new StringBuilder();
queryBuilder.append("select instance \n").append("from [");
URI uriEClass = EcoreUtil.getURI(findMetaClassOfType(typeName));
queryBuilder.append(uriEClass);
queryBuilder.append("] as instance");
Set<String> singleValueFeatureNames = singleAttributesMap.keySet();
int index = 0;
for (Iterator<String> iterator = singleValueFeatureNames.iterator(); iterator.hasNext(); index++) {
String featureName = iterator.next();
Object value = singleAttributesMap.get(featureName);
if (value != null) {
String featureValueInQuery = getFeatureValueAlias(index);
if (value instanceof IModelElementProxy && ((IModelElementProxy) value).getRealObject() != null) {
queryBuilder.append(", \"").append(EcoreUtil.getURI((EObject) ((IModelElementProxy) value).getRealObject()))
.append("\" as ").append(featureValueInQuery);
} else if (value instanceof EObject) {
queryBuilder.append(", \"").append(EcoreUtil.getURI(((EObject) value))).append("\" as ")
.append(featureValueInQuery);
}
}
}
Set<String> partitionableFeatureNames = partitionableReferenceValuedAttributesMap.keySet();
for (String featureName : partitionableFeatureNames) {
EObject value = partitionableReferenceValuedAttributesMap.get(featureName);
queryBuilder.append(",\n [").append(EcoreUtil.getURI(value.eClass())).append("] as ").append(featureName).append("_alias")
.append(" in elements {[").append(EcoreUtil.getURI(value)).append("]}").append("\n");
}
for (String featureName : partitionableFeatureNames) {
queryBuilder.append(" where instance.").append(featureName).append(" = ").append(featureName).append("_alias")
.append("\n");
}
index = 0;
for (Iterator<String> iterator = singleValueFeatureNames.iterator(); iterator.hasNext(); index++) {
String featureName = iterator.next();
Object value = singleAttributesMap.get(featureName);
queryBuilder.append(" where instance.").append(featureName);
if (value != null) {
if (value instanceof IModelElementProxy && ((IModelElementProxy) value).getRealObject() != null) {
String featureValueInQuery = getFeatureValueAlias(index);
queryBuilder.append(" = ").append(featureValueInQuery).append("\n");
} else if (value instanceof EObject) {
String featureValueInQuery = getFeatureValueAlias(index);
queryBuilder.append(" = ").append(featureValueInQuery).append("\n");
} else {
String valueString = String.valueOf(value);
queryBuilder.append(" = '").append(valueString).append("'\n");
}
} else {
queryBuilder.append(" = null\n");
}
}
QueryContext scopeProvider = EcoreHelper.getQueryContext(resourceSet, referenceScope);
ResultSet resultSet = queryProcessor.execute(queryBuilder.toString(), scopeProvider);
List<Object> result = new ArrayList<Object>(resultSet.getSize());
for (URI uri : resultSet.getUris("instance")) {
EObject object = resourceSet.getEObject(uri, true);
// TODO filter using multi-valued attributes
result.add(object);
}
return result;
}
private String getFeatureValueAlias(int index) {
return "feat" + index;
}
private EClassifier findMetaClassOfType(List<String> qualifiedTypeName) throws ModelAdapterException {
try {
ResolvedNameAndReferenceBean<EObject> result = metamodelLookup.resolveReference(qualifiedTypeName);
if (result != null) {
return (EClass) result.getReference();
} else {
throw new ModelAdapterException("Could not resolve any EClassifier for " + qualifiedTypeName);
}
} catch (MetaModelLookupException e) {
throw new ModelAdapterException("Failed to resolve the EClassifier for " + qualifiedTypeName, e);
}
}
}