private Map.Entry<OIdentifiable, Integer> nextChangedNotRemovedSBTreeEntry(
      Iterator<Map.Entry<OIdentifiable, Integer>> iterator) {
    while (iterator.hasNext()) {
      final Map.Entry<OIdentifiable, Integer> entry = iterator.next();
      final Change change = changes.get(entry.getKey());
      if (change == null) return entry;

      final int newValue = change.applyTo(entry.getValue());

      if (newValue > 0)
        return new Map.Entry<OIdentifiable, Integer>() {
          @Override
          public OIdentifiable getKey() {
            return entry.getKey();
          }

          @Override
          public Integer getValue() {
            return newValue;
          }

          @Override
          public Integer setValue(Integer value) {
            throw new UnsupportedOperationException();
          }
        };
    }

    return null;
  }
  @Override
  public boolean convertRecords2Links() {
    final Map<OIdentifiable, Change> newChangedValues = new HashMap<OIdentifiable, Change>();
    for (Map.Entry<OIdentifiable, Change> entry : changes.entrySet()) {
      OIdentifiable identifiable = entry.getKey();
      if (identifiable instanceof ORecord) {
        ORID identity = identifiable.getIdentity();
        ORecord record = (ORecord) identifiable;
        identity = record.getIdentity();

        newChangedValues.put(identity, entry.getValue());
      } else newChangedValues.put(entry.getKey().getIdentity(), entry.getValue());
    }

    for (Map.Entry<OIdentifiable, Change> entry : newChangedValues.entrySet()) {
      if (entry.getKey() instanceof ORecord) {
        ORecord record = (ORecord) entry.getKey();

        newChangedValues.put(record, entry.getValue());
      } else return false;
    }

    newEntries.clear();

    changes.clear();
    changes.putAll(newChangedValues);

    return true;
  }
    private Map.Entry<OIdentifiable, Change> nextChangedNotRemovedEntry(
        Iterator<Map.Entry<OIdentifiable, Change>> iterator) {
      Map.Entry<OIdentifiable, Change> entry;

      while (iterator.hasNext()) {
        entry = iterator.next();
        // TODO workaround
        if (entry.getValue().applyTo(0) > 0) return entry;
      }

      return null;
    }
  @Override
  public void convertLinks2Records() {
    TreeMap<OIdentifiable, Change> newChanges = new TreeMap<OIdentifiable, Change>();
    for (Map.Entry<OIdentifiable, Change> entry : changes.entrySet()) {
      final OIdentifiable key = entry.getKey().getRecord();
      if (key != null && this.owner != null) {
        ORecordInternal.unTrack(this.owner, entry.getKey());
        ORecordInternal.track(this.owner, key);
      }
      newChanges.put((key == null) ? entry.getKey() : key, entry.getValue());
    }

    changes.clear();
    changes.putAll(newChanges);
  }
    public <K> void serializeChanges(
        Map<K, Change> changes, OBinarySerializer<K> keySerializer, byte[] stream, int offset) {
      OIntegerSerializer.INSTANCE.serializeLiteral(changes.size(), stream, offset);
      offset += OIntegerSerializer.INT_SIZE;

      for (Map.Entry<K, Change> entry : changes.entrySet()) {
        K key = entry.getKey();
        if (((OIdentifiable) key).getIdentity().isTemporary())
          key = ((OIdentifiable) key).getRecord();

        keySerializer.serialize(key, stream, offset);
        offset += keySerializer.getObjectSize(key);

        offset += entry.getValue().serialize(stream, offset);
      }
    }
    @Override
    public void remove() {
      if (currentRemoved)
        throw new IllegalStateException("Current element has already been removed");

      if (currentValue == null)
        throw new IllegalStateException("Next method was not called for given iterator");

      if (removeFromNewEntries(currentValue)) {
        if (size >= 0) size--;
      } else {
        Change counter = changedValues.get(currentValue);
        if (counter != null) {
          counter.decrement();
          if (size >= 0)
            if (counter.isUndefined()) size = -1;
            else size--;
        } else {
          if (nextChange != null) {
            changedValues.put(currentValue, new DiffChange(-1));
            changedValuesIterator =
                changedValues.tailMap(nextChange.getKey(), false).entrySet().iterator();
          } else {
            changedValues.put(currentValue, new DiffChange(-1));
          }

          size = -1;
        }
      }

      if (OSBTreeRidBag.this.owner != null)
        ORecordInternal.unTrack(OSBTreeRidBag.this.owner, currentValue);

      if (updateOwner)
        fireCollectionChangedEvent(
            new OMultiValueChangeEvent<OIdentifiable, OIdentifiable>(
                OMultiValueChangeEvent.OChangeType.REMOVE,
                currentValue,
                null,
                currentValue,
                false));
      currentRemoved = true;
    }
  public void mergeChanges(OSBTreeRidBag treeRidBag) {
    for (Map.Entry<OIdentifiable, OModifiableInteger> entry : treeRidBag.newEntries.entrySet()) {
      mergeDiffEntry(entry.getKey(), entry.getValue().getValue());
    }

    for (Map.Entry<OIdentifiable, Change> entry : treeRidBag.changes.entrySet()) {
      final OIdentifiable rec = entry.getKey();
      final Change change = entry.getValue();
      final int diff;
      if (change instanceof DiffChange) diff = ((DiffChange) change).delta;
      else if (change instanceof AbsoluteChange)
        diff = ((AbsoluteChange) change).value - getAbsoluteValue(rec).value;
      else throw new IllegalArgumentException("change type is not supported");

      mergeDiffEntry(rec, diff);
    }
  }
  @Override
  public int serialize(byte[] stream, int offset, UUID ownerUuid) {
    for (Map.Entry<OIdentifiable, OModifiableInteger> entry : newEntries.entrySet()) {
      OIdentifiable identifiable = entry.getKey();
      assert identifiable instanceof ORecord;
      Change c = changes.get(identifiable);

      final int delta = entry.getValue().intValue();
      if (c == null) changes.put(identifiable, new DiffChange(delta));
      else c.applyDiff(delta);
    }
    newEntries.clear();

    final ORecordSerializationContext context;
    boolean remoteMode =
        ODatabaseRecordThreadLocal.INSTANCE.get().getStorage() instanceof OStorageProxy;
    if (remoteMode) {
      context = null;
    } else context = ORecordSerializationContext.getContext();

    // make sure that we really save underlying record.
    if (collectionPointer == null) {
      if (context != null) {
        final int clusterId = getHighLevelDocClusterId();
        assert clusterId > -1;
        collectionPointer =
            ODatabaseRecordThreadLocal.INSTANCE
                .get()
                .getSbTreeCollectionManager()
                .createSBTree(clusterId, ownerUuid);
      }
    }

    OBonsaiCollectionPointer collectionPointer;
    if (this.collectionPointer != null) collectionPointer = this.collectionPointer;
    else {
      collectionPointer = OBonsaiCollectionPointer.INVALID;
    }

    OLongSerializer.INSTANCE.serializeLiteral(collectionPointer.getFileId(), stream, offset);
    offset += OLongSerializer.LONG_SIZE;

    OBonsaiBucketPointer rootPointer = collectionPointer.getRootPointer();
    OLongSerializer.INSTANCE.serializeLiteral(rootPointer.getPageIndex(), stream, offset);
    offset += OLongSerializer.LONG_SIZE;

    OIntegerSerializer.INSTANCE.serializeLiteral(rootPointer.getPageOffset(), stream, offset);
    offset += OIntegerSerializer.INT_SIZE;

    // Keep this section for binary compatibility with versions older then 1.7.5
    OIntegerSerializer.INSTANCE.serializeLiteral(size, stream, offset);
    offset += OIntegerSerializer.INT_SIZE;

    if (context == null) {
      ChangeSerializationHelper.INSTANCE.serializeChanges(
          changes, OLinkSerializer.INSTANCE, stream, offset);
    } else {
      context.push(new ORidBagUpdateSerializationOperation(changes, collectionPointer));

      // 0-length serialized list of changes
      OIntegerSerializer.INSTANCE.serializeLiteral(0, stream, offset);
      offset += OIntegerSerializer.INT_SIZE;
    }

    return offset;
  }
    @Override
    public OIdentifiable next() {
      currentRemoved = false;
      if (currentCounter < currentFinalCounter) {
        currentCounter++;
        return currentValue;
      }

      if (newEntryIterator.hasNext()) {
        Map.Entry<OIdentifiable, OModifiableInteger> entry = newEntryIterator.next();
        currentValue = entry.getKey();
        currentFinalCounter = entry.getValue().intValue();
        currentCounter = 1;
        return currentValue;
      }

      if (nextChange != null && nextSBTreeEntry != null) {
        if (nextChange.getKey().compareTo(nextSBTreeEntry.getKey()) < 0) {
          currentValue = nextChange.getKey();
          currentFinalCounter = nextChange.getValue().applyTo(0);
          currentCounter = 1;

          nextChange = nextChangedNotRemovedEntry(changedValuesIterator);
        } else {
          currentValue = nextSBTreeEntry.getKey();
          currentFinalCounter = nextSBTreeEntry.getValue();
          currentCounter = 1;

          nextSBTreeEntry = nextChangedNotRemovedSBTreeEntry(sbTreeIterator);
          if (nextChange != null && nextChange.getKey().equals(currentValue))
            nextChange = nextChangedNotRemovedEntry(changedValuesIterator);
        }
      } else if (nextChange != null) {
        currentValue = nextChange.getKey();
        currentFinalCounter = nextChange.getValue().applyTo(0);
        currentCounter = 1;

        nextChange = nextChangedNotRemovedEntry(changedValuesIterator);
      } else if (nextSBTreeEntry != null) {
        currentValue = nextSBTreeEntry.getKey();
        currentFinalCounter = nextSBTreeEntry.getValue();
        currentCounter = 1;

        nextSBTreeEntry = nextChangedNotRemovedSBTreeEntry(sbTreeIterator);
      } else throw new NoSuchElementException();

      if (convertToRecord) return currentValue.getRecord();

      return currentValue;
    }