/* * The contents of this file are subject to the Mozilla Public License * Version 1.1 (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.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * The Original Code is the Kowari Metadata Store. * * The Initial Developer of the Original Code is Plugged In Software Pty * Ltd (http://www.pisoftware.com, mailto:info@pisoftware.com). Portions * created by Plugged In Software Pty Ltd are Copyright (C) 2001,2002 * Plugged In Software Pty Ltd. All Rights Reserved. * * Contributor(s): N/A. * * [NOTE: The text of this Exhibit A may differ slightly from the text * of the notices in the Source Code files of the Original Code. You * should use the text of this Exhibit A rather than the text found in the * Original Code Source Code for Your Modifications.] * */ package org.mulgara.store.xa; // Java 2 standard packages import java.io.*; import java.nio.*; // Third party packages import org.apache.log4j.Logger; // Local packages import org.mulgara.util.Constants; /** * This class contains data from a disk block and is used to abstract disk access. * Blocks are pooled, and are used to represent an arbitrary area of a file. * * @created 2001-09-20 * * @author David Makepeace * * @version $Revision: 1.10 $ * * @modified $Date: 2005/06/30 01:14:40 $ * * @maintenanceAuthor $Author: pgearon $ * * @company <A href="mailto:info@PIsoftware.com">Plugged In Software</A> * * @copyright ©2001 <a href="http://www.pisoftware.com/">Plugged In * Software Pty Ltd</a> * * @licence <a href="{@docRoot}/../../LICENCE">Mozilla Public License v1.1</a> */ public final class Block { public static final long INVALID_BLOCK_ID = -1; private final static Logger logger = Logger.getLogger(Block.class); /** The property for the block type. May be "direct" or "javaHeap" */ public static final String MEM_TYPE_PROP = "mulgara.xa.memoryType"; /** Enumeration of the different memory types for blocks */ enum BlockMemoryType { DIRECT, HEAP }; /** The default value to use for the block memory type. Used when nothing is configured. */ private static final BlockMemoryType DEFAULT = BlockMemoryType.DIRECT; /** The configured type of block type to use. */ private static final BlockMemoryType BLOCK_TYPE; static { String defBlockType = System.getProperty(MEM_TYPE_PROP, DEFAULT.name()); if (defBlockType.equalsIgnoreCase(BlockMemoryType.DIRECT.name())) { BLOCK_TYPE = BlockMemoryType.DIRECT; } else if (defBlockType.equalsIgnoreCase(BlockMemoryType.HEAP.name())) { BLOCK_TYPE = BlockMemoryType.HEAP; } else { logger.warn("Invalid value for property " + MEM_TYPE_PROP + ": " + defBlockType); BLOCK_TYPE = DEFAULT; } } /** The file that this block is attached to. */ private BlockFile blockFile; /** The size of the data in this block. */ private int blockSize; /** The ID of this block, unique within the file, and usually maps to a block position. */ private long blockId; // TODO: check if this is still needed /** * Indicates if this block owns the buffer inside it. If the buffer * is owned then it must be freed (and written back to disk if it is * dirty) when the block is released. */ private boolean ownsBuffer = false; /** Offset of this block of data from the beginning of the buffer, in bytes. */ private int byteOffset; /** Offset of this block of data from the beginning of the buffer, in 32 bit integers. */ private int intOffset; /** Offset of this block of data from the beginning of the buffer, in 64 bit longs. */ private int longOffset; /** The buffer containing the data of the block. */ private ByteBuffer bb; /** A cached read only ByteBuffer used when a ByteBuffer source of data is needed. */ private ByteBuffer sbb; /** The buffer as 32 bit integers. */ private IntBuffer ib; /** The data buffer as 64 bit longs. */ private LongBuffer lb; /** * Builds a block. * * @param blockFile The file that this data block is from. * @param blockSize The size of the data. * @param blockId The ID of the block in the file. * @param byteOffset The offset of the data for this block, from the start of the buffer. * @param bb The buffer containing the data. * @param sbb A cached read-only buffer, useful to use as a source for writing operations * which require a buffer of data. * @param ib The 32 bit integer offset of the data for this block, from the start of the buffer. * @param lb The 64 bit long offset of the data for this block, from the start of the buffer. */ private Block( BlockFile blockFile, int blockSize, long blockId, int byteOffset, ByteBuffer bb, ByteBuffer sbb, IntBuffer ib, LongBuffer lb ) { init(blockFile, blockSize, blockId, byteOffset, bb, sbb, ib, lb); } /** * Factory method to produce a block. * * @param blockFile The file to get the data from. * @param blockSize The size of the data. * @param blockId The ID of the block in the file. * @param byteOffset The offset of the data for this block, from the start of the buffer. * @param bb The buffer containing the data. * @param sbb A cached read-only buffer, useful to use as a source for writing operations * which require a buffer of data. * @param ib The 32 bit integer offset of the data for this block, from the start of the buffer. * @param lb The 64 bit long offset of the data for this block, from the start of the buffer. * @return A new block. */ public static Block newInstance( BlockFile blockFile, int blockSize, long blockId, int byteOffset, ByteBuffer bb, ByteBuffer sbb, IntBuffer ib, LongBuffer lb ) { return new Block(blockFile, blockSize, blockId, byteOffset, bb, sbb, ib, lb); } /** * Create a new block, not attached to any file. * * @param blockSize The size of the block to create. * @return A new block, with no file data. */ public static Block newInstance(int blockSize) { return newInstance(null, blockSize, 0, ByteOrder.nativeOrder()); } /** * Factory method to create a new block, allocating a new data buffer. * * @param blockFile The file to get the block from. * @param blockSize The size of the data in the block. * @param blockId The ID of the block from the file. * @param byteOrder Represents little endian or big endian byte ordering. * @return A new block. */ public static Block newInstance(BlockFile blockFile, int blockSize, long blockId, ByteOrder byteOrder) { ByteBuffer buffer = BLOCK_TYPE == BlockMemoryType.DIRECT ? ByteBuffer.allocateDirect(blockSize).order(byteOrder) : ByteBuffer.allocate(blockSize).order(byteOrder); Block block = Block.newInstance( blockFile, blockSize, blockId, 0, buffer, null, null, null ); block.ownsBuffer = true; return block; } /** * @author barmintor * @param block The buffer to be re-used by a BlockFile * @param blockId The ID of the block from the file. */ static void recycleBuffer(Block block, long blockId) { block.ownsBuffer = false; block.bb.clear(); block.ib.rewind(); block.lb.rewind(); block.init(blockId, 0, block.bb, null, block.ib, block.lb); block.ownsBuffer = true; } /** * Gets a byte from the block. * * @param offset The location of the byte within the data block. * @return The byte value. */ public byte getByte(int offset) { return bb.get(byteOffset + offset); } /** * Gets an integer from the block * * @param offset The location of the integer within the data block. Indexed as the * number of integers into the buffer. * @return The int value. */ public int getInt(int offset) { return ib.get(intOffset + offset); } /** * Gets a UInt from the block * * @param offset The location of the integer within the data block. Indexed as the * number of integers into the buffer. * @return The UInt value (as a long, so the top bit is handled correctly). */ public long getUInt(int offset) { return (long) ib.get(intOffset + offset) & Constants.MASK32; } /** * Gets the Long attribute of the Block object * * @param offset The location of the long within the data block. Indexed as the * number of longs into the buffer. * @return The Long value */ public long getLong(int offset) { return lb.get(longOffset + offset); } /** * Gets an array of bytes from the buffer. * * @param offset The location of the required byte array within the data block. * @param ba The array of bytes to fill. */ public void get(int offset, byte[] ba) { int start = byteOffset + offset; for (int i = 0; i < ba.length; ++i) { ba[i] = bb.get(start + i); } } /** * Gets a buffer of bytes from the buffer. * * @param offset The location of the required buffer within the data block. * @param byteBuffer The buffer to fill. */ public void get(int offset, ByteBuffer byteBuffer) { assert offset + byteBuffer.remaining() <= blockSize; ByteBuffer src = bb.asReadOnlyBuffer(); int pos = byteOffset + offset; src.position(pos); src.limit(pos + byteBuffer.remaining()); byteBuffer.put(src); } /** * Gets a read-only portion of the buffer. * * @param offset The location of the required buffer within the data block. * @param size The size of the slice to retrieve. * @return The read only portion of the buffer desired. */ public ByteBuffer getSlice(int offset, int size) { assert offset + size <= blockSize; ByteBuffer data = bb.asReadOnlyBuffer(); data.position(byteOffset + offset); data.limit(byteOffset + offset + size); return data.slice(); } /** * Gets an array of integers from the buffer. * * @param offset The integer offset to get the data from. * @param ia The array of integers to fill. */ public void get(int offset, int[] ia) { int start = intOffset + offset; for (int i = 0; i < ia.length; ++i) { ia[i] = ib.get(start + i); } } /** * Gets an array of longs from the buffer. * * @param offset The long offset to get the data from. * @param la The array of longs to fill. */ public void get(int offset, long[] la) { int start = longOffset + offset; for (int i = 0; i < la.length; ++i) { la[i] = lb.get(start + i); } } /** * Copies bytes from this block to the specified block. Only the write * session should call this method. * * @param offset Offset of the data to get. * @param block The data block to fill. */ public void get(int offset, Block block) { block.put(0, this, offset, block.blockSize); } /** * Gets the ByteBuffer attribute of the Block object * * @return The ByteBuffer value */ public ByteBuffer getByteBuffer() { return bb; } /** * Gets the BlockId attribute of the Block object * * @return The BlockId value */ public long getBlockId() { return blockId; } /** * Gets the ByteOffset attribute of the Block object * * @return The ByteOffset value */ public int getByteOffset() { return byteOffset; } /** * Puts a byte into the buffer. * * @param offset The offset into the buffer of the byte to set. * @param b The value of the byte to set. */ public void putByte(int offset, byte b) { bb.put(byteOffset + offset, b); } /** * Puts an int into the buffer. * * @param offset The offset into buffer measured in 32 bit integers. * @param i The integer value to set. */ public void putInt(int offset, int i) { ib.put(intOffset + offset, i); } /** * Puts an unsigned int into the buffer. * * @param offset The offset into buffer measured in 32 bit integers. * @param ui The unsigned integer to set, uses a long to avoid the sign bit. */ public void putUInt(int offset, long ui) { assert ui >= 0 && ui < (1L << 32) : ui + " isn't an unsigned int"; ib.put(intOffset + offset, (int) ui); } /** * Puts a long into the data buffer. * * @param offset The offset into buffer measured in 64 bit longs. * @param l The long to set. */ public void putLong(int offset, long l) { lb.put(longOffset + offset, l); } /** * Puts an array of bytes into the data buffer. * * @param offset The byte offset into the buffer. * @param ba The byte array with the data to put in the buffer. */ public void put(int offset, byte[] ba) { assert offset + ba.length <= blockSize; // There is only one writer so it is safe to change the position. bb.position(byteOffset + offset); bb.put(ba); } /** * Puts an buffer of bytes into the data buffer. * * @param offset The byte offset into the destination buffer. * @param byteBuffer The byte buffer with the data to put in the destination buffer. */ public void put(int offset, ByteBuffer byteBuffer) { assert offset + byteBuffer.remaining() <= blockSize; // There is only one writer so it is safe to change the position. bb.position(byteOffset + offset); bb.put(byteBuffer); } /** * Puts an array of integers into the data buffer. * * @param offset The offset into the buffer, indexed by 32 bit integers. * @param ia The array of integers to write. */ public void put(int offset, int[] ia) { // There is only one writer so it is safe to change the position. ib.position(intOffset + offset); ib.put(ia); } /** * Puts an array of longs into the data buffer. * * @param offset The offset into the buffer, index by 64 bit longs. * @param la The array of longs to write. */ public void put(int offset, long[] la) { // There is only one writer so it is safe to change the position. lb.position(longOffset + offset); lb.put(la); } /** * Puts the whole of the buffer backed by the specified block into this block * starting from the specified offset. * * @param offset The offset into the data buffer of this block to write to. * @param block The block with the data to write to the data buffer. */ public void put(int offset, Block block) { put(offset, block, 0, block.blockSize); } /** * Puts some data from one block into this block. * * @param offset The offset into this block of data to put the new data. * @param block The block containing the data to put into this block. * @param srcOffset The offset into the block parameter to find the source data. * @param length The number of bytes from the source block to copy over. */ public void put(int offset, Block block, int srcOffset, int length) { // There is only one writer so it is safe to change the position. bb.position(byteOffset + offset); bb.put(block.getSourceBuffer(srcOffset, length)); } /** * Writes the data in this block out to the file that this block represents. * This operation must occur in a writing phase. * * @throws IOException There was an exception writing to the file. */ public void write() throws IOException { blockFile.writeBlock(this); } /** * Performs a copy-on-write function for the block. If this block is unmodified * then copy this block to a new block in the file, and set this block to refer * to the new file block. If the block has already been modified then there is * no need to do anything. This operation must occur in a writing phase. * * @throws IOException There was an exception writing to the file. */ public void modify() throws IOException { blockFile.modifyBlock(this); } /** * Tells the file that this block is no longer in use and can be recycled. This is * accomplished by putting it on the free list for the file. * * @throws IOException There was an error writing to the free list. */ public void free() throws IOException { blockFile.freeBlock(blockId); } /** * Determines if the contents of this block are equal to the specified block. * * @param o the block to compare to. * @return <code>true</code> if the blocks compare equal, both in ID and in content. */ public boolean equals(Object o) { if (!(o instanceof Block)) { return false; } Block block = (Block) o; if (blockSize != block.blockSize) { return false; } if (block.bb == bb && block.getBlockId() == blockId) { return true; } ByteBuffer bb1 = bb.asReadOnlyBuffer(); bb1.limit(byteOffset + blockSize); bb1.position(byteOffset); ByteBuffer bb2 = block.bb.asReadOnlyBuffer(); bb2.limit(block.byteOffset + blockSize); bb2.position(block.byteOffset); return bb1.equals(bb2); } /** @see java.lang.Object#hashCode() */ public int hashCode() { return blockSize * 13 + ((int)(blockId >> 32) ^ (int)(blockId & 0xFFFF)) * 17 + bb.hashCode() * 19; } /** * Sets the file this block is a part of. * Used by {@link ManagedBlockFile} to redirect calls to itself. * * @param blockFile The new BlockFile value */ void setBlockFile(BlockFile blockFile) { this.blockFile = blockFile; } /** * Sets the BlockId attribute of the Block object. * * @param blockId The new BlockId value. */ void setBlockId(long blockId) { assert ownsBuffer; this.blockId = blockId; } /** * Gets the SourceBuffer attribute of the Block object. * * @return The SourceBuffer value. */ ByteBuffer getSourceBuffer() { return sbb; } /** * Initializes a block with all of its buffers and offsets into those buffers. * * @param blockId ID of the block. * @param byteOffset The offset of the data for this block, from the start of the buffer. * @param bb The buffer containing the data. * @param sbb A cached read-only buffer, useful to use as a source for writing operations * which require a buffer of data. * @param ib The 32 bit integer offset of the data for this block, from the start of the buffer. * @param lb The 64 bit long offset of the data for this block, from the start of the buffer. */ void init( long blockId, int byteOffset, ByteBuffer bb, ByteBuffer sbb, IntBuffer ib, LongBuffer lb ) { assert !ownsBuffer; this.blockId = blockId; this.byteOffset = byteOffset; this.bb = bb; this.sbb = sbb; if (ib == null) { bb.rewind(); ib = bb.asIntBuffer(); } this.ib = ib; if (lb == null) { bb.rewind(); lb = bb.asLongBuffer(); } this.lb = lb; intOffset = byteOffset / Constants.SIZEOF_INT; longOffset = byteOffset / Constants.SIZEOF_LONG; } /** * Gets section out of the SourceBuffer attribute of the Block object. * * @param offset An offset into the current buffer. * @param length The length of the required data. * @return The SourceBuffer value, with a limit and position set. */ private ByteBuffer getSourceBuffer(int offset, int length) { if (sbb == null) { sbb = bb.asReadOnlyBuffer(); } // Must set limit before position or an exception may be thrown. sbb.limit(byteOffset + offset + length); sbb.position(byteOffset + offset); return sbb; } /** * Initializes a block for use. * * @param blockFile The file that this data block is from. * @param blockSize The size of the data. * @param blockId The ID of the block in the file. * @param byteOffset The offset of the data for this block, from the start of the buffer. * @param bb The buffer containing the data. * @param sbb A cached read-only buffer, useful to use as a source for writing operations * which require a buffer of data. * @param ib The 32 bit integer offset of the data for this block, from the start of the buffer. * @param lb The 64 bit long offset of the data for this block, from the start of the buffer. */ private void init( BlockFile blockFile, int blockSize, long blockId, int byteOffset, ByteBuffer bb, ByteBuffer sbb, IntBuffer ib, LongBuffer lb ) { this.blockFile = blockFile; this.blockSize = blockSize; init(blockId, byteOffset, bb, sbb, ib, lb); } /** * Initializes a block for use, not setting up any of the data buffer, but still pointing to * the file for the block.. * * @param blockFile The file that data block will come from. * @param blockId The ID of the block within the file. */ void init(BlockFile blockFile, long blockId) { assert ownsBuffer; this.blockFile = blockFile; this.blockId = blockId; } }