| /******************************************************************************* |
| * Copyright (c) 2000, 2011 IBM Corporation 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: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.jdt.internal.corext.refactoring.rename; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.OperationCanceledException; |
| import org.eclipse.core.runtime.SubProgressMonitor; |
| |
| import org.eclipse.jdt.core.IMember; |
| import org.eclipse.jdt.core.IMethod; |
| import org.eclipse.jdt.core.IRegion; |
| import org.eclipse.jdt.core.IType; |
| import org.eclipse.jdt.core.ITypeHierarchy; |
| import org.eclipse.jdt.core.JavaCore; |
| import org.eclipse.jdt.core.JavaModelException; |
| import org.eclipse.jdt.core.WorkingCopyOwner; |
| import org.eclipse.jdt.core.search.IJavaSearchConstants; |
| import org.eclipse.jdt.core.search.IJavaSearchScope; |
| import org.eclipse.jdt.core.search.SearchEngine; |
| import org.eclipse.jdt.core.search.SearchMatch; |
| import org.eclipse.jdt.core.search.SearchParticipant; |
| import org.eclipse.jdt.core.search.SearchPattern; |
| import org.eclipse.jdt.core.search.SearchRequestor; |
| |
| import org.eclipse.jdt.internal.corext.refactoring.RefactoringScopeFactory; |
| import org.eclipse.jdt.internal.corext.refactoring.base.ReferencesInBinaryContext; |
| import org.eclipse.jdt.internal.corext.util.JavaModelUtil; |
| import org.eclipse.jdt.internal.corext.util.SearchUtils; |
| |
| public class RippleMethodFinder2 { |
| |
| private final IMethod fMethod; |
| private List<IMethod> fDeclarations; |
| private ITypeHierarchy fHierarchy; |
| private Map<IType, IMethod> fTypeToMethod; |
| private Set<IType> fRootTypes; |
| private MultiMap<IType, IType> fRootReps; |
| private Map<IType, ITypeHierarchy> fRootHierarchies; |
| private UnionFind fUnionFind; |
| |
| private final boolean fExcludeBinaries; |
| private final ReferencesInBinaryContext fBinaryRefs; |
| private Map<IMethod, SearchMatch> fDeclarationToMatch; |
| |
| private static class MultiMap<K, V> { |
| HashMap<K, Collection<V>> fImplementation= new HashMap<K, Collection<V>>(); |
| |
| public void put(K key, V value) { |
| Collection<V> collection= fImplementation.get(key); |
| if (collection == null) { |
| collection= new HashSet<V>(); |
| fImplementation.put(key, collection); |
| } |
| collection.add(value); |
| } |
| |
| public Collection<V> get(K key) { |
| return fImplementation.get(key); |
| } |
| } |
| private static class UnionFind { |
| HashMap<IType, IType> fElementToRepresentative= new HashMap<IType, IType>(); |
| |
| public void init(IType type) { |
| fElementToRepresentative.put(type, type); |
| } |
| |
| //path compression: |
| public IType find(IType element) { |
| IType root= element; |
| IType rep= fElementToRepresentative.get(root); |
| while (rep != null && ! rep.equals(root)) { |
| root= rep; |
| rep= fElementToRepresentative.get(root); |
| } |
| if (rep == null) |
| return null; |
| |
| rep= fElementToRepresentative.get(element); |
| while (! rep.equals(root)) { |
| IType temp= element; |
| element= rep; |
| fElementToRepresentative.put(temp, root); |
| rep= fElementToRepresentative.get(element); |
| } |
| return root; |
| } |
| |
| // //straightforward: |
| // public IType find(IType element) { |
| // IType current= element; |
| // IType rep= (IType) fElementToRepresentative.get(current); |
| // while (rep != null && ! rep.equals(current)) { |
| // current= rep; |
| // rep= (IType) fElementToRepresentative.get(current); |
| // } |
| // if (rep == null) |
| // return null; |
| // else |
| // return current; |
| // } |
| |
| public void union(IType rep1, IType rep2) { |
| fElementToRepresentative.put(rep1, rep2); |
| } |
| } |
| |
| |
| private RippleMethodFinder2(IMethod method, boolean excludeBinaries){ |
| fMethod= method; |
| fExcludeBinaries= excludeBinaries; |
| fBinaryRefs= null; |
| } |
| |
| private RippleMethodFinder2(IMethod method, ReferencesInBinaryContext binaryRefs) { |
| fMethod= method; |
| fExcludeBinaries= true; |
| fDeclarationToMatch= new HashMap<IMethod, SearchMatch>(); |
| fBinaryRefs= binaryRefs; |
| } |
| |
| public static IMethod[] getRelatedMethods(IMethod method, boolean excludeBinaries, IProgressMonitor pm, WorkingCopyOwner owner) throws CoreException { |
| try{ |
| if (! MethodChecks.isVirtual(method)) |
| return new IMethod[]{ method }; |
| |
| return new RippleMethodFinder2(method, excludeBinaries).getAllRippleMethods(pm, owner); |
| } finally{ |
| pm.done(); |
| } |
| } |
| public static IMethod[] getRelatedMethods(IMethod method, IProgressMonitor pm, WorkingCopyOwner owner) throws CoreException { |
| return getRelatedMethods(method, true, pm, owner); |
| } |
| |
| public static IMethod[] getRelatedMethods(IMethod method, ReferencesInBinaryContext binaryRefs, IProgressMonitor pm, WorkingCopyOwner owner) throws CoreException { |
| try { |
| if (! MethodChecks.isVirtual(method)) |
| return new IMethod[]{ method }; |
| |
| return new RippleMethodFinder2(method, binaryRefs).getAllRippleMethods(pm, owner); |
| } finally{ |
| pm.done(); |
| } |
| } |
| |
| private IMethod[] getAllRippleMethods(IProgressMonitor pm, WorkingCopyOwner owner) throws CoreException { |
| IMethod[] rippleMethods= findAllRippleMethods(pm, owner); |
| if (fDeclarationToMatch == null) |
| return rippleMethods; |
| |
| List<IMethod> rippleMethodsList= new ArrayList<IMethod>(Arrays.asList(rippleMethods)); |
| for (Iterator<IMethod> iter= rippleMethodsList.iterator(); iter.hasNext(); ) { |
| Object match= fDeclarationToMatch.get(iter.next()); |
| if (match != null) { |
| iter.remove(); |
| fBinaryRefs.add((SearchMatch) match); |
| } |
| } |
| fDeclarationToMatch= null; |
| return rippleMethodsList.toArray(new IMethod[rippleMethodsList.size()]); |
| } |
| |
| private IMethod[] findAllRippleMethods(IProgressMonitor pm, WorkingCopyOwner owner) throws CoreException { |
| pm.beginTask("", 4); //$NON-NLS-1$ |
| |
| findAllDeclarations(new SubProgressMonitor(pm, 1), owner); |
| |
| //TODO: report assertion as error status and fall back to only return fMethod |
| //check for bug 81058: |
| if (! fDeclarations.contains(fMethod)) |
| Assert.isTrue(false, "Search for method declaration did not find original element: " + fMethod.toString()); //$NON-NLS-1$ |
| |
| createHierarchyOfDeclarations(new SubProgressMonitor(pm, 1), owner); |
| createTypeToMethod(); |
| createUnionFind(); |
| if (pm.isCanceled()) |
| throw new OperationCanceledException(); |
| |
| fHierarchy= null; |
| fRootTypes= null; |
| |
| Map<IType, List<IType>> partitioning= new HashMap<IType, List<IType>>(); |
| for (Iterator<IType> iter= fTypeToMethod.keySet().iterator(); iter.hasNext();) { |
| IType type= iter.next(); |
| IType rep= fUnionFind.find(type); |
| List<IType> types= partitioning.get(rep); |
| if (types == null) |
| types= new ArrayList<IType>(); |
| types.add(type); |
| partitioning.put(rep, types); |
| } |
| Assert.isTrue(partitioning.size() > 0); |
| if (partitioning.size() == 1) |
| return fDeclarations.toArray(new IMethod[fDeclarations.size()]); |
| |
| //Multiple partitions; must look out for nasty marriage cases |
| //(types inheriting method from two ancestors, but without redeclaring it). |
| IType methodTypeRep= fUnionFind.find(fMethod.getDeclaringType()); |
| List<IType> relatedTypes= partitioning.get(methodTypeRep); |
| boolean hasRelatedInterfaces= false; |
| List<IMethod> relatedMethods= new ArrayList<IMethod>(); |
| for (Iterator<IType> iter= relatedTypes.iterator(); iter.hasNext();) { |
| IType relatedType= iter.next(); |
| relatedMethods.add(fTypeToMethod.get(relatedType)); |
| if (relatedType.isInterface()) |
| hasRelatedInterfaces= true; |
| } |
| |
| //Definition: An alien type is a type that is not a related type. The set of |
| // alien types diminishes as new types become related (a.k.a marry a relatedType). |
| |
| List<IMethod> alienDeclarations= new ArrayList<IMethod>(fDeclarations); |
| fDeclarations= null; |
| alienDeclarations.removeAll(relatedMethods); |
| List<IType> alienTypes= new ArrayList<IType>(); |
| boolean hasAlienInterfaces= false; |
| for (Iterator<IMethod> iter= alienDeclarations.iterator(); iter.hasNext();) { |
| IMethod alienDeclaration= iter.next(); |
| IType alienType= alienDeclaration.getDeclaringType(); |
| alienTypes.add(alienType); |
| if (alienType.isInterface()) |
| hasAlienInterfaces= true; |
| } |
| if (alienTypes.size() == 0) //no nasty marriage scenarios without types to marry with... |
| return relatedMethods.toArray(new IMethod[relatedMethods.size()]); |
| if (! hasRelatedInterfaces && ! hasAlienInterfaces) //no nasty marriage scenarios without interfaces... |
| return relatedMethods.toArray(new IMethod[relatedMethods.size()]); |
| |
| //find all subtypes of related types: |
| HashSet<IType> relatedSubTypes= new HashSet<IType>(); |
| List<IType> relatedTypesToProcess= new ArrayList<IType>(relatedTypes); |
| while (relatedTypesToProcess.size() > 0) { |
| //TODO: would only need subtype hierarchies of all top-of-ripple relatedTypesToProcess |
| for (Iterator<IType> iter= relatedTypesToProcess.iterator(); iter.hasNext();) { |
| if (pm.isCanceled()) |
| throw new OperationCanceledException(); |
| IType relatedType= iter.next(); |
| ITypeHierarchy hierarchy= getCachedHierarchy(relatedType, owner, new SubProgressMonitor(pm, 1)); |
| if (hierarchy == null) |
| hierarchy= relatedType.newTypeHierarchy(owner, new SubProgressMonitor(pm, 1)); |
| IType[] allSubTypes= hierarchy.getAllSubtypes(relatedType); |
| for (int i= 0; i < allSubTypes.length; i++) |
| relatedSubTypes.add(allSubTypes[i]); |
| } |
| relatedTypesToProcess.clear(); //processed; make sure loop terminates |
| |
| HashSet<IType> marriedAlienTypeReps= new HashSet<IType>(); |
| for (Iterator<IType> iter= alienTypes.iterator(); iter.hasNext();) { |
| if (pm.isCanceled()) |
| throw new OperationCanceledException(); |
| IType alienType= iter.next(); |
| IMethod alienMethod= fTypeToMethod.get(alienType); |
| ITypeHierarchy hierarchy= getCachedHierarchy(alienType, owner, new SubProgressMonitor(pm, 1)); |
| if (hierarchy == null) |
| hierarchy= alienType.newTypeHierarchy(owner, new SubProgressMonitor(pm, 1)); |
| IType[] allSubtypes= hierarchy.getAllSubtypes(alienType); |
| for (int i= 0; i < allSubtypes.length; i++) { |
| IType subtype= allSubtypes[i]; |
| if (relatedSubTypes.contains(subtype)) { |
| if (JavaModelUtil.isVisibleInHierarchy(alienMethod, subtype.getPackageFragment())) { |
| marriedAlienTypeReps.add(fUnionFind.find(alienType)); |
| } else { |
| // not overridden |
| } |
| } |
| } |
| } |
| |
| if (marriedAlienTypeReps.size() == 0) |
| return relatedMethods.toArray(new IMethod[relatedMethods.size()]); |
| |
| for (Iterator<IType> iter= marriedAlienTypeReps.iterator(); iter.hasNext();) { |
| IType marriedAlienTypeRep= iter.next(); |
| List<IType> marriedAlienTypes= partitioning.get(marriedAlienTypeRep); |
| for (Iterator<IType> iterator= marriedAlienTypes.iterator(); iterator.hasNext();) { |
| IType marriedAlienInterfaceType= iterator.next(); |
| relatedMethods.add(fTypeToMethod.get(marriedAlienInterfaceType)); |
| } |
| alienTypes.removeAll(marriedAlienTypes); //not alien any more |
| relatedTypesToProcess.addAll(marriedAlienTypes); //process freshly married types again |
| } |
| } |
| |
| fRootReps= null; |
| fRootHierarchies= null; |
| fTypeToMethod= null; |
| fUnionFind= null; |
| |
| return relatedMethods.toArray(new IMethod[relatedMethods.size()]); |
| } |
| |
| private ITypeHierarchy getCachedHierarchy(IType type, WorkingCopyOwner owner, IProgressMonitor monitor) throws JavaModelException { |
| IType rep= fUnionFind.find(type); |
| if (rep != null) { |
| Collection<IType> collection= fRootReps.get(rep); |
| for (Iterator<IType> iter= collection.iterator(); iter.hasNext();) { |
| IType root= iter.next(); |
| ITypeHierarchy hierarchy= fRootHierarchies.get(root); |
| if (hierarchy == null) { |
| hierarchy= root.newTypeHierarchy(owner, new SubProgressMonitor(monitor, 1)); |
| fRootHierarchies.put(root, hierarchy); |
| } |
| if (hierarchy.contains(type)) |
| return hierarchy; |
| } |
| } |
| return null; |
| } |
| |
| private void findAllDeclarations(IProgressMonitor monitor, WorkingCopyOwner owner) throws CoreException { |
| fDeclarations= new ArrayList<IMethod>(); |
| |
| class MethodRequestor extends SearchRequestor { |
| @Override |
| public void acceptSearchMatch(SearchMatch match) throws CoreException { |
| IMethod method= (IMethod) match.getElement(); |
| boolean isBinary= method.isBinary(); |
| if (fBinaryRefs != null || ! (fExcludeBinaries && isBinary)) { |
| fDeclarations.add(method); |
| } |
| if (isBinary && fBinaryRefs != null) { |
| fDeclarationToMatch.put(method, match); |
| } |
| } |
| } |
| |
| int limitTo = IJavaSearchConstants.DECLARATIONS | IJavaSearchConstants.IGNORE_DECLARING_TYPE | IJavaSearchConstants.IGNORE_RETURN_TYPE; |
| int matchRule= SearchPattern.R_ERASURE_MATCH | SearchPattern.R_CASE_SENSITIVE; |
| SearchPattern pattern= SearchPattern.createPattern(fMethod, limitTo, matchRule); |
| SearchParticipant[] participants= SearchUtils.getDefaultSearchParticipants(); |
| IJavaSearchScope scope= RefactoringScopeFactory.createRelatedProjectsScope(fMethod.getJavaProject(), IJavaSearchScope.SOURCES | IJavaSearchScope.APPLICATION_LIBRARIES | IJavaSearchScope.SYSTEM_LIBRARIES); |
| MethodRequestor requestor= new MethodRequestor(); |
| SearchEngine searchEngine= owner != null ? new SearchEngine(owner) : new SearchEngine(); |
| |
| searchEngine.search(pattern, participants, scope, requestor, monitor); |
| } |
| |
| private void createHierarchyOfDeclarations(IProgressMonitor pm, WorkingCopyOwner owner) throws JavaModelException { |
| IRegion region= JavaCore.newRegion(); |
| for (Iterator<IMethod> iter= fDeclarations.iterator(); iter.hasNext();) { |
| IType declaringType= iter.next().getDeclaringType(); |
| region.add(declaringType); |
| } |
| fHierarchy= JavaCore.newTypeHierarchy(region, owner, pm); |
| } |
| |
| private void createTypeToMethod() { |
| fTypeToMethod= new HashMap<IType, IMethod>(); |
| for (Iterator<IMethod> iter= fDeclarations.iterator(); iter.hasNext();) { |
| IMethod declaration= iter.next(); |
| fTypeToMethod.put(declaration.getDeclaringType(), declaration); |
| } |
| } |
| |
| private void createUnionFind() throws JavaModelException { |
| fRootTypes= new HashSet<IType>(fTypeToMethod.keySet()); |
| fUnionFind= new UnionFind(); |
| for (Iterator<IType> iter= fTypeToMethod.keySet().iterator(); iter.hasNext();) { |
| IType type= iter.next(); |
| fUnionFind.init(type); |
| } |
| for (Iterator<IType> iter= fTypeToMethod.keySet().iterator(); iter.hasNext();) { |
| IType type= iter.next(); |
| uniteWithSupertypes(type, type); |
| } |
| fRootReps= new MultiMap<IType, IType>(); |
| for (Iterator<IType> iter= fRootTypes.iterator(); iter.hasNext();) { |
| IType type= iter.next(); |
| IType rep= fUnionFind.find(type); |
| if (rep != null) |
| fRootReps.put(rep, type); |
| } |
| fRootHierarchies= new HashMap<IType, ITypeHierarchy>(); |
| } |
| |
| private void uniteWithSupertypes(IType anchor, IType type) throws JavaModelException { |
| IType[] supertypes= fHierarchy.getSupertypes(type); |
| for (int i= 0; i < supertypes.length; i++) { |
| IType supertype= supertypes[i]; |
| IType superRep= fUnionFind.find(supertype); |
| if (superRep == null) { |
| //Type doesn't declare method, but maybe supertypes? |
| uniteWithSupertypes(anchor, supertype); |
| } else { |
| //check whether method in supertype is really overridden: |
| IMember superMethod= fTypeToMethod.get(supertype); |
| if (JavaModelUtil.isVisibleInHierarchy(superMethod, anchor.getPackageFragment())) { |
| IType rep= fUnionFind.find(anchor); |
| fUnionFind.union(rep, superRep); |
| // current type is no root anymore |
| fRootTypes.remove(anchor); |
| uniteWithSupertypes(supertype, supertype); |
| } else { |
| //Not overridden -> overriding chain ends here. |
| } |
| } |
| } |
| } |
| } |