All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jdbm.recman.RecordFile Maven / Gradle / Ivy

/**
 * JDBM LICENSE v1.00
 *
 * Redistribution and use of this software and associated documentation
 * ("Software"), with or without modification, are permitted provided
 * that the following conditions are met:
 *
 * 1. Redistributions of source code must retain copyright
 *    statements and notices.  Redistributions must also contain a
 *    copy of this document.
 *
 * 2. Redistributions in binary form must reproduce the
 *    above copyright notice, this list of conditions and the
 *    following disclaimer in the documentation and/or other
 *    materials provided with the distribution.
 *
 * 3. The name "JDBM" must not be used to endorse or promote
 *    products derived from this Software without prior written
 *    permission of Cees de Groot.  For written permission,
 *    please contact [email protected].
 *
 * 4. Products derived from this Software may not be called "JDBM"
 *    nor may "JDBM" appear in their names without prior written
 *    permission of Cees de Groot.
 *
 * 5. Due credit should be given to the JDBM Project
 *    (http://jdbm.sourceforge.net/).
 *
 * THIS SOFTWARE IS PROVIDED BY THE JDBM PROJECT AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
 * CEES DE GROOT OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Copyright 2000 (C) Cees de Groot. All Rights Reserved.
 * Contributions are Copyright (C) 2000 by their associated contributors.
 *
 * $Id: RecordFile.java,v 1.6 2005/06/25 23:12:32 doomdark Exp $
 */
package jdbm.recman;


import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;

import org.apache.directory.server.i18n.I18n;


/**
 *  This class represents a random access file as a set of fixed size
 *  records. Each record has a physical record number, and records are
 *  cached in order to improve access.
 * 

* The set of dirty records on the in-use list constitutes a transaction. * Later on, we will send these records to some recovery thingy. */ public final class RecordFile { private TransactionManager transactionManager; // state transitions: free -> inUse -> dirty -> inTxn -> free // free is a cache, thus a FIFO. The rest are hashes. /** The list of free pages */ private final LinkedList free = new LinkedList(); /** The map of pages being currently used */ private final HashMap inUse = new HashMap(); /** The map of dirty pages (page being modified) */ private final HashMap dirty = new HashMap(); /** The map of page in a transaction */ private final HashMap inTxn = new HashMap(); /** A flag set if transactions is disabled. Default to false */ private boolean transactionsDisabled = false; /** The length of a single block. */ public final static int BLOCK_SIZE = 4096; /** The extension of a record file */ final static String EXTENSION = ".db"; /** A block of clean data to wipe clean pages. */ final static byte[] cleanData = new byte[BLOCK_SIZE]; /** The underlying file */ private RandomAccessFile file; /** The file name */ private final String fileName; /** * Creates a new object on the indicated filename. The file is * opened in read/write mode. * * @param fileName the name of the file to open or create, without * an extension. * @throws IOException whenever the creation of the underlying * RandomAccessFile throws it. */ RecordFile( String fileName ) throws IOException { this.fileName = fileName; file = new RandomAccessFile( fileName + EXTENSION, "rw" ); } /** * @return The TransactionManager if the transaction system is enabled. * @throws IOException If we can't create a TransactionManager */ TransactionManager getTxnMgr() throws IOException { if ( transactionsDisabled ) { throw new IllegalStateException( "Transactions are disabled." ); } if ( transactionManager == null ) { transactionManager = new TransactionManager( this ); } return transactionManager; } /** * @return the file name. */ String getFileName() { return fileName; } /** * Disables transactions: doesn't sync and doesn't use the * transaction manager. */ void disableTransactions() { transactionsDisabled = true; } /** * Gets a block from the file. The returned byte array is the in-memory * copy of the record, and thus can be written (and subsequently released * with a dirty flag in order to write the block back). * * @param blockId The record number to retrieve. */ BlockIo get( long blockId ) throws IOException { // try in transaction list, dirty list, free list BlockIo blockIo = inTxn.get( blockId ); if ( blockIo != null ) { inTxn.remove( blockId ); inUse.put( blockId, blockIo ); return blockIo; } blockIo = dirty.get( blockId ); if ( blockIo != null ) { dirty.remove( blockId ); inUse.put( blockId, blockIo ); return blockIo; } for ( Iterator iterator = free.iterator(); iterator.hasNext(); ) { BlockIo cur = iterator.next(); if ( cur.getBlockId() == blockId ) { blockIo = cur; iterator.remove(); inUse.put( blockId, blockIo ); return blockIo; } } // sanity check: can't be on in use list if ( inUse.get( blockId ) != null ) { throw new Error( I18n.err( I18n.ERR_554, blockId ) ); } // get a new node and read it from the file blockIo = getNewBlockIo( blockId ); long offset = blockId * BLOCK_SIZE; long fileLength = file.length(); if ( ( fileLength > 0 ) && ( offset <= fileLength ) ) { read( file, offset, blockIo.getData(), BLOCK_SIZE ); } inUse.put( blockId, blockIo ); blockIo.setClean(); return blockIo; } /** * Releases a block. * * @param blockId The record number to release. * @param isDirty If true, the block was modified since the get(). */ void release( long blockId, boolean isDirty ) throws IOException { BlockIo blockIo = inUse.get( blockId ); if ( blockIo == null ) { throw new IOException( I18n.err( I18n.ERR_555, blockId ) ); } if ( !blockIo.isDirty() && isDirty ) { blockIo.setDirty(); } release( blockIo ); } /** * Releases a block. * * @param block The block to release. */ void release( BlockIo block ) { inUse.remove( block.getBlockId() ); if ( block.isDirty() ) { // System.out.println( "Dirty: " + key + block ); dirty.put( block.getBlockId(), block ); } else { if ( !transactionsDisabled && block.isInTransaction() ) { inTxn.put( block.getBlockId(), block ); } else { free.add( block ); } } } /** * Discards a block (will not write the block even if it's dirty) * * @param block The block to discard. */ void discard( BlockIo block ) { inUse.remove( block.getBlockId() ); // note: block not added to free list on purpose, because // it's considered invalid } /** * Commits the current transaction by flushing all dirty buffers to disk. */ void commit() throws IOException { // debugging... if ( !inUse.isEmpty() && inUse.size() > 1 ) { showList( inUse.values().iterator() ); throw new Error( I18n.err( I18n.ERR_556, inUse.size() ) ); } // System.out.println("committing..."); if ( dirty.size() == 0 ) { // if no dirty blocks, skip commit process return; } if ( !transactionsDisabled ) { getTxnMgr().start(); } for ( BlockIo blockIo : dirty.values() ) { // System.out.println("node " + node + " map size now " + dirty.size()); if ( transactionsDisabled ) { sync( blockIo ); blockIo.setClean(); free.add( blockIo ); } else { getTxnMgr().add( blockIo ); inTxn.put( blockIo.getBlockId(), blockIo ); } } dirty.clear(); if ( !transactionsDisabled ) { getTxnMgr().commit(); } } /** * Rollback the current transaction by discarding all dirty buffers */ void rollback() throws IOException { // debugging... if ( !inUse.isEmpty() ) { showList( inUse.values().iterator() ); throw new Error( I18n.err( I18n.ERR_557, inUse.size() ) ); } // System.out.println("rollback..."); dirty.clear(); if ( !transactionsDisabled ) { getTxnMgr().synchronizeLogFromDisk(); } if ( !inTxn.isEmpty() ) { showList( inTxn.values().iterator() ); throw new Error( I18n.err( I18n.ERR_558, inTxn.size() ) ); } } /** * Commits and closes file. */ void close() throws IOException { if ( !dirty.isEmpty() ) { commit(); } if ( !transactionsDisabled ) { getTxnMgr().shutdown(); } if ( !inTxn.isEmpty() ) { showList( inTxn.values().iterator() ); throw new Error( I18n.err( I18n.ERR_559 ) ); } // these actually ain't that bad in a production release if ( !dirty.isEmpty() ) { System.out.println( "ERROR: dirty blocks at close time" ); showList( dirty.values().iterator() ); throw new Error( I18n.err( I18n.ERR_560 ) ); } if ( !inUse.isEmpty() ) { System.out.println( "ERROR: inUse blocks at close time" ); showList( inUse.values().iterator() ); throw new Error( I18n.err( I18n.ERR_561 ) ); } // debugging stuff to keep an eye on the free list // System.out.println("Free list size:" + free.size()); file.close(); file = null; } /** * Force closing the file and underlying transaction manager. * Used for testing purposed only. */ void forceClose() throws IOException { if ( !transactionsDisabled ) { getTxnMgr().forceClose(); } file.close(); } /** * Prints contents of a list */ private void showList( Iterator i ) { int cnt = 0; while ( i.hasNext() ) { System.out.println( "elem " + cnt + ": " + i.next() ); cnt++; } } /** * Returns a new BlockIo. The BlockIo is retrieved (and removed) from the * released list or created new. */ private BlockIo getNewBlockIo( long blockId ) throws IOException { BlockIo blockIo = null; if ( !free.isEmpty() ) { blockIo = ( BlockIo ) free.removeFirst(); blockIo.setBlockId( blockId ); } if ( blockIo == null ) { blockIo = new BlockIo( blockId, new byte[BLOCK_SIZE] ); } blockIo.setView( null ); return blockIo; } /** * Synchronizes a BlockIo to disk. This is called by the transaction manager's * synchronization code. * * @param blockIo The blocIo to write on disk * @exception IOException If we have a problem while trying to write the blockIo to disk */ void sync( BlockIo blockIo ) throws IOException { byte[] data = blockIo.getData(); if ( data != null ) { // Write the data to disk now. long offset = blockIo.getBlockId() * BLOCK_SIZE; file.seek( offset ); file.write( data ); } } /** * Releases a node from the transaction list, if it was sitting there. * * @param recycle true if block data can be reused */ void releaseFromTransaction( BlockIo node, boolean recycle ) throws IOException { if ( ( inTxn.remove( node.getBlockId() ) != null ) && recycle ) { free.add( node ); } } /** * Synchronizes the file. */ void sync() throws IOException { file.getFD().sync(); } /** * Utility method: Read a block from a RandomAccessFile */ private static void read( RandomAccessFile file, long offset, byte[] buffer, int nBytes ) throws IOException { file.seek( offset ); int remaining = nBytes; int pos = 0; while ( remaining > 0 ) { int read = file.read( buffer, pos, remaining ); if ( read == -1 ) { System.arraycopy( cleanData, 0, buffer, pos, remaining ); break; } remaining -= read; pos += read; } } /** * {@inheritDoc} */ public String toString() { StringBuilder sb = new StringBuilder(); sb.append( "RecordFile<" ).append( fileName ).append( ", " ); // The file size sb.append( "size : " ); try { sb.append( file.length() ).append( "bytes" ); } catch ( IOException ioe ) { sb.append( "unknown" ); } // Transactions if ( transactionsDisabled ) { sb.append( "(noTx)" ); } else { sb.append( "(Tx)" ); } // Dump the free blocks sb.append( "\n Free blockIo : " ).append( free.size() ); for ( BlockIo blockIo : free ) { sb.append( "\n " ); sb.append( blockIo ); } // Dump the inUse blocks sb.append( "\n InUse blockIo : " ).append( inUse.size() ); for ( BlockIo blockIo : inUse.values() ) { sb.append( "\n " ); sb.append( blockIo ); } // Dump the dirty blocks sb.append( "\n Dirty blockIo : " ).append( dirty.size() ); for ( BlockIo blockIo : dirty.values() ) { sb.append( "\n " ); sb.append( blockIo ); } // Dump the inTxn blocks sb.append( "\n InTxn blockIo : " ).append( inTxn.size() ); for ( BlockIo blockIo : inTxn.values() ) { sb.append( "\n " ); sb.append( blockIo ); } return sb.toString(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy