blob: f6d90648a9b490a2c923dc8a60318fdbf8f24f7b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2015 Wind River Systems, Inc.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Markus Schorn - initial API and implementation
* Sergey Prigogin (Google)
*******************************************************************************/
package org.eclipse.cdt.internal.ui.refactoring.rename;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.cdt.core.model.CoreModel;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.internal.formatter.scanner.SimpleScanner;
import org.eclipse.cdt.internal.formatter.scanner.Token;
import org.eclipse.cdt.utils.PathUtil;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceProxy;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.search.core.text.TextSearchEngine;
import org.eclipse.search.core.text.TextSearchMatchAccess;
import org.eclipse.search.core.text.TextSearchRequestor;
import org.eclipse.search.core.text.TextSearchScope;
import org.eclipse.ui.IWorkingSet;
import org.eclipse.ui.PlatformUI;
/**
* Wraps the platform text search and uses a scanner to categorize the text-matches
* by location (comments, string-literals, etc.).
*/
public class TextSearchWrapper {
public final static int SCOPE_FILE = 1;
public final static int SCOPE_WORKSPACE = 2;
public final static int SCOPE_RELATED_PROJECTS = 3;
public final static int SCOPE_SINGLE_PROJECT = 4;
public final static int SCOPE_WORKING_SET = 5;
private static class SearchScope extends TextSearchScope {
public static SearchScope newSearchScope(IFile[] files, IWorkingSet ws) {
IAdaptable[] adaptables = ws.getElements();
ArrayList<IResource> resources = new ArrayList<>();
for (int i = 0; i < adaptables.length; i++) {
IAdaptable adaptable = adaptables[i];
IResource resource = adaptable.getAdapter(IResource.class);
if (resource != null) {
resources.add(resource);
}
}
return newSearchScope(files, resources.toArray(new IResource[resources.size()]));
}
public static SearchScope newSearchScope(IFile[] files, IResource[] roots) {
if (files != null) {
ArrayList<IResource> resources = new ArrayList<>(files.length + roots.length);
for (IFile file : files) {
if (!isInForest(file, roots)) {
resources.add(file);
}
}
Collections.addAll(resources, roots);
roots = resources.toArray(new IResource[resources.size()]);
}
return new SearchScope(roots);
}
/**
* Checks is a file belongs to one of the given containers.
*/
private static boolean isInForest(IResource file, IResource[] roots) {
IPath filePath = file.getFullPath();
for (IResource root : roots) {
if (PathUtil.isPrefix(root.getFullPath(), filePath)) {
return true;
}
}
return false;
}
private IResource[] fRootResources;
private ArrayList<Matcher> fFileMatcher = new ArrayList<>();
private SearchScope(IResource[] roots) {
fRootResources = roots;
}
@Override
public IResource[] getRoots() {
return fRootResources;
}
@Override
public boolean contains(IResourceProxy proxy) {
if (proxy.isDerived()) {
return false;
}
if (proxy.getType() == IResource.FILE) {
return containsFile(proxy.getName());
}
return true;
}
private boolean containsFile(String name) {
for (Iterator<Matcher> iter = fFileMatcher.iterator(); iter.hasNext();) {
Matcher matcher = iter.next();
matcher.reset(name);
if (matcher.matches()) {
return true;
}
}
return false;
}
public void addFileNamePattern(String filePattern) {
Pattern p = Pattern.compile(filePatternToRegex(filePattern));
fFileMatcher.add(p.matcher("")); //$NON-NLS-1$
}
private String filePatternToRegex(String filePattern) {
StringBuilder result = new StringBuilder();
for (int i = 0; i < filePattern.length(); i++) {
char c = filePattern.charAt(i);
switch (c) {
case '\\':
case '(':
case ')':
case '{':
case '}':
case '.':
case '[':
case ']':
case '$':
case '^':
case '+':
case '|':
result.append('\\');
result.append(c);
break;
case '?':
result.append('.');
break;
case '*':
result.append(".*"); //$NON-NLS-1$
break;
default:
result.append(c);
break;
}
}
return result.toString();
}
}
public TextSearchWrapper() {
}
private TextSearchScope createSearchScope(IFile[] files, int scope, IFile file, String workingSetName,
String[] patterns) {
switch (scope) {
case SCOPE_WORKSPACE:
return defineSearchScope(files, file.getWorkspace().getRoot(), patterns);
case SCOPE_SINGLE_PROJECT:
return defineSearchScope(files, file.getProject(), patterns);
case SCOPE_FILE:
return defineSearchScope(files, file, patterns);
case SCOPE_WORKING_SET: {
return defineWorkingSetAsSearchScope(files, workingSetName, patterns);
}
}
return defineRelatedProjectsAsSearchScope(files, file.getProject(), patterns);
}
private TextSearchScope defineRelatedProjectsAsSearchScope(IFile[] files, IProject project, String[] patterns) {
HashSet<IProject> projects = new HashSet<>();
LinkedList<IProject> workThrough = new LinkedList<>();
workThrough.add(project);
while (!workThrough.isEmpty()) {
IProject proj = workThrough.removeLast();
if (projects.add(proj)) {
try {
workThrough.addAll(Arrays.asList(proj.getReferencedProjects()));
workThrough.addAll(Arrays.asList(proj.getReferencingProjects()));
} catch (CoreException e) {
// need to ignore
}
}
}
IResource[] roots = projects.toArray(new IResource[projects.size()]);
return defineSearchScope(files, roots, patterns);
}
private TextSearchScope defineWorkingSetAsSearchScope(IFile[] files, String workingSetName, String[] patterns) {
IWorkingSet workingSet = workingSetName != null
? PlatformUI.getWorkbench().getWorkingSetManager().getWorkingSet(workingSetName)
: null;
SearchScope result = workingSet != null ? SearchScope.newSearchScope(files, workingSet)
: SearchScope.newSearchScope(files, new IResource[0]);
applyFilePatterns(result, patterns);
return result;
}
private void applyFilePatterns(SearchScope scope, String[] patterns) {
for (String pattern : patterns) {
scope.addFileNamePattern(pattern);
}
}
private TextSearchScope defineSearchScope(IFile[] files, IResource root, String[] patterns) {
SearchScope result = SearchScope.newSearchScope(files, new IResource[] { root });
applyFilePatterns(result, patterns);
return result;
}
private TextSearchScope defineSearchScope(IFile[] files, IResource[] roots, String[] patterns) {
SearchScope result = SearchScope.newSearchScope(files, roots);
applyFilePatterns(result, patterns);
return result;
}
/**
* Searches for a given word.
*
* @param filesToSearch The files to search.
* @param scope Together with {@code file} and {@code workingSet} defines set of additional
* file to search. One of SCOPE_FILE, SCOPE_WORKSPACE, SCOPE_RELATED_PROJECTS,
* SCOPE_SINGLE_PROJECT, or SCOPE_WORKING_SET.
* @param scopeAnchor The file used as an anchor for the scope.
* @param workingSet The name of a working set. Ignored if {@code scope} is not
* SCOPE_WORKING_SET.
* @param patterns File name patterns.
* @param word The word to search for.
* @param monitor A progress monitor.
* @param target The list that gets populated with search results.
*/
public IStatus searchWord(IFile[] filesToSearch, int scope, IFile scopeAnchor, String workingSet, String[] patterns,
String word, IProgressMonitor monitor, final List<CRefactoringMatch> target) {
SubMonitor subMonitor = SubMonitor.convert(monitor, 100);
int startPos = target.size();
TextSearchEngine engine = TextSearchEngine.create();
StringBuilder searchPattern = new StringBuilder(word.length() + 8);
searchPattern.append("\\b"); //$NON-NLS-1$
searchPattern.append("\\Q"); //$NON-NLS-1$
searchPattern.append(word);
searchPattern.append("\\E"); //$NON-NLS-1$
searchPattern.append("\\b"); //$NON-NLS-1$
Pattern pattern = Pattern.compile(searchPattern.toString());
TextSearchScope searchscope = createSearchScope(filesToSearch, scope, scopeAnchor, workingSet, patterns);
TextSearchRequestor requestor = new TextSearchRequestor() {
@Override
public boolean acceptPatternMatch(TextSearchMatchAccess access) {
IFile file = access.getFile();
ICElement elem = CoreModel.getDefault().create(file);
if (elem instanceof ITranslationUnit) {
target.add(new CRefactoringMatch(file, access.getMatchOffset(), access.getMatchLength(), 0));
}
return true;
}
};
IStatus result = engine.search(searchscope, requestor, pattern, subMonitor.split(95));
categorizeMatches(target.subList(startPos, target.size()), subMonitor.split(5));
return result;
}
public void categorizeMatches(List<CRefactoringMatch> matches, IProgressMonitor monitor) {
SubMonitor subMonitor = SubMonitor.convert(monitor, RenameMessages.TextSearch_monitor_categorizeMatches,
matches.size());
IFile file = null;
ArrayList<int[]> locations = null;
for (Iterator<CRefactoringMatch> iter = matches.iterator(); iter.hasNext();) {
CRefactoringMatch match = iter.next();
IFile tfile = match.getFile();
if (file == null || !file.equals(tfile)) {
file = tfile;
locations = new ArrayList<>();
computeLocations(file, locations);
}
match.setLocation(findLocation(match, locations));
subMonitor.worked(1);
}
}
final static Comparator<int[]> COMPARE_FIRST_INTEGER = new Comparator<>() {
@Override
public int compare(int[] o1, int[] o2) {
return (o1)[0] - (o2)[0];
}
};
private int findLocation(CRefactoringMatch match, ArrayList<int[]> states) {
int pos = Collections.binarySearch(states, new int[] { match.getOffset() }, COMPARE_FIRST_INTEGER);
if (pos < 0) {
pos = -pos - 2;
if (pos < 0) {
pos = 0;
}
}
int endOffset = match.getOffset() + match.getLength();
int location = 0;
while (pos < states.size()) {
int[] info = states.get(pos);
if (info[0] >= endOffset) {
break;
}
location |= info[1];
pos++;
}
return location;
}
private void computeLocations(IFile file, ArrayList<int[]> locations) {
Reader reader;
SimpleScanner scanner = new SimpleScanner();
try {
reader = new BufferedReader(new InputStreamReader(file.getContents(), file.getCharset()));
} catch (CoreException e) {
return;
} catch (UnsupportedEncodingException e) {
return;
}
try {
scanner.initialize(reader, null);
scanner.setReuseToken(true);
Token token;
int lastState = 0;
while ((token = scanner.nextToken()) != null) {
int state = CRefactory.OPTION_IN_CODE_REFERENCES;
switch (token.getType()) {
case Token.tLINECOMMENT:
case Token.tBLOCKCOMMENT:
state = CRefactory.OPTION_IN_COMMENT;
break;
case Token.tSTRING:
case Token.tLSTRING:
case Token.tCHAR:
state = CRefactory.OPTION_IN_STRING_LITERAL;
break;
case Token.tPREPROCESSOR:
state = CRefactory.OPTION_IN_PREPROCESSOR_DIRECTIVE;
break;
case Token.tPREPROCESSOR_DEFINE:
state = CRefactory.OPTION_IN_MACRO_DEFINITION;
break;
case Token.tPREPROCESSOR_INCLUDE:
state = CRefactory.OPTION_IN_INCLUDE_DIRECTIVE;
break;
}
if (state != lastState) {
locations.add(new int[] { token.getOffset(), state });
lastState = state;
}
}
} finally {
try {
reader.close();
} catch (IOException e) {
}
}
}
}