Index: solr/core/src/java/org/apache/solr/analysis/HunspellStemFilterFactory.java =================================================================== --- solr/core/src/java/org/apache/solr/analysis/HunspellStemFilterFactory.java (revision 1354796) +++ solr/core/src/java/org/apache/solr/analysis/HunspellStemFilterFactory.java (working copy) @@ -28,6 +28,7 @@ import org.apache.lucene.analysis.util.ResourceLoader; import org.apache.lucene.analysis.util.ResourceLoaderAware; import org.apache.lucene.analysis.util.TokenFilterFactory; +import org.apache.lucene.util.IOUtils; /** * TokenFilterFactory that creates instances of {@link org.apache.lucene.analysis.hunspell.HunspellStemFilter}. @@ -76,7 +77,6 @@ else throw new InitializationException("Unknown value for " + PARAM_IGNORE_CASE + ": " + pic + ". Must be true or false"); } - String strictAffixParsingParam = args.get(PARAM_STRICT_AFFIX_PARSING); boolean strictAffixParsing = true; if(strictAffixParsingParam != null) { @@ -85,14 +85,22 @@ else throw new InitializationException("Unknown value for " + PARAM_STRICT_AFFIX_PARSING + ": " + strictAffixParsingParam + ". Must be true or false"); } + InputStream affix = null; + List dictionaries = new ArrayList(); + try { - List dictionaries = new ArrayList(); + dictionaries = new ArrayList(); for (String file : dictionaryFiles) { dictionaries.add(loader.openResource(file)); } - this.dictionary = new HunspellDictionary(loader.openResource(affixFile), dictionaries, luceneMatchVersion, ignoreCase, strictAffixParsing); + affix = loader.openResource(affixFile); + + this.dictionary = new HunspellDictionary(affix, dictionaries, luceneMatchVersion, ignoreCase, strictAffixParsing); } catch (Exception e) { throw new InitializationException("Unable to load hunspell data! [dictionary=" + args.get("dictionary") + ",affix=" + affixFile + "]", e); + } finally { + IOUtils.closeWhileHandlingException(affix); + IOUtils.closeWhileHandlingException(dictionaries); } } Index: lucene/analysis/common/src/test/org/apache/lucene/analysis/hunspell/HunspellDictionaryTest.java =================================================================== --- lucene/analysis/common/src/test/org/apache/lucene/analysis/hunspell/HunspellDictionaryTest.java (revision 1354796) +++ lucene/analysis/common/src/test/org/apache/lucene/analysis/hunspell/HunspellDictionaryTest.java (working copy) @@ -17,21 +17,100 @@ * limitations under the License. */ -import org.apache.lucene.util.LuceneTestCase; -import org.apache.lucene.util.Version; -import org.junit.Assert; -import org.junit.Test; - import java.io.IOException; import java.io.InputStream; import java.text.ParseException; import java.util.Arrays; -import static junit.framework.Assert.assertEquals; +import org.apache.lucene.util.LuceneTestCase; +import org.junit.Assert; +import org.junit.Test; public class HunspellDictionaryTest extends LuceneTestCase { + + private class CloseCheckInputStream extends InputStream { + private InputStream delegate; + + private boolean closed = false; + public CloseCheckInputStream(InputStream delegate) { + super(); + this.delegate = delegate; + } + + public int read() throws IOException { + return delegate.read(); + } + + public int hashCode() { + return delegate.hashCode(); + } + + public int read(byte[] b) throws IOException { + return delegate.read(b); + } + + public boolean equals(Object obj) { + return delegate.equals(obj); + } + + public int read(byte[] b, int off, int len) throws IOException { + return delegate.read(b, off, len); + } + + public long skip(long n) throws IOException { + return delegate.skip(n); + } + + public String toString() { + return delegate.toString(); + } + + public int available() throws IOException { + return delegate.available(); + } + + public void close() throws IOException { + this.closed = true; + delegate.close(); + } + + public void mark(int readlimit) { + delegate.mark(readlimit); + } + + public void reset() throws IOException { + delegate.reset(); + } + + public boolean markSupported() { + return delegate.markSupported(); + } + + public boolean isClosed() { + return this.closed; + } + + } + @Test + public void testResourceCleanup() throws IOException, ParseException { + CloseCheckInputStream affixStream = new CloseCheckInputStream(getClass().getResourceAsStream("testCompressed.aff")); + CloseCheckInputStream dictStream = new CloseCheckInputStream(getClass().getResourceAsStream("testCompressed.dic")); + + new HunspellDictionary(affixStream, dictStream, TEST_VERSION_CURRENT); + + assertFalse(affixStream.isClosed()); + assertFalse(dictStream.isClosed()); + + affixStream.close(); + dictStream.close(); + + assertTrue(affixStream.isClosed()); + assertTrue(dictStream.isClosed()); + } + + @Test public void testHunspellDictionary_loadDicAff() throws IOException, ParseException { InputStream affixStream = getClass().getResourceAsStream("test.aff"); InputStream dictStream = getClass().getResourceAsStream("test.dic"); @@ -40,7 +119,7 @@ assertEquals(3, dictionary.lookupSuffix(new char[]{'e'}, 0, 1).size()); assertEquals(1, dictionary.lookupPrefix(new char[]{'s'}, 0, 1).size()); assertEquals(1, dictionary.lookupWord(new char[]{'o', 'l', 'r'}, 0, 3).size()); - + affixStream.close(); dictStream.close(); } @@ -54,7 +133,7 @@ assertEquals(3, dictionary.lookupSuffix(new char[]{'e'}, 0, 1).size()); assertEquals(1, dictionary.lookupPrefix(new char[]{'s'}, 0, 1).size()); assertEquals(1, dictionary.lookupWord(new char[]{'o', 'l', 'r'}, 0, 3).size()); - + affixStream.close(); dictStream.close(); } @@ -69,7 +148,9 @@ assertEquals(1, dictionary.lookupPrefix(new char[]{'s'}, 0, 1).size()); assertEquals(1, dictionary.lookupWord(new char[]{'o', 'l', 'r'}, 0, 3).size()); //strict parsing disabled: malformed rule is not loaded - assertNull(dictionary.lookupPrefix(new char[]{'a'}, 0, 1)); + assertNull(dictionary.lookupPrefix(new char[]{'a'}, 0, 1)); + affixStream.close(); + dictStream.close(); affixStream = getClass().getResourceAsStream("testWrongAffixRule.aff"); dictStream = getClass().getResourceAsStream("test.dic"); @@ -81,7 +162,7 @@ Assert.assertEquals("The affix file contains a rule with less than five elements", e.getMessage()); Assert.assertEquals(23, e.getErrorOffset()); } - + affixStream.close(); dictStream.close(); } Index: lucene/analysis/common/src/java/org/apache/lucene/analysis/hunspell/HunspellDictionary.java =================================================================== --- lucene/analysis/common/src/java/org/apache/lucene/analysis/hunspell/HunspellDictionary.java (revision 1354796) +++ lucene/analysis/common/src/java/org/apache/lucene/analysis/hunspell/HunspellDictionary.java (working copy) @@ -66,10 +66,11 @@ /** * Creates a new HunspellDictionary containing the information read from the provided InputStreams to hunspell affix - * and dictionary files + * and dictionary files. + * You have to close the provided InputStreams yourself. * - * @param affix InputStream for reading the hunspell affix file - * @param dictionary InputStream for reading the hunspell dictionary file + * @param affix InputStream for reading the hunspell affix file (won't be closed). + * @param dictionary InputStream for reading the hunspell dictionary file (won't be closed). * @param version Lucene Version * @throws IOException Can be thrown while reading from the InputStreams * @throws ParseException Can be thrown if the content of the files does not meet expected formats @@ -80,10 +81,11 @@ /** * Creates a new HunspellDictionary containing the information read from the provided InputStreams to hunspell affix - * and dictionary files + * and dictionary files. + * You have to close the provided InputStreams yourself. * - * @param affix InputStream for reading the hunspell affix file - * @param dictionary InputStream for reading the hunspell dictionary file + * @param affix InputStream for reading the hunspell affix file (won't be closed). + * @param dictionary InputStream for reading the hunspell dictionary file (won't be closed). * @param version Lucene Version * @param ignoreCase If true, dictionary matching will be case insensitive * @throws IOException Can be thrown while reading from the InputStreams @@ -95,10 +97,11 @@ /** * Creates a new HunspellDictionary containing the information read from the provided InputStreams to hunspell affix - * and dictionary files + * and dictionary files. + * You have to close the provided InputStreams yourself. * - * @param affix InputStream for reading the hunspell affix file - * @param dictionaries InputStreams for reading the hunspell dictionary file + * @param affix InputStream for reading the hunspell affix file (won't be closed). + * @param dictionaries InputStreams for reading the hunspell dictionary file (won't be closed). * @param version Lucene Version * @param ignoreCase If true, dictionary matching will be case insensitive * @throws IOException Can be thrown while reading from the InputStreams @@ -110,10 +113,11 @@ /** * Creates a new HunspellDictionary containing the information read from the provided InputStreams to hunspell affix - * and dictionary files + * and dictionary files. + * You have to close the provided InputStreams yourself. * - * @param affix InputStream for reading the hunspell affix file - * @param dictionaries InputStreams for reading the hunspell dictionary file + * @param affix InputStream for reading the hunspell affix file (won't be closed). + * @param dictionaries InputStreams for reading the hunspell dictionary file (won't be closed). * @param version Lucene Version * @param ignoreCase If true, dictionary matching will be case insensitive * @param strictAffixParsing Affix strict parsing enabled or not (an error while reading a rule causes exception or is ignored) @@ -194,7 +198,6 @@ flagParsingStrategy = getFlagParsingStrategy(line); } } - reader.close(); } /**