/*
 * Copyright 2018 OrientDB.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.orientechnologies.orient.core.serialization.serializer.record.binary;

import com.orientechnologies.common.exception.OException;
import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.common.serialization.types.OByteSerializer;
import com.orientechnologies.common.serialization.types.OIntegerSerializer;
import com.orientechnologies.common.serialization.types.OLongSerializer;
import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal;
import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.db.record.ORecordElement;
import com.orientechnologies.orient.core.db.record.ORecordLazyMap;
import com.orientechnologies.orient.core.db.record.ORecordLazyMultiValue;
import com.orientechnologies.orient.core.db.record.ridbag.ORidBag;
import com.orientechnologies.orient.core.db.record.ridbag.ORidBagDelegate;
import com.orientechnologies.orient.core.db.record.ridbag.embedded.OEmbeddedRidBag;
import com.orientechnologies.orient.core.delta.ODeltaDocumentFieldType;
import com.orientechnologies.orient.core.delta.ODocumentDelta;
import com.orientechnologies.orient.core.exception.ODatabaseException;
import com.orientechnologies.orient.core.exception.ORecordNotFoundException;
import com.orientechnologies.orient.core.exception.OSerializationException;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.id.ORecordId;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.metadata.schema.OGlobalProperty;
import com.orientechnologies.orient.core.metadata.schema.OProperty;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.metadata.schema.OTypeInterface;
import com.orientechnologies.orient.core.record.ORecord;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.record.impl.ODocumentInternal;
import com.orientechnologies.orient.core.storage.OStorageProxy;
import com.orientechnologies.orient.core.storage.impl.local.paginated.ORecordSerializationContext;
import com.orientechnologies.orient.core.storage.index.sbtreebonsai.local.OBonsaiBucketPointer;
import com.orientechnologies.orient.core.storage.ridbag.sbtree.Change;
import com.orientechnologies.orient.core.storage.ridbag.sbtree.ChangeSerializationHelper;
import com.orientechnologies.orient.core.storage.ridbag.sbtree.OBonsaiCollectionPointer;
import com.orientechnologies.orient.core.storage.ridbag.sbtree.OSBTreeCollectionManager;
import com.orientechnologies.orient.core.storage.ridbag.sbtree.OSBTreeRidBag;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Calendar;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
import java.util.UUID;

/**
 * @author mdjurovi
 */
public class HelperClasses {
  protected static final String    CHARSET_UTF_8    = "UTF-8";
  protected static final ORecordId NULL_RECORD_ID   = new ORecordId(-2, ORID.CLUSTER_POS_INVALID);
  public static final    long      MILLISEC_PER_DAY = 86400000;

  public static class Tuple<T1, T2> {

    private final T1 firstVal;
    private final T2 secondVal;

    Tuple(T1 firstVal, T2 secondVal) {
      this.firstVal = firstVal;
      this.secondVal = secondVal;
    }

    public T1 getFirstVal() {
      return firstVal;
    }

    public T2 getSecondVal() {
      return secondVal;
    }
  }

  public static class Triple<T1, T2, T3> {// extends Tuple<T1, T2>{
    private final T1 firstVal;
    private final T2 secondVal;
    private final T3 thirdVal;

    public Triple(T1 firstVal, T2 secondVal, T3 thirdVal) {
      this.firstVal = firstVal;
      this.secondVal = secondVal;
      this.thirdVal = thirdVal;
    }

    public T3 getThirdVal() {
      return thirdVal;
    }

    public T1 getFirstVal() {
      return firstVal;
    }

    public T2 getSecondVal() {
      return secondVal;
    }
  }

  protected static class RecordInfo {
    public int   fieldStartOffset;
    public int   fieldLength;
    public OType fieldType;
  }

  protected static class MapRecordInfo extends RecordInfo {
    public String key;
    public OType  keyType;
  }

  public static OType readOType(final BytesContainer bytes, boolean justRunThrough) {
    if (justRunThrough) {
      bytes.offset++;
      return null;
    }
    return OType.getById(readByte(bytes));
  }

  public static OTypeInterface readDeltaDocumentType(final BytesContainer bytes) {
    return ODeltaDocumentFieldType.getFromId(readByte(bytes));
  }

//  public static ODeltaDocumentFieldType getDeltaTypeFromType(OType type){
//    if (type == null){
//      return null;
//    }
//    return ODeltaDocumentFieldType.getFromId(type.getId());
//  }

  public static void writeOType(BytesContainer bytes, int pos, OType type) {
    bytes.bytes[pos] = (byte) type.getId();
  }

  public static void writeType(BytesContainer bytes, OTypeInterface type) {
    int pos = bytes.alloc(1);
    bytes.bytes[pos] = (byte) type.getId();
  }

  public static void writeType(BytesContainer bytes, ODeltaDocumentFieldType type) {
    int pos = bytes.alloc(1);
    bytes.bytes[pos] = (byte) type.getId();
  }

  public static OType readType(BytesContainer bytes) {
    byte typeId = bytes.bytes[bytes.offset++];
    if (typeId == -1) {
      return null;
    }
    return OType.getById(typeId);
  }

  public static byte[] readBinary(final BytesContainer bytes) {
    final int n = OVarIntSerializer.readAsInteger(bytes);
    final byte[] newValue = new byte[n];
    System.arraycopy(bytes.bytes, bytes.offset, newValue, 0, newValue.length);
    bytes.skip(n);
    return newValue;
  }

  public static String readString(final BytesContainer bytes) {
    final int len = OVarIntSerializer.readAsInteger(bytes);
    if (len == 0) {
      return "";
    }
    final String res = stringFromBytes(bytes.bytes, bytes.offset, len);
    bytes.skip(len);
    return res;
  }

  public static int readInteger(final BytesContainer container) {
    final int value = OIntegerSerializer.INSTANCE.deserializeLiteral(container.bytes, container.offset);
    container.offset += OIntegerSerializer.INT_SIZE;
    return value;
  }

  public static byte readByte(final BytesContainer container) {
    return container.bytes[container.offset++];
  }

  public static long readLong(final BytesContainer container) {
    final long value = OLongSerializer.INSTANCE.deserializeLiteral(container.bytes, container.offset);
    container.offset += OLongSerializer.LONG_SIZE;
    return value;
  }

  public static ORecordId readOptimizedLink(final BytesContainer bytes, boolean justRunThrough) {
    int clusterId = OVarIntSerializer.readAsInteger(bytes);
    long clusterPos = OVarIntSerializer.readAsLong(bytes);
    if (justRunThrough)
      return null;
    else
      return new ORecordId(clusterId, clusterPos);
  }

  public static String stringFromBytes(final byte[] bytes, final int offset, final int len) {
    try {
      return new String(bytes, offset, len, CHARSET_UTF_8);
    } catch (UnsupportedEncodingException e) {
      throw OException.wrapException(new OSerializationException("Error on string decoding"), e);
    }
  }

  public static byte[] bytesFromString(final String toWrite) {
    try {
      return toWrite.getBytes(CHARSET_UTF_8);
    } catch (UnsupportedEncodingException e) {
      throw OException.wrapException(new OSerializationException("Error on string encoding"), e);
    }
  }

  public static long convertDayToTimezone(TimeZone from, TimeZone to, long time) {
    Calendar fromCalendar = Calendar.getInstance(from);
    fromCalendar.setTimeInMillis(time);
    Calendar toCalendar = Calendar.getInstance(to);
    toCalendar.setTimeInMillis(0);
    toCalendar.set(Calendar.ERA, fromCalendar.get(Calendar.ERA));
    toCalendar.set(Calendar.YEAR, fromCalendar.get(Calendar.YEAR));
    toCalendar.set(Calendar.MONTH, fromCalendar.get(Calendar.MONTH));
    toCalendar.set(Calendar.DAY_OF_MONTH, fromCalendar.get(Calendar.DAY_OF_MONTH));
    toCalendar.set(Calendar.HOUR_OF_DAY, 0);
    toCalendar.set(Calendar.MINUTE, 0);
    toCalendar.set(Calendar.SECOND, 0);
    toCalendar.set(Calendar.MILLISECOND, 0);
    return toCalendar.getTimeInMillis();
  }

  public static OGlobalProperty getGlobalProperty(final ODocument document, final int len) {
    final int id = (len * -1) - 1;
    return ODocumentInternal.getGlobalPropertyById(document, id);
  }

  public static int writeBinary(final BytesContainer bytes, final byte[] valueBytes) {
    final int pointer = OVarIntSerializer.write(bytes, valueBytes.length);
    final int start = bytes.alloc(valueBytes.length);
    System.arraycopy(valueBytes, 0, bytes.bytes, start, valueBytes.length);
    return pointer;
  }

  public static int writeOptimizedLink(final BytesContainer bytes, OIdentifiable link) {
    if (!link.getIdentity().isPersistent()) {
      try {
        final ORecord real = link.getRecord();
        if (real != null)
          link = real;
      } catch (ORecordNotFoundException ignored) {
        // IGNORE IT WILL FAIL THE ASSERT IN CASE
      }
    }
    if (link.getIdentity().getClusterId() < 0)
      throw new ODatabaseException("Impossible to serialize invalid link " + link.getIdentity());

    final int pos = OVarIntSerializer.write(bytes, link.getIdentity().getClusterId());
    OVarIntSerializer.write(bytes, link.getIdentity().getClusterPosition());
    return pos;
  }

  public static int writeNullLink(final BytesContainer bytes) {
    final int pos = OVarIntSerializer.write(bytes, NULL_RECORD_ID.getIdentity().getClusterId());
    OVarIntSerializer.write(bytes, NULL_RECORD_ID.getIdentity().getClusterPosition());
    return pos;
  }

  public static OType getTypeFromValueEmbedded(final Object fieldValue) {
    OType type = OType.getTypeByValue(fieldValue);
    if (type == OType.LINK && fieldValue instanceof ODocument && !((ODocument) fieldValue).getIdentity().isValid())
      type = OType.EMBEDDED;
    return type;
  }

  public static OTypeInterface getDeltaTypeFromValueEmbedded(final Object fieldValue) {
    if (fieldValue instanceof ODocumentDelta) {
      return ODeltaDocumentFieldType.DELTA_RECORD;
    }

    OType type = getTypeFromValueEmbedded(fieldValue);
    if (type == null) {
      return null;
    }
    return type;
  }

  public static int writeLinkCollection(final BytesContainer bytes, final Collection<OIdentifiable> value) {
    final int pos = OVarIntSerializer.write(bytes, value.size());

    final boolean disabledAutoConversion =
        value instanceof ORecordLazyMultiValue && ((ORecordLazyMultiValue) value).isAutoConvertToRecord();

    if (disabledAutoConversion)
      // AVOID TO FETCH RECORD
      ((ORecordLazyMultiValue) value).setAutoConvertToRecord(false);

    try {
      for (OIdentifiable itemValue : value) {
        // TODO: handle the null links
        if (itemValue == null)
          writeNullLink(bytes);
        else
          writeOptimizedLink(bytes, itemValue);
      }

    } finally {
      if (disabledAutoConversion)
        ((ORecordLazyMultiValue) value).setAutoConvertToRecord(true);
    }

    return pos;
  }

  public static Collection<OIdentifiable> readLinkCollection(final BytesContainer bytes, final Collection<OIdentifiable> found,
      boolean justRunThrough) {
    final int items = OVarIntSerializer.readAsInteger(bytes);
    for (int i = 0; i < items; i++) {
      ORecordId id = readOptimizedLink(bytes, justRunThrough);
      if (!justRunThrough) {
        if (id.equals(NULL_RECORD_ID))
          found.add(null);
        else
          found.add(id);
      }
    }
    return found;
  }

  public static int writeString(final BytesContainer bytes, final String toWrite) {
    final byte[] nameBytes = bytesFromString(toWrite);
    final int pointer = OVarIntSerializer.write(bytes, nameBytes.length);
    final int start = bytes.alloc(nameBytes.length);
    System.arraycopy(nameBytes, 0, bytes.bytes, start, nameBytes.length);
    return pointer;
  }

  public static int writeLinkMap(final BytesContainer bytes, final Map<Object, OIdentifiable> map) {
    final boolean disabledAutoConversion =
        map instanceof ORecordLazyMultiValue && ((ORecordLazyMultiValue) map).isAutoConvertToRecord();

    if (disabledAutoConversion)
      // AVOID TO FETCH RECORD
      ((ORecordLazyMultiValue) map).setAutoConvertToRecord(false);

    try {
      final int fullPos = OVarIntSerializer.write(bytes, map.size());
      for (Map.Entry<Object, OIdentifiable> entry : map.entrySet()) {
        writeString(bytes, entry.getKey().toString());
        if (entry.getValue() == null)
          writeNullLink(bytes);
        else
          writeOptimizedLink(bytes, entry.getValue());
      }
      return fullPos;

    } finally {
      if (disabledAutoConversion)
        ((ORecordLazyMultiValue) map).setAutoConvertToRecord(true);
    }
  }

  public static Map<Object, OIdentifiable> readLinkMap(final BytesContainer bytes, final ODocument document,
      boolean justRunThrough) {
    int size = OVarIntSerializer.readAsInteger(bytes);
    ORecordLazyMap result = null;
    if (!justRunThrough)
      result = new ORecordLazyMap(document);

    result.setInternalStatus(ORecordElement.STATUS.UNMARSHALLING);
    try {

      while ((size--) > 0) {
        final String key = readString(bytes);
        final ORecordId value = readOptimizedLink(bytes, justRunThrough);
        if (value.equals(NULL_RECORD_ID))
          result.put(key, null);
        else
          result.put(key, value);
      }
      return result;

    } finally {
      result.setInternalStatus(ORecordElement.STATUS.LOADED);
    }
  }

  public static void writeByte(BytesContainer bytes, byte val) {
    int pos = bytes.alloc(OByteSerializer.BYTE_SIZE);
    OByteSerializer.INSTANCE.serialize(val, bytes.bytes, pos);
  }

  public static void writeRidBag(BytesContainer bytes, ORidBag ridbag) {
    ridbag.checkAndConvert();

    UUID ownerUuid = ridbag.getTemporaryId();

    int positionOffset = bytes.offset;
    final OSBTreeCollectionManager sbTreeCollectionManager = ODatabaseRecordThreadLocal.instance().get()
        .getSbTreeCollectionManager();
    UUID uuid = null;
    if (sbTreeCollectionManager != null)
      uuid = sbTreeCollectionManager.listenForChanges(ridbag);

    byte configByte = 0;
    if (ridbag.isEmbedded())
      configByte |= 1;

    if (uuid != null)
      configByte |= 2;

    //alloc will move offset and do skip
    int posForWrite = bytes.alloc(OByteSerializer.BYTE_SIZE);
    OByteSerializer.INSTANCE.serialize(configByte, bytes.bytes, posForWrite);

    //removed serializing UUID

    if (ridbag.isEmbedded()) {
      writeEmbeddedRidbag(bytes, ridbag);
    } else {
      writeSBTreeRidbag(bytes, ridbag, ownerUuid);
    }
  }

  protected static void writeEmbeddedRidbag(BytesContainer bytes, ORidBag ridbag) {
    OVarIntSerializer.write(bytes, ridbag.size());
    Object[] entries = ((OEmbeddedRidBag) ridbag.getDelegate()).getEntries();
    ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.instance().getIfDefined();
    for (int i = 0; i < entries.length; i++) {
      Object entry = entries[i];
      if (entry instanceof OIdentifiable) {
        OIdentifiable itemValue = (OIdentifiable) entry;
        final ORID rid = itemValue.getIdentity();
        if (db != null && !db.isClosed() && db.getTransaction().isActive() && !itemValue.getIdentity().isPersistent()) {
          itemValue = db.getTransaction().getRecord(itemValue.getIdentity());
        }
        if (itemValue == null) {
          //should never happen
          String errorMessage = "Found null entry in ridbag with rid=" + rid;
          OSerializationException exc = new OSerializationException(errorMessage);
          OLogManager.instance().error(ORecordSerializerBinaryV1.class, errorMessage, null);
          throw exc;
        } else {
          entries[i] = itemValue.getIdentity();
          writeLinkOptimized(bytes, itemValue);
        }
      }
    }
  }

  protected static void writeSBTreeRidbag(BytesContainer bytes, ORidBag ridbag, UUID ownerUuid) {
    ((OSBTreeRidBag) ridbag.getDelegate()).applyNewEntries();

    OBonsaiCollectionPointer pointer = ridbag.getPointer();

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

    if (pointer == null && context != null) {
      final int clusterId = getHighLevelDocClusterId(ridbag);
      assert clusterId > -1;
      try {
        pointer = ODatabaseRecordThreadLocal.instance().get().getSbTreeCollectionManager().createSBTree(clusterId, ownerUuid);
      } catch (IOException e) {
        throw OException.wrapException(new ODatabaseException("Error during creation of ridbag"), e);
      }
    }

    ((OSBTreeRidBag) ridbag.getDelegate()).setCollectionPointer(pointer);

    OVarIntSerializer.write(bytes, pointer.getFileId());
    OVarIntSerializer.write(bytes, pointer.getRootPointer().getPageIndex());
    OVarIntSerializer.write(bytes, pointer.getRootPointer().getPageOffset());
    OVarIntSerializer.write(bytes, ridbag.size());

    if (context != null) {
      ((OSBTreeRidBag) ridbag.getDelegate()).handleContextSBTree(context, pointer);
      OVarIntSerializer.write(bytes, 0);
    } else {
      OVarIntSerializer.write(bytes, 0);

      //removed changes serialization
    }
  }

  private static int getHighLevelDocClusterId(ORidBag ridbag) {
    ORidBagDelegate delegate = ridbag.getDelegate();
    ORecordElement owner = delegate.getOwner();
    while (owner != null && owner.getOwner() != null) {
      owner = owner.getOwner();
    }

    if (owner != null)
      return ((OIdentifiable) owner).getIdentity().getClusterId();

    return -1;
  }

  public static void writeLinkOptimized(final BytesContainer bytes, OIdentifiable link) {
    ORID id = link.getIdentity();
    OVarIntSerializer.write(bytes, id.getClusterId());
    OVarIntSerializer.write(bytes, id.getClusterPosition());
  }

  public static ORidBag readRidbag(BytesContainer bytes) {
    byte configByte = OByteSerializer.INSTANCE.deserialize(bytes.bytes, bytes.offset++);
    boolean isEmbedded = (configByte & 1) != 0;

    UUID uuid = null;
    //removed deserializing UUID

    ORidBag ridbag = null;
    if (isEmbedded) {
      ridbag = new ORidBag();
      int size = OVarIntSerializer.readAsInteger(bytes);
      ridbag.getDelegate().setSize(size);
      for (int i = 0; i < size; i++) {
        OIdentifiable record = readLinkOptimizedEmbedded(bytes);
        ((OEmbeddedRidBag) ridbag.getDelegate()).addEntry(record);
      }
    } else {
      long fileId = OVarIntSerializer.readAsLong(bytes);
      long pageIndex = OVarIntSerializer.readAsLong(bytes);
      int pageOffset = OVarIntSerializer.readAsInteger(bytes);
      //read bag size
      OVarIntSerializer.readAsInteger(bytes);

      OBonsaiCollectionPointer pointer = null;
      if (fileId != -1)
        pointer = new OBonsaiCollectionPointer(fileId, new OBonsaiBucketPointer(pageIndex, pageOffset));

      Map<OIdentifiable, Change> changes = new HashMap<>();

      int changesSize = OVarIntSerializer.readAsInteger(bytes);
      for (int i = 0; i < changesSize; i++) {
        OIdentifiable recId = readLinkOptimizedSBTree(bytes);
        Change change = deserializeChange(bytes);
        changes.put(recId, change);
      }

      ridbag = new ORidBag(pointer, changes, uuid);
      ridbag.getDelegate().setSize(-1);
    }
    return ridbag;
  }

  private static OIdentifiable readLinkOptimizedEmbedded(final BytesContainer bytes) {
    ORID rid = new ORecordId(OVarIntSerializer.readAsInteger(bytes), OVarIntSerializer.readAsLong(bytes));
    OIdentifiable identifiable = null;
    if (rid.isTemporary())
      identifiable = rid.getRecord();

    if (identifiable == null)
      identifiable = rid;

    return identifiable;
  }

  private static OIdentifiable readLinkOptimizedSBTree(final BytesContainer bytes) {
    ORID rid = new ORecordId(OVarIntSerializer.readAsInteger(bytes), OVarIntSerializer.readAsLong(bytes));
    final OIdentifiable identifiable;
    if (rid.isTemporary() && rid.getRecord() != null)
      identifiable = rid.getRecord();
    else
      identifiable = rid;
    return identifiable;
  }

  private static Change deserializeChange(BytesContainer bytes) {
    byte type = OByteSerializer.INSTANCE.deserialize(bytes.bytes, bytes.offset);
    bytes.skip(OByteSerializer.BYTE_SIZE);
    int change = OIntegerSerializer.INSTANCE.deserialize(bytes.bytes, bytes.offset);
    bytes.skip(OIntegerSerializer.INT_SIZE);
    return ChangeSerializationHelper.createChangeInstance(type, change);
  }

  public static OType getLinkedType(ODocument document, OType type, String key) {
    if (type != OType.EMBEDDEDLIST && type != OType.EMBEDDEDSET && type != OType.EMBEDDEDMAP)
      return null;
    OClass immutableClass = ODocumentInternal.getImmutableSchemaClass(document);
    if (immutableClass != null) {
      OProperty prop = immutableClass.getProperty(key);
      if (prop != null) {
        return prop.getLinkedType();
      }
    }
    return null;
  }

}


/*
 *
 *  *  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.serialization;

import com.orientechnologies.common.exception.OException;
import com.orientechnologies.common.profiler.OAbstractProfiler.OProfilerHookValue;
import com.orientechnologies.common.profiler.OProfiler.METRIC_TYPE;
import com.orientechnologies.common.util.OArrays;
import com.orientechnologies.orient.core.Orient;
import com.orientechnologies.orient.core.exception.OSerializationException;

import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.util.Arrays;

/**
 * Class to parse and write buffers in very fast way.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 * @deprecated use {@link OByteArrayOutputStream} instead.
 */

@Deprecated
public class OMemoryStream extends OutputStream {
  public static final int DEF_SIZE = 1024;

  private byte[] buffer;
  private int    position;
  private Charset charset = Charset.forName("utf8");

  private static final int  NATIVE_COPY_THRESHOLD = 9;
  private static       long metricResize          = 0;

  static {
    Orient.instance().getProfiler()
        .registerHookValue("system.memory.stream.resize", "Number of resizes of memory stream buffer", METRIC_TYPE.COUNTER,
            new OProfilerHookValue() {
              public Object getValue() {
                return metricResize;
              }
            });
  }

  public OMemoryStream() {
    this(DEF_SIZE);
  }

  /**
   * Callee takes ownership of 'buf'.
   */
  public OMemoryStream(final int initialCapacity) {
    buffer = new byte[initialCapacity];
  }

  public OMemoryStream(byte[] stream) {
    buffer = stream;
  }

  /**
   * Move bytes left or right of an offset.
   *
   * @param iFrom     Starting position
   * @param iPosition Offset to the iFrom value: positive values mean move right, otherwise move left
   */
  public void move(final int iFrom, final int iPosition) {
    if (iPosition == 0)
      return;

    final int to = iFrom + iPosition;
    final int size = iPosition > 0 ? buffer.length - to : buffer.length - iFrom;

    System.arraycopy(buffer, iFrom, buffer, to, size);
  }

  public void copyFrom(final OMemoryStream iSource, final int iSize) {
    if (iSize < 0)
      return;

    assureSpaceFor(position + iSize);
    System.arraycopy(iSource.buffer, iSource.position, buffer, position, iSize);
  }

  public final void writeTo(final OutputStream out) throws IOException {
    out.write(buffer, 0, position);
  }

  public final byte[] getInternalBuffer() {
    return buffer;
  }

  /**
   * Returns the used buffer as byte[].
   *
   * @return [result.length = size()]
   */
  public final byte[] toByteArray() {
    if (position == buffer.length - 1)
      // 100% USED, RETURN THE FULL BUFFER
      return buffer;

    final int pos = position;

    final byte[] destinBuffer = new byte[pos];
    final byte[] sourceBuffer = buffer;

    if (pos < NATIVE_COPY_THRESHOLD)
      for (int i = 0; i < pos; ++i)
        destinBuffer[i] = sourceBuffer[i];
    else
      System.arraycopy(sourceBuffer, 0, destinBuffer, 0, pos);

    return destinBuffer;
  }

  /**
   * Does not reduce the current capacity.
   */
  public final void reset() {
    position = 0;
  }

  // OutputStream:

  @Override
  public final void write(final int b) {
    assureSpaceFor(OBinaryProtocol.SIZE_BYTE);
    buffer[position++] = (byte) b;
  }

  @Override
  public final void write(final byte[] iBuffer, final int iOffset, final int iLength) {
    final int pos = position;
    final int tot = pos + iLength;

    assureSpaceFor(iLength);

    final byte[] localBuffer = buffer;

    if (iLength < NATIVE_COPY_THRESHOLD)
      for (int i = 0; i < iLength; ++i)
        localBuffer[pos + i] = iBuffer[iOffset + i];
    else
      System.arraycopy(iBuffer, iOffset, localBuffer, pos, iLength);

    position = tot;
  }

  /**
   * Equivalent to {@link #reset()}.
   */
  @Override
  public final void close() {
    reset();
  }

  public final void setAsFixed(final byte[] iContent) {
    if (iContent == null)
      return;
    write(iContent, 0, iContent.length);
  }

  /**
   * Append byte[] to the stream.
   *
   * @param iContent
   * @return The begin offset of the appended content
   * @throws IOException
   */
  public int set(final byte[] iContent) {
    if (iContent == null)
      return -1;

    final int begin = position;

    assureSpaceFor(OBinaryProtocol.SIZE_INT + iContent.length);

    OBinaryProtocol.int2bytes(iContent.length, buffer, position);
    position += OBinaryProtocol.SIZE_INT;
    write(iContent, 0, iContent.length);

    return begin;
  }

  public void remove(final int iBegin, final int iEnd) {
    if (iBegin > iEnd)
      throw new IllegalArgumentException("Begin is bigger than end");

    if (iEnd > buffer.length)
      throw new IndexOutOfBoundsException("Position " + iEnd + " is greater than the buffer length (" + buffer.length + ")");

    System.arraycopy(buffer, iEnd, buffer, iBegin, buffer.length - iEnd);
  }

  public void set(final byte iContent) {
    write(iContent);
  }

  public final int setCustom(final String iContent) {
    try {
      return set(iContent.getBytes("UTF-8"));
    } catch (UnsupportedEncodingException e) {
      throw OException.wrapException(new OSerializationException("error encoding string"),e);
    }
  }

  public final int setUtf8(final String iContent) {
    return set(iContent.getBytes(charset));
  }

  public int set(final boolean iContent) {
    final int begin = position;
    write((byte) (iContent ? 1 : 0));
    return begin;
  }

  public int set(final char iContent) {
    assureSpaceFor(OBinaryProtocol.SIZE_CHAR);
    final int begin = position;
    OBinaryProtocol.char2bytes(iContent, buffer, position);
    position += OBinaryProtocol.SIZE_CHAR;
    return begin;
  }

  public int set(final int iContent) {
    assureSpaceFor(OBinaryProtocol.SIZE_INT);
    final int begin = position;
    OBinaryProtocol.int2bytes(iContent, buffer, position);
    position += OBinaryProtocol.SIZE_INT;
    return begin;
  }

  public int set(final long iContent) {
    assureSpaceFor(OBinaryProtocol.SIZE_LONG);
    final int begin = position;
    OBinaryProtocol.long2bytes(iContent, buffer, position);
    position += OBinaryProtocol.SIZE_LONG;
    return begin;
  }

  public int set(final short iContent) {
    assureSpaceFor(OBinaryProtocol.SIZE_SHORT);
    final int begin = position;
    OBinaryProtocol.short2bytes(iContent, buffer, position);
    position += OBinaryProtocol.SIZE_SHORT;
    return begin;
  }

  public int getPosition() {
    return position;
  }

  public OMemoryStream setPosition(final int iPosition) {
    position = iPosition;
    return this;
  }

  private void assureSpaceFor(final int iLength) {
    final byte[] localBuffer = buffer;
    final int pos = position;
    final int capacity = position + iLength;

    final int bufferLength = localBuffer.length;

    if (bufferLength < capacity) {
      metricResize++;

      final byte[] newbuf = new byte[Math.max(bufferLength << 1, capacity)];

      if (pos < NATIVE_COPY_THRESHOLD)
        for (int i = 0; i < pos; ++i)
          newbuf[i] = localBuffer[i];
      else
        System.arraycopy(localBuffer, 0, newbuf, 0, pos);

      buffer = newbuf;
    }
  }

  /**
   * Jumps bytes positioning forward of passed bytes.
   *
   * @param iLength Bytes to jump
   */
  public void fill(final int iLength) {
    assureSpaceFor(iLength);
    position += iLength;
  }

  /**
   * Fills the stream from current position writing iLength times the iFiller byte
   *
   * @param iLength Bytes to jump
   * @param iFiller Byte to use to fill the space
   */
  public void fill(final int iLength, final byte iFiller) {
    assureSpaceFor(iLength);
    Arrays.fill(buffer, position, position + iLength, iFiller);
    position += iLength;
  }

  public OMemoryStream jump(final int iOffset) {
    if (iOffset > buffer.length)
      throw new IndexOutOfBoundsException("Offset " + iOffset + " is greater than the buffer size " + buffer.length);
    position = iOffset;
    return this;
  }

  public byte[] getAsByteArrayFixed(final int iSize) {
    if (position >= buffer.length)
      return null;

    final byte[] portion = OArrays.copyOfRange(buffer, position, position + iSize);
    position += iSize;

    return portion;
  }

  /**
   * Browse the stream but just return the begin of the byte array. This is used to lazy load the information only when needed.
   */
  public int getAsByteArrayOffset() {
    if (position >= buffer.length)
      return -1;

    final int begin = position;

    final int size = OBinaryProtocol.bytes2int(buffer, position);
    position += OBinaryProtocol.SIZE_INT + size;

    return begin;
  }

  public int read() {
    return buffer[position++];
  }

  public int read(final byte[] b) {
    return read(b, 0, b.length);
  }

  public int read(final byte[] b, final int off, final int len) {
    if (position >= buffer.length)
      return 0;

    System.arraycopy(buffer, position, b, off, len);
    position += len;

    return len;
  }

  public byte[] getAsByteArray(int iOffset) {
    if (buffer == null || iOffset >= buffer.length)
      return null;

    final int size = OBinaryProtocol.bytes2int(buffer, iOffset);

    if (size == 0)
      return null;

    iOffset += OBinaryProtocol.SIZE_INT;

    return OArrays.copyOfRange(buffer, iOffset, iOffset + size);
  }

  public byte[] getAsByteArray() {
    if (position >= buffer.length)
      return null;

    final int size = OBinaryProtocol.bytes2int(buffer, position);
    position += OBinaryProtocol.SIZE_INT;

    final byte[] portion = OArrays.copyOfRange(buffer, position, position + size);
    position += size;

    return portion;
  }

  /**
   * Returns the available bytes to read.
   */
  public int available() {
    return buffer.length - position;
  }

  public String getAsString() {
    if (position >= buffer.length)
      return null;

    final int size = getVariableSize();
    String str = new String(buffer, position, size, charset);
    position += size;
    return str;
  }

  public boolean getAsBoolean() {
    return buffer[position++] == 1;
  }

  public char getAsChar() {
    final char value = OBinaryProtocol.bytes2char(buffer, position);
    position += OBinaryProtocol.SIZE_CHAR;
    return value;
  }

  public byte getAsByte() {
    return buffer[position++];
  }

  public long getAsLong() {
    final long value = OBinaryProtocol.bytes2long(buffer, position);
    position += OBinaryProtocol.SIZE_LONG;
    return value;
  }

  public int getAsInteger() {
    final int value = OBinaryProtocol.bytes2int(buffer, position);
    position += OBinaryProtocol.SIZE_INT;
    return value;
  }

  public short getAsShort() {
    final short value = OBinaryProtocol.bytes2short(buffer, position);
    position += OBinaryProtocol.SIZE_SHORT;
    return value;
  }

  public byte peek() {
    return buffer[position];
  }

  public void setSource(final byte[] iBuffer) {
    buffer = iBuffer;
    position = 0;
  }

  public byte[] copy() {
    if (buffer == null)
      return null;

    final int size = position > 0 ? position : buffer.length;

    final byte[] copy = new byte[size];
    System.arraycopy(buffer, 0, copy, 0, size);
    return copy;
  }

  public int getVariableSize() {
    if (position >= buffer.length)
      return -1;

    final int size = OBinaryProtocol.bytes2int(buffer, position);
    position += OBinaryProtocol.SIZE_INT;

    return size;
  }

  public int getSize() {
    return buffer.length;
  }

  public final int size() {
    return position;
  }
}


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

/**
 * Created by luigidellaquila on 08/08/16.
 */

import com.orientechnologies.orient.core.command.OCommandContext;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * @author Luigi Dell'Aquila (l.dellaquila-(at)-orientdb.com)
 */
public class OScriptExecutionPlan implements OInternalExecutionPlan {

  private String location;

  private final OCommandContext ctx;

  boolean executed = false;

  protected List<ScriptLineStep> steps = new ArrayList<>();

  OExecutionStepInternal lastStep = null;

  OResultSet finalResult = null;

  public OScriptExecutionPlan(OCommandContext ctx) {
    this.ctx = ctx;
  }

  @Override
  public void reset(OCommandContext ctx) {
    //TODO
    throw new UnsupportedOperationException();
  }

  @Override
  public void close() {
    lastStep.close();
  }

  @Override
  public OResultSet fetchNext(int n) {
    doExecute(n);
    return new OResultSet() {
      int totalFetched = 0;

      @Override
      public boolean hasNext() {
        return finalResult.hasNext() && totalFetched < n;
      }

      @Override
      public OResult next() {
        if (!hasNext()) {
          throw new IllegalStateException();
        }
        return finalResult.next();
      }

      @Override
      public void close() {
        finalResult.close();
      }

      @Override
      public Optional<OExecutionPlan> getExecutionPlan() {
        return finalResult == null ? Optional.empty() : finalResult.getExecutionPlan();
      }

      @Override
      public Map<String, Long> getQueryStats() {
        return null;
      }
    };
  }

  private void doExecute(int n) {
    if (!executed) {
      executeUntilReturn();
      executed = true;
      finalResult = new OInternalResultSet();
      OResultSet partial = lastStep.syncPull(ctx, n);
      while (partial.hasNext()) {
        while (partial.hasNext()) {
          ((OInternalResultSet) finalResult).add(partial.next());
        }
        partial = lastStep.syncPull(ctx, n);
      }
      if (lastStep instanceof ScriptLineStep) {
        ((OInternalResultSet) finalResult).setPlan(((ScriptLineStep) lastStep).plan);
      }
    }
  }

  @Override
  public String prettyPrint(int depth, int indent) {
    StringBuilder result = new StringBuilder();
    for (int i = 0; i < steps.size(); i++) {
      OExecutionStepInternal step = steps.get(i);
      result.append(step.prettyPrint(depth, indent));
      if (i < steps.size() - 1) {
        result.append("\n");
      }
    }
    return result.toString();
  }

  public void chain(OInternalExecutionPlan nextPlan, boolean profilingEnabled) {
    ScriptLineStep lastStep = steps.size() == 0 ? null : steps.get(steps.size() - 1);
    ScriptLineStep nextStep = new ScriptLineStep(nextPlan, ctx, profilingEnabled);
    if (lastStep != null) {
      lastStep.setNext(nextStep);
      nextStep.setPrevious(lastStep);
    }
    steps.add(nextStep);
    this.lastStep = nextStep;
  }

  @Override
  public List<OExecutionStep> getSteps() {
    //TODO do a copy of the steps
    return (List) steps;
  }

  public void setSteps(List<OExecutionStepInternal> steps) {
    this.steps = (List) steps;
  }

  @Override
  public OResult toResult() {
    OResultInternal result = new OResultInternal();
    result.setProperty("type", "ScriptExecutionPlan");
    result.setProperty("javaType", getClass().getName());
    result.setProperty("cost", getCost());
    result.setProperty("prettyPrint", prettyPrint(0, 2));
    result.setProperty("steps", steps == null ? null : steps.stream().map(x -> x.toResult()).collect(Collectors.toList()));
    return result;
  }

  @Override
  public long getCost() {
    return 0l;
  }

  @Override
  public boolean canBeCached() {
    return false;
  }

  public boolean containsReturn() {
    for (OExecutionStepInternal step : steps) {
      if (step instanceof ReturnStep) {
        return true;
      }
      if (step instanceof ScriptLineStep) {
        return ((ScriptLineStep) step).containsReturn();
      }
    }

    return false;
  }

  /**
   * executes all the script and returns last statement execution step, so that it can be executed from outside
   *
   * @return
   */
  public OExecutionStepInternal executeUntilReturn() {
    if (steps.size() > 0) {
      lastStep = steps.get(steps.size() - 1);
    }
    for (int i = 0; i < steps.size() - 1; i++) {
      ScriptLineStep step = steps.get(i);
      if (step.containsReturn()) {
        OExecutionStepInternal returnStep = step.executeUntilReturn(ctx);
        if (returnStep != null) {
          lastStep = returnStep;
          return lastStep;
        }
      }
      OResultSet lastResult = step.syncPull(ctx, 100);

      while (lastResult.hasNext()) {
        while (lastResult.hasNext()) {
          lastResult.next();
        }
        lastResult = step.syncPull(ctx, 100);
      }
    }
    this.lastStep = steps.get(steps.size() - 1);
    return lastStep;
  }

  /**
   * executes the whole script and returns last statement ONLY if it's a RETURN, otherwise it returns null;
   *
   * @return
   */
  public OExecutionStepInternal executeFull() {
    for (int i = 0; i < steps.size(); i++) {
      ScriptLineStep step = steps.get(i);
      if (step.containsReturn()) {
        OExecutionStepInternal returnStep = step.executeUntilReturn(ctx);
        if (returnStep != null) {
          return returnStep;
        }
      }
      OResultSet lastResult = step.syncPull(ctx, 100);

      while (lastResult.hasNext()) {
        while (lastResult.hasNext()) {
          lastResult.next();
        }
        lastResult = step.syncPull(ctx, 100);
      }
    }

    return null;
  }
}


/*
 *
 *  *  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.object.serialization;

import com.orientechnologies.common.exception.OException;
import com.orientechnologies.common.io.OUtils;
import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.common.reflection.OReflectionHelper;
import com.orientechnologies.orient.core.Orient;
import com.orientechnologies.orient.core.annotation.*;
import com.orientechnologies.orient.core.db.OUserObject2RecordHandler;
import com.orientechnologies.orient.core.db.object.ODatabaseObject;
import com.orientechnologies.orient.core.db.record.*;
import com.orientechnologies.orient.core.entity.OEntityManager;
import com.orientechnologies.orient.core.exception.OConfigurationException;
import com.orientechnologies.orient.core.exception.OSchemaException;
import com.orientechnologies.orient.core.exception.OSerializationException;
import com.orientechnologies.orient.core.exception.OTransactionException;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.id.ORecordId;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.metadata.schema.OProperty;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.record.ORecordInternal;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.serialization.serializer.record.OSerializationThreadLocal;
import com.orientechnologies.orient.core.tx.OTransactionOptimistic;
import com.orientechnologies.orient.object.db.OObjectLazyList;
import com.orientechnologies.orient.object.db.OObjectLazyMap;
import com.orientechnologies.orient.object.db.OObjectNotDetachedException;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import java.util.Map.Entry;

@SuppressWarnings("unchecked")
/**
 * Helper class to manage POJO by using the reflection.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 * @author Luca Molino
 * @author Jacques Desodt
 */
public class OObjectSerializerHelper {
  public static final Class<?>[]                            callbackAnnotationClasses = new Class[] { OBeforeDeserialization.class,
      OAfterDeserialization.class, OBeforeSerialization.class, OAfterSerialization.class };
  private static final Class<?>[]                           NO_ARGS                   = new Class<?>[] {};
  private static final HashMap<String, List<Field>>         classes                   = new HashMap<String, List<Field>>();
  private static final HashMap<String, Method>              callbacks                 = new HashMap<String, Method>();
  private static final HashMap<String, Object>              getters                   = new HashMap<String, Object>();
  private static final HashMap<String, Object>              setters                   = new HashMap<String, Object>();
  private static final HashMap<Class<?>, Field>             boundDocumentFields       = new HashMap<Class<?>, Field>();
  private static final HashMap<Class<?>, Field>             fieldIds                  = new HashMap<Class<?>, Field>();
  private static final HashMap<Class<?>, Field>             fieldVersions             = new HashMap<Class<?>, Field>();
  private static final HashMap<Class<?>, List<String>>      embeddedFields            = new HashMap<Class<?>, List<String>>();
  public static HashMap<Class<?>, OObjectSerializerContext> serializerContexts        = new LinkedHashMap<Class<?>, OObjectSerializerContext>();
  @SuppressWarnings("rawtypes")
  public static Class                                       jpaIdClass;
  @SuppressWarnings("rawtypes")
  public static Class                                       jpaVersionClass;
  @SuppressWarnings("rawtypes")
  public static Class                                       jpaAccessClass;
  @SuppressWarnings("rawtypes")
  public static Class                                       jpaEmbeddedClass;
  @SuppressWarnings("rawtypes")
  public static Class                                       jpaTransientClass;
  @SuppressWarnings("rawtypes")
  public static Class                                       jpaOneToOneClass;
  @SuppressWarnings("rawtypes")
  public static Class                                       jpaOneToManyClass;
  @SuppressWarnings("rawtypes")
  public static Class                                       jpaManyToManyClass;

  static {
    try {
      // DETERMINE IF THERE IS AVAILABLE JPA 1
      jpaIdClass = Class.forName("javax.persistence.Id");
      jpaVersionClass = Class.forName("javax.persistence.Version");
      jpaEmbeddedClass = Class.forName("javax.persistence.Embedded");
      jpaTransientClass = Class.forName("javax.persistence.Transient");
      jpaOneToOneClass = Class.forName("javax.persistence.OneToOne");
      jpaOneToManyClass = Class.forName("javax.persistence.OneToMany");
      jpaManyToManyClass = Class.forName("javax.persistence.ManyToMany");
      // DETERMINE IF THERE IS AVAILABLE JPA 2
      jpaAccessClass = Class.forName("javax.persistence.Access");

    } catch (Exception e) {
      // IGNORE THE EXCEPTION: JPA NOT FOUND
    }
  }

  public static boolean hasField(final Object iPojo, final String iProperty) {
    final Class<?> c = iPojo.getClass();
    final String className = c.getName();

    getClassFields(c);

    return getters.get(className + "." + iProperty) != null;
  }

  public static String getDocumentBoundField(final Class<?> iClass) {
    getClassFields(iClass);
    final Field f = boundDocumentFields.get(iClass);
    return f != null ? f.getName() : null;
  }

  public static Class<?> getFieldType(final Object iPojo, final String iProperty) {
    final Class<?> c = iPojo.getClass();
    final String className = c.getName();

    getClassFields(c);

    try {
      final Object o = getters.get(className + "." + iProperty);

      if (o == null)
        return null;
      else if (o instanceof Field)
        return ((Field) o).getType();
      else
        return ((Method) o).getReturnType();
    } catch (Exception e) {
      throw OException.wrapException(new OSchemaException("Cannot get the value of the property: " + iProperty), e);
    }
  }

  public static Class<?> getFieldType(ODocument iDocument, final OEntityManager iEntityManager) {
    if (iDocument.getInternalStatus() == ORecordElement.STATUS.NOT_LOADED)
      iDocument = (ODocument) iDocument.load();

    if (iDocument.getClassName() == null) {
      return null;
    } else {
      return iEntityManager.getEntityClass(iDocument.getClassName());
    }
  }

  public static Object getFieldValue(final Object iPojo, final String iProperty) {
    final Class<?> c = iPojo.getClass();
    final String className = c.getName();

    getClassFields(c);

    try {
      Object o = getters.get(className + "." + iProperty);

      if (o instanceof Method)
        return ((Method) o).invoke(iPojo);
      else if (o instanceof Field)
        return ((Field) o).get(iPojo);
      return null;
    } catch (Exception e) {
      throw OException.wrapException(new OSchemaException("Cannot get the value of the property: " + iProperty), e);
    }
  }

  public static void setFieldValue(final Object iPojo, final String iProperty, final Object iValue) {
    final Class<?> c = iPojo.getClass();
    final String className = c.getName();

    getClassFields(c);

    try {
      Object o = setters.get(className + "." + iProperty);

      if (o instanceof Method) {
        ((Method) o).invoke(iPojo,
            OObjectSerializerHelper.convertInObject(iPojo, iProperty, iValue, ((Method) o).getParameterTypes()[0]));
      } else if (o instanceof Field) {
        ((Field) o).set(iPojo, OType.convert(iValue, ((Field) o).getType()));
      }

    } catch (Exception e) {

      throw OException.wrapException(
          new OSchemaException("Cannot set the value '" + iValue + "' to the property '" + iProperty + "' for the pojo: " + iPojo),
          e);
    }
  }

  public static String setObjectID(final ORID iIdentity, final Object iPojo) {
    if (iPojo == null)
      return null;

    final Class<?> pojoClass = iPojo.getClass();

    getClassFields(pojoClass);

    final Field idField = fieldIds.get(pojoClass);
    if (idField != null) {
      Class<?> fieldType = idField.getType();

      final String idFieldName = idField.getName();

      if (ORID.class.isAssignableFrom(fieldType))
        setFieldValue(iPojo, idFieldName, iIdentity);
      else if (Number.class.isAssignableFrom(fieldType))
        setFieldValue(iPojo, idFieldName, iIdentity != null ? iIdentity.getClusterPosition() : null);
      else if (fieldType.equals(String.class))
        setFieldValue(iPojo, idFieldName, iIdentity != null ? iIdentity.toString() : null);
      else if (fieldType.equals(Object.class))
        setFieldValue(iPojo, idFieldName, iIdentity);
      else
        OLogManager.instance().warn(OObjectSerializerHelper.class,
            "@Id field has been declared as %s while the supported are: ORID, Number, String, Object", fieldType);
      return idFieldName;
    }
    return null;
  }

  public static ORecordId getObjectID(final ODatabaseObject iDb, final Object iPojo) {
    getClassFields(iPojo.getClass());

    final Field idField = fieldIds.get(iPojo.getClass());
    if (idField != null) {
      final Object id = getFieldValue(iPojo, idField.getName());

      if (id != null) {
        // FOUND
        if (id instanceof ORecordId) {
          return (ORecordId) id;
        } else if (id instanceof Number) {
          // TREATS AS CLUSTER POSITION
          final OClass cls = iDb.getMetadata().getSchema().getClass(iPojo.getClass());
          if (cls == null)
            throw new OConfigurationException("Class " + iPojo.getClass() + " is not managed by current database");

          return new ORecordId(cls.getDefaultClusterId(), ((Number) id).longValue());
        } else if (id instanceof String)
          return new ORecordId((String) id);
      }
    }
    return null;
  }

  public static String getObjectIDFieldName(final Object iPojo) {
    getClassFields(iPojo.getClass());

    final Field idField = fieldIds.get(iPojo.getClass());
    if (idField != null) {
      return idField.getName();
    }
    return null;
  }

  public static boolean hasObjectID(final Object iPojo) {
    getClassFields(iPojo.getClass());
    return fieldIds.get(iPojo.getClass()) != null;
  }

  public static String setObjectVersion(final int iVersion, final Object iPojo) {
    if (iPojo == null)
      return null;

    final Class<?> pojoClass = iPojo.getClass();
    getClassFields(pojoClass);

    final Field vField = fieldVersions.get(pojoClass);
    if (vField != null) {
      Class<?> fieldType = vField.getType();

      final String vFieldName = vField.getName();

      if (Integer.TYPE.isAssignableFrom(fieldType)) {
        setFieldValue(iPojo, vFieldName, iVersion);
      } else if (Number.class.isAssignableFrom(fieldType)) {
        setFieldValue(iPojo, vFieldName, iVersion);
      } else if (fieldType.equals(String.class))
        setFieldValue(iPojo, vFieldName, String.valueOf(iVersion));
      else
        OLogManager.instance().warn(OObjectSerializerHelper.class,
            "@Version field has been declared as %s while the supported are: Number, String, Object", fieldType);
      return vFieldName;
    }
    return null;
  }

  public static int getObjectVersion(final Object iPojo) {
    getClassFields(iPojo.getClass());
    final Field idField = fieldVersions.get(iPojo.getClass());
    if (idField != null) {
      final Object ver = getFieldValue(iPojo, idField.getName());

      return convertVersion(ver);
    }
    throw new OObjectNotDetachedException("Cannot retrieve the object's VERSION for '" + iPojo + "' because has not been detached");
  }

  private static int convertVersion(final Object ver) {
    if (ver != null) {
      if (ver instanceof Number) {
        // TREATS AS CLUSTER POSITION
        return ((Number) ver).intValue();

      } else if (ver instanceof String) {
        return Integer.parseInt((String) ver);
      } else
        OLogManager.instance().warn(OObjectSerializerHelper.class,
            "@Version field has been declared as %s while the supported are: Number, String, Object", ver.getClass());
    }
    return -1;
  }

  public static String getObjectVersionFieldName(final Object iPojo) {
    getClassFields(iPojo.getClass());

    final Field idField = fieldVersions.get(iPojo.getClass());
    if (idField != null) {
      return idField.getName();
    }
    return null;
  }

  public static boolean hasObjectVersion(final Object iPojo) {
    getClassFields(iPojo.getClass());
    return fieldVersions.get(iPojo.getClass()) != null;
  }

  /**
   * Serialize the user POJO to a ORecordDocument instance.
   *
   * @param iPojo
   *          User pojo to serialize
   * @param iRecord
   *          Record where to update
   * @param iObj2RecHandler
   */
  public static ODocument toStream(final Object iPojo, final ODocument iRecord, final OEntityManager iEntityManager,
      final OClass schemaClass, final OUserObject2RecordHandler iObj2RecHandler, final ODatabaseObject db,
      final boolean iSaveOnlyDirty) {
    if (iSaveOnlyDirty && !iRecord.isDirty())
      return iRecord;

    final long timer = Orient.instance().getProfiler().startChrono();

    final Integer identityRecord = System.identityHashCode(iRecord);

    if (OSerializationThreadLocal.INSTANCE.get().contains(identityRecord))
      return iRecord;

    OSerializationThreadLocal.INSTANCE.get().add(identityRecord);

    OProperty schemaProperty;

    final Class<?> pojoClass = iPojo.getClass();

    final List<Field> properties = getClassFields(pojoClass);

    // CHECK FOR ID BINDING
    final Field idField = fieldIds.get(pojoClass);
    if (idField != null) {
      Object id = getFieldValue(iPojo, idField.getName());
      if (id != null) {
        // FOUND
        if (id instanceof ORecordId) {
          ORecordInternal.setIdentity(iRecord, (ORecordId) id);
        } else if (id instanceof Number) {
          // TREATS AS CLUSTER POSITION
          ((ORecordId) iRecord.getIdentity()).setClusterId(schemaClass.getDefaultClusterId());
          ((ORecordId) iRecord.getIdentity()).setClusterPosition(((Number) id).longValue());
        } else if (id instanceof String)
          ((ORecordId) iRecord.getIdentity()).fromString((String) id);
        else if (id.getClass().equals(Object.class))
          ORecordInternal.setIdentity(iRecord, (ORecordId) id);
        else
          OLogManager.instance().warn(OObjectSerializerHelper.class,
              "@Id field has been declared as %s while the supported are: ORID, Number, String, Object", id.getClass());
      }
    }

    // CHECK FOR VERSION BINDING
    final Field vField = fieldVersions.get(pojoClass);
    boolean versionConfigured = false;
    if (vField != null) {
      versionConfigured = true;
      Object ver = getFieldValue(iPojo, vField.getName());

      final int version = convertVersion(ver);
      ORecordInternal.setVersion(iRecord, version);
    }

    if (db.isMVCC() && !versionConfigured && db.getTransaction() instanceof OTransactionOptimistic)
      throw new OTransactionException("Cannot involve an object of class '" + pojoClass
          + "' in an Optimistic Transaction commit because it does not define @Version or @OVersion and therefore cannot handle MVCC");

    // SET OBJECT CLASS
    iRecord.setClassName(schemaClass != null ? schemaClass.getName() : null);

    String fieldName;
    Object fieldValue;

    // CALL BEFORE MARSHALLING
    invokeCallback(iPojo, iRecord, OBeforeSerialization.class);

    for (Field p : properties) {
      fieldName = p.getName();

      if (idField != null && fieldName.equals(idField.getName()))
        continue;

      if (vField != null && fieldName.equals(vField.getName()))
        continue;

      fieldValue = serializeFieldValue(getFieldType(iPojo, fieldName), getFieldValue(iPojo, fieldName));

      schemaProperty = schemaClass != null ? schemaClass.getProperty(fieldName) : null;

      if (fieldValue != null) {
        if (isEmbeddedObject(iPojo.getClass(), fieldValue.getClass(), fieldName, iEntityManager)) {
          // AUTO CREATE SCHEMA PROPERTY
          if (schemaClass == null) {
            db.getMetadata().getSchema().createClass(iPojo.getClass());
            iRecord.setClassNameIfExists(iPojo.getClass().getSimpleName());
          }

          if (schemaProperty == null) {
            OType t = OType.getTypeByClass(fieldValue.getClass());
            if (t == null)
              t = OType.EMBEDDED;
            schemaProperty = iRecord.getSchemaClass().createProperty(fieldName, t);
          }
        }
      }

      fieldValue = typeToStream(fieldValue, schemaProperty != null ? schemaProperty.getType() : null, iEntityManager,
          iObj2RecHandler, db, iRecord, iSaveOnlyDirty);

      iRecord.field(fieldName, fieldValue);
    }

    iObj2RecHandler.registerUserObject(iPojo, iRecord);

    // CALL AFTER MARSHALLING
    invokeCallback(iPojo, iRecord, OAfterSerialization.class);

    OSerializationThreadLocal.INSTANCE.get().remove(identityRecord);

    Orient.instance().getProfiler().stopChrono("Object.toStream", "Serialize object to stream", timer);

    return iRecord;
  }

  public static Object serializeFieldValue(final Class<?> type, final Object iFieldValue) {
    for (Class<?> classContext : serializerContexts.keySet()) {
      if (classContext != null && classContext.isAssignableFrom(type)) {
        return serializerContexts.get(classContext).serializeFieldValue(type, iFieldValue);
      }
    }

    if (serializerContexts.get(null) != null)
      return serializerContexts.get(null).serializeFieldValue(type, iFieldValue);

    return iFieldValue;
  }

  public static Object unserializeFieldValue(final Class<?> type, final Object iFieldValue) {
    for (Class<?> classContext : serializerContexts.keySet()) {
      if (classContext != null && classContext.isAssignableFrom(type)) {
        return serializerContexts.get(classContext).unserializeFieldValue(type, iFieldValue);
      }
    }

    if (serializerContexts.get(null) != null)
      return serializerContexts.get(null).unserializeFieldValue(type, iFieldValue);

    return iFieldValue;
  }

  private static Object typeToStream(Object iFieldValue, OType iType, final OEntityManager iEntityManager,
      final OUserObject2RecordHandler iObj2RecHandler, final ODatabaseObject db, final ODocument iRecord,
      final boolean iSaveOnlyDirty) {
    if (iFieldValue == null)
      return null;

    if (!OType.isSimpleType(iFieldValue)) {
      Class<?> fieldClass = iFieldValue.getClass();

      if (fieldClass.isArray()) {
        // ARRAY
        iFieldValue = multiValueToStream(Arrays.asList(iFieldValue), iType, iEntityManager, iObj2RecHandler, db, iRecord,
            iSaveOnlyDirty);
      } else if (Collection.class.isAssignableFrom(fieldClass)) {
        // COLLECTION (LIST OR SET)
        iFieldValue = multiValueToStream(iFieldValue, iType, iEntityManager, iObj2RecHandler, db, iRecord, iSaveOnlyDirty);
      } else if (Map.class.isAssignableFrom(fieldClass)) {
        // MAP
        iFieldValue = multiValueToStream(iFieldValue, iType, iEntityManager, iObj2RecHandler, db, iRecord, iSaveOnlyDirty);
      } else if (fieldClass.isEnum()) {
        // ENUM
        iFieldValue = ((Enum<?>) iFieldValue).name();
      } else {
        // LINK OR EMBEDDED
        fieldClass = iEntityManager.getEntityClass(fieldClass.getSimpleName());
        if (fieldClass != null) {
          // RECOGNIZED TYPE, SERIALIZE IT
          final ODocument linkedDocument = (ODocument) iObj2RecHandler.getRecordByUserObject(iFieldValue, true);

          final Object pojo = iFieldValue;
          iFieldValue = toStream(pojo, linkedDocument, iEntityManager, linkedDocument.getSchemaClass(), iObj2RecHandler, db,
              iSaveOnlyDirty);

          iObj2RecHandler.registerUserObject(pojo, linkedDocument);

        } else {
          final Object result = serializeFieldValue(null, iFieldValue);
          if (iFieldValue == result)
            throw new OSerializationException("Linked type [" + iFieldValue.getClass() + ":" + iFieldValue
                + "] cannot be serialized because is not part of registered entities. To fix this error register this class");

          iFieldValue = result;
        }
      }
    }
    return iFieldValue;
  }

  private static Object multiValueToStream(final Object iMultiValue, OType iType, final OEntityManager iEntityManager,
      final OUserObject2RecordHandler iObj2RecHandler, final ODatabaseObject db, final ODocument iRecord,
      final boolean iSaveOnlyDirty) {
    if (iMultiValue == null)
      return null;

    final Collection<Object> sourceValues;
    if (iMultiValue instanceof Collection<?>) {
      sourceValues = (Collection<Object>) iMultiValue;
    } else {
      sourceValues = (Collection<Object>) ((Map<?, ?>) iMultiValue).values();
    }

    if (iType == null) {
      if (sourceValues.size() == 0)
        return iMultiValue;

      // TRY TO UNDERSTAND THE COLLECTION TYPE BY ITS CONTENT
      final Object firstValue = sourceValues.iterator().next();

      if (firstValue == null)
        return iMultiValue;

      // DETERMINE THE RIGHT TYPE BASED ON SOURCE MULTI VALUE OBJECT
      if (OType.isSimpleType(firstValue)) {
        if (iMultiValue instanceof List)
          iType = OType.EMBEDDEDLIST;
        else if (iMultiValue instanceof Set)
          iType = OType.EMBEDDEDSET;
        else
          iType = OType.EMBEDDEDMAP;
      } else {
        if (iMultiValue instanceof List)
          iType = OType.LINKLIST;
        else if (iMultiValue instanceof Set)
          iType = OType.LINKSET;
        else
          iType = OType.LINKMAP;
      }
    }

    Object result = iMultiValue;
    final OType linkedType;

    // CREATE THE RETURN MULTI VALUE OBJECT BASED ON DISCOVERED TYPE
    if (iType.equals(OType.EMBEDDEDSET) || iType.equals(OType.LINKSET)) {
      if (iRecord != null && iType.equals(OType.EMBEDDEDSET))
        result = new OTrackedSet<Object>(iRecord);
      else
        result = new ORecordLazySet(iRecord);
    } else if (iType.equals(OType.EMBEDDEDLIST) || iType.equals(OType.LINKLIST)) {
      if (iRecord != null && iType.equals(OType.EMBEDDEDLIST))
        result = new OTrackedList<Object>(iRecord);
      else
        result = new ArrayList<Object>();
    }
    // } else if (iType.equals(OType.EMBEDDEDLIST) ||
    // iType.equals(OType.LINKLIST)) {
    // result = new ArrayList<Object>();
    // } else if (iType.equals(OType.EMBEDDEDMAP) ||
    // iType.equals(OType.LINKMAP)) {
    // result = new HashMap<String, Object>();
    // } else
    // throw new IllegalArgumentException("Type " + iType +
    // " must be a collection");

    if (iType.equals(OType.LINKLIST) || iType.equals(OType.LINKSET) || iType.equals(OType.LINKMAP))
      linkedType = OType.LINK;
    else if (iType.equals(OType.EMBEDDEDLIST) || iType.equals(OType.EMBEDDEDSET) || iType.equals(OType.EMBEDDEDMAP))
      linkedType = OType.EMBEDDED;
    else
      throw new IllegalArgumentException("Type " + iType + " must be a multi value type (collection or map)");

    if (iMultiValue instanceof Set<?>) {
      for (Object o : sourceValues) {
        ((Collection<Object>) result).add(typeToStream(o, linkedType, iEntityManager, iObj2RecHandler, db, null, iSaveOnlyDirty));
      }
    } else if (iMultiValue instanceof List<?>) {
      for (int i = 0; i < sourceValues.size(); i++) {
        ((List<Object>) result).add(
            typeToStream(((List<?>) sourceValues).get(i), linkedType, iEntityManager, iObj2RecHandler, db, null, iSaveOnlyDirty));
      }
    } else {
      if (iMultiValue instanceof OObjectLazyMap<?>) {
        result = ((OObjectLazyMap<?>) iMultiValue).getUnderlying();
      } else {
        if (iRecord != null && iType.equals(OType.EMBEDDEDMAP))
          result = new OTrackedMap<Object>(iRecord);
        else
          result = new HashMap<Object, Object>();
        for (Entry<Object, Object> entry : ((Map<Object, Object>) iMultiValue).entrySet()) {
          ((Map<Object, Object>) result).put(entry.getKey(),
              typeToStream(entry.getValue(), linkedType, iEntityManager, iObj2RecHandler, db, null, iSaveOnlyDirty));
        }
      }
    }

    return result;
  }

  public static List<Field> getClassFields(final Class<?> iClass) {
    if (iClass.getName().startsWith("java.lang"))
      return null;

    synchronized (classes) {
      if (classes.containsKey(iClass.getName()))
        return classes.get(iClass.getName());

      return analyzeClass(iClass);
    }
  }

  /**
   * Returns the declared generic types of a class.
   *
   * @param iObject
   *          Class to examine
   * @return The array of Type if any, otherwise null
   */
  public static Type[] getGenericTypes(final Object iObject) {
    if (iObject instanceof OTrackedMultiValue) {
      final Class<?> cls = ((OTrackedMultiValue<?, ?>) iObject).getGenericClass();
      if (cls != null)
        return new Type[] { cls };
    }

    return OReflectionHelper.getGenericTypes(iObject.getClass());
  }

  public static void invokeCallback(final Object iPojo, final ODocument iDocument, final Class<?> iAnnotation) {
    final Method m = callbacks.get(iPojo.getClass().getSimpleName() + "." + iAnnotation.getSimpleName());

    if (m != null)

      try {
        if (m.getParameterTypes().length > 0)
          m.invoke(iPojo, iDocument);
        else
          m.invoke(iPojo);
      } catch (Exception e) {
        throw OException
            .wrapException(
                new OConfigurationException(
                    "Error on executing user callback '" + m.getName() + "' annotated with '" + iAnnotation.getSimpleName() + "'"),
                e);
      }
  }

  public static void bindSerializerContext(final Class<?> iClassContext, final OObjectSerializerContext iSerializerContext) {
    serializerContexts.put(iClassContext, iSerializerContext);
  }

  public static void unbindSerializerContext(final Class<?> iClassContext) {
    serializerContexts.remove(iClassContext);
  }

  protected static List<Field> analyzeClass(final Class<?> iClass) {

    final List<Field> properties = new ArrayList<Field>();
    classes.put(iClass.getName(), properties);

    String fieldName;
    Class<?> fieldType;
    int fieldModifier;
    boolean autoBinding;

    for (Class<?> currentClass = iClass; currentClass != Object.class;) {
      for (Field f : currentClass.getDeclaredFields()) {
        fieldModifier = f.getModifiers();
        if (Modifier.isStatic(fieldModifier) || Modifier.isNative(fieldModifier) || Modifier.isTransient(fieldModifier))
          continue;

        if (f.getName().equals("this$0"))
          continue;

        if (jpaTransientClass != null) {
          Annotation ann = f.getAnnotation(jpaTransientClass);
          if (ann != null)
            // @Transient DEFINED, JUMP IT
            continue;
        }

        fieldName = f.getName();
        fieldType = f.getType();
        properties.add(f);

        // CHECK FOR AUTO-BINDING
        autoBinding = true;
        if (f.getAnnotation(OAccess.class) == null || f.getAnnotation(OAccess.class).value() == OAccess.OAccessType.PROPERTY)
          autoBinding = true;
        // JPA 2+ AVAILABLE?
        else if (jpaAccessClass != null) {
          Annotation ann = f.getAnnotation(jpaAccessClass);
          if (ann != null) {
            // TODO: CHECK IF CONTAINS VALUE=FIELD
            autoBinding = true;
          }
        }

        if (f.getAnnotation(ODocumentInstance.class) != null)
          // BOUND DOCUMENT ON IT
          boundDocumentFields.put(iClass, f);

        boolean idFound = false;
        if (f.getAnnotation(OId.class) != null) {
          // RECORD ID
          fieldIds.put(iClass, f);
          idFound = true;
        }
        // JPA 1+ AVAILABLE?
        else if (jpaIdClass != null && f.getAnnotation(jpaIdClass) != null) {
          // RECORD ID
          fieldIds.put(iClass, f);
          idFound = true;
        }
        if (idFound) {
          // CHECK FOR TYPE
          if (fieldType.isPrimitive())
            OLogManager.instance().warn(OObjectSerializerHelper.class, "Field '%s' cannot be a literal to manage the Record Id",
                f.toString());
          else if (!ORID.class.isAssignableFrom(fieldType) && fieldType != String.class && fieldType != Object.class
              && !Number.class.isAssignableFrom(fieldType))
            OLogManager.instance().warn(OObjectSerializerHelper.class, "Field '%s' cannot be managed as type: %s", f.toString(),
                fieldType);
        }

        boolean vFound = false;
        if (f.getAnnotation(OVersion.class) != null) {
          // RECORD ID
          fieldVersions.put(iClass, f);
          vFound = true;
        }
        // JPA 1+ AVAILABLE?
        else if (jpaVersionClass != null && f.getAnnotation(jpaVersionClass) != null) {
          // RECORD ID
          fieldVersions.put(iClass, f);
          vFound = true;
        }
        if (vFound) {
          // CHECK FOR TYPE
          if (fieldType.isPrimitive())
            OLogManager.instance().warn(OObjectSerializerHelper.class, "Field '%s' cannot be a literal to manage the Version",
                f.toString());
          else if (fieldType != String.class && fieldType != Object.class && !Number.class.isAssignableFrom(fieldType))
            OLogManager.instance().warn(OObjectSerializerHelper.class, "Field '%s' cannot be managed as type: %s", f.toString(),
                fieldType);
        }

        // JPA 1+ AVAILABLE?
        if (jpaEmbeddedClass != null && f.getAnnotation(jpaEmbeddedClass) != null) {
          if (embeddedFields.get(iClass) == null)
            embeddedFields.put(iClass, new ArrayList<String>());
          embeddedFields.get(iClass).add(fieldName);
        }

        if (autoBinding)
          // TRY TO GET THE VALUE BY THE GETTER (IF ANY)
          try {
            String getterName = "get" + OUtils.camelCase(fieldName);
            Method m = currentClass.getMethod(getterName, NO_ARGS);
            getters.put(iClass.getName() + "." + fieldName, m);
          } catch (Exception e) {
            registerFieldGetter(iClass, fieldName, f);
          }
        else
          registerFieldGetter(iClass, fieldName, f);

        if (autoBinding)
          // TRY TO GET THE VALUE BY THE SETTER (IF ANY)
          try {
            String getterName = "set" + OUtils.camelCase(fieldName);
            Method m = currentClass.getMethod(getterName, f.getType());
            setters.put(iClass.getName() + "." + fieldName, m);
          } catch (Exception e) {
            registerFieldSetter(iClass, fieldName, f);
          }
        else
          registerFieldSetter(iClass, fieldName, f);
      }

      registerCallbacks(iClass, currentClass);

      currentClass = currentClass.getSuperclass();

      if (currentClass.equals(ODocument.class))
        // POJO EXTENDS ODOCUMENT: SPECIAL CASE: AVOID TO CONSIDER
        // ODOCUMENT FIELDS
        currentClass = Object.class;
    }
    return properties;
  }

  @SuppressWarnings("rawtypes")
  private static void registerCallbacks(final Class<?> iRootClass, final Class<?> iCurrentClass) {
    // FIND KEY METHODS
    for (Method m : iCurrentClass.getDeclaredMethods()) {
      // SEARCH FOR CALLBACK ANNOTATIONS
      for (Class annotationClass : callbackAnnotationClasses) {
        if (m.getAnnotation(annotationClass) != null)
          callbacks.put(iRootClass.getSimpleName() + "." + annotationClass.getSimpleName(), m);
      }
    }
  }

  private static void registerFieldSetter(final Class<?> iClass, String fieldName, Field f) {
    // TRY TO GET THE VALUE BY ACCESSING DIRECTLY TO THE PROPERTY
    if (!f.isAccessible())
      f.setAccessible(true);

    setters.put(iClass.getName() + "." + fieldName, f);
  }

  private static void registerFieldGetter(final Class<?> iClass, String fieldName, Field f) {
    // TRY TO GET THE VALUE BY ACCESSING DIRECTLY TO THE PROPERTY
    if (!f.isAccessible())
      f.setAccessible(true);

    getters.put(iClass.getName() + "." + fieldName, f);
  }

  private static boolean isEmbeddedObject(final Class<?> iPojoClass, final Class<?> iFieldClass, final String iFieldName,
      final OEntityManager iEntityManager) {
    return embeddedFields.get(iPojoClass) != null && embeddedFields.get(iPojoClass).contains(iFieldName);
  }

  public static Object convertDocumentInType(final ODocument oDocument, final Class<?> type) {
    Object pojo = null;
    try {
      pojo = type.newInstance();
      final List<Field> fields = OObjectSerializerHelper.analyzeClass(type);
      for (Field aField : fields) {
        OObjectSerializerHelper.setFieldFromDocument(oDocument, pojo, aField);
      }
    } catch (Exception e) {
      OLogManager.instance().error(null, "Error on converting document in object", e);
    }
    return pojo;
  }

  private static void setFieldFromDocument(final ODocument iDocument, final Object iPojo, final Field iField) throws Exception {
    final String idFieldName = OObjectSerializerHelper.setObjectID(iDocument.getIdentity(), iPojo);
    final String vFieldName = OObjectSerializerHelper.setObjectVersion(iDocument.getVersion(), iPojo);
    final String fieldName = iField.getName();
    // Don't assign id and version fields, used by Orient
    if (!fieldName.equals(idFieldName) && !fieldName.equals(vFieldName)) {
      // Assign only fields that are in the document
      if (iDocument.containsField(fieldName)) {
        Class<?> aClass = (Class<?>) iField.getGenericType();
        Object fieldValue = iDocument.field(fieldName);
        Object realValue = OObjectSerializerHelper.getObject(fieldValue, aClass);
        String setterName = "set" + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
        final Method m = iPojo.getClass().getMethod(setterName, aClass);
        m.invoke(iPojo, realValue);
      }
    }
  }

  private static Object getObject(final Object fieldValue, final Class<?> aClass) {
    if (fieldValue instanceof ODocument)
      return OObjectSerializerHelper.convertDocumentInType((ODocument) fieldValue, aClass);
    return fieldValue;
  }

  public static Object convertInObject(final Object iPojo, final String iField, final Object iValue, final Class<?> parameterType) {
    // New conversion method working with OLazyObjectList
    if (!(iValue instanceof OObjectLazyList<?>))
      return OType.convert(iValue, parameterType);

    List<Object> aSubList = null;
    try {
      final Field aField = OObjectSerializerHelper.getField(iPojo, iField);
      final Class<?> listClass = aField.getType();
      final ParameterizedType aType = (ParameterizedType) aField.getGenericType();
      final Class<?> objectClass = (Class<?>) aType.getActualTypeArguments()[0];
      final OObjectLazyList<?> aList = (OObjectLazyList<?>) iValue;
      // Instantiation of the list
      if (listClass.isInterface()) {
        aSubList = new ArrayList<Object>();
      } else {
        aSubList = (List<Object>) listClass.newInstance();
      }
      for (final Object value : aList) {
        if (value instanceof ODocument) {
          final ODocument aDocument = (ODocument) value;
          aSubList.add(OObjectSerializerHelper.convertDocumentInType(aDocument, objectClass));
        } else {
          aSubList.add(value);
        }
      }
    } catch (Exception e) {
      OLogManager.instance().error(null, "Error on convertInObject()", e);
    }
    return aSubList;
  }

  private static Field getField(final Object iPojo, final String iField) {
    final List<Field> fields = OObjectSerializerHelper.getClassFields(iPojo.getClass());
    if (fields != null) {
      for (Field f : fields)
        if (f.getName().equals(iField))
          return f;
    }
    return null;
  }
}


/*
  *
  *  *  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.operator;

import java.util.Collection;
import java.util.List;

import com.orientechnologies.orient.core.command.OCommandContext;
import com.orientechnologies.orient.core.db.ODatabase;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.serialization.serializer.record.binary.ODocumentSerializer;
import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition;

/**
 * Operator that filters the target records.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 *
 */
public abstract class OQueryTargetOperator extends OQueryOperator {
  protected OQueryTargetOperator(final String iKeyword, final int iPrecedence, final boolean iLogical) {
    super(iKeyword, iPrecedence, false);
  }

  public abstract Collection<OIdentifiable> filterRecords(final ODatabase<?> iRecord, final List<String> iTargetClasses,
      final OSQLFilterCondition iCondition, final Object iLeft, final Object iRight);

  /**
   * At run-time the evaluation per record must return always true since the recordset are filtered at the beginning unless an
   * operator can work in both modes. In this case sub-class must extend it.
   */
  @Override
  public Object evaluateRecord(final OIdentifiable iRecord, ODocument iCurrentResult, final OSQLFilterCondition iCondition,
      final Object iLeft, final Object iRight, OCommandContext iContext, final ODocumentSerializer serializer) {
    return true;
  }
}


/*
 *
 *  *  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.types.OModifiableBoolean;
import com.orientechnologies.common.util.OPair;
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.db.ODatabaseDocumentInternal;
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.id.ORID;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.record.OElement;
import com.orientechnologies.orient.core.record.OVertex;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.sql.functions.OSQLFunctionRuntime;

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

/**
 * SQL MOVE VERTEX command.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 */
public class OCommandExecutorSQLMoveVertex extends OCommandExecutorSQLSetAware implements OCommandDistributedReplicateRequest {
  public static final  String NAME          = "MOVE VERTEX";
  private static final String KEYWORD_MERGE = "MERGE";
  private static final String KEYWORD_BATCH = "BATCH";
  private              String source        = null;
  private String                      clusterName;
  private String                      className;
  private OClass                      clazz;
  private List<OPair<String, Object>> fields;
  private ODocument                   merge;
  private int batch = 100;

  @SuppressWarnings("unchecked")
  public OCommandExecutorSQLMoveVertex parse(final OCommandRequest iRequest) {
    final ODatabaseDocument database = getDatabase();

    init((OCommandRequestText) iRequest);

    parserRequiredKeyword("MOVE");
    parserRequiredKeyword("VERTEX");

    source = parserRequiredWord(false, "Syntax error", " =><,\r\n");
    if (source == null)
      throw new OCommandSQLParsingException("Cannot find source");

    parserRequiredKeyword("TO");

    String temp = parseOptionalWord(true);

    while (temp != null) {
      if (temp.startsWith("CLUSTER:")) {
        if (className != null)
          throw new OCommandSQLParsingException("Cannot define multiple sources. Found both cluster and class.");

        clusterName = temp.substring("CLUSTER:".length());
        if (database.getClusterIdByName(clusterName) == -1)
          throw new OCommandSQLParsingException("Cluster '" + clusterName + "' was not found");

      } else if (temp.startsWith("CLASS:")) {
        if (clusterName != null)
          throw new OCommandSQLParsingException("Cannot define multiple sources. Found both cluster and class.");

        className = temp.substring("CLASS:".length());

        clazz = database.getMetadata().getSchema().getClass(className);

        if (clazz == null)
          throw new OCommandSQLParsingException("Class '" + className + "' was not found");

      } else if (temp.equals(KEYWORD_SET)) {
        fields = new ArrayList<OPair<String, Object>>();
        parseSetFields(clazz, fields);

      } else if (temp.equals(KEYWORD_MERGE)) {
        merge = parseJSON();

      } else if (temp.equals(KEYWORD_BATCH)) {
        temp = parserNextWord(true);
        if (temp != null)
          batch = Integer.parseInt(temp);
      }

      temp = parserOptionalWord(true);
      if (parserIsEnded())
        break;
    }

    return this;
  }

  /**
   * Executes the command and return the ODocument object created.
   */
  public Object execute(final Map<Object, Object> iArgs) {

    ODatabaseDocumentInternal db = getDatabase();

    db.begin();

    if (className == null && clusterName == null)
      throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet");

    OModifiableBoolean shutdownGraph = new OModifiableBoolean();
    final boolean txAlreadyBegun = getDatabase().getTransaction().isActive();

    try {
      final Set<OIdentifiable> sourceRIDs = OSQLEngine.getInstance().parseRIDTarget(db, source, context, iArgs);

      // CREATE EDGES
      final List<ODocument> result = new ArrayList<ODocument>(sourceRIDs.size());

      for (OIdentifiable from : sourceRIDs) {
        final OVertex fromVertex = toVertex(from);
        if (fromVertex == null)
          continue;

        final ORID oldVertex = fromVertex.getIdentity().copy();
        final ORID newVertex = fromVertex.moveTo(className, clusterName);

        final ODocument newVertexDoc = newVertex.getRecord();

        if (fields != null) {
          // EVALUATE FIELDS
          for (final OPair<String, Object> f : fields) {
            if (f.getValue() instanceof OSQLFunctionRuntime)
              f.setValue(((OSQLFunctionRuntime) f.getValue()).getValue(newVertex.getRecord(), null, context));
          }

          OSQLHelper.bindParameters(newVertexDoc, fields, new OCommandParameters(iArgs), context);
        }

        if (merge != null)
          newVertexDoc.merge(merge, true, false);

        // SAVE CHANGES
        newVertexDoc.save();

        // PUT THE MOVE INTO THE RESULT
        result
            .add(new ODocument().setTrackingChanges(false).field("old", oldVertex, OType.LINK).field("new", newVertex, OType.LINK));

        if (batch > 0 && result.size() % batch == 0) {
            db.commit();
            db.begin();
        }
      }

        db.commit();

      return result;
    } finally {
//      if (!txAlreadyBegun)
//        db.commit();

    }
  }

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

  @Override
  public String getSyntax() {
    return "MOVE VERTEX <source> TO <destination> [SET [<field>=<value>]* [,]] [MERGE <JSON>] [BATCH <batch-size>]";
  }

  private OVertex toVertex(OIdentifiable item) {
    if (item != null && item instanceof OElement) {
      return ((OElement) item).asVertex().orElse(null);
    } else {
      item = getDatabase().load(item.getIdentity());
      if (item != null && item instanceof OElement) {
        return ((OElement) item).asVertex().orElse(null);
      }
    }
    return null;
  }

}


/*
 *
 *  *  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.orient.core.storage.impl.local.paginated.wal.OWALChanges;

import java.nio.ByteBuffer;

/**
 * Serializer for {@link String} type.
 *
 * @author Ilya Bershadskiy (ibersh20-at-gmail.com)
 * @since 18.01.12
 */
public class OStringSerializer implements OBinarySerializer<String> {
  public static final OStringSerializer INSTANCE = new OStringSerializer();
  public static final byte              ID       = 13;

  public int getObjectSize(final String object, Object... hints) {
    return object.length() * 2 + OIntegerSerializer.INT_SIZE;
  }

  public void serialize(final String object, final byte[] stream, int startPosition, Object... hints) {
    final int length = object.length();
    OIntegerSerializer.INSTANCE.serializeLiteral(length, stream, startPosition);

    startPosition += OIntegerSerializer.INT_SIZE;
    final char[] stringContent = new char[length];

    object.getChars(0, length, stringContent, 0);

    for (char character : stringContent) {
      stream[startPosition] = (byte) character;
      startPosition++;

      stream[startPosition] = (byte) (character >>> 8);
      startPosition++;
    }
  }

  public String deserialize(final byte[] stream, int startPosition) {
    final int len = OIntegerSerializer.INSTANCE.deserializeLiteral(stream, startPosition);
    final char[] buffer = new char[len];

    startPosition += OIntegerSerializer.INT_SIZE;

    for (int i = 0; i < len; i++) {
      buffer[i] = (char) ((0xFF & stream[startPosition]) | ((0xFF & stream[startPosition + 1]) << 8));
      startPosition += 2;
    }

    return new String(buffer);
  }

  public int getObjectSize(final byte[] stream, final int startPosition) {
    return OIntegerSerializer.INSTANCE.deserializeLiteral(stream, startPosition) * 2 + OIntegerSerializer.INT_SIZE;
  }

  public byte getId() {
    return ID;
  }

  public int getObjectSizeNative(byte[] stream, int startPosition) {
    return OIntegerSerializer.INSTANCE.deserializeNative(stream, startPosition) * 2 + OIntegerSerializer.INT_SIZE;
  }

  @Override
  public void serializeNativeObject(String object, byte[] stream, int startPosition, Object... hints) {
    int length = object.length();
    OIntegerSerializer.INSTANCE.serializeNative(length, stream, startPosition);

    startPosition += OIntegerSerializer.INT_SIZE;
    char[] stringContent = new char[length];

    object.getChars(0, length, stringContent, 0);

    for (char character : stringContent) {
      stream[startPosition] = (byte) character;
      startPosition++;

      stream[startPosition] = (byte) (character >>> 8);
      startPosition++;
    }
  }

  public String deserializeNativeObject(byte[] stream, int startPosition) {
    int len = OIntegerSerializer.INSTANCE.deserializeNative(stream, startPosition);
    char[] buffer = new char[len];

    startPosition += OIntegerSerializer.INT_SIZE;

    for (int i = 0; i < len; i++) {
      buffer[i] = (char) ((0xFF & stream[startPosition]) | ((0xFF & stream[startPosition + 1]) << 8));
      startPosition += 2;
    }

    return new String(buffer);
  }

  public boolean isFixedLength() {
    return false;
  }

  public int getFixedLength() {
    throw new UnsupportedOperationException("Length of serialized string is not fixed.");
  }

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

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

    byte[] binaryData = new byte[length * 2];
    char[] stringContent = new char[length];

    object.getChars(0, length, stringContent, 0);

    int counter = 0;
    for (char character : stringContent) {
      binaryData[counter] = (byte) character;
      counter++;

      binaryData[counter] = (byte) (character >>> 8);
      counter++;
    }

    buffer.put(binaryData);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public String deserializeFromByteBufferObject(ByteBuffer buffer) {
    int len = buffer.getInt();

    final char[] chars = new char[len];
    final byte[] binaryData = new byte[2 * len];
    buffer.get(binaryData);

    for (int i = 0; i < len; i++)
      chars[i] = (char) ((0xFF & binaryData[i << 1]) | ((0xFF & binaryData[(i << 1) + 1]) << 8));

    return new String(chars);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getObjectSizeInByteBuffer(ByteBuffer buffer) {
    return buffer.getInt() * 2 + OIntegerSerializer.INT_SIZE;
  }

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

    final char[] chars = new char[len];
    offset += OIntegerSerializer.INT_SIZE;

    byte[] binaryData = walChanges.getBinaryValue(buffer, offset, 2 * len);

    for (int i = 0; i < len; i++)
      chars[i] = (char) ((0xFF & binaryData[i << 1]) | ((0xFF & binaryData[(i << 1) + 1]) << 8));

    return new String(chars);
  }

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