/*
* 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.exoplatform.services.jcr.impl.core.query.lucene;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermDocs;
import org.apache.lucene.index.TermPositions;
import java.io.IOException;
import java.util.BitSet;
import java.util.Map;
/**
* Overwrites the methods that would modify the index and throws an
* {@link UnsupportedOperationException} in each of those methods. A
* <code>ReadOnlyIndexReader</code> will always show all documents that have
* not been deleted at the time when the index reader is created.
*/
class ReadOnlyIndexReader extends RefCountingIndexReader
{
/**
* The deleted documents as initially read from the IndexReader passed
* in the constructor of this class.
*/
private final BitSet deleted;
/**
* The version of the index reader from where the deleted BitSet was
* obtained from.
*/
private long deletedDocsVersion;
/**
* Creates a new index reader based on <code>reader</code> at
* <code>modificationTick</code>.
*
* @param reader the underlying <code>IndexReader</code>.
* @param deleted the documents that are deleted in
* <code>reader</code>.
* @param deletedDocsVersion the version of the index reader from where the
* deleted BitSet was obtained from.
*/
public ReadOnlyIndexReader(SharedIndexReader reader, BitSet deleted, long deletedDocsVersion)
{
super(reader);
this.deleted = deleted;
this.deletedDocsVersion = deletedDocsVersion;
// acquire underlying reader
reader.acquire();
}
/**
* @return version of the deleted docs.
*/
long getDeletedDocsVersion()
{
return deletedDocsVersion;
}
/**
* Returns the tick value when the underlying {@link CachingIndexReader} was
* created.
*
* @return the creation tick for the underlying reader.
*/
long getCreationTick()
{
return getBase().getCreationTick();
}
/**
* Updates the deleted documents in this index reader. When this method
* returns this index reader will have the same documents marked as deleted
* as the passed <code>reader</code>.
* <br>
* This method is not thread-safe! Make sure no other thread is concurrently
* using this reader at the same time.
*
* @param reader the reader from where to obtain the deleted documents
* info.
*/
void updateDeletedDocs(CommittableIndexReader reader)
{
int maxDoc = reader.maxDoc();
for (int i = 0; i < maxDoc; i++)
{
if (reader.isDeleted(i))
{
deleted.set(i);
}
}
deletedDocsVersion = reader.getModificationCount();
}
/**
* Returns the <code>DocId</code> of the parent of <code>n</code> or
* {@link DocId#NULL} if <code>n</code> does not have a parent
* (<code>n</code> is the root node).
*
* @param n the document number.
* @return the <code>DocId</code> of <code>n</code>'s parent.
* @throws IOException if an error occurs while reading from the index.
*/
public DocId getParent(int n) throws IOException
{
return getBase().getParent(n, deleted);
}
/**
* Returns the {@link SharedIndexReader} this reader is based on.
*
* @return the {@link SharedIndexReader} this reader is based on.
*/
public SharedIndexReader getBase()
{
return (SharedIndexReader)in;
}
//---------------------< IndexReader overwrites >---------------------------
/**
* Returns true if document <code>n</code> has been deleted
* @param n the document number
* @return true if document <code>n</code> has been deleted
*/
public boolean isDeleted(int n)
{
return deleted.get(n);
}
/**
* Returns <code>true</code> if any documents have been deleted.
*
* @return <code>true</code> if any documents have been deleted.
*/
public boolean hasDeletions()
{
return !deleted.isEmpty();
}
/**
* Returns the number of documents in this index reader.
*
* @return the number of documents in this index reader.
*/
public int numDocs()
{
return maxDoc() - deleted.cardinality();
}
/**
* @exception UnsupportedOperationException always
*/
protected final void doDelete(int docNum)
{
throw new UnsupportedOperationException("IndexReader is read-only");
}
/**
* @exception UnsupportedOperationException always
*/
protected final void doUndeleteAll()
{
throw new UnsupportedOperationException("IndexReader is read-only");
}
/**
* @exception UnsupportedOperationException always
*/
protected final void doCommit(Map<String, String> commitUserData)
{
if (!hasChanges)
{
return;
}
throw new UnsupportedOperationException("IndexReader is read-only");
}
/**
* Wraps the underlying <code>TermDocs</code> and filters out documents
* marked as deleted.<br>
* If <code>term</code> is for a {@link FieldNames#UUID} field and this
* <code>ReadOnlyIndexReader</code> does not have such a document,
* {@link EmptyTermDocs#INSTANCE} is returned.
*
* @param term the term to enumerate the docs for.
* @return TermDocs for <code>term</code>.
* @throws IOException if an error occurs while reading from the index.
*/
public TermDocs termDocs(Term term) throws IOException
{
// do not wrap for empty TermDocs
TermDocs td = in.termDocs(term);
if (td != EmptyTermDocs.INSTANCE)
{
td = new FilteredTermDocs(td);
}
return td;
}
/**
* Wraps the underlying <code>TermDocs</code> and filters out documents
* marked as deleted.
*
* @return TermDocs over the whole index.
* @throws IOException if an error occurs while reading from the index.
*/
public TermDocs termDocs() throws IOException
{
return new FilteredTermDocs(super.termDocs());
}
/**
* Wraps the underlying <code>TermPositions</code> and filters out documents
* marked as deleted.
*
* @return TermPositions over the whole index.
* @throws IOException if an error occurs while reading from the index.
*/
public TermPositions termPositions() throws IOException
{
return new FilteredTermPositions(super.termPositions());
}
//----------------------< FilteredTermDocs >--------------------------------
/**
* Filters a wrapped TermDocs by omitting documents marked as deleted.
*/
private class FilteredTermDocs extends FilterTermDocs
{
/**
* Creates a new filtered TermDocs based on <code>in</code>.
*
* @param in the TermDocs to filter.
*/
public FilteredTermDocs(TermDocs in)
{
super(in);
}
/**
* {@inheritDoc}
*/
public boolean next() throws IOException
{
boolean hasNext = super.next();
while (hasNext && deleted.get(super.doc()))
{
hasNext = super.next();
}
return hasNext;
}
/**
* {@inheritDoc}
*/
public int read(int[] docs, int[] freqs) throws IOException
{
int count;
for (count = 0; count < docs.length && next(); count++)
{
docs[count] = doc();
freqs[count] = freq();
}
return count;
}
/**
* {@inheritDoc}
*/
public boolean skipTo(int i) throws IOException
{
boolean exists = super.skipTo(i);
while (exists && deleted.get(doc()))
{
exists = next();
}
return exists;
}
}
//---------------------< FilteredTermPositions >----------------------------
/**
* Filters a wrapped TermPositions by omitting documents marked as deleted.
*/
private final class FilteredTermPositions extends FilteredTermDocs implements TermPositions
{
/**
* Creates a new filtered TermPositions based on <code>in</code>.
*
* @param in the TermPositions to filter.
*/
public FilteredTermPositions(TermPositions in)
{
super(in);
}
/**
* {@inheritDoc}
*/
public int nextPosition() throws IOException
{
return ((TermPositions)this.in).nextPosition();
}
/**
* {@inheritDoc}
*/
public int getPayloadLength()
{
return ((TermPositions)in).getPayloadLength();
}
/**
* {@inheritDoc}
*/
public byte[] getPayload(byte[] data, int offset) throws IOException
{
return ((TermPositions)in).getPayload(data, offset);
}
/**
* {@inheritDoc}
*/
public boolean isPayloadAvailable()
{
return ((TermPositions)in).isPayloadAvailable();
}
}
}