/** * 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 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; /** * A leaf node of a B+Tree. * * @author Benjamin * * @param <K> the Key type. * @param <V> the Value type. */ final class LeafNode<K extends Comparable<K>, V> extends AbstractNode<K, V> { /** * The node records. */ private final NavigableMap<K, ValueWrapper<V>> records; /** * Creates a new empty <code>LeafNode</code>. * * @param btree the b+Tree to which this node belongs. */ public LeafNode(BTree<K, V> btree) { this(btree, new TreeMap<K, ValueWrapper<V>>()); } /** * Creates a new <code>LeafNode</code> that contains the specified records. * * @param btree the b+Tree to which this node belongs. * @param records the node records. */ private LeafNode(BTree<K, V> btree, NavigableMap<K, ValueWrapper<V>> records) { super(btree); this.records = records; } /** * Creates a new <code>LeafNode</code> that contains the specified records. * * @param btree the b+Tree to which this node belongs. * @param records the node records. */ public static <K extends Comparable<K>, V> LeafNode<K, V> newInstance(BTree<K, V> btree, SortedMap<K, ValueWrapper<V>> records) { return new LeafNode<K, V>(btree, new TreeMap<K, ValueWrapper<V>>(records)); } /** * {@inheritDoc} */ @Override public int getType() { return LEAF_NODE; } /** * {@inheritDoc} */ @Override public Node<K, V>[] insert(K key, V value) throws IOException { LeafNode<K, V> newNode = copy().addRecord(key, value); if (newNode.isFull()) { return newNode.split(); } return toArray(newNode); } /** * {@inheritDoc} */ @Override public Node<K, V> delete(K key) { return copy().removeRecord(key); } /** * {@inheritDoc} */ @Override public boolean isFull() { return getNumberOfElements() >= getBranchingFactor(); } /** * {@inheritDoc} */ @Override public K getFirstKey() { return this.records.firstKey(); } /** * Returns the last key of this node. * * @return the last key of this node. */ public K getLastKey() { return this.records.lastKey(); } /** * {@inheritDoc} */ @Override public String toString() { return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE).append("records", this.records).toString(); } /** * {@inheritDoc} */ @Override public Node<K, V> merge(Node<K, V> rightNode) throws IOException { LeafNode<K, V> newNode = new LeafNode<>(getBTree(), new TreeMap<>(this.records)); newNode.records.putAll(((LeafNode<K, V>) getBTree().unwrapNode(rightNode)).records); return newNode; } /** * {@inheritDoc} */ @Override public Node<K, V>[] rebalanceWithRightNode(Node<K, V> rightNode) throws IOException { LeafNode<K, V> rightLeafNode = (LeafNode<K, V>) getBTree().unwrapNode(rightNode); K firstKey = rightLeafNode.getFirstKey(); V firstKeyValue = rightLeafNode.get(firstKey); LeafNode<K, V> newRightNode = rightLeafNode.copy().removeRecord(firstKey); LeafNode<K, V> newLeftNode = copy().addRecord(firstKey, firstKeyValue); return toArray(newLeftNode, newRightNode); } /** * {@inheritDoc} */ @Override public Node<K, V>[] rebalanceWithLeftNode(Node<K, V> leftNode) throws IOException { LeafNode<K, V> leftLeafNode = (LeafNode<K, V>) getBTree().unwrapNode(leftNode); K lastKey = leftLeafNode.getLastKey(); V LastKeyValue = leftLeafNode.get(lastKey); LeafNode<K, V> newRightNode = leftLeafNode.copy().removeRecord(lastKey); LeafNode<K, V> newLeftNode = copy().addRecord(lastKey, LastKeyValue); return toArray(newLeftNode, newRightNode); } /** * {@inheritDoc} * * @throws IOException */ @Override public NodeVisitResult accept(NodeVisitor<K, V> visitor) throws IOException { for (Entry<K, ValueWrapper<V>> entry : this.records.entrySet()) { NodeVisitResult result = visitor.visitRecord(entry.getKey(), entry.getValue()); if (result == NodeVisitResult.TERMINATE) { return NodeVisitResult.TERMINATE; } if (result == NodeVisitResult.SKIP_SIBLINGS) { break; } } return NodeVisitResult.CONTINUE; } /** * {@inheritDoc} */ @Override protected int getNumberOfElements() { return this.records.size(); } /** * {@inheritDoc} */ @Override protected int getMinimumNumberOfElements() { return (int) Math.floor(getBranchingFactor() / 2.0); } /** * A <code>Map</code> containing the key-value mapping of this node. * * @return a <code>Map</code> containing the key-value mapping of this node. * @throws IOException if an I/O problem occurs. */ Map<K, V> toMap() throws IOException { Map<K, V> map = new TreeMap<>(); for (Entry<K, ValueWrapper<V>> entry : this.records.entrySet()) { map.put(entry.getKey(), entry.getValue().getValue()); } return map; } Map<K, ValueWrapper<V>> getRecords() { return Collections.unmodifiableMap(this.records); } /** * Splits this node in two. * * @return the two new nodes created by the split. */ private Node<K, V>[] split() { int half = getBranchingFactor() >> 1; K halfKey = Iterables.get(this.records.keySet(), half); SortedMap<K, ValueWrapper<V>> head = this.records.headMap(halfKey); SortedMap<K, ValueWrapper<V>> tail = this.records.tailMap(halfKey); return toArray(new LeafNode<K, V>(getBTree(), new TreeMap<>(head)), new LeafNode<K, V>(getBTree(), new TreeMap<>(tail))); } /** * * {@inheritDoc} */ @Override public V get(K key) throws IOException { ValueWrapper<V> value = this.records.get(key); if (value == null) { return null; } return value.getValue(); } /** * {@inheritDoc} */ @Override public KeyValueIterator<K, V> iterator(K fromKey, K toKey) throws IOException { Map<K, ValueWrapper<V>> subMap = this.records.subMap(fromKey, true, toKey, true); return new LeafNodeKeyValueIterator<>(subMap); } /** * * {@inheritDoc} */ @Override public boolean contains(K key) throws IOException { return this.records.containsKey(key); } /** * Adds the specified record to this node. * * @param key the record key. * @param value the record value. * @return this node. * @throws IOException if an IO errors occurs while adding the record. */ private LeafNode<K, V> addRecord(K key, V value) throws IOException { this.records.put(key, getBTree().wrapValue(value)); return this; } /** * Returns a copy of this node. * * @return a copy of this node. */ private LeafNode<K, V> copy() { return new LeafNode<>(getBTree(), new TreeMap<>(this.records)); } /** * Removes the record with the specified key. * * @param key the key of the record to remove. * @return this node. */ private LeafNode<K, V> removeRecord(K key) { this.records.remove(key); return this; } /** * <code>KeyValueIterator</code> used to iterate over the records of this leaf node. */ public static final class LeafNodeKeyValueIterator<K extends Comparable<K>, V> implements KeyValueIterator<K, V> { /** * The iterator over the map entries */ private final Iterator<Entry<K, ValueWrapper<V>>> iterator; /** * The current record. */ private Entry<K, ValueWrapper<V>> entry; public LeafNodeKeyValueIterator(Map<K, ValueWrapper<V>> map) { this.iterator = map.entrySet().iterator(); } /** * {@inheritDoc} */ @Override public boolean next() throws IOException { if (this.iterator.hasNext()) { this.entry = this.iterator.next(); return true; } return false; } /** * {@inheritDoc} */ @Override public K getKey() { checkState(); return this.entry.getKey(); } /** * {@inheritDoc} */ @Override public V getValue() throws IOException { checkState(); return this.entry.getValue().getValue(); } /** * Checks that the iterator is in a valid state. */ private void checkState() { if (this.entry == null) { if (this.iterator.hasNext()) { throw new IllegalStateException("next must be called before trying to retrieve the record key or value"); } throw new NoSuchElementException(); } } } }