diff --git a/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/internal/pdom/tests/BTreeExpensiveTests.java b/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/internal/pdom/tests/BTreeExpensiveTests.java
new file mode 100644
index 00000000000..afa27b7e1a9
--- /dev/null
+++ b/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/internal/pdom/tests/BTreeExpensiveTests.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (c) 2006 Symbian Software Systems and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Symbian - Initial implementation
+ *******************************************************************************/
+package org.eclipse.cdt.internal.pdom.tests;
+
+/**
+ * Tests which are too expensive to run as part of normal testing, but
+ * should be run after B-tree related development.
+ *
+ * The 'Full Checking' tests perform a full validation of the B-tree
+ * invariants after each B-tree operation, and so are especially
+ * expensive and cpu hungry.
+ */
+/*
+ * JUnit only invokes methods declared in the class itself (not super-classes)
+ */
+public class BTreeExpensiveTests extends BTreeTests {
+
+ public void testBySortedSetMirror() throws Exception {
+ sortedMirrorTest(100);
+ }
+
+ // @Override
+ public void testInsertion() throws Exception {
+ super.testInsertion();
+ }
+
+ /*
+ * N.B. Each of the following tests are quite expensive (i.e. > 10mins each on a 2Ghz machine)
+ */
+
+ public void testBySortedSetMirror1682762087() throws Exception {
+ System.out.println("1682762087 Full Checking");
+ trial(1682762087, true); // exposed bugs in 2a,b
+ }
+
+ public void testBySortedSetMirror322922974() throws Exception {
+ System.out.println("322922974 Full Checking");
+ trial(322922974, true); // exposed bugs in 3b(ii)
+ }
+
+ public void testBySortedSetMirror_588448152() throws Exception {
+ System.out.println("-588448152 Full Checking");
+ trial(-588448152, true); // exposed root-delete-on-merge problems
+ }
+}
diff --git a/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/internal/pdom/tests/BTreeTests.java b/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/internal/pdom/tests/BTreeTests.java
new file mode 100644
index 00000000000..3f7406e413f
--- /dev/null
+++ b/core/org.eclipse.cdt.core.tests/parser/org/eclipse/cdt/internal/pdom/tests/BTreeTests.java
@@ -0,0 +1,226 @@
+/*******************************************************************************
+ * Copyright (c) 2006 Symbian Software Systems and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Symbian - Initial implementation
+ *******************************************************************************/
+package org.eclipse.cdt.internal.pdom.tests;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Random;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import junit.framework.TestCase;
+
+import org.eclipse.cdt.internal.core.pdom.db.BTree;
+import org.eclipse.cdt.internal.core.pdom.db.Database;
+import org.eclipse.cdt.internal.core.pdom.db.IBTreeComparator;
+import org.eclipse.cdt.internal.core.pdom.db.IBTreeVisitor;
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * Test insertion/deletion of records of a mock record type in a B-tree
+ *
+ * @author aferguso
+ *
+ */
+public class BTreeTests extends TestCase {
+ protected File dbFile;
+ protected Database db;
+ protected BTree btree;
+ protected int rootRecord;
+ protected IBTreeComparator comparator;
+
+ protected boolean debugMode = false;
+
+ // setUp is not used since we need to parameterize this method,
+ // and invoke it multiple times per Junit test
+ protected void init(int degree) throws Exception {
+ dbFile = File.createTempFile("pdomtest", "db");
+ db = new Database(dbFile.getAbsolutePath());
+ rootRecord = Database.DATA_AREA;
+ btree = new BTree(db, rootRecord, degree);
+ comparator = new BTMockRecordComparator();
+ }
+
+ // tearDown is not used for the same reason as above
+ protected void finish() throws Exception {
+ db.close();
+ dbFile.deleteOnExit();
+ }
+
+
+ public void testBySortedSetMirrorLite() throws Exception {
+ sortedMirrorTest(8);
+ }
+
+ /**
+ * Test random (but reproducible via known seed) sequences of insertions/deletions
+ * and use TreeSet as a reference implementation to check behaviour against.
+ * @throws Exception
+ */
+ protected void sortedMirrorTest(int noTrials) throws Exception {
+ Random seeder = new Random(90210);
+
+ for(int i=0; i= iParent; --i) {
+ for (int i = MAX_RECORDS - 2; i >= iParent; --i) {
int r = getRecord(pChunk, parent, i);
if (r != 0) {
putRecord(pChunk, parent, i + 1, r);
@@ -118,9 +145,9 @@ public class BTree {
}
putRecord(pChunk, parent, iParent, median);
putChild(pChunk, parent, iParent + 1, newnode);
-
+
putRecord(chunk, node, MEDIAN_RECORD, 0);
-
+
// set the node to the correct one to follow
if (comparator.compare(record, median) > 0) {
node = newnode;
@@ -131,7 +158,7 @@ public class BTree {
// search to find the insert point
int i;
- for (i = 0; i < NUM_RECORDS; ++i) {
+ for (i = 0; i < MAX_RECORDS; ++i) {
int record1 = getRecord(chunk, node, i);
if (record1 == 0) {
// past the end
@@ -154,7 +181,7 @@ public class BTree {
} else {
// were at the leaf, add us in.
// first copy everything after over one
- for (int j = NUM_RECORDS - 2; j >= i; --j) {
+ for (int j = MAX_RECORDS - 2; j >= i; --j) {
int r = getRecord(chunk, node, j);
if (r != 0)
putRecord(chunk, node, j + 1, r);
@@ -171,18 +198,316 @@ public class BTree {
// put the record in the first slot of the node
putRecord(db.getChunk(root), root, 0, record);
}
-
+
private int allocateNode() throws CoreException {
- return db.malloc((2 * NUM_RECORDS + 1) * Database.INT_SIZE);
+ return db.malloc((2 * MAX_RECORDS + 1) * Database.INT_SIZE);
}
-
+
/**
- * Deletes the record from the b-tree.
- *
- * @param offset of the record
+ * Deletes the specified record from the B-tree.
+ *
+ * If the specified record is not present then this routine has no effect.
+ *
+ * Specifying a record r for which there is another record q existing in the B-tree
+ * where cmp.compare(r,q)==0 && r!=q will also have no effect
+ *
+ * N.B. The record is not deleted itself - its storage is not deallocated.
+ * The reference to the record in the btree is deleted.
+ *
+ * @param record the record to delete
+ * @param cmp the comparator for locating the record
+ * @throws CoreException
*/
- public void delete(int record) {
- // TODO some day
+ public void delete(int record, IBTreeComparator cmp) throws CoreException {
+ try {
+ deleteImp(record, getRoot(), DELMODE_NORMAL, cmp);
+ } catch(BTreeKeyNotFoundException e) {
+ // contract of this method is to NO-OP upon this event
+ }
+ }
+
+ private class BTreeKeyNotFoundException extends Exception {
+ private static final long serialVersionUID = 9065438266175091670L;
+ public BTreeKeyNotFoundException(String msg) {
+ super(msg);
+ }
+ }
+
+ /**
+ * Used in implementation of delete routines
+ */
+ private class BTNode {
+ final int node;
+ final int keyCount;
+ final Chunk chunk;
+
+ BTNode(int node) throws CoreException {
+ this.node = node;
+ this.chunk = db.getChunk(node);
+ int i=0;
+ while(i
+ * There is no distinction between keys and records.
+ *
+ * This implements a single downward pass (with minor exceptions) deletion
+ *
+ * @param key the address of the record to delete
+ * @param nodeRecord a node that (directly or indirectly) contains the specified key/record
+ * @param mode one of DELMODE_NORMAL, DELMODE_DELETE_MINIMUM, DELMODE_DELETE_MAXIMUM
+ * where DELMODE_NORMAL: locates the specified key/record using the comparator provided
+ * DELMODE_DELETE_MINIMUM: locates and deletes the minimum element in the subtree rooted at nodeRecord
+ * DELMODE_DELETE_MAXIMUM: locates and deletes the maximum element in the subtree rooted at nodeRecord
+ * @param cmp the comparator used to locate the record in the tree
+ * @return the address of the record removed from the B-tree
+ * @throws CoreException
+ */
+ private int deleteImp(int key, int nodeRecord, int mode, IBTreeComparator cmp)
+ throws CoreException, BTreeKeyNotFoundException {
+ BTNode node = new BTNode(nodeRecord);
+
+ // Determine index of key in current node, or -1 if its not in this node
+ int keyIndexInNode = -1;
+ if(mode==DELMODE_NORMAL)
+ for(int i=0; i MIN_RECORDS) {
+ /* Case 2a: Delete key by overwriting it with its successor (which occurs in a leaf node) */
+ int subst = deleteImp(-1, succ.node, DELMODE_DELETE_MINIMUM, cmp);
+ putRecord(node.chunk, node.node, keyIndexInNode, subst);
+ return key;
+ }
+
+ BTNode pred = node.getChild(keyIndexInNode);
+ if(pred!=null && pred.keyCount > MIN_RECORDS) {
+ /* Case 2b: Delete key by overwriting it with its predecessor (which occurs in a leaf node) */
+ int subst = deleteImp(-1, pred.node, DELMODE_DELETE_MAXIMUM, cmp);
+ putRecord(node.chunk, node.node, keyIndexInNode, subst);
+ return key;
+ }
+
+ /* Case 2c: Merge successor and predecessor */
+ // assert(pred!=null && succ!=null);
+ mergeNodes(succ, node, keyIndexInNode, pred);
+ return deleteImp(key, pred.node, mode, cmp);
+ } else {
+ /* Case 3: non-leaf node which does not itself contain the key */
+
+ /* Determine root of subtree that should contain the key */
+ int subtreeIndex;
+ switch(mode) {
+ case DELMODE_NORMAL:
+ subtreeIndex = node.keyCount;
+ for(int i=0; i0) {
+ subtreeIndex = i;
+ break;
+ }
+ break;
+ case DELMODE_DELETE_MINIMUM: subtreeIndex = 0; break;
+ case DELMODE_DELETE_MAXIMUM: subtreeIndex = node.keyCount; break;
+ default: throw new CoreException(new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, IStatus.OK, Messages.getString("BTree.UnknownMode"), null)); //$NON-NLS-1$
+ }
+
+ BTNode child = node.getChild(subtreeIndex);
+ if(child==null) {
+ throw new CoreException(new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, IStatus.OK, Messages.getString("BTree.IntegrityError"), null)); //$NON-NLS-1$
+ }
+
+ if(child.keyCount > MIN_RECORDS) {
+ return deleteImp(key, child.node, mode, cmp);
+ } else {
+ BTNode sibR = node.getChild(subtreeIndex+1);
+ if(sibR!=null && sibR.keyCount > MIN_RECORDS) {
+ /* Case 3a (i): child will underflow upon deletion, take a key from rightSibling */
+ int rightKey = getRecord(node.chunk, node.node, subtreeIndex);
+ int leftmostRightSiblingKey = getRecord(sibR.chunk, sibR.node, 0);
+ append(child, rightKey, getChild(sibR.chunk, sibR.node, 0));
+ nodeContentDelete(sibR, 0, 1);
+ putRecord(node.chunk, node.node, subtreeIndex, leftmostRightSiblingKey);
+ return deleteImp(key, child.node, mode, cmp);
+ }
+
+ BTNode sibL = node.getChild(subtreeIndex-1);
+ if(sibL!=null && sibL.keyCount > MIN_RECORDS) {
+ /* Case 3a (ii): child will underflow upon deletion, take a key from leftSibling */
+ int leftKey = getRecord(node.chunk, node.node, subtreeIndex-1);
+ prepend(child, leftKey, getChild(sibL.chunk, sibL.node, sibL.keyCount));
+ int rightmostLeftSiblingKey = getRecord(sibL.chunk, sibL.node, sibL.keyCount-1);
+ putRecord(sibL.chunk, sibL.node, sibL.keyCount-1, 0);
+ putChild(sibL.chunk, sibL.node, sibL.keyCount, 0);
+ putRecord(node.chunk, node.node, subtreeIndex-1, rightmostLeftSiblingKey);
+ return deleteImp(key, child.node, mode, cmp);
+ }
+
+ /* Case 3b (i,ii): leftSibling, child, rightSibling all have minimum number of keys */
+
+ if(sibL!=null) { // merge child into leftSibling
+ mergeNodes(child, node, subtreeIndex-1, sibL);
+ return deleteImp(key, sibL.node, mode, cmp);
+ }
+
+ if(sibR!=null) { // merge rightSibling into child
+ mergeNodes(sibR, node, subtreeIndex, child);
+ return deleteImp(key, child.node, mode, cmp);
+ }
+
+ throw new BTreeKeyNotFoundException(
+ MessageFormat.format(Messages.getString("BTree.DeletionOnAbsentKey"), //$NON-NLS-1$
+ new Object[]{new Integer(key), new Integer(mode)}));
+ }
+ }
+ }
+ }
+
+ /**
+ * Merge node 'src' onto the right side of node 'dst' using node
+ * 'keyProvider' as the source of the median key. Bounds checking is not
+ * performed.
+ * @param src the key to merge into dst
+ * @param mid the node that provides the median key for the new node
+ * @param kIndex the index of the key in the node mid which is to become the new node's median key
+ * @param dst the node which is the basis and result of the merge
+ */
+ public void mergeNodes(BTNode src, BTNode keyProvider, int kIndex, BTNode dst)
+ throws CoreException {
+ nodeContentCopy(src, 0, dst, dst.keyCount+1, src.keyCount+1);
+ int midKey = getRecord(keyProvider.chunk, keyProvider.node, kIndex);
+ putRecord(dst.chunk, dst.node, dst.keyCount, midKey);
+ int keySucc = kIndex+1 == MAX_RECORDS ? 0 : getRecord(keyProvider.chunk, keyProvider.node, kIndex+1);
+ db.free(getChild(keyProvider.chunk, keyProvider.node, kIndex+1));
+ nodeContentDelete(keyProvider, kIndex+1, 1);
+ putRecord(keyProvider.chunk, keyProvider.node, kIndex, keySucc);
+ if(kIndex == 0 && keySucc == 0) {
+ /*
+ * The root node is excused from the property that a node must have a least MIN keys
+ * This means we must special case it at the point when its had all of its keys deleted
+ * entirely during merge operations (which push one of its keys down as a pivot)
+ */
+ int rootNode = getRoot();
+ if(rootNode == keyProvider.node) {
+ db.putInt(rootPointer, dst.node);
+ db.free(rootNode);
+ }
+ }
+ }
+
+ /**
+ * Insert the key and (its predecessor) child at the left side of the specified node. Bounds checking
+ * is not performed.
+ * @param node the node to prepend to
+ * @param key the new leftmost (least) key
+ * @param child the new leftmost (least) subtree root
+ */
+ private void prepend(BTNode node, int key, int child) {
+ nodeContentCopy(node, 0, node, 1, node.keyCount+1);
+ putRecord(node.chunk, node.node, 0, key);
+ putChild(node.chunk, node.node, 0, child);
+ }
+
+ /**
+ * Insert the key and (its successor) child at the right side of the specified node. Bounds checking
+ * is not performed.
+ * @param node
+ * @param key
+ * @param child
+ */
+ private void append(BTNode node, int key, int child) {
+ putRecord(node.chunk, node.node, node.keyCount, key);
+ putChild(node.chunk, node.node, node.keyCount + 1, child);
+ }
+
+ /**
+ * Overwrite a section of the specified node (dst) with the specified section of the source node. Bounds checking
+ * is not performed. To allow just copying of the final child (which has no corresponding key) the routine
+ * behaves as though there were a corresponding key existing with value zero.
+ * Copying from a node to itself is permitted.
+ * @param src the node to read from
+ * @param srcPos the initial index to read from (inclusive)
+ * @param dst the node to write to
+ * @param dstPos the intial index to write to (inclusive)
+ * @param length the number of (key,(predecessor)child) nodes to write
+ */
+ private void nodeContentCopy(BTNode src, int srcPos, BTNode dst, int dstPos, int length) {
+ for(int i=length-1; i>=0; i--) { // this order is important when src==dst!
+ int srcIndex = srcPos + i;
+ int dstIndex = dstPos + i;
+
+ if(srcIndex
+ * Content is deleted and remaining content is moved leftward the appropriate amount.
+ * @param node the node to delete content from
+ * @param i the start index (inclusive) to delete from
+ * @param length the length of the sequence to delete
+ */
+ private void nodeContentDelete(BTNode node, int i, int length) {
+ for(int index=i; index<=MAX_RECORDS; index++) {
+ int newKey = (index+length) < node.keyCount ? getRecord(node.chunk, node.node, index+length) : 0;
+ int newChild = (index+length) < node.keyCount+1 ? getChild(node.chunk, node.node, index+length) : 0;
+ if(index 0) {
- // start point is to the left
- if (!accept(getChild(chunk, node, i), visitor, false))
+ int child = getChild(chunk, node, 0);
+ if (child != 0)
+ if (!accept(child, visitor, true))
return false;
+ }
+
+ int i;
+ for (i = 0; i < MAX_RECORDS; ++i) {
+ int record = getRecord(chunk, node, i);
+ if (record == 0)
+ break;
+
+ if (found) {
if (!visitor.visit(record))
return false;
if (!accept(getChild(chunk, node, i + 1), visitor, true))
return false;
- found = true;
- } else if (compare == 0) {
- if (!visitor.visit(record))
- return false;
- if (!accept(getChild(chunk, node, i + 1), visitor, true))
+ } else {
+ int compare = visitor.compare(record);
+ if (compare > 0) {
+ // start point is to the left
+ if (!accept(getChild(chunk, node, i), visitor, false))
return false;
- found = true;
+ if (!visitor.visit(record))
+ return false;
+ if (!accept(getChild(chunk, node, i + 1), visitor, true))
+ return false;
+ found = true;
+ } else if (compare == 0) {
+ if (!visitor.visit(record))
+ return false;
+ if (!accept(getChild(chunk, node, i + 1), visitor, true))
+ return false;
+ found = true;
+ }
+ }
+ }
+
+ if (!found)
+ return accept(getChild(chunk, node, i), visitor, false);
+
+ return true;
+ } finally {
+ if(visitor instanceof IBTreeVisitor2) {
+ ((IBTreeVisitor2)visitor).postNode(node);
+ }
+ }
+ }
+
+ /*
+ * TODO: It would be good to move these into IBTreeVisitor and eliminate
+ * IBTreeVisitor2 if this is acceptable.
+ */
+ private interface IBTreeVisitor2 extends IBTreeVisitor {
+ void preNode(int node) throws CoreException;
+ void postNode(int node) throws CoreException;
+ }
+
+ /**
+ * Debugging method for checking B-tree invariants
+ * @return the empty String if B-tree invariants hold, otherwise
+ * a human readable report
+ * @throws CoreException
+ */
+ public String getInvariantsErrorReport() throws CoreException {
+ InvariantsChecker checker = new InvariantsChecker();
+ accept(checker);
+ return checker.isValid() ? "" : checker.getMsg(); //$NON-NLS-1$
+ }
+
+ /**
+ * A B-tree visitor for checking some B-tree invariants.
+ * Note ordering invariants are not checked here.
+ */
+ private class InvariantsChecker implements IBTreeVisitor2 {
+ boolean valid = true;
+ String msg = ""; //$NON-NLS-1$
+ Integer leafDepth;
+ int depth;
+
+ public String getMsg() { return msg; }
+ public boolean isValid() { return valid; }
+ public void postNode(int node) throws CoreException { depth--; }
+ public int compare(int record) throws CoreException { return 1; }
+ public boolean visit(int record) throws CoreException { return true; }
+
+ public void preNode(int node) throws CoreException {
+ depth++;
+
+ // collect information for checking
+ int keyCount = 0;
+ int indexFirstBlankKey = MAX_RECORDS;
+ int indexLastNonBlankKey = 0;
+ for(int i=0; i MAX_RECORDS) {
+ valid = false;
+ msg += MessageFormat.format(Messages.getString("BTree.IntegrityErrorC"), new Object[]{new Integer(node)}); //$NON-NLS-1$
+ }
+
+ // Check: All leaf nodes are at the same depth
+ if(childCount==0) {
+ if(leafDepth==null) {
+ leafDepth = new Integer(depth);
+ }
+ if(depth!=leafDepth.intValue()) {
+ valid = false;
+ msg += Messages.getString("BTree.IntegrityErrorD"); //$NON-NLS-1$
}
}
}
-
- if (!found)
- return accept(getChild(chunk, node, i), visitor, false);
-
- return true;
}
-
}
diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/Database.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/Database.java
index 53df4759c5f..730ef2c7bac 100644
--- a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/Database.java
+++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/Database.java
@@ -7,6 +7,7 @@
*
* Contributors:
* QNX - Initial API and implementation
+ * Symbian - Add some non-javadoc implementation notes
*******************************************************************************/
package org.eclipse.cdt.internal.core.pdom.db;
@@ -19,8 +20,36 @@ import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
/**
- * @author Doug Schaefer
+ * Database encapsulates access to a flat binary format file with a memory-manager-like API for
+ * obtaining and releasing areas of storage (memory).
*
+ * @author Doug Schaefer
+ */
+/*
+ * The file encapsulated is divided into Chunks of size CHUNK_SIZE, and a table of contents
+ * mapping chunk index to chunk address is maintained. Chunk structure exists only conceptually -
+ * its not a structure that appears in the file.
+ *
+ * ===== The first chunk is used by Database itself for house-keeping purposes and has structure
+ *
+ * offset content
+ * _____________________________
+ * 0 | version number
+ * INT_SIZE | pointer to head of linked list of blocks of size MIN_SIZE
+ * .. | ...
+ * INT_SIZE * m (1) | pointer to head of linked list of blocks of size MIN_SIZE * m
+ * DATA_AREA | undefined (PDOM stores its own house-keeping data in this area)
+ *
+ * (1) where m <= (CHUNK_SIZE / MIN_SIZE)
+ *
+ * ===== block structure
+ *
+ * offset content
+ * _____________________________
+ * 0 | size of block (negative indicates in use, positive unused)
+ * PREV_OFFSET | pointer to prev block (of same size)
+ * NEXT_OFFSET | pointer to next block (of same size)
+ *
*/
public class Database {
@@ -214,7 +243,7 @@ public class Database {
int blocksize = - chunk.getInt(block);
if (blocksize < 0)
// already freed
- throw new CoreException(new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, 0, "Already Freed", new Exception()));
+ throw new CoreException(new Status(IStatus.ERROR, CCorePlugin.PLUGIN_ID, 0, "Already Freed", new Exception())); //$NON-NLS-1$
addBlock(chunk, blocksize, block);
freed += blocksize;
}
@@ -276,11 +305,11 @@ public class Database {
}
public void reportFreeBlocks() throws CoreException {
- System.out.println("Allocated size: " + toc.length * CHUNK_SIZE);
- System.out.println("malloc'ed: " + malloced);
- System.out.println("free'd: " + freed);
- System.out.println("wasted: " + (toc.length * CHUNK_SIZE - (malloced - freed)));
- System.out.println("Free blocks");
+ System.out.println("Allocated size: " + toc.length * CHUNK_SIZE); //$NON-NLS-1$
+ System.out.println("malloc'ed: " + malloced); //$NON-NLS-1$
+ System.out.println("free'd: " + freed); //$NON-NLS-1$
+ System.out.println("wasted: " + (toc.length * CHUNK_SIZE - (malloced - freed))); //$NON-NLS-1$
+ System.out.println("Free blocks"); //$NON-NLS-1$
for (int bs = MIN_SIZE; bs <= CHUNK_SIZE; bs += MIN_SIZE) {
int count = 0;
int block = getFirstBlock(bs);
@@ -289,7 +318,17 @@ public class Database {
block = getInt(block + NEXT_OFFSET);
}
if (count != 0)
- System.out.println("Block size: " + bs + "=" + count);
+ System.out.println("Block size: " + bs + "=" + count); //$NON-NLS-1$ //$NON-NLS-2$
}
}
+
+ /**
+ * Closes the database, releasing the file lock. This is public for testing purposes only.
+ *
+ * The behaviour of any further calls to the Database is undefined
+ * @throws IOException
+ */
+ public void close() throws IOException {
+ file.close();
+ }
}
diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/Messages.java b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/Messages.java
new file mode 100644
index 00000000000..2399a46381a
--- /dev/null
+++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/Messages.java
@@ -0,0 +1,22 @@
+package org.eclipse.cdt.internal.core.pdom.db;
+
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+
+public class Messages {
+ private static final String BUNDLE_NAME = "org.eclipse.cdt.internal.core.pdom.db.messages"; //$NON-NLS-1$
+
+ private static final ResourceBundle RESOURCE_BUNDLE = ResourceBundle
+ .getBundle(BUNDLE_NAME);
+
+ private Messages() {
+ }
+
+ public static String getString(String key) {
+ try {
+ return RESOURCE_BUNDLE.getString(key);
+ } catch (MissingResourceException e) {
+ return '!' + key + '!';
+ }
+ }
+}
diff --git a/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/messages.properties b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/messages.properties
new file mode 100644
index 00000000000..fe42cc21dee
--- /dev/null
+++ b/core/org.eclipse.cdt.core/parser/org/eclipse/cdt/internal/core/pdom/db/messages.properties
@@ -0,0 +1,8 @@
+BTree.IllegalDegree=BTree degree must be >=2
+BTree.DeletionOnAbsentKey=Deletion of key not in btree: {0} mode={1}
+BTree.UnknownMode=BTree unknown deletion mode error
+BTree.IntegrityError=BTree integrity error
+BTree.IntegrityErrorA=[{0} blanks inconsistent b={1} nb={2}]
+BTree.IntegrityErrorB=[{0} wrong number of children w.r.t. key count]
+BTree.IntegrityErrorC=[{0} key count out of range]
+BTree.IntegrityErrorD=Leaf nodes at differing depths