/*
 *
 *  *  Copyright 2010-2016 OrientDB LTD (http://orientdb.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://orientdb.com
 *
 */

package com.orientechnologies.orient.core.storage.index.sbtree.multivalue.v1;

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.common.serialization.types.OShortSerializer;
import com.orientechnologies.orient.core.encryption.OEncryption;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.id.ORecordId;
import com.orientechnologies.orient.core.storage.cache.OCacheEntry;
import com.orientechnologies.orient.core.storage.impl.local.paginated.base.ODurablePage;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ConcurrentSkipListMap;

/**
 * @author Andrey Lomakin (a.lomakin-at-orientdb.com)
 * @since 8/7/13
 */
final class Bucket<K> extends ODurablePage {
  private static final int RID_SIZE                        = OShortSerializer.SHORT_SIZE + OLongSerializer.LONG_SIZE;
  private static final int SINGLE_ELEMENT_LINKED_ITEM_SIZE = OIntegerSerializer.INT_SIZE + RID_SIZE + OByteSerializer.BYTE_SIZE;

  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 POSITIONS_ARRAY_OFFSET = RIGHT_SIBLING_OFFSET + OLongSerializer.LONG_SIZE;

  private final boolean isLeaf;

  private final OBinarySerializer<K> keySerializer;

  private final Comparator<? super K> comparator = ODefaultComparator.INSTANCE;

  private final OEncryption encryption;

  Bucket(final OCacheEntry cacheEntry, final boolean isLeaf, final OBinarySerializer<K> keySerializer,
      final OEncryption encryption) {
    super(cacheEntry);

    this.isLeaf = isLeaf;
    this.keySerializer = keySerializer;
    this.encryption = encryption;

    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);
  }

  Bucket(final OCacheEntry cacheEntry, final OBinarySerializer<K> keySerializer, final OEncryption encryption) {
    super(cacheEntry);
    this.encryption = encryption;

    this.isLeaf = getByteValue(IS_LEAF_OFFSET) > 0;
    this.keySerializer = keySerializer;
  }

  boolean isEmpty() {
    return size() == 0;
  }

  int find(final K key) {
    int low = 0;
    int high = size() - 1;

    while (low <= high) {
      final int mid = (low + high) >>> 1;
      final K midVal = getKey(mid);
      final 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.
  }

  int remove(final int entryIndex) {
    assert isLeaf;

    final int entryPosition = getIntValue(POSITIONS_ARRAY_OFFSET + entryIndex * OIntegerSerializer.INT_SIZE);

    int position = entryPosition;
    int nextItem = getIntValue(position);
    position += OIntegerSerializer.INT_SIZE;

    final int keySize;
    if (encryption == null) {
      keySize = getObjectSizeInDirectMemory(keySerializer, position);
    } else {
      final int encryptedSize = getIntValue(position);
      keySize = encryptedSize + OIntegerSerializer.INT_SIZE;
    }

    if (nextItem == -1) {
      removeMainEntry(entryIndex, entryPosition, keySize);

      return 1;
    }

    final List<Integer> itemsToRemove = new ArrayList<>(8);
    final List<Integer> itemsToRemoveSize = new ArrayList<>(8);

    final int entrySize = keySize + OIntegerSerializer.INT_SIZE + RID_SIZE;
    int totalSpace = entrySize;

    while (nextItem > 0) {
      final int arraySize = 0xFF & getByteValue(nextItem + OIntegerSerializer.INT_SIZE);
      final int itemSize = arraySize * RID_SIZE + OIntegerSerializer.INT_SIZE + OByteSerializer.BYTE_SIZE;
      totalSpace += itemSize;

      itemsToRemove.add(nextItem);
      itemsToRemoveSize.add(itemSize);

      nextItem = getIntValue(nextItem);
    }

    int size = getIntValue(SIZE_OFFSET);

    final TreeMap<Integer, Integer> entries = new TreeMap<>();
    @SuppressWarnings("SpellCheckingInspection")
    final ConcurrentSkipListMap<Integer, Integer> nexts = new ConcurrentSkipListMap<>();

    int currentPositionOffset = POSITIONS_ARRAY_OFFSET;

    for (int i = 0; i < size; i++) {
      if (i != entryIndex) {
        final int currentEntryPosition = getIntValue(currentPositionOffset);
        final int currentNextPosition = getIntValue(currentEntryPosition);

        entries.put(currentEntryPosition, currentPositionOffset);
        if (currentNextPosition > 0) {
          nexts.put(currentNextPosition, currentEntryPosition);
        }
      }

      currentPositionOffset += OIntegerSerializer.INT_SIZE;
    }

    int freeSpacePointer = getIntValue(FREE_POINTER_OFFSET);

    int clearedSpace = 0;
    int counter = 0;

    for (final int itemToRemove : itemsToRemove) {
      final int itemSize = itemsToRemoveSize.get(counter);

      if (itemToRemove > freeSpacePointer) {
        moveData(freeSpacePointer, freeSpacePointer + itemSize, itemToRemove - freeSpacePointer);
        final int diff = totalSpace - clearedSpace;

        final SortedMap<Integer, Integer> entriesRefToCorrect = entries.headMap(itemToRemove);
        for (final Map.Entry<Integer, Integer> entry : entriesRefToCorrect.entrySet()) {
          final int currentEntryOffset = entry.getValue();
          final int currentEntryPosition = entry.getKey();

          setIntValue(currentEntryOffset, currentEntryPosition + diff);
        }

        entriesRefToCorrect.clear();

        final SortedMap<Integer, Integer> linkRefToCorrect = nexts.headMap(itemToRemove);
        for (final Map.Entry<Integer, Integer> entry : linkRefToCorrect.entrySet()) {
          final int first = entry.getKey();
          final int currentEntryPosition = entry.getValue();

          if (first < itemToRemove) {
            if (currentEntryPosition > 0) {
              final int updatedEntryPosition;

              if (currentEntryPosition < itemToRemove) {
                final int itemsBefore = -Collections.binarySearch(itemsToRemove, currentEntryPosition) - 1;

                if (counter >= itemsBefore) {
                  updatedEntryPosition = currentEntryPosition + itemsToRemoveSize.subList(itemsBefore, counter + 1).stream()
                      .mapToInt(Integer::intValue).sum();
                } else {
                  updatedEntryPosition = currentEntryPosition;
                }
              } else {
                updatedEntryPosition = currentEntryPosition;
              }

              setIntValue(updatedEntryPosition, first + diff);
            } else {
              final int compositeEntryPosition = -currentEntryPosition;
              final int prevCounter = compositeEntryPosition >>> 16;
              final int prevEntryPosition = compositeEntryPosition & 0xFFFF;

              final int updatedEntryPosition =
                  prevEntryPosition + itemsToRemoveSize.subList(prevCounter + 1, counter + 1).stream().mapToInt(Integer::intValue)
                      .sum();//offset of removal of prevCounter item already taken into account

              setIntValue(updatedEntryPosition, first + diff);
            }
          }

          final int[] lastEntry = incrementalUpdateAllLinkedListReferences(first, itemSize, itemToRemove, diff);

          if (lastEntry[1] > 0) {
            nexts.put(lastEntry[1], -((counter << 16) | lastEntry[0]));
          }
        }

        linkRefToCorrect.clear();
      }

      clearedSpace += itemsToRemoveSize.get(counter);
      counter++;

      freeSpacePointer += itemSize;
    }

    if (entryPosition > freeSpacePointer) {
      moveData(freeSpacePointer, freeSpacePointer + entrySize, entryPosition - freeSpacePointer);

      @SuppressWarnings("UnnecessaryLocalVariable")
      final int diff = entrySize;

      final SortedMap<Integer, Integer> entriesRefToCorrect = entries.headMap(entryPosition);
      for (final Map.Entry<Integer, Integer> entry : entriesRefToCorrect.entrySet()) {
        final int currentEntryOffset = entry.getValue();
        final int currentEntryPosition = entry.getKey();

        setIntValue(currentEntryOffset, currentEntryPosition + diff);
      }

      final SortedMap<Integer, Integer> linkRefToCorrect = nexts.headMap(entryPosition);
      for (final Map.Entry<Integer, Integer> entry : linkRefToCorrect.entrySet()) {
        final int first = entry.getKey();
        final int currentEntryPosition = entry.getValue();

        if (currentEntryPosition > 0) {
          int updatedEntryPosition;

          final int itemsBefore = -Collections.binarySearch(itemsToRemove, currentEntryPosition) - 1;
          if (itemsToRemove.size() > itemsBefore) {
            updatedEntryPosition = currentEntryPosition + itemsToRemoveSize.subList(itemsBefore, itemsToRemove.size()).stream()
                .mapToInt(Integer::intValue).sum();
          } else {
            updatedEntryPosition = currentEntryPosition;
          }

          if (currentEntryPosition < entryPosition) {
            updatedEntryPosition += entrySize;
          }

          setIntValue(updatedEntryPosition, first + diff);
        } else {
          final int compositeEntryPosition = -currentEntryPosition;
          final int prevCounter = compositeEntryPosition >>> 16;
          final int prevEntryPosition = compositeEntryPosition & 0xFFFF;

          final int updatedEntryPosition =
              entrySize + itemsToRemoveSize.subList(prevCounter + 1, itemsToRemove.size()).stream().mapToInt(Integer::intValue)
                  .sum() + prevEntryPosition;

          setIntValue(updatedEntryPosition, first + diff);
        }

        updateAllLinkedListReferences(first, entryPosition, diff);
      }
    }

    freeSpacePointer += entrySize;

    setIntValue(FREE_POINTER_OFFSET, freeSpacePointer);

    if (entryIndex < size) {
      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);

    return itemsToRemove.size() + 1;
  }

  private void removeMainEntry(final int entryIndex, final int entryPosition, final int keySize) {
    int nextItem;
    int size = getIntValue(SIZE_OFFSET);

    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);

    final int freePointer = getIntValue(FREE_POINTER_OFFSET);
    final int entrySize = OIntegerSerializer.INT_SIZE + RID_SIZE + keySize;

    boolean moved = false;
    if (size > 0 && entryPosition > freePointer) {
      moveData(freePointer, freePointer + entrySize, entryPosition - freePointer);
      moved = true;
    }

    setIntValue(FREE_POINTER_OFFSET, freePointer + entrySize);

    if (moved) {
      int currentPositionOffset = POSITIONS_ARRAY_OFFSET;

      for (int i = 0; i < size; i++) {
        final int currentEntryPosition = getIntValue(currentPositionOffset);
        final int updatedEntryPosition;

        if (currentEntryPosition < entryPosition) {
          updatedEntryPosition = currentEntryPosition + entrySize;
          setIntValue(currentPositionOffset, updatedEntryPosition);
        } else {
          updatedEntryPosition = currentEntryPosition;
        }

        nextItem = getIntValue(updatedEntryPosition);
        if (nextItem > 0 && nextItem < entryPosition) {
          //update reference to the first item of linked list
          setIntValue(updatedEntryPosition, nextItem + entrySize);

          updateAllLinkedListReferences(nextItem, entryPosition, entrySize);
        }

        currentPositionOffset += OIntegerSerializer.INT_SIZE;
      }

    }
  }

  boolean remove(final int entryIndex, final ORID value) {
    assert isLeaf;

    final int entryPosition = getIntValue(POSITIONS_ARRAY_OFFSET + entryIndex * OIntegerSerializer.INT_SIZE);

    int position = entryPosition;
    int nextItem = getIntValue(position);
    position += OIntegerSerializer.INT_SIZE;

    final int keySize;
    if (encryption == null) {
      keySize = getObjectSizeInDirectMemory(keySerializer, position);
    } else {
      final int encryptedSize = getIntValue(position);
      keySize = encryptedSize + OIntegerSerializer.INT_SIZE;
    }

    position += keySize;

    //only single element in list
    if (nextItem == -1) {
      final int clusterId = getShortValue(position);
      if (clusterId != value.getClusterId()) {
        return false;
      }

      position += OShortSerializer.SHORT_SIZE;

      final long clusterPosition = getLongValue(position);
      if (clusterPosition == value.getClusterPosition()) {
        removeMainEntry(entryIndex, entryPosition, keySize);
        return true;
      }
    } else {
      int clusterId = getShortValue(position);
      long clusterPosition = getLongValue(position + OShortSerializer.SHORT_SIZE);

      if (clusterId == value.getClusterId() && clusterPosition == value.getClusterPosition()) {
        final int nextNextItem = getIntValue(nextItem);
        final int nextItemSize = 0xFF & getByteValue(nextItem + OIntegerSerializer.INT_SIZE);

        final byte[] nextValue = getBinaryValue(nextItem + OIntegerSerializer.INT_SIZE + OByteSerializer.BYTE_SIZE, RID_SIZE);

        assert nextItemSize > 0;
        final int freePointer = getIntValue(FREE_POINTER_OFFSET);
        if (nextItemSize == 1) {
          setIntValue(entryPosition, nextNextItem);
          setIntValue(FREE_POINTER_OFFSET, freePointer + OIntegerSerializer.INT_SIZE + RID_SIZE + OByteSerializer.BYTE_SIZE);
        } else {
          setByteValue(nextItem + OIntegerSerializer.INT_SIZE, (byte) (nextItemSize - 1));
          setIntValue(FREE_POINTER_OFFSET, freePointer + RID_SIZE);
        }

        setBinaryValue(entryPosition + OIntegerSerializer.INT_SIZE + keySize, nextValue);

        if (nextItem > freePointer || nextItemSize > 1) {
          if (nextItemSize == 1) {
            moveData(freePointer, freePointer + SINGLE_ELEMENT_LINKED_ITEM_SIZE, nextItem - freePointer);
          } else {
            moveData(freePointer, freePointer + RID_SIZE,
                nextItem + OIntegerSerializer.INT_SIZE + OByteSerializer.BYTE_SIZE - freePointer);
          }

          final int diff = nextItemSize > 1 ? RID_SIZE : OIntegerSerializer.INT_SIZE + OByteSerializer.BYTE_SIZE + RID_SIZE;

          final int size = getIntValue(SIZE_OFFSET);
          int currentPositionOffset = POSITIONS_ARRAY_OFFSET;

          for (int i = 0; i < size; i++) {
            final int currentEntryPosition = getIntValue(currentPositionOffset);
            final int updatedEntryPosition;

            if (currentEntryPosition < nextItem) {
              updatedEntryPosition = currentEntryPosition + diff;
              setIntValue(currentPositionOffset, updatedEntryPosition);
            } else {
              updatedEntryPosition = currentEntryPosition;
            }

            final int currentNextItem = getIntValue(updatedEntryPosition);
            if (currentNextItem > 0 && currentNextItem < nextItem + diff) {
              //update reference to the first item of linked list
              setIntValue(updatedEntryPosition, currentNextItem + diff);

              updateAllLinkedListReferences(currentNextItem, nextItem + diff, diff);
            }

            currentPositionOffset += OIntegerSerializer.INT_SIZE;
          }
        }

        return true;
      } else {
        int prevItem = entryPosition;

        while (nextItem > 0) {
          final int nextNextItem = getIntValue(nextItem);
          final int nextItemSize = 0xFF & getByteValue(nextItem + OIntegerSerializer.INT_SIZE);

          if (nextItemSize == 1) {
            clusterId = getShortValue(nextItem + OIntegerSerializer.INT_SIZE + OByteSerializer.BYTE_SIZE);
            clusterPosition = getLongValue(
                nextItem + OIntegerSerializer.INT_SIZE + OByteSerializer.BYTE_SIZE + OShortSerializer.SHORT_SIZE);

            if (clusterId == value.getClusterId() && clusterPosition == value.getClusterPosition()) {
              setIntValue(prevItem, nextNextItem);

              final int freePointer = getIntValue(FREE_POINTER_OFFSET);
              setIntValue(FREE_POINTER_OFFSET, freePointer + SINGLE_ELEMENT_LINKED_ITEM_SIZE);

              if (nextItem > freePointer) {
                moveData(freePointer, freePointer + SINGLE_ELEMENT_LINKED_ITEM_SIZE, nextItem - freePointer);

                final int size = getIntValue(SIZE_OFFSET);
                int currentPositionOffset = POSITIONS_ARRAY_OFFSET;

                for (int i = 0; i < size; i++) {
                  final int currentEntryPosition = getIntValue(currentPositionOffset);
                  final int updatedEntryPosition;

                  if (currentEntryPosition < nextItem) {
                    updatedEntryPosition = currentEntryPosition + SINGLE_ELEMENT_LINKED_ITEM_SIZE;
                    setIntValue(currentPositionOffset, updatedEntryPosition);
                  } else {
                    updatedEntryPosition = currentEntryPosition;
                  }

                  final int currentNextItem = getIntValue(updatedEntryPosition);
                  if (currentNextItem > 0 && currentNextItem < nextItem) {
                    //update reference to the first item of linked list
                    setIntValue(updatedEntryPosition, currentNextItem + SINGLE_ELEMENT_LINKED_ITEM_SIZE);

                    updateAllLinkedListReferences(currentNextItem, nextItem, SINGLE_ELEMENT_LINKED_ITEM_SIZE);
                  }

                  currentPositionOffset += OIntegerSerializer.INT_SIZE;
                }
              }

              return true;
            }
          } else {
            for (int i = 0; i < nextItemSize; i++) {
              clusterId = getShortValue(nextItem + OIntegerSerializer.INT_SIZE + OByteSerializer.BYTE_SIZE + i * RID_SIZE);
              clusterPosition = getLongValue(
                  nextItem + OIntegerSerializer.INT_SIZE + OShortSerializer.SHORT_SIZE + OByteSerializer.BYTE_SIZE + i * RID_SIZE);

              if (clusterId == value.getClusterId() && clusterPosition == value.getClusterPosition()) {
                final int freePointer = getIntValue(FREE_POINTER_OFFSET);
                setIntValue(FREE_POINTER_OFFSET, freePointer + RID_SIZE);

                setByteValue(nextItem + OIntegerSerializer.INT_SIZE, (byte) (nextItemSize - 1));

                moveData(freePointer, freePointer + RID_SIZE,
                    nextItem + OIntegerSerializer.INT_SIZE + OByteSerializer.BYTE_SIZE + i * RID_SIZE - freePointer);

                final int size = getIntValue(SIZE_OFFSET);
                int currentPositionOffset = POSITIONS_ARRAY_OFFSET;

                for (int n = 0; n < size; n++) {
                  final int currentEntryPosition = getIntValue(currentPositionOffset);
                  final int updatedEntryPosition;

                  if (currentEntryPosition < nextItem) {
                    updatedEntryPosition = currentEntryPosition + RID_SIZE;
                    setIntValue(currentPositionOffset, updatedEntryPosition);
                  } else {
                    updatedEntryPosition = currentEntryPosition;
                  }

                  final int currentNextItem = getIntValue(updatedEntryPosition);
                  if (currentNextItem > 0 && currentNextItem < nextItem + RID_SIZE) {
                    //update reference to the first item of linked list
                    setIntValue(updatedEntryPosition, currentNextItem + RID_SIZE);

                    updateAllLinkedListReferences(currentNextItem, nextItem + RID_SIZE, RID_SIZE);
                  }

                  currentPositionOffset += OIntegerSerializer.INT_SIZE;
                }

                return true;
              }
            }
          }

          prevItem = nextItem;
          nextItem = nextNextItem;
        }
      }
    }

    return false;
  }

  private void updateAllLinkedListReferences(final int firstItem, final int boundary, final int diffSize) {
    int currentItem = firstItem + diffSize;

    while (true) {
      final int nextItem = getIntValue(currentItem);

      if (nextItem > 0 && nextItem < boundary) {
        setIntValue(currentItem, nextItem + diffSize);
        currentItem = nextItem + diffSize;
      } else {
        return;
      }
    }
  }

  private int[] incrementalUpdateAllLinkedListReferences(final int firstItem, final int itemSize, final int boundary,
      final int diffSize) {
    int currentItem = firstItem + itemSize;

    while (true) {
      final int nextItem = getIntValue(currentItem);

      if (nextItem > 0 && nextItem < boundary) {
        setIntValue(currentItem, nextItem + diffSize);
        currentItem = nextItem + itemSize;
      } else {
        return new int[] { currentItem, nextItem };
      }
    }
  }

  public int size() {
    return getIntValue(SIZE_OFFSET);
  }

  LeafEntry getLeafEntry(final int entryIndex) {
    assert isLeaf;

    int entryPosition = getIntValue(entryIndex * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET);

    final byte[] key;
    int nextItem = getIntValue(entryPosition);
    entryPosition += OIntegerSerializer.INT_SIZE;

    if (encryption == null) {
      final int keySize = getObjectSizeInDirectMemory(keySerializer, entryPosition);
      key = getBinaryValue(entryPosition, keySize);

      entryPosition += keySize;
    } else {
      final int encryptionSize = getIntValue(entryPosition);
      key = getBinaryValue(entryPosition, encryptionSize + OIntegerSerializer.INT_SIZE);

      entryPosition += encryptionSize + OIntegerSerializer.INT_SIZE;
    }

    final List<ORID> values = new ArrayList<>(8);

    int clusterId = getShortValue(entryPosition);
    entryPosition += OShortSerializer.SHORT_SIZE;

    long clusterPosition = getLongValue(entryPosition);

    values.add(new ORecordId(clusterId, clusterPosition));

    while (nextItem > 0) {
      final int nextNextItem = getIntValue(nextItem);
      final int nextItemSize = 0xFF & getByteValue(nextItem + OIntegerSerializer.INT_SIZE);

      for (int i = 0; i < nextItemSize; i++) {
        clusterId = getShortValue(nextItem + OIntegerSerializer.INT_SIZE + OByteSerializer.BYTE_SIZE + i * RID_SIZE);
        clusterPosition = getLongValue(
            nextItem + OShortSerializer.SHORT_SIZE + OIntegerSerializer.INT_SIZE + OByteSerializer.BYTE_SIZE + i * RID_SIZE);

        values.add(new ORecordId(clusterId, clusterPosition));
      }

      nextItem = nextNextItem;
    }

    return new LeafEntry(key, values);
  }

  NonLeafEntry getNonLeafEntry(final int entryIndex) {
    assert !isLeaf;

    int entryPosition = getIntValue(entryIndex * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET);

    final int leftChild = getIntValue(entryPosition);
    entryPosition += OIntegerSerializer.INT_SIZE;

    final int rightChild = getIntValue(entryPosition);
    entryPosition += OIntegerSerializer.INT_SIZE;

    final byte[] key;

    if (encryption == null) {
      final int keySize = getObjectSizeInDirectMemory(keySerializer, entryPosition);
      key = getBinaryValue(entryPosition, keySize);
    } else {
      final int encryptionSize = getIntValue(entryPosition);
      key = getBinaryValue(entryPosition, encryptionSize + OIntegerSerializer.INT_SIZE);
    }

    return new NonLeafEntry(key, leftChild, rightChild);
  }

  int getLeft(final int entryIndex) {
    assert !isLeaf;

    final int entryPosition = getIntValue(entryIndex * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET);

    return getIntValue(entryPosition);
  }

  int getRight(final int entryIndex) {
    assert !isLeaf;

    final int entryPosition = getIntValue(entryIndex * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET);

    return getIntValue(entryPosition + OIntegerSerializer.INT_SIZE);
  }

  /**
   * Obtains the value stored under the given entry index in this bucket.
   *
   * @param entryIndex the value entry index.
   *
   * @return the obtained value.
   */
  List<ORID> getValues(final int entryIndex) {
    assert isLeaf;

    int entryPosition = getIntValue(entryIndex * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET);
    int nextItem = getIntValue(entryPosition);
    entryPosition += OIntegerSerializer.INT_SIZE;

    // skip key
    if (encryption == null) {
      entryPosition += getObjectSizeInDirectMemory(keySerializer, entryPosition);
    } else {
      final int encryptedSize = getIntValue(entryPosition);
      entryPosition += OIntegerSerializer.INT_SIZE + encryptedSize;
    }

    int clusterId = getShortValue(entryPosition);
    long clusterPosition = getLongValue(entryPosition + OShortSerializer.SHORT_SIZE);

    final List<ORID> results = new ArrayList<>(8);
    results.add(new ORecordId(clusterId, clusterPosition));

    while (nextItem > 0) {
      final int nextNextItem = getIntValue(nextItem);
      final int nextItemSize = 0xFF & getByteValue(nextItem + OIntegerSerializer.INT_SIZE);

      for (int i = 0; i < nextItemSize; i++) {
        clusterId = getShortValue(nextItem + OIntegerSerializer.INT_SIZE + OByteSerializer.BYTE_SIZE + i * RID_SIZE);
        clusterPosition = getLongValue(
            nextItem + OIntegerSerializer.INT_SIZE + OShortSerializer.SHORT_SIZE + OByteSerializer.BYTE_SIZE + i * RID_SIZE);

        results.add(new ORecordId(clusterId, clusterPosition));
      }

      nextItem = nextNextItem;
    }

    return results;
  }

  public K getKey(final int index) {
    int entryPosition = getIntValue(index * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET);

    if (!isLeaf) {
      entryPosition += 2 * OIntegerSerializer.INT_SIZE;
    } else {
      entryPosition += OIntegerSerializer.INT_SIZE;
    }

    if (encryption == null) {
      return deserializeFromDirectMemory(keySerializer, entryPosition);
    } else {
      final int encryptedSize = getIntValue(entryPosition);
      entryPosition += OIntegerSerializer.INT_SIZE;

      final byte[] encryptedKey = getBinaryValue(entryPosition, encryptedSize);
      final byte[] serializedKey = encryption.decrypt(encryptedKey);
      return keySerializer.deserializeNativeObject(serializedKey, 0);
    }
  }

  byte[] getRawKey(final int index) {
    int entryPosition = getIntValue(index * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET);

    if (!isLeaf) {
      entryPosition += 2 * OIntegerSerializer.INT_SIZE;
    } else {
      entryPosition += OIntegerSerializer.INT_SIZE;
    }

    if (encryption == null) {
      final int keySize = getObjectSizeInDirectMemory(keySerializer, entryPosition);
      return getBinaryValue(entryPosition, keySize);
    } else {
      final int encryptedSize = getIntValue(entryPosition);
      return getBinaryValue(entryPosition, encryptedSize + OIntegerSerializer.INT_SIZE);
    }
  }

  public boolean isLeaf() {
    return isLeaf;
  }

  public void addAll(final List<Entry> entries) {
    if (!isLeaf) {
      for (int i = 0; i < entries.size(); i++) {
        final NonLeafEntry entry = (NonLeafEntry) entries.get(i);
        addNonLeafEntry(i, entry.key, entry.leftChild, entry.rightChild, false);
      }
    } else {
      for (int i = 0; i < entries.size(); i++) {
        final LeafEntry entry = (LeafEntry) entries.get(i);
        final byte[] key = entry.key;
        final List<ORID> values = entry.values;

        addNewLeafEntry(i, key, values.get(0));

        int n = 1;
        while (n < values.size()) {
          n += appendNewLeafEntries(i, values.subList(n, values.size()));
        }
      }
    }

    setIntValue(SIZE_OFFSET, entries.size());
  }

  public void shrink(final int newSize) {
    if (isLeaf) {
      final List<LeafEntry> entries = new ArrayList<>(newSize);

      for (int i = 0; i < newSize; i++) {
        entries.add(getLeafEntry(i));
      }

      setIntValue(FREE_POINTER_OFFSET, MAX_PAGE_SIZE_BYTES);

      int index = 0;
      for (final LeafEntry entry : entries) {
        final byte[] key = entry.key;
        final List<ORID> values = entry.values;

        addNewLeafEntry(index, key, values.get(0));

        int n = 1;
        while (n < values.size()) {
          n += appendNewLeafEntries(index, values.subList(n, values.size()));
        }
        index++;
      }

      setIntValue(SIZE_OFFSET, newSize);
    } else {
      final List<NonLeafEntry> entries = new ArrayList<>(newSize);

      for (int i = 0; i < newSize; i++) {
        entries.add(getNonLeafEntry(i));
      }

      setIntValue(FREE_POINTER_OFFSET, MAX_PAGE_SIZE_BYTES);

      int index = 0;
      for (final NonLeafEntry entry : entries) {
        addNonLeafEntry(index, entry.key, entry.leftChild, entry.rightChild, false);
        index++;
      }

      setIntValue(SIZE_OFFSET, newSize);
    }
  }

  void cutSingleEntry(final int amountItemsToRemove) {
    assert size() == 1;

    final int entryPosition = getIntValue(POSITIONS_ARRAY_OFFSET);
    final List<Integer> items = new ArrayList<>(8);
    final List<Integer> itemSizes = new ArrayList<>(8);

    {
      int nextItem = getIntValue(entryPosition);

      while (true) {
        final int nextNextItem = getIntValue(nextItem);
        final int nextItemSize = (0xFF & getByteValue(nextItem + OIntegerSerializer.INT_SIZE));

        itemSizes.add(nextItemSize);
        items.add(nextItem);

        if (nextNextItem == -1) {
          break;
        }

        nextItem = nextNextItem;
      }
    }

    int halfIndex = -1;
    int lastEntrySize = -1;

    int currentSize = 1;

    for (int i = 0; i < itemSizes.size(); i++) {
      final int itemSize = itemSizes.get(i);
      currentSize += itemSize;

      if (currentSize >= amountItemsToRemove) {
        halfIndex = i;
        lastEntrySize = currentSize - amountItemsToRemove;
        break;
      }
    }

    assert halfIndex >= 0;
    assert lastEntrySize >= 0;

    final List<Integer> itemsToRemove;

    final byte[] firstRid;
    if (lastEntrySize == 0) {
      itemsToRemove = items.subList(1, halfIndex + 1);
      final int lastItemPos = items.get(halfIndex + 1);

      firstRid = getBinaryValue(lastItemPos + OIntegerSerializer.INT_SIZE + OByteSerializer.BYTE_SIZE, RID_SIZE);
    } else {
      itemsToRemove = items.subList(1, halfIndex);
      final int lastItemPos = items.get(halfIndex);

      firstRid = getBinaryValue(
          lastItemPos + OIntegerSerializer.INT_SIZE + OByteSerializer.BYTE_SIZE + RID_SIZE * (itemSizes.get(halfIndex)
              - lastEntrySize), RID_SIZE);
    }

    int freePointer = getIntValue(FREE_POINTER_OFFSET);

    for (int i = 0; i < itemsToRemove.size(); i++) {
      final int itemPos = itemsToRemove.get(i);
      final int itemSize = itemSizes.get(i);
      final int sizeDiff = OIntegerSerializer.INT_SIZE + OByteSerializer.BYTE_SIZE + RID_SIZE * itemSize;

      if (itemPos > freePointer) {
        moveData(freePointer, freePointer + sizeDiff, itemPos - freePointer);
      }

      freePointer += sizeDiff;
    }

    final int nextFirsItem;
    {
      if (lastEntrySize == 1) {
        final int itemPos = items.get(halfIndex);
        nextFirsItem = getIntValue(itemPos);

        final int itemSize = 0xFF & getByteValue(itemPos + OIntegerSerializer.INT_SIZE);
        final int sizeDiff = OIntegerSerializer.INT_SIZE + OByteSerializer.BYTE_SIZE + RID_SIZE * itemSize;

        if (itemPos > freePointer) {
          moveData(freePointer, freePointer + sizeDiff, itemPos - freePointer);
        }

        freePointer += sizeDiff;

      } else if (lastEntrySize > 1) {
        final int itemPos = items.get(halfIndex);
        final int oldSize = 0xFF & getByteValue(itemPos + OIntegerSerializer.INT_SIZE);
        final int newSize = lastEntrySize - 1;

        setByteValue(itemPos + OIntegerSerializer.INT_SIZE, (byte) newSize);

        final int spaceDiff = RID_SIZE * (oldSize - newSize);
        moveData(freePointer, freePointer + spaceDiff,
            itemPos + OIntegerSerializer.INT_SIZE + OByteSerializer.BYTE_SIZE - freePointer);

        nextFirsItem = itemPos + spaceDiff;
        freePointer += spaceDiff;
      } else {
        final int itemPos = items.get(halfIndex + 1);
        final int itemSize = 0xFF & getByteValue(itemPos + OIntegerSerializer.INT_SIZE);

        if (itemSize == 1) {
          nextFirsItem = getIntValue(itemPos);
          final int sizeDiff = OIntegerSerializer.INT_SIZE + OByteSerializer.BYTE_SIZE + RID_SIZE;

          if (itemPos > freePointer) {
            moveData(freePointer, freePointer + sizeDiff, itemPos - freePointer);
          }

          freePointer += sizeDiff;
        } else {
          final int newSize = itemSize - 1;

          setByteValue(itemPos + OIntegerSerializer.INT_SIZE, (byte) newSize);

          moveData(freePointer, freePointer + RID_SIZE,
              itemPos + OIntegerSerializer.INT_SIZE + OByteSerializer.BYTE_SIZE - freePointer);

          nextFirsItem = itemPos + RID_SIZE;
          freePointer += RID_SIZE;
        }
      }
    }

    setIntValue(FREE_POINTER_OFFSET, freePointer);

    setIntValue(entryPosition, nextFirsItem);

    final int keySize;
    if (encryption == null) {
      keySize = getObjectSizeInDirectMemory(keySerializer, entryPosition + OIntegerSerializer.INT_SIZE);
    } else {
      final int encryptedSize = getIntValue(entryPosition + OIntegerSerializer.INT_SIZE);
      keySize = OIntegerSerializer.INT_SIZE + encryptedSize;
    }

    setBinaryValue(entryPosition + OIntegerSerializer.INT_SIZE + keySize, firstRid);
  }

  boolean addNewLeafEntry(final int index, final byte[] serializedKey, final ORID value) {
    assert isLeaf;

    final int entrySize = serializedKey.length + RID_SIZE + OIntegerSerializer.INT_SIZE; //next item pointer at the begging of entry
    final int size = getIntValue(SIZE_OFFSET);

    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);

    freePointer += setIntValue(freePointer, -1); //next item pointer
    freePointer += setBinaryValue(freePointer, serializedKey);//key
    freePointer += setShortValue(freePointer, (short) value.getClusterId());//rid
    setLongValue(freePointer, value.getClusterPosition());

    return true;
  }

  boolean appendNewLeafEntry(final int index, final ORID value) {
    assert isLeaf;

    final int itemSize = OIntegerSerializer.INT_SIZE + RID_SIZE + OByteSerializer.BYTE_SIZE;//next item pointer + RID + size
    int freePointer = getIntValue(FREE_POINTER_OFFSET);

    final int size = getIntValue(SIZE_OFFSET);

    if (freePointer - itemSize < size * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET) {
      return false;
    }

    freePointer -= itemSize;
    setIntValue(FREE_POINTER_OFFSET, freePointer);

    final int entryPosition = getIntValue(index * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET);
    final int nextItem = getIntValue(entryPosition);

    setIntValue(entryPosition, freePointer);//update list header

    freePointer += setIntValue(freePointer, nextItem);//next item pointer
    freePointer += setByteValue(freePointer, (byte) 1);//size
    freePointer += setShortValue(freePointer, (short) value.getClusterId());//rid
    setLongValue(freePointer, value.getClusterPosition());

    return true;
  }

  private int appendNewLeafEntries(final int index, final List<ORID> values) {
    assert isLeaf;

    final int listSize = Math.min(values.size(), 255);
    final int itemSize =
        OIntegerSerializer.INT_SIZE + RID_SIZE * listSize + OByteSerializer.BYTE_SIZE;//next item pointer + RIDs + size

    int freePointer = getIntValue(FREE_POINTER_OFFSET);

    final int size = getIntValue(SIZE_OFFSET);

    if (freePointer - itemSize < size * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET) {
      return -1;
    }

    freePointer -= itemSize;
    setIntValue(FREE_POINTER_OFFSET, freePointer);

    final int entryPosition = getIntValue(index * OIntegerSerializer.INT_SIZE + POSITIONS_ARRAY_OFFSET);
    final int nextItem = getIntValue(entryPosition);

    setIntValue(entryPosition, freePointer);//update list header

    freePointer += setIntValue(freePointer, nextItem);//next item pointer
    freePointer += setByteValue(freePointer, (byte) listSize);

    for (int i = 0; i < listSize; i++) {
      final ORID rid = values.get(i);

      freePointer += setShortValue(freePointer, (short) rid.getClusterId());
      freePointer += setLongValue(freePointer, rid.getClusterPosition());
    }

    return listSize;
  }

  boolean addNonLeafEntry(final int index, final byte[] serializedKey, final int leftChild, final int rightChild,
      final boolean updateNeighbors) {
    assert !isLeaf;

    final int entrySize = serializedKey.length + 2 * OIntegerSerializer.INT_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);

    freePointer += setIntValue(freePointer, leftChild);
    freePointer += setIntValue(freePointer, rightChild);

    setBinaryValue(freePointer, serializedKey);

    size++;

    if (updateNeighbors && size > 1) {
      if (index < size - 1) {
        final int nextEntryPosition = getIntValue(POSITIONS_ARRAY_OFFSET + (index + 1) * OIntegerSerializer.INT_SIZE);
        setIntValue(nextEntryPosition, rightChild);
      }

      if (index > 0) {
        final int prevEntryPosition = getIntValue(POSITIONS_ARRAY_OFFSET + (index - 1) * OIntegerSerializer.INT_SIZE);
        setIntValue(prevEntryPosition + OIntegerSerializer.INT_SIZE, leftChild);
      }
    }

    return true;
  }

  void setLeftSibling(final long pageIndex) {
    setLongValue(LEFT_SIBLING_OFFSET, pageIndex);
  }

  public long getLeftSibling() {
    return getLongValue(LEFT_SIBLING_OFFSET);
  }

  void setRightSibling(final long pageIndex) {
    setLongValue(RIGHT_SIBLING_OFFSET, pageIndex);
  }

  public long getRightSibling() {
    return getLongValue(RIGHT_SIBLING_OFFSET);
  }

  static class Entry {
    final byte[] key;

    Entry(final byte[] key) {
      this.key = key;
    }
  }

  static final class LeafEntry extends Entry {
    final List<ORID> values;

    LeafEntry(final byte[] key, final List<ORID> values) {
      super(key);
      this.values = values;
    }
  }

  static final class NonLeafEntry extends Entry {
    final int leftChild;
    final int rightChild;

    NonLeafEntry(final byte[] key, final int leftChild, final int rightChild) {
      super(key);

      this.leftChild = leftChild;
      this.rightChild = rightChild;
    }
  }
}


/*
 *
 *  *  Copyright 2010-2016 OrientDB LTD (http://orientdb.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://orientdb.com
 *
 */
package com.orientechnologies.orient.server;

import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.common.util.OCallable;
import com.orientechnologies.orient.core.config.OGlobalConfiguration;
import com.orientechnologies.orient.core.db.*;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.metadata.schema.OSchema;
import com.orientechnologies.orient.core.record.OElement;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.sql.executor.OResultSet;

import java.util.UUID;

public class OSystemDatabase {
  public static final String SYSTEM_DB_NAME = "OSystem";

  public static final String SERVER_INFO_CLASS  = "ServerInfo";
  public static final String SERVER_ID_PROPERTY = "serverId";

  private final OrientDBInternal context;
  private       String           serverId;

  public OSystemDatabase(final OrientDBInternal context) {
    this.context = context;
    init();
  }

  public String getSystemDatabaseName() {
    return OSystemDatabase.SYSTEM_DB_NAME;
  }

  /**
   * Adds the specified cluster to the class, if it doesn't already exist.
   */
  public void createCluster(final String className, final String clusterName) {
    final ODatabaseDocumentInternal currentDB = ODatabaseRecordThreadLocal.instance().getIfDefined();
    try {
      final ODatabaseDocumentInternal sysdb = openSystemDatabase();
      try {

        if (!sysdb.existsCluster(clusterName)) {
          OSchema schema = sysdb.getMetadata().getSchema();
          OClass cls = schema.getClass(className);

          if (cls != null) {
            cls.addCluster(clusterName);
          } else {
            OLogManager.instance().error(this, "createCluster() Class name %s does not exist", null, className);
          }
        }

      } finally {
        sysdb.close();
      }

    } finally {
      if (currentDB != null)
        ODatabaseRecordThreadLocal.instance().set(currentDB);
      else
        ODatabaseRecordThreadLocal.instance().remove();
    }
  }

  /**
   * Opens the System Database and returns an ODatabaseDocumentInternal object. The caller is responsible for retrieving any
   * ThreadLocal-stored database before openSystemDatabase() is called and restoring it after the database is closed.
   */
  public ODatabaseDocumentInternal openSystemDatabase() {
    return context.openNoAuthorization(getSystemDatabaseName());
  }

  public Object execute(final OCallable<Object, OResultSet> callback, final String sql, final Object... args) {
    final ODatabaseDocumentInternal currentDB = ODatabaseRecordThreadLocal.instance().getIfDefined();
    try {
      // BYPASS SECURITY
      final ODatabase<?> db = openSystemDatabase();
      try {
        final OResultSet result = db.command(sql, args);

        if (callback != null)
          return callback.call(result);
        else
          return result;

      } finally {
        db.close();
      }

    } finally {
      if (currentDB != null)
        ODatabaseRecordThreadLocal.instance().set(currentDB);
      else
        ODatabaseRecordThreadLocal.instance().remove();
    }
  }

  public ODocument save(final ODocument document) {
    return save(document, null);
  }

  public ODocument save(final ODocument document, final String clusterName) {
    final ODatabaseDocumentInternal currentDB = ODatabaseRecordThreadLocal.instance().getIfDefined();
    try {
      // BYPASS SECURITY
      final ODatabaseDocumentInternal db = openSystemDatabase();
      try {
        if (clusterName != null)
          return (ODocument) db.save(document, clusterName);
        else
          return (ODocument) db.save(document);
      } finally {
        db.close();
      }

    } finally {
      if (currentDB != null)
        ODatabaseRecordThreadLocal.instance().set(currentDB);
      else
        ODatabaseRecordThreadLocal.instance().remove();
    }
  }

  private void init() {
    final ODatabaseRecordThreadLocal tl = ODatabaseRecordThreadLocal.instance();
    final ODatabaseDocumentInternal oldDbInThread = tl != null ? tl.getIfDefined() : null;
    try {
      if (!exists()) {
        OLogManager.instance().info(this, "Creating the system database '%s' for current server", SYSTEM_DB_NAME);

        OrientDBConfig config = OrientDBConfig.builder().addConfig(OGlobalConfiguration.CREATE_DEFAULT_USERS, false)
            .addConfig(OGlobalConfiguration.CLASS_MINIMUM_CLUSTERS, 1).build();
        context.create(SYSTEM_DB_NAME, null, null, ODatabaseType.PLOCAL, config);
      }
      checkServerId();

    } finally {
      if (oldDbInThread != null) {
        ODatabaseRecordThreadLocal.instance().set(oldDbInThread);
      } else {
        ODatabaseRecordThreadLocal.instance().remove();
      }
    }
  }

  private synchronized void checkServerId() {
    ODatabaseDocumentInternal db = openSystemDatabase();
    try {
      OClass clazz = db.getClass(SERVER_INFO_CLASS);
      if (clazz == null) {
        clazz = db.createClass(SERVER_INFO_CLASS);
      }
      OElement info;
      if (clazz.count() == 0) {
        info = db.newElement(SERVER_INFO_CLASS);
      } else {
        info = db.browseClass(clazz.getName()).next();
      }
      this.serverId = info.getProperty(SERVER_ID_PROPERTY);
      if (this.serverId == null) {
        this.serverId = UUID.randomUUID().toString();
        info.setProperty(SERVER_ID_PROPERTY, serverId);
        info.save();
      }
    } finally {
      db.close();
    }
  }

  public void executeInDBScope(OCallable<Void, ODatabaseSession> callback) {
    executeWithDB(callback);
  }

  public <T> T executeWithDB(OCallable<T, ODatabaseSession> callback) {
    final ODatabaseDocumentInternal currentDB = ODatabaseRecordThreadLocal.instance().getIfDefined();
    try {
      try (final ODatabaseSession db = openSystemDatabase()) {
        return callback.call(db);
      }
    } finally {
      if (currentDB != null)
        ODatabaseRecordThreadLocal.instance().set(currentDB);
      else
        ODatabaseRecordThreadLocal.instance().remove();
    }

  }

  public boolean exists() {
    return context.exists(getSystemDatabaseName(), null, null);
  }

  public String getServerId() {
    return serverId;
  }
}


/*
  *
  *  *  Copyright 2010-2016 OrientDB LTD (http://orientdb.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://orientdb.com
  *
  */
package com.orientechnologies.orient.core.cache;

import com.orientechnologies.common.profiler.OAbstractProfiler.OProfilerHookValue;
import com.orientechnologies.common.profiler.OProfiler.METRIC_TYPE;
import com.orientechnologies.orient.core.Orient;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.record.ORecord;

import java.util.HashSet;
import java.util.Set;

/**
 * Cache of documents. Delegates real work on storing to {@link ORecordCache} implementation passed at creation time leaving only DB
 * specific functionality
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 */
public abstract class OAbstractRecordCache {
  protected ORecordCache underlying;
  protected String profilerPrefix         = "noname";
  protected String profilerMetadataPrefix = "noname";
  protected int    excludedCluster        = -1;

  /**
   * Create cache backed by given implementation
   *
   * @param impl
   *          actual implementation of cache
   */
  public OAbstractRecordCache(final ORecordCache impl) {
    underlying = impl;
  }

  /**
   * Tell whether cache is enabled
   *
   * @return {@code true} if cache enabled at call time, otherwise - {@code false}
   */
  public boolean isEnabled() {
    return underlying.isEnabled();
  }

  /**
   * Switch cache state between enabled and disabled
   *
   * @param enable
   *          pass {@code true} to enable, otherwise - {@code false}
   */
  public void setEnable(final boolean enable) {
    if (enable)
      underlying.enable();
    else
      underlying.disable();
  }

  /**
   * Remove record with specified identifier
   *
   * @param rid
   *          unique identifier of record
   * @return record stored in cache if any, otherwise - {@code null}
   */
  public ORecord freeRecord(final ORID rid) {
    return underlying.remove(rid);
  }

  /**
   * Remove all records belonging to specified cluster
   *
   * @param cid
   *          identifier of cluster
   */
  public void freeCluster(final int cid) {
    final Set<ORID> toRemove = new HashSet<ORID>(underlying.size() / 2);

    final Set<ORID> keys = new HashSet<ORID>(underlying.keys());
    for (final ORID id : keys)
      if (id.getClusterId() == cid)
        toRemove.add(id);

    for (final ORID ridToRemove : toRemove)
      underlying.remove(ridToRemove);
  }

  /**
   * Remove record entry
   *
   * @param rid
   *          unique record identifier
   */
  public void deleteRecord(final ORID rid) {
    underlying.remove(rid);
  }

  /**
   * Clear the entire cache by removing all the entries
   */
  public void clear() {
    underlying.clear();
  }

  /**
   * Total number of cached entries
   *
   * @return non-negative integer
   */
  public int getSize() {
    return underlying.size();
  }

  /**
   * All operations running at cache initialization stage
   */
  public void startup() {
    underlying.startup();

    Orient.instance().getProfiler()
        .registerHookValue(profilerPrefix + "current", "Number of entries in cache", METRIC_TYPE.SIZE, new OProfilerHookValue() {
          public Object getValue() {
            return getSize();
          }
        }, profilerMetadataPrefix + "current");

  }

  /**
   * All operations running at cache destruction stage
   */
  public void shutdown() {
    underlying.shutdown();

    if (Orient.instance().getProfiler() != null) {
      Orient.instance().getProfiler().unregisterHookValue(profilerPrefix + "enabled");
      Orient.instance().getProfiler().unregisterHookValue(profilerPrefix + "current");
      Orient.instance().getProfiler().unregisterHookValue(profilerPrefix + "max");
    }
  }
}


package com.orientechnologies.orient.core.command.script;

import com.orientechnologies.orient.core.command.script.transformer.OScriptTransformer;

import java.util.Collections;

/**
 * Static Factories of OScriptResultSet objects
 * <p>
 * Used in script results with conversion to OResult for single iteration
 * Created by Enrico Risa on 27/01/17.
 */

public class OScriptResultSets {

  /**
   * Empty result set
   *
   * @return
   */
  public static OScriptResultSet empty() {
    return new OScriptResultSet(Collections.EMPTY_LIST.iterator(), null);
  }

  /**
   * Result set with a single result;
   *
   * @return
   */
  public static OScriptResultSet singleton(Object entity, OScriptTransformer transformer) {
    return new OScriptResultSet(Collections.singletonList(entity).iterator(), transformer);
  }
}


/*
 *
 *  *  Copyright 2010-2016 OrientDB LTD (http://orientdb.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://orientdb.com
 *
 */
package com.orientechnologies.orient.core.metadata.security;

import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.record.impl.ODocument;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * Contains the user settings about security and permissions roles.

 * Allowed operation are the classic CRUD, namely:
 *

 *
CREATE

 *
READ

 *
UPDATE

 *
DELETE

 *

 * Mode = ALLOW (allow all but) or DENY (deny all but)
 */
@SuppressWarnings("unchecked")
public class ORole extends OIdentity implements OSecurityRole {
  public static final String                ADMIN              = "admin";
  public static final String                CLASS_NAME         = "ORole";
  public final static int                   PERMISSION_NONE    = 0;
  public final static int                   PERMISSION_CREATE  = registerPermissionBit(0, "Create");
  public final static int                   PERMISSION_READ    = registerPermissionBit(1, "Read");
  public final static int                   PERMISSION_UPDATE  = registerPermissionBit(2, "Update");
  public final static int                   PERMISSION_DELETE  = registerPermissionBit(3, "Delete");
  public final static int                   PERMISSION_EXECUTE = registerPermissionBit(4, "Execute");
  public final static int                   PERMISSION_ALL     = PERMISSION_CREATE + PERMISSION_READ + PERMISSION_UPDATE
                                                                  + PERMISSION_DELETE + PERMISSION_EXECUTE;
  protected final static byte               STREAM_DENY        = 0;
  protected final static byte               STREAM_ALLOW       = 1;
  private static final long                 serialVersionUID   = 1L;
  // CRUD OPERATIONS
  private static Map<Integer, String>       PERMISSION_BIT_NAMES;
  protected ALLOW_MODES                     mode               = ALLOW_MODES.DENY_ALL_BUT;
  protected ORole                           parentRole;

  private Map<ORule.ResourceGeneric, ORule> rules              = new HashMap<ORule.ResourceGeneric, ORule>();

  /**
   * Constructor used in unmarshalling.
   */
  public ORole() {
  }

  public ORole(final String iName, final ORole iParent, final ALLOW_MODES iAllowMode) {
    super(CLASS_NAME);
    document.field("name", iName);

    parentRole = iParent;
    document.field("inheritedRole", iParent != null ? iParent.getDocument() : null);
    setMode(iAllowMode);

    updateRolesDocumentContent();
  }

  /**
   * Create the role by reading the source document.
   */
  public ORole(final ODocument iSource) {
    fromStream(iSource);
  }

  /**
   * Convert the permission code to a readable string.
   *
   * @param iPermission
   *          Permission to convert
   * @return String representation of the permission
   */
  public static String permissionToString(final int iPermission) {
    int permission = iPermission;
    final StringBuilder returnValue = new StringBuilder(128);
    for (Entry<Integer, String> p : PERMISSION_BIT_NAMES.entrySet()) {
      if ((permission & p.getKey()) == p.getKey()) {
        if (returnValue.length() > 0)
          returnValue.append(", ");
        returnValue.append(p.getValue());
        permission &= ~p.getKey();
      }
    }
    if (permission != 0) {
      if (returnValue.length() > 0)
        returnValue.append(", ");
      returnValue.append("Unknown 0x");
      returnValue.append(Integer.toHexString(permission));
    }

    return returnValue.toString();
  }

  public static int registerPermissionBit(final int iBitNo, final String iName) {
    if (iBitNo < 0 || iBitNo > 31)
      throw new IndexOutOfBoundsException("Permission bit number must be positive and less than 32");

    final int value = 1 << iBitNo;
    if (PERMISSION_BIT_NAMES == null)
      PERMISSION_BIT_NAMES = new HashMap<Integer, String>();

    if (PERMISSION_BIT_NAMES.containsKey(value))
      throw new IndexOutOfBoundsException("Permission bit number " + String.valueOf(iBitNo) + " already in use");

    PERMISSION_BIT_NAMES.put(value, iName);
    return value;
  }

  @Override
  public void fromStream(final ODocument iSource) {
    if (document != null)
      return;

    document = iSource;

    try {
      final Number modeField = document.field("mode");
      mode = modeField == null ? ALLOW_MODES.DENY_ALL_BUT : modeField.byteValue() == STREAM_ALLOW ? ALLOW_MODES.ALLOW_ALL_BUT
          : ALLOW_MODES.DENY_ALL_BUT;
    } catch (Exception ex) {
      OLogManager.instance().error(this, "illegal mode " + ex.getMessage(), ex);
      mode = ALLOW_MODES.DENY_ALL_BUT;
    }

    final OIdentifiable role = document.field("inheritedRole");
    parentRole = role != null ? document.getDatabase().getMetadata().getSecurity().getRole(role) : null;

    boolean rolesNeedToBeUpdated = false;
    Object loadedRules = document.field("rules");
    if (loadedRules instanceof Map) {
      loadOldVersionOfRules((Map<String, Number>) loadedRules);
    } else {
      final Set<ODocument> storedRules = (Set<ODocument>) loadedRules;
      if (storedRules != null) {
        for (ODocument ruleDoc : storedRules) {
          final ORule.ResourceGeneric resourceGeneric = ORule.ResourceGeneric.valueOf(ruleDoc.<String> field("resourceGeneric"));
          if(resourceGeneric==null) continue;
          final Map<String, Byte> specificResources = ruleDoc.field("specificResources");
          final Byte access = ruleDoc.field("access");

          final ORule rule = new ORule(resourceGeneric, specificResources, access);
          rules.put(resourceGeneric, rule);
        }
      }

      // convert the format of roles presentation to classic one
      rolesNeedToBeUpdated = true;
    }

    if (getName().equals("admin") && !hasRule(ORule.ResourceGeneric.BYPASS_RESTRICTED, null))
      // FIX 1.5.1 TO ASSIGN database.bypassRestricted rule to the role
      addRule(ORule.ResourceGeneric.BYPASS_RESTRICTED, null, ORole.PERMISSION_ALL).save();

    if (rolesNeedToBeUpdated) {
      updateRolesDocumentContent();
      save();
    }
  }

  public boolean allow(final ORule.ResourceGeneric resourceGeneric, String resourceSpecific, final int iCRUDOperation) {
    final ORule rule = rules.get(resourceGeneric);
    if (rule != null) {
      final Boolean allowed = rule.isAllowed(resourceSpecific, iCRUDOperation);
      if (allowed != null)
        return allowed;
    }

    if (parentRole != null)
      // DELEGATE TO THE PARENT ROLE IF ANY
      return parentRole.allow(resourceGeneric, resourceSpecific, iCRUDOperation);

    return mode == ALLOW_MODES.ALLOW_ALL_BUT;
  }

  public boolean hasRule(final ORule.ResourceGeneric resourceGeneric, String resourceSpecific) {
    ORule rule = rules.get(resourceGeneric);

    if (rule == null)
      return false;

    if (resourceSpecific != null && !rule.containsSpecificResource(resourceSpecific))
      return false;

    return true;
  }

  public ORole addRule(final ORule.ResourceGeneric resourceGeneric, String resourceSpecific, final int iOperation) {
    ORule rule = rules.get(resourceGeneric);

    if (rule == null) {
      rule = new ORule(resourceGeneric, null, null);
      rules.put(resourceGeneric, rule);
    }

    rule.grantAccess(resourceSpecific, iOperation);

    rules.put(resourceGeneric, rule);

    updateRolesDocumentContent();

    return this;
  }

  @Deprecated
  @Override
  public boolean allow(String iResource, int iCRUDOperation) {
    final String specificResource = ORule.mapLegacyResourceToSpecificResource(iResource);
    final ORule.ResourceGeneric resourceGeneric = ORule.mapLegacyResourceToGenericResource(iResource);

    if (specificResource == null || specificResource.equals("*"))
      return allow(resourceGeneric, null, iCRUDOperation);

    return allow(resourceGeneric, specificResource, iCRUDOperation);
  }

  @Deprecated
  @Override
  public boolean hasRule(String iResource) {
    final String specificResource = ORule.mapLegacyResourceToSpecificResource(iResource);
    final ORule.ResourceGeneric resourceGeneric = ORule.mapLegacyResourceToGenericResource(iResource);

    if (specificResource == null || specificResource.equals("*"))
      return hasRule(resourceGeneric, null);

    return hasRule(resourceGeneric, specificResource);
  }

  @Deprecated
  @Override
  public OSecurityRole addRule(String iResource, int iOperation) {
    final String specificResource = ORule.mapLegacyResourceToSpecificResource(iResource);
    final ORule.ResourceGeneric resourceGeneric = ORule.mapLegacyResourceToGenericResource(iResource);

    if (specificResource == null || specificResource.equals("*"))
      return addRule(resourceGeneric, null, iOperation);

    return addRule(resourceGeneric, specificResource, iOperation);
  }

  @Deprecated
  @Override
  public OSecurityRole grant(String iResource, int iOperation) {
    final String specificResource = ORule.mapLegacyResourceToSpecificResource(iResource);
    final ORule.ResourceGeneric resourceGeneric = ORule.mapLegacyResourceToGenericResource(iResource);

    if (specificResource == null || specificResource.equals("*"))
      return grant(resourceGeneric, null, iOperation);

    return grant(resourceGeneric, specificResource, iOperation);
  }

  @Deprecated
  @Override
  public OSecurityRole revoke(String iResource, int iOperation) {
    final String specificResource = ORule.mapLegacyResourceToSpecificResource(iResource);
    final ORule.ResourceGeneric resourceGeneric = ORule.mapLegacyResourceToGenericResource(iResource);

    if (specificResource == null || specificResource.equals("*"))
      return revoke(resourceGeneric, null, iOperation);

    return revoke(resourceGeneric, specificResource, iOperation);
  }

  /**
   * Grant a permission to the resource.
   *
   * @return
   */
  public ORole grant(final ORule.ResourceGeneric resourceGeneric, String resourceSpecific, final int iOperation) {
    ORule rule = rules.get(resourceGeneric);

    if (rule == null) {
      rule = new ORule(resourceGeneric, null, null);
      rules.put(resourceGeneric, rule);
    }

    rule.grantAccess(resourceSpecific, iOperation);

    rules.put(resourceGeneric, rule);
    updateRolesDocumentContent();
    return this;
  }

  /**
   * Revoke a permission to the resource.
   */
  public ORole revoke(final ORule.ResourceGeneric resourceGeneric, String resourceSpecific, final int iOperation) {
    if (iOperation == PERMISSION_NONE)
      return this;

    ORule rule = rules.get(resourceGeneric);

    if (rule == null) {
      rule = new ORule(resourceGeneric, null, null);
      rules.put(resourceGeneric, rule);
    }

    rule.revokeAccess(resourceSpecific, iOperation);
    rules.put(resourceGeneric, rule);

    updateRolesDocumentContent();

    return this;
  }

  public String getName() {
    return document.field("name");
  }

  public ALLOW_MODES getMode() {
    return mode;
  }

  public ORole setMode(final ALLOW_MODES iMode) {
    this.mode = iMode;
    document.field("mode", mode == ALLOW_MODES.ALLOW_ALL_BUT ? STREAM_ALLOW : STREAM_DENY);
    return this;
  }

  public ORole getParentRole() {
    return parentRole;
  }

  public ORole setParentRole(final OSecurityRole iParent) {
    this.parentRole = (ORole) iParent;
    document.field("inheritedRole", parentRole != null ? parentRole.getDocument() : null);
    return this;
  }

  @Override
  public ORole save() {
    document.save(ORole.class.getSimpleName());
    return this;
  }

  public Set<ORule> getRuleSet() {
    return new HashSet<ORule>(rules.values());
  }

  @Deprecated
  public Map<String, Byte> getRules() {
    final Map<String, Byte> result = new HashMap<String, Byte>();

    for (ORule rule : rules.values()) {
      String name = ORule.mapResourceGenericToLegacyResource(rule.getResourceGeneric());

      if (rule.getAccess() != null) {
        result.put(name, rule.getAccess());
      }

      for (Map.Entry<String, Byte> specificResource : rule.getSpecificResources().entrySet()) {
        result.put(name + "." + specificResource.getKey(), specificResource.getValue());
      }
    }

    return result;
  }

  @Override
  public String toString() {
    return getName();
  }

  @Override
  public OIdentifiable getIdentity() {
    return document;
  }

  private void loadOldVersionOfRules(final Map<String, Number> storedRules) {
    if (storedRules != null)
      for (Entry<String, Number> a : storedRules.entrySet()) {
        ORule.ResourceGeneric resourceGeneric = ORule.mapLegacyResourceToGenericResource(a.getKey());
        ORule rule = rules.get(resourceGeneric);
        if (rule == null) {
          rule = new ORule(resourceGeneric, null, null);
          rules.put(resourceGeneric, rule);
        }

        String specificResource = ORule.mapLegacyResourceToSpecificResource(a.getKey());
        if (specificResource == null || specificResource.equals("*")) {
          rule.grantAccess(null, a.getValue().intValue());
        } else {
          rule.grantAccess(specificResource, a.getValue().intValue());
        }
      }
  }

  private ODocument updateRolesDocumentContent() {
    return document.field("rules", getRules());
  }
}


/*
 *
 *  *  Copyright 2010-2016 OrientDB LTD (http://orientdb.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://orientdb.com
 *
 */

package com.orientechnologies.common.serialization.types;

import com.orientechnologies.common.serialization.OBinaryConverter;
import com.orientechnologies.common.serialization.OBinaryConverterFactory;
import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 * Serializer for {@link Float} type.
 *
 * @author Ilya Bershadskiy (ibersh20-at-gmail.com)
 * @since 18.01.12
 */
public class OFloatSerializer implements OBinarySerializer<Float> {
  public static final  byte             ID         = 7;
  /**
   * size of float value in bytes
   */
  public static final  int              FLOAT_SIZE = 4;
  private static final OBinaryConverter CONVERTER  = OBinaryConverterFactory.getConverter();
  public static final  OFloatSerializer INSTANCE   = new OFloatSerializer();

  public int getObjectSize(Float object, Object... hints) {
    return FLOAT_SIZE;
  }

  public void serialize(Float object, byte[] stream, int startPosition, Object... hints) {
    OIntegerSerializer.INSTANCE.serializeLiteral(Float.floatToIntBits(object), stream, startPosition);
  }

  public Float deserialize(final byte[] stream, final int startPosition) {
    return Float.intBitsToFloat(OIntegerSerializer.INSTANCE.deserializeLiteral(stream, startPosition));
  }

  public int getObjectSize(final byte[] stream, final int startPosition) {
    return FLOAT_SIZE;
  }

  public byte getId() {
    return ID;
  }

  public int getObjectSizeNative(byte[] stream, int startPosition) {
    return FLOAT_SIZE;
  }

  @Override
  public void serializeNativeObject(Float object, byte[] stream, int startPosition, Object... hints) {
    CONVERTER.putInt(stream, startPosition, Float.floatToIntBits(object), ByteOrder.nativeOrder());
  }

  @Override
  public Float deserializeNativeObject(byte[] stream, int startPosition) {
    return Float.intBitsToFloat(CONVERTER.getInt(stream, startPosition, ByteOrder.nativeOrder()));
  }

  public void serializeNative(final float object, final byte[] stream, final int startPosition, final Object... hints) {
    CONVERTER.putInt(stream, startPosition, Float.floatToIntBits(object), ByteOrder.nativeOrder());
  }

  public float deserializeNative(final byte[] stream, final int startPosition) {
    return Float.intBitsToFloat(CONVERTER.getInt(stream, startPosition, ByteOrder.nativeOrder()));
  }

  public boolean isFixedLength() {
    return true;
  }

  public int getFixedLength() {
    return FLOAT_SIZE;
  }

  @Override
  public Float preprocess(final Float value, final Object... hints) {
    return value;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void serializeInByteBufferObject(Float object, ByteBuffer buffer, Object... hints) {
    buffer.putInt(Float.floatToIntBits(object));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Float deserializeFromByteBufferObject(ByteBuffer buffer) {
    return Float.intBitsToFloat(buffer.getInt());
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getObjectSizeInByteBuffer(ByteBuffer buffer) {
    return FLOAT_SIZE;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Float deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) {
    return Float.intBitsToFloat(walChanges.getIntValue(buffer, offset));
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getObjectSizeInByteBuffer(ByteBuffer buffer, OWALChanges walChanges, int offset) {
    return FLOAT_SIZE;
  }
}


/*
 *
 *  *  Copyright 2010-2016 OrientDB LTD (http://orientdb.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://orientdb.com
 *
 */
package com.orientechnologies.orient.core.sql;

import com.orientechnologies.common.parser.OStringParser;
import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest;
import com.orientechnologies.orient.core.command.OCommandRequest;
import com.orientechnologies.orient.core.command.OCommandRequestText;
import com.orientechnologies.orient.core.command.OCommandResultListener;
import com.orientechnologies.orient.core.config.OGlobalConfiguration;
import com.orientechnologies.orient.core.db.document.ODatabaseDocument;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.exception.OCommandExecutionException;
import com.orientechnologies.orient.core.index.*;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.metadata.security.ORole;
import com.orientechnologies.orient.core.record.ORecord;
import com.orientechnologies.orient.core.record.ORecordAbstract;
import com.orientechnologies.orient.core.record.ORecordInternal;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.sql.filter.OSQLFilter;
import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition;
import com.orientechnologies.orient.core.sql.parser.ODeleteStatement;
import com.orientechnologies.orient.core.sql.query.OSQLAsynchQuery;
import com.orientechnologies.orient.core.sql.query.OSQLQuery;
import com.orientechnologies.orient.core.storage.OStorage;
import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * SQL UPDATE command.
 *
 * @author Luca Garulli
 *
 */
public class OCommandExecutorSQLDelete extends OCommandExecutorSQLAbstract
    implements OCommandDistributedReplicateRequest, OCommandResultListener {
  public static final String   NAME            = "DELETE FROM";
  public static final String   KEYWORD_DELETE  = "DELETE";
  private static final String  VALUE_NOT_FOUND = "_not_found_";

  private OSQLQuery<ODocument> query;
  private String               indexName       = null;
  private int                  recordCount     = 0;
  private String               lockStrategy    = "NONE";
  private String               returning       = "COUNT";
  private List<ORecord>        allDeletedRecords;

  private OSQLFilter           compiledFilter;
  private boolean              unsafe          = false;

  public OCommandExecutorSQLDelete() {
  }

  @SuppressWarnings("unchecked")
  public OCommandExecutorSQLDelete parse(final OCommandRequest iRequest) {
    final OCommandRequestText textRequest = (OCommandRequestText) iRequest;

    String queryText = textRequest.getText();
    String originalQuery = queryText;
    try {
      queryText = preParse(queryText, iRequest);
      textRequest.setText(queryText);
      final ODatabaseDocument database = getDatabase();

      init((OCommandRequestText) iRequest);

      query = null;
      recordCount = 0;

      if (parserTextUpperCase.endsWith(KEYWORD_UNSAFE)) {
        unsafe = true;
        parserText = parserText.substring(0, parserText.length() - KEYWORD_UNSAFE.length() - 1);
        parserTextUpperCase = parserTextUpperCase.substring(0, parserTextUpperCase.length() - KEYWORD_UNSAFE.length() - 1);
      }

      parserRequiredKeyword(OCommandExecutorSQLDelete.KEYWORD_DELETE);
      parserRequiredKeyword(OCommandExecutorSQLDelete.KEYWORD_FROM);

      String subjectName = parserRequiredWord(false, "Syntax error", " =><,\r\n");
      if (subjectName == null)
        throwSyntaxErrorException("Invalid subject name. Expected cluster, class, index or sub-query");

      if (OStringParser.startsWithIgnoreCase(subjectName, OCommandExecutorSQLAbstract.INDEX_PREFIX)) {
        // INDEX
        indexName = subjectName.substring(OCommandExecutorSQLAbstract.INDEX_PREFIX.length());

        if (!parserIsEnded()) {
          while (!parserIsEnded()) {
            final String word = parserGetLastWord();

            if (word.equals(KEYWORD_LOCK))
              lockStrategy = parseLock();
            else if (word.equals(KEYWORD_RETURN))
              returning = parseReturn();
            else if (word.equals(KEYWORD_UNSAFE))
              unsafe = true;
            else if (word.equalsIgnoreCase(KEYWORD_WHERE))
              compiledFilter = OSQLEngine.getInstance().parseCondition(parserText.substring(parserGetCurrentPosition()),
                  getContext(), KEYWORD_WHERE);

            parserNextWord(true);
          }

        } else
          parserSetCurrentPosition(-1);

      } else if (subjectName.startsWith("(")) {
        subjectName = subjectName.trim();
        query = database.command(new OSQLAsynchQuery<ODocument>(subjectName.substring(1, subjectName.length() - 1), this));
        parserNextWord(true);
        if (!parserIsEnded()) {
          while (!parserIsEnded()) {
            final String word = parserGetLastWord();

            if (word.equals(KEYWORD_LOCK))
              lockStrategy = parseLock();
            else if (word.equals(KEYWORD_RETURN))
              returning = parseReturn();
            else if (word.equals(KEYWORD_UNSAFE))
              unsafe = true;
            else if (word.equalsIgnoreCase(KEYWORD_WHERE))
              compiledFilter = OSQLEngine.getInstance().parseCondition(parserText.substring(parserGetCurrentPosition()),
                  getContext(), KEYWORD_WHERE);

            parserNextWord(true);
          }
        }
      } else {
        parserNextWord(true);

        while (!parserIsEnded()) {
          final String word = parserGetLastWord();

          if (word.equals(KEYWORD_LOCK))
            lockStrategy = parseLock();
          else if (word.equals(KEYWORD_RETURN))
            returning = parseReturn();
          else {
            parserGoBack();
            break;
          }

          parserNextWord(true);
        }

        final String condition = parserGetCurrentPosition() > -1 ? " " + parserText.substring(parserGetCurrentPosition()) : "";
        query = database.command(new OSQLAsynchQuery<ODocument>("select from " + getSelectTarget(subjectName) + condition, this));
      }
    } finally {
      textRequest.setText(originalQuery);
    }

    return this;
  }

  private String getSelectTarget(String subjectName) {
    if (preParsedStatement == null) {
      return subjectName;
    }
    return ((ODeleteStatement) preParsedStatement).fromClause.toString();
  }

  public Object execute(final Map<Object, Object> iArgs) {
    if (query == null && indexName == null)
      throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet");

    if (!returning.equalsIgnoreCase("COUNT"))
      allDeletedRecords = new ArrayList<ORecord>();

    if (query != null) {
      // AGAINST CLUSTERS AND CLASSES
      query.setContext(getContext());

      Object prevLockValue = query.getContext().getVariable("$locking");

      if (lockStrategy.equals("RECORD"))
        query.getContext().setVariable("$locking", OStorage.LOCKING_STRATEGY.EXCLUSIVE_LOCK);

      query.execute(iArgs);

      query.getContext().setVariable("$locking", prevLockValue);

      if (returning.equalsIgnoreCase("COUNT"))
        // RETURNS ONLY THE COUNT
        return recordCount;
      else
        // RETURNS ALL THE DELETED RECORDS
        return allDeletedRecords;

    } else {
      // AGAINST INDEXES
      if (compiledFilter != null)
        compiledFilter.bindParameters(iArgs);

      final OIndex index = getDatabase().getMetadata().getIndexManager().getIndex(indexName);
      if (index == null)
        throw new OCommandExecutionException("Target index '" + indexName + "' not found");

      Object key = null;
      Object value = VALUE_NOT_FOUND;

      if (compiledFilter == null || compiledFilter.getRootCondition() == null) {
        if (returning.equalsIgnoreCase("COUNT")) {
          // RETURNS ONLY THE COUNT
          final long total = index.getSize();
          index.clear();
          return total;
        } else {
          // RETURNS ALL THE DELETED RECORDS
          OIndexCursor cursor = index.cursor();
          Map.Entry<Object, OIdentifiable> entry;

          while ((entry = cursor.nextEntry()) != null) {
            OIdentifiable rec = entry.getValue();
            rec = rec.getRecord();
            if (rec != null)
              allDeletedRecords.add((ORecord) rec);
          }

          index.clear();

          return allDeletedRecords;
        }

      } else {
        if (KEYWORD_KEY.equalsIgnoreCase(compiledFilter.getRootCondition().getLeft().toString()))
          // FOUND KEY ONLY
          key = getIndexKey(index.getDefinition(), compiledFilter.getRootCondition().getRight());

        else if (KEYWORD_RID.equalsIgnoreCase(compiledFilter.getRootCondition().getLeft().toString())) {
          // BY RID
          value = OSQLHelper.getValue(compiledFilter.getRootCondition().getRight());

        } else if (compiledFilter.getRootCondition().getLeft() instanceof OSQLFilterCondition) {
          // KEY AND VALUE
          final OSQLFilterCondition leftCondition = (OSQLFilterCondition) compiledFilter.getRootCondition().getLeft();
          if (KEYWORD_KEY.equalsIgnoreCase(leftCondition.getLeft().toString()))
            key = getIndexKey(index.getDefinition(), leftCondition.getRight());

          final OSQLFilterCondition rightCondition = (OSQLFilterCondition) compiledFilter.getRootCondition().getRight();
          if (KEYWORD_RID.equalsIgnoreCase(rightCondition.getLeft().toString()))
            value = OSQLHelper.getValue(rightCondition.getRight());

        }

        final boolean result;
        if (value != VALUE_NOT_FOUND) {
          assert key != null;
          result = index.remove(key, (OIdentifiable) value);
        } else
          result = index.remove(key);

        if (returning.equalsIgnoreCase("COUNT"))
          return result ? 1 : 0;
        else
          // TODO: REFACTOR INDEX TO RETURN DELETED ITEMS
          throw new UnsupportedOperationException();
      }
    }
  }

  @Override
  public long getDistributedTimeout() {
    return getDatabase().getConfiguration().getValueAsLong(OGlobalConfiguration.DISTRIBUTED_COMMAND_TASK_SYNCH_TIMEOUT);
  }

  /**
   * Deletes the current record.
   */
  public boolean result(final Object iRecord) {
    final ORecordAbstract record = ((OIdentifiable) iRecord).getRecord();

    if (record instanceof ODocument && compiledFilter != null
        && !Boolean.TRUE.equals(this.compiledFilter.evaluate(record, (ODocument) record, getContext()))) {
      return true;
    }
    try {
      if (record.getIdentity().isValid()) {
        if (returning.equalsIgnoreCase("BEFORE"))
          allDeletedRecords.add(record);

        // RESET VERSION TO DISABLE MVCC AVOIDING THE CONCURRENT EXCEPTION IF LOCAL CACHE IS NOT UPDATED
//        ORecordInternal.setVersion(record, -1);

        if (!unsafe && record instanceof ODocument) {
          // CHECK IF ARE VERTICES OR EDGES
          final OClass cls = ((ODocument) record).getSchemaClass();
          if (cls != null) {
            if (cls.isSubClassOf("V"))
              // FOUND VERTEX
              throw new OCommandExecutionException(
                  "'DELETE' command cannot delete vertices. Use 'DELETE VERTEX' command instead, or apply the 'UNSAFE' keyword to force it");
            else if (cls.isSubClassOf("E"))
              // FOUND EDGE
              throw new OCommandExecutionException(
                  "'DELETE' command cannot delete edges. Use 'DELETE EDGE' command instead, or apply the 'UNSAFE' keyword to force it");
          }
        }

        record.delete();

        recordCount++;
        return true;
      }
      return false;
    } finally {
      if (lockStrategy.equalsIgnoreCase("RECORD"))
        ((OAbstractPaginatedStorage) getDatabase().getStorage()).releaseWriteLock(record.getIdentity());
    }
  }

  public String getSyntax() {
    return "DELETE FROM <Class>|RID|cluster:<cluster> [UNSAFE] [LOCK <NONE|RECORD>] [RETURN <COUNT|BEFORE>] [WHERE <condition>*]";
  }

  @Override
  public void end() {
  }

  @Override
  public int getSecurityOperationType() {
    return ORole.PERMISSION_DELETE;
  }

  /**
   * Parses the returning keyword if found.
   */
  protected String parseReturn() throws OCommandSQLParsingException {
    final String returning = parserNextWord(true);

    if (!returning.equalsIgnoreCase("COUNT") && !returning.equalsIgnoreCase("BEFORE"))
      throwParsingException("Invalid " + KEYWORD_RETURN + " value set to '" + returning
          + "' but it should be COUNT (default), BEFORE. Example: " + KEYWORD_RETURN + " BEFORE");

    return returning;
  }

  private Object getIndexKey(final OIndexDefinition indexDefinition, Object value) {
    if (indexDefinition instanceof OCompositeIndexDefinition) {
      if (value instanceof List) {
        final List<?> values = (List<?>) value;
        List<Object> keyParams = new ArrayList<Object>(values.size());

        for (Object o : values) {
          keyParams.add(OSQLHelper.getValue(o));
        }
        return indexDefinition.createValue(keyParams);
      } else {
        value = OSQLHelper.getValue(value);
        if (value instanceof OCompositeKey) {
          return value;
        } else {
          return indexDefinition.createValue(value);
        }
      }
    } else {
      return OSQLHelper.getValue(value);
    }
  }

  @Override
  public QUORUM_TYPE getQuorumType() {
    return QUORUM_TYPE.WRITE;
  }

  @Override
  public OCommandDistributedReplicateRequest.DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() {
    return DISTRIBUTED_EXECUTION_MODE.LOCAL;
    // ALWAYS EXECUTE THE COMMAND LOCALLY BECAUSE THERE IS NO A DISTRIBUTED UNDO WITH SHARDING
    //
    // return (indexName != null || query != null) && !getDatabase().getTransaction().isActive() ?
    // DISTRIBUTED_EXECUTION_MODE.REPLICATE
    // : DISTRIBUTED_EXECUTION_MODE.LOCAL;
  }

  public DISTRIBUTED_RESULT_MGMT getDistributedResultManagement() {
    return DISTRIBUTED_RESULT_MGMT.CHECK_FOR_EQUALS;
    // return getDistributedExecutionMode() == DISTRIBUTED_EXECUTION_MODE.LOCAL ? DISTRIBUTED_RESULT_MGMT.CHECK_FOR_EQUALS
    // : DISTRIBUTED_RESULT_MGMT.MERGE;
  }

  @Override
  public Object getResult() {
    return null;
  }
}


package com.orientechnologies.orient.core.sql.executor;

import com.orientechnologies.orient.core.command.OCommandContext;
import com.orientechnologies.orient.core.exception.OCommandExecutionException;
import com.orientechnologies.orient.core.sql.parser.*;
import com.orientechnologies.orient.core.storage.OStorage;

/**
 * Created by luigidellaquila on 08/08/16.
 */
public class OMoveVertexExecutionPlanner {
  private final OFromItem         source;
  private final OIdentifier       targetClass;
  private final OCluster          targetCluster;
  private final OUpdateOperations updateOperations;
  private final OBatch            batch;

  public OMoveVertexExecutionPlanner(OMoveVertexStatement oStatement) {
    this.source = oStatement.getSource();
    this.targetClass = oStatement.getTargetClass();
    this.targetCluster = oStatement.getTargetCluster();
    this.updateOperations = oStatement.getUpdateOperations();
    this.batch = oStatement.getBatch();
  }

  public OUpdateExecutionPlan createExecutionPlan(OCommandContext ctx, boolean enableProfiling) {
    OUpdateExecutionPlan result = new OUpdateExecutionPlan(ctx);

    handleSource(result, ctx, this.source, enableProfiling);
    convertToModifiableResult(result, ctx, enableProfiling);
    handleTarget(result, targetClass, targetCluster, ctx, enableProfiling);
    handleOperations(result, ctx, this.updateOperations, enableProfiling);
    handleBatch(result, ctx, this.batch, enableProfiling);
    handleSave(result, ctx, enableProfiling);
    return result;
  }

  private void handleTarget(OUpdateExecutionPlan result, OIdentifier targetClass, OCluster targetCluster, OCommandContext ctx, boolean profilingEnabled) {
    result.chain(new MoveVertexStep(targetClass, targetCluster, ctx, profilingEnabled));
  }

  private void handleBatch(OUpdateExecutionPlan result, OCommandContext ctx, OBatch batch, boolean profilingEnabled) {
    if (batch != null) {
      result.chain(new BatchStep(batch, ctx, profilingEnabled));
    }
  }

  /**
   * add a step that transforms a normal OResult in a specific object that under setProperty() updates the actual OIdentifiable
   *
   * @param plan the execution plan
   * @param ctx  the executino context
   */
  private void convertToModifiableResult(OUpdateExecutionPlan plan, OCommandContext ctx, boolean profilingEnabled) {
    plan.chain(new ConvertToUpdatableResultStep(ctx, profilingEnabled));
  }

  private void handleResultForReturnCount(OUpdateExecutionPlan result, OCommandContext ctx, boolean returnCount, boolean profilingEnabled) {
    if (returnCount) {
      result.chain(new CountStep(ctx, profilingEnabled));
    }
  }

  private void handleResultForReturnAfter(OUpdateExecutionPlan result, OCommandContext ctx, boolean returnAfter,
      OProjection returnProjection, boolean profilingEnabled) {
    if (returnAfter) {
      //re-convert to normal step
      result.chain(new ConvertToResultInternalStep(ctx, profilingEnabled));
      if (returnProjection != null) {
        result.chain(new ProjectionCalculationStep(returnProjection, ctx, profilingEnabled));
      }
    }
  }

  private void handleResultForReturnBefore(OUpdateExecutionPlan result, OCommandContext ctx, boolean returnBefore,
      OProjection returnProjection, boolean profilingEnabled) {
    if (returnBefore) {
      result.chain(new UnwrapPreviousValueStep(ctx, profilingEnabled));
      if (returnProjection != null) {
        result.chain(new ProjectionCalculationStep(returnProjection, ctx, profilingEnabled));
      }
    }
  }

  private void handleSave(OUpdateExecutionPlan result, OCommandContext ctx, boolean profilingEnabled) {
    result.chain(new SaveElementStep(ctx, profilingEnabled));
  }

  private void handleTimeout(OUpdateExecutionPlan result, OCommandContext ctx, OTimeout timeout, boolean profilingEnabled) {
    if (timeout != null && timeout.getVal().longValue() > 0) {
      result.chain(new TimeoutStep(timeout, ctx, profilingEnabled));
    }
  }

  private void handleReturnBefore(OUpdateExecutionPlan result, OCommandContext ctx, boolean returnBefore, boolean profilingEnabled) {
    if (returnBefore) {
      result.chain(new CopyRecordContentBeforeUpdateStep(ctx, profilingEnabled));
    }
  }

  private void handleLock(OUpdateExecutionPlan result, OCommandContext ctx, OStorage.LOCKING_STRATEGY lockRecord) {

  }

  private void handleLimit(OUpdateExecutionPlan plan, OCommandContext ctx, OLimit limit, boolean profilingEnabled) {
    if (limit != null) {
      plan.chain(new LimitExecutionStep(limit, ctx, profilingEnabled));
    }
  }

  private void handleUpsert(OUpdateExecutionPlan plan, OCommandContext ctx, OFromClause target, OWhereClause where,
      boolean upsert, boolean profilingEnabled) {
    if (upsert) {
      plan.chain(new UpsertStep(target, where, ctx, profilingEnabled));
    }
  }

  private void handleOperations(OUpdateExecutionPlan plan, OCommandContext ctx, OUpdateOperations op, boolean profilingEnabled) {
    if (op != null) {
      switch (op.getType()) {
      case OUpdateOperations.TYPE_SET:
        plan.chain(new UpdateSetStep(op.getUpdateItems(), ctx, profilingEnabled));
        break;
      case OUpdateOperations.TYPE_REMOVE:
        plan.chain(new UpdateRemoveStep(op.getUpdateRemoveItems(), ctx, profilingEnabled));
        break;
      case OUpdateOperations.TYPE_MERGE:
        plan.chain(new UpdateMergeStep(op.getJson(), ctx, profilingEnabled));
        break;
      case OUpdateOperations.TYPE_CONTENT:
        plan.chain(new UpdateContentStep(op.getJson(), ctx, profilingEnabled));
        break;
      case OUpdateOperations.TYPE_PUT:
      case OUpdateOperations.TYPE_INCREMENT:
      case OUpdateOperations.TYPE_ADD:
        throw new OCommandExecutionException("Cannot execute with UPDATE PUT/ADD/INCREMENT new executor: " + op);
      }
    }
  }

  private void handleSource(OUpdateExecutionPlan result, OCommandContext ctx, OFromItem source, boolean profilingEnabled) {
    OSelectStatement sourceStatement = new OSelectStatement(-1);
    sourceStatement.setTarget(new OFromClause(-1));
    sourceStatement.getTarget().setItem(source);
    OSelectExecutionPlanner planner = new OSelectExecutionPlanner(sourceStatement);
    result.chain(new SubQueryStep(planner.createExecutionPlan(ctx, profilingEnabled, false), ctx, ctx, profilingEnabled));
  }
}


/*
 *
 *  *  Copyright 2010-2016 OrientDB LTD (http://orientdb.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://orientdb.com
 *
 */

package com.orientechnologies.common.serialization.types;

import com.orientechnologies.common.serialization.OBinaryConverter;
import com.orientechnologies.common.serialization.OBinaryConverterFactory;
import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 * Serializer for {@link Integer} type.
 *
 * @author Ilya Bershadskiy (ibersh20-at-gmail.com)
 * @since 17.01.12
 */
public class OIntegerSerializer implements OBinarySerializer<Integer> {
  public static final  byte               ID        = 8;
  /**
   * size of int value in bytes
   */
  public static final  int                INT_SIZE  = 4;
  private static final OBinaryConverter   CONVERTER = OBinaryConverterFactory.getConverter();
  public static final  OIntegerSerializer INSTANCE  = new OIntegerSerializer();

  public int getObjectSize(Integer object, Object... hints) {
    return INT_SIZE;
  }

  public void serialize(final Integer object, final byte[] stream, final int startPosition, final Object... hints) {
    serializeLiteral(object.intValue(), stream, startPosition);
  }

  public void serializeLiteral(final int value, final byte[] stream, final int startPosition) {
    stream[startPosition] = (byte) ((value >>> 24) & 0xFF);
    stream[startPosition + 1] = (byte) ((value >>> 16) & 0xFF);
    stream[startPosition + 2] = (byte) ((value >>> 8) & 0xFF);
    stream[startPosition + 3] = (byte) ((value >>> 0) & 0xFF);
  }

  public Integer deserialize(final byte[] stream, final int startPosition) {
    return deserializeLiteral(stream, startPosition);
  }

  public int deserializeLiteral(final byte[] stream, final int startPosition) {
    return (stream[startPosition]) << 24 | (0xff & stream[startPosition + 1]) << 16 | (0xff & stream[startPosition + 2]) << 8 | ((
        0xff & stream[startPosition + 3]));
  }

  public int getObjectSize(final byte[] stream, final int startPosition) {
    return INT_SIZE;
  }

  public byte getId() {
    return ID;
  }

  public int getObjectSizeNative(final byte[] stream, final int startPosition) {
    return INT_SIZE;
  }

  @Override
  public void serializeNativeObject(Integer object, byte[] stream, int startPosition, Object... hints) {
    CONVERTER.putInt(stream, startPosition, object, ByteOrder.nativeOrder());
  }

  @Override
  public Integer deserializeNativeObject(final byte[] stream, final int startPosition) {
    return CONVERTER.getInt(stream, startPosition, ByteOrder.nativeOrder());
  }

  public void serializeNative(int object, byte[] stream, int startPosition, Object... hints) {
    CONVERTER.putInt(stream, startPosition, object, ByteOrder.nativeOrder());
  }

  public int deserializeNative(final byte[] stream, final int startPosition) {
    return CONVERTER.getInt(stream, startPosition, ByteOrder.nativeOrder());
  }

  public boolean isFixedLength() {
    return true;
  }

  public int getFixedLength() {
    return INT_SIZE;
  }

  @Override
  public Integer preprocess(final Integer value, final Object... hints) {
    return value;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void serializeInByteBufferObject(Integer object, ByteBuffer buffer, Object... hints) {
    buffer.putInt(object);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Integer deserializeFromByteBufferObject(ByteBuffer buffer) {
    return buffer.getInt();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getObjectSizeInByteBuffer(ByteBuffer buffer) {
    return INT_SIZE;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public Integer deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) {
    return walChanges.getIntValue(buffer, offset);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getObjectSizeInByteBuffer(ByteBuffer buffer, OWALChanges walChanges, int offset) {
    return INT_SIZE;
  }
}