diff --git a/CHANGES.txt b/CHANGES.txt index 89135fdba3f..16b9d69dd0f 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -29,6 +29,11 @@ API Changes share an index over NFS by customizing when prior commits are deleted. (Mike McCandless) + 4. LUCENE-818: changed most public methods of IndexWriter, + IndexReader (and its subclasses), FieldsReader and RAMDirectory to + throw AlreadyClosedException if they are accessed after being + closed. (Mike McCandless) + Bug fixes 1. LUCENE-804: Fixed build.xml to pack a fully compilable src dist. (Doron Cohen) diff --git a/src/java/org/apache/lucene/index/FieldsReader.java b/src/java/org/apache/lucene/index/FieldsReader.java index 4f0898b6353..6a56883c38a 100644 --- a/src/java/org/apache/lucene/index/FieldsReader.java +++ b/src/java/org/apache/lucene/index/FieldsReader.java @@ -20,6 +20,7 @@ package org.apache.lucene.index; import org.apache.lucene.document.*; import org.apache.lucene.store.Directory; import org.apache.lucene.store.IndexInput; +import org.apache.lucene.store.AlreadyClosedException; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -46,6 +47,7 @@ final class FieldsReader { private final IndexInput indexStream; private int size; + private boolean closed; private ThreadLocal fieldsStreamTL = new ThreadLocal(); @@ -58,6 +60,15 @@ final class FieldsReader { size = (int) (indexStream.length() / 8); } + /** + * @throws AlreadyClosedException if this FieldsReader is closed + */ + protected final void ensureOpen() throws AlreadyClosedException { + if (closed) { + throw new AlreadyClosedException("this FieldsReader is closed"); + } + } + /** * Closes the underlying {@link org.apache.lucene.store.IndexInput} streams, including any ones associated with a * lazy implementation of a Field. This means that the Fields values will not be accessible. @@ -65,13 +76,16 @@ final class FieldsReader { * @throws IOException */ final void close() throws IOException { - fieldsStream.close(); - cloneableFieldsStream.close(); - indexStream.close(); - IndexInput localFieldsStream = (IndexInput) fieldsStreamTL.get(); - if (localFieldsStream != null) { - localFieldsStream.close(); - fieldsStreamTL.set(null); + if (!closed) { + fieldsStream.close(); + cloneableFieldsStream.close(); + indexStream.close(); + IndexInput localFieldsStream = (IndexInput) fieldsStreamTL.get(); + if (localFieldsStream != null) { + localFieldsStream.close(); + fieldsStreamTL.set(null); + } + closed = true; } } @@ -323,6 +337,7 @@ final class FieldsReader { * binaryValue() must be set. */ public byte[] binaryValue() { + ensureOpen(); if (fieldsData == null) { final byte[] b = new byte[toRead]; IndexInput localFieldsStream = getFieldStream(); @@ -349,6 +364,7 @@ final class FieldsReader { * and binaryValue() must be set. */ public Reader readerValue() { + ensureOpen(); return fieldsData instanceof Reader ? (Reader) fieldsData : null; } @@ -358,6 +374,7 @@ final class FieldsReader { * binaryValue() must be set. */ public String stringValue() { + ensureOpen(); if (fieldsData == null) { IndexInput localFieldsStream = getFieldStream(); try { @@ -380,18 +397,22 @@ final class FieldsReader { } public long getPointer() { + ensureOpen(); return pointer; } public void setPointer(long pointer) { + ensureOpen(); this.pointer = pointer; } public int getToRead() { + ensureOpen(); return toRead; } public void setToRead(int toRead) { + ensureOpen(); this.toRead = toRead; } } diff --git a/src/java/org/apache/lucene/index/FilterIndexReader.java b/src/java/org/apache/lucene/index/FilterIndexReader.java index 99dc3e36618..f23c0e79d49 100644 --- a/src/java/org/apache/lucene/index/FilterIndexReader.java +++ b/src/java/org/apache/lucene/index/FilterIndexReader.java @@ -92,43 +92,84 @@ public class FilterIndexReader extends IndexReader { public TermFreqVector[] getTermFreqVectors(int docNumber) throws IOException { + ensureOpen(); return in.getTermFreqVectors(docNumber); } public TermFreqVector getTermFreqVector(int docNumber, String field) throws IOException { + ensureOpen(); return in.getTermFreqVector(docNumber, field); } - public int numDocs() { return in.numDocs(); } - public int maxDoc() { return in.maxDoc(); } + public int numDocs() { + // Don't call ensureOpen() here (it could affect performance) + return in.numDocs(); + } - public Document document(int n, FieldSelector fieldSelector) throws CorruptIndexException, IOException { return in.document(n, fieldSelector); } + public int maxDoc() { + // Don't call ensureOpen() here (it could affect performance) + return in.maxDoc(); + } + + public Document document(int n, FieldSelector fieldSelector) throws CorruptIndexException, IOException { + ensureOpen(); + return in.document(n, fieldSelector); + } + + public boolean isDeleted(int n) { + // Don't call ensureOpen() here (it could affect performance) + return in.isDeleted(n); + } + + public boolean hasDeletions() { + // Don't call ensureOpen() here (it could affect performance) + return in.hasDeletions(); + } - public boolean isDeleted(int n) { return in.isDeleted(n); } - public boolean hasDeletions() { return in.hasDeletions(); } protected void doUndeleteAll() throws CorruptIndexException, IOException {in.undeleteAll();} public boolean hasNorms(String field) throws IOException { + ensureOpen(); return in.hasNorms(field); } - public byte[] norms(String f) throws IOException { return in.norms(f); } + public byte[] norms(String f) throws IOException { + ensureOpen(); + return in.norms(f); + } + public void norms(String f, byte[] bytes, int offset) throws IOException { + ensureOpen(); in.norms(f, bytes, offset); } + protected void doSetNorm(int d, String f, byte b) throws CorruptIndexException, IOException { in.setNorm(d, f, b); } - public TermEnum terms() throws IOException { return in.terms(); } - public TermEnum terms(Term t) throws IOException { return in.terms(t); } + public TermEnum terms() throws IOException { + ensureOpen(); + return in.terms(); + } - public int docFreq(Term t) throws IOException { return in.docFreq(t); } + public TermEnum terms(Term t) throws IOException { + ensureOpen(); + return in.terms(t); + } - public TermDocs termDocs() throws IOException { return in.termDocs(); } + public int docFreq(Term t) throws IOException { + ensureOpen(); + return in.docFreq(t); + } + + public TermDocs termDocs() throws IOException { + ensureOpen(); + return in.termDocs(); + } public TermPositions termPositions() throws IOException { + ensureOpen(); return in.termPositions(); } @@ -138,9 +179,17 @@ public class FilterIndexReader extends IndexReader { public Collection getFieldNames(IndexReader.FieldOption fieldNames) { + ensureOpen(); return in.getFieldNames(fieldNames); } - public long getVersion() { return in.getVersion(); } - public boolean isCurrent() throws CorruptIndexException, IOException { return in.isCurrent(); } + public long getVersion() { + ensureOpen(); + return in.getVersion(); + } + + public boolean isCurrent() throws CorruptIndexException, IOException { + ensureOpen(); + return in.isCurrent(); + } } diff --git a/src/java/org/apache/lucene/index/IndexReader.java b/src/java/org/apache/lucene/index/IndexReader.java index 625d1684279..b0c21c535e8 100644 --- a/src/java/org/apache/lucene/index/IndexReader.java +++ b/src/java/org/apache/lucene/index/IndexReader.java @@ -25,6 +25,7 @@ import org.apache.lucene.store.FSDirectory; import org.apache.lucene.store.IndexInput; import org.apache.lucene.store.Lock; import org.apache.lucene.store.LockObtainFailedException; +import org.apache.lucene.store.AlreadyClosedException; import java.io.File; import java.io.FileOutputStream; @@ -115,7 +116,16 @@ public abstract class IndexReader { private boolean directoryOwner; private boolean closeDirectory; private IndexDeletionPolicy deletionPolicy; - private boolean isClosed; + private boolean closed; + + /** + * @throws AlreadyClosedException if this IndexReader is closed + */ + protected final void ensureOpen() throws AlreadyClosedException { + if (closed) { + throw new AlreadyClosedException("this IndexReader is closed"); + } + } private SegmentInfos segmentInfos; private Lock writeLock; @@ -208,8 +218,12 @@ public abstract class IndexReader { }.run(); } - /** Returns the directory this index resides in. */ - public Directory directory() { return directory; } + /** Returns the directory this index resides in. + */ + public Directory directory() { + ensureOpen(); + return directory; + } /** * Returns the time the index in the named directory was last modified. @@ -301,6 +315,7 @@ public abstract class IndexReader { * Version number when this IndexReader was opened. */ public long getVersion() { + ensureOpen(); return segmentInfos.getVersion(); } @@ -313,6 +328,7 @@ public abstract class IndexReader { * @throws IOException if there is a low-level IO error */ public boolean isCurrent() throws CorruptIndexException, IOException { + ensureOpen(); return SegmentInfos.readCurrentVersion(directory) == segmentInfos.getVersion(); } @@ -321,7 +337,8 @@ public abstract class IndexReader { * @return true if the index is optimized; false otherwise */ public boolean isOptimized() { - return segmentInfos.size() == 1 && hasDeletions() == false; + ensureOpen(); + return segmentInfos.size() == 1 && hasDeletions() == false; } /** @@ -407,6 +424,7 @@ public abstract class IndexReader { * @throws IOException if there is a low-level IO error */ public Document document(int n) throws CorruptIndexException, IOException { + ensureOpen(); return document(n, null); } @@ -445,6 +463,7 @@ public abstract class IndexReader { public boolean hasNorms(String field) throws IOException { // backward compatible implementation. // SegmentReader has an efficient implementation. + ensureOpen(); return norms(field) != null; } @@ -477,11 +496,11 @@ public abstract class IndexReader { * @throws LockObtainFailedException if another writer * has this index open (write.lock could not * be obtained) - * @throws IOException if this reader was closed already - * or there is a low-level IO error + * @throws IOException if there is a low-level IO error */ public final synchronized void setNorm(int doc, String field, byte value) throws StaleReaderException, CorruptIndexException, LockObtainFailedException, IOException { + ensureOpen(); if(directoryOwner) acquireWriteLock(); hasChanges = true; @@ -504,27 +523,31 @@ public abstract class IndexReader { * @throws LockObtainFailedException if another writer * has this index open (write.lock could not * be obtained) - * @throws IOException if this reader was closed already - * or there is a low-level IO error + * @throws IOException if there is a low-level IO error */ public void setNorm(int doc, String field, float value) throws StaleReaderException, CorruptIndexException, LockObtainFailedException, IOException { + ensureOpen(); setNorm(doc, field, Similarity.encodeNorm(value)); } /** Returns an enumeration of all the terms in the index. * The enumeration is ordered by Term.compareTo(). Each term * is greater than all that precede it in the enumeration. + * @throws IOException if there is a low-level IO error */ public abstract TermEnum terms() throws IOException; /** Returns an enumeration of all terms after a given term. * The enumeration is ordered by Term.compareTo(). Each term * is greater than all that precede it in the enumeration. + * @throws IOException if there is a low-level IO error */ public abstract TermEnum terms(Term t) throws IOException; - /** Returns the number of documents containing the term t. */ + /** Returns the number of documents containing the term t. + * @throws IOException if there is a low-level IO error + */ public abstract int docFreq(Term t) throws IOException; /** Returns an enumeration of all the documents which contain @@ -536,14 +559,18 @@ public abstract class IndexReader { * *

The enumeration is ordered by document number. Each document number * is greater than all that precede it in the enumeration. + * @throws IOException if there is a low-level IO error */ public TermDocs termDocs(Term term) throws IOException { + ensureOpen(); TermDocs termDocs = termDocs(); termDocs.seek(term); return termDocs; } - /** Returns an unpositioned {@link TermDocs} enumerator. */ + /** Returns an unpositioned {@link TermDocs} enumerator. + * @throws IOException if there is a low-level IO error + */ public abstract TermDocs termDocs() throws IOException; /** Returns an enumeration of all the documents which contain @@ -561,14 +588,18 @@ public abstract class IndexReader { *

This positional information faciliates phrase and proximity searching. *

The enumeration is ordered by document number. Each document number is * greater than all that precede it in the enumeration. + * @throws IOException if there is a low-level IO error */ public TermPositions termPositions(Term term) throws IOException { + ensureOpen(); TermPositions termPositions = termPositions(); termPositions.seek(term); return termPositions; } - /** Returns an unpositioned {@link TermPositions} enumerator. */ + /** Returns an unpositioned {@link TermPositions} enumerator. + * @throws IOException if there is a low-level IO error + */ public abstract TermPositions termPositions() throws IOException; /** @@ -584,10 +615,9 @@ public abstract class IndexReader { * @throws IOException if there is a low-level IO error */ private void acquireWriteLock() throws StaleReaderException, CorruptIndexException, LockObtainFailedException, IOException { + ensureOpen(); if (stale) throw new StaleReaderException("IndexReader out of date and no longer valid for delete, undelete, or setNorm operations"); - if (isClosed) - throw new IOException("this reader is closed"); if (writeLock == null) { Lock writeLock = directory.makeLock(IndexWriter.WRITE_LOCK_NAME); @@ -620,10 +650,10 @@ public abstract class IndexReader { * @throws LockObtainFailedException if another writer * has this index open (write.lock could not * be obtained) - * @throws IOException if this reader was closed already - * or there is a low-level IO error + * @throws IOException if there is a low-level IO error */ public final synchronized void deleteDocument(int docNum) throws StaleReaderException, CorruptIndexException, LockObtainFailedException, IOException { + ensureOpen(); if(directoryOwner) acquireWriteLock(); hasChanges = true; @@ -652,10 +682,10 @@ public abstract class IndexReader { * @throws LockObtainFailedException if another writer * has this index open (write.lock could not * be obtained) - * @throws IOException if this reader was closed already - * or there is a low-level IO error + * @throws IOException if there is a low-level IO error */ public final int deleteDocuments(Term term) throws StaleReaderException, CorruptIndexException, LockObtainFailedException, IOException { + ensureOpen(); TermDocs docs = termDocs(term); if (docs == null) return 0; int n = 0; @@ -678,10 +708,10 @@ public abstract class IndexReader { * has this index open (write.lock could not * be obtained) * @throws CorruptIndexException if the index is corrupt - * @throws IOException if this reader was closed already - * or there is a low-level IO error + * @throws IOException if there is a low-level IO error */ public final synchronized void undeleteAll() throws StaleReaderException, CorruptIndexException, LockObtainFailedException, IOException { + ensureOpen(); if(directoryOwner) acquireWriteLock(); hasChanges = true; @@ -790,19 +820,16 @@ public abstract class IndexReader { * Closes files associated with this index. * Also saves any new deletions to disk. * No other methods should be called after this has been called. - * @throws IOException if this reader was closed already - * or there is a low-level IO error + * @throws IOException if there is a low-level IO error */ public final synchronized void close() throws IOException { - if (directoryOwner && isClosed) { - throw new IOException("this reader is already closed"); - } - commit(); - doClose(); - if(closeDirectory) - directory.close(); - if (directoryOwner) { - isClosed = true; + if (!closed) { + commit(); + doClose(); + if (directoryOwner) + closed = true; + if(closeDirectory) + directory.close(); } } diff --git a/src/java/org/apache/lucene/index/IndexWriter.java b/src/java/org/apache/lucene/index/IndexWriter.java index 6eb1b412a4e..09383a86f88 100644 --- a/src/java/org/apache/lucene/index/IndexWriter.java +++ b/src/java/org/apache/lucene/index/IndexWriter.java @@ -24,6 +24,7 @@ import org.apache.lucene.store.Directory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.store.Lock; import org.apache.lucene.store.LockObtainFailedException; +import org.apache.lucene.store.AlreadyClosedException; import org.apache.lucene.store.RAMDirectory; import java.io.File; @@ -208,6 +209,16 @@ public class IndexWriter { private boolean useCompoundFile = true; private boolean closeDir; + private boolean closed; + + /** + * @throws AlreadyClosedException if this IndexWriter is closed + */ + protected final void ensureOpen() throws AlreadyClosedException { + if (closed) { + throw new AlreadyClosedException("this IndexWriter is closed"); + } + } /** Get the current setting of whether to use the compound file format. * Note that this just returns the value you set with setUseCompoundFile(boolean) @@ -215,6 +226,7 @@ public class IndexWriter { * @see #setUseCompoundFile(boolean) */ public boolean getUseCompoundFile() { + ensureOpen(); return useCompoundFile; } @@ -223,6 +235,7 @@ public class IndexWriter { * is finished. This is done regardless of what directory is in use. */ public void setUseCompoundFile(boolean value) { + ensureOpen(); useCompoundFile = value; } @@ -231,6 +244,7 @@ public class IndexWriter { * @see Similarity#setDefault(Similarity) */ public void setSimilarity(Similarity similarity) { + ensureOpen(); this.similarity = similarity; } @@ -239,6 +253,7 @@ public class IndexWriter { *

This defaults to the current value of {@link Similarity#getDefault()}. */ public Similarity getSimilarity() { + ensureOpen(); return this.similarity; } @@ -264,6 +279,7 @@ public class IndexWriter { * @see #DEFAULT_TERM_INDEX_INTERVAL */ public void setTermIndexInterval(int interval) { + ensureOpen(); this.termIndexInterval = interval; } @@ -271,7 +287,10 @@ public class IndexWriter { * * @see #setTermIndexInterval(int) */ - public int getTermIndexInterval() { return termIndexInterval; } + public int getTermIndexInterval() { + ensureOpen(); + return termIndexInterval; + } /** * Constructs an IndexWriter for the index in path. @@ -580,6 +599,7 @@ public class IndexWriter { *

The default value is {@link Integer#MAX_VALUE}. */ public void setMaxMergeDocs(int maxMergeDocs) { + ensureOpen(); this.maxMergeDocs = maxMergeDocs; } @@ -587,6 +607,7 @@ public class IndexWriter { * @see #setMaxMergeDocs */ public int getMaxMergeDocs() { + ensureOpen(); return maxMergeDocs; } @@ -603,6 +624,7 @@ public class IndexWriter { * By default, no more than 10,000 terms will be indexed for a field. */ public void setMaxFieldLength(int maxFieldLength) { + ensureOpen(); this.maxFieldLength = maxFieldLength; } @@ -610,6 +632,7 @@ public class IndexWriter { * @see #setMaxFieldLength */ public int getMaxFieldLength() { + ensureOpen(); return maxFieldLength; } @@ -624,6 +647,7 @@ public class IndexWriter { * @throws IllegalArgumentException if maxBufferedDocs is smaller than 2 */ public void setMaxBufferedDocs(int maxBufferedDocs) { + ensureOpen(); if (maxBufferedDocs < 2) throw new IllegalArgumentException("maxBufferedDocs must at least be 2"); this.minMergeDocs = maxBufferedDocs; @@ -633,6 +657,7 @@ public class IndexWriter { * @see #setMaxBufferedDocs */ public int getMaxBufferedDocs() { + ensureOpen(); return minMergeDocs; } @@ -646,6 +671,7 @@ public class IndexWriter { * @throws IllegalArgumentException if maxBufferedDeleteTerms is smaller than 1

*/ public void setMaxBufferedDeleteTerms(int maxBufferedDeleteTerms) { + ensureOpen(); if (maxBufferedDeleteTerms < 1) throw new IllegalArgumentException("maxBufferedDeleteTerms must at least be 1"); this.maxBufferedDeleteTerms = maxBufferedDeleteTerms; @@ -655,6 +681,7 @@ public class IndexWriter { * @see #setMaxBufferedDeleteTerms */ public int getMaxBufferedDeleteTerms() { + ensureOpen(); return maxBufferedDeleteTerms; } @@ -669,6 +696,7 @@ public class IndexWriter { *

This must never be less than 2. The default value is 10. */ public void setMergeFactor(int mergeFactor) { + ensureOpen(); if (mergeFactor < 2) throw new IllegalArgumentException("mergeFactor cannot be less than 2"); this.mergeFactor = mergeFactor; @@ -678,6 +706,7 @@ public class IndexWriter { * @see #setMergeFactor */ public int getMergeFactor() { + ensureOpen(); return mergeFactor; } @@ -701,6 +730,7 @@ public class IndexWriter { * to this. */ public void setInfoStream(PrintStream infoStream) { + ensureOpen(); this.infoStream = infoStream; deleter.setInfoStream(infoStream); } @@ -709,6 +739,7 @@ public class IndexWriter { * @see #setInfoStream */ public PrintStream getInfoStream() { + ensureOpen(); return infoStream; } @@ -717,6 +748,7 @@ public class IndexWriter { * @see #setDefaultWriteLockTimeout to change the default value for all instances of IndexWriter. */ public void setWriteLockTimeout(long writeLockTimeout) { + ensureOpen(); this.writeLockTimeout = writeLockTimeout; } @@ -724,6 +756,7 @@ public class IndexWriter { * @see #setWriteLockTimeout */ public long getWriteLockTimeout() { + ensureOpen(); return writeLockTimeout; } @@ -777,22 +810,26 @@ public class IndexWriter { * @throws IOException if there is a low-level IO error */ public synchronized void close() throws CorruptIndexException, IOException { - flushRamSegments(); + if (!closed) { + flushRamSegments(); - if (commitPending) { - segmentInfos.write(directory); // now commit changes - deleter.checkpoint(segmentInfos, true); - commitPending = false; - rollbackSegmentInfos = null; - } + if (commitPending) { + segmentInfos.write(directory); // now commit changes + deleter.checkpoint(segmentInfos, true); + commitPending = false; + rollbackSegmentInfos = null; + } - ramDirectory.close(); - if (writeLock != null) { - writeLock.release(); // release write lock - writeLock = null; + ramDirectory.close(); + if (writeLock != null) { + writeLock.release(); // release write lock + writeLock = null; + } + closed = true; + + if(closeDir) + directory.close(); } - if(closeDir) - directory.close(); } /** Release the write lock, if needed. */ @@ -809,17 +846,20 @@ public class IndexWriter { /** Returns the Directory used by this index. */ public Directory getDirectory() { - return directory; + ensureOpen(); + return directory; } /** Returns the analyzer used by this index. */ public Analyzer getAnalyzer() { - return analyzer; + ensureOpen(); + return analyzer; } /** Returns the number of documents currently in this index. */ public synchronized int docCount() { + ensureOpen(); int count = ramSegmentInfos.size(); for (int i = 0; i < segmentInfos.size(); i++) { SegmentInfo si = segmentInfos.info(i); @@ -897,6 +937,7 @@ public class IndexWriter { * @throws IOException if there is a low-level IO error */ public void addDocument(Document doc, Analyzer analyzer) throws CorruptIndexException, IOException { + ensureOpen(); SegmentInfo newSegmentInfo = buildSingleDocSegment(doc, analyzer); synchronized (this) { ramSegmentInfos.addElement(newSegmentInfo); @@ -922,6 +963,7 @@ public class IndexWriter { * @throws IOException if there is a low-level IO error */ public synchronized void deleteDocuments(Term term) throws CorruptIndexException, IOException { + ensureOpen(); bufferDeleteTerm(term); maybeFlushRamSegments(); } @@ -935,6 +977,7 @@ public class IndexWriter { * @throws IOException if there is a low-level IO error */ public synchronized void deleteDocuments(Term[] terms) throws CorruptIndexException, IOException { + ensureOpen(); for (int i = 0; i < terms.length; i++) { bufferDeleteTerm(terms[i]); } @@ -954,6 +997,7 @@ public class IndexWriter { * @throws IOException if there is a low-level IO error */ public void updateDocument(Term term, Document doc) throws CorruptIndexException, IOException { + ensureOpen(); updateDocument(term, doc, getAnalyzer()); } @@ -972,6 +1016,7 @@ public class IndexWriter { */ public void updateDocument(Term term, Document doc, Analyzer analyzer) throws CorruptIndexException, IOException { + ensureOpen(); SegmentInfo newSegmentInfo = buildSingleDocSegment(doc, analyzer); synchronized (this) { bufferDeleteTerm(term); @@ -1107,6 +1152,7 @@ public class IndexWriter { * @throws IOException if there is a low-level IO error */ public synchronized void optimize() throws CorruptIndexException, IOException { + ensureOpen(); flushRamSegments(); while (segmentInfos.size() > 1 || (segmentInfos.size() == 1 && @@ -1200,6 +1246,7 @@ public class IndexWriter { * @throws IOException if there is a low-level IO error */ public void abort() throws IOException { + ensureOpen(); if (!autoCommit) { // Keep the same segmentInfos instance but replace all @@ -1290,6 +1337,7 @@ public class IndexWriter { public synchronized void addIndexes(Directory[] dirs) throws CorruptIndexException, IOException { + ensureOpen(); optimize(); // start with zero or 1 seg int start = segmentInfos.size(); @@ -1375,6 +1423,7 @@ public class IndexWriter { // 1 flush ram segments + ensureOpen(); flushRamSegments(); // 2 copy segment infos and find the highest level from dirs @@ -1479,6 +1528,7 @@ public class IndexWriter { public synchronized void addIndexes(IndexReader[] readers) throws CorruptIndexException, IOException { + ensureOpen(); optimize(); // start with zero or 1 seg final String mergedName = newSegmentName(); @@ -1610,6 +1660,7 @@ public class IndexWriter { * @throws IOException if there is a low-level IO error */ public final synchronized void flush() throws CorruptIndexException, IOException { + ensureOpen(); flushRamSegments(); } @@ -1617,6 +1668,7 @@ public class IndexWriter { * Useful for size management with flushRamDocs() */ public final long ramSizeInBytes() { + ensureOpen(); return ramDirectory.sizeInBytes(); } @@ -1624,6 +1676,7 @@ public class IndexWriter { * Useful when calling flushRamSegments() */ public final synchronized int numRamDocs() { + ensureOpen(); return ramSegmentInfos.size(); } diff --git a/src/java/org/apache/lucene/index/MultiReader.java b/src/java/org/apache/lucene/index/MultiReader.java index d5c8b1e8d5b..c936cad7022 100644 --- a/src/java/org/apache/lucene/index/MultiReader.java +++ b/src/java/org/apache/lucene/index/MultiReader.java @@ -72,24 +72,21 @@ public class MultiReader extends IndexReader { } - /** Return an array of term frequency vectors for the specified document. - * The array contains a vector for each vectorized field in the document. - * Each vector vector contains term numbers and frequencies for all terms - * in a given vectorized field. - * If no such fields existed, the method returns null. - */ public TermFreqVector[] getTermFreqVectors(int n) throws IOException { + ensureOpen(); int i = readerIndex(n); // find segment num return subReaders[i].getTermFreqVectors(n - starts[i]); // dispatch to segment } public TermFreqVector getTermFreqVector(int n, String field) throws IOException { + ensureOpen(); int i = readerIndex(n); // find segment num return subReaders[i].getTermFreqVector(n - starts[i], field); } public synchronized int numDocs() { + // Don't call ensureOpen() here (it could affect performance) if (numDocs == -1) { // check cache int n = 0; // cache miss--recompute for (int i = 0; i < subReaders.length; i++) @@ -100,21 +97,27 @@ public class MultiReader extends IndexReader { } public int maxDoc() { + // Don't call ensureOpen() here (it could affect performance) return maxDoc; } // inherit javadoc public Document document(int n, FieldSelector fieldSelector) throws CorruptIndexException, IOException { + ensureOpen(); int i = readerIndex(n); // find segment num return subReaders[i].document(n - starts[i], fieldSelector); // dispatch to segment reader } public boolean isDeleted(int n) { + // Don't call ensureOpen() here (it could affect performance) int i = readerIndex(n); // find segment num return subReaders[i].isDeleted(n - starts[i]); // dispatch to segment reader } - public boolean hasDeletions() { return hasDeletions; } + public boolean hasDeletions() { + // Don't call ensureOpen() here (it could affect performance) + return hasDeletions; + } protected void doDelete(int n) throws CorruptIndexException, IOException { numDocs = -1; // invalidate cache @@ -153,6 +156,7 @@ public class MultiReader extends IndexReader { } public boolean hasNorms(String field) throws IOException { + ensureOpen(); for (int i = 0; i < subReaders.length; i++) { if (subReaders[i].hasNorms(field)) return true; } @@ -166,6 +170,7 @@ public class MultiReader extends IndexReader { } public synchronized byte[] norms(String field) throws IOException { + ensureOpen(); byte[] bytes = (byte[])normsCache.get(field); if (bytes != null) return bytes; // cache hit @@ -181,6 +186,7 @@ public class MultiReader extends IndexReader { public synchronized void norms(String field, byte[] result, int offset) throws IOException { + ensureOpen(); byte[] bytes = (byte[])normsCache.get(field); if (bytes==null && !hasNorms(field)) bytes=fakeNorms(); if (bytes != null) // cache hit @@ -198,14 +204,17 @@ public class MultiReader extends IndexReader { } public TermEnum terms() throws IOException { + ensureOpen(); return new MultiTermEnum(subReaders, starts, null); } public TermEnum terms(Term term) throws IOException { + ensureOpen(); return new MultiTermEnum(subReaders, starts, term); } public int docFreq(Term t) throws IOException { + ensureOpen(); int total = 0; // sum freqs in segments for (int i = 0; i < subReaders.length; i++) total += subReaders[i].docFreq(t); @@ -213,10 +222,12 @@ public class MultiReader extends IndexReader { } public TermDocs termDocs() throws IOException { + ensureOpen(); return new MultiTermDocs(subReaders, starts); } public TermPositions termPositions() throws IOException { + ensureOpen(); return new MultiTermPositions(subReaders, starts); } @@ -244,11 +255,9 @@ public class MultiReader extends IndexReader { subReaders[i].close(); } - /** - * @see IndexReader#getFieldNames(IndexReader.FieldOption) - */ public Collection getFieldNames (IndexReader.FieldOption fieldNames) { // maintain a unique set of field names + ensureOpen(); Set fieldSet = new HashSet(); for (int i = 0; i < subReaders.length; i++) { IndexReader reader = subReaders[i]; diff --git a/src/java/org/apache/lucene/index/ParallelReader.java b/src/java/org/apache/lucene/index/ParallelReader.java index bfc6d73dca8..b0497bd3f87 100644 --- a/src/java/org/apache/lucene/index/ParallelReader.java +++ b/src/java/org/apache/lucene/index/ParallelReader.java @@ -66,8 +66,11 @@ public class ParallelReader extends IndexReader { /** Construct a ParallelReader. */ public ParallelReader() throws IOException { super(null); } - /** Add an IndexReader. */ + /** Add an IndexReader. + * @throws IOException if there is a low-level IO error + */ public void add(IndexReader reader) throws IOException { + ensureOpen(); add(reader, false); } @@ -79,10 +82,12 @@ public class ParallelReader extends IndexReader { * of documents * @throws IllegalArgumentException if not all indexes have the same value * of {@link IndexReader#maxDoc()} + * @throws IOException if there is a low-level IO error */ public void add(IndexReader reader, boolean ignoreStoredFields) throws IOException { + ensureOpen(); if (readers.size() == 0) { this.maxDoc = reader.maxDoc(); this.numDocs = reader.numDocs(); @@ -110,14 +115,24 @@ public class ParallelReader extends IndexReader { readers.add(reader); } - public int numDocs() { return numDocs; } + public int numDocs() { + // Don't call ensureOpen() here (it could affect performance) + return numDocs; + } - public int maxDoc() { return maxDoc; } + public int maxDoc() { + // Don't call ensureOpen() here (it could affect performance) + return maxDoc; + } - public boolean hasDeletions() { return hasDeletions; } + public boolean hasDeletions() { + // Don't call ensureOpen() here (it could affect performance) + return hasDeletions; + } // check first reader public boolean isDeleted(int n) { + // Don't call ensureOpen() here (it could affect performance) if (readers.size() > 0) return ((IndexReader)readers.get(0)).isDeleted(n); return false; @@ -141,6 +156,7 @@ public class ParallelReader extends IndexReader { // append fields from storedFieldReaders public Document document(int n, FieldSelector fieldSelector) throws CorruptIndexException, IOException { + ensureOpen(); Document result = new Document(); for (int i = 0; i < storedFieldReaders.size(); i++) { IndexReader reader = (IndexReader)storedFieldReaders.get(i); @@ -166,6 +182,7 @@ public class ParallelReader extends IndexReader { // get all vectors public TermFreqVector[] getTermFreqVectors(int n) throws IOException { + ensureOpen(); ArrayList results = new ArrayList(); Iterator i = fieldToReader.entrySet().iterator(); while (i.hasNext()) { @@ -182,22 +199,26 @@ public class ParallelReader extends IndexReader { public TermFreqVector getTermFreqVector(int n, String field) throws IOException { + ensureOpen(); IndexReader reader = ((IndexReader)fieldToReader.get(field)); return reader==null ? null : reader.getTermFreqVector(n, field); } public boolean hasNorms(String field) throws IOException { + ensureOpen(); IndexReader reader = ((IndexReader)fieldToReader.get(field)); return reader==null ? false : reader.hasNorms(field); } public byte[] norms(String field) throws IOException { + ensureOpen(); IndexReader reader = ((IndexReader)fieldToReader.get(field)); return reader==null ? null : reader.norms(field); } public void norms(String field, byte[] result, int offset) throws IOException { + ensureOpen(); IndexReader reader = ((IndexReader)fieldToReader.get(field)); if (reader!=null) reader.norms(field, result, offset); @@ -211,31 +232,38 @@ public class ParallelReader extends IndexReader { } public TermEnum terms() throws IOException { + ensureOpen(); return new ParallelTermEnum(); } public TermEnum terms(Term term) throws IOException { + ensureOpen(); return new ParallelTermEnum(term); } public int docFreq(Term term) throws IOException { + ensureOpen(); IndexReader reader = ((IndexReader)fieldToReader.get(term.field())); return reader==null ? 0 : reader.docFreq(term); } public TermDocs termDocs(Term term) throws IOException { + ensureOpen(); return new ParallelTermDocs(term); } public TermDocs termDocs() throws IOException { + ensureOpen(); return new ParallelTermDocs(); } public TermPositions termPositions(Term term) throws IOException { + ensureOpen(); return new ParallelTermPositions(term); } public TermPositions termPositions() throws IOException { + ensureOpen(); return new ParallelTermPositions(); } @@ -251,6 +279,7 @@ public class ParallelReader extends IndexReader { public Collection getFieldNames (IndexReader.FieldOption fieldNames) { + ensureOpen(); Set fieldSet = new HashSet(); for (int i = 0; i < readers.size(); i++) { IndexReader reader = ((IndexReader)readers.get(i)); diff --git a/src/java/org/apache/lucene/index/SegmentReader.java b/src/java/org/apache/lucene/index/SegmentReader.java index baca6a8bcde..c5cefd306ea 100644 --- a/src/java/org/apache/lucene/index/SegmentReader.java +++ b/src/java/org/apache/lucene/index/SegmentReader.java @@ -266,10 +266,12 @@ class SegmentReader extends IndexReader { } static boolean hasDeletions(SegmentInfo si) throws IOException { + // Don't call ensureOpen() here (it could affect performance) return si.hasDeletions(); } public boolean hasDeletions() { + // Don't call ensureOpen() here (it could affect performance) return deletedDocs != null; } @@ -300,10 +302,12 @@ class SegmentReader extends IndexReader { } public TermEnum terms() { + ensureOpen(); return tis.terms(); } public TermEnum terms(Term t) throws IOException { + ensureOpen(); return tis.terms(t); } @@ -312,6 +316,7 @@ class SegmentReader extends IndexReader { * @throws IOException if there is a low-level IO error */ public synchronized Document document(int n, FieldSelector fieldSelector) throws CorruptIndexException, IOException { + ensureOpen(); if (isDeleted(n)) throw new IllegalArgumentException ("attempt to access a deleted document"); @@ -323,14 +328,17 @@ class SegmentReader extends IndexReader { } public TermDocs termDocs() throws IOException { + ensureOpen(); return new SegmentTermDocs(this); } public TermPositions termPositions() throws IOException { + ensureOpen(); return new SegmentTermPositions(this); } public int docFreq(Term t) throws IOException { + ensureOpen(); TermInfo ti = tis.get(t); if (ti != null) return ti.docFreq; @@ -339,6 +347,7 @@ class SegmentReader extends IndexReader { } public int numDocs() { + // Don't call ensureOpen() here (it could affect performance) int n = maxDoc(); if (deletedDocs != null) n -= deletedDocs.count(); @@ -346,6 +355,7 @@ class SegmentReader extends IndexReader { } public int maxDoc() { + // Don't call ensureOpen() here (it could affect performance) return si.docCount; } @@ -353,6 +363,7 @@ class SegmentReader extends IndexReader { * @see IndexReader#getFieldNames(IndexReader.FieldOption fldOption) */ public Collection getFieldNames(IndexReader.FieldOption fieldOption) { + ensureOpen(); Set fieldSet = new HashSet(); for (int i = 0; i < fieldInfos.size(); i++) { @@ -394,6 +405,7 @@ class SegmentReader extends IndexReader { public synchronized boolean hasNorms(String field) { + ensureOpen(); return norms.containsKey(field); } @@ -426,6 +438,7 @@ class SegmentReader extends IndexReader { // returns fake norms if norms aren't available public synchronized byte[] norms(String field) throws IOException { + ensureOpen(); byte[] bytes = getNorms(field); if (bytes==null) bytes=fakeNorms(); return bytes; @@ -447,6 +460,7 @@ class SegmentReader extends IndexReader { public synchronized void norms(String field, byte[] bytes, int offset) throws IOException { + ensureOpen(); Norm norm = (Norm) norms.get(field); if (norm == null) { System.arraycopy(fakeNorms(), 0, bytes, offset, maxDoc()); @@ -537,6 +551,7 @@ class SegmentReader extends IndexReader { */ public TermFreqVector getTermFreqVector(int docNumber, String field) throws IOException { // Check if this field is invalid or has no stored term vector + ensureOpen(); FieldInfo fi = fieldInfos.fieldInfo(field); if (fi == null || !fi.storeTermVector || termVectorsReaderOrig == null) return null; @@ -557,6 +572,7 @@ class SegmentReader extends IndexReader { * @throws IOException */ public TermFreqVector[] getTermFreqVectors(int docNumber) throws IOException { + ensureOpen(); if (termVectorsReaderOrig == null) return null; diff --git a/src/java/org/apache/lucene/store/AlreadyClosedException.java b/src/java/org/apache/lucene/store/AlreadyClosedException.java new file mode 100644 index 00000000000..435b9ddf487 --- /dev/null +++ b/src/java/org/apache/lucene/store/AlreadyClosedException.java @@ -0,0 +1,28 @@ +package org.apache.lucene.store; + +/** + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 + * + * 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. + */ + +/** + * This exception is thrown when there is an attempt to + * access something that has already been closed. + */ +public class AlreadyClosedException extends IllegalStateException { + public AlreadyClosedException(String message) { + super(message); + } +} diff --git a/src/java/org/apache/lucene/store/RAMDirectory.java b/src/java/org/apache/lucene/store/RAMDirectory.java index 1c895656747..cd7ad1a6ce7 100644 --- a/src/java/org/apache/lucene/store/RAMDirectory.java +++ b/src/java/org/apache/lucene/store/RAMDirectory.java @@ -97,6 +97,7 @@ public class RAMDirectory extends Directory implements Serializable { /** Returns an array of strings, one for each file in the directory. */ public synchronized final String[] list() { + ensureOpen(); Set fileNames = fileMap.keySet(); String[] result = new String[fileNames.size()]; int i = 0; @@ -108,6 +109,7 @@ public class RAMDirectory extends Directory implements Serializable { /** Returns true iff the named file exists in this directory. */ public final boolean fileExists(String name) { + ensureOpen(); RAMFile file; synchronized (this) { file = (RAMFile)fileMap.get(name); @@ -119,6 +121,7 @@ public class RAMDirectory extends Directory implements Serializable { * @throws IOException if the file does not exist */ public final long fileModified(String name) throws IOException { + ensureOpen(); RAMFile file; synchronized (this) { file = (RAMFile)fileMap.get(name); @@ -132,6 +135,7 @@ public class RAMDirectory extends Directory implements Serializable { * @throws IOException if the file does not exist */ public void touchFile(String name) throws IOException { + ensureOpen(); RAMFile file; synchronized (this) { file = (RAMFile)fileMap.get(name); @@ -154,6 +158,7 @@ public class RAMDirectory extends Directory implements Serializable { * @throws IOException if the file does not exist */ public final long fileLength(String name) throws IOException { + ensureOpen(); RAMFile file; synchronized (this) { file = (RAMFile)fileMap.get(name); @@ -167,6 +172,7 @@ public class RAMDirectory extends Directory implements Serializable { * directory. This is currently quantized to * BufferedIndexOutput.BUFFER_SIZE. */ public synchronized final long sizeInBytes() { + ensureOpen(); return sizeInBytes; } @@ -174,6 +180,7 @@ public class RAMDirectory extends Directory implements Serializable { * @throws IOException if the file does not exist */ public synchronized void deleteFile(String name) throws IOException { + ensureOpen(); RAMFile file = (RAMFile)fileMap.get(name); if (file!=null) { fileMap.remove(name); @@ -188,6 +195,7 @@ public class RAMDirectory extends Directory implements Serializable { * @deprecated */ public synchronized final void renameFile(String from, String to) throws IOException { + ensureOpen(); RAMFile fromFile = (RAMFile)fileMap.get(from); if (fromFile==null) throw new FileNotFoundException(from); @@ -202,6 +210,7 @@ public class RAMDirectory extends Directory implements Serializable { /** Creates a new, empty file in the directory with the given name. Returns a stream writing this file. */ public IndexOutput createOutput(String name) { + ensureOpen(); RAMFile file = new RAMFile(this); synchronized (this) { RAMFile existing = (RAMFile)fileMap.get(name); @@ -216,6 +225,7 @@ public class RAMDirectory extends Directory implements Serializable { /** Returns a stream reading an existing file. */ public IndexInput openInput(String name) throws IOException { + ensureOpen(); RAMFile file; synchronized (this) { file = (RAMFile)fileMap.get(name); @@ -230,4 +240,12 @@ public class RAMDirectory extends Directory implements Serializable { fileMap = null; } + /** + * @throws AlreadyClosedException if this IndexReader is closed + */ + protected final void ensureOpen() throws AlreadyClosedException { + if (fileMap == null) { + throw new AlreadyClosedException("this RAMDirectory is closed"); + } + } } diff --git a/src/test/org/apache/lucene/index/TestFieldsReader.java b/src/test/org/apache/lucene/index/TestFieldsReader.java index 25ca4e74cb7..2d2e83c496c 100644 --- a/src/test/org/apache/lucene/index/TestFieldsReader.java +++ b/src/test/org/apache/lucene/index/TestFieldsReader.java @@ -23,6 +23,7 @@ import org.apache.lucene.document.*; import org.apache.lucene.search.Similarity; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.store.RAMDirectory; +import org.apache.lucene.store.AlreadyClosedException; import org.apache.lucene.util._TestUtil; import java.io.File; @@ -133,6 +134,36 @@ public class TestFieldsReader extends TestCase { } } + public void testLazyFieldsAfterClose() throws Exception { + assertTrue(dir != null); + assertTrue(fieldInfos != null); + FieldsReader reader = new FieldsReader(dir, "test", fieldInfos); + assertTrue(reader != null); + assertTrue(reader.size() == 1); + Set loadFieldNames = new HashSet(); + loadFieldNames.add(DocHelper.TEXT_FIELD_1_KEY); + loadFieldNames.add(DocHelper.TEXT_FIELD_UTF1_KEY); + Set lazyFieldNames = new HashSet(); + lazyFieldNames.add(DocHelper.LARGE_LAZY_FIELD_KEY); + lazyFieldNames.add(DocHelper.LAZY_FIELD_KEY); + lazyFieldNames.add(DocHelper.LAZY_FIELD_BINARY_KEY); + lazyFieldNames.add(DocHelper.TEXT_FIELD_UTF2_KEY); + lazyFieldNames.add(DocHelper.COMPRESSED_TEXT_FIELD_2_KEY); + SetBasedFieldSelector fieldSelector = new SetBasedFieldSelector(loadFieldNames, lazyFieldNames); + Document doc = reader.doc(0, fieldSelector); + assertTrue("doc is null and it shouldn't be", doc != null); + Fieldable field = doc.getFieldable(DocHelper.LAZY_FIELD_KEY); + assertTrue("field is null and it shouldn't be", field != null); + assertTrue("field is not lazy and it should be", field.isLazy()); + reader.close(); + try { + String value = field.stringValue(); + fail("did not hit AlreadyClosedException as expected"); + } catch (AlreadyClosedException e) { + // expected + } + } + public void testLoadFirst() throws Exception { assertTrue(dir != null); assertTrue(fieldInfos != null); diff --git a/src/test/org/apache/lucene/index/TestIndexReader.java b/src/test/org/apache/lucene/index/TestIndexReader.java index 7a401cc82f8..b24f71c335e 100644 --- a/src/test/org/apache/lucene/index/TestIndexReader.java +++ b/src/test/org/apache/lucene/index/TestIndexReader.java @@ -26,6 +26,7 @@ import org.apache.lucene.store.Directory; import org.apache.lucene.store.RAMDirectory; import org.apache.lucene.store.FSDirectory; import org.apache.lucene.store.LockObtainFailedException; +import org.apache.lucene.store.AlreadyClosedException; import org.apache.lucene.analysis.standard.StandardAnalyzer; import org.apache.lucene.analysis.WhitespaceAnalyzer; import org.apache.lucene.document.Document; @@ -279,21 +280,21 @@ public class TestIndexReader extends TestCase try { reader.deleteDocument(4); fail("deleteDocument after close failed to throw IOException"); - } catch (IOException e) { + } catch (AlreadyClosedException e) { // expected } try { reader.setNorm(5, "aaa", 2.0f); fail("setNorm after close failed to throw IOException"); - } catch (IOException e) { + } catch (AlreadyClosedException e) { // expected } try { reader.undeleteAll(); fail("undeleteAll after close failed to throw IOException"); - } catch (IOException e) { + } catch (AlreadyClosedException e) { // expected } } diff --git a/src/test/org/apache/lucene/index/TestIndexWriter.java b/src/test/org/apache/lucene/index/TestIndexWriter.java index 341899489ba..11acacaa396 100644 --- a/src/test/org/apache/lucene/index/TestIndexWriter.java +++ b/src/test/org/apache/lucene/index/TestIndexWriter.java @@ -19,6 +19,7 @@ import org.apache.lucene.store.FSDirectory; import org.apache.lucene.store.RAMDirectory; import org.apache.lucene.store.IndexInput; import org.apache.lucene.store.IndexOutput; +import org.apache.lucene.store.AlreadyClosedException; import org.apache.lucene.store.MockRAMDirectory; import org.apache.lucene.store.LockFactory; @@ -724,6 +725,25 @@ public class TestIndexWriter extends TestCase } } + public void testChangesAfterClose() throws IOException { + Directory dir = new RAMDirectory(); + + IndexWriter writer = null; + + writer = new IndexWriter(dir, new WhitespaceAnalyzer(), true); + addDoc(writer); + + // close + writer.close(); + try { + addDoc(writer); + fail("did not hit AlreadyClosedException"); + } catch (AlreadyClosedException e) { + // expected + } + } + + // Simulate a corrupt index by removing one of the cfs // files and make sure we get an IOException trying to // open the index: