/* This file is part of the db4o object database http://www.db4o.com Copyright (C) 2004 - 2011 Versant Corporation http://www.versant.com db4o is free software; you can redistribute it and/or modify it under the terms of version 3 of the GNU General Public License as published by the Free Software Foundation. db4o 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, see http://www.gnu.org/licenses/. */ package com.db4o.internal.btree; import com.db4o.*; import com.db4o.defragment.*; import com.db4o.foundation.*; import com.db4o.internal.*; import com.db4o.internal.caching.*; import com.db4o.internal.ids.*; import com.db4o.internal.slots.*; import com.db4o.marshall.*; /** * @exclude */ public class BTree extends LocalPersistentBase implements TransactionParticipant, BTreeStructureListener { private final BTreeConfiguration _config; private static final byte BTREE_VERSION = (byte)1; private static final int DEFRAGMENT_INCREMENT_OFFSET = 1 // version byte + Const4.INT_LENGTH * 2; // size, node size private final Indexable4 _keyHandler; private BTreeNode _root; /** * All instantiated nodes are held in this tree. */ private TreeIntObject _nodes; private int _size; private Visitor4 _removeListener; private final TransactionLocal<Integer> _sizeDeltaInTransaction = new TransactionLocal<Integer>() { @Override public Integer initialValueFor(Transaction transaction) { return 0; } }; protected Queue4 _processing; private int _nodeSize; int _halfNodeSize; private BTreeStructureListener _structureListener; private final Cache4<Integer, BTreeNodeCacheEntry> _nodeCache; private TreeIntObject _evictedFromCache; private boolean _disposed; public BTree(Transaction trans, BTreeConfiguration config, int id, Indexable4 keyHandler, final int treeNodeSize) { super(config._idSystem); _config = config; if (null == keyHandler) { throw new ArgumentNullException(); } _nodeSize = treeNodeSize; _nodeCache = CacheFactory.newLRUIntCache(config._cacheSize); _halfNodeSize = _nodeSize / 2; _nodeSize = _halfNodeSize * 2; _keyHandler = keyHandler; setID(id); if(isNew()){ setStateDirty(); _root = new BTreeNode(this, 0, true, 0, 0, 0); _root.write(trans.systemTransaction()); addNode(_root); write(trans.systemTransaction()); }else{ setStateDeactivated(); } } public BTree(Transaction trans, BTreeConfiguration config, Indexable4 keyHandler) { this(trans, config, 0, keyHandler); } public BTree(Transaction trans, BTreeConfiguration config, int id, Indexable4 keyHandler){ this(trans, config, id, keyHandler, config(trans).bTreeNodeSize()); } public BTree(Transaction trans, int id, Indexable4 keyHandler) { this(trans, BTreeConfiguration.DEFAULT, id, keyHandler); } public BTree(Transaction trans, int id, Indexable4 keyHandler, int nodeSize) { this(trans, BTreeConfiguration.DEFAULT, id, keyHandler, nodeSize); } public BTreeNode root() { return _root; } public int nodeSize() { return _nodeSize; } public void add(Transaction trans, Object key){ keyCantBeNull(key); PreparedComparison preparedComparison = _keyHandler.prepareComparison(trans.context(), key); add(trans, preparedComparison, key); } public void add(Transaction trans, PreparedComparison preparedComparison, Object key){ ensureActive(trans); enlist(trans); BTreeNode rootOrSplit = _root.add(trans, preparedComparison, key); if(rootOrSplit != null && rootOrSplit != _root){ ensureDirty(trans); _root = new BTreeNode(trans, _root, rootOrSplit); _root.write(trans.systemTransaction()); addNode(_root); } convertCacheEvictedNodesToReadMode(); } public Object remove(Transaction trans, Object key){ BTreePointer bTreePointer = searchPointer(trans, key); if(bTreePointer == null){ return null; } Object result = bTreePointer.key(); enlist(trans); PreparedComparison preparedComparison = keyHandler().prepareComparison(trans.context(), key); BTreeNode node = bTreePointer.node(); node.remove(trans, preparedComparison, key, bTreePointer.index()); convertCacheEvictedNodesToReadMode(); return result; } public BTreeRange searchRange(Transaction trans, Object key) { keyCantBeNull(key); return searchRange(trans, keyHandler().prepareComparison(trans.context(), key)); } public BTreePointer searchPointer(Transaction trans, Object key) { ensureActive(trans); keyCantBeNull(key); PreparedComparison preparedComparison = keyHandler().prepareComparison(trans.context(), key); BTreeNodeSearchResult start = searchLeaf(trans, preparedComparison, SearchTarget.LOWEST); BTreePointer bTreePointer = start.firstValidPointer(); if(bTreePointer == null){ convertCacheEvictedNodesToReadMode(); return null; } Object found = bTreePointer.key(); convertCacheEvictedNodesToReadMode(); if(preparedComparison.compareTo(found) == 0){ return bTreePointer; } return null; } public Object search(Transaction trans, Object key) { BTreePointer bTreePointer = searchPointer(trans, key); if(bTreePointer != null){ return bTreePointer.key(); } return null; } private BTreeRange searchRange(Transaction trans, PreparedComparison preparedComparison) { ensureActive(trans); // TODO: Optimize the following. // Part of the search operates against the same nodes. // As long as the bounds are on one node, the search // should walk the nodes in one go. BTreeNodeSearchResult start = searchLeaf(trans, preparedComparison, SearchTarget.LOWEST); BTreeNodeSearchResult end = searchLeaf(trans, preparedComparison, SearchTarget.HIGHEST); BTreeRange range = start.createIncludingRange(end); convertCacheEvictedNodesToReadMode(); return range; } private void keyCantBeNull(Object key) { if (null == key) { throw new ArgumentNullException(); } } public Indexable4 keyHandler() { return _keyHandler; } public BTreeNodeSearchResult searchLeafByObject(Transaction trans, Object key, SearchTarget target) { return searchLeaf(trans, _keyHandler.prepareComparison(trans.context(), key), target); } public BTreeNodeSearchResult searchLeaf(Transaction trans, PreparedComparison preparedComparison, SearchTarget target) { ensureActive(trans); BTreeNodeSearchResult result = _root.searchLeaf(trans, preparedComparison, target); convertCacheEvictedNodesToReadMode(); return result; } public void commit(final Transaction transaction){ if(_disposed){ return; } updateSize(transaction); commitNodes(transaction); finishTransaction(transaction); convertCacheEvictedNodesToReadMode(); } private void updateSize(final Transaction transaction) { final ByRef<Integer> sizeInTransaction = sizeIn(transaction); int sizeModification = sizeInTransaction.value; if(sizeModification == 0){ return; } ensureDirty(transaction); _size += sizeModification; sizeInTransaction.value = 0; } private ByRef<Integer> sizeIn(final Transaction trans) { return trans.get(_sizeDeltaInTransaction); } private void commitNodes(final Transaction trans){ processEachNode(new Procedure4<BTreeNode>() { public void apply(BTreeNode node) { node.commit(trans); }}); } private void processEachNode(Procedure4<BTreeNode> action) { if(_nodes == null) return; processAllNodes(); while(_processing.hasNext()){ action.apply((BTreeNode)_processing.next()); } _processing = null; } public void rollback(final Transaction trans){ rollbackNodes(trans); finishTransaction(trans); convertCacheEvictedNodesToReadMode(); } private void finishTransaction(final Transaction trans) { final Transaction systemTransaction = trans.systemTransaction(); writeAllNodes(systemTransaction); write(systemTransaction); purge(); } private void rollbackNodes(final Transaction trans) { processEachNode(new Procedure4<BTreeNode>() { public void apply(BTreeNode node) { node.rollback(trans); }}); } private void writeAllNodes(final Transaction systemTransaction){ if(_nodes == null){ return; } _nodes.traverse(new Visitor4<TreeIntObject>() { public void visit(TreeIntObject obj) { ((BTreeNode)obj.getObject()).write(systemTransaction); } }); } private void purge(){ if(_nodes == null){ return; } Tree temp = _nodes; _nodes = null; _root.holdChildrenAsIDs(); addNode(_root); temp.traverse(new Visitor4() { public void visit(Object obj) { BTreeNode node = (BTreeNode)((TreeIntObject)obj).getObject(); node.purge(); } }); for(BTreeNodeCacheEntry entry : _nodeCache){ entry._node.holdChildrenAsIDs(); } } private void processAllNodes(){ _processing = new NonblockingQueue(); _nodes.traverse(new Visitor4<TreeIntObject>() { public void visit(TreeIntObject node) { _processing.add(node.getObject()); } }); } private void ensureActive(Transaction trans){ if(! isActive()){ read(trans.systemTransaction()); } } private void ensureDirty(Transaction trans){ ensureActive(trans); enlist(trans); setStateDirty(); } private void enlist(Transaction trans) { if(canEnlistWithTransaction()){ ((LocalTransaction)trans).enlist(this); } } protected boolean canEnlistWithTransaction(){ return _config._canEnlistWithTransaction; } public byte getIdentifier() { return Const4.BTREE; } public void setRemoveListener(Visitor4 vis){ _removeListener = vis; } public int ownLength() { return 1 + Const4.OBJECT_LENGTH + (Const4.INT_LENGTH * 2) + Const4.ID_LENGTH; } public BTreeNode produceNode(int id){ if(DTrace.enabled){ DTrace.BTREE_PRODUCE_NODE.log(id); } TreeIntObject addtio = new TreeIntObject(id); _nodes = (TreeIntObject)Tree.add(_nodes, addtio); TreeIntObject tio = (TreeIntObject)addtio.addedOrExisting(); BTreeNode node = (BTreeNode)tio.getObject(); if(node == null){ node = cacheEntry(new BTreeNode(id, this))._node; tio.setObject(node); addToProcessing(node); } return node; } void addNode(BTreeNode node){ _nodes = (TreeIntObject)Tree.add(_nodes, new TreeIntObject(node.getID(), node)); addToProcessing(node); } void addToProcessing(BTreeNode node){ if(_processing != null){ _processing.add(node); } } void removeNode(BTreeNode node){ _nodes = (TreeIntObject)_nodes.removeLike(new TreeInt(node.getID())); } void notifyRemoveListener(Object obj){ if(_removeListener != null){ _removeListener.visit(obj); } } public void readThis(Transaction a_trans, ByteArrayBuffer a_reader) { a_reader.incrementOffset(1); // first byte is version, for possible future format changes _size = a_reader.readInt(); _nodeSize = a_reader.readInt(); _halfNodeSize = nodeSize() / 2; _root = produceNode(a_reader.readInt()); } public void writeThis(Transaction trans, ByteArrayBuffer a_writer) { a_writer.writeByte(BTREE_VERSION); a_writer.writeInt(_size); a_writer.writeInt(nodeSize()); a_writer.writeIDOf(trans, _root); } public int size(Transaction trans){ // This implementation of size will not work accurately for multiple // transactions. If two transactions call clear and both commit, _size // can end up negative. // For multiple transactions the size patches only are an estimate. ensureActive(trans); return _size + sizeIn(trans).value; } public void traverseKeys(Transaction trans, Visitor4 visitor){ ensureActive(trans); if(_root == null){ return; } _root.traverseKeys(trans, visitor); convertCacheEvictedNodesToReadMode(); } public void sizeChanged(Transaction transaction, BTreeNode node, int changeBy){ notifyCountChanged(transaction, node, changeBy); final ByRef<Integer> sizeInTransaction = sizeIn(transaction); sizeInTransaction.value = sizeInTransaction.value + changeBy; } public void dispose(Transaction transaction) { } public BTreePointer firstPointer(Transaction trans) { ensureActive(trans); if (null == _root) { return null; } BTreePointer pointer = _root.firstPointer(trans); convertCacheEvictedNodesToReadMode(); return pointer; } public BTreePointer lastPointer(Transaction trans) { ensureActive(trans); if (null == _root) { return null; } BTreePointer pointer = _root.lastPointer(trans); convertCacheEvictedNodesToReadMode(); return pointer; } public BTree debugLoadFully(Transaction trans) { ensureActive(trans); _root.debugLoadFully(trans); return this; } private void traverseAllNodes(Transaction trans, Visitor4 command) { ensureActive(trans); _root.traverseAllNodes(trans, command); } public void defragIndex(DefragmentContextImpl context) { if (Deploy.debug) { context.readBegin(Const4.BTREE); } context.incrementOffset(DEFRAGMENT_INCREMENT_OFFSET); context.copyID(); if (Deploy.debug) { context.readEnd(); } } public void defragIndexNode(DefragmentContextImpl context) { BTreeNode.defragIndex(context, _keyHandler); } public void defragBTree(final DefragmentServices services) { DefragmentContextImpl.processCopy(services,getID(),new SlotCopyHandler() { public void processCopy(DefragmentContextImpl context) { defragIndex(context); } }); services.traverseAllIndexSlots(this, new Visitor4() { public void visit(Object obj) { final int id=((Integer)obj).intValue(); DefragmentContextImpl.processCopy(services, id, new SlotCopyHandler() { public void processCopy(DefragmentContextImpl context) { defragIndexNode(context); } }); } }); convertCacheEvictedNodesToReadMode(); } int compareKeys(Context context, Object key1, Object key2) { PreparedComparison preparedComparison = _keyHandler.prepareComparison(context, key1); return preparedComparison.compareTo(key2); } private static Config4Impl config(Transaction trans) { if (null == trans) { throw new ArgumentNullException(); } return trans.container().configImpl(); } public void free(LocalTransaction systemTrans) { _disposed = true; freeAllNodeIds(systemTrans, allNodeIds(systemTrans)); super.free((LocalTransaction)systemTrans); } private void freeAllNodeIds(LocalTransaction systemTrans, final Iterator4 allNodeIDs) { TransactionalIdSystem idSystem = idSystem(systemTrans); while(allNodeIDs.moveNext()){ int id = ((Integer)allNodeIDs.current()).intValue(); idSystem.notifySlotDeleted(id, slotChangeFactory()); } } public Iterator4 allNodeIds(Transaction systemTrans) { final Collection4 allNodeIDs = new Collection4(); traverseAllNodes(systemTrans, new Visitor4() { public void visit(Object node) { allNodeIDs.add(new Integer(((BTreeNode)node).getID())); } }); return allNodeIDs.iterator(); } public BTreeRange asRange(Transaction trans){ return new BTreeRangeSingle(trans, this, firstPointer(trans), null); } private void traverseAllNodes(final Visitor4 visitor){ if(_nodes == null){ return; } _nodes.traverse(new Visitor4() { public void visit(Object obj) { visitor.visit(((TreeIntObject)obj).getObject()); } }); } public String toString() { final StringBuffer sb = new StringBuffer(); sb.append("BTree "); sb.append(getID()); sb.append(" Active Nodes: \n"); traverseAllNodes(new Visitor4() { public void visit(Object obj) { sb.append(obj); sb.append("\n"); } }); return sb.toString(); } public void structureListener(BTreeStructureListener listener) { _structureListener = listener; } public void notifySplit(Transaction trans, BTreeNode originalNode, BTreeNode newRightNode) { if(_structureListener != null){ _structureListener.notifySplit(trans, originalNode, newRightNode); } } public void notifyDeleted(Transaction trans, BTreeNode node) { if(_structureListener != null){ _structureListener.notifyDeleted(trans, node); } } public void notifyCountChanged(Transaction trans, BTreeNode node, int diff) { if(_structureListener != null){ _structureListener.notifyCountChanged(trans, node, diff); } } public Iterator4 iterator(Transaction trans) { return new BTreeIterator(trans, this); } public void clear(Transaction transaction) { BTreePointer currentPointer = firstPointer(transaction); while(currentPointer != null && currentPointer.isValid()){ BTreeNode node = currentPointer.node(); int index = currentPointer.index(); node.remove(transaction, index); currentPointer = currentPointer.next(); } } public Cache4<Integer, BTreeNodeCacheEntry> nodeCache(){ return _nodeCache; } BTreeNodeCacheEntry cacheEntry(final BTreeNode node){ return _nodeCache.produce(node.getID(), new Function4<Integer, BTreeNodeCacheEntry>() { public BTreeNodeCacheEntry apply(Integer id) { return new BTreeNodeCacheEntry(node); } }, new Procedure4<BTreeNodeCacheEntry>() { public void apply(BTreeNodeCacheEntry entry) { evictedFromCache(entry._node); } }); } @Override public SlotChangeFactory slotChangeFactory() { return _config._slotChangeFactory; } public void evictedFromCache(BTreeNode node){ _evictedFromCache = Tree.add(_evictedFromCache, new TreeIntObject(node.getID(), node)); } public void convertCacheEvictedNodesToReadMode(){ if(_evictedFromCache == null){ return; } Tree.traverse(_evictedFromCache, new Visitor4<TreeIntObject>() { public void visit(TreeIntObject treeIntObject) { ((BTreeNode)treeIntObject._object).toReadMode(); } }); _evictedFromCache = null; } }