/* XXL: The eXtensible and fleXible Library for data processing
Copyright (C) 2000-2011 Prof. Dr. Bernhard Seeger
Head of the Database Research Group
Department of Mathematics and Computer Science
University of Marburg
Germany
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 3 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; If not, see <http://www.gnu.org/licenses/>.
http://code.google.com/p/xxl/
*/
package xxl.core.io;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import xxl.core.collections.containers.Container;
import xxl.core.cursors.AbstractCursor;
import xxl.core.cursors.Cursor;
import xxl.core.cursors.sources.io.InputStreamCursor;
import xxl.core.functions.AbstractFunction;
import xxl.core.functions.Constant;
import xxl.core.functions.Function;
import xxl.core.io.converters.Converter;
import xxl.core.util.WrappingRuntimeException;
/**
* This class contains some static methods which can be used
* to store objects in a chain of linked Block-objects
* inside a container. This is useful, because the size of
* objects inside a container is usually bounded by the
* page size.
* <p>
* Each Block is supposed to have a block header. The first 5
* bytes of each Block are reserved for storing internally used
* data. The next bytes are used to store a pointer to the
* next Block (size is determined by the objectIdConverter of
* the Container).
* <p>
* So, each method gets the page size and the size of the header.
* The size of the header has to be larger than or equal to
* 5+size of the identifyers of the Container.
*/
public class LinkedContainerBlocks {
/**
* No instances are allowed (only static methods).
*/
private LinkedContainerBlocks() {
}
/**
* Internally used class which returns a cursor of
* linked blocks.
*/
private static class LinkedContainerBlockCursor extends AbstractCursor {
/** The page identifyer of the next page which is visited. */
protected Object nextPageId;
/** The container */
protected Container container;
/** The function which reads and returns the next page identifyer from a block object. */
protected Function readNextPageId;
/**
* Constructs a Cursor which delivers Blocks which are connected via links inside
* a container.
* @param container The container used.
* @param firstPageId The start identifyer of the chain of blocks.
* @param readNextPageId The function which reads and returns the next page identifyer from a block object.
*/
public LinkedContainerBlockCursor (Container container, Object firstPageId, Function readNextPageId) {
this.container = container;
this.nextPageId = firstPageId;
this.readNextPageId = readNextPageId;
}
/**
* Determines iff there is another Block.
* @return true iff there is another Block.
*/
public boolean hasNextObject() {
return nextPageId!=null;
}
/**
* Returns the next Block.
* @return the next Block.
*/
public Object nextObject() {
Block b = (Block) container.get(nextPageId);
if (b.get(4)==1)
nextPageId = readNextPageId.invoke(b);
else
nextPageId=null;
return b;
}
}
/**
* Reads and returns objects from a container starting at the given page
* identifyer.
* @param container The container used to store the data.
* @param converter The converter used to deserialize the objects of the iteration.
* @param pageId First Identifyer of the Container where the chain of blocks starts.
* @param blockSize The size of each Block inside the Container.
* @param headerSize The size of the header inside each Block.
* @return The cursor containing the objects from the chained Blocks.
*/
public static Cursor readObjectsFromLinkedContainerBlocks(final Container container, Converter converter, Object pageId, int blockSize, int headerSize) {
Cursor cursor = new LinkedContainerBlockCursor(
container,
pageId,
new AbstractFunction() {
public Object invoke(Object block) {
try {
return container.objectIdConverter().read(((Block) block).dataInputStream(5),null);
}
catch (IOException e) {
throw new WrappingRuntimeException(e);
}
}
}
);
InputStream is = new MultiBlockInputStream(
cursor,
headerSize,
blockSize-headerSize,
Block.GET_REAL_LENGTH
);
return new InputStreamCursor(
new java.io.DataInputStream(is),
converter
);
}
/**
* Removes a chain of linked blocks from the container.
* @param container The container used to store the data.
* @param pageId The page identifyer of the first Block to be
* removed. This does not have to be the first Block of the chain.
* @param removeFirstBlock True iff the Block with the identifyer
* pageId also has to be removed.
*/
public static void removeLinkedBlocks(Container container, Object pageId, boolean removeFirstBlock) {
Object newId;
boolean remove = removeFirstBlock;
while (true) {
Block b = (Block) container.get(pageId);
if (b.get(4)==1) {
try {
newId = container.objectIdConverter().read(b.dataInputStream(5),null);
}
catch (IOException e) {
throw new WrappingRuntimeException(e);
}
if (remove)
container.remove(pageId);
else
remove = true;
pageId = newId;
}
else {
if (remove)
container.remove(pageId);
return;
}
}
}
/**
* Writes the objects from the Iterator into the Container. If multiple
* Blocks are used, they are linked.
* @param container The container used to store the data.
* @param converter The converter used to serialize the objects of the iteration.
* @param it The objects to be stored.
* @param firstPageId First Identifyer of the Container which has to be
* overwritten. If a chain of Blocks is detected, then all of them are
* overwritten or removed. If firstPageId is null then only a new chain is
* written.
* @param blockSize The size of each Block inside the Container.
* @param headerSize The size of the header inside each Block.
* @return The page identifyer used for the first Block inside the Container.
*/
public static Object writeObjectsToLinkedContainerBlocks(Container container, Converter converter, Iterator it, Object firstPageId, int blockSize, int headerSize) {
Object pageId=null;
Cursor cursor = new ObjectToBlockCursor(
it,
converter,
blockSize-headerSize,
headerSize,
container.objectIdConverter().getSerializedSize(),
// max EntrySize (container id plus tuple id)
Block.SET_REAL_LENGTH
);
// store Blocks inside container and link them
if (cursor.hasNext()) {
Object lastId=firstPageId;
Object currentId;
boolean readNextId = firstPageId!=null;
Block lastBlock = (Block) cursor.next();
Block currentBlock;
if (lastId==null)
lastId = container.reserve(new Constant(lastBlock));
pageId = lastId;
while (cursor.hasNext()) {
currentId = null;
currentBlock = (Block) cursor.next();
currentBlock.set(4,(byte) 0);
if (readNextId) {
Block b = (Block) container.get(lastId);
if (b.get(4)==1) {
try {
currentId = container.objectIdConverter().read(b.dataInputStream(5),null);
}
catch (IOException e) {
throw new WrappingRuntimeException(e);
}
}
else
readNextId = false;
}
if (currentId==null)
currentId = container.reserve(new Constant(currentBlock));
// link the Blocks
try {
lastBlock.set(4,(byte) 1);
container.objectIdConverter().write(lastBlock.dataOutputStream(5),currentId);
}
catch (IOException e) {
throw new WrappingRuntimeException(e);
}
container.update(lastId, lastBlock);
lastBlock = currentBlock;
lastId = currentId;
}
if (readNextId)
removeLinkedBlocks(container, lastId, false);
// last link is already 0
container.update(lastId,lastBlock);
}
return pageId;
}
}