/******************************************************************************* * Copyright (c) 2009, 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.ocl.oppositefinder.query2; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.emf.common.notify.Notifier; 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.EReference; import org.eclipse.emf.ecore.EcorePackage; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl; import org.eclipse.emf.ecore.util.ECrossReferenceAdapter; 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.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.ocl.ecore.opposites.DefaultOppositeEndFinder; import org.eclipse.ocl.ecore.opposites.OppositeEndFinder; import de.hpi.sam.bp2009.solution.queryContextScopeProvider.QueryContextProvider; /** * Uses EMF query2 to reverse-navigate a reference that has no opposite. The search scope * is dynamically determined for the start object of the navigation, using a * {@link QueryContextProvider}. If none is specifically passed to the constructor, * a default one is used which searches in the {@link ResourceSet} of the object * where navigation starts. However, with this default the implementation should behave * roughly like the base class implementation, except that no {@link ECrossReferenceAdapter} * is required. * * @author Axel Uhl (D043530) * */ public class Query2OppositeEndFinder implements OppositeEndFinder { /** * Used to obtain a query context for a given {@link EObject}. */ private final QueryContextProvider queryContextProvider; private final DefaultOppositeEndFinder delegate; private static Query2OppositeEndFinder instance; public static Query2OppositeEndFinder getInstance() { if (instance == null) { instance = new Query2OppositeEndFinder(); } return instance; } /** * Uses a {@link DefaultQueryContextProvider} to determine the context for queries */ protected Query2OppositeEndFinder() { this(new DefaultQueryContextProvider()); } public Query2OppositeEndFinder(QueryContextProvider queryContextProvider) { this.queryContextProvider = queryContextProvider; // using a singleton instance as delegate instead of inheriting saves the effort // of re-constructing the metadata cache managed by the DefaultOppositeEndFinder // over and over again delegate = DefaultOppositeEndFinder.getInstance(); } @Override public Collection<EObject> navigateOppositePropertyWithForwardScope(EReference property, EObject target) throws IllegalArgumentException { if (target instanceof EObject) { EObject etarget = target; Collection<EObject> result = null; if (property instanceof EReference && (((EClass) (property).getEType()).isSuperTypeOf(etarget.eClass()) || ((EClass) (property).getEType()).equals(EcorePackage.eINSTANCE.getEObject()))) { QueryContext queryContext = queryContextProvider.getForwardScopeQueryContext(etarget); ResourceSet rs = etarget.eResource().getResourceSet(); if (rs == null) { rs = new ResourceSetImpl(); } result = EcoreHelper.getInstance().reverseNavigate(etarget, property, queryContext, rs, IndexFactory.getInstance()); } return result; } throw new IllegalArgumentException(); } @Override public Collection<EObject> navigateOppositePropertyWithBackwardScope(EReference property, EObject etarget) { Collection<EObject> result = null; if (property instanceof EReference && (((EClass) (property).getEType()).isSuperTypeOf(etarget.eClass()) || ((EClass) (property).getEType()) .equals(EcorePackage.eINSTANCE.getEObject()))) { QueryContext queryContext = queryContextProvider.getBackwardScopeQueryContext(etarget); ResourceSet rs = null; if (etarget.eResource() != null) { rs = etarget.eResource().getResourceSet(); } if (rs == null) { rs = queryContext.getResourceSet(); if (rs == null) { rs = new ResourceSetImpl(); } } result = EcoreHelper.getInstance().reverseNavigate(etarget, property, queryContext, rs, IndexFactory.getInstance()); } return result; } @Override public void findOppositeEnds(EClassifier classifier, String name, List<EReference> ends) { delegate.findOppositeEnds(classifier, name, ends); } @Override public Map<String, EReference> getAllOppositeEnds(EClassifier classifier) { return delegate.getAllOppositeEnds(classifier); } /** * Uses a backward-scope {see {@link ProjectBasedScopeProvider#getBackwardScopeAsQueryContext()}) (all things that can see * <code>context</code> are in scope) to find all elements of a type equal or conforming to <code>cls</code>. */ @Override public Set<EObject> getAllInstancesSeeing(EClass cls, Notifier context) { QueryContext scope = queryContextProvider.getBackwardScopeQueryContext(context); return getAllInstancesWithScope(cls, context, scope); } /** * Uses a forward-scope {see {@link ProjectBasedScopeProvider#getForwardScopeAsQueryContext()}) (all things visible from * <code>context</code> are in scope) to find all elements of a type equal or conforming to <code>cls</code>. */ @Override public Set<EObject> getAllInstancesSeenBy(EClass cls, Notifier context) { QueryContext scope = queryContextProvider.getForwardScopeQueryContext(context); return getAllInstancesWithScope(cls, context, scope); } private Set<EObject> getAllInstancesWithScope(EClass cls, Notifier context, QueryContext scope) { Set<EObject> result = new HashSet<EObject>(); QueryProcessor queryProcessor = QueryProcessorFactory.getDefault().createQueryProcessor(IndexFactory.getInstance()); SelectEntry select = new SelectAlias("obj"); FromType from = new FromType(/* aliasName */ "obj", /* type URI */ EcoreUtil.getURI(cls), /* withoutsubtypes */ false); Query query = new Query(new SelectEntry[] { select }, new FromEntry[] { from }); // by default, a from-clause includes all subtypes unless the "withoutsubtypes" option is used ResultSet resultSet = queryProcessor.execute(query, scope); if (!resultSet.isEmpty()) { for (int i = 0; i < resultSet.getSize(); i++) { String uri = resultSet.getUri(i, "obj").toString(); EObject obj = getEObject(context, uri); result.add(obj); } } return result; } private EObject getEObject(Notifier context, String uri) { EObject result; if (context instanceof ResourceSet) { result = ((ResourceSet) context).getEObject(URI.createURI(uri), /* loadOnDemand */ true); } else if (context instanceof Resource) { Resource r = (Resource) context; String uriFragment = uri.split("#")[1]; result = r.getEObject(uriFragment); } else if (context instanceof EObject) { ResourceSet rs = ((EObject) context).eResource().getResourceSet(); result = getEObject(rs, uri); } else { throw new RuntimeException("Expected Resource, ResourceSet or EObject but got "+context.getClass().getName()); } return result; } }