/* -*- tab-width: 4 -*- * * Electric(tm) VLSI Design System * * File: BTree.java * * Copyright (c) 2009 Sun Microsystems and Static Free Software * * Electric(tm) 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; either version 3 of the License, or * (at your option) any later version. * * Electric(tm) 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 Electric(tm); see the file COPYING. If not, write to * the Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, Mass 02111-1307, USA. */ package com.sun.electric.database.geometry.btree; import java.io.*; import java.util.*; import com.sun.electric.database.geometry.btree.unboxed.*; import com.sun.electric.database.geometry.btree.CachingPageStorage.CachedPage; /** * Internal use only; kind of a hack. This is just a "parser" * for the page format. * * Possible feature: store the buckets of an interior node * internally as a simple balanced tree (a splay tree?). The * System.arraycopy()'s are scaling very poorly as the page size * increases. * * - once we do this, we can probably afford to move to 32Kb * pages, which will give us great performance on nearly any * filesystem or storage device, even RAID storage (which has * huge block sizes). */ abstract class NodeCursor <K extends Serializable & Comparable, V extends Serializable, S extends Serializable> { protected boolean dirty = false; protected CachingPageStorage ps; protected static final int SIZEOF_INT = 4; protected final BTree<K,V,S> bt; protected CachedPage cp = null; protected NodeCursor(BTree<K,V,S> bt) { this.bt = bt; this.ps = bt.ps; } public abstract void initBuf(CachedPage cp, boolean isRightMost); protected abstract void setNumBuckets(int num); public void setBuf(CachedPage cp) { assert !dirty; this.cp = cp; } public CachedPage getCachedPage() { return cp; } public void forgetCachedPage() { cp = null; } public void writeBack() { dirty = false; cp.setDirty(); } /** * This method writes back the first half of the node's contents, * deposits the second half on a new page, and (if key!=null) * writes the least key beneath the right half into key[key_ofs]; * it then returns the number of values appearing anywhere below * the left page. After returning the cursor will be pointing at * the right half. */ public int split(byte[] key, int key_ofs, int splitPoint) { assert isFull(); int endOfBuf = endOfBuf(); int ret = 0; for(int i=0; i<splitPoint; i++) ret += getNumValsBelowBucket(i); // chop off our second half, point our parent at the page-to-be, and write back setNumBuckets(splitPoint); boolean wasRightMost = isRightMost(); setRightMost(false); writeBack(); if (key!=null) getKey(splitPoint, key, key_ofs); // move the second half of our entries to the front of the block, and write back byte[] oldbuf = cp.getBuf(); initBuf(ps.getPage(ps.createPage(), false), wasRightMost); setNumBuckets(getMaxBuckets()-splitPoint); scoot(oldbuf, endOfBuf, splitPoint); writeBack(); return ret; } public boolean isFull() { return getNumBuckets() >= getMaxBuckets(); } public int getPageId() { return cp.getPageId(); } public byte[] getBuf() { return cp.getBuf(); } /** * Each node has a number of buckets, separated by keys; keys * appear between buckets as well as before the first bucket and * after the last. All values in a bucket are greater than or * equal to the key to the left of the bucket and strictly less * than the key to the right. * * Leaf nodes have a real key immediately before each entry -- * this is the entry's actual key -- plus an additional imaginary * key after the last child. * * Interior nodes have an imaginary key before the first node, a * real key between each pair of nodes, and an imaginary key * after the last node. * * Imaginary keys are either infinitely large or infinitely * small, depending on which end of the sequence they appear on. * * This numbering scheme might seem strange, but it really helps * avoid fencepost bugs -- I tried several conventions before * settling on this one. * * Key zero is to the left of bucket zero, and the last key is to * the right of the last child (and has an index one greater than * it). */ public abstract int getNumBuckets(); public abstract int getMaxBuckets(); /** * Compares the key at position keynum to the key represented by * the bytes provided. Same semantics as Comparable.compareTo(). * To simplify other codepaths, if keynum<0, the return value is * always positive and if keynum>=getNumBuckets()+1 the return value is * always negative. */ public abstract int compare(byte[] key, int key_ofs, int keynum); public abstract boolean isLeafNode(); /** i is the position of the leftmost key which is less than or equal to the key we are testing */ public int search(byte[] key, int key_ofs) { int left = -1; int right = getNumBuckets(); while(left+1<right) { assert compare(key, key_ofs, left) >= 0; int i = (left+right)/2; int comp = compare(key, key_ofs, i); if (comp==0) return i; else if (comp>0) left = i; else if (comp<0) right = i; } return left; } protected abstract int endOfBuf(); public abstract void getKey(int keynum, byte[] key, int key_ofs); /** kludge */ protected abstract void scoot(byte[] oldbuf, int endOfBuf, int splitPoint); /** the total number of values stored in bucket or any descendent thereof */ public abstract int getNumValsBelowBucket(int bucket); public abstract void getSummary(int bucket, byte[] buf, int ofs); public boolean isRightMost() { return bt.ui.deserializeInt(getBuf(), 0*SIZEOF_INT)!=0; } protected void setRightMost(boolean r) { bt.ui.serializeInt(r?1:0, getBuf(), 0*SIZEOF_INT); } }