/* * * * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com) * * * * 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. * * * * For more information: http://www.orientechnologies.com * */ package com.orientechnologies.orient.core.index.sbtree.local; import com.orientechnologies.common.comparator.ODefaultComparator; import com.orientechnologies.common.serialization.types.OBinarySerializer; import com.orientechnologies.common.serialization.types.OByteSerializer; import com.orientechnologies.common.serialization.types.OIntegerSerializer; import com.orientechnologies.common.serialization.types.OLongSerializer; import com.orientechnologies.orient.core.metadata.schema.OType; import com.orientechnologies.orient.core.storage.cache.OCacheEntry; import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurablePage; import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.io.IOException; import java.util.ArrayList; import java.util.Comparator; import java.util.List; /** * @author Andrey Lomakin * @since 8/7/13 */ public class OSBTreeBucket<K, V> extends ODurablePage { private static final int FREE_POINTER_OFFSET = NEXT_FREE_POSITION; private static final int SIZE_OFFSET = FREE_POINTER_OFFSET + OIntegerSerializer.INT_SIZE; private static final int IS_LEAF_OFFSET = SIZE_OFFSET + OIntegerSerializer.INT_SIZE; private static final int LEFT_SIBLING_OFFSET = IS_LEAF_OFFSET + OByteSerializer.BYTE_SIZE; private static final int RIGHT_SIBLING_OFFSET = LEFT_SIBLING_OFFSET + OLongSerializer.LONG_SIZE; private static final int TREE_SIZE_OFFSET = RIGHT_SIBLING_OFFSET + OLongSerializer.LONG_SIZE; /** * KEY_SERIALIZER_OFFSET and VALUE_SERIALIZER_OFFSET are no longer used by sb-tree since 1.7. * * However we left them in buckets to support backward compatibility. */ private static final int KEY_SERIALIZER_OFFSET = TREE_SIZE_OFFSET + OLongSerializer.LONG_SIZE; private static final int VALUE_SERIALIZER_OFFSET = KEY_SERIALIZER_OFFSET + OByteSerializer.BYTE_SIZE; private static final int FREE_VALUES_LIST_OFFSET = VALUE_SERIALIZER_OFFSET + OByteSerializer.BYTE_SIZE; private static final int POSITIONS_ARRAY_OFFSET = FREE_VALUES_LIST_OFFSET + OLongSerializer.LONG_SIZE; private final boolean isLeaf; private final OBinarySerializer<K> keySerializer; private final OBinarySerializer<V> valueSerializer; private final OType[] keyTypes; private final Comparator<? super K> comparator = ODefaultComparator.INSTANCE; @SuppressFBWarnings("EI_EXPOSE_REP2") public OSBTreeBucket(OCacheEntry cacheEntry, boolean isLeaf, OBinarySerializer<K> keySerializer, OType[] keyTypes, OBinarySerializer<V> valueSerializer, OWALChanges changes) throws IOException { super(cacheEntry, changes); this.isLeaf = isLeaf; this.keySerializer = keySerializer; this.keyTypes = keyTypes; this.valueSerializer = valueSerializer; setIntValue(FREE_POINTER_OFFSET, MAX_PAGE_SIZE_BYTES); setIntValue(SIZE_OFFSET, 0); setByteValue(IS_LEAF_OFFSET, (byte) (isLeaf ? 1 : 0)); setLongValue(LEFT_SIBLING_OFFSET, -1); setLongValue(RIGHT_SIBLING_OFFSET, -1); setLongValue(TREE_SIZE_OFFSET, 0); setLongValue(FREE_VALUES_LIST_OFFSET, -1); setByteValue(KEY_SERIALIZER_OFFSET, this.keySerializer.getId()); setByteValue(VALUE_SERIALIZER_OFFSET, this.valueSerializer.getId()); } @SuppressFBWarnings("EI_EXPOSE_REP2") public OSBTreeBucket(OCacheEntry cacheEntry, OBinarySerializer<K> keySerializer, OType[] keyTypes, OBinarySerializer<V> valueSerializer, OWALChanges changes) { super(cacheEntry, changes); this.keyTypes = keyTypes; this.isLeaf = getByteValue(IS_LEAF_OFFSET) > 0; this.keySerializer = keySerializer; this.valueSerializer = valueSerializer; } public void setTreeSize(long size) throws IOException { setLongValue(TREE_SIZE_OFFSET, size); } public long getTreeSize() { return getLongValue(TREE_SIZE_OFFSET); } public boolean isEmpty() { return size() == 0; } public long getValuesFreeListFirstIndex() { return getLongValue(FREE_VALUES_LIST_OFFSET); } public void setValuesFreeListFirstIndex(long pageIndex) throws IOException { setLongValue(FREE_VALUES_LIST_OFFSET, pageIndex); } public int find(K key) { int low = 0; int high = size() - 1; while (low <= high) { int mid = (low + high) >>> 1; K midVal = getKey(mid); int cmp = comparator.compare(midVal, key); if (cmp < 0) low = mid + 1; else if (cmp > 0) high = mid - 1; else return mid; // key found } return -(low + 1); // key not found. } public long remove(int entryIndex) throws IOException { int entryPosition = getIntValue(POSITIONS_ARRAY_OFFSET + entryIndex * OIntegerSerializer.INT_SIZE); int keySize = getObjectSizeInDirectMemory(keySerializer, entryPosition); int entrySize; long linkValue = -1; if (isLeaf) { if (valueSerializer.isFixedLength()) { entrySize = keySize + valueSerializer.getFixedLength() + OByteSerializer.BYTE_SIZE; } else { final boolean isLink = getByteValue(entryPosition + keySize) > 0; if (!isLink) entrySize = keySize + getObjectSizeInDirectMemory(valueSerializer, entryPosition + keySize + OByteSerializer.BYTE_SIZE) + OByteSerializer.BYTE_SIZE; else { entrySize = keySize + OByteSerializer.BYTE_SIZE + OLongSerializer.LONG_SIZE; linkValue = deserializeFromDirectMemory(OLongSerializer.INSTANCE, entryPosition + keySize + OByteSerializer.BYTE_SIZE); } } } else { throw new IllegalStateException("Remove is applies to leaf buckets only"); } int size = size(); if (entryIndex < size - 1) { moveData(POSITIONS_ARRAY_OFFSET + (entryIndex + 1) * OIntegerSerializer.INT_SIZE, POSITIONS_ARRAY_OFFSET + entryIndex * OIntegerSerializer.INT_SIZE, (size - entryIndex - 1) * OIntegerSerializer.INT_SIZE); } size--; setIntValue(SIZE_OFFSET, size); int freePointer = getIntValue(FREE_POINTER_OFFSET); if (size > 0 && entryPosition > freePointer) { moveData(freePointer, freePointer + entrySize, entryPosition - freePointer); } setIntValue(FREE_POINTER_OFFSET, freePointer + entrySize); int currentPositionOffset = POSITIONS_ARRAY_OFFSET; for (int i = 0; i < size; i++) { int currentEntryPosition = getIntValue(currentPositionOffset); if (currentEntryPosition < entryPosition) setIntValue(currentPositionOffset, currentEntryPosition + entrySize); currentPositionOffset += OIntegerSerializer.INT_SIZE; } return linkValue; } public int size() { return getIntValue(SIZE_OFFSET); } public SBTreeEntry<K, V> getEntry(int entryIndex) { int entryPosition = getIntValue(entryIndex * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET); if (isLeaf) { K key = deserializeFromDirectMemory(keySerializer, entryPosition); entryPosition += getObjectSizeInDirectMemory(keySerializer, entryPosition); boolean isLinkValue = getByteValue(entryPosition) > 0; long link = -1; V value = null; if (isLinkValue) link = deserializeFromDirectMemory(OLongSerializer.INSTANCE, entryPosition + OByteSerializer.BYTE_SIZE); else value = deserializeFromDirectMemory(valueSerializer, entryPosition + OByteSerializer.BYTE_SIZE); return new SBTreeEntry<K, V>(-1, -1, key, new OSBTreeValue<V>(link >= 0, link, value)); } else { long leftChild = getLongValue(entryPosition); entryPosition += OLongSerializer.LONG_SIZE; long rightChild = getLongValue(entryPosition); entryPosition += OLongSerializer.LONG_SIZE; K key = deserializeFromDirectMemory(keySerializer, entryPosition); return new SBTreeEntry<K, V>(leftChild, rightChild, key, null); } } /** * Obtains the value stored under the given entry index in this bucket. * * @param entryIndex the value entry index. * * @return the obtained value. */ public OSBTreeValue<V> getValue(int entryIndex) { assert isLeaf; int entryPosition = getIntValue(entryIndex * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET); // skip key entryPosition += getObjectSizeInDirectMemory(keySerializer, entryPosition); boolean isLinkValue = getByteValue(entryPosition) > 0; long link = -1; V value = null; if (isLinkValue) link = deserializeFromDirectMemory(OLongSerializer.INSTANCE, entryPosition + OByteSerializer.BYTE_SIZE); else value = deserializeFromDirectMemory(valueSerializer, entryPosition + OByteSerializer.BYTE_SIZE); return new OSBTreeValue<V>(link >= 0, link, value); } public K getKey(int index) { int entryPosition = getIntValue(index * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET); if (!isLeaf) entryPosition += 2 * OLongSerializer.LONG_SIZE; return deserializeFromDirectMemory(keySerializer, entryPosition); } public boolean isLeaf() { return isLeaf; } public void addAll(List<SBTreeEntry<K, V>> entries) throws IOException { for (int i = 0; i < entries.size(); i++) addEntry(i, entries.get(i), false); } public void shrink(int newSize) throws IOException { List<SBTreeEntry<K, V>> treeEntries = new ArrayList<SBTreeEntry<K, V>>(newSize); for (int i = 0; i < newSize; i++) { treeEntries.add(getEntry(i)); } setIntValue(FREE_POINTER_OFFSET, MAX_PAGE_SIZE_BYTES); setIntValue(SIZE_OFFSET, 0); int index = 0; for (SBTreeEntry<K, V> entry : treeEntries) { addEntry(index, entry, false); index++; } } public boolean addEntry(int index, SBTreeEntry<K, V> treeEntry, boolean updateNeighbors) throws IOException { final int keySize = keySerializer.getObjectSize(treeEntry.key, (Object[]) keyTypes); int valueSize = 0; int entrySize = keySize; if (isLeaf) { if (valueSerializer.isFixedLength()) valueSize = valueSerializer.getFixedLength(); else { if (treeEntry.value.isLink()) valueSize = OLongSerializer.LONG_SIZE; else valueSize = valueSerializer.getObjectSize(treeEntry.value.getValue()); } entrySize += valueSize + OByteSerializer.BYTE_SIZE; } else entrySize += 2 * OLongSerializer.LONG_SIZE; int size = size(); int freePointer = getIntValue(FREE_POINTER_OFFSET); if (freePointer - entrySize < (size + 1) * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET) return false; if (index <= size - 1) { moveData(POSITIONS_ARRAY_OFFSET + index * OIntegerSerializer.INT_SIZE, POSITIONS_ARRAY_OFFSET + (index + 1) * OIntegerSerializer.INT_SIZE, (size - index) * OIntegerSerializer.INT_SIZE); } freePointer -= entrySize; setIntValue(FREE_POINTER_OFFSET, freePointer); setIntValue(POSITIONS_ARRAY_OFFSET + index * OIntegerSerializer.INT_SIZE, freePointer); setIntValue(SIZE_OFFSET, size + 1); if (isLeaf) { byte[] serializedKey = new byte[keySize]; keySerializer.serializeNativeObject(treeEntry.key, serializedKey, 0, (Object[]) keyTypes); freePointer += setBinaryValue(freePointer, serializedKey); freePointer += setByteValue(freePointer, treeEntry.value.isLink() ? (byte) 1 : (byte) 0); byte[] serializedValue = new byte[valueSize]; if (treeEntry.value.isLink()) OLongSerializer.INSTANCE.serializeNative(treeEntry.value.getLink(), serializedValue, 0); else valueSerializer.serializeNativeObject(treeEntry.value.getValue(), serializedValue, 0); setBinaryValue(freePointer, serializedValue); } else { freePointer += setLongValue(freePointer, treeEntry.leftChild); freePointer += setLongValue(freePointer, treeEntry.rightChild); byte[] serializedKey = new byte[keySize]; keySerializer.serializeNativeObject(treeEntry.key, serializedKey, 0, (Object[]) keyTypes); setBinaryValue(freePointer, serializedKey); size++; if (updateNeighbors && size > 1) { if (index < size - 1) { final int nextEntryPosition = getIntValue(POSITIONS_ARRAY_OFFSET + (index + 1) * OIntegerSerializer.INT_SIZE); setLongValue(nextEntryPosition, treeEntry.rightChild); } if (index > 0) { final int prevEntryPosition = getIntValue(POSITIONS_ARRAY_OFFSET + (index - 1) * OIntegerSerializer.INT_SIZE); setLongValue(prevEntryPosition + OLongSerializer.LONG_SIZE, treeEntry.leftChild); } } } return true; } public int updateValue(int index, OSBTreeValue<V> value) throws IOException { int entryPosition = getIntValue(index * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET); entryPosition += getObjectSizeInDirectMemory(keySerializer, entryPosition); boolean isLinkValue = getByteValue(entryPosition) > 0; entryPosition += OByteSerializer.BYTE_SIZE; int newSize = 0; if (value.isLink()) newSize = OLongSerializer.LONG_SIZE; else newSize = valueSerializer.getObjectSize(value.getValue()); final int oldSize; if (isLinkValue) oldSize = OLongSerializer.LONG_SIZE; else oldSize = getObjectSizeInDirectMemory(valueSerializer, entryPosition); if (newSize != oldSize) return -1; byte[] serializedValue = new byte[newSize]; if (value.isLink()) OLongSerializer.INSTANCE.serializeNative(value.getLink(), serializedValue, 0); else valueSerializer.serializeNativeObject(value.getValue(), serializedValue, 0); byte[] oldSerializedValue = getBinaryValue(entryPosition, oldSize); if (ODefaultComparator.INSTANCE.compare(oldSerializedValue, serializedValue) == 0) return 0; setBinaryValue(entryPosition, serializedValue); return 1; } public void setLeftSibling(long pageIndex) throws IOException { setLongValue(LEFT_SIBLING_OFFSET, pageIndex); } public long getLeftSibling() { return getLongValue(LEFT_SIBLING_OFFSET); } public void setRightSibling(long pageIndex) throws IOException { setLongValue(RIGHT_SIBLING_OFFSET, pageIndex); } public long getRightSibling() { return getLongValue(RIGHT_SIBLING_OFFSET); } public static final class SBTreeEntry<K, V> implements Comparable<SBTreeEntry<K, V>> { private final Comparator<? super K> comparator = ODefaultComparator.INSTANCE; public final long leftChild; public final long rightChild; public final K key; public final OSBTreeValue<V> value; public SBTreeEntry(long leftChild, long rightChild, K key, OSBTreeValue<V> value) { this.leftChild = leftChild; this.rightChild = rightChild; this.key = key; this.value = value; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final SBTreeEntry<?, ?> that = (SBTreeEntry<?, ?>) o; if (leftChild != that.leftChild) return false; if (rightChild != that.rightChild) return false; if (!key.equals(that.key)) return false; if (value != null) { if (!value.equals(that.value)) return false; } else { if (that.value != null) return false; } return true; } @Override public int hashCode() { int result = (int) (leftChild ^ (leftChild >>> 32)); result = 31 * result + (int) (rightChild ^ (rightChild >>> 32)); result = 31 * result + key.hashCode(); result = 31 * result + (value != null ? value.hashCode() : 0); return result; } @Override public String toString() { return "SBTreeEntry{" + "leftChild=" + leftChild + ", rightChild=" + rightChild + ", key=" + key + ", value=" + value + '}'; } @Override public int compareTo(SBTreeEntry<K, V> other) { return comparator.compare(key, other.key); } } }