/*
*
* * 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.hashindex.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.config.OGlobalConfiguration;
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.Comparator;
import java.util.Iterator;
import java.util.NoSuchElementException;
/**
* @author Andrey Lomakin
* @since 2/17/13
*/
public class OHashIndexBucket<K, V> extends ODurablePage implements Iterable<OHashIndexBucket.Entry<K, V>> {
private static final int FREE_POINTER_OFFSET = NEXT_FREE_POSITION;
private static final int DEPTH_OFFSET = FREE_POINTER_OFFSET + OIntegerSerializer.INT_SIZE;
private static final int SIZE_OFFSET = DEPTH_OFFSET + OByteSerializer.BYTE_SIZE;
private static final int HISTORY_OFFSET = SIZE_OFFSET + OIntegerSerializer.INT_SIZE;
private static final int NEXT_REMOVED_BUCKET_OFFSET = HISTORY_OFFSET + OLongSerializer.LONG_SIZE * 64;
private static final int POSITIONS_ARRAY_OFFSET = NEXT_REMOVED_BUCKET_OFFSET + OLongSerializer.LONG_SIZE;
public static final int MAX_BUCKET_SIZE_BYTES = OGlobalConfiguration.DISK_CACHE_PAGE_SIZE.getValueAsInteger() * 1024;
private final OBinarySerializer<K> keySerializer;
private final OBinarySerializer<V> valueSerializer;
private final OType[] keyTypes;
private final Comparator keyComparator = ODefaultComparator.INSTANCE;
@SuppressFBWarnings("EI_EXPOSE_REP2")
public OHashIndexBucket(int depth, OCacheEntry cacheEntry, OBinarySerializer<K> keySerializer,
OBinarySerializer<V> valueSerializer, OType[] keyTypes, OWALChanges changes) throws IOException {
super(cacheEntry, changes);
this.keySerializer = keySerializer;
this.valueSerializer = valueSerializer;
this.keyTypes = keyTypes;
init(depth);
}
@SuppressFBWarnings("EI_EXPOSE_REP2")
public OHashIndexBucket(OCacheEntry cacheEntry, OBinarySerializer<K> keySerializer, OBinarySerializer<V> valueSerializer,
OType[] keyTypes, OWALChanges changes) {
super(cacheEntry, changes);
this.keySerializer = keySerializer;
this.valueSerializer = valueSerializer;
this.keyTypes = keyTypes;
}
public void init(int depth) throws IOException {
setByteValue(DEPTH_OFFSET, (byte) depth);
setIntValue(FREE_POINTER_OFFSET, MAX_BUCKET_SIZE_BYTES);
setIntValue(SIZE_OFFSET, 0);
}
public Entry<K, V> find(final K key, final long hashCode) {
final int index = binarySearch(key, hashCode);
if (index < 0)
return null;
return getEntry(index);
}
private int binarySearch(K key, long hashCode) {
int low = 0;
int high = size() - 1;
while (low <= high) {
final int mid = (low + high) >>> 1;
final long midHashCode = getHashCode(mid);
final int cmp;
if (lessThanUnsigned(midHashCode, hashCode))
cmp = -1;
else if (greaterThanUnsigned(midHashCode, hashCode))
cmp = 1;
else {
final K midVal = getKey(mid);
cmp = keyComparator.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.
}
private static boolean lessThanUnsigned(long longOne, long longTwo) {
return (longOne + Long.MIN_VALUE) < (longTwo + Long.MIN_VALUE);
}
private static boolean greaterThanUnsigned(long longOne, long longTwo) {
return (longOne + Long.MIN_VALUE) > (longTwo + Long.MIN_VALUE);
}
public Entry<K, V> getEntry(int index) {
int entryPosition = getIntValue(POSITIONS_ARRAY_OFFSET + index * OIntegerSerializer.INT_SIZE);
final long hashCode = getLongValue(entryPosition);
entryPosition += OLongSerializer.LONG_SIZE;
final K key = deserializeFromDirectMemory(keySerializer, entryPosition);
entryPosition += getObjectSizeInDirectMemory(keySerializer, entryPosition);
final V value = deserializeFromDirectMemory(valueSerializer, entryPosition);
return new Entry<K, V>(key, value, hashCode);
}
public long getHashCode(int index) {
int entryPosition = getIntValue(POSITIONS_ARRAY_OFFSET + index * OIntegerSerializer.INT_SIZE);
return getLongValue(entryPosition);
}
public K getKey(int index) {
int entryPosition = getIntValue(POSITIONS_ARRAY_OFFSET + index * OIntegerSerializer.INT_SIZE);
return deserializeFromDirectMemory(keySerializer, entryPosition + OLongSerializer.LONG_SIZE);
}
/**
* Obtains the value stored under the given index in this bucket.
*
* @param index the value index.
*
* @return the obtained value.
*/
public V getValue(int index) {
int entryPosition = getIntValue(POSITIONS_ARRAY_OFFSET + index * OIntegerSerializer.INT_SIZE);
// skip hash code
entryPosition += OLongSerializer.LONG_SIZE;
// skip key
entryPosition += getObjectSizeInDirectMemory(keySerializer, entryPosition);
return deserializeFromDirectMemory(valueSerializer, entryPosition);
}
public int getIndex(final long hashCode, final K key) {
return binarySearch(key, hashCode);
}
public int size() {
return getIntValue(SIZE_OFFSET);
}
public Iterator<Entry<K, V>> iterator() {
return new EntryIterator(0);
}
public Iterator<Entry<K, V>> iterator(int index) {
return new EntryIterator(index);
}
public int mergedSize(OHashIndexBucket buddyBucket) {
return POSITIONS_ARRAY_OFFSET + size() * OIntegerSerializer.INT_SIZE + (MAX_BUCKET_SIZE_BYTES - getIntValue(
FREE_POINTER_OFFSET)) + buddyBucket.size() * OIntegerSerializer.INT_SIZE + (MAX_BUCKET_SIZE_BYTES - getIntValue(
FREE_POINTER_OFFSET));
}
public int getContentSize() {
return POSITIONS_ARRAY_OFFSET + size() * OIntegerSerializer.INT_SIZE + (MAX_BUCKET_SIZE_BYTES - getIntValue(
FREE_POINTER_OFFSET));
}
public int updateEntry(int index, V value) throws IOException {
int entryPosition = getIntValue(POSITIONS_ARRAY_OFFSET + index * OIntegerSerializer.INT_SIZE);
entryPosition += OLongSerializer.LONG_SIZE;
entryPosition += getObjectSizeInDirectMemory(keySerializer, entryPosition);
final int newSize = valueSerializer.getObjectSize(value);
final int oldSize = getObjectSizeInDirectMemory(valueSerializer, entryPosition);
if (newSize != oldSize)
return -1;
byte[] newSerializedValue = new byte[newSize];
valueSerializer.serializeNativeObject(value, newSerializedValue, 0);
byte[] oldSerializedValue = getBinaryValue(entryPosition, oldSize);
if (ODefaultComparator.INSTANCE.compare(oldSerializedValue, newSerializedValue) == 0)
return 0;
setBinaryValue(entryPosition, newSerializedValue);
return 1;
}
public Entry<K, V> deleteEntry(int index) throws IOException {
final Entry<K, V> removedEntry = getEntry(index);
final int freePointer = getIntValue(FREE_POINTER_OFFSET);
final int positionOffset = POSITIONS_ARRAY_OFFSET + index * OIntegerSerializer.INT_SIZE;
final int entryPosition = getIntValue(positionOffset);
final int keySize = getObjectSizeInDirectMemory(keySerializer, entryPosition + OLongSerializer.LONG_SIZE);
final int ridSize = getObjectSizeInDirectMemory(valueSerializer, entryPosition + keySize + OLongSerializer.LONG_SIZE);
final int entrySize = keySize + ridSize + OLongSerializer.LONG_SIZE;
moveData(positionOffset + OIntegerSerializer.INT_SIZE, positionOffset,
size() * OIntegerSerializer.INT_SIZE - (index + 1) * OIntegerSerializer.INT_SIZE);
if (entryPosition > freePointer)
moveData(freePointer, freePointer + entrySize, entryPosition - freePointer);
int currentPositionOffset = POSITIONS_ARRAY_OFFSET;
int size = size();
for (int i = 0; i < size - 1; i++) {
int currentEntryPosition = getIntValue(currentPositionOffset);
if (currentEntryPosition < entryPosition)
setIntValue(currentPositionOffset, currentEntryPosition + entrySize);
currentPositionOffset += OIntegerSerializer.INT_SIZE;
}
setIntValue(FREE_POINTER_OFFSET, freePointer + entrySize);
setIntValue(SIZE_OFFSET, size - 1);
return removedEntry;
}
public boolean addEntry(long hashCode, K key, V value) throws IOException {
int entreeSize =
keySerializer.getObjectSize(key, (Object[]) keyTypes) + valueSerializer.getObjectSize(value) + OLongSerializer.LONG_SIZE;
int freePointer = getIntValue(FREE_POINTER_OFFSET);
int size = size();
if (freePointer - entreeSize < POSITIONS_ARRAY_OFFSET + (size + 1) * OIntegerSerializer.INT_SIZE)
return false;
final int index = binarySearch(key, hashCode);
if (index >= 0)
throw new IllegalArgumentException("Given value is present in bucket.");
final int insertionPoint = -index - 1;
insertEntry(hashCode, key, value, insertionPoint, entreeSize);
return true;
}
private void insertEntry(long hashCode, K key, V value, int insertionPoint, int entreeSize) throws IOException {
int freePointer = getIntValue(FREE_POINTER_OFFSET);
int size = size();
final int positionsOffset = insertionPoint * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET;
moveData(positionsOffset, positionsOffset + OIntegerSerializer.INT_SIZE,
size() * OIntegerSerializer.INT_SIZE - insertionPoint * OIntegerSerializer.INT_SIZE);
final int entreePosition = freePointer - entreeSize;
setIntValue(positionsOffset, entreePosition);
serializeEntry(hashCode, key, value, entreePosition);
setIntValue(FREE_POINTER_OFFSET, entreePosition);
setIntValue(SIZE_OFFSET, size + 1);
}
public void appendEntry(long hashCode, K key, V value) throws IOException {
final int positionsOffset = size() * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET;
final int entreeSize =
keySerializer.getObjectSize(key, (Object[]) keyTypes) + valueSerializer.getObjectSize(value) + OLongSerializer.LONG_SIZE;
final int freePointer = getIntValue(FREE_POINTER_OFFSET);
final int entreePosition = freePointer - entreeSize;
setIntValue(positionsOffset, entreePosition);
serializeEntry(hashCode, key, value, entreePosition);
setIntValue(FREE_POINTER_OFFSET, freePointer - entreeSize);
setIntValue(SIZE_OFFSET, size() + 1);
}
private void serializeEntry(long hashCode, K key, V value, int entryOffset) throws IOException {
setLongValue(entryOffset, hashCode);
entryOffset += OLongSerializer.LONG_SIZE;
final int keySize = keySerializer.getObjectSize(key, (Object[]) keyTypes);
byte[] binaryKey = new byte[keySize];
keySerializer.serializeNativeObject(key, binaryKey, 0, (Object[]) keyTypes);
setBinaryValue(entryOffset, binaryKey);
entryOffset += keySize;
final int valueSize = valueSerializer.getObjectSize(value);
final byte[] binaryValue = new byte[valueSize];
valueSerializer.serializeNativeObject(value, binaryValue, 0);
setBinaryValue(entryOffset, binaryValue);
}
public int getDepth() {
return getByteValue(DEPTH_OFFSET);
}
public void setDepth(int depth) {
setByteValue(DEPTH_OFFSET, (byte) depth);
}
public long getNextRemovedBucketPair() {
return getLongValue(NEXT_REMOVED_BUCKET_OFFSET);
}
public void setNextRemovedBucketPair(long nextRemovedBucketPair) throws IOException {
setLongValue(NEXT_REMOVED_BUCKET_OFFSET, nextRemovedBucketPair);
}
public long getSplitHistory(int level) {
return getLongValue(HISTORY_OFFSET + OLongSerializer.LONG_SIZE * level);
}
public void setSplitHistory(int level, long position) throws IOException {
setLongValue(HISTORY_OFFSET + OLongSerializer.LONG_SIZE * level, position);
}
public static class Entry<K, V> {
public final K key;
public final V value;
public final long hashCode;
public Entry(K key, V value, long hashCode) {
this.key = key;
this.value = value;
this.hashCode = hashCode;
}
}
private final class EntryIterator implements Iterator<Entry<K, V>> {
private int currentIndex;
private EntryIterator(int currentIndex) {
this.currentIndex = currentIndex;
}
@Override
public boolean hasNext() {
return currentIndex < size();
}
@Override
public Entry<K, V> next() {
if (currentIndex >= size())
throw new NoSuchElementException("Iterator was reached last element");
final Entry<K, V> entry = getEntry(currentIndex);
currentIndex++;
return entry;
}
@Override
public void remove() {
throw new UnsupportedOperationException("Remove operation is not supported");
}
}
}