blob: 881d9d88c3046c40ffb0ffc78545148bef687848 [file] [log] [blame]
/*******************************************************************************
* 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;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
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.NullProgressMonitor;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.ltk.core.refactoring.RefactoringStatusEntry;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.WorkingCopyOwner;
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.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
import org.eclipse.jdt.internal.corext.util.Messages;
import org.eclipse.jdt.internal.corext.util.SearchUtils;
import org.eclipse.jdt.internal.ui.viewsupport.BasicElementLabels;
/**
* Helper class to use the search engine in refactorings.
*
* TODO:
* - is inefficient: uses sets instead of lists, creates useless intermediate collections
* - destroys locality by doing multiple passes over result sets instead of processing results in a pipeline
* - does not allow users to modify search matches
* - generates boilerplate error messages and has no way to configure them
*
* @since 3.1
*/
public final class RefactoringSearchEngine2 {
/** Default implementation of a search requestor */
private static class DefaultSearchRequestor implements IRefactoringSearchRequestor {
public final SearchMatch acceptSearchMatch(final SearchMatch match) {
return match;
}
}
/** Search requestor which only collects compilation units */
private class RefactoringCompilationUnitCollector extends RefactoringSearchCollector {
/** The collected compilation units */
private final Set<ICompilationUnit> fCollectedUnits= new HashSet<ICompilationUnit>();
/** The inaccurate matches */
private final Set<SearchMatch> fInaccurateMatches= new HashSet<SearchMatch>();
@Override
public final void acceptSearchMatch(final SearchMatch match) throws CoreException {
final SearchMatch accepted= fRequestor.acceptSearchMatch(match);
if (accepted != null) {
final IResource resource= accepted.getResource();
if (!resource.equals(fLastResource)) {
final IJavaElement element= JavaCore.create(resource);
if (element instanceof ICompilationUnit)
fCollectedUnits.add((ICompilationUnit) element);
}
if (fInaccurate && accepted.getAccuracy() == SearchMatch.A_INACCURATE && !fInaccurateMatches.contains(accepted)) {
fStatus.addEntry(fSeverity, Messages.format(RefactoringCoreMessages.RefactoringSearchEngine_inaccurate_match, BasicElementLabels.getResourceName(accepted.getResource())), null, null, RefactoringStatusEntry.NO_CODE);
fInaccurateMatches.add(accepted);
}
}
}
@Override
public final void clearResults() {
super.clearResults();
fCollectedUnits.clear();
fInaccurateMatches.clear();
}
@Override
public final Collection<IResource> getBinaryResources() {
return Collections.emptySet();
}
@Override
public final Collection<ICompilationUnit> getCollectedMatches() {
return fCollectedUnits;
}
@Override
public final Collection<SearchMatch> getInaccurateMatches() {
return fInaccurateMatches;
}
}
private abstract class RefactoringSearchCollector extends SearchRequestor {
protected IResource fLastResource= null;
public void clearResults() {
fLastResource= null;
}
public abstract Collection<IResource> getBinaryResources();
public abstract Collection<?> getCollectedMatches();
public abstract Collection<SearchMatch> getInaccurateMatches();
}
/** Search requestor which collects every search match */
private class RefactoringSearchMatchCollector extends RefactoringSearchCollector {
/** The binary resources */
private final Set<IResource> fBinaryResources= new HashSet<IResource>();
/** The collected matches */
private final List<SearchMatch> fCollectedMatches= new ArrayList<SearchMatch>();
/** The inaccurate matches */
private final Set<SearchMatch> fInaccurateMatches= new HashSet<SearchMatch>();
@Override
public final void acceptSearchMatch(final SearchMatch match) throws CoreException {
final SearchMatch accepted= fRequestor.acceptSearchMatch(match);
if (accepted != null) {
fCollectedMatches.add(accepted);
final IResource resource= accepted.getResource();
if (!resource.equals(fLastResource)) {
if (fBinary) {
final IJavaElement element= JavaCore.create(resource);
if (!(element instanceof ICompilationUnit)) {
final IProject project= resource.getProject();
if (!fGrouping)
fStatus.addEntry(fSeverity, Messages.format(RefactoringCoreMessages.RefactoringSearchEngine_binary_match_ungrouped, BasicElementLabels.getResourceName(project)), null, null, RefactoringStatusEntry.NO_CODE);
else if (!fBinaryResources.contains(resource))
fStatus.addEntry(fSeverity, Messages.format(RefactoringCoreMessages.RefactoringSearchEngine_binary_match_grouped, BasicElementLabels.getResourceName(project)), null, null, RefactoringStatusEntry.NO_CODE);
fBinaryResources.add(resource);
}
}
if (fInaccurate && accepted.getAccuracy() == SearchMatch.A_INACCURATE && !fInaccurateMatches.contains(accepted)) {
fStatus.addEntry(fSeverity, Messages.format(RefactoringCoreMessages.RefactoringSearchEngine_inaccurate_match, BasicElementLabels.getResourceName(resource)), null, null, RefactoringStatusEntry.NO_CODE);
fInaccurateMatches.add(accepted);
}
}
}
}
@Override
public final void clearResults() {
super.clearResults();
fCollectedMatches.clear();
fInaccurateMatches.clear();
fBinaryResources.clear();
}
@Override
public final Collection<IResource> getBinaryResources() {
return fBinaryResources;
}
@Override
public final Collection<SearchMatch> getCollectedMatches() {
return fCollectedMatches;
}
@Override
public final Collection<SearchMatch> getInaccurateMatches() {
return fInaccurateMatches;
}
}
/** The compilation unit granularity */
public static final int GRANULARITY_COMPILATION_UNIT= 2;
/** The search match granularity */
public static final int GRANULARITY_SEARCH_MATCH= 1;
/** Should binary matches be filtered? */
private boolean fBinary= false;
/** The refactoring search collector */
private RefactoringSearchCollector fCollector= null;
/** The search granularity */
private int fGranularity= GRANULARITY_SEARCH_MATCH;
/** Should the matches be grouped by resource? */
private boolean fGrouping= true;
/** Should inaccurate matches be filtered? */
private boolean fInaccurate= true;
/** The working copy owner, or <code>null</code> */
private WorkingCopyOwner fOwner= null;
/** The search pattern, or <code>null</code> */
private SearchPattern fPattern= null;
/** The search requestor */
private IRefactoringSearchRequestor fRequestor= new DefaultSearchRequestor();
/** The search scope */
private IJavaSearchScope fScope= SearchEngine.createWorkspaceScope();
/** The severity */
private int fSeverity= RefactoringStatus.WARNING;
/** The search status */
private RefactoringStatus fStatus= new RefactoringStatus();
/** The working copies */
private ICompilationUnit[] fWorkingCopies= {};
/**
* Creates a new refactoring search engine.
*/
public RefactoringSearchEngine2() {
// Do nothing
}
/**
* Creates a new refactoring search engine.
*
* @param pattern the search pattern
*/
public RefactoringSearchEngine2(final SearchPattern pattern) {
Assert.isNotNull(pattern);
fPattern= pattern;
}
/**
* Clears all results found so far, and sets resets the status to {@link RefactoringStatus#OK}.
*/
public final void clearResults() {
getCollector().clearResults();
fStatus= new RefactoringStatus();
}
/**
* Returns the affected compilation units of the previous search queries.
* <p>
* In order to retrieve the compilation units, grouping by resource must have been enabled before searching.
*
* @return the compilation units of the previous queries
*/
public final ICompilationUnit[] getAffectedCompilationUnits() {
if (fGranularity == GRANULARITY_COMPILATION_UNIT) {
final Collection<?> collection= getCollector().getCollectedMatches();
final ICompilationUnit[] units= new ICompilationUnit[collection.size()];
int index= 0;
for (final Iterator<?> iterator= collection.iterator(); iterator.hasNext(); index++)
units[index]= (ICompilationUnit) iterator.next();
return units;
} else {
final SearchResultGroup[] groups= getGroupedMatches();
final ICompilationUnit[] units= new ICompilationUnit[groups.length];
for (int index= 0; index < groups.length; index++)
units[index]= groups[index].getCompilationUnit();
return units;
}
}
/**
* Returns the affected java projects of the previous search queries.
* <p>
* In order to retrieve the java projects, grouping by resource must have been enabled before searching.
*
* @return the java projects of the previous queries (element type:
* <code>&lt;IJavaProject, Collection&lt;SearchResultGroup&gt;&gt;</code> if granularity is {@link #GRANULARITY_SEARCH_MATCH} or
* <code>&lt;IJavaProject, Collection&lt;ICompilationUnit&gt;&gt;</code> if it is {@link #GRANULARITY_COMPILATION_UNIT}).
*/
public final Map<IJavaProject, ? extends Set<?>> getAffectedProjects() {
IJavaProject project= null;
ICompilationUnit unit= null;
if (fGranularity == GRANULARITY_COMPILATION_UNIT) {
final Map<IJavaProject, Set<ICompilationUnit>> map= new HashMap<IJavaProject, Set<ICompilationUnit>>();
final ICompilationUnit[] units= getAffectedCompilationUnits();
for (int index= 0; index < units.length; index++) {
unit= units[index];
project= unit.getJavaProject();
if (project != null) {
Set<ICompilationUnit> set= map.get(project);
if (set == null) {
set= new HashSet<ICompilationUnit>();
map.put(project, set);
}
set.add(unit);
}
}
return map;
} else {
final Map<IJavaProject, Set<SearchResultGroup>> map= new HashMap<IJavaProject, Set<SearchResultGroup>>();
final SearchResultGroup[] groups= getGroupedMatches();
SearchResultGroup group= null;
for (int index= 0; index < groups.length; index++) {
group= groups[index];
unit= group.getCompilationUnit();
if (unit != null) {
project= unit.getJavaProject();
if (project != null) {
Set<SearchResultGroup> set= map.get(project);
if (set == null) {
set= new HashSet<SearchResultGroup>();
map.put(project, set);
}
set.add(group);
}
}
}
return map;
}
}
/**
* Returns the refactoring search collector.
*
* @return the refactoring search collector
*/
private RefactoringSearchCollector getCollector() {
if (fCollector == null) {
if (fGranularity == GRANULARITY_COMPILATION_UNIT)
fCollector= new RefactoringCompilationUnitCollector();
else if (fGranularity == GRANULARITY_SEARCH_MATCH)
fCollector= new RefactoringSearchMatchCollector();
else
Assert.isTrue(false);
}
return fCollector;
}
/**
* Returns the found search matches in grouped by their containing resource.
*
* @return the found search matches
*/
private SearchResultGroup[] getGroupedMatches() {
final Map<IResource, List<SearchMatch>> grouped= new HashMap<IResource, List<SearchMatch>>();
List<SearchMatch> matches= null;
IResource resource= null;
SearchMatch match= null;
for (final Iterator<?> iterator= getSearchMatches().iterator(); iterator.hasNext();) {
match= (SearchMatch) iterator.next();
resource= match.getResource();
if (!grouped.containsKey(resource))
grouped.put(resource, new ArrayList<SearchMatch>(4));
matches= grouped.get(resource);
matches.add(match);
}
if (fBinary) {
final Collection<IResource> collection= getCollector().getBinaryResources();
for (final Iterator<IResource> iterator= grouped.keySet().iterator(); iterator.hasNext();) {
resource= iterator.next();
if (collection.contains(resource))
iterator.remove();
}
}
final SearchResultGroup[] result= new SearchResultGroup[grouped.keySet().size()];
int index= 0;
for (final Iterator<IResource> iterator= grouped.keySet().iterator(); iterator.hasNext();) {
resource= iterator.next();
matches= grouped.get(resource);
result[index++]= new SearchResultGroup(resource, matches.toArray(new SearchMatch[matches.size()]));
}
return result;
}
/**
* Returns the search pattern currently used for searching.
*
* @return the search pattern
*/
public final SearchPattern getPattern() {
return fPattern;
}
/**
* Returns the results of the previous search queries.
* <p>
* The result depends on the following conditions:
* <ul>
* <li>If the search granularity is {@link #GRANULARITY_COMPILATION_UNIT}, the results are elements of type {@link ICompilationUnit}.</li>
* <li>If grouping by resource is enabled, the results are elements of type {@link SearchResultGroup}, otherwise the elements are of type {@link SearchMatch}.</li>
* </ul>
*
* @return the results of the previous queries
*/
public final Object[] getResults() {
if (fGranularity == GRANULARITY_COMPILATION_UNIT)
return getAffectedCompilationUnits();
else {
if (fGrouping)
return getGroupedMatches();
else
return getUngroupedMatches();
}
}
/**
* Returns the search matches filtered by their accuracy.
*
* @return the filtered search matches
*/
private Collection<?> getSearchMatches() {
Collection<?> results= null;
if (fInaccurate) {
results= new LinkedList<Object>(getCollector().getCollectedMatches());
final Collection<SearchMatch> collection= getCollector().getInaccurateMatches();
SearchMatch match= null;
for (final Iterator<?> iterator= results.iterator(); iterator.hasNext();) {
match= (SearchMatch) iterator.next();
if (collection.contains(match))
iterator.remove();
}
} else
results= getCollector().getCollectedMatches();
return results;
}
/**
* Returns the refactoring status of this search engine.
*
* @return the refactoring status
*/
public final RefactoringStatus getStatus() {
return fStatus;
}
/**
* Returns the found search matches in no particular order.
*
* @return the found search matches
*/
private SearchMatch[] getUngroupedMatches() {
Collection<?> results= null;
if (fBinary) {
results= new LinkedList<Object>(getSearchMatches());
final Collection<IResource> collection= getCollector().getBinaryResources();
SearchMatch match= null;
for (final Iterator<?> iterator= results.iterator(); iterator.hasNext();) {
match= (SearchMatch) iterator.next();
if (collection.contains(match.getResource()))
iterator.remove();
}
} else
results= getSearchMatches();
final SearchMatch[] matches= new SearchMatch[results.size()];
results.toArray(matches);
return matches;
}
/**
* Performs the search according to the specified pattern.
*
* @param monitor the progress monitor, or <code>null</code>
* @throws JavaModelException if an error occurs during search
*/
public final void searchPattern(IProgressMonitor monitor) throws JavaModelException {
Assert.isNotNull(fPattern);
if (monitor == null)
monitor= new NullProgressMonitor();
try {
monitor.beginTask("", 1); //$NON-NLS-1$
monitor.setTaskName(RefactoringCoreMessages.RefactoringSearchEngine_searching_occurrences);
try {
SearchEngine engine= null;
if (fOwner != null)
engine= new SearchEngine(fOwner);
else
engine= new SearchEngine(fWorkingCopies);
engine.search(fPattern, SearchUtils.getDefaultSearchParticipants(), fScope, getCollector(), new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL));
} catch (CoreException exception) {
throw new JavaModelException(exception);
}
} finally {
monitor.done();
}
}
/**
* Performs the search of referenced fields.
*
* @param element the java element whose referenced fields have to be found
* @param monitor the progress monitor, or <code>null</code>
* @throws JavaModelException if an error occurs during search
*/
public final void searchReferencedFields(final IJavaElement element, IProgressMonitor monitor) throws JavaModelException {
Assert.isNotNull(element);
if (monitor == null)
monitor= new NullProgressMonitor();
try {
monitor.beginTask("", 1); //$NON-NLS-1$
monitor.setTaskName(RefactoringCoreMessages.RefactoringSearchEngine_searching_referenced_fields);
try {
SearchEngine engine= null;
if (fOwner != null)
engine= new SearchEngine(fOwner);
else
engine= new SearchEngine(fWorkingCopies);
engine.searchDeclarationsOfAccessedFields(element, getCollector(), new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL));
} catch (CoreException exception) {
throw new JavaModelException(exception);
}
} finally {
monitor.done();
}
}
/**
* Performs the search of referenced methods.
*
* @param element the java element whose referenced methods have to be found
* @param monitor the progress monitor, or <code>null</code>
* @throws JavaModelException if an error occurs during search
*/
public final void searchReferencedMethods(final IJavaElement element, IProgressMonitor monitor) throws JavaModelException {
Assert.isNotNull(element);
if (monitor == null)
monitor= new NullProgressMonitor();
try {
monitor.beginTask("", 1); //$NON-NLS-1$
monitor.setTaskName(RefactoringCoreMessages.RefactoringSearchEngine_searching_referenced_methods);
try {
SearchEngine engine= null;
if (fOwner != null)
engine= new SearchEngine(fOwner);
else
engine= new SearchEngine(fWorkingCopies);
engine.searchDeclarationsOfSentMessages(element, getCollector(), new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL));
} catch (CoreException exception) {
throw new JavaModelException(exception);
}
} finally {
monitor.done();
}
}
/**
* Performs the search of referenced types.
*
* @param element the java element whose referenced types have to be found
* @param monitor the progress monitor, or <code>null</code>
* @throws JavaModelException if an error occurs during search
*/
public final void searchReferencedTypes(final IJavaElement element, IProgressMonitor monitor) throws JavaModelException {
Assert.isNotNull(element);
if (monitor == null)
monitor= new NullProgressMonitor();
try {
monitor.beginTask("", 1); //$NON-NLS-1$
monitor.setTaskName(RefactoringCoreMessages.RefactoringSearchEngine_searching_referenced_types);
try {
SearchEngine engine= null;
if (fOwner != null)
engine= new SearchEngine(fOwner);
else
engine= new SearchEngine(fWorkingCopies);
engine.searchDeclarationsOfReferencedTypes(element, getCollector(), new SubProgressMonitor(monitor, 1, SubProgressMonitor.SUPPRESS_SUBTASK_LABEL));
} catch (CoreException exception) {
throw new JavaModelException(exception);
}
} finally {
monitor.done();
}
}
/**
* Determines how search matches are filtered.
* <p>
* This method must be called before start searching. The default is to filter inaccurate matches only.
*
* @param inaccurate <code>true</code> to filter inaccurate matches, <code>false</code> otherwise
* @param binary <code>true</code> to filter binary matches, <code>false</code> otherwise
*/
public final void setFiltering(final boolean inaccurate, final boolean binary) {
fInaccurate= inaccurate;
fBinary= binary;
}
/**
* Sets the granularity to use during the searches.
* <p>
* This method must be called before start searching. The default is a granularity of {@link #GRANULARITY_SEARCH_MATCH}.
*
* @param granularity The granularity to use. Must be one of the <code>GRANULARITY_XXX</code> constants.
*/
public final void setGranularity(final int granularity) {
Assert.isTrue(granularity == GRANULARITY_COMPILATION_UNIT || granularity == GRANULARITY_SEARCH_MATCH);
fGranularity= granularity;
}
/**
* Sets the working copies to take precedence during the searches.
* <p>
* This method must be called before start searching. The default is to use no working copies
*
* @param copies the working copies to use
*/
public final void setWorkingCopies(final ICompilationUnit[] copies) {
Assert.isNotNull(copies);
fWorkingCopies= new ICompilationUnit[copies.length];
System.arraycopy(copies, 0, fWorkingCopies, 0, copies.length);
}
/**
* Determines how search matches are grouped.
* <p>
* This method must be called before start searching. The default is to group by containing resource.
*
* @param grouping <code>true</code> to group matches by their containing resource, <code>false</code> otherwise
*/
public final void setGrouping(final boolean grouping) {
fGrouping= grouping;
}
/**
* Sets the disjunction of search patterns to be used during search.
* <p>
* This method must be called before {@link RefactoringSearchEngine2#searchPattern(IProgressMonitor)}
*
* @param first the first search pattern to set
* @param second the second search pattern to set
*/
public final void setOrPattern(final SearchPattern first, final SearchPattern second) {
Assert.isNotNull(first);
Assert.isNotNull(second);
fPattern= SearchPattern.createOrPattern(first, second);
}
/**
* Sets the working copy owner to use during search.
* <p>
* This method must be called before start searching. The default is to use no working copy owner.
*
* @param owner the working copy owner to use, or <code>null</code> to use none
*/
public final void setOwner(final WorkingCopyOwner owner) {
fOwner= owner;
}
/**
* Sets the search pattern to be used during search.
* <p>
* This method must be called before {@link RefactoringSearchEngine2#searchPattern(IProgressMonitor)}
*
* @param elements the set of elements
* @param limitTo determines the nature of the expected matches. This is a combination of {@link org.eclipse.jdt.core.search.IJavaSearchConstants}.
*/
public final void setPattern(final IJavaElement[] elements, final int limitTo) {
Assert.isNotNull(elements);
Assert.isTrue(elements.length > 0);
SearchPattern pattern= SearchPattern.createPattern(elements[0], limitTo, SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE);
IJavaElement element= null;
for (int index= 1; index < elements.length; index++) {
element= elements[index];
pattern= SearchPattern.createOrPattern(pattern, SearchPattern.createPattern(element, limitTo, SearchUtils.GENERICS_AGNOSTIC_MATCH_RULE));
}
setPattern(pattern);
}
/**
* Sets the search pattern to be used during search.
* <p>
* This method must be called before {@link RefactoringSearchEngine2#searchPattern(IProgressMonitor)}
*
* @param pattern the search pattern to set
*/
public final void setPattern(final SearchPattern pattern) {
Assert.isNotNull(pattern);
fPattern= pattern;
}
/**
* Sets the search requestor for this search engine.
* <p>
* This method must be called before start searching. The default is a non-filtering search requestor.
*
* @param requestor the search requestor to set
*/
public final void setRequestor(final IRefactoringSearchRequestor requestor) {
Assert.isNotNull(requestor);
fRequestor= requestor;
}
/**
* Sets the search scope for this search engine.
* <p>
* This method must be called before start searching. The default is the entire workspace as search scope.
*
* @param scope the search scope to set
*/
public final void setScope(final IJavaSearchScope scope) {
Assert.isNotNull(scope);
fScope= scope;
}
/**
* Sets the severity of the generated status entries.
* <p>
* This method must be called before start searching. The default is a severity of {@link RefactoringStatus#OK}.
*
* @param severity the severity to set
*/
public final void setSeverity(final int severity) {
Assert.isTrue(severity == RefactoringStatus.WARNING || severity == RefactoringStatus.INFO || severity == RefactoringStatus.FATAL || severity == RefactoringStatus.ERROR);
fSeverity= severity;
}
/**
* Sets the refactoring status for this search engine.
* <p>
* This method must be called before start searching. The default is an empty status with status {@link RefactoringStatus#OK}.
*
* @param status the refactoring status to set
*/
public final void setStatus(final RefactoringStatus status) {
Assert.isNotNull(status);
fStatus= status;
}
}