Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1053)

Unified Diff: editor/tools/plugins/com.google.dart.tools.search/src/com/google/dart/tools/search/internal/core/text/TextSearchExecutor.java

Issue 10387008: External file text search engine support. (Closed) Base URL: http://dart.googlecode.com/svn/branches/bleeding_edge/dart/
Patch Set: Created 8 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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;
+ }
+ }
+ }
+
+}

Powered by Google App Engine
This is Rietveld 408576698