/** * Copyright 2013 Benjamin Lerer * * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.horizondb.db.btree; import io.horizondb.db.btree.NodeVisitor.NodeVisitResult; import java.io.IOException; import java.util.Collections; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; import java.util.NavigableMap; import java.util.NoSuchElementException; import java.util.SortedMap; import java.util.TreeMap; import javax.annotation.concurrent.Immutable; import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.builder.ToStringStyle; import com.google.common.collect.Iterables; import static io.horizondb.db.util.ArrayUtils.toArray; /** * An internal node of a BTree. * * @author Benjamin * * @param <K> the Key type. * @param <V> the Value type. */ @Immutable final class InternalNode<K extends Comparable<K>, V> extends AbstractNode<K, V> { /** * The children of this node per first key. */ private final NavigableMap<K, Node<K, V>> children; /** * Creates a new <code>InternalNode</code> that has the specified children. * * @param btree the b+Tree to which this node belongs. * @param nodes the node children. * @throws IOException if an I/O problem occurs. */ @SafeVarargs public InternalNode(BTree<K, V> btree, Node<K, V>... nodes) throws IOException { this(btree, new TreeMap<K, Node<K, V>>()); addChildren(nodes); } /** * Creates a new <code>InternalNode</code> that contains the specified records. * * @param btree the b+Tree to which this node belongs. * @param children the node children. */ public static <K extends Comparable<K>, V> InternalNode<K, V> newInstance(BTree<K, V> btree, SortedMap<K, Node<K, V>> records) { return new InternalNode<K, V>(btree, new TreeMap<K, Node<K, V>>(records)); } /** * {@inheritDoc} */ @Override public int getType() { return INTERNAL_NODE; } /** * Creates a new <code>InternalNode</code> that has the specified children. * * @param btree the b+Tree to which this node belongs. * @param children the node children. */ private InternalNode(BTree<K, V> btree, NavigableMap<K, Node<K, V>> children) { super(btree); this.children = children; } /** * {@inheritDoc} */ @Override public Node<K, V>[] insert(K key, V value) throws IOException { Node<K, V> oldNode = find(key); Node<K, V>[] newNodes = oldNode.insert(key, value); InternalNode<K, V> newInternalNode = copy().removeChild(oldNode).addChildren(newNodes); if (newInternalNode.isFull()) { return newInternalNode.split(); } return toArray(newInternalNode); } /** * {@inheritDoc} */ @Override public Node<K, V>[] rebalanceWithRightNode(Node<K, V> rightNode) throws IOException { InternalNode<K, V> rightInternalNode = (InternalNode<K, V>) getBTree().unwrapNode(rightNode); Node<K, V> firstChild = rightInternalNode.getFirstChild(); InternalNode<K, V> newRightNode = rightInternalNode.copy().removeChild(firstChild); InternalNode<K, V> newLeftNode = copy().addChild(firstChild); return toArray(newLeftNode, newRightNode); } /** * {@inheritDoc} */ @Override public Node<K, V>[] rebalanceWithLeftNode(Node<K, V> leftNode) throws IOException { InternalNode<K, V> leftInternalNode = (InternalNode<K, V>) getBTree().unwrapNode(leftNode); Node<K, V> lastChild = leftInternalNode.getLastChild(); InternalNode<K, V> newLeftNode = leftInternalNode.copy().removeChild(lastChild); InternalNode<K, V> newRightNode = copy().addChild(lastChild); return toArray(newLeftNode, newRightNode); } /** * {@inheritDoc} */ @Override public Node<K, V> delete(K key) throws IOException { Node<K, V> oldNode = find(key); Node<K, V> newNode = oldNode.delete(key); if (newNode.hasLessThanMinimumNumberOfElement()) { return rebalanceOrMerge(oldNode, newNode); } return copy().removeChild(oldNode).addChild(newNode); } /** * {@inheritDoc} */ @Override public K getFirstKey() { return this.children.firstKey(); } /** * {@inheritDoc} */ @Override public V get(K key) throws IOException { return find(key).get(key); } /** * {@inheritDoc} */ @Override public KeyValueIterator<K, V> iterator(K fromKey, K toKey) throws IOException { K key = this.children.floorKey(fromKey); if (key == null) { key = fromKey; } SortedMap<K, Node<K, V>> subMap = this.children.subMap(key, true, toKey, true); return new InternalNodeKeyValueIterator<K, V>(fromKey, toKey, subMap); } /** * {@inheritDoc} */ @Override public boolean contains(K key) throws IOException { return find(key).contains(key); } /** * {@inheritDoc} */ @Override public boolean isFull() { return getNumberOfElements() > getBranchingFactor(); } /** * {@inheritDoc} */ @Override public String toString() { return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append("children", this.children).toString(); } /** * {@inheritDoc} */ @Override public Node<K, V> merge(Node<K, V> right) { return copy().addChildrenFrom((InternalNode<K, V>) right); } /** * {@inheritDoc} */ @Override public NodeVisitResult accept(NodeVisitor<K, V> visitor) throws IOException { for (Entry<K, Node<K, V>> entry : this.children.entrySet()) { K key = entry.getKey(); Node<K, V> node = entry.getValue(); NodeVisitResult result = visitor.preVisitNode(key, node); if (result == NodeVisitResult.TERMINATE) { return NodeVisitResult.TERMINATE; } if (result == NodeVisitResult.SKIP_SIBLINGS) { break; } if (result != NodeVisitResult.SKIP_SUBTREE) { if (node.accept(visitor) == NodeVisitResult.TERMINATE) { return NodeVisitResult.TERMINATE; } } result = visitor.postVisitNode(key, node); if (result == NodeVisitResult.TERMINATE) { return NodeVisitResult.TERMINATE; } if (result == NodeVisitResult.SKIP_SIBLINGS) { break; } } return NodeVisitResult.CONTINUE; } /** * {@inheritDoc} */ @Override protected int getNumberOfElements() { return this.children.size(); } /** * {@inheritDoc} */ @Override protected int getMinimumNumberOfElements() { return (int) Math.ceil(getBranchingFactor() / 2.0); } /** * Returns an unmodifiable <code>Map</code> representation of this node. * * @return an unmodifiable <code>Map</code> representation of this node. */ Map<K, Node<K, V>> toMap() { return Collections.unmodifiableMap(this.children); } /** * Returns the child node with the specified index. * * <p> * This method is intended for test only. * </p> * * @param index the child index. * @return the child node with the specified index. */ Node<K, V> getChild(int index) { return Iterables.get(this.children.values(), index); } /** * Returns an <code>Iterable</code> containing the keys of this node. * * @return an <code>Iterable</code> containing the keys of this node. */ Iterable<K> getKeys() { return Collections.unmodifiableSet(this.children.keySet()); } /** * Finds the node which contains the specified key. * * @param key the key for which the node must be found. * @return the node which contains the specified key. */ private Node<K, V> find(K key) { Entry<K, Node<K, V>> entry = this.children.floorEntry(key); if (entry == null) { return this.children.firstEntry().getValue(); } return entry.getValue(); } /** * Returns the right sibling of the node which contains the specified key. * * @param key the key. * @return the right sibling of the node which contains the specified key or <code>null</code> if the node has no * right sibling. */ private Node<K, V> getRightNode(K key) { Entry<K, Node<K, V>> entry = this.children.higherEntry(key); if (entry == null) { return null; } return entry.getValue(); } /** * Returns the left sibling of the node which contains the specified key. * * @param key the key. * @return the left sibling of the node which contains the specified key or <code>null</code> if the node has no * left sibling. */ private Node<K, V> getLeftNode(K key) { Entry<K, Node<K, V>> entry = this.children.lowerEntry(key); if (entry == null) { return null; } return entry.getValue(); } /** * Adds to the children of this node the children of the specified one. * * @param node the node from which the children must be added. * @return this node. */ private InternalNode<K, V> addChildrenFrom(InternalNode<K, V> node) { this.children.putAll(node.children); return this; } /** * Returns <code>true</code> if the specified node is not null and has more than the minimum number of elements, * <code>false</code> otherwise. * * @param node the node to check. * @return <code>true</code> if the specified node is not null and has more than the minimum number of elements, * <code>false</code> otherwise. * @throws IOException if an I/O problem occurs. */ private static <K extends Comparable<K>, V> boolean hasNodeMoreThanMinimumNumberOfElement(Node<K, V> node) throws IOException { return node != null && node.hasMoreThanMinimumNumberOfElement(); } /** * Returns <code>true</code> if this node has only one child, <code>false</code> otherwise. * * @return <code>true</code> if this node has only one child, <code>false</code> otherwise. */ private boolean hasOnlyOneChild() { return this.children.size() == 1; } /** * @return */ private Node<K, V>[] split() { int half = getBranchingFactor() >> 1; K halfKey = Iterables.get(this.children.keySet(), half + 1); NavigableMap<K, Node<K, V>> head = (NavigableMap<K, Node<K, V>>) this.children.headMap(halfKey); NavigableMap<K, Node<K, V>> tail = (NavigableMap<K, Node<K, V>>) this.children.tailMap(halfKey); return toArray(new InternalNode<K, V>(getBTree(), new TreeMap<>(head)), new InternalNode<K, V>(getBTree(), new TreeMap<>(tail))); } /** * Returns the first child of this internal node. * * @return the first child of this internal node. */ private Node<K, V> getFirstChild() { return this.children.firstEntry().getValue(); } /** * Returns the last child of this internal node. * * @return the last child of this internal node. */ private Node<K, V> getLastChild() { return this.children.lastEntry().getValue(); } /** * Adds the specified nodes to the children of this node. * * @param nodes the nodes to add. * @return this node. * @throws IOException if an I/O problem occurs. */ @SafeVarargs private final InternalNode<K, V> addChildren(Node<K, V>... nodes) throws IOException { for (Node<K, V> node : nodes) { addChild(node); } return this; } /** * Adds the specified node to the children of this node. * * @param node the node to add. * @return this node. * @throws IOException if an I/O problem occurs. */ private InternalNode<K, V> addChild(Node<K, V> node) throws IOException { this.children.put(node.getFirstKey(), getBTree().wrapNode(node)); return this; } /** * Removes the specified children of this node. * * @param nodes the children to remove. * @return this node. * @throws IOException if an I/O problem occurs. */ @SafeVarargs private final InternalNode<K, V> removeChildren(Node<K, V>... nodes) throws IOException { for (Node<K, V> node : nodes) { removeChild(node); } return this; } /** * Re-balances if possible the specified node with its siblings or if it is not possible merge it with one of its * siblings. * * @param index the node index. * @param node the node on which re-balancing or merging must be performed. * @throws IOException if an IO problem occurs. */ private Node<K, V> rebalanceOrMerge(Node<K, V> oldNode, Node<K, V> newNode) throws IOException { Node<K, V> rightNode = getRightNode(oldNode.getFirstKey()); if (hasNodeMoreThanMinimumNumberOfElement(rightNode)) { return copy().removeChildren(oldNode, rightNode).addChildren(newNode.rebalanceWithRightNode(rightNode)); } Node<K, V> leftNode = getLeftNode(oldNode.getFirstKey()); if (hasNodeMoreThanMinimumNumberOfElement(leftNode)) { return copy().removeChildren(leftNode, oldNode).addChildren(newNode.rebalanceWithLeftNode(leftNode)); } if (rightNode != null) { Node<K, V> mergedNode = newNode.merge(rightNode); InternalNode<K, V> newInternalNode = copy().removeChildren(oldNode, rightNode).addChildren(mergedNode); if (newInternalNode.hasOnlyOneChild()) { return mergedNode; } return newInternalNode; } Node<K, V> mergedNode = leftNode.merge(newNode); InternalNode<K, V> newInternalNode = copy().removeChildren(leftNode, oldNode).addChildren(mergedNode); if (newInternalNode.hasOnlyOneChild()) { return mergedNode; } return newInternalNode; } /** * Removes the specified child of this node. * * @param node the child to remove. * @return this node. * @throws IOException if an I/O problem occurs. */ private InternalNode<K, V> removeChild(Node<K, V> node) throws IOException { this.children.remove(node.getFirstKey()); return this; } /** * Returns a copy of this node. * * @return a copy of this node. */ private InternalNode<K, V> copy() { return new InternalNode<K, V>(getBTree(), new TreeMap<>(this.children)); } /** * <code>KeyValueIterator</code> used to iterate over the records of this leaf node. */ public static final class InternalNodeKeyValueIterator<K extends Comparable<K>, V> implements KeyValueIterator<K, V> { /** * The from key (inclusive). */ public final K fromKey; /** * The to key (exclusive). */ public final K toKey; /** * The iterator over the node entries */ private final Iterator<Node<K, V>> iterator; /** * The iterator over the node records */ private KeyValueIterator<K, V> nodeIterator; public InternalNodeKeyValueIterator(K fromKey, K toKey, Map<K, Node<K, V>> map) { this.fromKey = fromKey; this.toKey = toKey; this.iterator = map.values().iterator(); } /** * {@inheritDoc} */ @Override public boolean next() throws IOException { if (this.nodeIterator != null && this.nodeIterator.next()) { return true; } if (this.iterator.hasNext()) { this.nodeIterator = this.iterator.next().iterator(this.fromKey, this.toKey); return next(); } this.nodeIterator = null; return false; } /** * {@inheritDoc} */ @Override public K getKey() { checkState(); return this.nodeIterator.getKey(); } /** * {@inheritDoc} */ @Override public V getValue() throws IOException { checkState(); return this.nodeIterator.getValue(); } /** * Checks that the iterator is in a valid state. */ private void checkState() { if (this.nodeIterator == null) { if (this.iterator.hasNext()) { throw new IllegalStateException("next must be called before trying to retrieve the record key " + "or value"); } throw new NoSuchElementException(); } } } }