--- /dev/null
+package org.apache.lucene.index;
+
+/**
+ * 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.
+ */
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.FieldSelector;
+import org.apache.lucene.search.Similarity;
+import org.apache.lucene.index.FieldInfo.IndexOptions;
+import org.apache.lucene.store.BufferedIndexInput;
+import org.apache.lucene.store.Directory;
+import org.apache.lucene.store.IndexInput;
+import org.apache.lucene.util.BitVector;
+import org.apache.lucene.util.CloseableThreadLocal;
+import org.apache.lucene.util.StringHelper;
+
+/**
+ * @lucene.experimental
+ */
+public class SegmentReader extends IndexReader implements Cloneable {
+ protected boolean readOnly;
+
+ private SegmentInfo si;
+ private int readBufferSize;
+
+ CloseableThreadLocal<FieldsReader> fieldsReaderLocal = new FieldsReaderLocal();
+ CloseableThreadLocal<TermVectorsReader> termVectorsLocal = new CloseableThreadLocal<TermVectorsReader>();
+
+ BitVector deletedDocs = null;
+ AtomicInteger deletedDocsRef = null;
+ private boolean deletedDocsDirty = false;
+ private boolean normsDirty = false;
+
+ // TODO: we should move this tracking into SegmentInfo;
+ // this way SegmentInfo.toString shows pending deletes
+ private int pendingDeleteCount;
+
+ private boolean rollbackHasChanges = false;
+ private boolean rollbackDeletedDocsDirty = false;
+ private boolean rollbackNormsDirty = false;
+ private SegmentInfo rollbackSegmentInfo;
+ private int rollbackPendingDeleteCount;
+
+ // optionally used for the .nrm file shared by multiple norms
+ IndexInput singleNormStream;
+ AtomicInteger singleNormRef;
+
+ SegmentCoreReaders core;
+
+ /**
+ * Sets the initial value
+ */
+ private class FieldsReaderLocal extends CloseableThreadLocal<FieldsReader> {
+ @Override
+ protected FieldsReader initialValue() {
+ return (FieldsReader) core.getFieldsReaderOrig().clone();
+ }
+ }
+
+ Map<String,SegmentNorms> norms = new HashMap<String,SegmentNorms>();
+
+ /**
+ * @throws CorruptIndexException if the index is corrupt
+ * @throws IOException if there is a low-level IO error
+ */
+ public static SegmentReader get(boolean readOnly, SegmentInfo si, int termInfosIndexDivisor) throws CorruptIndexException, IOException {
+ return get(readOnly, si.dir, si, BufferedIndexInput.BUFFER_SIZE, true, termInfosIndexDivisor);
+ }
+
+ /**
+ * @throws CorruptIndexException if the index is corrupt
+ * @throws IOException if there is a low-level IO error
+ */
+ public static SegmentReader get(boolean readOnly,
+ Directory dir,
+ SegmentInfo si,
+ int readBufferSize,
+ boolean doOpenStores,
+ int termInfosIndexDivisor)
+ throws CorruptIndexException, IOException {
+ SegmentReader instance = readOnly ? new ReadOnlySegmentReader() : new SegmentReader();
+ instance.readOnly = readOnly;
+ instance.si = si;
+ instance.readBufferSize = readBufferSize;
+
+ boolean success = false;
+
+ try {
+ instance.core = new SegmentCoreReaders(instance, dir, si, readBufferSize, termInfosIndexDivisor);
+ if (doOpenStores) {
+ instance.core.openDocStores(si);
+ }
+ instance.loadDeletedDocs();
+ instance.openNorms(instance.core.cfsDir, readBufferSize);
+ success = true;
+ } finally {
+
+ // With lock-less commits, it's entirely possible (and
+ // fine) to hit a FileNotFound exception above. In
+ // this case, we want to explicitly close any subset
+ // of things that were opened so that we don't have to
+ // wait for a GC to do so.
+ if (!success) {
+ instance.doClose();
+ }
+ }
+ return instance;
+ }
+
+ void openDocStores() throws IOException {
+ core.openDocStores(si);
+ }
+
+ private boolean checkDeletedCounts() throws IOException {
+ final int recomputedCount = deletedDocs.getRecomputedCount();
+
+ assert deletedDocs.count() == recomputedCount : "deleted count=" + deletedDocs.count() + " vs recomputed count=" + recomputedCount;
+
+ assert si.getDelCount() == recomputedCount :
+ "delete count mismatch: info=" + si.getDelCount() + " vs BitVector=" + recomputedCount;
+
+ // Verify # deletes does not exceed maxDoc for this
+ // segment:
+ assert si.getDelCount() <= maxDoc() :
+ "delete count mismatch: " + recomputedCount + ") exceeds max doc (" + maxDoc() + ") for segment " + si.name;
+
+ return true;
+ }
+
+ private void loadDeletedDocs() throws IOException {
+ // NOTE: the bitvector is stored using the regular directory, not cfs
+ if (hasDeletions(si)) {
+ deletedDocs = new BitVector(directory(), si.getDelFileName());
+ deletedDocsRef = new AtomicInteger(1);
+ assert checkDeletedCounts();
+ if (deletedDocs.size() != si.docCount) {
+ throw new CorruptIndexException("document count mismatch: deleted docs count " + deletedDocs.size() + " vs segment doc count " + si.docCount + " segment=" + si.name);
+ }
+ } else
+ assert si.getDelCount() == 0;
+ }
+
+ /**
+ * Clones the norm bytes. May be overridden by subclasses. New and experimental.
+ * @param bytes Byte array to clone
+ * @return New BitVector
+ */
+ protected byte[] cloneNormBytes(byte[] bytes) {
+ byte[] cloneBytes = new byte[bytes.length];
+ System.arraycopy(bytes, 0, cloneBytes, 0, bytes.length);
+ return cloneBytes;
+ }
+
+ /**
+ * Clones the deleteDocs BitVector. May be overridden by subclasses. New and experimental.
+ * @param bv BitVector to clone
+ * @return New BitVector
+ */
+ protected BitVector cloneDeletedDocs(BitVector bv) {
+ ensureOpen();
+ return (BitVector)bv.clone();
+ }
+
+ @Override
+ public final synchronized Object clone() {
+ try {
+ return clone(readOnly); // Preserve current readOnly
+ } catch (Exception ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ @Override
+ public final synchronized IndexReader clone(boolean openReadOnly) throws CorruptIndexException, IOException {
+ return reopenSegment(si, true, openReadOnly);
+ }
+
+ @Override
+ protected synchronized IndexReader doOpenIfChanged()
+ throws CorruptIndexException, IOException {
+ return reopenSegment(si, false, readOnly);
+ }
+
+ @Override
+ protected synchronized IndexReader doOpenIfChanged(boolean openReadOnly)
+ throws CorruptIndexException, IOException {
+ return reopenSegment(si, false, openReadOnly);
+ }
+
+ synchronized SegmentReader reopenSegment(SegmentInfo si, boolean doClone, boolean openReadOnly) throws CorruptIndexException, IOException {
+ ensureOpen();
+ boolean deletionsUpToDate = (this.si.hasDeletions() == si.hasDeletions())
+ && (!si.hasDeletions() || this.si.getDelFileName().equals(si.getDelFileName()));
+ boolean normsUpToDate = true;
+
+ boolean[] fieldNormsChanged = new boolean[core.fieldInfos.size()];
+ final int fieldCount = core.fieldInfos.size();
+ for (int i = 0; i < fieldCount; i++) {
+ if (!this.si.getNormFileName(i).equals(si.getNormFileName(i))) {
+ normsUpToDate = false;
+ fieldNormsChanged[i] = true;
+ }
+ }
+
+ // if we're cloning we need to run through the reopenSegment logic
+ // also if both old and new readers aren't readonly, we clone to avoid sharing modifications
+ if (normsUpToDate && deletionsUpToDate && !doClone && openReadOnly && readOnly) {
+ return null;
+ }
+
+ // When cloning, the incoming SegmentInfos should not
+ // have any changes in it:
+ assert !doClone || (normsUpToDate && deletionsUpToDate);
+
+ // clone reader
+ SegmentReader clone = openReadOnly ? new ReadOnlySegmentReader() : new SegmentReader();
+
+ boolean success = false;
+ try {
+ core.incRef();
+ clone.core = core;
+ clone.readOnly = openReadOnly;
+ clone.si = si;
+ clone.readBufferSize = readBufferSize;
+ clone.pendingDeleteCount = pendingDeleteCount;
+ clone.readerFinishedListeners = readerFinishedListeners;
+
+ if (!openReadOnly && hasChanges) {
+ // My pending changes transfer to the new reader
+ clone.deletedDocsDirty = deletedDocsDirty;
+ clone.normsDirty = normsDirty;
+ clone.hasChanges = hasChanges;
+ hasChanges = false;
+ }
+
+ if (doClone) {
+ if (deletedDocs != null) {
+ deletedDocsRef.incrementAndGet();
+ clone.deletedDocs = deletedDocs;
+ clone.deletedDocsRef = deletedDocsRef;
+ }
+ } else {
+ if (!deletionsUpToDate) {
+ // load deleted docs
+ assert clone.deletedDocs == null;
+ clone.loadDeletedDocs();
+ } else if (deletedDocs != null) {
+ deletedDocsRef.incrementAndGet();
+ clone.deletedDocs = deletedDocs;
+ clone.deletedDocsRef = deletedDocsRef;
+ }
+ }
+
+ clone.norms = new HashMap<String,SegmentNorms>();
+
+ // Clone norms
+ for (int i = 0; i < fieldNormsChanged.length; i++) {
+
+ // Clone unchanged norms to the cloned reader
+ if (doClone || !fieldNormsChanged[i]) {
+ final String curField = core.fieldInfos.fieldInfo(i).name;
+ SegmentNorms norm = this.norms.get(curField);
+ if (norm != null)
+ clone.norms.put(curField, (SegmentNorms) norm.clone());
+ }
+ }
+
+ // If we are not cloning, then this will open anew
+ // any norms that have changed:
+ clone.openNorms(si.getUseCompoundFile() ? core.getCFSReader() : directory(), readBufferSize);
+
+ success = true;
+ } finally {
+ if (!success) {
+ // An exception occurred during reopen, we have to decRef the norms
+ // that we incRef'ed already and close singleNormsStream and FieldsReader
+ clone.decRef();
+ }
+ }
+
+ return clone;
+ }
+
+ @Override
+ protected void doCommit(Map<String,String> commitUserData) throws IOException {
+ if (hasChanges) {
+ startCommit();
+ boolean success = false;
+ try {
+ commitChanges(commitUserData);
+ success = true;
+ } finally {
+ if (!success) {
+ rollbackCommit();
+ }
+ }
+ }
+ }
+
+ private synchronized void commitChanges(Map<String,String> commitUserData) throws IOException {
+ if (deletedDocsDirty) { // re-write deleted
+ si.advanceDelGen();
+
+ assert deletedDocs.size() == si.docCount;
+
+ // We can write directly to the actual name (vs to a
+ // .tmp & renaming it) because the file is not live
+ // until segments file is written:
+ final String delFileName = si.getDelFileName();
+ boolean success = false;
+ try {
+ deletedDocs.write(directory(), delFileName);
+ success = true;
+ } finally {
+ if (!success) {
+ try {
+ directory().deleteFile(delFileName);
+ } catch (Throwable t) {
+ // suppress this so we keep throwing the
+ // original exception
+ }
+ }
+ }
+
+ si.setDelCount(si.getDelCount()+pendingDeleteCount);
+ pendingDeleteCount = 0;
+ assert deletedDocs.count() == si.getDelCount(): "delete count mismatch during commit: info=" + si.getDelCount() + " vs BitVector=" + deletedDocs.count();
+ } else {
+ assert pendingDeleteCount == 0;
+ }
+
+ if (normsDirty) { // re-write norms
+ si.setNumFields(core.fieldInfos.size());
+ for (final SegmentNorms norm : norms.values()) {
+ if (norm.dirty) {
+ norm.reWrite(si);
+ }
+ }
+ }
+ deletedDocsDirty = false;
+ normsDirty = false;
+ hasChanges = false;
+ }
+
+ FieldsReader getFieldsReader() {
+ return fieldsReaderLocal.get();
+ }
+
+ @Override
+ protected void doClose() throws IOException {
+ termVectorsLocal.close();
+ fieldsReaderLocal.close();
+
+ if (deletedDocs != null) {
+ deletedDocsRef.decrementAndGet();
+ // null so if an app hangs on to us we still free most ram
+ deletedDocs = null;
+ }
+
+ for (final SegmentNorms norm : norms.values()) {
+ norm.decRef();
+ }
+ if (core != null) {
+ core.decRef();
+ }
+ }
+
+ static boolean hasDeletions(SegmentInfo si) throws IOException {
+ // Don't call ensureOpen() here (it could affect performance)
+ return si.hasDeletions();
+ }
+
+ @Override
+ public boolean hasDeletions() {
+ // Don't call ensureOpen() here (it could affect performance)
+ return deletedDocs != null;
+ }
+
+ static boolean usesCompoundFile(SegmentInfo si) throws IOException {
+ return si.getUseCompoundFile();
+ }
+
+ static boolean hasSeparateNorms(SegmentInfo si) throws IOException {
+ return si.hasSeparateNorms();
+ }
+
+ @Override
+ protected void doDelete(int docNum) {
+ if (deletedDocs == null) {
+ deletedDocs = new BitVector(maxDoc());
+ deletedDocsRef = new AtomicInteger(1);
+ }
+ // there is more than 1 SegmentReader with a reference to this
+ // deletedDocs BitVector so decRef the current deletedDocsRef,
+ // clone the BitVector, create a new deletedDocsRef
+ if (deletedDocsRef.get() > 1) {
+ AtomicInteger oldRef = deletedDocsRef;
+ deletedDocs = cloneDeletedDocs(deletedDocs);
+ deletedDocsRef = new AtomicInteger(1);
+ oldRef.decrementAndGet();
+ }
+ deletedDocsDirty = true;
+ if (!deletedDocs.getAndSet(docNum)) {
+ pendingDeleteCount++;
+ }
+ }
+
+ @Override
+ protected void doUndeleteAll() {
+ deletedDocsDirty = false;
+ if (deletedDocs != null) {
+ assert deletedDocsRef != null;
+ deletedDocsRef.decrementAndGet();
+ deletedDocs = null;
+ deletedDocsRef = null;
+ pendingDeleteCount = 0;
+ si.clearDelGen();
+ si.setDelCount(0);
+ } else {
+ assert deletedDocsRef == null;
+ assert pendingDeleteCount == 0;
+ }
+ }
+
+ List<String> files() throws IOException {
+ return new ArrayList<String>(si.files());
+ }
+
+ @Override
+ public TermEnum terms() {
+ ensureOpen();
+ return core.getTermsReader().terms();
+ }
+
+ @Override
+ public TermEnum terms(Term t) throws IOException {
+ ensureOpen();
+ return core.getTermsReader().terms(t);
+ }
+
+ FieldInfos fieldInfos() {
+ return core.fieldInfos;
+ }
+
+ @Override
+ public Document document(int n, FieldSelector fieldSelector) throws CorruptIndexException, IOException {
+ ensureOpen();
+ if (n < 0 || n >= maxDoc()) {
+ throw new IllegalArgumentException("docID must be >= 0 and < maxDoc=" + maxDoc() + " (got docID=" + n + ")");
+ }
+ return getFieldsReader().doc(n, fieldSelector);
+ }
+
+ @Override
+ public synchronized boolean isDeleted(int n) {
+ return (deletedDocs != null && deletedDocs.get(n));
+ }
+
+ @Override
+ public TermDocs termDocs(Term term) throws IOException {
+ if (term == null) {
+ return new AllTermDocs(this);
+ } else {
+ return super.termDocs(term);
+ }
+ }
+
+ @Override
+ public TermDocs termDocs() throws IOException {
+ ensureOpen();
+ return new SegmentTermDocs(this);
+ }
+
+ @Override
+ public TermPositions termPositions() throws IOException {
+ ensureOpen();
+ return new SegmentTermPositions(this);
+ }
+
+ @Override
+ public int docFreq(Term t) throws IOException {
+ ensureOpen();
+ TermInfo ti = core.getTermsReader().get(t);
+ if (ti != null)
+ return ti.docFreq;
+ else
+ return 0;
+ }
+
+ @Override
+ public int numDocs() {
+ // Don't call ensureOpen() here (it could affect performance)
+ int n = maxDoc();
+ if (deletedDocs != null)
+ n -= deletedDocs.count();
+ return n;
+ }
+
+ @Override
+ public int maxDoc() {
+ // Don't call ensureOpen() here (it could affect performance)
+ return si.docCount;
+ }
+
+ /**
+ * @see IndexReader#getFieldNames(org.apache.lucene.index.IndexReader.FieldOption)
+ */
+ @Override
+ public Collection<String> getFieldNames(IndexReader.FieldOption fieldOption) {
+ ensureOpen();
+
+ Set<String> fieldSet = new HashSet<String>();
+ for (int i = 0; i < core.fieldInfos.size(); i++) {
+ FieldInfo fi = core.fieldInfos.fieldInfo(i);
+ if (fieldOption == IndexReader.FieldOption.ALL) {
+ fieldSet.add(fi.name);
+ }
+ else if (!fi.isIndexed && fieldOption == IndexReader.FieldOption.UNINDEXED) {
+ fieldSet.add(fi.name);
+ }
+ else if (fi.indexOptions == IndexOptions.DOCS_ONLY && fieldOption == IndexReader.FieldOption.OMIT_TERM_FREQ_AND_POSITIONS) {
+ fieldSet.add(fi.name);
+ }
+ else if (fi.indexOptions == IndexOptions.DOCS_AND_FREQS && fieldOption == IndexReader.FieldOption.OMIT_POSITIONS) {
+ fieldSet.add(fi.name);
+ }
+ else if (fi.storePayloads && fieldOption == IndexReader.FieldOption.STORES_PAYLOADS) {
+ fieldSet.add(fi.name);
+ }
+ else if (fi.isIndexed && fieldOption == IndexReader.FieldOption.INDEXED) {
+ fieldSet.add(fi.name);
+ }
+ else if (fi.isIndexed && fi.storeTermVector == false && fieldOption == IndexReader.FieldOption.INDEXED_NO_TERMVECTOR) {
+ fieldSet.add(fi.name);
+ }
+ else if (fi.storeTermVector == true &&
+ fi.storePositionWithTermVector == false &&
+ fi.storeOffsetWithTermVector == false &&
+ fieldOption == IndexReader.FieldOption.TERMVECTOR) {
+ fieldSet.add(fi.name);
+ }
+ else if (fi.isIndexed && fi.storeTermVector && fieldOption == IndexReader.FieldOption.INDEXED_WITH_TERMVECTOR) {
+ fieldSet.add(fi.name);
+ }
+ else if (fi.storePositionWithTermVector && fi.storeOffsetWithTermVector == false && fieldOption == IndexReader.FieldOption.TERMVECTOR_WITH_POSITION) {
+ fieldSet.add(fi.name);
+ }
+ else if (fi.storeOffsetWithTermVector && fi.storePositionWithTermVector == false && fieldOption == IndexReader.FieldOption.TERMVECTOR_WITH_OFFSET) {
+ fieldSet.add(fi.name);
+ }
+ else if ((fi.storeOffsetWithTermVector && fi.storePositionWithTermVector) &&
+ fieldOption == IndexReader.FieldOption.TERMVECTOR_WITH_POSITION_OFFSET) {
+ fieldSet.add(fi.name);
+ }
+ }
+ return fieldSet;
+ }
+
+ @Override
+ public boolean hasNorms(String field) {
+ ensureOpen();
+ return norms.containsKey(field);
+ }
+
+ @Override
+ public byte[] norms(String field) throws IOException {
+ ensureOpen();
+ final SegmentNorms norm = norms.get(field);
+ if (norm == null) {
+ // not indexed, or norms not stored
+ return null;
+ }
+ return norm.bytes();
+ }
+
+ @Override
+ protected void doSetNorm(int doc, String field, byte value)
+ throws IOException {
+ SegmentNorms norm = norms.get(field);
+ if (norm == null) {
+ // field does not store norms
+ throw new IllegalStateException("Cannot setNorm for field " + field + ": norms were omitted");
+ }
+
+ normsDirty = true;
+ norm.copyOnWrite()[doc] = value; // set the value
+ }
+
+ /** Read norms into a pre-allocated array. */
+ @Override
+ public synchronized void norms(String field, byte[] bytes, int offset)
+ throws IOException {
+
+ ensureOpen();
+ SegmentNorms norm = norms.get(field);
+ if (norm == null) {
+ Arrays.fill(bytes, offset, bytes.length, Similarity.getDefault().encodeNormValue(1.0f));
+ return;
+ }
+
+ norm.bytes(bytes, offset, maxDoc());
+ }
+
+ // For testing
+ /** @lucene.internal */
+ int getPostingsSkipInterval() {
+ return core.getTermsReader().getSkipInterval();
+ }
+
+ private void openNorms(Directory cfsDir, int readBufferSize) throws IOException {
+ long nextNormSeek = SegmentNorms.NORMS_HEADER.length; //skip header (header unused for now)
+ int maxDoc = maxDoc();
+ for (int i = 0; i < core.fieldInfos.size(); i++) {
+ FieldInfo fi = core.fieldInfos.fieldInfo(i);
+ if (norms.containsKey(fi.name)) {
+ // in case this SegmentReader is being re-opened, we might be able to
+ // reuse some norm instances and skip loading them here
+ continue;
+ }
+ if (fi.isIndexed && !fi.omitNorms) {
+ Directory d = directory();
+ String fileName = si.getNormFileName(fi.number);
+ if (!si.hasSeparateNorms(fi.number)) {
+ d = cfsDir;
+ }
+
+ // singleNormFile means multiple norms share this file
+ boolean singleNormFile = IndexFileNames.matchesExtension(fileName, IndexFileNames.NORMS_EXTENSION);
+ IndexInput normInput = null;
+ long normSeek;
+
+ if (singleNormFile) {
+ normSeek = nextNormSeek;
+ if (singleNormStream == null) {
+ singleNormStream = d.openInput(fileName, readBufferSize);
+ singleNormRef = new AtomicInteger(1);
+ } else {
+ singleNormRef.incrementAndGet();
+ }
+ // All norms in the .nrm file can share a single IndexInput since
+ // they are only used in a synchronized context.
+ // If this were to change in the future, a clone could be done here.
+ normInput = singleNormStream;
+ } else {
+ normInput = d.openInput(fileName);
+ // if the segment was created in 3.2 or after, we wrote the header for sure,
+ // and don't need to do the sketchy file size check. otherwise, we check
+ // if the size is exactly equal to maxDoc to detect a headerless file.
+ // NOTE: remove this check in Lucene 5.0!
+ String version = si.getVersion();
+ final boolean isUnversioned =
+ (version == null || StringHelper.getVersionComparator().compare(version, "3.2") < 0)
+ && normInput.length() == maxDoc();
+ if (isUnversioned) {
+ normSeek = 0;
+ } else {
+ normSeek = SegmentNorms.NORMS_HEADER.length;
+ }
+ }
+
+ norms.put(fi.name, new SegmentNorms(normInput, fi.number, normSeek, this));
+ nextNormSeek += maxDoc; // increment also if some norms are separate
+ }
+ }
+ }
+
+ boolean termsIndexLoaded() {
+ return core.termsIndexIsLoaded();
+ }
+
+ // NOTE: only called from IndexWriter when a near
+ // real-time reader is opened, or applyDeletes is run,
+ // sharing a segment that's still being merged. This
+ // method is not thread safe, and relies on the
+ // synchronization in IndexWriter
+ void loadTermsIndex(int termsIndexDivisor) throws IOException {
+ core.loadTermsIndex(si, termsIndexDivisor);
+ }
+
+ // for testing only
+ boolean normsClosed() {
+ if (singleNormStream != null) {
+ return false;
+ }
+ for (final SegmentNorms norm : norms.values()) {
+ if (norm.refCount > 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // for testing only
+ boolean normsClosed(String field) {
+ return norms.get(field).refCount == 0;
+ }
+
+ /**
+ * Create a clone from the initial TermVectorsReader and store it in the ThreadLocal.
+ * @return TermVectorsReader
+ */
+ TermVectorsReader getTermVectorsReader() {
+ TermVectorsReader tvReader = termVectorsLocal.get();
+ if (tvReader == null) {
+ TermVectorsReader orig = core.getTermVectorsReaderOrig();
+ if (orig == null) {
+ return null;
+ } else {
+ try {
+ tvReader = (TermVectorsReader) orig.clone();
+ } catch (CloneNotSupportedException cnse) {
+ return null;
+ }
+ }
+ termVectorsLocal.set(tvReader);
+ }
+ return tvReader;
+ }
+
+ TermVectorsReader getTermVectorsReaderOrig() {
+ return core.getTermVectorsReaderOrig();
+ }
+
+ /** Return a term frequency vector for the specified document and field. The
+ * vector returned contains term numbers and frequencies for all terms in
+ * the specified field of this document, if the field had storeTermVector
+ * flag set. If the flag was not set, the method returns null.
+ * @throws IOException
+ */
+ @Override
+ public TermFreqVector getTermFreqVector(int docNumber, String field) throws IOException {
+ // Check if this field is invalid or has no stored term vector
+ ensureOpen();
+ FieldInfo fi = core.fieldInfos.fieldInfo(field);
+ if (fi == null || !fi.storeTermVector)
+ return null;
+
+ TermVectorsReader termVectorsReader = getTermVectorsReader();
+ if (termVectorsReader == null)
+ return null;
+
+ return termVectorsReader.get(docNumber, field);
+ }
+
+
+ @Override
+ public void getTermFreqVector(int docNumber, String field, TermVectorMapper mapper) throws IOException {
+ ensureOpen();
+ FieldInfo fi = core.fieldInfos.fieldInfo(field);
+ if (fi == null || !fi.storeTermVector)
+ return;
+
+ TermVectorsReader termVectorsReader = getTermVectorsReader();
+ if (termVectorsReader == null) {
+ return;
+ }
+
+
+ termVectorsReader.get(docNumber, field, mapper);
+ }
+
+
+ @Override
+ public void getTermFreqVector(int docNumber, TermVectorMapper mapper) throws IOException {
+ ensureOpen();
+
+ TermVectorsReader termVectorsReader = getTermVectorsReader();
+ if (termVectorsReader == null)
+ return;
+
+ termVectorsReader.get(docNumber, mapper);
+ }
+
+ /** 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.
+ * @throws IOException
+ */
+ @Override
+ public TermFreqVector[] getTermFreqVectors(int docNumber) throws IOException {
+ ensureOpen();
+
+ TermVectorsReader termVectorsReader = getTermVectorsReader();
+ if (termVectorsReader == null)
+ return null;
+
+ return termVectorsReader.get(docNumber);
+ }
+
+ /** {@inheritDoc} */
+ @Override
+ public String toString() {
+ final StringBuilder buffer = new StringBuilder();
+ if (hasChanges) {
+ buffer.append('*');
+ }
+ buffer.append(si.toString(core.dir, pendingDeleteCount));
+ return buffer.toString();
+ }
+
+ /**
+ * Return the name of the segment this reader is reading.
+ */
+ public String getSegmentName() {
+ return core.segment;
+ }
+
+ /**
+ * Return the SegmentInfo of the segment this reader is reading.
+ */
+ SegmentInfo getSegmentInfo() {
+ return si;
+ }
+
+ void setSegmentInfo(SegmentInfo info) {
+ si = info;
+ }
+
+ void startCommit() {
+ rollbackSegmentInfo = (SegmentInfo) si.clone();
+ rollbackHasChanges = hasChanges;
+ rollbackDeletedDocsDirty = deletedDocsDirty;
+ rollbackNormsDirty = normsDirty;
+ rollbackPendingDeleteCount = pendingDeleteCount;
+ for (SegmentNorms norm : norms.values()) {
+ norm.rollbackDirty = norm.dirty;
+ }
+ }
+
+ void rollbackCommit() {
+ si.reset(rollbackSegmentInfo);
+ hasChanges = rollbackHasChanges;
+ deletedDocsDirty = rollbackDeletedDocsDirty;
+ normsDirty = rollbackNormsDirty;
+ pendingDeleteCount = rollbackPendingDeleteCount;
+ for (SegmentNorms norm : norms.values()) {
+ norm.dirty = norm.rollbackDirty;
+ }
+ }
+
+ /** Returns the directory this index resides in. */
+ @Override
+ public Directory directory() {
+ // Don't ensureOpen here -- in certain cases, when a
+ // cloned/reopened reader needs to commit, it may call
+ // this method on the closed original reader
+ return core.dir;
+ }
+
+ // This is necessary so that cloned SegmentReaders (which
+ // share the underlying postings data) will map to the
+ // same entry in the FieldCache. See LUCENE-1579.
+ @Override
+ public final Object getCoreCacheKey() {
+ return core.freqStream;
+ }
+
+ @Override
+ public Object getDeletesCacheKey() {
+ return deletedDocs;
+ }
+
+ @Override
+ public long getUniqueTermCount() {
+ return core.getTermsReader().size();
+ }
+
+ /**
+ * Lotsa tests did hacks like:<br/>
+ * SegmentReader reader = (SegmentReader) IndexReader.open(dir);<br/>
+ * They broke. This method serves as a hack to keep hacks working
+ * We do it with R/W access for the tests (BW compatibility)
+ * @deprecated Remove this when tests are fixed!
+ */
+ @Deprecated
+ static SegmentReader getOnlySegmentReader(Directory dir) throws IOException {
+ return getOnlySegmentReader(IndexReader.open(dir,false));
+ }
+
+ static SegmentReader getOnlySegmentReader(IndexReader reader) {
+ if (reader instanceof SegmentReader)
+ return (SegmentReader) reader;
+
+ if (reader instanceof DirectoryReader) {
+ IndexReader[] subReaders = reader.getSequentialSubReaders();
+ if (subReaders.length != 1)
+ throw new IllegalArgumentException(reader + " has " + subReaders.length + " segments instead of exactly one");
+
+ return (SegmentReader) subReaders[0];
+ }
+
+ throw new IllegalArgumentException(reader + " is not a SegmentReader or a single-segment DirectoryReader");
+ }
+
+ @Override
+ public int getTermInfosIndexDivisor() {
+ return core.termsIndexDivisor;
+ }
+
+ @Override
+ protected void readerFinished() {
+ // Do nothing here -- we have more careful control on
+ // when to notify that a SegmentReader has finished,
+ // because a given core is shared across many cloned
+ // SegmentReaders. We only notify once that core is no
+ // longer used (all SegmentReaders sharing it have been
+ // closed).
+ }
+}