Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTerry Parker2014-07-29 23:58:34 +0000
committerTerry Parker2015-01-23 18:05:39 +0000
commit8041d58d7588a3871f4593462b7fa1686de64058 (patch)
tree39a89ed6824d0218ea315ef2e5ce2927e19ed340 /org.eclipse.search
parent058694a2fadc002a78b472c249d6a923c09e332f (diff)
downloadeclipse.platform.text-8041d58d7588a3871f4593462b7fa1686de64058.tar.gz
eclipse.platform.text-8041d58d7588a3871f4593462b7fa1686de64058.tar.xz
eclipse.platform.text-8041d58d7588a3871f4593462b7fa1686de64058.zip
Execute text search processing in parallel, yielding a 3x-4x speedup. Previously all files in a text search were processed serially. This change moves the work into Jobs and uses a JobGroup to parallelize the processing. The following additional changes were needed: * Some “global” state (e.g., FileCharSequenceProvider and ReusableMatchAccess) that were previously accessed as class member variables were changed to per-file or per-job instances. * Access to FileSearchQuery.TextSearchResultCollector needed to be synchronized. Its intermediate cache, which was previously an ArrayList and assumed the last entry held the previous match from the current file, was changed to a Map of IFile->ArrayList so that each file’s results are segregated. Converted spaces to tabs in the search() method to improve the Gerrit code review experience. Added minimal performance logging. Change-Id: I3e36da89cd891acabb902e28415d8ddecea4df60 Signed-off-by: Terry Parker <tparker@google.com>
Diffstat (limited to 'org.eclipse.search')
-rw-r--r--org.eclipse.search/.options4
-rw-r--r--org.eclipse.search/build.properties1
-rw-r--r--org.eclipse.search/search/org/eclipse/search/internal/core/text/TextSearchVisitor.java292
-rw-r--r--org.eclipse.search/search/org/eclipse/search/internal/ui/text/FileSearchQuery.java74
4 files changed, 267 insertions, 104 deletions
diff --git a/org.eclipse.search/.options b/org.eclipse.search/.options
new file mode 100644
index 000000000..f80dc7bac
--- /dev/null
+++ b/org.eclipse.search/.options
@@ -0,0 +1,4 @@
+# Debugging options for the org.eclipse.search plug-in
+
+# Reports performance of text searches
+org.eclipse.search/perf=false
diff --git a/org.eclipse.search/build.properties b/org.eclipse.search/build.properties
index 139bc09a5..6b1490be6 100644
--- a/org.eclipse.search/build.properties
+++ b/org.eclipse.search/build.properties
@@ -9,6 +9,7 @@
# IBM Corporation - initial API and implementation
###############################################################################
bin.includes = plugin.xml,\
+ .options,\
about.html,\
icons/,\
plugin.properties,\
diff --git a/org.eclipse.search/search/org/eclipse/search/internal/core/text/TextSearchVisitor.java b/org.eclipse.search/search/org/eclipse/search/internal/core/text/TextSearchVisitor.java
index b0bb14c68..4464e3c58 100644
--- a/org.eclipse.search/search/org/eclipse/search/internal/core/text/TextSearchVisitor.java
+++ b/org.eclipse.search/search/org/eclipse/search/internal/core/text/TextSearchVisitor.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2008 IBM Corporation and others.
+ * Copyright (c) 2000, 2015 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
@@ -7,6 +7,7 @@
*
* Contributors:
* IBM Corporation - initial API and implementation
+ * Terry Parker <tparker@google.com> (Google Inc.) - Bug 441016 - Speed up text search by parallelizing it using JobGroups
*******************************************************************************/
package org.eclipse.search.internal.core.text;
@@ -32,6 +33,7 @@ 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.core.runtime.jobs.JobGroup;
import org.eclipse.core.resources.IFile;
@@ -67,6 +69,10 @@ import org.eclipse.search.ui.NewSearchUI;
*/
public class TextSearchVisitor {
+ public static final boolean TRACING= "true".equalsIgnoreCase(Platform.getDebugOption("org.eclipse.search/perf")); //$NON-NLS-1$ //$NON-NLS-2$
+ private static final int NUMBER_OF_LOGICAL_THREADS= Runtime.getRuntime().availableProcessors();
+ private static final int FILES_PER_JOB= 50;
+
public static class ReusableMatchAccess extends TextSearchMatchAccess {
private int fOffset;
@@ -106,49 +112,134 @@ public class TextSearchVisitor {
}
}
+ /**
+ * A JobGroup for text searches across multiple files.
+ */
+ private static class TextSearchJobGroup extends JobGroup {
+ public TextSearchJobGroup(String name, int maxThreads, int initialJobCount) {
+ super(name, maxThreads, initialJobCount);
+ }
+
+ // Always continue processing all other files, even if errors are encountered in individual files.
+ protected boolean shouldCancel(IStatus lastCompletedJobResult, int numberOfFailedJobs, int numberOfCancelledJobs) {
+ return false;
+ }
+ }
+
+ /**
+ * A job to find matches in a set of files.
+ */
+ private class TextSearchJob extends Job {
+ private final IFile[] fFiles;
+ private final int fBegin;
+ private final int fEnd;
+ private final Map fDocumentsInEditors;
+ private ReusableMatchAccess fReusableMatchAccess;
+ private IProgressMonitor fMonitor;
+
+ /**
+ * Searches for matches in a set of files.
+ *
+ * @param files an array of IFiles, a portion of which is to be processed
+ * @param begin the first element in the file array to process
+ * @param end one past the last element in the array to process
+ * @param documentsInEditors a map from IFile to IDocument for all open, dirty editors
+ */
+ public TextSearchJob(IFile[] files, int begin, int end, Map documentsInEditors) {
+ super(files[begin].getName());
+ setSystem(true);
+ fFiles = files;
+ fBegin = begin;
+ fEnd = end;
+ fDocumentsInEditors = documentsInEditors;
+ }
+
+ protected IStatus run(IProgressMonitor inner) {
+ fMonitor= inner;
+ MultiStatus multiStatus=
+ new MultiStatus(NewSearchUI.PLUGIN_ID, IStatus.OK, SearchMessages.TextSearchEngine_statusMessage, null);
+ for (int i = fBegin; i < fEnd; i++) {
+ IStatus status= processFile(fFiles[i], this);
+ // Only accumulate interesting status
+ if (!status.isOK())
+ multiStatus.add(status);
+ // Group cancellation is propagated to this job's monitor.
+ // Stop processing and return the status for the completed jobs.
+ if (inner.isCanceled())
+ break;
+ }
+ return multiStatus;
+ }
+
+ public IProgressMonitor getMonitor() {
+ return fMonitor;
+ }
+
+ public Map getDocumentsInEditors() {
+ return fDocumentsInEditors;
+ }
+
+ public ReusableMatchAccess getReusableMatchAccess() {
+ if (fReusableMatchAccess == null)
+ fReusableMatchAccess = new ReusableMatchAccess();
+ return fReusableMatchAccess;
+ }
+ }
+
private final TextSearchRequestor fCollector;
- private final Matcher fMatcher;
+ private final Pattern fSearchPattern;
private IProgressMonitor fProgressMonitor;
private int fNumberOfScannedFiles;
private int fNumberOfFilesToScan;
private IFile fCurrentFile;
+ private Object fLock= new Object();
private final MultiStatus fStatus;
- private final FileCharSequenceProvider fFileCharSequenceProvider;
-
- private final ReusableMatchAccess fMatchAccess;
-
public TextSearchVisitor(TextSearchRequestor collector, Pattern searchPattern) {
fCollector= collector;
fStatus= new MultiStatus(NewSearchUI.PLUGIN_ID, IStatus.OK, SearchMessages.TextSearchEngine_statusMessage, null);
- fMatcher= searchPattern.pattern().length() == 0 ? null : searchPattern.matcher(new String());
-
- fFileCharSequenceProvider= new FileCharSequenceProvider();
- fMatchAccess= new ReusableMatchAccess();
+ fSearchPattern= searchPattern;
}
public IStatus search(IFile[] files, IProgressMonitor monitor) {
+ if (files.length == 0) {
+ return fStatus;
+ }
fProgressMonitor= monitor == null ? new NullProgressMonitor() : monitor;
- fNumberOfScannedFiles= 0;
- fNumberOfFilesToScan= files.length;
- fCurrentFile= null;
-
- Job monitorUpdateJob= new Job(SearchMessages.TextSearchVisitor_progress_updating_job) {
- private int fLastNumberOfScannedFiles= 0;
+ fNumberOfScannedFiles= 0;
+ fNumberOfFilesToScan= files.length;
+ fCurrentFile= null;
+ int jobCount = Math.round((files.length + FILES_PER_JOB - 1) / FILES_PER_JOB);
+ final JobGroup jobGroup= new TextSearchJobGroup("Text Search", NUMBER_OF_LOGICAL_THREADS, jobCount); //$NON-NLS-1$
+ long startTime= TRACING ? System.currentTimeMillis() : 0;
+
+ Job monitorUpdateJob= new Job(SearchMessages.TextSearchVisitor_progress_updating_job) {
+ private int fLastNumberOfScannedFiles= 0;
+
+ public IStatus run(IProgressMonitor inner) {
+ while (!inner.isCanceled()) {
+ // Propagate user cancellation to the JobGroup.
+ if (fProgressMonitor.isCanceled()) {
+ jobGroup.cancel();
+ break;
+ }
- public IStatus run(IProgressMonitor inner) {
- while (!inner.isCanceled()) {
- IFile file= fCurrentFile;
+ IFile file;
+ int numberOfScannedFiles;
+ synchronized (fLock) {
+ file= fCurrentFile;
+ numberOfScannedFiles= fNumberOfScannedFiles;
+ }
if (file != null) {
String fileName= file.getName();
- Object[] args= { fileName, new Integer(fNumberOfScannedFiles), new Integer(fNumberOfFilesToScan)};
+ Object[] args= { fileName, new Integer(numberOfScannedFiles), new Integer(fNumberOfFilesToScan)};
fProgressMonitor.subTask(Messages.format(SearchMessages.TextSearchVisitor_scanning, args));
- int steps= fNumberOfScannedFiles - fLastNumberOfScannedFiles;
+ int steps= numberOfScannedFiles - fLastNumberOfScannedFiles;
fProgressMonitor.worked(steps);
fLastNumberOfScannedFiles += steps;
}
@@ -158,49 +249,61 @@ public class TextSearchVisitor {
return Status.OK_STATUS;
}
}
- return Status.OK_STATUS;
- }
- };
-
- try {
- String taskName= fMatcher == null ? SearchMessages.TextSearchVisitor_filesearch_task_label : Messages.format(SearchMessages.TextSearchVisitor_textsearch_task_label, fMatcher.pattern().pattern());
- fProgressMonitor.beginTask(taskName, fNumberOfFilesToScan);
- monitorUpdateJob.setSystem(true);
- monitorUpdateJob.schedule();
- try {
- fCollector.beginReporting();
- processFiles(files);
- return fStatus;
- } finally {
- monitorUpdateJob.cancel();
- }
- } finally {
- fProgressMonitor.done();
- fCollector.endReporting();
- }
+ return Status.OK_STATUS;
+ }
+ };
+
+ try {
+ String taskName= fSearchPattern.pattern().length() == 0
+ ? SearchMessages.TextSearchVisitor_filesearch_task_label
+ : Messages.format(SearchMessages.TextSearchVisitor_textsearch_task_label, fSearchPattern.pattern());
+ fProgressMonitor.beginTask(taskName, fNumberOfFilesToScan);
+ monitorUpdateJob.setSystem(true);
+ monitorUpdateJob.schedule();
+ try {
+ fCollector.beginReporting();
+ Map documentsInEditors= PlatformUI.isWorkbenchRunning() ? evalNonFileBufferDocuments() : Collections.EMPTY_MAP;
+ int filesPerJob = (files.length + jobCount - 1) / jobCount;
+ for (int first= 0; first < files.length; first += filesPerJob) {
+ int end= Math.min(files.length, first + filesPerJob);
+ Job job= new TextSearchJob(files, first, end, documentsInEditors);
+ job.setJobGroup(jobGroup);
+ job.schedule();
+ }
+
+ // The monitorUpdateJob is managing progress and cancellation,
+ // so it is ok to pass a null monitor into the job group.
+ jobGroup.join(0, null);
+ if (fProgressMonitor.isCanceled())
+ throw new OperationCanceledException(SearchMessages.TextSearchVisitor_canceled);
+
+ fStatus.addAll(jobGroup.getResult());
+ return fStatus;
+ } catch (InterruptedException e) {
+ throw new OperationCanceledException(SearchMessages.TextSearchVisitor_canceled);
+ } finally {
+ monitorUpdateJob.cancel();
+ }
+ } finally {
+ fProgressMonitor.done();
+ fCollector.endReporting();
+ if (TRACING) {
+ Object[] args= { new Integer(fNumberOfScannedFiles), new Integer(jobCount), new Integer(NUMBER_OF_LOGICAL_THREADS), new Long(System.currentTimeMillis() - startTime) };
+ System.out.println(Messages.format(
+ "[TextSearch] Search duration for {0} files in {1} jobs using {2} threads: {3}ms", args)); //$NON-NLS-1$
+ }
+ }
}
public IStatus search(TextSearchScope scope, IProgressMonitor monitor) {
return search(scope.evaluateFilesInScope(fStatus), monitor);
- }
-
- private void processFiles(IFile[] files) {
- final Map documentsInEditors;
- if (PlatformUI.isWorkbenchRunning())
- documentsInEditors= evalNonFileBufferDocuments();
- else
- documentsInEditors= Collections.EMPTY_MAP;
-
- for (int i= 0; i < files.length; i++) {
- fCurrentFile= files[i];
- boolean res= processFile(fCurrentFile, documentsInEditors);
- if (!res)
- break;
- }
}
/**
- * @return returns a map from IFile to IDocument for all open, dirty editors
+ * Returns a map from IFile to IDocument for all open, dirty editors. After creation this map
+ * is not modified, so returning a non-synchronized map is ok.
+ *
+ * @return a map from IFile to IDocument for all open, dirty editors
*/
private Map evalNonFileBufferDocuments() {
Map result= new HashMap();
@@ -242,32 +345,39 @@ public class TextSearchVisitor {
}
}
- public boolean processFile(IFile file, Map documentsInEditors) {
+ public IStatus processFile(IFile file, TextSearchJob job) {
+ // A natural cleanup after the change to use JobGroups is accepted would be to move these
+ // methods to the TextSearchJob class.
+ IProgressMonitor monitor= job.getMonitor();
+ ReusableMatchAccess matchAccess= job.getReusableMatchAccess();
+ Matcher matcher= fSearchPattern.pattern().length() == 0 ? null : fSearchPattern.matcher(new String());
+ FileCharSequenceProvider fileCharSequenceProvider= new FileCharSequenceProvider();
+
try {
- if (!fCollector.acceptFile(file) || fMatcher == null) {
- return true;
- }
+ if (!fCollector.acceptFile(file) || matcher == null) {
+ return Status.OK_STATUS;
+ }
- IDocument document= getOpenDocument(file, documentsInEditors);
+ IDocument document= getOpenDocument(file, job.getDocumentsInEditors());
if (document != null) {
DocumentCharSequence documentCharSequence= new DocumentCharSequence(document);
// assume all documents are non-binary
- locateMatches(file, documentCharSequence);
+ locateMatches(file, documentCharSequence, matcher, matchAccess, monitor);
} else {
CharSequence seq= null;
try {
- seq= fFileCharSequenceProvider.newCharSequence(file);
+ seq= fileCharSequenceProvider.newCharSequence(file);
if (hasBinaryContent(seq, file) && !fCollector.reportBinaryFile(file)) {
- return true;
+ return Status.OK_STATUS;
}
- locateMatches(file, seq);
+ locateMatches(file, seq, matcher, matchAccess, monitor);
} catch (FileCharSequenceProvider.FileCharSequenceException e) {
e.throwWrappedException();
} finally {
if (seq != null) {
try {
- fFileCharSequenceProvider.releaseCharSequence(seq);
+ fileCharSequenceProvider.releaseCharSequence(seq);
} catch (IOException e) {
SearchPlugin.log(e);
}
@@ -277,30 +387,32 @@ public class TextSearchVisitor {
} catch (UnsupportedCharsetException e) {
String[] args= { getCharSetName(file), file.getFullPath().makeRelative().toString()};
String message= Messages.format(SearchMessages.TextSearchVisitor_unsupportedcharset, args);
- fStatus.add(new Status(IStatus.ERROR, NewSearchUI.PLUGIN_ID, IStatus.ERROR, message, e));
+ return 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);
- fStatus.add(new Status(IStatus.ERROR, NewSearchUI.PLUGIN_ID, IStatus.ERROR, message, e));
+ return 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);
- fStatus.add(new Status(IStatus.ERROR, NewSearchUI.PLUGIN_ID, IStatus.ERROR, message, e));
+ return 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);
- fStatus.add(new Status(IStatus.ERROR, NewSearchUI.PLUGIN_ID, IStatus.ERROR, message, e));
+ return new Status(IStatus.ERROR, NewSearchUI.PLUGIN_ID, IStatus.ERROR, message, e);
} catch (StackOverflowError e) {
+ // Trigger cancellation of remaining jobs in the group.
+ // An alternative is to move this method into TextSearchJob and call getJobGroup().cancel() directly.
+ fProgressMonitor.setCanceled(true);
String message= SearchMessages.TextSearchVisitor_patterntoocomplex0;
- fStatus.add(new Status(IStatus.ERROR, NewSearchUI.PLUGIN_ID, IStatus.ERROR, message, e));
- return false;
+ return new Status(IStatus.ERROR, NewSearchUI.PLUGIN_ID, IStatus.ERROR, message, e);
} finally {
- fNumberOfScannedFiles++;
+ synchronized (fLock) {
+ fCurrentFile= file;
+ fNumberOfScannedFiles++;
+ }
}
- if (fProgressMonitor.isCanceled())
- throw new OperationCanceledException(SearchMessages.TextSearchVisitor_canceled);
-
- return true;
+ return monitor.isCanceled() ? Status.CANCEL_STATUS : Status.OK_STATUS;
}
private boolean hasBinaryContent(CharSequence seq, IFile file) throws CoreException {
@@ -330,29 +442,27 @@ public class TextSearchVisitor {
return false;
}
- private void locateMatches(IFile file, CharSequence searchInput) throws CoreException {
+ private void locateMatches(IFile file, CharSequence searchInput, Matcher matcher, ReusableMatchAccess matchAccess, IProgressMonitor monitor) throws CoreException {
try {
- fMatcher.reset(searchInput);
+ matcher.reset(searchInput);
int k= 0;
- while (fMatcher.find()) {
- int start= fMatcher.start();
- int end= fMatcher.end();
+ while (matcher.find()) {
+ int start= matcher.start();
+ int end= matcher.end();
if (end != start) { // don't report 0-length matches
- fMatchAccess.initialize(file, start, end - start, searchInput);
- boolean res= fCollector.acceptPatternMatch(fMatchAccess);
+ matchAccess.initialize(file, start, end - start, searchInput);
+ boolean res= fCollector.acceptPatternMatch(matchAccess);
if (!res) {
return; // no further reporting requested
}
}
- if (k++ == 20) {
- if (fProgressMonitor.isCanceled()) {
- throw new OperationCanceledException(SearchMessages.TextSearchVisitor_canceled);
- }
- k= 0;
+ // Periodically check for cancellation and quit working on the current file if the job has been cancelled.
+ if (++k % 20 == 0 && monitor.isCanceled()) {
+ break;
}
}
} finally {
- fMatchAccess.initialize(null, 0, 0, new String()); // clear references
+ matchAccess.initialize(null, 0, 0, new String()); // clear references
}
}
diff --git a/org.eclipse.search/search/org/eclipse/search/internal/ui/text/FileSearchQuery.java b/org.eclipse.search/search/org/eclipse/search/internal/ui/text/FileSearchQuery.java
index b3b425091..620433341 100644
--- a/org.eclipse.search/search/org/eclipse/search/internal/ui/text/FileSearchQuery.java
+++ b/org.eclipse.search/search/org/eclipse/search/internal/ui/text/FileSearchQuery.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2000, 2014 IBM Corporation and others.
+ * Copyright (c) 2000, 2015 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
@@ -11,10 +11,14 @@
* Ulrich Etter, etteru@ethz.ch - 47136 Search view should show match objects
* Roman Fuchs, fuchsro@ethz.ch - 47136 Search view should show match objects
* Christian Walther (Indel AG) - Bug 399094: Add whole word option to file search
+ * Terry Parker <tparker@google.com> (Google Inc.) - Bug 441016 - Speed up text search by parallelizing it using JobGroups
*******************************************************************************/
package org.eclipse.search.internal.ui.text;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.CoreException;
@@ -48,7 +52,8 @@ public class FileSearchQuery implements ISearchQuery {
private final boolean fSearchInBinaries;
private final boolean fIsLightweightAutoRefresh;
- private ArrayList fCachedMatches;
+ private Map fCachedMatches; // map of IFile -> ArrayList of FileMatches
+ private Object fLock= new Object();
private TextSearchResultCollector(AbstractTextSearchResult result, boolean isFileSearchOnly, boolean searchInBinaries) {
fResult= result;
@@ -63,7 +68,9 @@ public class FileSearchQuery implements ISearchQuery {
return false;
if (fIsFileSearchOnly) {
- fResult.addMatch(new FileMatch(file));
+ synchronized (fLock) {
+ fResult.addMatch(new FileMatch(file));
+ }
}
flushMatches();
return true;
@@ -77,22 +84,55 @@ public class FileSearchQuery implements ISearchQuery {
}
public boolean acceptPatternMatch(TextSearchMatchAccess matchRequestor) throws CoreException {
+ ArrayList matches;
+ synchronized(fLock) {
+ // fCachedMatches is set to null when the caller invokes endReporting(),
+ // indicating that no further results are desired/expected, so discard
+ // any additional results.
+ if (fCachedMatches == null) {
+ return false;
+ }
+ matches= (ArrayList) fCachedMatches.get(matchRequestor.getFile());
+ }
+
int matchOffset= matchRequestor.getMatchOffset();
- LineElement lineElement= getLineElement(matchOffset, matchRequestor);
+ /*
+ * Another job may call flushCaches() at any time, which will clear the cached matches.
+ * Any addition of matches to the cache needs to be protected against the flushing of
+ * the cache by other jobs. It is OK to call getLineElement() with an unprotected local
+ * reference to the matches for this file, because getLineElement() uses previous matches
+ * as an optimization when creating new matches but doesn't update the cache directly
+ * (and because each file is processed by at most one job).
+ */
+ LineElement lineElement= getLineElement(matchOffset, matchRequestor, matches);
if (lineElement != null) {
FileMatch fileMatch= new FileMatch(matchRequestor.getFile(), matchOffset, matchRequestor.getMatchLength(), lineElement);
- fCachedMatches.add(fileMatch);
+ synchronized(fLock) {
+ // fCachedMatches is set to null when the caller invokes endReporting(),
+ // indicating that no further results are desired/expected, so discard
+ // any additional results.
+ if (fCachedMatches == null) {
+ return false;
+ }
+ matches= (ArrayList) fCachedMatches.get(matchRequestor.getFile());
+ if (matches == null) {
+ matches= new ArrayList();
+ fCachedMatches.put(matchRequestor.getFile(), matches);
+ }
+ matches.add(fileMatch);
+ }
}
return true;
}
- private LineElement getLineElement(int offset, TextSearchMatchAccess matchRequestor) {
+ private LineElement getLineElement(int offset, TextSearchMatchAccess matchRequestor, ArrayList matches) {
int lineNumber= 1;
int lineStart= 0;
- if (!fCachedMatches.isEmpty()) {
+
+ if (matches != null) {
// match on same line as last?
- FileMatch last= (FileMatch) fCachedMatches.get(fCachedMatches.size() - 1);
+ FileMatch last= (FileMatch) matches.get(matches.size() - 1);
LineElement lineElement= last.getLineElement();
if (lineElement.contains(offset)) {
return lineElement;
@@ -142,18 +182,26 @@ public class FileSearchQuery implements ISearchQuery {
}
public void beginReporting() {
- fCachedMatches= new ArrayList();
+ fCachedMatches= new HashMap();
}
public void endReporting() {
flushMatches();
- fCachedMatches= null;
+ synchronized (fLock) {
+ fCachedMatches= null;
+ }
}
private void flushMatches() {
- if (!fCachedMatches.isEmpty()) {
- fResult.addMatches((Match[]) fCachedMatches.toArray(new Match[fCachedMatches.size()]));
- fCachedMatches.clear();
+ synchronized (fLock) {
+ if (fCachedMatches != null && !fCachedMatches.isEmpty()) {
+ Iterator it = fCachedMatches.values().iterator();
+ while(it.hasNext()) {
+ ArrayList matches= (ArrayList) it.next();
+ fResult.addMatches((Match[]) matches.toArray(new Match[matches.size()]));
+ }
+ fCachedMatches.clear();
+ }
}
}
}

Back to the top