/*
* 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 java.io.IOException;
import java.util.BitSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implements a document id which can be based on a Node uuid or a lucene
* document number.
*/
abstract class DocId {
static final int[] EMPTY = new int[0];
private static final Logger log = LoggerFactory.getLogger("exo.jcr.component.core.DocId");
/**
* All DocIds with a value smaller than {@link Short#MAX_VALUE}.
*/
private static final PlainDocId[] LOW_DOC_IDS = new PlainDocId[Short.MAX_VALUE];
static {
for (int i = 0; i < LOW_DOC_IDS.length; i++) {
LOW_DOC_IDS[i] = new PlainDocId(i);
}
}
/**
* Indicates a null DocId. Will be returned if the root node is asked for
* its parent.
*/
static final DocId NULL = new DocId() {
/**
* Always returns an empty array.
* @param reader the index reader.
* @param docNumbers a int array for reuse as return value.
* @return always an empty array.
*/
final int[] getDocumentNumbers(MultiIndexReader reader,
int[] docNumbers) {
return EMPTY;
}
/**
* Always returns <code>this</code>.
* @param offset the offset to apply.
* @return always <code>this</code>.
*/
final DocId applyOffset(int offset) {
return this;
}
/**
* Always returns <code>true</code>.
* @param deleted the deleted documents.
* @return always <code>true</code>.
*/
final boolean isValid(BitSet deleted) {
return true;
}
};
/**
* Returns the document numbers of this <code>DocId</code>. An empty array
* is returned if this id is invalid.
*
* @param reader the IndexReader to resolve this <code>DocId</code>.
* @param docNumbers an array for reuse. An implementation should use the
* passed array as a container for the return value,
* unless the length of the returned array is different
* from <code>docNumbers</code>. In which case an
* implementation will create a new array with an
* appropriate size.
* @return the document numbers of this <code>DocId</code> or
* empty if it is invalid (e.g. does not exist).
* @throws IOException if an error occurs while reading from the index.
*/
abstract int[] getDocumentNumbers(MultiIndexReader reader, int[] docNumbers)
throws IOException;
/**
* Applies an offset to this <code>DocId</code>. The returned <code>DocId</code>
* may be the same as <code>this</code> if this <code>DocId</code> does
* not need to know about an offset.
*
* @param offset the offset to apply to.
* @return <code>DocId</code> with <code>offset</code> applied.
*/
abstract DocId applyOffset(int offset);
/**
* Returns <code>true</code> if this <code>DocId</code> is valid against the
* set of <code>deleted</code> documents; otherwise <code>false</code>.
*
* @param deleted the deleted documents.
* @return <code>true</code> if this <code>DocId</code> is not delted;
* otherwise <code>false</code>.
*/
abstract boolean isValid(BitSet deleted);
/**
* Creates a <code>DocId</code> based on a document number.
*
* @param docNumber the document number.
* @return a <code>DocId</code> based on a document number.
*/
static DocId create(int docNumber) {
if (docNumber < Short.MAX_VALUE) {
// use cached values for docNumbers up to 32k
return LOW_DOC_IDS[docNumber];
}
else
{
return new PlainDocId(docNumber);
}
}
// /**
// * Creates a <code>DocId</code> based on a node UUID.
// *
// * @param uuid the node uuid.
// * @return a <code>DocId</code> based on a node UUID.
// * @throws IllegalArgumentException if the <code>uuid</code> is malformed.
// */
// static DocId create(String uuid) {
// return create(uuid);
// }
/**
* Creates a <code>DocId</code> based on a node UUID.
*
* @param uuid the node uuid.
* @return a <code>DocId</code> based on a node UUID.
*/
static DocId create(String uuid) {
return new UUIDDocId(uuid);
}
/**
* Creates a <code>DocId</code> that references multiple UUIDs.
*
* @param uuids the UUIDs of the referenced nodes.
* @return a <code>DocId</code> based on multiple node UUIDs.
*/
static DocId create(String[] uuids) {
return new MultiUUIDDocId(uuids);
}
//--------------------------< internal >------------------------------------
/**
* <code>DocId</code> based on a document number.
*/
private static final class PlainDocId extends DocId {
/**
* The document number or <code>-1</code> if not set.
*/
private final int docNumber;
/**
* Creates a <code>DocId</code> based on a document number.
*
* @param docNumber the lucene document number.
*/
PlainDocId(int docNumber) {
this.docNumber = docNumber;
}
/**
* {@inheritDoc}
*/
int[] getDocumentNumbers(MultiIndexReader reader, int[] docNumbers) {
if (docNumbers.length == 1) {
docNumbers[0] = docNumber;
return docNumbers;
} else {
return new int[]{docNumber};
}
}
/**
* {@inheritDoc}
*/
DocId applyOffset(int offset) {
return new PlainDocId(docNumber + offset);
}
/**
* {@inheritDoc}
*/
boolean isValid(BitSet deleted) {
return !deleted.get(docNumber);
}
/**
* Returns a String representation for this <code>DocId</code>.
*
* @return a String representation for this <code>DocId</code>.
*/
public String toString() {
return "PlainDocId(" + docNumber + ")";
}
}
/**
* <code>DocId</code> based on a UUID.
*/
private static final class UUIDDocId extends DocId {
/**
* The node identifier or <code>null</code> if not set.
*/
private final String identifier;
/**
* The previously calculated foreign segment document id.
*/
private ForeignSegmentDocId doc;
/**
* Creates a <code>DocId</code> based on a Node uuid.
*
* @param uuid the Node uuid.
*/
UUIDDocId(String identifier) {
this.identifier = identifier;
}
/**
* {@inheritDoc}
*/
int[] getDocumentNumbers(MultiIndexReader reader, int[] docNumbers)
throws IOException {
int realDoc = -1;
ForeignSegmentDocId segDocId = doc;
if (segDocId != null) {
realDoc = reader.getDocumentNumber(segDocId);
}
if (realDoc == -1) {
// Cached doc was invalid => create new one
segDocId = reader.createDocId(identifier);
if (segDocId != null) {
realDoc = reader.getDocumentNumber(segDocId);
doc = segDocId;
}
}
if (docNumbers.length == 1) {
docNumbers[0] = realDoc;
return docNumbers;
} else {
return new int[]{realDoc};
}
}
/**
* This implementation will return <code>this</code>. Document number is
* not known until resolved in {@link #getDocumentNumbers(MultiIndexReader,int[])}.
*
* {@inheritDoc}
*/
DocId applyOffset(int offset) {
return this;
}
/**
* Always returns <code>true</code>.
*
* @param deleted the deleted documents.
* @return always <code>true</code>.
*/
boolean isValid(BitSet deleted) {
return true;
}
/**
* Returns a String representation for this <code>DocId</code>.
*
* @return a String representation for this <code>DocId</code>.
*/
public String toString() {
return "UUIDDocId(" + identifier + ")";
}
}
/**
* A DocId based on multiple UUIDDocIds.
*/
private static final class MultiUUIDDocId extends DocId {
/**
* The internal uuid based doc ids.
*/
private final UUIDDocId[] docIds;
/**
* @param uuids the uuids of the referenced nodes.
* @throws IllegalArgumentException if one of the uuids is malformed.
*/
MultiUUIDDocId(String[] uuids) {
this.docIds = new UUIDDocId[uuids.length];
for (int i = 0; i < uuids.length; i++) {
docIds[i] = new UUIDDocId(uuids[i]);
}
}
/**
* {@inheritDoc}
*/
int[] getDocumentNumbers(MultiIndexReader reader, int[] docNumbers)
throws IOException {
int[] tmp = new int[1];
docNumbers = new int[docIds.length];
for (int i = 0; i < docNumbers.length; i++) {
docNumbers[i] = docIds[i].getDocumentNumbers(reader, tmp)[0];
}
return docNumbers;
}
/**
* This implementation will return <code>this</code>. Document number is
* not known until resolved in {@link #getDocumentNumbers(MultiIndexReader,int[])}.
*
* {@inheritDoc}
*/
DocId applyOffset(int offset) {
return this;
}
/**
* Always returns <code>true</code>.
*
* @param deleted the deleted documents.
* @return always <code>true</code>.
*/
boolean isValid(BitSet deleted) {
return true;
}
/**
* Returns a String representation for this <code>DocId</code>.
*
* @return a String representation for this <code>DocId</code>.
*/
public String toString() {
StringBuilder sb = new StringBuilder("MultiUUIDDocId(");
String separator = "";
for (int i = 0; i < docIds.length; i++) {
sb.append(separator);
separator = ", ";
sb.append(docIds[i].identifier);
}
sb.append(")");
return sb.toString();
}
}
}