Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-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