diff -r eda741b8be66 o.openidex.util/manifest.mf
--- a/o.openidex.util/manifest.mf Tue Aug 23 08:57:54 2011 +0200
+++ b/o.openidex.util/manifest.mf Tue Aug 23 17:08:57 2011 +0200
@@ -1,5 +1,5 @@
Manifest-Version: 1.0
OpenIDE-Module: org.openidex.util/3
OpenIDE-Module-Localizing-Bundle: org/openidex/resources/Bundle.properties
-OpenIDE-Module-Specification-Version: 3.30
+OpenIDE-Module-Specification-Version: 3.31
AutoUpdate-Essential-Module: true
diff -r eda741b8be66 o.openidex.util/src/org/openidex/search/SearchGroup.java
--- a/o.openidex.util/src/org/openidex/search/SearchGroup.java Tue Aug 23 08:57:54 2011 +0200
+++ b/o.openidex.util/src/org/openidex/search/SearchGroup.java Tue Aug 23 17:08:57 2011 +0200
@@ -164,9 +164,21 @@
return searchRoots.toArray(new Node[searchRoots.size()]);
}
+ /** This method is invoked when the current search is being stopped.
+ *
+ * You can override it to terminate any internal tasks that have been
+ * started to process individual found items and that take a long time
+ * to finish.
+ *
+ * The default implementation does nothing.
+ */
+ protected void onStopSearch() {
+ }
+
/** Stops searching. */
public final void stopSearch() {
stopped = true;
+ onStopSearch();
}
/**
diff -r eda741b8be66 o.openidex.util/test/unit/src/org/openidex/search/SearchGroupTest.java
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/o.openidex.util/test/unit/src/org/openidex/search/SearchGroupTest.java Tue Aug 23 17:08:57 2011 +0200
@@ -0,0 +1,153 @@
+/*
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
+ *
+ * Copyright 2011 Oracle and/or its affiliates. All rights reserved.
+ *
+ * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
+ * Other names may be trademarks of their respective owners.
+ *
+ * The contents of this file are subject to the terms of either the GNU
+ * General Public License Version 2 only ("GPL") or the Common Development and
+ * Distribution License("CDDL") (collectively, the "License"). You may not use
+ * this file except in compliance with the License. You can obtain a copy of
+ * the License at http://www.netbeans.org/cddl-gplv2.html or
+ * nbbuild/licenses/CDDL-GPL-2-CP. See the License for the specific language
+ * governing permissions and limitations under the License. When distributing
+ * the software, include this License Header Notice in each file and include
+ * the License file at nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided by
+ * Oracle in the GPL Version 2 section of the License file that accompanied
+ * this code. If applicable, add the following below the License Header, with
+ * the fields enclosed by brackets [] replaced by your own identifying
+ * information: "Portions Copyrighted [year] [name of copyright owner]"
+ *
+ * If you wish your version of this file to be governed by only the CDDL or
+ * only the GPL Version 2, indicate your decision by adding "[Contributor]
+ * elects to include this software in this distribution under the [CDDL or GPL
+ * Version 2] license." If you do not indicate a single choice of license, a
+ * recipient has the option to distribute your version of this file under
+ * either the CDDL, the GPL Version 2 or to extend the choice of license to its
+ * licensees as provided above. However, if you add GPL Version 2 code and
+ * therefore, elected the GPL Version 2 license, then the option applies only
+ * if the new code is made subject to such option by the copyright holder.
+ *
+ * Contributor(s):
+ *
+ * Portions Copyrighted 2011 Sun Microsystems, Inc.
+ */
+package org.openidex.search;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+import org.netbeans.junit.NbTestCase;
+import org.openide.nodes.Node;
+
+/**
+ *
+ * @author jhavlin
+ */
+public class SearchGroupTest extends NbTestCase {
+
+ public SearchGroupTest(String name) {
+ super(name);
+ }
+
+ /** Test that SearchGroup.onStopSearch is invoked properly.
+ */
+ public void testOnStopSearchPositive() throws InterruptedException {
+
+ FakeSearchGroup fsg = new FakeSearchGroup() {
+
+ @Override
+ protected void onStopSearch() {
+ getInnerTaks().terminate();
+ }
+ };
+ try {
+ Thread searchThread = new Thread(new SearchRunner(fsg));
+ searchThread.start();
+ Thread.sleep(10);
+ assertTrue("Search should be running now", searchThread.isAlive());
+ fsg.stopSearch();
+ Thread.sleep(10);
+ assertFalse("Search has not been stopped", searchThread.isAlive());
+
+ } finally {
+ fsg.innerTask.terminate();
+ }
+ }
+
+ /** Test that long-running internal task is not terminated unless
+ * method SearchGroup.onStopSearch is overriden to manage it.
+ */
+ public void testOnStopSearchNegative() throws InterruptedException {
+
+ FakeSearchGroup fsg = new FakeSearchGroup();
+ try {
+ Thread searchThread = new Thread(new SearchRunner(fsg));
+ searchThread.start();
+ Thread.sleep(10);
+ assertTrue("Search should be running now", searchThread.isAlive());
+ fsg.stopSearch();
+ Thread.sleep(10);
+ assertTrue("Search should be still running", searchThread.isAlive());
+ fsg.getInnerTaks().terminate(); // terminate inner task explicitly
+ Thread.sleep(10);
+ assertFalse("Inner task wasn't stopped", searchThread.isAlive());
+ } finally {
+ fsg.getInnerTaks().terminate();
+ }
+ }
+
+ /** Helper class for simulating internal long-running job. */
+ private static class TerminatableLongTask {
+
+ private AtomicBoolean stopped = new AtomicBoolean(false);
+
+ public void start() {
+ while (!stopped.get()) {
+ }
+ }
+
+ public final void terminate() {
+ stopped.set(true);
+ }
+ }
+
+ /** Helper trivial implementation of SearchGroup that contains internal
+ * long-running task. */
+ private static class FakeSearchGroup extends SearchGroup {
+
+ private TerminatableLongTask innerTask = new TerminatableLongTask();
+
+ @Override
+ protected void doSearch() {
+ innerTask.start();
+ }
+
+ @Override
+ public Node getNodeForFoundObject(Object object) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ public TerminatableLongTask getInnerTaks() {
+ return innerTask;
+ }
+ }
+
+ /** Helper Runnable for starting a search group in a new thread.
+ */
+ private static class SearchRunner implements Runnable {
+
+ private SearchGroup sg;
+
+ public SearchRunner(SearchGroup sg) {
+ this.sg = sg;
+ }
+
+ @Override
+ public void run() {
+ sg.prepareSearch();
+ sg.doSearch();
+ }
+ }
+}
diff -r eda741b8be66 utilities/nbproject/project.xml
--- a/utilities/nbproject/project.xml Tue Aug 23 08:57:54 2011 +0200
+++ b/utilities/nbproject/project.xml Tue Aug 23 17:08:57 2011 +0200
@@ -194,7 +194,7 @@
3
- 3.20
+ 3.31
diff -r eda741b8be66 utilities/src/org/netbeans/modules/search/BasicSearchCriteria.java
--- a/utilities/src/org/netbeans/modules/search/BasicSearchCriteria.java Tue Aug 23 08:57:54 2011 +0200
+++ b/utilities/src/org/netbeans/modules/search/BasicSearchCriteria.java Tue Aug 23 17:08:57 2011 +0200
@@ -66,6 +66,7 @@
import org.openide.loaders.DataObject;
import org.openide.loaders.DataObjectNotFoundException;
import org.openide.nodes.Node;
+import org.openide.util.NbBundle;
import org.openidex.search.SearchPattern;
import static java.util.logging.Level.FINER;
import static java.util.logging.Level.FINEST;
@@ -91,6 +92,13 @@
/** array of searchable application/x-suffix MIME-type suffixes */
private static final Collection searchableXMimeTypes;
+
+ /** List of currently processed searches. */
+ private List currentlyProcessedSequences =
+ new ArrayList(1);
+
+ /** Termination flag */
+ private boolean terminated = false;
static {
searchableXMimeTypes = new HashSet(17);
@@ -670,6 +678,10 @@
assert !textPatternValid || (textPattern != null);
assert !fileNamePatternValid || (fileNamePattern != null);
+ synchronized (this) {
+ terminated = false;
+ currentlyProcessedSequences.clear();
+ }
}
/**
@@ -767,13 +779,16 @@
*/
private boolean checkFileContent(FileObject fo) {
assert fo != null;
+ assureMemory(fo);
lastCharset = FileEncodingQuery.getEncoding(fo);
SearchPattern sp = createSearchPattern();
BufferedCharSequence bcs = null;
try {
InputStream stream = fo.getInputStream();
bcs = new BufferedCharSequence(stream, lastCharset, fo.getSize());
+ registerProcessedSequence(bcs);
ArrayList txtDetails = getTextDetails(bcs, fo, sp);
+ unregisterProcessedSequence(bcs);
if (txtDetails.isEmpty()){
return false;
}
@@ -818,6 +833,53 @@
return false;
}
+ /** Assure that there is enough available memory for searching in file.
+ * Idea and code copied from org.openidex.search.DataObjectSearchGroup.
+ * Uses rough estimate of memory requirements based on file size.
+ */
+ private void assureMemory(FileObject fo) {
+
+ long fileSize = fo.getSize();
+ long requiredEstimate = fileSize * 3;
+ if ((int) requiredEstimate < 0) {
+ // Integer overflow.
+ throwNoMemoryExeption(fo, fileSize);
+ }
+ if (getAvailableMemory() < requiredEstimate) {
+ System.gc();
+ try {
+ byte[] gcProvocation = new byte[(int) requiredEstimate];
+ gcProvocation[(int) fileSize] = 42;
+ gcProvocation = null;
+ } catch (OutOfMemoryError ooe) {
+ throwNoMemoryExeption(fo, fileSize);
+ }
+ }
+ }
+
+ /** Get approximation of available memory in bytes. */
+ private long getAvailableMemory() {
+
+ Runtime rt = Runtime.getRuntime();
+
+ long max = rt.maxMemory();
+ long free = rt.freeMemory();
+ long total = rt.totalMemory();
+
+ long available = (max - total) + free;
+ return available;
+ }
+
+ /** Throw an exception telling that there is not enough memory for searching
+ * in a file.
+ */
+ private void throwNoMemoryExeption(FileObject fo, long fileSize) {
+ String text = NbBundle.getMessage(
+ BasicSearchCriteria.class, "TEXT_MSG_NOT_ENOUGH_MEMORY",
+ fo.getNameExt(), fileSize / 1024);
+ throw new RuntimeException(text);
+ }
+
private ArrayList getTextDetails(BufferedCharSequence bcs,
FileObject fo,
SearchPattern sp)
@@ -827,8 +889,9 @@
ArrayList txtDetails = new ArrayList();
FindState fs = new FindState(bcs);
+ final int limit = ResultModel.Limit.MATCHES_COUNT_LIMIT.getValue();
Matcher matcher = textPattern.matcher(bcs);
- while (matcher.find()) {
+ while (matcher.find() && txtDetails.size() < limit) {
int matcherStart = matcher.start();
int column = fs.calcColumn(matcherStart);
@@ -1027,4 +1090,29 @@
}
} // FindState
+ /** Register BufferedCharSequence that is being processed by this object.
+ * It is used when user needs to terminate the current search. */
+ private synchronized void registerProcessedSequence(
+ BufferedCharSequence bcs) throws IOException {
+ if (terminated) {
+ bcs.close();
+ } else {
+ currentlyProcessedSequences.add(bcs);
+ }
+ }
+
+ /** Unregister a BufferedCharSequence after it was processed. */
+ private synchronized void unregisterProcessedSequence(
+ BufferedCharSequence bcc) {
+ currentlyProcessedSequences.remove(bcc);
+ }
+
+ /** Stop all searches that are processed by this instance. */
+ synchronized void terminateCurrentSearches() throws IOException {
+ for (BufferedCharSequence bcs: currentlyProcessedSequences) {
+ bcs.close();
+ }
+ currentlyProcessedSequences.clear();
+ terminated = true;
+ }
}
diff -r eda741b8be66 utilities/src/org/netbeans/modules/search/BufferedCharSequence.java
--- a/utilities/src/org/netbeans/modules/search/BufferedCharSequence.java Tue Aug 23 08:57:54 2011 +0200
+++ b/utilities/src/org/netbeans/modules/search/BufferedCharSequence.java Tue Aug 23 17:08:57 2011 +0200
@@ -230,13 +230,15 @@
* @return the underlying instance of this class.
* @throws IOException if an I/O error occurs.
*/
- public BufferedCharSequence close() throws IOException {
- reset();
- source.close();
- source = null;
- sink = null;
- coderResult = null;
- isClosed = true;
+ public synchronized BufferedCharSequence close() throws IOException {
+ if (!isClosed) {
+ reset();
+ source.close();
+ source = null;
+ sink = null;
+ coderResult = null;
+ isClosed = true;
+ }
return this;
}
diff -r eda741b8be66 utilities/src/org/netbeans/modules/search/Bundle.properties
--- a/utilities/src/org/netbeans/modules/search/Bundle.properties Tue Aug 23 08:57:54 2011 +0200
+++ b/utilities/src/org/netbeans/modules/search/Bundle.properties Tue Aug 23 17:08:57 2011 +0200
@@ -109,6 +109,7 @@
TEXT_MSG_FOUND_X_NODES_LIMIT=Found {1} {1,choice,1#match|1