/*
* ModeShape (http://www.modeshape.org)
*
* Licensed 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.modeshape.jcr.query.engine.process;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicLong;
import org.mapdb.BTreeKeySerializer;
import org.mapdb.Serializer;
import org.modeshape.common.logging.Logger;
import org.modeshape.jcr.cache.CachedNode;
import org.modeshape.jcr.cache.CachedNodeSupplier;
import org.modeshape.jcr.index.local.MapDB.KeySerializerWithComparator;
import org.modeshape.jcr.query.BufferManager;
import org.modeshape.jcr.query.BufferManager.DistinctBuffer;
import org.modeshape.jcr.query.BufferManager.SortingBuffer;
import org.modeshape.jcr.query.NodeSequence;
import org.modeshape.jcr.query.RowExtractors.ExtractFromRow;
import org.modeshape.jcr.query.engine.process.BufferedRows.BufferedRow;
import org.modeshape.jcr.query.engine.process.BufferedRows.BufferedRowFactory;
import org.modeshape.jcr.query.model.TypeSystem.TypeFactory;
/**
* @author Randall Hauch (rhauch@redhat.com)
*/
public abstract class BufferingSequence extends DelegatingSequence {
protected static final Logger logger = Logger.getLogger(BufferingSequence.class);
protected static final boolean trace = logger.isTraceEnabled();
protected final SortingBuffer<Object, BufferedRow> buffer;
protected final BufferedRowFactory<? extends BufferedRow> rowFactory;
protected final ExtractFromRow extractor;
protected final CachedNodeSupplier cache;
protected final int width;
protected final String workspaceName;
protected final AtomicLong remainingRowCount = new AtomicLong();
protected final AtomicLong rowsLeftInBatch = new AtomicLong();
@SuppressWarnings( "unchecked" )
protected BufferingSequence( String workspaceName,
NodeSequence delegate,
ExtractFromRow extractor,
BufferManager bufferMgr,
CachedNodeSupplier nodeCache,
boolean pack,
boolean useHeap,
boolean allowDuplicates ) {
super(delegate);
assert extractor != null;
this.workspaceName = workspaceName;
this.width = delegate.width();
this.cache = nodeCache;
this.extractor = extractor;
// Set up the row factory based upon the width of the delegate sequence...
this.rowFactory = BufferedRows.serializer(nodeCache, width);
// Set up the buffer ...
SortingBuffer<Object, BufferedRow> buffer = null;
TypeFactory<?> keyType = extractor.getType();
if (allowDuplicates) {
@SuppressWarnings( "rawtypes" )
Serializer<? extends Comparable> keySerializer = (Serializer<? extends Comparable<?>>)bufferMgr.serializerFor(keyType);
buffer = bufferMgr.createSortingWithDuplicatesBuffer(keySerializer, extractor.getType().getComparator(),
(BufferedRowFactory<BufferedRow>)rowFactory).keepSize(true)
.useHeap(useHeap).make();
} else {
BTreeKeySerializer<Object> keySerializer = (BTreeKeySerializer<Object>)bufferMgr.bTreeKeySerializerFor(keyType, pack);
if (keySerializer instanceof KeySerializerWithComparator) {
keySerializer = ((KeySerializerWithComparator<Object>)keySerializer).withComparator(extractor.getType()
.getComparator());
}
buffer = bufferMgr.createSortingBuffer(keySerializer, (BufferedRowFactory<BufferedRow>)rowFactory).keepSize(true)
.useHeap(useHeap).make();
}
this.buffer = buffer;
}
@Override
public boolean isEmpty() {
return false;
}
protected long rowCount() {
return buffer.size();
}
protected BufferedRow createRow( Batch currentRow ) {
return rowFactory.createRow(currentRow);
}
/**
* Load all of the rows from the supplied sequence into the buffer.
*
* @param sequence the node sequence; may not be null
* @param extractor the extractor for the sortable value; may not be null
* @param rowsWithNullKey the buffer into which should be placed all rows for which the extracted key value is null; may be
* null if these are not to be kept
* @return the size of the first non-empty batch, or 0 if there are no rows found
*/
protected int loadAll( NodeSequence sequence,
ExtractFromRow extractor,
DistinctBuffer<BufferedRow> rowsWithNullKey ) {
// Put all of the batches from the sequence into the buffer
Batch batch = sequence.nextBatch();
int batchSize = 0;
Object value = null;
boolean firstBatchCounted = false;
while (batch != null) {
while (batch.hasNext()) {
batch.nextRow();
value = extractor.getValueInRow(batch);
if (value instanceof Object[]) {
// Put each of the values in the buffer ...
for (Object v : (Object[])value) {
buffer.put(v, createRow(batch));
}
} else if (value != null) {
buffer.put(value, createRow(batch));
} else if (rowsWithNullKey != null) {
rowsWithNullKey.addIfAbsent(createRow(batch));
}
if (!firstBatchCounted) {
++batchSize;
}
}
firstBatchCounted = batchSize != 0;
batch = sequence.nextBatch();
}
return batchSize;
}
protected Batch batchFrom( final Iterator<BufferedRow> rows,
final long maxBatchSize ) {
if (rows == null || !rows.hasNext()) return null;
if (maxBatchSize == 0 || remainingRowCount.get() <= 0) return NodeSequence.emptyBatch(workspaceName, this.width);
final long rowsInBatch = Math.min(maxBatchSize, remainingRowCount.get());
rowsLeftInBatch.set(rowsInBatch);
return new Batch() {
private BufferedRow current;
@Override
public int width() {
return width;
}
@Override
public long rowCount() {
return rowsInBatch;
}
@Override
public String getWorkspaceName() {
return workspaceName;
}
@Override
public boolean isEmpty() {
return rowsInBatch <= 0;
}
@Override
public boolean hasNext() {
return rowsLeftInBatch.get() > 0 && rows.hasNext();
}
@Override
public void nextRow() {
current = rows.next();
remainingRowCount.decrementAndGet();
rowsLeftInBatch.decrementAndGet();
}
@Override
public CachedNode getNode() {
return current.getNode();
}
@Override
public CachedNode getNode( int index ) {
return current.getNode(index);
}
@Override
public float getScore() {
return current.getScore();
}
@Override
public float getScore( int index ) {
return current.getScore(index);
}
@Override
public String toString() {
return "(buffered-batch size=" + rowsInBatch + " )";
}
};
}
@Override
public void close() {
try {
super.close();
} finally {
buffer.close();
}
}
}