/******************************************************************************* * Copyright (c) 2004, 2008 Wind River Systems, Inc. * 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: * 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.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.SubProgressMonitor; 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; 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.utils.PathUtil; import org.eclipse.cdt.internal.formatter.scanner.SimpleScanner; import org.eclipse.cdt.internal.formatter.scanner.Token; /** * 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<IResource>(); for (int i = 0; i < adaptables.length; i++) { IAdaptable adaptable = adaptables[i]; IResource resource= (IResource) 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<IResource>(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<Matcher>(); 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<IProject>(); LinkedList<IProject> workThrough= new LinkedList<IProject>(); 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) { 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, new SubProgressMonitor(monitor, 95)); categorizeMatches(target.subList(startPos, target.size()), new SubProgressMonitor(monitor, 5)); return result; } public void categorizeMatches(List<CRefactoringMatch> matches, IProgressMonitor monitor) { monitor.beginTask(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<int[]>(); computeLocations(file, locations); } match.setLocation(findLocation(match, locations)); monitor.worked(1); } } final static Comparator<int[]> COMPARE_FIRST_INTEGER= new Comparator<int[]>() { 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) { } } } }