/*
Copyright (C) SYSTAP, LLC DBA Blazegraph 2006-2016. All rights reserved.
Contact:
SYSTAP, LLC DBA Blazegraph
2501 Calvert ST NW #106
Washington, DC 20008
licenses@blazegraph.com
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; version 2 of the License.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
/*
* Created on Aug 25, 2009
*/
package com.bigdata.btree;
import com.bigdata.btree.data.INodeData;
import com.bigdata.btree.raba.IRaba;
import com.bigdata.btree.raba.MutableKeyBuffer;
import com.bigdata.io.AbstractFixedByteArrayBuffer;
/**
* Implementation maintains Java objects corresponding to the persistent data
* and defines methods for a variety of mutations on the {@link INodeData}
* record which operate by direct manipulation of the Java objects.
* <p>
* Note: package private fields are used so that they may be directly accessed
* by the {@link Node} class.
*
* @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a>
* @version $Id$
*/
public class MutableNodeData implements INodeData {
/**
* A representation of each key in the node. Each key is a variable length
* unsigned byte[].
* <p>
* The #of keys depends on whether this is a {@link Node} or a {@link Leaf}.
* A leaf has one key per value - that is, the maximum #of keys for a leaf
* is specified by the branching factor. In contrast a node has m-1 keys
* where m is the maximum #of children (aka the branching factor).
* <p>
* For both a {@link Node} and a {@link Leaf}, this array is dimensioned to
* accept one more key than the maximum capacity so that the key that causes
* overflow and forces the split may be inserted. This greatly simplifies
* the logic for computing the split point and performing the split.
* Therefore you always allocate this object with a capacity <code>m</code>
* keys for a {@link Node} and <code>m+1</code> keys for a {@link Leaf}.
*
* @see Node#findChild(int searchKeyOffset, byte[] searchKey)
* @see IRaba#search(byte[])
*/
final MutableKeyBuffer keys;
/**
* <p>
* The persistent address of each child node (may be nodes or leaves). The
* capacity of this array is m, where m is the {@link #branchingFactor}.
* Valid indices are in [0:nkeys+1] since nchildren := nkeys+1 for a
* {@link Node}. The key is {@link #NULL} until the child has been
* persisted. The protocol for persisting child nodes requires that we use a
* pre-order traversal (the general case is a directed graph) so that we can
* update the keys on the parent before the parent itself is persisted.
* </p>
* <p>
* Note: It is an error if there is an attempt to serialize a node having a
* null entry in this array and a non-null entry in the {@link #keys} array.
* </p>
* <p>
* This array is dimensioned to one more than the maximum capacity so that
* the child reference corresponding to the key that causes overflow and
* forces the split may be inserted. This greatly simplifies the logic for
* computing the split point and performing the split.
* </p>
*/
final long[] childAddr;
/**
* The #of entries spanned by this node. This value should always be equal
* to the sum of the defined values in {@link #childEntryCounts}.
* <p>
* When a node is split, the value is updated by subtracting off the counts
* for the children that are being moved to the new sibling.
* <p>
* When a node is joined, the value is updated by adding in the counts for
* the children that are being moved to the new sibling.
* <p>
* When a key is redistributed from a node to a sibling, the value is
* updated by subtracting off the count for the child from the source
* sibling and adding it in to this node.
* <p>
* This field is initialized by the various {@link Node} constructors.
*/
long nentries;
/**
* The #of entries spanned by each direct child of this node.
* <p>
* The appropriate element in this array is incremented on all ancestor
* nodes by {@link Leaf#insert(Object, Object)} and decremented on all
* ancestors nodes by {@link Leaf#remove(Object)}. Since the ancestors are
* guaranteed to be mutable as preconditions for those operations we are
* able to traverse the {@link AbstractNode#parent} reference in a straight
* forward manner.
*/
final long[] childEntryCounts;
/**
* <code>true</code> iff the B+Tree is maintaining per tuple revision
* timestamps.
*/
final boolean hasVersionTimestamps;
/**
* The minimum tuple revision timestamp for any leaf spanned by this node
* IFF the B+Tree is maintaining tuple revision timestamps.
*/
long minimumVersionTimestamp;
/**
* The maximum tuple revision timestamp for any leaf spanned by this node
* IFF the B+Tree is maintaining tuple revision timestamps.
*/
long maximumVersionTimestamp;
/**
* Create an empty mutable data record.
*
* @param branchingFactor
* The branching factor for the owning B+Tree. This is used to
* initialize the various arrays to the correct capacity.
* @param hasVersionTimestamps
* <code>true</code> iff the B+Tree is maintaining per tuple
* version timestamps.
*/
public MutableNodeData(final int branchingFactor,
final boolean hasVersionTimestamps) {
nentries = 0;
keys = new MutableKeyBuffer(branchingFactor);
childAddr = new long[branchingFactor + 1];
childEntryCounts = new long[branchingFactor + 1];
this.hasVersionTimestamps = hasVersionTimestamps;
minimumVersionTimestamp = maximumVersionTimestamp = 0L;
}
/**
* Makes a mutable copy of the source data record.
*
* @param branchingFactor
* The branching factor for the owning B+Tree. This is used to
* initialize the various arrays to the correct capacity.
* @param src
* The source data record.
*/
public MutableNodeData(final int branchingFactor, final INodeData src) {
if (src == null)
throw new IllegalArgumentException();
keys = new MutableKeyBuffer(branchingFactor, src.getKeys());
nentries = src.getSpannedTupleCount();
if (nentries <= 0)
throw new RuntimeException();
childAddr = new long[branchingFactor + 1];
childEntryCounts = new long[branchingFactor + 1];
final int nkeys = keys.size();
long sum = 0;
for (int i = 0; i <= nkeys; i++) {
childAddr[i] = src.getChildAddr(i);
final long tmp = childEntryCounts[i] = src.getChildEntryCount(i);
if (tmp <= 0)
throw new RuntimeException();
sum += tmp;
}
this.hasVersionTimestamps = src.hasVersionTimestamps();
if(src.hasVersionTimestamps()) {
minimumVersionTimestamp = src.getMinimumVersionTimestamp();
maximumVersionTimestamp = src.getMaximumVersionTimestamp();
}
if(sum != nentries)
throw new RuntimeException();
}
/**
* Ctor based on just the "data" -- used by unit tests.
*
* @param nentries
* @param keys
* @param childAddr
* @param childEntryCounts
*/
public MutableNodeData(final long nentries, final IRaba keys,
final long[] childAddr, final long[] childEntryCounts,
final boolean hasVersionTimestamps,
final long minimumVersionTimestamp,
final long maximumVersionTimestamp) {
assert keys != null;
assert childAddr != null;
assert childEntryCounts != null;
assert keys.capacity() + 1 == childAddr.length;
assert childAddr.length == childEntryCounts.length;
this.nentries = nentries;
this.keys = (MutableKeyBuffer) keys;
this.childAddr = childAddr;
this.childEntryCounts = childEntryCounts;
this.hasVersionTimestamps = hasVersionTimestamps;
this.minimumVersionTimestamp = minimumVersionTimestamp;
this.maximumVersionTimestamp = maximumVersionTimestamp;
}
/**
* No - this is a mutable data record.
*/
final public boolean isReadOnly() {
return false;
}
/**
* No.
*/
final public boolean isCoded() {
return false;
}
final public AbstractFixedByteArrayBuffer data() {
throw new UnsupportedOperationException();
}
public final long getSpannedTupleCount() {
return nentries;
}
/**
* Range check a child index.
*
* @param index
* The index of a child in [0:nkeys+1].
* @return <code>true</code>
*
* @throws IndexOutOfBoundsException
* if the index is not in the legal range.
*/
final protected boolean rangeCheckChildIndex(final int index) {
if (index < 0 || index > getKeys().size() + 1)
throw new IndexOutOfBoundsException();
return true;
}
public final long getChildAddr(final int index) {
assert rangeCheckChildIndex(index);
return childAddr[index];
}
final public long getChildEntryCount(final int index) {
assert rangeCheckChildIndex(index);
return childEntryCounts[index];
}
final public int getChildCount() {
return getKeys().size() + 1;
}
final public int getKeyCount() {
return keys.size();
}
final public IRaba getKeys() {
return keys;
}
final public boolean isLeaf() {
return false;
}
final public boolean hasVersionTimestamps() {
return hasVersionTimestamps;
}
final public long getMaximumVersionTimestamp() {
if(!hasVersionTimestamps)
throw new UnsupportedOperationException();
return maximumVersionTimestamp;
}
final public long getMinimumVersionTimestamp() {
if(!hasVersionTimestamps)
throw new UnsupportedOperationException();
return minimumVersionTimestamp;
}
}