/******************************************************************************* * 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.ocl.oppositefinder.query2; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.eclipse.emf.common.util.BasicEList; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.common.util.UniqueEList; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EReference; 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.EcoreUtil; import org.eclipse.emf.query.index.Index; import org.eclipse.emf.query.index.query.IndexQueryFactory; import org.eclipse.emf.query.index.query.QueryCommand; import org.eclipse.emf.query.index.query.QueryExecutor; import org.eclipse.emf.query.index.query.ResourceQuery; import org.eclipse.emf.query.index.query.descriptors.ResourceDescriptor; import org.eclipse.emf.query.index.update.IndexUpdater; import org.eclipse.emf.query.index.update.ResourceIndexer; import org.eclipse.emf.query.index.update.UpdateCommandAdapter; import org.eclipse.emf.query2.EmfHelper; import org.eclipse.emf.query2.FromEntry; import org.eclipse.emf.query2.FromFixedSet; 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.emf.query2.WhereEntry; import org.eclipse.emf.query2.WhereRelationReference; public class EcoreHelper { private static EcoreHelper instance; private QueryProcessor queryProcessor; public static EcoreHelper getInstance() { if (instance == null) { instance = new EcoreHelper(); } return instance; } public Collection<EClass> getAllSubclasses(EClass c, Index index) { return new EmfHelper(getQueryContext(new ResourceSetImpl(), index), index).getAllSubtypes(c); } /** * With references that may occur as hidden opposites (see {@link EReference#getOwnedOpposite()}), * it is not possible to simply ask the container of a reference to find the owning class. * Instead, for hidden opposites, the opposite's type will be used. */ private EClass getOwningType(EReference reference) { if (reference.eContainer() instanceof EClass) { return (EClass) reference.eContainer(); } else { return (EClass) reference.getEOpposite().getEType(); } } private EList<EObject> createEList(boolean unique) { EList<EObject> result; if (unique) { result = new UniqueEList<EObject>(); } else { result = new BasicEList<EObject>(); } return result; } /** * Traverses <tt>forwardReference</tt> in reverse, that is, looks for objects that, when starting * from them and navigating <tt>forwardReference</tt>, it results in <tt>from</tt>.<p> * * Precondition: <tt>forwardReference.getEOpposite() != null</tt> */ public Collection<EObject> reverseNavigate(EObject from, EReference forwardReference, QueryContext scope, ResourceSet rs, Index index) { boolean unique; EReference opposite = forwardReference.getEOpposite(); if (opposite != null) { unique = forwardReference.getEOpposite().isUnique(); } else { unique = false; } Collection<EObject> result = createEList(unique); if (opposite != null) { if (opposite.isMany()) { for (Object o : ((Collection<?>) from.eGet(opposite))) { result.add((EObject) o); } } else { EObject singleResult = (EObject) from.eGet(opposite); if (singleResult != null) { result.add(singleResult); } } } else { reverseNavigate(from, forwardReference, scope, rs, result, index); } return result; } /** * Same as {@link #reverseNavigate(EObject, EReference, QueryContext, ResourceSet, Index)}, but <tt>forwardReference</tt> * does not have to have an opposite. Instead, uniqueness of the result collection is determined by the * <tt>unique</tt> parameter. */ public Collection<EObject> reverseNavigate(EObject from, EReference forwardReference, QueryContext scope, ResourceSet rs, boolean unique, Index index) { Collection<EObject> result = createEList(unique); reverseNavigate(from, forwardReference, scope, rs, index); return result; } /** * Navigates along the <tt>to</tt> reference, starting at object <tt>from</tt>. The reference * can (and usually will) be a "hidden opposite" which is owned by another {@link EReference} * (see also {@link EReference#getOwnedOpposite()}). The <tt>to</tt> reference has to have * a valid {@link EReference#getEOpposite() opposite}. */ public Collection<EObject> navigateByQuery(EObject from, EReference to, QueryContext scope, ResourceSet rs, Index index) { return reverseNavigate(from, to.getEOpposite(), scope, rs, index); } /** * Same as {@link #navigateByQuery(EObject, EReference, QueryContext, ResourceSet, Index)}, only that the * {@link ResourceSet} is determined from the <tt>from</tt> object */ public Collection<EObject> navigateByQuery(EObject from, EReference to, QueryContext scope, Index index) { return reverseNavigate(from, to.getEOpposite(), scope, from.eResource().getResourceSet(), index); } /** * Same as {@link #reverseNavigate(EObject, EReference, QueryContext, ResourceSet, Index)}, only that * <tt>forwardReference</tt> does not have to have an {@link EReference#getEOpposite() opposite}. * Instead, a collection is passed in to which the result are added. Therefore, the opposite * reference is not required, as no uniqueness attribute needs to be determined. * @param rs used to resolve the element URIs resulting from the query */ public void reverseNavigate(EObject from, EReference forwardReference, QueryContext scope, ResourceSet rs, Collection<EObject> result, Index index) { if (from.eResource() != null) { // no way to find anything by query2 if not in a resource if (forwardReference.isContainment()) { // TODO this can be written much shorter using feature IDs EObject container = from.eContainer(); if (container != null) { if ((forwardReference.isMany() && ((Collection<?>) container.eGet(forwardReference)).contains(from)) || (!forwardReference.isMany() && container == from)) { result.add(container); } } } else { EClass owningType = getOwningType(forwardReference); SelectEntry select = new SelectAlias("target"); FromFixedSet fromFromElement = new FromFixedSet("source", EcoreUtil.getURI(from.eClass()), new URI[] { EcoreUtil.getURI(from) }); FromType fromTarget = new FromType("target", EcoreUtil.getURI(owningType), /* _withoutSubtypes */false); WhereEntry where = new WhereRelationReference(/* _leftAlias */"target", /* _featureName */ forwardReference.getName(), /* _rightAlias */"source"); Query query = new Query(new SelectEntry[] { select }, new FromEntry[] { fromFromElement, fromTarget }, new WhereEntry[] { where }); final ResultSet resultSet = getQueryProcessor(index).execute(query, scope); for (int i = 0; i < resultSet.getSize(); i++) { result.add(rs.getEObject(resultSet.getUri(i, "target"), /* loadOnDemand */true)); //$NON-NLS-1$ } } } } private QueryProcessor getQueryProcessor(Index index) { if (queryProcessor == null) { queryProcessor = QueryProcessorFactory.getDefault().createQueryProcessor(index); } return queryProcessor; } /** * Same as {@link #reverseNavigate(EObject, EReference, QueryContext, ResourceSet, Collection, Index)}, only * that the resource set is determined from the <tt>from</tt> object. */ public void reverseNavigate(EObject from, EReference forwardReference, QueryContext scope, Collection<EObject> result, Index index) { reverseNavigate(from, forwardReference, scope, from.eResource().getResourceSet(), result, index); } /** * Constructs a query context that contains all of <tt>rs</tt>'s resources and all * metamodel resources */ public QueryContext getQueryContext(final ResourceSet rs, final Index index) { return new QueryContext() { public URI[] getResourceScope() { final List<URI> result = new ArrayList<URI>(); index.executeQueryCommand(new QueryCommand() { public void execute(QueryExecutor queryExecutor) { ResourceQuery<ResourceDescriptor> resourceQuery = IndexQueryFactory.createResourceQuery(); for (ResourceDescriptor desc : queryExecutor.execute(resourceQuery)) { result.add(desc.getURI()); } for (Resource r:rs.getResources()) { result.add(r.getURI()); } } }); return result.toArray(new URI[0]); } public ResourceSet getResourceSet() { return rs; } }; } public void addResourceToDefaultIndex(Index index, final Resource... resources) { index.executeUpdateCommand(new UpdateCommandAdapter() { @Override public void execute(IndexUpdater updater) { ResourceIndexer rd = new ResourceIndexer(); for (Resource r : resources) { if (r.isLoaded()) { rd.resourceChanged(updater, r); } } } }); } }