/** * Copyright (c) 2012 Cloudsmith Inc. and other contributors, as listed below. * 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: * Cloudsmith * */ package org.cloudsmith.geppetto.pp.dsl.ui.editor.findrefs; import static com.google.common.collect.Iterables.transform; import static com.google.common.collect.Sets.newHashSet; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import org.cloudsmith.geppetto.pp.dsl.adapters.CrossReferenceAdapterFactory; import org.cloudsmith.geppetto.pp.dsl.linking.PPReferenceDescription; import org.cloudsmith.geppetto.pp.dsl.ui.internal.util.CancelablePredicate; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.core.runtime.SubMonitor; import org.eclipse.emf.common.util.TreeIterator; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.xtext.EcoreUtil2; import org.eclipse.xtext.resource.EObjectDescription; import org.eclipse.xtext.resource.IEObjectDescription; import org.eclipse.xtext.resource.IReferenceDescription; import org.eclipse.xtext.resource.IResourceDescription; import org.eclipse.xtext.resource.IResourceDescriptions; import org.eclipse.xtext.util.IAcceptor; import org.eclipse.xtext.util.concurrent.IUnitOfWork; import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; import com.google.common.collect.Iterators; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.inject.Inject; /** * This is a to a large part a copy of the ReferenceFinder in the corresponding package in Xtext, modified to search * for references the Geppetto way. * * */ public class PPReferenceFinder { public static class DescriptionURIPredicate extends CancelablePredicate<IEObjectDescription> { final private URI uri; public DescriptionURIPredicate(EObject eobj, IProgressMonitor monitor) { super(monitor); // Create URI the same way as IEObjectDescription URI is created uri = EObjectDescription.create("dummy", eobj).getEObjectURI(); } public DescriptionURIPredicate(URI uri, IProgressMonitor monitor) { super(monitor); // Create URI the same way as IEObjectDescription URI is created this.uri = uri; } @Override public boolean monitoredApply(IEObjectDescription input) { return input.getEObjectURI().equals(uri); } } /** * Executes <code>work</code> on the element referred to by the <code>targetURI</code>. That involves reloading the * element if it is proxified or the editor it belonged to has been closed. */ interface ILocalResourceAccess { <R> R readOnly(URI resourceURI, IUnitOfWork<R, ResourceSet> work); } interface IPPQueryData { String getLabel(); URI getLeadElementURI(); URI getLocalContextResourceURI(); Predicate<IReferenceDescription> getResultFilter(); Set<URI> getTargetURIs(); } private static class PPCrossReferencer { /** * @param targetResources * @return */ public static Map<EObject, Collection<IEObjectDescription>> find(Set<Resource> targetResources) { Map<EObject, Collection<IEObjectDescription>> result = Maps.newHashMap(); for(Resource r : targetResources) { TreeIterator<EObject> itor = r.getAllContents(); while(itor.hasNext()) { EObject e = itor.next(); List<IEObjectDescription> references = CrossReferenceAdapterFactory.eINSTANCE.get(e); if(references != null && references.size() > 0) result.put(e, references); } } return result; } } private IResourceDescriptions index; private static Function<EObject, Resource> eobj2Resource = new Function<EObject, Resource>() { @Override public Resource apply(EObject from) { return from.eResource(); } }; private static final Iterable<IEObjectDescription> emptyIterable = new Iterable<IEObjectDescription>() { @Override public Iterator<IEObjectDescription> iterator() { return Iterators.emptyIterator(); } }; private static final Function<Map<EObject, Collection<IEObjectDescription>>, Integer> sizeFunction = new Function<Map<EObject, Collection<IEObjectDescription>>, Integer>() { @Override public Integer apply(Map<EObject, Collection<IEObjectDescription>> from) { // size of all references candidates int size = 0; for(Collection<?> c : from.values()) size += c.size(); return size; } }; @Inject public PPReferenceFinder(IResourceDescriptions index) { this.index = index; } private void checkMonitor(IProgressMonitor monitor) { if(monitor != null && monitor.isCanceled()) throw new OperationCanceledException("Puppet reference search canceled"); } /** * Returns the length of a containing URI's fragment or 0 if the candidate is not a container. * * @param contained * @param containerCandidate * @return */ private int containerSpecificity(URI containedURI, URI containerURI) { if(!containedURI.path().equals(containerURI.path())) return 0; // same resource, if desc's fragment is in at the start of the path, then contained is contained by containerCandidate if(containedURI.fragment() != null && containedURI.fragment().startsWith(containerURI.fragment())) return containerURI.fragment().length(); return 0; } public void findAllReferences(IPPQueryData queryData, ILocalResourceAccess localResourceAccess, final IAcceptor<IReferenceDescription> acceptor, IProgressMonitor monitor) { final SubMonitor subMonitor = SubMonitor.convert(monitor, 2); if(!queryData.getTargetURIs().isEmpty()) { findLocalReferences(queryData, localResourceAccess, acceptor, subMonitor.newChild(1)); findIndexedReferences(queryData, acceptor, subMonitor.newChild(1)); } } protected IEObjectDescription findClosestExportedContainerDescriptor(EObject element, Iterable<IEObjectDescription> exportedElements) { IEObjectDescription closest = null; int maxSpecificity = 0; for(IEObjectDescription containerCandidate : exportedElements) { int specificity = containerSpecificity( EcoreUtil2.getNormalizedURI(element), containerCandidate.getEObjectURI()); if(specificity > maxSpecificity) { maxSpecificity = specificity; closest = containerCandidate; } } return closest; } /** * @param queryData * @param acceptor * @param newChild */ public void findIndexedReferences(final IPPQueryData queryData, final IAcceptor<IReferenceDescription> acceptor, IProgressMonitor monitor) { findIndexedReferences(queryData.getTargetURIs(), acceptor, queryData.getResultFilter(), monitor); } protected void findIndexedReferences(Set<URI> targetURIs, IAcceptor<IReferenceDescription> acceptor, Predicate<IReferenceDescription> filter, IProgressMonitor monitor) { // use only resource path of targets to disqualify references from within the targets Set<URI> targetResourceURIs = newHashSet(transform(targetURIs, new Function<URI, URI>() { public URI apply(URI from) { return from.trimFragment(); } })); // All resources that may have one of the targets as source final Iterable<IResourceDescription> allResourceDescriptions = index.getAllResourceDescriptions(); int numResources = Iterables.size(allResourceDescriptions); // NOTE: the Iterables.size is optimized for Collection<?> SubMonitor subMonitor = SubMonitor.convert(monitor, "Find puppet references", numResources); for(IResourceDescription resourceDescription : allResourceDescriptions) { if(resourceDescription != null) { if(subMonitor.isCanceled()) return; if(!targetResourceURIs.contains(resourceDescription.getURI())) { // local references are filtered out - this is an external reference for(IReferenceDescription referenceDescription : resourceDescription.getReferenceDescriptions()) { if(subMonitor.isCanceled()) return; if(targetURIs.contains(referenceDescription.getTargetEObjectUri()) && (filter == null || filter.apply(referenceDescription))) { acceptor.accept(referenceDescription); } } } subMonitor.worked(1); } } } /** * @param queryData * @param localResourceAccess * @param acceptor * @param newChild */ private void findLocalReferences(final IPPQueryData queryData, ILocalResourceAccess localResourceAccess, final IAcceptor<IReferenceDescription> acceptor, IProgressMonitor monitor) { final SubMonitor subMonitor = SubMonitor.convert(monitor, "Find references", 1); localResourceAccess.readOnly(queryData.getLocalContextResourceURI(), new IUnitOfWork<Boolean, ResourceSet>() { public Boolean exec(ResourceSet localContext) throws Exception { Set<EObject> targets = newHashSet(); for(URI targetURI : queryData.getTargetURIs()) { EObject target = localContext.getEObject(targetURI, true); if(target != null) targets.add(target); } findLocalReferences(targets, acceptor, queryData.getResultFilter(), subMonitor); return true; } }); } public void findLocalReferences(Set<? extends EObject> targets, IAcceptor<IReferenceDescription> acceptor, Predicate<IReferenceDescription> filter, IProgressMonitor monitor) { checkMonitor(monitor); if(targets != null && !targets.isEmpty()) { Set<Resource> targetResources = Sets.newHashSet(Iterables.transform(targets, eobj2Resource)); Map<EObject, Collection<IEObjectDescription>> objectsWithXRef = PPCrossReferencer.find(targetResources); SubMonitor subMonitor = SubMonitor.convert(monitor, "Find local puppet references", targets.size()); for(EObject target : targets) { Predicate<IEObjectDescription> p = new DescriptionURIPredicate( target, subMonitor.newChild(sizeFunction.apply(objectsWithXRef))); Map<Resource, Iterable<IEObjectDescription>> exportedObjectsByResourceCache = Maps.newHashMap(); for(Entry<EObject, Collection<IEObjectDescription>> entry : objectsWithXRef.entrySet()) { for(IEObjectDescription targetCandidate : entry.getValue()) if(p.apply(targetCandidate)) { EObject eObj = entry.getKey(); Resource r = eObj.eResource(); Iterable<IEObjectDescription> exported = exportedObjectsByResourceCache.get(r); if(exported == null) exportedObjectsByResourceCache.put(r, exported = getExportedElements(r)); // find the exported container closest to the source referencing the target IEObjectDescription closestExported = findClosestExportedContainerDescriptor(eObj, exported); IReferenceDescription refDesc = PPReferenceDescription.create( EcoreUtil2.getNormalizedURI(eObj), closestExported, targetCandidate); if(filter == null || filter.apply(refDesc)) acceptor.accept(refDesc); } } } } } protected Iterable<IEObjectDescription> getExportedElements(Resource resource) { IResourceDescription resourceDescription = index.getResourceDescription(EcoreUtil2.getNormalizedURI(resource)); if(resourceDescription != null) return resourceDescription.getExportedObjects(); return emptyIterable; } }