package org.mulgara.store.xa;
import org.apache.log4j.Logger;
import java.io.IOException;
import org.mulgara.query.TuplesException;
import org.mulgara.store.tuples.AbstractTuples;
import org.mulgara.store.tuples.DenseLongMatrix;
import org.mulgara.store.xa.Block;
import org.mulgara.store.xa.BlockFile;
/**
* Split memory backed from file backed cache lines.
*
* @created 2004-03-24
*
* @author Andrae Muys
*
* @version $Revision: 1.9 $
*
* @modified $Date: 2005/01/05 04:59:12 $
*
* @maintenanceAuthor $Author: newmana $
*
* @company <A href="mailto:info@PIsoftware.com">Plugged In Software</A>
*
* @copyright ©2004 <a href="http://www.pisoftware.com/">Plugged In
* Software Pty Ltd</a>
*
* @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a>
*/
public class BlockCacheLine extends CacheLine {
protected long[] currentTuple;
protected long[] previousTuple;
protected long[] prefix;
protected BlockFile file;
protected Block block;
protected long initialBlockId;
protected long nextBlockId;
protected int nrBlocks;
protected int blockSize;
protected int offset;
protected int width;
protected int nextTuple;
protected int tuplesPerBlock;
private final static Logger logger = Logger.getLogger(BlockCacheLine.class);
public BlockCacheLine(BlockFile file,
int blockSize,
DenseLongMatrix buffer,
int size) throws TuplesException {
super(size);
this.file = file;
this.width = buffer.getWidth();
if (width == 0) {
throw new IllegalArgumentException("Attempt to materialize a tuple " +
"with no variables.");
}
this.blockSize = blockSize;
this.initialBlockId = file.getNrBlocks() - 1;
if (this.initialBlockId < 0) {
this.initialBlockId = 0;
}
this.currentTuple = new long[width];
this.previousTuple = new long[width];
try {
appendBufferToFile(file, blockSize, buffer, size);
} catch (IOException ie) {
logger.warn("Failed to write Temporary File");
throw new TuplesException("Failed to write Temporary File", ie);
}
this.nextBlockId = this.initialBlockId;
try {
this.block = file.readBlock(this.nextBlockId++);
} catch (IOException ie) {
logger.warn("Failed to read Temporary File");
throw new TuplesException("Failed to read Temporary File", ie);
}
this.offset = 0;
}
public boolean isEmpty() {
if (prefix == null) {
return nextTuple > segmentSize;
} else {
return nextTuple > segmentSize || matchPrefix(currentTuple, prefix) != 0;
}
}
public void advance() throws TuplesException {
assert file != null; // Indicates close() has been called.
try {
if (isEmpty()) {
logger.debug("advancing empty tuples");
block = null;
return;
}
// If just cloned, then read in previous block.
if (block == null) {
if (logger.isDebugEnabled()) logger.debug("BlockCache " + this + " Refreshing from clone block " + (nextBlockId - 1));
block = file.readBlock(nextBlockId - 1);
}
if (endOfBlock(offset)) {
block = file.readBlock(nextBlockId++);
offset = 0;
}
long[] tmp = previousTuple;
previousTuple = currentTuple;
currentTuple = tmp;
offset = loadTupleFromBlock(currentTuple, block, offset);
if (logger.isDebugEnabled()) {
logger.debug("currentTuple: " + AbstractTuples.toString(currentTuple) + " new offset: " + offset);
}
nextTuple++;
} catch (IOException ie) {
logger.warn("IO Error accessing temporary file");
throw new TuplesException("IO Error accessing temporary file", ie);
}
}
public void reset(long[] prefix) throws TuplesException {
if (logger.isDebugEnabled()) {
logger.debug("Entering reset with prefix: " + AbstractTuples.toString(prefix));
}
super.reset(prefix);
assert prefix.length <= width;
this.prefix = prefix.length > 0 ? (long[])prefix.clone() : null;
try {
if (this.prefix == null) {
block = file.readBlock(initialBlockId);
offset = 0;
nextTuple = 0;
} else {
block = findBlock(this.prefix);
offset = findOffset(this.prefix, block);
loadTupleFromBlock(currentTuple, block, offset);
nextTuple = (int)(block.getBlockId() - initialBlockId) * tuplesPerBlock + (offset / width);
}
nextBlockId = block.getBlockId() + 1;
} catch (IOException ie) {
logger.warn("IO Error accessing temporary file", ie);
throw new TuplesException("IO Error accessing temporary file", ie);
}
}
public long[] getCurrentTuple(long[] tuple) {
return currentTuple;
}
public long[] getPreviousTuple(long[] tuple) {
return previousTuple;
}
public void close(int closer) throws TuplesException {
super.close(closer);
if (block != null) {
block = null;
file = null;
}
}
public Object clone() {
BlockCacheLine b = (BlockCacheLine)super.clone();
b.block = null;
b.currentTuple = (long[])currentTuple.clone();
b.previousTuple = (long[])previousTuple.clone();
return b;
}
private int appendBufferToFile(BlockFile file, int blockSize,
DenseLongMatrix buffer, int size)
throws IOException {
tuplesPerBlock = blockSize / (this.width * SIZEOF_LONG);
// (n + d - 1) / d rounds up n/d.
nrBlocks = (size + tuplesPerBlock - 1) / tuplesPerBlock;
file.setNrBlocks(this.initialBlockId + nrBlocks + 1);
long blockId = this.initialBlockId;
Block block = file.allocateBlock(blockId++);
int offset = 0;
for (int i = 0; i < size; i++) {
if (endOfBlock(offset)) {
block.write();
block = file.allocateBlock(blockId++);
offset = 0;
}
for (int j = 0; j < this.width; j++) {
block.putLong(offset++, buffer.get(i, j));
}
}
block.write();
return offset;
}
/**
* @return true if there is no more room in block for another tuple.
*/
private boolean endOfBlock(int offset) {
return (offset + width) * SIZEOF_LONG > blockSize;
}
private int loadTupleFromBlock(long[] currentTuple, Block block, int offset) {
for (int i = 0; i < currentTuple.length; i++) {
currentTuple[i] = block.getLong(offset++);
}
return offset;
}
/**
* Finds the block in the current cacheline containing the first tuple matching prefix.
*
* @param prefix Prefix to match
* @return The block containing the current prefix.
*/
private Block findBlock(long[] prefix) throws TuplesException {
if (logger.isDebugEnabled()) logger.debug("Finding block matching prefix");
try {
assert prefix.length > 0 && prefix.length <= width;
Block first = file.readBlock(initialBlockId);
long[] tmp = new long[width];
loadTupleFromBlock(tmp, first, 0);
if (logger.isDebugEnabled()) {
logger.debug("Initial tuple for block " + first.getBlockId() + " : " + AbstractTuples.toString(tmp));
}
if (compareBlockWithPrefix(first, prefix) >= 0) return first;
final long lastBlockId = initialBlockId + nrBlocks - 1;
Block last = file.readBlock(lastBlockId);
boolean found;
switch (compareBlockWithPrefix(last, prefix)) {
case -1:
return last;
case 0:
found = true;
break;
case +1:
found = false;
break;
default:
throw new IllegalStateException("compareBlockWithPrefix returned non-comparison");
}
Block result = findBlock(prefix, found, initialBlockId, first, lastBlockId, last, null);
if (result == null) {
throw new TuplesException("Failed to find block within valid range, is BlockCacheLine sorted?");
}
return result;
} catch (IOException ei) {
throw new TuplesException("IO Error while searching BlockCacheLine", ei);
}
}
/**
* Find the block with the smallest blockId containing a possible match on the prefix.
*
* INV: 1 <= prefix.length <= width.
* if !found then lowBlock < prefix < highBlock
* if found then lowBlock < prefix == highBlock
* @maintenanceAuthor barmintor
*/
private Block findBlock(long[] prefix, boolean found, long lowBound, Block lowBlock, long highBound, Block highBlock, Block recycleBlock) throws TuplesException {
if (logger.isDebugEnabled()) {
logger.debug("finding Block with prefix: " + AbstractTuples.toString(prefix) + " found: " + found +
" lowBound: " + lowBound + " highBound: " + highBound);
}
try {
if (highBound - lowBound <= 1) return highBlock;
long midBound = (int)((lowBound + highBound) / 2);
Block midBlock = file.recycleBlock(midBound, recycleBlock);
switch (compareBlockWithPrefix(midBlock, prefix)) {
case -1:
return findBlock(prefix, found, midBound, midBlock, highBound, highBlock, lowBlock);
case 0:
return findBlock(prefix, true, lowBound, lowBlock, midBound, midBlock, highBlock);
case +1:
assert !found;
return findBlock(prefix, false, lowBound, lowBlock, midBound, midBlock, highBlock);
default:
throw new IllegalStateException("compareBlockWithPrefix returned non-comparison");
}
} catch (IOException ei) {
throw new TuplesException("IO Error while searching BlockCacheLine", ei);
}
}
private int compareBlockWithPrefix(Block block, long[] prefix) {
assert prefix.length <= width;
long[] first = new long[width];
loadTupleFromBlock(first, block, 0);
switch (matchPrefix(first, prefix)) {
case -1: // Smallest tuple in block < prefix
long[] last = new long[width];
loadTupleFromBlock(last, block, tupleIdToOffset(tuplesPerBlock - 1));
switch (matchPrefix(last, prefix)) {
case -1: // Largest tuple in block < prefix
return -1; // Entire block < prefix
case 0: // Largest tuple in block == prefix
case +1: // Largest tuple in block > prefix
if (logger.isDebugEnabled()) {
logger.debug("Found prefix in block: " + block.getBlockId() + " prefix: " +
AbstractTuples.toString(prefix) + " first: " + AbstractTuples.toString(first) +
" last: " + AbstractTuples.toString(last));
}
return 0; // Block ranges over prefix
default:
throw new IllegalStateException("matchPrefix returned non-comparison");
}
case 0: // Smallest tuple in block == prefix
return 0; // Block includes prefix
case +1: // Smallest tuple in block > prefix
return +1; // Entire block > prefix
default:
throw new IllegalStateException("matchPrefix returned non-comparison");
}
}
long[] readTupleFromBlock(long[] tuple, Block block, int tupleId) throws TuplesException {
if (tupleId < 0) {
throw new TuplesException("Error tupleId < 0 :" + tupleId);
}
if (tupleId > tuplesPerBlock) {
throw new TuplesException("Error tupleId(" + tupleId + ") > tuplesPerBlock(" + tuplesPerBlock + ")");
}
if (tuple == null) {
tuple = new long[width];
}
loadTupleFromBlock(tuple, block, tupleIdToOffset(tupleId));
return tuple;
}
int tupleIdToOffset(int tupleId) {
return tupleId * width;
}
/**
* Returns the offset of the first tuple in a block matching the given prefix.
*
* @param prefix Prefix to match
* @param block Block to search
* @return Offset of first match of prefix in block
*/
private int findOffset(long[] prefix, Block block) throws TuplesException {
assert prefix.length > 0 && prefix.length <= width;
long[] first = readTupleFromBlock(null, block, 0);
if (matchPrefix(first, prefix) >= 0) {
return 0;
}
int highBound;
long lastBlockId = initialBlockId + nrBlocks - 1; // Note blockId's are 0-indexed.
if (block.getBlockId() < lastBlockId) {
highBound = tuplesPerBlock - 1; // Last tuple in a full block.
} else if (block.getBlockId() == lastBlockId) {
int tupleIndexOfFirstTupleInLastBlock = tuplesPerBlock * (nrBlocks - 1);
int numberOfTuplesInLastBlock = segmentSize - tupleIndexOfFirstTupleInLastBlock;
highBound = numberOfTuplesInLastBlock - 1;
} else {
throw new TuplesException("BlockId past end of BlockCacheLine");
}
long[] last = readTupleFromBlock(null, block, highBound);
boolean found;
switch (matchPrefix(last, prefix)) {
case -1:
return tupleIdToOffset(highBound);
case 0:
found = true;
break;
case +1:
found = false;
break;
default:
throw new IllegalStateException("compareBlockWithPrefix returned non-comparison");
}
return findOffset(prefix, block, found, 0, highBound);
}
/**
* Find the smallest offset with tuple < prefix
*
* INV: 1 <= prefix.length <= width.
* if !found then lowBlock < prefix < highBlock
* if found then lowBlock < prefix == highBlock
*/
private int findOffset(long[] prefix, Block block, boolean found, int lowBound, int highBound) throws TuplesException {
if (highBound - lowBound <= 1) {
return tupleIdToOffset(highBound);
}
int midBound = (int)((lowBound + highBound) / 2);
long[] tuple = readTupleFromBlock(null, block, midBound);
switch (matchPrefix(tuple, prefix)) {
case -1:
return findOffset(prefix, block, found, midBound, highBound);
case 0:
return findOffset(prefix, block, true, lowBound, midBound);
case +1:
assert !found;
return findOffset(prefix, block, false, lowBound, midBound);
default:
throw new IllegalStateException("compareBlockWithPrefix returned non-comparison");
}
}
}