Index: editor/tools/plugins/com.google.dart.tools.search/src/com/google/dart/tools/search/internal/core/text/TextSearchExecutor.java |
=================================================================== |
--- editor/tools/plugins/com.google.dart.tools.search/src/com/google/dart/tools/search/internal/core/text/TextSearchExecutor.java (revision 0) |
+++ editor/tools/plugins/com.google.dart.tools.search/src/com/google/dart/tools/search/internal/core/text/TextSearchExecutor.java (revision 0) |
@@ -0,0 +1,573 @@ |
+/* |
+ * Copyright (c) 2012, the Dart project authors. |
+ * |
+ * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except |
+ * in compliance with the License. You may obtain a copy of the License at |
+ * |
+ * http://www.eclipse.org/legal/epl-v10.html |
+ * |
+ * Unless required by applicable law or agreed to in writing, software distributed under the License |
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
+ * or implied. See the License for the specific language governing permissions and limitations under |
+ * the License. |
+ */ |
+package com.google.dart.tools.search.internal.core.text; |
+ |
+import com.google.dart.tools.search.core.text.TextSearchMatchAccess; |
+import com.google.dart.tools.search.core.text.TextSearchRequestor; |
+import com.google.dart.tools.search.core.text.TextSearchScope; |
+import com.google.dart.tools.search.internal.core.text.FileCharSequenceProvider.FileCharSequenceException; |
+import com.google.dart.tools.search.internal.ui.Messages; |
+import com.google.dart.tools.search.internal.ui.SearchMessages; |
+import com.google.dart.tools.search.internal.ui.SearchPlugin; |
+import com.google.dart.tools.search.internal.ui.text.ExternalFile; |
+import com.google.dart.tools.search.internal.ui.text.ExternalFileMatch; |
+import com.google.dart.tools.search.internal.ui.text.FileMatch; |
+import com.google.dart.tools.search.internal.ui.text.FileResource; |
+import com.google.dart.tools.search.internal.ui.text.FileResourceMatch; |
+import com.google.dart.tools.search.internal.ui.text.LineElement; |
+import com.google.dart.tools.search.internal.ui.text.WorkspaceFile; |
+import com.google.dart.tools.search.ui.NewSearchUI; |
+ |
+import org.eclipse.core.filebuffers.FileBuffers; |
+import org.eclipse.core.filebuffers.ITextFileBuffer; |
+import org.eclipse.core.filebuffers.ITextFileBufferManager; |
+import org.eclipse.core.filebuffers.LocationKind; |
+import org.eclipse.core.resources.IFile; |
+import org.eclipse.core.runtime.CoreException; |
+import org.eclipse.core.runtime.IProgressMonitor; |
+import org.eclipse.core.runtime.IStatus; |
+import org.eclipse.core.runtime.MultiStatus; |
+import org.eclipse.core.runtime.NullProgressMonitor; |
+import org.eclipse.core.runtime.OperationCanceledException; |
+import org.eclipse.core.runtime.Platform; |
+import org.eclipse.core.runtime.Status; |
+import org.eclipse.core.runtime.content.IContentDescription; |
+import org.eclipse.core.runtime.content.IContentType; |
+import org.eclipse.core.runtime.content.IContentTypeManager; |
+import org.eclipse.core.runtime.jobs.Job; |
+import org.eclipse.jface.text.IDocument; |
+import org.eclipse.ui.IEditorInput; |
+import org.eclipse.ui.IEditorPart; |
+import org.eclipse.ui.IEditorReference; |
+import org.eclipse.ui.IFileEditorInput; |
+import org.eclipse.ui.IWorkbench; |
+import org.eclipse.ui.IWorkbenchPage; |
+import org.eclipse.ui.IWorkbenchWindow; |
+import org.eclipse.ui.PlatformUI; |
+import org.eclipse.ui.texteditor.ITextEditor; |
+ |
+import java.io.CharConversionException; |
+import java.io.File; |
+import java.io.IOException; |
+import java.nio.charset.IllegalCharsetNameException; |
+import java.nio.charset.UnsupportedCharsetException; |
+import java.util.Collections; |
+import java.util.HashMap; |
+import java.util.Map; |
+import java.util.regex.Matcher; |
+import java.util.regex.Pattern; |
+ |
+/** |
+ * Executes a text search across {@link File} and {@link IFile} resources. |
+ */ |
+public class TextSearchExecutor { |
+ |
+ private static class ReusableMatchAccess extends TextSearchMatchAccess { |
+ |
+ private int offset; |
+ private int length; |
+ private Object /* <IFile,File> */file; |
danrubel
2012/05/08 00:22:43
I recommend two fields... "file" and "resource"...
|
+ private CharSequence content; |
+ |
+ @Override |
+ public FileResourceMatch createMatch(LineElement lineElement) { |
+ if (file instanceof IFile) { |
+ return new FileMatch((IFile) file, getMatchOffset(), getMatchLength(), lineElement); |
+ } |
+ if (file instanceof File) { |
+ return new ExternalFileMatch((File) file, getMatchOffset(), getMatchLength(), lineElement); |
+ } |
+ throw new IllegalArgumentException("file must be of type IFile or File"); |
+ } |
+ |
+ @Override |
+ public FileResource<?> getFile() { |
+ if (file instanceof IFile) { |
+ return new WorkspaceFile((IFile) file); |
+ } |
+ if (file instanceof File) { |
+ return new ExternalFile((File) file); |
+ } |
+ throw new IllegalArgumentException("file must be of type IFile or File"); |
+ } |
+ |
+ @Override |
+ public String getFileContent(int offset, int length) { |
+ return content.subSequence(offset, offset + length).toString(); // must pass a copy! |
+ } |
+ |
+ @Override |
+ public char getFileContentChar(int offset) { |
+ return content.charAt(offset); |
+ } |
+ |
+ @Override |
+ public int getFileContentLength() { |
+ return content.length(); |
+ } |
+ |
+ @Override |
+ public int getMatchLength() { |
+ return length; |
+ } |
+ |
+ @Override |
+ public int getMatchOffset() { |
+ return offset; |
+ } |
+ |
+ public void initialize(Object file, int offset, int length, CharSequence content) { |
+ this.file = file; |
+ this.offset = offset; |
+ this.length = length; |
+ this.content = content; |
+ } |
+ } |
+ |
+ private final TextSearchRequestor collector; |
+ private final Matcher matcher; |
+ |
+ private IProgressMonitor progressMonitor; |
+ |
+ private int numberOfScannedFiles; |
+ private int numberOfFilesToScan; |
+ |
+ private Object /* File, IFile */currentFile; |
+ |
+ private final MultiStatus status; |
+ |
+ private ExternalFileCharSequenceProvider externalFileCharSequenceProvider; |
+ private final FileCharSequenceProvider fileCharSequenceProvider; |
+ |
+ private final ReusableMatchAccess matchAccess; |
+ |
+ public TextSearchExecutor(TextSearchRequestor collector, Pattern searchPattern) { |
+ this.collector = collector; |
+ this.status = new MultiStatus(NewSearchUI.PLUGIN_ID, IStatus.OK, |
+ SearchMessages.TextSearchEngine_statusMessage, null); |
+ |
+ this.matcher = searchPattern.pattern().length() == 0 ? null |
+ : searchPattern.matcher(new String()); |
+ |
+ this.fileCharSequenceProvider = new FileCharSequenceProvider(); |
+ this.externalFileCharSequenceProvider = new ExternalFileCharSequenceProvider(); |
+ this.matchAccess = new ReusableMatchAccess(); |
+ } |
+ |
+ /** |
+ * Initiate a search across the given resources. |
+ * |
+ * @param files the workspace file roots to search |
+ * @param externalFiles the external file roots to search |
+ * @param monitor a progress monitor for reporting progress |
+ * @return execution status |
+ */ |
+ public IStatus search(IFile[] files, File[] externalFiles, IProgressMonitor monitor) { |
+ progressMonitor = monitor == null ? new NullProgressMonitor() : monitor; |
+ numberOfScannedFiles = 0; |
+ numberOfFilesToScan = files.length + externalFiles.length; |
+ currentFile = null; |
+ |
+ Job monitorUpdateJob = new Job(SearchMessages.TextSearchVisitor_progress_updating_job) { |
+ private int fLastNumberOfScannedFiles = 0; |
+ |
+ @Override |
+ public IStatus run(IProgressMonitor inner) { |
+ while (!inner.isCanceled()) { |
+ Object file = currentFile; |
+ if (file != null) { |
+ String fileName = getFileName(file); |
+ Object[] args = { |
+ fileName, new Integer(numberOfScannedFiles), new Integer(numberOfFilesToScan)}; |
+ progressMonitor.subTask(Messages.format(SearchMessages.TextSearchVisitor_scanning, args)); |
+ int steps = numberOfScannedFiles - fLastNumberOfScannedFiles; |
+ progressMonitor.worked(steps); |
+ fLastNumberOfScannedFiles += steps; |
+ } |
+ try { |
+ Thread.sleep(100); |
+ } catch (InterruptedException e) { |
+ return Status.OK_STATUS; |
+ } |
+ } |
+ return Status.OK_STATUS; |
+ } |
+ }; |
+ |
+ try { |
+ String taskName = matcher == null ? SearchMessages.TextSearchVisitor_filesearch_task_label |
+ : Messages.format(SearchMessages.TextSearchVisitor_textsearch_task_label, |
+ matcher.pattern().pattern()); |
+ progressMonitor.beginTask(taskName, numberOfFilesToScan); |
+ monitorUpdateJob.setSystem(true); |
+ monitorUpdateJob.schedule(); |
+ try { |
+ collector.beginReporting(); |
+ processFiles(files); |
+ processExternalFiles(externalFiles); |
+ return status; |
+ } finally { |
+ monitorUpdateJob.cancel(); |
+ } |
+ } finally { |
+ progressMonitor.done(); |
+ collector.endReporting(); |
+ } |
+ } |
+ |
+ /** |
+ * Initiate a search in a given search scope. |
+ * |
+ * @param scope the scope for the search |
+ * @param monitor a progress monitor for reporting progress |
+ * @return execution status |
+ */ |
+ public IStatus search(TextSearchScope scope, IProgressMonitor monitor) { |
+ return search(scope.evaluateFilesInScope(status), scope.evaluateExternalFilesInScope(status), |
+ monitor); |
+ } |
+ |
+ /** |
+ * @return returns a map from IFile to IDocument for all open, dirty editors |
+ */ |
+ private Map<IFile, IDocument> evalNonFileBufferDocuments() { |
+ Map<IFile, IDocument> result = new HashMap<IFile, IDocument>(); |
+ IWorkbench workbench = SearchPlugin.getDefault().getWorkbench(); |
+ IWorkbenchWindow[] windows = workbench.getWorkbenchWindows(); |
+ for (int i = 0; i < windows.length; i++) { |
+ IWorkbenchPage[] pages = windows[i].getPages(); |
+ for (int x = 0; x < pages.length; x++) { |
+ IEditorReference[] editorRefs = pages[x].getEditorReferences(); |
+ for (int z = 0; z < editorRefs.length; z++) { |
+ IEditorPart ep = editorRefs[z].getEditor(false); |
+ if (ep instanceof ITextEditor && ep.isDirty()) { // only dirty editors |
+ evaluateTextEditor(result, ep); |
+ } |
+ } |
+ } |
+ } |
+ return result; |
+ } |
+ |
+ private void evaluateTextEditor(Map<IFile, IDocument> result, IEditorPart ep) { |
+ IEditorInput input = ep.getEditorInput(); |
+ if (input instanceof IFileEditorInput) { |
+ IFile file = ((IFileEditorInput) input).getFile(); |
+ if (!result.containsKey(file)) { // take the first editor found |
+ ITextFileBufferManager bufferManager = FileBuffers.getTextFileBufferManager(); |
+ ITextFileBuffer textFileBuffer = bufferManager.getTextFileBuffer(file.getFullPath(), |
+ LocationKind.IFILE); |
+ if (textFileBuffer != null) { |
+ // file buffer has precedence |
+ result.put(file, textFileBuffer.getDocument()); |
+ } else { |
+ // use document provider |
+ IDocument document = ((ITextEditor) ep).getDocumentProvider().getDocument(input); |
+ if (document != null) { |
+ result.put(file, document); |
+ } |
+ } |
+ } |
+ } |
+ } |
+ |
+ private String getCharSetName(File file) { |
+ return "unknown"; |
+ } |
+ |
+ private String getCharSetName(IFile file) { |
+ try { |
+ return file.getCharset(); |
+ } catch (CoreException e) { |
+ return "unknown"; //$NON-NLS-1$ |
+ } |
+ } |
+ |
+ private String getExceptionMessage(Exception e) { |
+ String message = e.getLocalizedMessage(); |
+ if (message == null) { |
+ return e.getClass().getName(); |
+ } |
+ return message; |
+ } |
+ |
+ private String getFileName(Object file) { |
+ if (file instanceof File) { |
+ return ((File) file).getName(); |
+ } |
+ if (file instanceof IFile) { |
+ return ((IFile) file).getName(); |
+ } |
+ return null; |
+ } |
+ |
+ private IDocument getOpenDocument(IFile file, Map<IFile, IDocument> documentsInEditors) { |
+ IDocument document = documentsInEditors.get(file); |
+ if (document == null) { |
+ ITextFileBufferManager bufferManager = FileBuffers.getTextFileBufferManager(); |
+ ITextFileBuffer textFileBuffer = bufferManager.getTextFileBuffer(file.getFullPath(), |
+ LocationKind.IFILE); |
+ if (textFileBuffer != null) { |
+ document = textFileBuffer.getDocument(); |
+ } |
+ } |
+ return document; |
+ } |
+ |
+ private boolean hasBinaryContent(CharSequence seq, File file) throws CoreException { |
+ |
+ // avoid calling seq.length() at it runs through the complete file, |
+ // thus it would do so for all binary files. |
+ try { |
+ int limit = FileCharSequenceProvider.BUFFER_SIZE; |
+ for (int i = 0; i < limit; i++) { |
+ if (seq.charAt(i) == '\0') { |
+ return true; |
+ } |
+ } |
+ } catch (IndexOutOfBoundsException e) { |
+ } catch (FileCharSequenceException ex) { |
+ if (ex.getCause() instanceof CharConversionException) { |
+ return true; |
+ } |
+ throw ex; |
+ } |
+ return false; |
+ } |
+ |
+ private boolean hasBinaryContent(CharSequence seq, IFile file) throws CoreException { |
+ IContentDescription desc = file.getContentDescription(); |
+ if (desc != null) { |
+ IContentType contentType = desc.getContentType(); |
+ if (contentType != null |
+ && contentType.isKindOf(Platform.getContentTypeManager().getContentType( |
+ IContentTypeManager.CT_TEXT))) { |
+ return false; |
+ } |
+ } |
+ |
+ // avoid calling seq.length() at it runs through the complete file, |
+ // thus it would do so for all binary files. |
+ try { |
+ int limit = FileCharSequenceProvider.BUFFER_SIZE; |
+ for (int i = 0; i < limit; i++) { |
+ if (seq.charAt(i) == '\0') { |
+ return true; |
+ } |
+ } |
+ } catch (IndexOutOfBoundsException e) { |
+ } catch (FileCharSequenceException ex) { |
+ if (ex.getCause() instanceof CharConversionException) { |
+ return true; |
+ } |
+ throw ex; |
+ } |
+ return false; |
+ } |
+ |
+ private void locateMatches(File file, CharSequence searchInput) throws CoreException { |
+ try { |
+ matcher.reset(searchInput); |
+ int k = 0; |
+ while (matcher.find()) { |
+ int start = matcher.start(); |
+ int end = matcher.end(); |
+ if (end != start) { // don't report 0-length matches |
+ matchAccess.initialize(file, start, end - start, searchInput); |
+ boolean res = collector.acceptPatternMatch(matchAccess); |
+ if (!res) { |
+ return; // no further reporting requested |
+ } |
+ } |
+ if (k++ == 20) { |
+ if (progressMonitor.isCanceled()) { |
+ throw new OperationCanceledException(SearchMessages.TextSearchVisitor_canceled); |
+ } |
+ k = 0; |
+ } |
+ } |
+ } finally { |
+ matchAccess.initialize(null, 0, 0, new String()); // clear references |
+ } |
+ } |
+ |
+ private void locateMatches(IFile file, CharSequence searchInput) throws CoreException { |
+ try { |
+ matcher.reset(searchInput); |
+ int k = 0; |
+ while (matcher.find()) { |
+ int start = matcher.start(); |
+ int end = matcher.end(); |
+ if (end != start) { // don't report 0-length matches |
+ matchAccess.initialize(file, start, end - start, searchInput); |
+ boolean res = collector.acceptPatternMatch(matchAccess); |
+ if (!res) { |
+ return; // no further reporting requested |
+ } |
+ } |
+ if (k++ == 20) { |
+ if (progressMonitor.isCanceled()) { |
+ throw new OperationCanceledException(SearchMessages.TextSearchVisitor_canceled); |
+ } |
+ k = 0; |
+ } |
+ } |
+ } finally { |
+ matchAccess.initialize(null, 0, 0, new String()); // clear references |
+ } |
+ } |
+ |
+ private boolean processExternalFile(File file) { |
+ try { |
+ if (!collector.acceptExternalFile(file) || matcher == null) { |
+ return true; |
+ } |
+ |
+ CharSequence seq = null; |
+ try { |
+ seq = externalFileCharSequenceProvider.newCharSequence(file); |
+ //TODO(pquitslund): flip this test? |
+ if (hasBinaryContent(seq, file) && !collector.reportBinaryExternalFile(file)) { |
+ return true; |
+ } |
+ locateMatches(file, seq); |
+ } catch (FileCharSequenceProvider.FileCharSequenceException e) { |
+ e.throwWrappedException(); |
+ } finally { |
+ if (seq != null) { |
+ try { |
+ fileCharSequenceProvider.releaseCharSequence(seq); |
+ } catch (IOException e) { |
+ SearchPlugin.log(e); |
+ } |
+ } |
+ |
+ } |
+ } catch (UnsupportedCharsetException e) { |
+ String[] args = {getCharSetName(file), file.getAbsolutePath().toString()}; |
+ String message = Messages.format(SearchMessages.TextSearchVisitor_unsupportedcharset, args); |
+ status.add(new Status(IStatus.ERROR, NewSearchUI.PLUGIN_ID, IStatus.ERROR, message, e)); |
+ } catch (IllegalCharsetNameException e) { |
+ String[] args = {getCharSetName(file), file.getAbsolutePath().toString()}; |
+ String message = Messages.format(SearchMessages.TextSearchVisitor_illegalcharset, args); |
+ status.add(new Status(IStatus.ERROR, NewSearchUI.PLUGIN_ID, IStatus.ERROR, message, e)); |
+ } catch (IOException e) { |
+ String[] args = {getExceptionMessage(e), file.getAbsolutePath().toString()}; |
+ String message = Messages.format(SearchMessages.TextSearchVisitor_error, args); |
+ status.add(new Status(IStatus.ERROR, NewSearchUI.PLUGIN_ID, IStatus.ERROR, message, e)); |
+ } catch (CoreException e) { |
+ String[] args = {getExceptionMessage(e), file.getAbsolutePath().toString()}; |
+ String message = Messages.format(SearchMessages.TextSearchVisitor_error, args); |
+ status.add(new Status(IStatus.ERROR, NewSearchUI.PLUGIN_ID, IStatus.ERROR, message, e)); |
+ } catch (StackOverflowError e) { |
+ String message = SearchMessages.TextSearchVisitor_patterntoocomplex0; |
+ status.add(new Status(IStatus.ERROR, NewSearchUI.PLUGIN_ID, IStatus.ERROR, message, e)); |
+ return false; |
+ } finally { |
+ numberOfScannedFiles++; |
+ } |
+ if (progressMonitor.isCanceled()) { |
+ throw new OperationCanceledException(SearchMessages.TextSearchVisitor_canceled); |
+ } |
+ |
+ return true; |
+ } |
+ |
+ private void processExternalFiles(File[] files) { |
+ for (File file : files) { |
+ currentFile = file; |
+ processExternalFile(file); |
+ } |
+ } |
+ |
+ private boolean processFile(IFile file, Map<IFile, IDocument> documentsInEditors) { |
+ try { |
+ if (!collector.acceptFile(file) || matcher == null) { |
+ return true; |
+ } |
+ |
+ IDocument document = getOpenDocument(file, documentsInEditors); |
+ |
+ if (document != null) { |
+ DocumentCharSequence documentCharSequence = new DocumentCharSequence(document); |
+ // assume all documents are non-binary |
+ locateMatches(file, documentCharSequence); |
+ } else { |
+ CharSequence seq = null; |
+ try { |
+ seq = fileCharSequenceProvider.newCharSequence(file); |
+ if (hasBinaryContent(seq, file) && !collector.reportBinaryFile(file)) { |
+ return true; |
+ } |
+ locateMatches(file, seq); |
+ } catch (FileCharSequenceProvider.FileCharSequenceException e) { |
+ e.throwWrappedException(); |
+ } finally { |
+ if (seq != null) { |
+ try { |
+ fileCharSequenceProvider.releaseCharSequence(seq); |
+ } catch (IOException e) { |
+ SearchPlugin.log(e); |
+ } |
+ } |
+ } |
+ } |
+ } catch (UnsupportedCharsetException e) { |
+ String[] args = {getCharSetName(file), file.getFullPath().makeRelative().toString()}; |
+ String message = Messages.format(SearchMessages.TextSearchVisitor_unsupportedcharset, args); |
+ status.add(new Status(IStatus.ERROR, NewSearchUI.PLUGIN_ID, IStatus.ERROR, message, e)); |
+ } catch (IllegalCharsetNameException e) { |
+ String[] args = {getCharSetName(file), file.getFullPath().makeRelative().toString()}; |
+ String message = Messages.format(SearchMessages.TextSearchVisitor_illegalcharset, args); |
+ status.add(new Status(IStatus.ERROR, NewSearchUI.PLUGIN_ID, IStatus.ERROR, message, e)); |
+ } catch (IOException e) { |
+ String[] args = {getExceptionMessage(e), file.getFullPath().makeRelative().toString()}; |
+ String message = Messages.format(SearchMessages.TextSearchVisitor_error, args); |
+ status.add(new Status(IStatus.ERROR, NewSearchUI.PLUGIN_ID, IStatus.ERROR, message, e)); |
+ } catch (CoreException e) { |
+ String[] args = {getExceptionMessage(e), file.getFullPath().makeRelative().toString()}; |
+ String message = Messages.format(SearchMessages.TextSearchVisitor_error, args); |
+ status.add(new Status(IStatus.ERROR, NewSearchUI.PLUGIN_ID, IStatus.ERROR, message, e)); |
+ } catch (StackOverflowError e) { |
+ String message = SearchMessages.TextSearchVisitor_patterntoocomplex0; |
+ status.add(new Status(IStatus.ERROR, NewSearchUI.PLUGIN_ID, IStatus.ERROR, message, e)); |
+ return false; |
+ } finally { |
+ numberOfScannedFiles++; |
+ } |
+ if (progressMonitor.isCanceled()) { |
+ throw new OperationCanceledException(SearchMessages.TextSearchVisitor_canceled); |
+ } |
+ |
+ return true; |
+ } |
+ |
+ private void processFiles(IFile[] files) { |
+ final Map<IFile, IDocument> documentsInEditors; |
+ if (PlatformUI.isWorkbenchRunning()) { |
+ documentsInEditors = evalNonFileBufferDocuments(); |
+ } else { |
+ documentsInEditors = Collections.emptyMap(); |
+ } |
+ |
+ for (IFile file : files) { |
+ currentFile = file; |
+ boolean res = processFile(file, documentsInEditors); |
+ if (!res) { |
+ break; |
+ } |
+ } |
+ } |
+ |
+} |