/**
* 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.
*/
package org.apache.solr.search;
import org.apache.lucene.index.*;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.LockObtainFailedException;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.FieldSelector;
import org.apache.lucene.util.Bits;
import org.apache.lucene.util.BytesRef;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
/** Solr wrapper for IndexReader that contains extra context.
* This is currently experimental, for internal use only, and subject to change.
*/
public class SolrIndexReader extends FilterIndexReader {
private final SolrIndexReader[] subReaders;
private final SolrIndexReader[] leafReaders;
private int[] leafOffsets;
private final SolrIndexReader parent;
private final int base; // docid offset of this reader within parent
private static int[] zeroIntArray = new int[]{0};
// top level searcher for this reader tree
// a bit if a hack currently... searcher needs to set
SolrIndexSearcher searcher;
// Shared info about the wrapped reader.
private SolrReaderInfo info;
/** Recursively wrap an IndexReader in SolrIndexReader instances.
* @param in the reader to wrap
* @param parent the parent, if any (null if none)
* @param base the docid offset in the parent (0 if top level)
*/
public SolrIndexReader(IndexReader in, SolrIndexReader parent, int base) {
super(in);
assert(!(in instanceof SolrIndexReader));
this.parent = parent;
this.base = base;
IndexReader subs[] = in.getSequentialSubReaders();
if (subs != null) {
subReaders = new SolrIndexReader[subs.length];
int numLeaves = subs.length;
leafOffsets = new int[numLeaves];
int b=0;
for (int i=0; i<subReaders.length; i++) {
SolrIndexReader sir = subReaders[i] = new SolrIndexReader(subs[i], this, b);
leafOffsets[i] = b;
b += sir.maxDoc();
IndexReader subLeaves[] = sir.leafReaders;
numLeaves += subLeaves.length - 1; // subtract 1 for the parent
}
leafReaders = getLeaves(numLeaves);
} else {
subReaders = null;
leafReaders = new SolrIndexReader[]{this};
leafOffsets = zeroIntArray;
}
}
private SolrIndexReader[] getLeaves(int numLeaves) {
// fast path for a normal multiReader
if (subReaders==null || numLeaves == subReaders.length) return subReaders;
SolrIndexReader[] leaves = new SolrIndexReader[numLeaves];
leafOffsets = new int[numLeaves];
int i=0;
int b = 0;
for (SolrIndexReader sir : subReaders) {
SolrIndexReader subLeaves[] = sir.leafReaders;
if (subLeaves == null) {
leafOffsets[i] = b;
b += sir.maxDoc();
leaves[i++] = sir;
} else {
for (SolrIndexReader subLeaf : subLeaves) {
leafOffsets[i] = b;
b += subLeaf.maxDoc();
leaves[i++] = subLeaf;
}
}
}
assert(i == numLeaves && b == maxDoc());
return leaves;
}
/** return the leaf readers in this reader tree, or an array of size 1 containing "this" if "this" is a leaf */
public SolrIndexReader[] getLeafReaders() {
return leafReaders;
}
/** Return the doc id offsets for each leaf reader. This will be different than getBase() for
* any leaf reader who is not a direct descendant of "this".
*/
public int[] getLeafOffsets() {
return leafOffsets;
}
/** Given an array of IndexReader offsets, find which contains the given doc */
public static int readerIndex(int doc, int[] offsets) { // find reader for doc doc:
int high = offsets.length - 1;
// fast-path for a big optimized index and a bunch of smaller ones.
if (high <= 0 || doc < offsets[1]) return 0;
int low = 1;
while (high >= low) {
int mid = (low + high) >>> 1;
int offset = offsets[mid];
// check low first since first segments are normally bigger.
if (doc < offset)
high = mid - 1;
else if (doc > offset) {
low = mid + 1;
}
else {
// exact match on the offset.
return mid;
}
}
// low is the insertion point, high should be just below that (and the segment we want).
return high;
}
static String shortName(Object o) {
return o.getClass().getSimpleName()+ "@" + Integer.toHexString(o.hashCode());
}
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("SolrIndexReader{this=").append(Integer.toHexString(this.hashCode()));
sb.append(",r=").append(shortName(in));
sb.append(",refCnt=").append(getRefCount());
sb.append(",segments=");
sb.append(subReaders == null ? 1 : subReaders.length);
if (parent != null) {
sb.append(",parent=").append(parent.toString());
}
sb.append('}');
return sb.toString();
}
static void setSearcher(SolrIndexReader sr, SolrIndexSearcher searcher) {
sr.searcher = searcher;
SolrIndexReader[] readers = sr.getSequentialSubReaders();
if (readers == null) return;
for (SolrIndexReader r : readers) {
setSearcher(r, searcher);
}
}
private static void buildInfoMap(SolrIndexReader other, HashMap<IndexReader, SolrReaderInfo> map) {
if (other == null) return;
map.put(other.getWrappedReader(), other.info);
SolrIndexReader[] readers = other.getSequentialSubReaders();
if (readers == null) return;
for (SolrIndexReader r : readers) {
buildInfoMap(r, map);
}
}
private static void setInfo(SolrIndexReader target, HashMap<IndexReader, SolrReaderInfo> map) {
SolrReaderInfo info = map.get(target.getWrappedReader());
if (info == null) info = new SolrReaderInfo(target.getWrappedReader());
target.info = info;
SolrIndexReader[] readers = target.getSequentialSubReaders();
if (readers == null) return;
for (SolrIndexReader r : readers) {
setInfo(r, map);
}
}
/** Copies SolrReaderInfo instances from the source to this SolrIndexReader */
public void associateInfo(SolrIndexReader source) {
// seemed safer to not mess with reopen() but simply set
// one set of caches from another reader tree.
HashMap<IndexReader, SolrReaderInfo> map = new HashMap<IndexReader, SolrReaderInfo>();
buildInfoMap(source, map);
setInfo(this, map);
}
public IndexReader getWrappedReader() {
return in;
}
/** returns the parent reader, or null of none */
public SolrIndexReader getParent() {
return parent;
}
/** returns the docid offset within the parent reader */
public int getBase() {
return base;
}
@Override
public Directory directory() {
return in.directory();
}
@Override
public Bits getDeletedDocs() {
return in.getDeletedDocs();
}
@Override
public TermFreqVector[] getTermFreqVectors(int docNumber) throws IOException {
return in.getTermFreqVectors(docNumber);
}
@Override
public TermFreqVector getTermFreqVector(int docNumber, String field)
throws IOException {
return in.getTermFreqVector(docNumber, field);
}
@Override
public void getTermFreqVector(int docNumber, String field, TermVectorMapper mapper) throws IOException {
in.getTermFreqVector(docNumber, field, mapper);
}
@Override
public void getTermFreqVector(int docNumber, TermVectorMapper mapper) throws IOException {
in.getTermFreqVector(docNumber, mapper);
}
@Override
public int numDocs() {
return in.numDocs();
}
@Override
public int maxDoc() {
return in.maxDoc();
}
@Override
public Document document(int n, FieldSelector fieldSelector) throws CorruptIndexException, IOException {
return in.document(n, fieldSelector);
}
@Override
public boolean hasDeletions() {
return in.hasDeletions();
}
@Override
protected void doUndeleteAll() throws CorruptIndexException, IOException {in.undeleteAll();}
@Override
public boolean hasNorms(String field) throws IOException {
return in.hasNorms(field);
}
@Override
public byte[] norms(String f) throws IOException {
return in.norms(f);
}
@Override
public void norms(String f, byte[] bytes, int offset) throws IOException {
in.norms(f, bytes, offset);
}
@Override
protected void doSetNorm(int d, String f, byte b) throws CorruptIndexException, IOException {
in.setNorm(d, f, b);
}
@Override
public Fields fields() throws IOException {
return in.fields();
}
@Override
public int docFreq(Term t) throws IOException {
ensureOpen();
return in.docFreq(t);
}
@Override
public int docFreq(String field, BytesRef t) throws IOException {
return in.docFreq(field, t);
}
@Override
public Terms terms(String field) throws IOException {
return in.terms(field);
}
@Override
public DocsEnum termDocsEnum(Bits skipDocs, String field, BytesRef term) throws IOException {
return in.termDocsEnum(skipDocs, field, term);
}
@Override
public DocsAndPositionsEnum termPositionsEnum(Bits skipDocs, String field, BytesRef term) throws IOException {
return in.termPositionsEnum(skipDocs, field, term);
}
@Override
protected void doDelete(int n) throws CorruptIndexException, IOException { in.deleteDocument(n); }
// Let FilterIndexReader handle commit()... we cannot override commit()
// or call in.commit() ourselves.
// protected void doCommit() throws IOException { in.commit(); }
@Override
protected void doClose() throws IOException {
in.close();
}
@Override
public Collection getFieldNames(IndexReader.FieldOption fieldNames) {
return in.getFieldNames(fieldNames);
}
@Override
public long getVersion() {
return in.getVersion();
}
@Override
public boolean isCurrent() throws CorruptIndexException, IOException {
return in.isCurrent();
}
@Override
public boolean isOptimized() {
return in.isOptimized();
}
@Override
public SolrIndexReader[] getSequentialSubReaders() {
return subReaders;
}
@Override
public int getSubReaderDocBase(IndexReader subReader) {
return in.getSubReaderDocBase(subReader);
}
@Override
public int hashCode() {
return in.hashCode();
}
@Override
public boolean equals(Object o) {
if (o instanceof SolrIndexReader) {
o = ((SolrIndexReader)o).in;
}
return in.equals(o);
}
@Override
public int getRefCount() {
return in.getRefCount();
}
@Override
public IndexReader reopen(IndexCommit commit) throws CorruptIndexException, IOException {
return in.reopen(commit);
}
@Override
public Object clone() {
// hmmm, is this right?
return super.clone();
}
@Override
public IndexReader clone(boolean openReadOnly) throws CorruptIndexException, IOException {
// hmmm, is this right?
return super.clone(openReadOnly);
}
@Override
public Map getCommitUserData() {
return in.getCommitUserData();
}
@Override
public long getUniqueTermCount() throws IOException {
return in.getUniqueTermCount();
}
@Override
public SolrIndexReader reopen(boolean openReadOnly) throws IOException {
IndexReader r = in.reopen(openReadOnly);
if (r == in) {
return this;
}
SolrIndexReader sr = new SolrIndexReader(r, null, 0);
sr.associateInfo(this);
return sr;
}
@Override
public SolrIndexReader reopen() throws CorruptIndexException, IOException {
return reopen(true);
}
@Override
public void decRef() throws IOException {
in.decRef();
}
@Override
public void deleteDocument(int docNum) throws StaleReaderException, CorruptIndexException, LockObtainFailedException, IOException {
in.deleteDocument(docNum);
}
@Override
public int deleteDocuments(Term term) throws StaleReaderException, CorruptIndexException, LockObtainFailedException, IOException {
return in.deleteDocuments(term);
}
@Override
public Document document(int n) throws CorruptIndexException, IOException {
return in.document(n);
}
// @Override
// public String getCommitUserData() {
// return in.getCommitUserData();
// }
@Override
public IndexCommit getIndexCommit() throws IOException {
return in.getIndexCommit();
}
@Override
public void incRef() {
in.incRef();
}
@Override
public int numDeletedDocs() {
return in.numDeletedDocs();
}
@Override
public void setNorm(int doc, String field, byte value) throws StaleReaderException, CorruptIndexException, LockObtainFailedException, IOException {
in.setNorm(doc, field, value);
}
@Override
public void setNorm(int doc, String field, float value) throws StaleReaderException, CorruptIndexException, LockObtainFailedException, IOException {
in.setNorm(doc, field, value);
}
@Override
public void undeleteAll() throws StaleReaderException, CorruptIndexException, LockObtainFailedException, IOException {
in.undeleteAll();
}
@Override
public Object getCoreCacheKey() {
return in.getCoreCacheKey();
}
@Override
public int getTermInfosIndexDivisor() {
return in.getTermInfosIndexDivisor();
}
}
/** SolrReaderInfo contains information that is the same for
* every SolrIndexReader that wraps the same IndexReader.
* Multiple SolrIndexReader instances will be accessing this
* class concurrently.
*/
class SolrReaderInfo {
private final IndexReader reader;
public SolrReaderInfo(IndexReader reader) {
this.reader = reader;
}
public IndexReader getReader() { return reader; }
}