/*
* 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.*;
// Third party packages
import org.apache.log4j.Logger;
// Local packages
import org.mulgara.util.Constants;
/**
* A file of AVLNodes forming an AVL tree.
* AVL trees are used for storing data in sorted order.
* Branches of these trees are also used to create new "phases"
* which allow modification of the file without affecting what
* other threads see. This allows a single phase to be a "writing"
* phase, while other phases all get to read their own immutable
* version of the file.
*
* @created 2001-10-03
*
* @author David Makepeace
* @author Paula Gearon
*
* @version $Revision: 1.10 $
*
* @modified $Date: 2005/07/05 04:23:54 $
*
* @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 AVLFile {
/** Logger. */
@SuppressWarnings("unused")
private final static Logger logger = Logger.getLogger(AVLFile.class);
/** The underlying block file */
private ManagedBlockFile avlBlockFile;
/** The size of the user data stored in each node of the tree. */
@SuppressWarnings("unused") // keep this member, since it isn't kept elsewhere
private int payloadSize;
/** The most recent phase for the file. */
private Phase currentPhase;
/**
* Creates a new block file which contains an AVL tree.
*
* @param file A {@link java.io.File} object giving the details of the file.
* @param payloadSize Size of the payload in longs. Must be at least 1.
* @throws IOException If an i/o error occurs.
*/
public AVLFile(File file, int payloadSize) throws IOException {
if (payloadSize < 1) {
throw new IllegalArgumentException("payloadSize is less than 1");
}
avlBlockFile = new ManagedBlockFile(file, (AVLNode.HEADER_SIZE + payloadSize) * Constants.SIZEOF_LONG,
BlockFile.IOType.MAPPED);
this.payloadSize = payloadSize;
}
/**
* Creates a new block file which contains an AVL tree.
*
* @param fileName The name of the file to create.
* @param payloadSize Size of the payload in longs. Must be at least 1.
* @throws IOException If an i/o error occurs.
*/
public AVLFile(String fileName, int payloadSize) throws IOException {
this(new File(fileName), payloadSize);
}
/**
* Returns the index of the leaf node in the find result which was returned
* when a node was not found.
*
* @param findResult A two element array resulting from a tree search.
* @return The higher node which is valid.
*/
public static int leafIndex(AVLNode[] findResult) {
if (findResult.length != 2) {
throw new IllegalArgumentException("findResult contains found node.");
}
return (
findResult[0] == null ||
findResult[0].getRightId() != AVLNode.NULL_NODE
) ? 1 : 0;
}
/**
* Releases the nodes returned from a find when a result was not found.
*
* @param avlNodes A two element array of AVLNodes. One or the other may be null.
*/
public static void release(AVLNode[] avlNodes) {
if (avlNodes[0] != null) {
avlNodes[0].release();
avlNodes[0] = null;
}
if ( (avlNodes.length == 2) && (avlNodes[1] != null)) {
avlNodes[1].release();
avlNodes[1] = null;
}
}
/**
* Reduces the file down to an empty tree.
*
* @throws IOException If an I/O error occurs.
*/
public void clear() throws IOException {
avlBlockFile.clear();
}
/**
* Wait until all data has been written to the file.
*
* @throws IOException If an I/O error occurs.
*/
public void force() throws IOException {
avlBlockFile.force();
}
/**
* Unmaps the file. This will have no effect if the file is not mapped.
*/
public synchronized void unmap() {
if (avlBlockFile != null) {
avlBlockFile.unmap();
}
}
/**
* Close the file.
*
* @throws IOException If an I/O error occurs.
*/
public synchronized void close() throws IOException {
if (avlBlockFile != null) {
avlBlockFile.close();
}
}
/**
* Deletes the file.
*
* @throws IOException If and I/O error occurs.
*/
public synchronized void delete() throws IOException {
try {
if (avlBlockFile != null) {
avlBlockFile.delete();
}
} finally {
avlBlockFile = null;
}
}
/**
* This class represents a set of branches in the file which contain
* a unique view of the data.
*/
public final class Phase implements PersistableMetaRoot {
// in longs
/** The total size of a node and Phase information, in longs. Not used. */
public final static int RECORD_SIZE =
AVLNode.HEADER_SIZE + ManagedBlockFile.Phase.RECORD_SIZE;
/** The offset of the root ID, in longs. */
private final static int IDX_ROOT_ID = 0;
/** The offset of the number of nodes in the phase, in longs. */
private final static int IDX_NR_NODES = 1;
/** The size of the Phase header, in longs. */
private final static int HEADER_SIZE = 2;
/** The ID of the block at the root of this phase tree. */
private long rootId;
/** The number of nodes in this pase tree. */
private long nrNodes;
/** The phase on the block file. */
private ManagedBlockFile.Phase avlBlockFilePhase;
/**
* Creates a new phase for this AVL tree.
*
* @throws IOException If and I/O error occurs.
*/
public Phase() throws IOException {
if (currentPhase == null) {
rootId = AVLNode.NULL_NODE;
nrNodes = 0;
} else {
rootId = currentPhase.rootId;
nrNodes = currentPhase.nrNodes;
}
avlBlockFilePhase = avlBlockFile.new Phase();
check();
currentPhase = this;
}
/**
* Constructs a phase, copying another phase.
*
* @param p The phase to copy.
* @throws IOException If an I/O error occurs.
*/
public Phase(Phase p) throws IOException {
assert p != null;
rootId = p.rootId;
nrNodes = p.nrNodes;
avlBlockFilePhase = avlBlockFile.new Phase(p.avlBlockFilePhase);
check();
currentPhase = this;
}
/**
* Construct a phase, copy the phase data found in an existing block.
*
* @param b The block containing the phase data.
* @param offset The offset of the phase data within the block.
* @throws IOException If an I/O error occurs.
*/
public Phase(Block b, int offset) throws IOException {
rootId = b.getLong(offset + IDX_ROOT_ID);
nrNodes = b.getLong(offset + IDX_NR_NODES);
avlBlockFilePhase = avlBlockFile.new Phase(b, offset + HEADER_SIZE);
check();
currentPhase = this;
}
/**
* Indicates if there are any remaining references to the current phase.
*
* @return <code>true</code> if the phase on the free list is still being referenced.
*/
public boolean isInUse() {
return avlBlockFilePhase.isInUse();
}
/**
* Retrieve the number of AVL nodes in this phase.
*
* @return The number of AVL nodes in the tree.
*/
public long getNrNodes() {
return nrNodes;
}
/**
* Get the AVLNode of the root of this phase tree.
*
* @return The AVL node at the root of this tree.
*/
public AVLNode getRootNode() {
return rootId != AVLNode.NULL_NODE ? AVLNode.newInstance(this, null, 0, rootId) : null;
}
/**
* Check if the tree contains any data.
*
* @return <code>true</code> if the tree is empty.
*/
public boolean isEmpty() {
return rootId == AVLNode.NULL_NODE;
}
/**
* Writes this PersistableMetaRoot to the specified Block. The ints are
* written at the specified offset.
*
* @param b the Block.
* @param offset the offset into the Block to start writing.
*/
public void writeToBlock(Block b, int offset) {
check();
b.putLong(offset + IDX_ROOT_ID, rootId);
b.putLong(offset + IDX_NR_NODES, nrNodes);
avlBlockFilePhase.writeToBlock(b, offset + HEADER_SIZE);
}
/**
* Checks if this phase if the current phase (the writing phase).
*
* @return <code>true</code> if this phase is the current one.
*/
boolean isCurrent() {
return this == currentPhase;
}
/**
* Insert the first AVLNode in the tree.
*
* @param newNode The node to become the only node in the tree.
* @throws IOException If an I/O error occurred.
*/
public void insertFirst(AVLNode newNode) throws IOException {
if (!isEmpty()) {
throw new IllegalStateException(
"insertFirst() called on AVL tree that is not empty");
}
// Tree is empty. Set the root.
setRootId(newNode.getId());
incNrNodes();
}
/**
* Finds an AVLNode containing the requested data.
*
* @param comparator The means of comparing the key to the payload in the AVLNodes.
* @param key The data to search for.
* @return An array of nodes. If the tree is empty, then <code>null</code>.
* If the data exists, then a single element array containing the required node.
* If the data does not exist then return the pair of nodes that the data exists between.
*/
public AVLNode[] find(AVLComparator comparator, long[] key) {
AVLNode rootNode = getRootNode();
if (rootNode == null) return null;
try {
return AVLNode.findDown(rootNode, comparator, key);
} finally {
rootNode.release();
}
}
/**
* Get a new AVLNode, unattached to any data.
*
* @return The new node.
* @throws IOException If an I/O error occurred.
*/
public AVLNode newAVLNodeInstance() throws IOException {
return AVLNode.newInstance(this);
}
/**
* Add a new reference to this phase.
*
* @return a {@link Token} representing a reference to this phase.
*/
public Token use() {
return new Token();
}
/**
* Sets the root of this phase to a particular node.
*
* @param rootId The ID of the AVLNode to use.
*/
void setRootId(long rootId) {
if (this != currentPhase) {
throw new IllegalStateException(
"Attempt to set the rootId on a read-only phase."
);
}
this.rootId = rootId;
}
/**
* Get the phase of the underlying block file.
*
* @return The {@link ManagedBlockFile.Phase} associated with this phase.
*/
ManagedBlockFile.Phase getAVLBlockFilePhase() {
return avlBlockFilePhase;
}
/**
* Increment the number of nodes in this phase of the AVLTree.
*/
void incNrNodes() {
++nrNodes;
}
/**
* Decrement the number of nodes in this phase of the AVLTree.
*/
void decNrNodes() {
assert nrNodes > 0;
--nrNodes;
}
/**
* Debug check that the structure of the tree is as expected.
*/
private void check() {
assert rootId != AVLNode.NULL_NODE || nrNodes == 0 :
"AVLFile is empty but nrNodes == " + nrNodes;
assert rootId == AVLNode.NULL_NODE || nrNodes > 0 :
"AVLFile not empty but nrNodes == " + nrNodes;
}
/**
* This class represents a reference to the enclosing phase.
*/
public final class Token {
/** Token for the matching phase of the underlying block file. */
private ManagedBlockFile.Phase.Token avlBlockFileToken;
/**
* Creates the token, adding a reference count to the block file phase.
*/
Token() {
avlBlockFileToken = avlBlockFilePhase.use();
}
/**
* Get the phase for this token.
*
* @return The encapsulating phase.
*/
public Phase getPhase() {
assert avlBlockFileToken != null : "Invalid Token";
return Phase.this;
}
/**
* Releases this reference to the encapsulating phase.
*/
public void release() {
assert avlBlockFileToken != null : "Invalid Token";
avlBlockFileToken.release();
avlBlockFileToken = null;
}
}
}
}