/*
 *
 *  *  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.collection.OMultiCollectionIterator;
import com.orientechnologies.common.collection.OMultiValue;
import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.common.util.OCallable;
import com.orientechnologies.common.util.OCollections;
import com.orientechnologies.orient.core.collate.OCollate;
import com.orientechnologies.orient.core.collate.OCollateFactory;
import com.orientechnologies.orient.core.command.OCommandContext;
import com.orientechnologies.orient.core.command.OCommandExecutor;
import com.orientechnologies.orient.core.command.OCommandExecutorAbstract;
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.ODatabaseException;
import com.orientechnologies.orient.core.id.ORecordId;
import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper;
import com.orientechnologies.orient.core.sql.executor.OResult;
import com.orientechnologies.orient.core.sql.filter.OSQLFilter;
import com.orientechnologies.orient.core.sql.filter.OSQLTarget;
import com.orientechnologies.orient.core.sql.functions.OSQLFunction;
import com.orientechnologies.orient.core.sql.functions.OSQLFunctionFactory;
import com.orientechnologies.orient.core.sql.method.OSQLMethod;
import com.orientechnologies.orient.core.sql.method.OSQLMethodFactory;
import com.orientechnologies.orient.core.sql.operator.OQueryOperator;
import com.orientechnologies.orient.core.sql.operator.OQueryOperatorFactory;
import com.orientechnologies.orient.core.sql.parser.OStatement;
import com.orientechnologies.orient.core.sql.parser.OStatementCache;
import com.orientechnologies.orient.core.sql.parser.OrientSql;
import com.orientechnologies.orient.core.sql.parser.ParseException;
import com.orientechnologies.orient.core.sql.query.OSQLSynchQuery;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.*;

import static com.orientechnologies.common.util.OClassLoaderHelper.lookupProviderWithOrientClassLoader;

public class OSQLEngine {

  protected static final  OSQLEngine                       INSTANCE           = new OSQLEngine();
  private static volatile List<OSQLFunctionFactory>        FUNCTION_FACTORIES = null;
  private static          List<OSQLMethodFactory>          METHOD_FACTORIES   = null;
  private static          List<OCommandExecutorSQLFactory> EXECUTOR_FACTORIES = null;
  private static          List<OQueryOperatorFactory>      OPERATOR_FACTORIES = null;
  private static          List<OCollateFactory>            COLLATE_FACTORIES  = null;
  private static          OQueryOperator[]                 SORTED_OPERATORS   = null;
  private static          ClassLoader                      orientClassLoader  = OSQLEngine.class.getClassLoader();

  public static OStatement parse(String query, ODatabaseDocumentInternal db) {
    return OStatementCache.get(query, db);
  }

  public static List<OStatement> parseScript(String script, ODatabaseDocumentInternal db) {
    final InputStream is = new ByteArrayInputStream(script.getBytes());
    return parseScript(is, db);
  }

  public static List<OStatement> parseScript(InputStream script, ODatabaseDocumentInternal db) {
    try {
      final OrientSql osql = new OrientSql(script);
      List<OStatement> result = osql.parseScript();
      return result;
    } catch (ParseException e) {
      throw new OCommandSQLParsingException(e, "");
    }
  }

  /**
   * internal use only, to sort operators.
   */
  private static final class Pair {

    final OQueryOperator before;
    final OQueryOperator after;

    public Pair(final OQueryOperator before, final OQueryOperator after) {
      this.before = before;
      this.after = after;
    }

    @Override
    public boolean equals(final Object obj) {
      if (obj instanceof Pair) {
        final Pair that = (Pair) obj;
        return before == that.before && after == that.after;
      }
      return false;
    }

    @Override
    public int hashCode() {
      return System.identityHashCode(before) + 31 * System.identityHashCode(after);
    }

    @Override
    public String toString() {
      return before + " > " + after;
    }

  }

  protected OSQLEngine() {
  }

  public static void registerOperator(final OQueryOperator iOperator) {
    ODynamicSQLElementFactory.OPERATORS.add(iOperator);
    SORTED_OPERATORS = null; // clear cache
  }

  /**
   * @return Iterator of all function factories
   */
  public static Iterator<OSQLFunctionFactory> getFunctionFactories() {
    if (FUNCTION_FACTORIES == null) {
      synchronized (INSTANCE) {
        if (FUNCTION_FACTORIES == null) {
          final Iterator<OSQLFunctionFactory> ite = lookupProviderWithOrientClassLoader(OSQLFunctionFactory.class,
              orientClassLoader);

          final List<OSQLFunctionFactory> factories = new ArrayList<OSQLFunctionFactory>();
          while (ite.hasNext()) {
            factories.add(ite.next());
          }
          FUNCTION_FACTORIES = Collections.unmodifiableList(factories);
        }
      }
    }
    return FUNCTION_FACTORIES.iterator();
  }

  public static Iterator<OSQLMethodFactory> getMethodFactories() {
    if (METHOD_FACTORIES == null) {
      synchronized (INSTANCE) {
        if (METHOD_FACTORIES == null) {

          final Iterator<OSQLMethodFactory> ite = lookupProviderWithOrientClassLoader(OSQLMethodFactory.class, orientClassLoader);

          final List<OSQLMethodFactory> factories = new ArrayList<OSQLMethodFactory>();
          while (ite.hasNext()) {
            factories.add(ite.next());
          }
          METHOD_FACTORIES = Collections.unmodifiableList(factories);
        }
      }
    }
    return METHOD_FACTORIES.iterator();
  }

  /**
   * @return Iterator of all function factories
   */
  public static Iterator<OCollateFactory> getCollateFactories() {
    if (COLLATE_FACTORIES == null) {
      synchronized (INSTANCE) {
        if (COLLATE_FACTORIES == null) {

          final Iterator<OCollateFactory> ite = lookupProviderWithOrientClassLoader(OCollateFactory.class, orientClassLoader);

          final List<OCollateFactory> factories = new ArrayList<OCollateFactory>();
          while (ite.hasNext()) {
            factories.add(ite.next());
          }
          COLLATE_FACTORIES = Collections.unmodifiableList(factories);
        }
      }
    }
    return COLLATE_FACTORIES.iterator();
  }

  /**
   * @return Iterator of all operator factories
   */
  public static Iterator<OQueryOperatorFactory> getOperatorFactories() {
    if (OPERATOR_FACTORIES == null) {
      synchronized (INSTANCE) {
        if (OPERATOR_FACTORIES == null) {

          final Iterator<OQueryOperatorFactory> ite = lookupProviderWithOrientClassLoader(OQueryOperatorFactory.class,
              orientClassLoader);

          final List<OQueryOperatorFactory> factories = new ArrayList<OQueryOperatorFactory>();
          while (ite.hasNext()) {
            factories.add(ite.next());
          }
          OPERATOR_FACTORIES = Collections.unmodifiableList(factories);
        }
      }
    }
    return OPERATOR_FACTORIES.iterator();
  }

  /**
   * @return Iterator of all command factories
   */
  public static Iterator<OCommandExecutorSQLFactory> getCommandFactories() {
    if (EXECUTOR_FACTORIES == null) {
      synchronized (INSTANCE) {
        if (EXECUTOR_FACTORIES == null) {

          final Iterator<OCommandExecutorSQLFactory> ite = lookupProviderWithOrientClassLoader(OCommandExecutorSQLFactory.class,
              orientClassLoader);
          final List<OCommandExecutorSQLFactory> factories = new ArrayList<OCommandExecutorSQLFactory>();
          while (ite.hasNext()) {
            try {
              factories.add(ite.next());
            } catch (Exception e) {
              OLogManager.instance().warn(null, "Cannot load OCommandExecutorSQLFactory instance from service registry", e);
            }
          }

          EXECUTOR_FACTORIES = Collections.unmodifiableList(factories);
        }
      }
    }
    return EXECUTOR_FACTORIES.iterator();
  }

  /**
   * Iterates on all factories and append all function names.
   *
   * @return Set of all function names.
   */
  public static Set<String> getFunctionNames() {
    final Set<String> types = new HashSet<String>();
    final Iterator<OSQLFunctionFactory> ite = getFunctionFactories();
    while (ite.hasNext()) {
      types.addAll(ite.next().getFunctionNames());
    }
    return types;
  }

  public static Set<String> getMethodNames() {
    final Set<String> types = new HashSet<String>();
    final Iterator<OSQLMethodFactory> ite = getMethodFactories();
    while (ite.hasNext()) {
      types.addAll(ite.next().getMethodNames());
    }
    return types;
  }

  /**
   * Iterates on all factories and append all collate names.
   *
   * @return Set of all colate names.
   */
  public static Set<String> getCollateNames() {
    final Set<String> types = new HashSet<String>();
    final Iterator<OCollateFactory> ite = getCollateFactories();
    while (ite.hasNext()) {
      types.addAll(ite.next().getNames());
    }
    return types;
  }

  /**
   * Iterates on all factories and append all command names.
   *
   * @return Set of all command names.
   */
  public static Set<String> getCommandNames() {
    final Set<String> types = new HashSet<String>();
    final Iterator<OCommandExecutorSQLFactory> ite = getCommandFactories();
    while (ite.hasNext()) {
      types.addAll(ite.next().getCommandNames());
    }
    return types;
  }

  /**
   * Scans for factory plug-ins on the application class path. This method is needed because the application class path can
   * theoretically change, or additional plug-ins may become available. Rather than re-scanning the classpath on every invocation of
   * the API, the class path is scanned automatically only on the first invocation. Clients can call this method to prompt a
   * re-scan. Thus this method need only be invoked by sophisticated applications which dynamically make new plug-ins available at
   * runtime.
   */
  public static void scanForPlugins() {
    // clear cache, will cause a rescan on next getFunctionFactories call
    FUNCTION_FACTORIES = null;
  }

  public static Object foreachRecord(final OCallable<Object, OIdentifiable> iCallable, Object iCurrent,
      final OCommandContext iContext) {
    if (iCurrent == null)
      return null;

    if (!OCommandExecutorAbstract.checkInterruption(iContext))
      return null;

    if (iCurrent instanceof Iterable && !(iCurrent instanceof OIdentifiable)) {
      iCurrent = ((Iterable) iCurrent).iterator();
    }
    if (OMultiValue.isMultiValue(iCurrent) || iCurrent instanceof Iterator) {
      final OMultiCollectionIterator<Object> result = new OMultiCollectionIterator<Object>();
      for (Object o : OMultiValue.getMultiValueIterable(iCurrent, false)) {
        if (iContext != null && !iContext.checkTimeout())
          return null;

        if (OMultiValue.isMultiValue(o) || o instanceof Iterator) {
          for (Object inner : OMultiValue.getMultiValueIterable(o, false)) {
            result.add(iCallable.call((OIdentifiable) inner));
          }
        } else
          result.add(iCallable.call((OIdentifiable) o));
      }
      return result;
    } else if (iCurrent instanceof OIdentifiable) {
      return iCallable.call((OIdentifiable) iCurrent);
    } else if (iCurrent instanceof OResult) {
      return iCallable.call(((OResult) iCurrent).toElement());
    }

    return null;
  }

  public static OSQLEngine getInstance() {
    return INSTANCE;
  }

  public static OCollate getCollate(final String name) {
    for (Iterator<OCollateFactory> iter = getCollateFactories(); iter.hasNext(); ) {
      OCollateFactory f = iter.next();
      final OCollate c = f.getCollate(name);
      if (c != null)
        return c;
    }
    return null;
  }

  public static OSQLMethod getMethod(String iMethodName) {
    iMethodName = iMethodName.toLowerCase(Locale.ENGLISH);

    final Iterator<OSQLMethodFactory> ite = getMethodFactories();
    while (ite.hasNext()) {
      final OSQLMethodFactory factory = ite.next();
      if (factory.hasMethod(iMethodName)) {
        return factory.createMethod(iMethodName);
      }
    }

    return null;
  }

  public OQueryOperator[] getRecordOperators() {
    if (SORTED_OPERATORS == null) {
      synchronized (INSTANCE) {
        if (SORTED_OPERATORS == null) {
          // sort operators, will happen only very few times since we cache the
          // result
          final Iterator<OQueryOperatorFactory> ite = getOperatorFactories();
          final List<OQueryOperator> operators = new ArrayList<OQueryOperator>();
          while (ite.hasNext()) {
            final OQueryOperatorFactory factory = ite.next();
            operators.addAll(factory.getOperators());
          }

          final List<OQueryOperator> sorted = new ArrayList<OQueryOperator>();
          final Set<Pair> pairs = new LinkedHashSet<Pair>();
          for (final OQueryOperator ca : operators) {
            for (final OQueryOperator cb : operators) {
              if (ca != cb) {
                switch (ca.compare(cb)) {
                case BEFORE:
                  pairs.add(new Pair(ca, cb));
                  break;
                case AFTER:
                  pairs.add(new Pair(cb, ca));
                  break;
                }
                switch (cb.compare(ca)) {
                case BEFORE:
                  pairs.add(new Pair(cb, ca));
                  break;
                case AFTER:
                  pairs.add(new Pair(ca, cb));
                  break;
                }
              }
            }
          }
          boolean added;
          do {
            added = false;
            scan:
            for (final Iterator<OQueryOperator> it = operators.iterator(); it.hasNext(); ) {
              final OQueryOperator candidate = it.next();
              for (final Pair pair : pairs) {
                if (pair.after == candidate) {
                  continue scan;
                }
              }
              sorted.add(candidate);
              it.remove();
              for (final Iterator<Pair> itp = pairs.iterator(); itp.hasNext(); ) {
                if (itp.next().before == candidate) {
                  itp.remove();
                }
              }
              added = true;
            }
          } while (added);
          if (!operators.isEmpty()) {
            throw new ODatabaseException("Invalid sorting. " + OCollections.toString(pairs));
          }
          SORTED_OPERATORS = sorted.toArray(new OQueryOperator[sorted.size()]);
        }
      }
    }
    return SORTED_OPERATORS;
  }

  public void registerFunction(final String iName, final OSQLFunction iFunction) {
    ODynamicSQLElementFactory.FUNCTIONS.put(iName.toLowerCase(Locale.ENGLISH), iFunction);
  }

  public void registerFunction(final String iName, final Class<? extends OSQLFunction> iFunctionClass) {
    ODynamicSQLElementFactory.FUNCTIONS.put(iName.toLowerCase(Locale.ENGLISH), iFunctionClass);
  }

  public OSQLFunction getFunction(String iFunctionName) {
    iFunctionName = iFunctionName.toLowerCase(Locale.ENGLISH);

    if (iFunctionName.equalsIgnoreCase("any") || iFunctionName.equalsIgnoreCase("all"))
      // SPECIAL FUNCTIONS
      return null;

    final Iterator<OSQLFunctionFactory> ite = getFunctionFactories();
    while (ite.hasNext()) {
      final OSQLFunctionFactory factory = ite.next();
      if (factory.hasFunction(iFunctionName)) {
        return factory.createFunction(iFunctionName);
      }
    }

    throw new OCommandSQLParsingException(
        "No function with name '" + iFunctionName + "', available names are : " + OCollections.toString(getFunctionNames()));
  }

  public void unregisterFunction(String iName) {
    iName = iName.toLowerCase(Locale.ENGLISH);
    ODynamicSQLElementFactory.FUNCTIONS.remove(iName);
  }

  public OCommandExecutor getCommand(String candidate) {
    candidate = candidate.trim();
    final Set<String> names = getCommandNames();
    String commandName = candidate;
    boolean found = names.contains(commandName);
    int pos = -1;
    while (!found) {
      pos = OStringSerializerHelper.getLowerIndexOf(candidate, pos + 1, " ", "\n", "\r", "\t", "(", "[");
      if (pos > -1) {
        commandName = candidate.substring(0, pos);
        //remove double spaces
        commandName = commandName.replaceAll(" +", " ");
        found = names.contains(commandName);
      } else {
        break;
      }
    }

    if (found) {
      final Iterator<OCommandExecutorSQLFactory> ite = getCommandFactories();
      while (ite.hasNext()) {
        final OCommandExecutorSQLFactory factory = ite.next();
        if (factory.getCommandNames().contains(commandName)) {
          return factory.createCommand(commandName);
        }
      }
    }

    return null;
  }

  public OSQLFilter parseCondition(final String iText, final OCommandContext iContext, final String iFilterKeyword) {
    return new OSQLFilter(iText, iContext, iFilterKeyword);
  }

  public OSQLTarget parseTarget(final String iText, final OCommandContext iContext) {
    return new OSQLTarget(iText, iContext);
  }

  public Set<OIdentifiable> parseRIDTarget(final ODatabaseDocument database, String iTarget, final OCommandContext iContext,
      Map<Object, Object> iArgs) {
    final Set<OIdentifiable> ids;
    if (iTarget.startsWith("(")) {
      // SUB-QUERY
      final OSQLSynchQuery<Object> query = new OSQLSynchQuery<Object>(iTarget.substring(1, iTarget.length() - 1));
      query.setContext(iContext);

      final List<OIdentifiable> result = database.query(query, iArgs);
      if (result == null || result.isEmpty())
        ids = Collections.emptySet();
      else {
        ids = new HashSet<OIdentifiable>((int) (result.size() * 1.3));
        for (OIdentifiable aResult : result)
          ids.add(aResult.getIdentity());
      }
    } else if (iTarget.startsWith("[")) {
      // COLLECTION OF RIDS
      final String[] idsAsStrings = iTarget.substring(1, iTarget.length() - 1).split(",");
      ids = new HashSet<OIdentifiable>((int) (idsAsStrings.length * 1.3));
      for (String idsAsString : idsAsStrings) {
        if (idsAsString.startsWith("$")) {
          Object r = iContext.getVariable(idsAsString);
          if (r instanceof OIdentifiable)
            ids.add((OIdentifiable) r);
          else
            OMultiValue.add(ids, r);
        } else
          ids.add(new ORecordId(idsAsString));
      }
    } else {
      // SINGLE RID
      if (iTarget.startsWith("$")) {
        Object r = iContext.getVariable(iTarget);
        if (r instanceof OIdentifiable)
          ids = Collections.<OIdentifiable>singleton((OIdentifiable) r);
        else
          ids = (Set<OIdentifiable>) OMultiValue.add(new HashSet<OIdentifiable>(OMultiValue.getSize(r)), r);

      } else
        ids = Collections.<OIdentifiable>singleton(new ORecordId(iTarget));

    }
    return ids;
  }
}


package com.orientechnologies.orient.core.serialization.serializer.record.binary;

import com.orientechnologies.common.serialization.types.OByteSerializer;
import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal;
import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.db.record.ORecordElement;
import com.orientechnologies.orient.core.db.record.OTrackedMap;
import com.orientechnologies.orient.core.db.record.ridbag.ORidBag;
import com.orientechnologies.orient.core.exception.OSerializationException;
import com.orientechnologies.orient.core.metadata.OMetadataInternal;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.metadata.schema.OGlobalProperty;
import com.orientechnologies.orient.core.metadata.schema.OImmutableSchema;
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.record.impl.ODocumentEntry;
import com.orientechnologies.orient.core.record.impl.ODocumentInternal;
import com.orientechnologies.orient.core.serialization.serializer.record.binary.HelperClasses.MapRecordInfo;
import com.orientechnologies.orient.core.serialization.serializer.record.binary.HelperClasses.Triple;
import com.orientechnologies.orient.core.serialization.serializer.record.binary.HelperClasses.Tuple;

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

import static com.orientechnologies.orient.core.serialization.serializer.record.binary.HelperClasses.getGlobalProperty;
import static com.orientechnologies.orient.core.serialization.serializer.record.binary.HelperClasses.getTypeFromValueEmbedded;
import static com.orientechnologies.orient.core.serialization.serializer.record.binary.HelperClasses.readByte;
import static com.orientechnologies.orient.core.serialization.serializer.record.binary.HelperClasses.readOType;
import static com.orientechnologies.orient.core.serialization.serializer.record.binary.HelperClasses.readString;
import static com.orientechnologies.orient.core.serialization.serializer.record.binary.HelperClasses.stringFromBytes;
import static com.orientechnologies.orient.core.serialization.serializer.record.binary.HelperClasses.writeOType;
import static com.orientechnologies.orient.core.serialization.serializer.record.binary.HelperClasses.writeString;

public class ORecordSerializerBinaryV1 extends ORecordSerializerBinaryV0 {

  private enum Signal {
    CONTINUE, RETURN, RETURN_VALUE, NO_ACTION
  }

  private Tuple<Boolean, String> processNamedFieldInDeserializePartial(final String[] iFields, final BytesContainer bytes, int len,
      byte[][] fields) {
    boolean match = false;
    String fieldName = null;
    for (int i = 0; i < iFields.length; ++i) {
      if (iFields[i] != null && iFields[i].length() == len) {
        boolean matchField = true;
        for (int j = 0; j < len; ++j) {
          if (bytes.bytes[bytes.offset + j] != fields[i][j]) {
            matchField = false;
            break;
          }
        }
        if (matchField) {
          match = true;
          break;
        }
      }
    }

    //field name is used only if match exists
    if (match)
      fieldName = stringFromBytes(bytes.bytes, bytes.offset, len).intern();
    bytes.skip(len);
    return new Tuple<>(match, fieldName);
  }

  private Tuple<Boolean, String> checkIfPropertyNameMatchSome(OGlobalProperty prop, final String[] iFields) {
    String fieldName = prop.getName();

    boolean matchField = false;
    for (String f : iFields) {
      if (fieldName.equals(f)) {
        matchField = true;
        break;
      }
    }

    return new Tuple<>(matchField, fieldName);
  }

  private Triple<Signal, Triple<Integer, OType, String>, Integer> processPropertyFiledInDeserializePartial(final ODocument document,
      final int len, final String[] iFields, final BytesContainer bytes, int cumulativeLength, int headerStart, int headerLength) {
    // LOAD GLOBAL PROPERTY BY ID
    final OGlobalProperty prop = getGlobalProperty(document, len);
    Tuple<Boolean, String> matchFieldName = checkIfPropertyNameMatchSome(prop, iFields);

    boolean matchField = matchFieldName.getFirstVal();
    String fieldName = matchFieldName.getSecondVal();

    int fieldLength = OVarIntSerializer.readAsInteger(bytes);
    OType type = getPropertyTypeFromStream(prop, bytes);

    if (!matchField) {
      return new Triple<>(Signal.CONTINUE, null, cumulativeLength + fieldLength);
    }

    int valuePos;
    if (fieldLength == 0) {
      valuePos = 0;
    } else {
      valuePos = cumulativeLength + headerStart + headerLength;
    }
    Triple<Integer, OType, String> value = new Triple<>(valuePos, type, fieldName);
    return new Triple<>(Signal.RETURN_VALUE, value, cumulativeLength + fieldLength);
  }

  @Override
  public void deserializePartial(ODocument document, BytesContainer bytes, String[] iFields) {
    // TRANSFORMS FIELDS FOM STRINGS TO BYTE[]
    final byte[][] fields = new byte[iFields.length][];
    for (int i = 0; i < iFields.length; ++i) {
      fields[i] = iFields[i].getBytes();
    }

    String fieldName;
    int valuePos;
    OType type;
    int unmarshalledFields = 0;

    int headerLength = OVarIntSerializer.readAsInteger(bytes);
    int headerStart = bytes.offset;
    int cumulativeLength = 0;

    while (true) {
      if (bytes.offset >= headerStart + headerLength)
        break;

      final int len = OVarIntSerializer.readAsInteger(bytes);

      if (len > 0) {
        // CHECK BY FIELD NAME SIZE: THIS AVOID EVEN THE UNMARSHALLING OF FIELD NAME
        Tuple<Boolean, String> matchFieldName = processNamedFieldInDeserializePartial(iFields, bytes, len, fields);
        boolean match = matchFieldName.getFirstVal();
        fieldName = matchFieldName.getSecondVal();
        Tuple<Integer, OType> pointerAndType = getFieldSizeAndTypeFromCurrentPosition(bytes);
        int fieldLength = pointerAndType.getFirstVal();

        if (!match) {
          // FIELD NOT INCLUDED: SKIP IT
          cumulativeLength += fieldLength;
          continue;
        }

        type = pointerAndType.getSecondVal();
        if (fieldLength == 0) {
          valuePos = 0;
        } else {
          valuePos = headerStart + headerLength + cumulativeLength;
        }
        cumulativeLength += fieldLength;
      } else {
        // LOAD GLOBAL PROPERTY BY ID
        Triple<Signal, Triple<Integer, OType, String>, Integer> actionSignal = processPropertyFiledInDeserializePartial(document,
            len, iFields, bytes, cumulativeLength, headerStart, headerLength);
        cumulativeLength = actionSignal.getThirdVal();
        switch (actionSignal.getFirstVal()) {
        case CONTINUE:
          continue;
        case RETURN_VALUE:
        default:
          valuePos = actionSignal.getSecondVal().getFirstVal();
          type = actionSignal.getSecondVal().getSecondVal();
          fieldName = actionSignal.getSecondVal().getThirdVal();
          break;
        }
      }

      if (valuePos != 0) {
        int headerCursor = bytes.offset;
        bytes.offset = valuePos;
        final Object value = deserializeValue(bytes, type, document);
        bytes.offset = headerCursor;
        ODocumentInternal.rawField(document, fieldName, value, type);
      } else
        ODocumentInternal.rawField(document, fieldName, null, null);

      if (++unmarshalledFields == iFields.length)
        // ALL REQUESTED FIELDS UNMARSHALLED: EXIT
        break;
    }
  }

  @Override
  public void deserializePartialWithClassName(final ODocument document, final BytesContainer bytes, final String[] iFields) {

    final String className = readString(bytes);
    if (className.length() != 0)
      ODocumentInternal.fillClassNameIfNeeded(document, className);

    deserializePartial(document, bytes, iFields);
  }

  private boolean checkMatchForLargerThenZero(final BytesContainer bytes, final byte[] field, int len) {
    if (field.length != len) {
      return false;
    }
    boolean match = true;
    for (int j = 0; j < len; ++j)
      if (bytes.bytes[bytes.offset + j] != field[j]) {
        match = false;
        break;
      }

    return match;
  }

  private OType getPropertyTypeFromStream(final OGlobalProperty prop, final BytesContainer bytes) {
    final OType type;
    if (prop.getType() != OType.ANY)
      type = prop.getType();
    else
      type = readOType(bytes, false);

    return type;
  }

  private Triple<Signal, OBinaryField, Integer> processNamedFieldDeserializeField(final BytesContainer bytes,
      final String iFieldName, final byte[] field, int len, Integer cumulativeLength, int headerStart, int headerLength) {

    boolean match = checkMatchForLargerThenZero(bytes, field, len);

    bytes.skip(len);
    Tuple<Integer, OType> pointerAndType = getFieldSizeAndTypeFromCurrentPosition(bytes);
    final int fieldLength = pointerAndType.getFirstVal();
    final OType type = pointerAndType.getSecondVal();

    if (fieldLength == 0)
      return new Triple<>(Signal.RETURN_VALUE, null, cumulativeLength);

    if (!match)
      return new Triple<>(Signal.CONTINUE, null, cumulativeLength + fieldLength);

    if (!getComparator().isBinaryComparable(type))
      return new Triple<>(Signal.RETURN_VALUE, null, cumulativeLength + fieldLength);

    int valuePos = headerStart + headerLength + cumulativeLength;
    bytes.offset = valuePos;
    return new Triple<>(Signal.RETURN_VALUE, new OBinaryField(iFieldName, type, bytes, null), cumulativeLength + fieldLength);
  }

  private Triple<Signal, OBinaryField, Integer> processPropertyDeserializeField(int len, final OImmutableSchema _schema,
      final String iFieldName, final OClass iClass, final BytesContainer bytes, int cumulativeLength, int headerStart,
      int headerLength) {
    final int id = (len * -1) - 1;
    final OGlobalProperty prop = _schema.getGlobalPropertyById(id);
    final int fieldLength = OVarIntSerializer.readAsInteger(bytes);
    final OType type;
    type = getPropertyTypeFromStream(prop, bytes);

    if (!iFieldName.equals(prop.getName())) {
      return new Triple<>(Signal.NO_ACTION, null, cumulativeLength + fieldLength);
    }

    int valuePos = headerStart + headerLength + cumulativeLength;

    if (fieldLength == 0 || valuePos == 0 || !getComparator().isBinaryComparable(type))
      return new Triple<>(Signal.RETURN_VALUE, null, cumulativeLength + fieldLength);

    bytes.offset = valuePos;

    final OProperty classProp = iClass.getProperty(iFieldName);
    return new Triple<>(Signal.RETURN_VALUE,
        new OBinaryField(iFieldName, type, bytes, classProp != null ? classProp.getCollate() : null),
        cumulativeLength + fieldLength);
  }

  @Override
  public OBinaryField deserializeField(final BytesContainer bytes, final OClass iClass, final String iFieldName) {
    final byte[] field = iFieldName.getBytes();

    final OMetadataInternal metadata = ODatabaseRecordThreadLocal.instance().get().getMetadata();
    final OImmutableSchema _schema = metadata.getImmutableSchemaSnapshot();

    int headerLength = OVarIntSerializer.readAsInteger(bytes);
    int headerStart = bytes.offset;
    int cumulativeLength = 0;

    while (true) {
      if (bytes.offset >= headerStart + headerLength)
        return null;

      final int len = OVarIntSerializer.readAsInteger(bytes);

      if (len > 0) {
        // CHECK BY FIELD NAME SIZE: THIS AVOID EVEN THE UNMARSHALLING OF FIELD NAME
        Triple<Signal, OBinaryField, Integer> actionSignal = processNamedFieldDeserializeField(bytes, iFieldName, field, len,
            cumulativeLength, headerStart, headerLength);
        cumulativeLength = actionSignal.getThirdVal();
        switch (actionSignal.getFirstVal()) {
        case RETURN_VALUE:
          return actionSignal.getSecondVal();
        case CONTINUE:
        case NO_ACTION:
        default:
          break;
        }
      } else {
        // LOAD GLOBAL PROPERTY BY ID
        Triple<Signal, OBinaryField, Integer> actionSignal = processPropertyDeserializeField(len, _schema, iFieldName, iClass,
            bytes, cumulativeLength, headerStart, headerLength);
        cumulativeLength = actionSignal.getThirdVal();
        switch (actionSignal.getFirstVal()) {
        case RETURN_VALUE:
          return actionSignal.getSecondVal();
        case CONTINUE:
        case NO_ACTION:
        default:
          break;
        }
      }
    }
  }

  @Override
  public OBinaryField deserializeFieldWithClassName(final BytesContainer bytes, final OClass iClass, final String iFieldName) {
    // akip class name bytes
    final int classNameLen = OVarIntSerializer.readAsInteger(bytes);
    bytes.skip(classNameLen);

    return deserializeField(bytes, iClass, iFieldName);
  }

  @Override
  public void deserialize(final ODocument document, final BytesContainer bytes) {
    int headerLength = OVarIntSerializer.readAsInteger(bytes);
    int headerStart = bytes.offset;

    int last = 0;
    String fieldName;
    OType type;
    int cumulativeSize = 0;
    while (bytes.offset < headerStart + headerLength) {
      OGlobalProperty prop;
      final int len = OVarIntSerializer.readAsInteger(bytes);
      int fieldLength;
      if (len > 0) {
        // PARSE FIELD NAME
        fieldName = stringFromBytes(bytes.bytes, bytes.offset, len).intern();
        bytes.skip(len);
        Tuple<Integer, OType> pointerAndType = getFieldSizeAndTypeFromCurrentPosition(bytes);
        fieldLength = pointerAndType.getFirstVal();
        type = pointerAndType.getSecondVal();
      } else {
        // LOAD GLOBAL PROPERTY BY ID
        prop = getGlobalProperty(document, len);
        fieldName = prop.getName();
        fieldLength = OVarIntSerializer.readAsInteger(bytes);
        type = getPropertyTypeFromStream(prop, bytes);
      }

      if (ODocumentInternal.rawContainsField(document, fieldName)) {
        cumulativeSize += fieldLength;
        continue;
      }

      if (fieldLength != 0) {
        int headerCursor = bytes.offset;

        int valuePos = cumulativeSize + headerStart + headerLength;

        bytes.offset = valuePos;
        final Object value = deserializeValue(bytes, type, document);
        if (bytes.offset > last)
          last = bytes.offset;
        bytes.offset = headerCursor;
        ODocumentInternal.rawField(document, fieldName, value, type);
      } else
        ODocumentInternal.rawField(document, fieldName, null, null);

      cumulativeSize += fieldLength;
    }

    ORecordInternal.clearSource(document);

    if (last > bytes.offset) {
      bytes.offset = last;
    }
  }

  @Override
  public void deserializeWithClassName(final ODocument document, final BytesContainer bytes) {

    final String className = readString(bytes);
    if (className.length() != 0)
      ODocumentInternal.fillClassNameIfNeeded(document, className);

    deserialize(document, bytes);
  }

  @Override
  public String[] getFieldNames(ODocument reference, final BytesContainer bytes, boolean readClassName) {
    // SKIP CLASS NAME
    if (readClassName) {
      final int classNameLen = OVarIntSerializer.readAsInteger(bytes);
      bytes.skip(classNameLen);
    }

    //skip header length
    int headerLength = OVarIntSerializer.readAsInteger(bytes);
    int headerStart = bytes.offset;

    final List<String> result = new ArrayList<>();

    String fieldName;
    while (bytes.offset < headerStart + headerLength) {
      OGlobalProperty prop;
      final int len = OVarIntSerializer.readAsInteger(bytes);
      if (len > 0) {
        // PARSE FIELD NAME
        fieldName = stringFromBytes(bytes.bytes, bytes.offset, len).intern();
        result.add(fieldName);

        // SKIP THE REST
        bytes.skip(len);
        OVarIntSerializer.readAsInteger(bytes);
        bytes.skip(1);
      } else {
        // LOAD GLOBAL PROPERTY BY ID
        final int id = (len * -1) - 1;
        prop = ODocumentInternal.getGlobalPropertyById(reference, id);
        if (prop == null) {
          throw new OSerializationException("Missing property definition for property id '" + id + "'");
        }
        result.add(prop.getName());

        // SKIP THE REST
        OVarIntSerializer.readAsInteger(bytes);
        if (prop.getType() == OType.ANY)
          bytes.skip(1);
      }
    }

    return result.toArray(new String[0]);
  }

  private void serializeWriteValues(final BytesContainer headerBuffer, final BytesContainer valuesBuffer, final ODocument document,
      Set<Entry<String, ODocumentEntry>> fields, final Map<String, OProperty> props) {

    for (Entry<String, ODocumentEntry> field : fields) {
      ODocumentEntry docEntry = field.getValue();
      if (!field.getValue().exist()) {
        continue;
      }
      if (docEntry.property == null && props != null) {
        OProperty prop = props.get(field.getKey());
        if (prop != null && docEntry.type == prop.getType()) {
          docEntry.property = prop;
        }
      }

      if (docEntry.property == null) {
        String fieldName = field.getKey();
        writeString(headerBuffer, fieldName);
      } else {
        OVarIntSerializer.write(headerBuffer, (docEntry.property.getId() + 1) * -1);
      }

      final Object value = field.getValue().value;

      final OType type;
      if (value != null) {
        type = getFieldType(field.getValue());
        if (type == null) {
          throw new OSerializationException(
              "Impossible serialize value of type " + value.getClass() + " with the ODocument binary serializer");
        }
        Tuple<Integer, Integer> dataPointerAndLength = serializeValue(valuesBuffer, value, type,
            getLinkedType(document, type, field.getKey()));
        int valueLength = dataPointerAndLength.getSecondVal();
        OVarIntSerializer.write(headerBuffer, valueLength);
      }
      //handle null fields
      else {
        OVarIntSerializer.write(headerBuffer, 0);
        type = OType.ANY;
      }

      //write type. Type should be written both for regular and null fields
      if (field.getValue().property == null || field.getValue().property.getType() == OType.ANY) {
        int typeOffset = headerBuffer.alloc(OByteSerializer.BYTE_SIZE);
        OByteSerializer.INSTANCE.serialize((byte) type.getId(), headerBuffer.bytes, typeOffset);
      }
    }
  }

  private void merge(BytesContainer destinationBuffer, BytesContainer sourceBuffer1, BytesContainer sourceBuffer2) {
    destinationBuffer.offset = destinationBuffer.allocExact(sourceBuffer1.offset + sourceBuffer2.offset);
    System.arraycopy(sourceBuffer1.bytes, 0, destinationBuffer.bytes, destinationBuffer.offset, sourceBuffer1.offset);
    System.arraycopy(sourceBuffer2.bytes, 0, destinationBuffer.bytes, destinationBuffer.offset + sourceBuffer1.offset,
        sourceBuffer2.offset);
    destinationBuffer.offset += sourceBuffer1.offset + sourceBuffer2.offset;
  }

  private void serializeDocument(final ODocument document, final BytesContainer bytes, final OClass clazz) {
    //allocate space for header length

    final Map<String, OProperty> props = clazz != null ? clazz.propertiesMap() : null;
    final Set<Entry<String, ODocumentEntry>> fields = ODocumentInternal.rawEntries(document);

    BytesContainer valuesBuffer = new BytesContainer();
    BytesContainer headerBuffer = new BytesContainer();

    serializeWriteValues(headerBuffer, valuesBuffer, document, fields, props);
    int headerLength = headerBuffer.offset;
    //write header length as soon as possible
    OVarIntSerializer.write(bytes, headerLength);

    merge(bytes, headerBuffer, valuesBuffer);
  }

  @Override
  public void serializeWithClassName(final ODocument document, final BytesContainer bytes, final boolean iClassOnly) {
    final OClass clazz = serializeClass(document, bytes, true);
    if (iClassOnly) {
      writeEmptyString(bytes);
      return;
    }
    serializeDocument(document, bytes, clazz);
  }

  @SuppressWarnings("unchecked")
  @Override
  public void serialize(final ODocument document, final BytesContainer bytes, final boolean iClassOnly) {
    final OClass clazz = serializeClass(document, bytes, false);
    if (iClassOnly) {
      writeEmptyString(bytes);
      return;
    }
    serializeDocument(document, bytes, clazz);
  }

  private OClass serializeClass(final ODocument document, final BytesContainer bytes, boolean serializeClassName) {
    final OClass clazz = ODocumentInternal.getImmutableSchemaClass(document);
    if (serializeClassName) {
      if (clazz != null && document.isEmbedded())
        writeString(bytes, clazz.getName());
      else
        writeEmptyString(bytes);
    }
    return clazz;
  }

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

  @Override
  public <RET> RET deserializeFieldTyped(BytesContainer bytes, String iFieldName, boolean isEmbedded, int serializerVersion) {
    if (isEmbedded) {
      skipClassName(bytes);
    }
    return deserializeFieldTypedLoopAndReturn(bytes, iFieldName, serializerVersion);
  }

  @Override
  protected <RET> RET deserializeFieldTypedLoopAndReturn(BytesContainer bytes, String iFieldName, int serializerVersion) {
    final byte[] field = iFieldName.getBytes();

    int headerLength = OVarIntSerializer.readAsInteger(bytes);
    int headerStart = bytes.offset;
    int cumulativeLength = 0;

    final OMetadataInternal metadata = ODatabaseRecordThreadLocal.instance().get().getMetadata();
    final OImmutableSchema _schema = metadata.getImmutableSchemaSnapshot();

    while (true) {
      if (bytes.offset >= headerStart + headerLength)
        return null;

      int len = OVarIntSerializer.readAsInteger(bytes);

      if (len > 0) {
        // CHECK BY FIELD NAME SIZE: THIS AVOID EVEN THE UNMARSHALLING OF FIELD NAME
        boolean match = checkMatchForLargerThenZero(bytes, field, len);

        bytes.skip(len);
        Tuple<Integer, OType> pointerAndType = getFieldSizeAndTypeFromCurrentPosition(bytes);
        int fieldLength = pointerAndType.getFirstVal();
        OType type = pointerAndType.getSecondVal();

        int valuePos = cumulativeLength + headerStart + headerLength;
        cumulativeLength += fieldLength;
        if (valuePos == 0 || fieldLength == 0)
          return null;

        if (!match)
          continue;

        bytes.offset = valuePos;
        Object value = deserializeValue(bytes, type, null, false, fieldLength, serializerVersion, false);
        //noinspection unchecked
        return (RET) value;
      } else {
        // LOAD GLOBAL PROPERTY BY ID
        final int id = (len * -1) - 1;
        final OGlobalProperty prop = _schema.getGlobalPropertyById(id);
        final int fieldLength = OVarIntSerializer.readAsInteger(bytes);
        OType type = getPropertyTypeFromStream(prop, bytes);

        int valuePos = cumulativeLength + headerStart + headerLength;
        cumulativeLength += fieldLength;

        if (!iFieldName.equals(prop.getName()))
          continue;

        if (valuePos == 0 || fieldLength == 0)
          return null;

        bytes.offset = valuePos;

        Object value = deserializeValue(bytes, type, null, false, fieldLength, serializerVersion, false);
        //noinspection unchecked
        return (RET) value;
      }
    }
  }

  @Override
  public boolean isSerializingClassNameForEmbedded() {
    return true;
  }

  /**
   * use only for named fields
   */
  private Tuple<Integer, OType> getFieldSizeAndTypeFromCurrentPosition(BytesContainer bytes) {
    int fieldSize = OVarIntSerializer.readAsInteger(bytes);
    OType type = readOType(bytes, false);
    return new Tuple<>(fieldSize, type);
  }

  @Override
  public void deserializeDebug(BytesContainer bytes, ODatabaseDocumentInternal db, ORecordSerializationDebug debugInfo,
      OImmutableSchema schema) {

    int headerLength = OVarIntSerializer.readAsInteger(bytes);
    int headerPos = bytes.offset;

    debugInfo.properties = new ArrayList<>();
    int last = 0;
    String fieldName;
    OType type;
    int cumulativeLength = 0;
    while (true) {
      ORecordSerializationDebugProperty debugProperty = new ORecordSerializationDebugProperty();
      OGlobalProperty prop;

      int fieldLength;

      try {
        if (bytes.offset >= headerPos + headerLength)
          break;

        final int len = OVarIntSerializer.readAsInteger(bytes);
        debugInfo.properties.add(debugProperty);
        if (len > 0) {
          // PARSE FIELD NAME
          fieldName = stringFromBytes(bytes.bytes, bytes.offset, len).intern();
          bytes.skip(len);

          Tuple<Integer, OType> valuePositionAndType = getFieldSizeAndTypeFromCurrentPosition(bytes);
          fieldLength = valuePositionAndType.getFirstVal();
          type = valuePositionAndType.getSecondVal();
        } else {
          // LOAD GLOBAL PROPERTY BY ID
          final int id = (len * -1) - 1;
          debugProperty.globalId = id;
          prop = schema.getGlobalPropertyById(id);
          fieldLength = OVarIntSerializer.readAsInteger(bytes);
          debugProperty.valuePos = headerPos + headerLength + cumulativeLength;
          if (prop != null) {
            fieldName = prop.getName();
            type = getPropertyTypeFromStream(prop, bytes);
          } else {
            cumulativeLength += fieldLength;
            continue;
          }
        }
        debugProperty.name = fieldName;
        debugProperty.type = type;

        int valuePos;
        if (fieldLength > 0)
          valuePos = headerPos + headerLength + cumulativeLength;
        else
          valuePos = 0;

        cumulativeLength += fieldLength;

        if (valuePos != 0) {
          int headerCursor = bytes.offset;
          bytes.offset = valuePos;
          try {
            debugProperty.value = deserializeValue(bytes, type, new ODocument());
          } catch (RuntimeException ex) {
            debugProperty.faildToRead = true;
            debugProperty.readingException = ex;
            debugProperty.failPosition = bytes.offset;
          }
          if (bytes.offset > last)
            last = bytes.offset;
          bytes.offset = headerCursor;
        } else
          debugProperty.value = null;
      } catch (RuntimeException ex) {
        debugInfo.readingFailure = true;
        debugInfo.readingException = ex;
        debugInfo.failPosition = bytes.offset;
      }
    }
  }

  @SuppressWarnings("unchecked")
  @Override
  protected int writeEmbeddedMap(BytesContainer bytes, Map<Object, Object> map) {
    final int fullPos = OVarIntSerializer.write(bytes, map.size());
    for (Entry<Object, Object> entry : map.entrySet()) {
      // TODO:check skip of complex types
      // FIXME: changed to support only string key on map
      OType type = OType.STRING;
      writeOType(bytes, bytes.alloc(1), type);
      writeString(bytes, entry.getKey().toString());
      final Object value = entry.getValue();
      if (value != null) {
        type = getTypeFromValueEmbedded(value);
        if (type == null) {
          throw new OSerializationException(
              "Impossible serialize value of type " + value.getClass() + " with the ODocument binary serializer");
        }
        writeOType(bytes, bytes.alloc(1), type);
        serializeValue(bytes, value, type, null);
      } else {
        //signal for null value
        OByteSerializer.INSTANCE.serialize((byte) -1, bytes.bytes, bytes.alloc(1));
      }
    }

    return fullPos;
  }

  @Override
  protected Object readEmbeddedMap(final BytesContainer bytes, final ODocument document) {
    int size = OVarIntSerializer.readAsInteger(bytes);
    final OTrackedMap<Object> result = new OTrackedMap<>(document);

    result.setInternalStatus(ORecordElement.STATUS.UNMARSHALLING);
    try {
      for (int i = 0; i < size; i++) {
        OType keyType = readOType(bytes, false);
        Object key = deserializeValue(bytes, keyType, document);
        final OType type = HelperClasses.readType(bytes);
        if (type != null) {
          Object value = deserializeValue(bytes, type, document);
          result.put(key, value);
        } else
          result.put(key, null);
      }
      return result;

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

  @Override
  protected List<MapRecordInfo> getPositionsFromEmbeddedMap(final BytesContainer bytes, int serializerVersion) {
    List<MapRecordInfo> retList = new ArrayList<>();

    int numberOfElements = OVarIntSerializer.readAsInteger(bytes);

    for (int i = 0; i < numberOfElements; i++) {
      OType keyType = readOType(bytes, false);
      String key = readString(bytes);
      OType valueType = HelperClasses.readType(bytes);
      if (valueType != null) {
        MapRecordInfo recordInfo = new MapRecordInfo();
        recordInfo.fieldStartOffset = bytes.offset;
        recordInfo.fieldType = valueType;
        recordInfo.key = key;
        recordInfo.keyType = keyType;
        int currentOffset = bytes.offset;

        deserializeValue(bytes, valueType, null, true, -1, serializerVersion, true);
        recordInfo.fieldLength = bytes.offset - currentOffset;

        retList.add(recordInfo);
      }
    }

    return retList;
  }

  @Override
  protected int writeRidBag(BytesContainer bytes, ORidBag ridbag) {
    int positionOffset = bytes.offset;
    HelperClasses.writeRidBag(bytes, ridbag);
    return positionOffset;
  }

  @Override
  protected ORidBag readRidbag(BytesContainer bytes) {
    return HelperClasses.readRidbag(bytes);
  }

}


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

import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.record.ORecord;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;

/**
 * Wrapper to use the HTTP response in functions and scripts. This class mimics the J2EE HTTPResponse class.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 *
 */
public class OHttpResponseWrapper {
  private final OHttpResponse response;

  /**
   * @param iResponse
   */
  public OHttpResponseWrapper(final OHttpResponse iResponse) {
    response = iResponse;
  }

  public OHttpResponse getResponse() {
    return response;
  }

  /**
   * Returns the response's additional headers.
   *
   * @return The additional headers in form of String
   */
  public String getHeader() {
    return response.headers;
  }

  /**
   * Sets the response's additional headers to send back. To specify multiple headers use the line breaks.
   *
   * @param iHeader
   *          String containing the header
   * @return The object itself for fluent chained calls
   */
  public OHttpResponseWrapper setHeader(final String iHeader) {
    response.setHeader(iHeader);
    return this;
  }

  /**
   * Returns the response's character set used.
   *
   * @return The character set in form of String
   */
  public String getCharacterSet() {
    return response.characterSet;
  }

  /**
   * Sets the response's character set.
   *
   * @param iCharacterSet
   *          String containing the charset to use
   * @return The object itself for fluent chained calls
   */
  public OHttpResponseWrapper setCharacterSet(final String iCharacterSet) {
    response.characterSet = iCharacterSet;
    return this;
  }

  public String getHttpVersion() {
    return response.httpVersion;
  }

  public String[] getAdditionalResponseHeaders() {
    return response.additionalHeaders;
  }

  public OutputStream getOutputStream() {
    return response.getOutputStream();
  }

  /**
   * Sets the response's status as HTTP code and reason.
   *
   * @param iHttpCode
   *          Response's HTTP code
   * @param iReason
   *          Response's reason
   * @return The object itself for fluent chained calls
   */
  public OHttpResponseWrapper writeStatus(final int iHttpCode, final String iReason) throws IOException {
    response.writeStatus(iHttpCode, iReason);
    return this;
  }

  /**
   * Sets the response's headers using the keep-alive.
   *
   * @param iContentType
   *          Response's content type
   * @return The object itself for fluent chained calls
   */

  public OHttpResponseWrapper writeHeaders(final String iContentType) throws IOException {
    response.writeHeaders(iContentType);
    return this;
  }

  /**
   * Sets the response's headers specifying when using the keep-alive or not.
   *
   * @param iContentType
   *          Response's content type
   * @param iKeepAlive
   *          Use the keep-alive of the connection
   * @return The object itself for fluent chained calls
   */
  public OHttpResponseWrapper writeHeaders(final String iContentType, final boolean iKeepAlive) throws IOException {
    response.writeHeaders(iContentType, iKeepAlive);
    return this;
  }

  /**
   * Writes a line in the response. A line feed will be appended at the end of the content.
   *
   * @param iContent
   *          Content to send as string
   * @return The object itself for fluent chained calls
   */
  public OHttpResponseWrapper writeLine(final String iContent) throws IOException {
    response.writeLine(iContent);
    return this;
  }

  /**
   * Writes content directly to the response.
   *
   * @param iContent
   *          Content to send as string
   * @return The object itself for fluent chained calls
   */
  public OHttpResponseWrapper writeContent(final String iContent) throws IOException {
    response.writeContent(iContent);
    return this;
  }

  /**
   * Writes records as response. The records are serialized in JSON format.
   *
   * @param iRecords
   *          List of records to serialize
   * @return The object itself for fluent chained calls
   */
  public OHttpResponseWrapper writeRecords(final List<OIdentifiable> iRecords) throws IOException {
    response.writeRecords(iRecords);
    return this;
  }

  /**
   * Writes records as response specifying a fetch-plan to serialize nested records. The records are serialized in JSON format.
   *
   * @param iRecords
   *          List of records to serialize
   * @param iFetchPlan
   *          Fetch plan to specify nested records
   * @return The object itself for fluent chained calls
   */
  public OHttpResponseWrapper writeRecords(final Object iRecords, final String iFetchPlan) throws IOException {
    response.writeRecords(iRecords, iFetchPlan);
    return this;
  }

  /**
   * Writes a record as response. The record is serialized in JSON format.
   *
   * @param iRecord
   *          Record to serialize
   * @return The object itself for fluent chained calls
   */
  public OHttpResponseWrapper writeRecord(final ORecord iRecord) throws IOException {
    response.writeRecord(iRecord);
    return this;
  }

  /**
   * Writes a record as response. The record is serialized in JSON format.
   *
   * @param iRecord
   *          Record to serialize
   * @param iFetchPlan
   *          Fetch plan to specify nested records
   * @return The object itself for fluent chained calls
   */
  public OHttpResponseWrapper writeRecord(final ORecord iRecord, final String iFetchPlan) throws IOException {
    response.writeRecord(iRecord, iFetchPlan, null);
    return this;
  }

  /**
   * Sends the complete HTTP response in one call.
   *
   * @param iCode
   *          HTTP response's Code
   * @param iReason
   *          Response's reason
   * @param iContentType
   *          Response's content type
   * @param iContent
   *          Content to send. Content can be a string for plain text, binary data to return directly binary information,
   *          OIdentifiable for a single record and Collection<OIdentifiable> for a collection of records
   * @return The object itself for fluent chained calls
   */
  public OHttpResponseWrapper send(final int iCode, final String iReason, final String iContentType, final Object iContent)
      throws IOException {
    response.send(iCode, iReason, iContentType, iContent, null);
    return this;
  }

  /**
   * Sends the complete HTTP response in one call specifying additional headers. Keep-alive is set.
   *
   * @param iCode
   *          HTTP response's Code
   * @param iReason
   *          Response's reason
   * @param iContentType
   *          Response's content type
   * @param iContent
   *          Content to send. Content can be a string for plain text, binary data to return directly binary information,
   *          OIdentifiable for a single record and Collection<OIdentifiable> for a collection of records
   * @param iHeaders
   *          Response's additional headers
   * @return The object itself for fluent chained calls
   */
  public OHttpResponseWrapper send(final int iCode, final String iReason, final String iContentType, final Object iContent,
      final String iHeaders) throws IOException {
    response.send(iCode, iReason, iContentType, iContent, iHeaders);
    return this;
  }

  /**
   * Sends the complete HTTP response in one call specifying a stream as content.
   *
   * @param iCode
   *          HTTP response's Code
   * @param iReason
   *          Response's reason
   * @param iContentType
   *          Response's content type
   * @param iContent
   *          java.io.InputStream object
   * @param iSize
   *          Content size in bytes
   * @return The object itself for fluent chained calls
   */
  public OHttpResponseWrapper sendStream(final int iCode, final String iReason, final String iContentType,
      final InputStream iContent, final long iSize) throws IOException {
    response.sendStream(iCode, iReason, iContentType, iContent, iSize);
    return this;
  }

  /**
   * Sends the complete HTTP response in one call specifying a stream as content.
   *
   * @param iCode
   *          HTTP response's Code
   * @param iReason
   *          Response's reason
   * @param iContentType
   *          Response's content type
   * @param iContent
   *          java.io.InputStream object
   * @param iSize
   *          Content size in bytes
   * @param iFileName
   *          Optional file name
   * @return The object itself for fluent chained calls
   */
  public OHttpResponseWrapper sendStream(final int iCode, final String iReason, final String iContentType,
      final InputStream iContent, final long iSize, final String iFileName) throws IOException {
    response.sendStream(iCode, iReason, iContentType, iContent, iSize, iFileName);
    return this;
  }

  /**
   * Flushes the content to the TCP/IP socket.
   *
   * @return The object itself for fluent chained calls
   */
  public OHttpResponseWrapper flush() throws IOException {
    response.flush();
    return this;
  }

  public String getContentType() {
    return response.getContentType();
  }

  public void setContentType(final String contentType) {
    response.setContentType(contentType);
  }

  public String getContent() {
    return response.getContent();
  }

  public void setContent(String content) {
    response.setContent(content);
  }

  public int getCode() {
    return response.getCode();
  }

  public void setCode(int code) {
    response.setCode(code);
  }
}


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

import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal;
import com.orientechnologies.orient.core.db.record.ORecordOperation;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.record.ORecord;
import com.orientechnologies.orient.core.storage.OStorage;

/**
 * Iterator class to browse forward and backward the records of a cluster. Once browsed in a direction, the iterator cannot change
 * it.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 */
public class ORecordIteratorCluster<REC extends ORecord> extends OIdentifiableIterator<REC> {
  private ORecord currentRecord;

  public ORecordIteratorCluster(final ODatabaseDocumentInternal iDatabase, final int iClusterId) {
    this(iDatabase, iClusterId, ORID.CLUSTER_POS_INVALID, ORID.CLUSTER_POS_INVALID, OStorage.LOCKING_STRATEGY.DEFAULT);
  }

  public ORecordIteratorCluster(final ODatabaseDocumentInternal iDatabase, final int iClusterId, final long firstClusterEntry,
      final long lastClusterEntry) {
    this(iDatabase, iClusterId, firstClusterEntry, lastClusterEntry, OStorage.LOCKING_STRATEGY.NONE);
  }

  protected ORecordIteratorCluster(final ODatabaseDocumentInternal database) {
    super(database, OStorage.LOCKING_STRATEGY.NONE);
  }

  @Deprecated
  public ORecordIteratorCluster(final ODatabaseDocumentInternal iDatabase, final int iClusterId, final long firstClusterEntry,
      final long lastClusterEntry, final OStorage.LOCKING_STRATEGY iLockingStrategy) {
    super(iDatabase, iLockingStrategy);

    if (iClusterId == ORID.CLUSTER_ID_INVALID)
      throw new IllegalArgumentException("The clusterId is invalid");

    checkForSystemClusters(iDatabase, new int[] { iClusterId });

    current.setClusterId(iClusterId);
    final long[] range = database.getStorage().getClusterDataRange(current.getClusterId());

    if (firstClusterEntry == ORID.CLUSTER_POS_INVALID)
      this.firstClusterEntry = range[0];
    else
      this.firstClusterEntry = firstClusterEntry > range[0] ? firstClusterEntry : range[0];

    if (lastClusterEntry == ORID.CLUSTER_POS_INVALID)
      this.lastClusterEntry = range[1];
    else
      this.lastClusterEntry = lastClusterEntry < range[1] ? lastClusterEntry : range[1];

    totalAvailableRecords = database.countClusterElements(current.getClusterId());

    txEntries = iDatabase.getTransaction().getNewRecordEntriesByClusterIds(new int[] { iClusterId });

    if (txEntries != null)
      // ADJUST TOTAL ELEMENT BASED ON CURRENT TRANSACTION'S ENTRIES
      for (ORecordOperation entry : txEntries) {
        switch (entry.type) {
        case ORecordOperation.CREATED:
          totalAvailableRecords++;
          break;

        case ORecordOperation.DELETED:
          totalAvailableRecords--;
          break;
        }
      }

    begin();
  }

  @Override
  public boolean hasPrevious() {
    checkDirection(false);

    updateRangesOnLiveUpdate();

    if (currentRecord != null) {
      return true;
    }

    if (limit > -1 && browsedRecords >= limit)
      // LIMIT REACHED
      return false;

    boolean thereAreRecordsToBrowse = getCurrentEntry() > firstClusterEntry;

    if (thereAreRecordsToBrowse) {
      ORecord record = getRecord();
      currentRecord = readCurrentRecord(record, -1);
    }

    return currentRecord != null;
  }

  public boolean hasNext() {
    checkDirection(true);

    if (Thread.interrupted())
      // INTERRUPTED
      return false;

    updateRangesOnLiveUpdate();

    if (currentRecord != null) {
      return true;
    }

    if (limit > -1 && browsedRecords >= limit)
      // LIMIT REACHED
      return false;

    if (browsedRecords >= totalAvailableRecords)
      return false;

    if (!(current.getClusterPosition() < ORID.CLUSTER_POS_INVALID) && getCurrentEntry() < lastClusterEntry) {
      ORecord record = getRecord();
      try {
        currentRecord = readCurrentRecord(record, +1);
      } catch (Exception e) {
        OLogManager.instance().error(this, "Error during read of record", e);

        final ORID recordRid = record == null ? null : record.getIdentity();

        if (recordRid != null)
          brokenRIDs.add(recordRid.copy());

        currentRecord = null;
      }

      if (currentRecord != null)
        return true;
    }

    // CHECK IN TX IF ANY
    if (txEntries != null)
      return txEntries.size() - (currentTxEntryPosition + 1) > 0;

    return false;
  }

  /**
   * Return the element at the current position and move backward the cursor to the previous position available.
   *
   * @return the previous record found, otherwise the NoSuchElementException exception is thrown when no more records are found.
   */
  @SuppressWarnings("unchecked")
  @Override
  public REC previous() {
    checkDirection(false);

    if (currentRecord != null) {
      try {
        return (REC) currentRecord;
      } finally {
        currentRecord = null;
      }
    }
    // ITERATE UNTIL THE PREVIOUS GOOD RECORD
    while (hasPrevious()) {
      try {
        return (REC) currentRecord;
      } finally {
        currentRecord = null;
      }
    }

    return null;
  }

  /**
   * Return the element at the current position and move forward the cursor to the next position available.
   *
   * @return the next record found, otherwise the NoSuchElementException exception is thrown when no more records are found.
   */
  @SuppressWarnings("unchecked")
  public REC next() {
    checkDirection(true);

    ORecord record;

    // ITERATE UNTIL THE NEXT GOOD RECORD
    while (hasNext()) {
      // FOUND
      if (currentRecord != null) {
        try {
          return (REC) currentRecord;
        } finally {
          currentRecord = null;
        }
      }

      record = getTransactionEntry();
      if (record != null)
        return (REC) record;
    }

    return null;
  }

  /**
   * Move the iterator to the begin of the range. If no range was specified move to the first record of the cluster.
   *
   * @return The object itself
   */
  @Override
  public ORecordIteratorCluster<REC> begin() {
    browsedRecords = 0;

    updateRangesOnLiveUpdate();
    resetCurrentPosition();

    currentRecord = readCurrentRecord(getRecord(), +1);

    return this;
  }

  /**
   * Move the iterator to the end of the range. If no range was specified move to the last record of the cluster.
   *
   * @return The object itself
   */
  @Override
  public ORecordIteratorCluster<REC> last() {
    browsedRecords = 0;

    updateRangesOnLiveUpdate();
    resetCurrentPosition();

    currentRecord = readCurrentRecord(getRecord(), -1);

    return this;
  }

  /**
   * Tell to the iterator that the upper limit must be checked at every cycle. Useful when concurrent deletes or additions change
   * the size of the cluster while you're browsing it. Default is false.
   *
   * @param iLiveUpdated True to activate it, otherwise false (default)
   *
   * @see #isLiveUpdated()
   */
  @Override
  public ORecordIteratorCluster<REC> setLiveUpdated(boolean iLiveUpdated) {
    super.setLiveUpdated(iLiveUpdated);

    // SET THE RANGE LIMITS
    if (iLiveUpdated) {
      firstClusterEntry = 0L;
      lastClusterEntry = Long.MAX_VALUE;
    } else {
      final long[] range = database.getStorage().getClusterDataRange(current.getClusterId());
      firstClusterEntry = range[0];
      lastClusterEntry = range[1];
    }

    totalAvailableRecords = database.countClusterElements(current.getClusterId());

    return this;
  }

  private void updateRangesOnLiveUpdate() {
    if (liveUpdated) {
      final long[] range = database.getStorage().getClusterDataRange(current.getClusterId());

      firstClusterEntry = range[0];
      lastClusterEntry = range[1];
    }
  }
}


package com.orientechnologies.orient.object.db;

import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal;
import com.orientechnologies.orient.core.db.ODatabaseType;
import com.orientechnologies.orient.core.db.OrientDB;
import com.orientechnologies.orient.core.db.OrientDBConfig;
import com.orientechnologies.orient.core.db.object.ODatabaseObject;

import java.util.List;

/**
 * OrientDB Object management environment, it allow to connect to an environment and manipulate databases or open sessions.
 *

 * Usage example:
 *

 * Remote Example:
 *

 *
 * OrientDBObject orientDbObject = new OrientDBObject("remote:localhost","root","root");
 * if(orientDbObject.createIfNotExists("test",ODatabaseType.MEMORY)){
 *  ODatabaseDocument session = orientDbObject.open("test","admin","admin");
 *  session.createClass("MyClass");
 *  session.close();
 * }
 * ODatabaseObject session = orientDbObject.open("test","writer","writer");
 * //...
 * session.close();
 * orientDbObject.close();
 *
 *

 *

 * Embedded example:
 *

 *
 * OrientDBObject orientDbObject = new OrientDBObject("embedded:./databases/",null,null);
 * orientDbObject.create("test",ODatabaseType.PLOCAL);
 * ODatabaseObject session = orientDbObject.open("test","admin","admin");
 * //...
 * session.close();
 * orientDbObject.close();
 *
 *

 *

 * Database Manipulation Example:
 *

 *

 *
 * OrientDB orientDbObject = ...
 * if(!orientDbObject.exists("one")){
 *  orientDbObject.create("one",ODatabaseType.PLOCAL);
 * }
 * if(orientDbObject.exists("two")){
 *  orientDbObject.drop("two");
 * }
 * List&ltString&gt databases = orientDbObject.list();
 * assertEquals(databases.size(),1);
 * assertEquals(databases.get("0"),"one");
 *
 *

 *

 *

 *

 *

 * Created by tglman on 13/01/17.
 */
public class OrientDBObject implements AutoCloseable {

  private OrientDB orientDB;

  /**
   * Create a new OrientDb Object instance from a given {@link OrientDB}
   *
   * @param orientDB the given environment.
   */
  public OrientDBObject(OrientDB orientDB) {
    this.orientDB = orientDB;
  }

  /**
   * Create a new OrientDb Object instance for a specific environment
   *

   * possible kind of urls 'embedded','remote', for the case of remote and distributed can be specified multiple nodes using comma.
   *

   * Remote Example:
   *

   *
   * OrientDBObject orientDbObject = new OrientDBObject("remote:localhost");
   * ODatabaseObject session = orientDbObject.open("test","admin","admin");
   * //...
   * session.close();
   * orientDbObject.close();
   *
   *

   *

   * Embedded Example:
   *

   *
   * OrientDBObject orientDbObject = new OrientDBObject("embedded:./databases/");
   * ODatabaseObject session = orientDbObject.open("test","admin","admin");
   * //...
   * session.close();
   * orientDbObject.close();
   *
   *

   *
   * @param environment the url for the specific environment.
   * @param config      configuration for the specific environment for the list of option {@see OGlobalConfiguration}.
   */
  public OrientDBObject(String environment, OrientDBConfig config) {
    this(environment, null, null, config);
  }

  /**
   * Create a new OrientDB Object instance for a specific environment
   *

   * possible kind of urls 'embedded','remote', for the case of remote and distributed can be specified multiple nodes using comma.
   *

   * Remote Example:
   *

   *
   * OrientDBObject orientDbObject = new OrientDBObject("remote:localhost","root","root");
   * orientDbObject.create("test",ODatabaseType.PLOCAL);
   * ODatabaseObject session = orientDbObject.open("test","admin","admin");
   * //...
   * session.close();
   * orientDbObject.close();
   *
   *

   *

   * Embedded Example:
   *

   *
   * OrientDBObject orientDbObject = new OrientDBObject("embedded:./databases/",null,null);
   * orientDbObject.create("test",ODatabaseType.MEMORY);
   * ODatabaseObject session = orientDbObject.open("test","admin","admin");
   * //...
   * session.close();
   * orientDbObject.close();
   *
   *

   *
   * @param environment    the url for the specific environment.
   * @param serverUser     the server user allowed to manipulate databases.
   * @param serverPassword relative to the server user.
   * @param config         configuration for the specific environment for the list of option {@see OGlobalConfiguration}.
   */
  public OrientDBObject(String environment, String serverUser, String serverPassword, OrientDBConfig config) {
    this.orientDB = new OrientDB(environment, serverUser, serverPassword, config);
  }

  /**
   * Open a database specified by name using the username and password if needed
   *
   * @param name     of the database to open
   * @param user     the username allowed to open the database
   * @param password related to the specified username
   *
   * @return the opened database
   */
  public ODatabaseObject open(String name, String user, String password) {
    return new OObjectDatabaseTx((ODatabaseDocumentInternal) orientDB.open(name, user, password));
  }

  /**
   * Open a database specified by name using the username and password if needed, with specific configuration
   *
   * @param name     of the database to open
   * @param user     the username allowed to open the database
   * @param password related to the specified username
   * @param config   database specific configuration that override the orientDB global settings where needed.
   *
   * @return the opened database
   */
  public ODatabaseObject open(String name, String user, String password, OrientDBConfig config) {
    return new OObjectDatabaseTx((ODatabaseDocumentInternal) orientDB.open(name, user, password, config));
  }

  /**
   * Create a new database
   *
   * @param name database name
   * @param type can be plocal or memory
   */
  public void create(String name, ODatabaseType type) {
    orientDB.create(name, type);
  }

  /**
   * Create a new database
   *
   * @param name   database name
   * @param type   can be plocal or memory
   * @param config database specific configuration that override the orientDB global settings where needed.
   */
  public void create(String name, ODatabaseType type, OrientDBConfig config) {
    orientDB.create(name, type, config);
  }

  /**
   * Check if a database exists
   *
   * @param name database name to check
   *
   * @return boolean true if exist false otherwise.
   */
  public boolean exists(String name) {
    return orientDB.exists(name);
  }

  /**
   * Drop a database
   *
   * @param name database name
   */
  public void drop(String name) {
    orientDB.drop(name);
  }

  /**
   * List of database exiting in the current environment
   *
   * @return a set of databases names.
   */
  public List<String> list() {
    return orientDB.list();
  }

  public void close() {
    orientDB.close();
  }

  protected OrientDB getOrientDB() {
    return orientDB;
  }
}


/* Generated By:JJTree: Do not edit this line. OArrayRangeSelector.java Version 4.3 */
/* JavaCCOptions:MULTI=true,NODE_USES_PARSER=false,VISITOR=true,TRACK_TOKENS=true,NODE_PREFIX=O,NODE_EXTENDS=,NODE_FACTORY=,SUPPORT_CLASS_VISIBILITY_PUBLIC=true */
package com.orientechnologies.orient.core.sql.parser;

import com.orientechnologies.common.collection.OMultiValue;
import com.orientechnologies.orient.core.command.OCommandContext;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.exception.OCommandExecutionException;
import com.orientechnologies.orient.core.sql.executor.OResult;
import com.orientechnologies.orient.core.sql.executor.OResultInternal;

import java.lang.reflect.Array;
import java.util.*;

public class OArrayRangeSelector extends SimpleNode {
  protected Integer from;
  protected Integer to;
  protected boolean newRange = false;
  protected boolean included = false;

  protected OArrayNumberSelector fromSelector;
  protected OArrayNumberSelector toSelector;

  public OArrayRangeSelector(int id) {
    super(id);
  }

  public OArrayRangeSelector(OrientSql p, int id) {
    super(p, id);
  }

  /**
   * Accept the visitor.
   **/
  public Object jjtAccept(OrientSqlVisitor visitor, Object data) {
    return visitor.visit(this, data);
  }

  public void toString(Map<Object, Object> params, StringBuilder builder) {
    if (from != null) {
      builder.append(from);
    } else {
      fromSelector.toString(params, builder);
    }
    if (newRange) {
      builder.append("..");
      if (included) {
        builder.append('.');
      }
    } else {
      builder.append("-");
    }
    if (to != null) {
      builder.append(to);
    } else {
      toSelector.toString(params, builder);
    }
  }

  public Object execute(OIdentifiable iCurrentRecord, Object result, OCommandContext ctx) {
    if (result == null) {
      return null;
    }
    if (!OMultiValue.isMultiValue(result)) {
      return null;
    }
    Integer lFrom = from;
    if (fromSelector != null) {
      lFrom = fromSelector.getValue(iCurrentRecord, result, ctx);
    }
    if (lFrom == null) {
      lFrom = 0;
    }
    Integer lTo = to;
    if (toSelector != null) {
      lTo = toSelector.getValue(iCurrentRecord, result, ctx);
    }
    if(included){
      lTo++;
    }
    if (lFrom > lTo) {
      return null;
    }
    Object[] arrayResult = OMultiValue.array(result);

    if (arrayResult == null || arrayResult.length == 0) {
      return arrayResult;
    }
    lFrom = Math.max(lFrom, 0);
    if (arrayResult.length < lFrom) {
      return null;
    }
    lFrom = Math.min(lFrom, arrayResult.length - 1);

    lTo = Math.min(lTo, arrayResult.length);

    return Arrays.asList(Arrays.copyOfRange(arrayResult, lFrom, lTo));
  }

  public Object execute(OResult iCurrentRecord, Object result, OCommandContext ctx) {
    if (result == null) {
      return null;
    }
    if (!OMultiValue.isMultiValue(result)) {
      return null;
    }
    Integer lFrom = from;
    if (fromSelector != null) {
      lFrom = fromSelector.getValue(iCurrentRecord, result, ctx);
    }
    if (lFrom == null) {
      lFrom = 0;
    }
    Integer lTo = to;
    if (toSelector != null) {
      lTo = toSelector.getValue(iCurrentRecord, result, ctx);
    }
    if(included){
      lTo++;
    }
    if (lFrom > lTo) {
      return null;
    }
    Object[] arrayResult = OMultiValue.array(result);

    if (arrayResult == null || arrayResult.length == 0) {
      return arrayResult;
    }
    lFrom = Math.max(lFrom, 0);
    if (arrayResult.length < lFrom) {
      return null;
    }
    lFrom = Math.min(lFrom, arrayResult.length - 1);

    lTo = Math.min(lTo, arrayResult.length);

    return Arrays.asList(Arrays.copyOfRange(arrayResult, lFrom, lTo));
  }

  public boolean needsAliases(Set<String> aliases) {
    if (fromSelector != null && fromSelector.needsAliases(aliases)) {
      return true;
    }
    if (toSelector != null && toSelector.needsAliases(aliases)) {
      return true;
    }
    return false;
  }

  public OArrayRangeSelector copy() {
    OArrayRangeSelector result = new OArrayRangeSelector(-1);
    result.from = from;
    result.to = to;
    result.newRange = newRange;
    result.included = included;

    result.fromSelector = fromSelector == null ? null : fromSelector.copy();
    result.toSelector = toSelector == null ? null : toSelector.copy();

    return result;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o)
      return true;
    if (o == null || getClass() != o.getClass())
      return false;

    OArrayRangeSelector that = (OArrayRangeSelector) o;

    if (newRange != that.newRange)
      return false;
    if (included != that.included)
      return false;
    if (from != null ? !from.equals(that.from) : that.from != null)
      return false;
    if (to != null ? !to.equals(that.to) : that.to != null)
      return false;
    if (fromSelector != null ? !fromSelector.equals(that.fromSelector) : that.fromSelector != null)
      return false;
    if (toSelector != null ? !toSelector.equals(that.toSelector) : that.toSelector != null)
      return false;

    return true;
  }

  @Override
  public int hashCode() {
    int result = from != null ? from.hashCode() : 0;
    result = 31 * result + (to != null ? to.hashCode() : 0);
    result = 31 * result + (newRange ? 1 : 0);
    result = 31 * result + (included ? 1 : 0);
    result = 31 * result + (fromSelector != null ? fromSelector.hashCode() : 0);
    result = 31 * result + (toSelector != null ? toSelector.hashCode() : 0);
    return result;
  }

  public void extractSubQueries(SubQueryCollector collector) {
    if (fromSelector != null) {
      fromSelector.extractSubQueries(collector);
    }
    if (toSelector != null) {
      toSelector.extractSubQueries(collector);
    }
  }

  public boolean refersToParent() {
    if (fromSelector != null && fromSelector.refersToParent()) {
      return true;
    }
    if (toSelector != null && toSelector.refersToParent()) {
      return true;
    }
    return false;
  }

  /**
   * @param target
   * @param value
   * @param ctx
   *
   * @return
   */
  public void setValue(Object target, Object value, OCommandContext ctx) {
    if (target == null) {
      return;
    }
    if (target.getClass().isArray()) {
      setArrayValue(target, value, ctx);
    } else if (target instanceof List) {
      setValue((List) target, value, ctx);
    } else if (OMultiValue.isMultiValue(value)) {
      //TODO
    }
    //TODO

  }

  public void setValue(List target, Object value, OCommandContext ctx) {
    int from = this.from == null ? 0 : this.from;
    int to = target.size() - 1;
    if (this.to != null) {
      to = this.to;
      if (!included) {
        to--;
      }
    }
    if (from > to) {
      target.clear();
      return;
    }
    for (int i = 0; i <= to; i++) {
      if (i < from && target.size() - 1 < i) {
        target.set(i, null);
      } else if (i >= from) {
        target.set(i, value);
      }
      //else leave untouched the existing element
    }
  }

  public void setValue(Set target, Object value, OCommandContext ctx) {
    Set result = new LinkedHashSet<>();
    int from = this.from == null ? 0 : this.from;
    int to = target.size() - 1;
    if (this.to != null) {
      to = this.to;
      if (!included) {
        to--;
      }
    }
    if (from > to) {
      target.clear();
      return;
    }
    Iterator targetIterator = target.iterator();
    for (int i = 0; i <= to; i++) {
      Object next = null;
      if (targetIterator.hasNext()) {
        next = targetIterator.next();
      }
      if (i < from && target.size() - 1 < i) {
        result.add(null);
      } else if (i >= from) {
        result.add(value);
      } else {
        result.add(next);
      }
      target.clear();
      target.addAll(result);
    }
  }

  public void setValue(Map target, Object value, OCommandContext ctx) {
    int from = this.from == null ? 0 : this.from;
    int to = this.to;
    if (!included) {
      to--;
    }
    if (from > to) {
      target.clear();
      return;
    }
    for (int i = from; i <= to; i++) {
      target.put(i, value);
    }
  }

  private void setArrayValue(Object target, Object value, OCommandContext ctx) {

    int from = this.from == null ? 0 : this.from;
    int to = Array.getLength(target) - 1;
    if (this.to != null) {
      to = this.to;
      if (!included) {
        to--;
      }
    }
    if (from > to || from >= Array.getLength(target)) {
      return;
    }
    to = Math.min(to, Array.getLength(target) - 1);
    for (int i = from; i <= to; i++) {
      Array.set(target, i, value);//TODO type conversion?
    }
  }

  public void applyRemove(Object currentValue, OResultInternal originalRecord, OCommandContext ctx) {
    if (currentValue == null) {
      return;
    }
    Integer from = this.from;
    if (fromSelector != null) {
      from = fromSelector.getValue(originalRecord, null, ctx);
    }
    Integer to = this.to;
    if (toSelector != null) {
      to = toSelector.getValue(originalRecord, null, ctx);
    }
    if (from == null || to == null) {
      throw new OCommandExecutionException("Invalid range expression: " + toString() + " one of the elements is null");
    }
    if (included) {
      to++;
    }
    if (from < 0) {
      from = 0;
    }
    if (from >= to) {
      return;
    }
    int range = to - from;
    if (currentValue instanceof List) {
      List list = (List) currentValue;
      for (int i = 0; i < range; i++) {
        if (list.size() > from) {
          list.remove(from);
        } else {
          break;
        }
      }
    } else if (currentValue instanceof Set) {
      Iterator iter = ((Set) currentValue).iterator();
      int count = 0;
      while (iter.hasNext()) {
        iter.next();
        if (count >= from) {
          if (count < to) {
            iter.remove();
          } else {
            break;
          }
        }
        count++;
      }
    } else {
      throw new OCommandExecutionException(
          "Trying to remove elements from " + currentValue + " (" + currentValue.getClass().getSimpleName() + ")");
    }
  }

  public OResult serialize() {
    OResultInternal result = new OResultInternal();
    result.setProperty("from", from);
    result.setProperty("to", to);
    result.setProperty("newRange", newRange);
    result.setProperty("included", included);

    if (fromSelector != null) {
      result.setProperty("fromSelector", fromSelector.serialize());
    }
    if (toSelector != null) {
      result.setProperty("toSelector", toSelector.serialize());
    }
    return result;
  }

  public void deserialize(OResult fromResult) {
    from = fromResult.getProperty("from");
    to = fromResult.getProperty("to");
    newRange = fromResult.getProperty("newRange");
    included = fromResult.getProperty("included");

    if (fromResult.getProperty("fromSelector") != null) {
      fromSelector = new OArrayNumberSelector(-1);
      fromSelector.deserialize(fromResult.getProperty("fromSelector"));
    }
    if (fromResult.getProperty("toSelector") != null) {
      toSelector = new OArrayNumberSelector(-1);
      toSelector.deserialize(fromResult.getProperty("toSelector"));
    }
  }
}
/* JavaCC - OriginalChecksum=594a372e31fcbcd3ed962c2260e76468 (do not edit this line) */


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

import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.orient.core.db.ODatabase;
import com.orientechnologies.orient.core.db.ODatabasePoolBase;
import com.orientechnologies.orient.core.db.ODatabasePooled;
import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.exception.ODatabaseException;
import com.orientechnologies.orient.core.metadata.security.OToken;

/**
 * Pooled wrapper to the ODatabaseDocumentTx class. Allows to being reused across calls. The close() method does not close the
 * database for real but release it to the owner pool. The database born as opened and will leave open until the pool is closed.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 * @see ODatabasePoolBase
 */
@SuppressWarnings("unchecked")
public class ODatabaseDocumentTxPooled extends ODatabaseDocumentTx implements ODatabasePooled {

  private ODatabaseDocumentPool ownerPool;
  private String                userName;

  public ODatabaseDocumentTxPooled(final ODatabaseDocumentPool iOwnerPool, final String iURL, final String iUserName,
      final String iUserPassword) {
    super(iURL);
    ownerPool = iOwnerPool;
    userName = iUserName;

    super.open(iUserName, iUserPassword);
  }

  public void reuse(final Object iOwner, final Object[] iAdditionalArgs) {
    ownerPool = (ODatabaseDocumentPool) iOwner;
    getLocalCache().invalidate();
    // getMetadata().reload();
    ODatabaseRecordThreadLocal.instance().set(this);

    try {
      callOnOpenListeners();
    } catch (Exception e) {
      OLogManager.instance().error(this, "Error on reusing database '%s' in pool", e, getName());
    }
  }

  @Override
  public ODatabaseDocumentTxPooled open(final String iUserName, final String iUserPassword) {
    throw new UnsupportedOperationException(
        "Database instance was retrieved from a pool. You cannot open the database in this way. Use directly a ODatabaseDocumentTx instance if you want to manually open the connection");
  }

  @Override
  public ODatabaseDocumentTxPooled open(final OToken iToken) {
    throw new UnsupportedOperationException(
        "Database instance was retrieved from a pool. You cannot open the database in this way. Use directly a ODatabaseDocumentTx instance if you want to manually open the connection");
  }

  @Override
  public ODatabaseDocumentTxPooled create() {
    throw new UnsupportedOperationException(
        "Database instance was retrieved from a pool. You cannot open the database in this way. Use directly a ODatabaseDocumentTx instance if you want to manually open the connection");
  }

  @Override
  public <DB extends ODatabase> DB create(String incrementalBackupPath) {
    throw new UnsupportedOperationException(
        "Database instance was retrieved from a pool. You cannot open the database in this way. Use directly a ODatabaseDocumentTx instance if you want to manually open the connection");
  }

  public boolean isUnderlyingOpen() {
    return !super.isClosed();
  }

  @Override
  public boolean isClosed() {
    return ownerPool == null || super.isClosed();
  }

  /**
   * @return true if database is obtained from the pool and false otherwise.
   */
  @Override
  public boolean isPooled() {
    return true;
  }

  /**
   * Avoid to close it but rather release itself to the owner pool.
   */
  @Override
  public void close() {
    if (isClosed())
      return;

    checkOpenness();

    if (ownerPool != null && ownerPool.getConnectionsInCurrentThread(getURL(), userName) > 1) {
      ownerPool.release(this);
      return;
    }

    try {
      commit(true);
    } catch (Exception e) {
      OLogManager.instance().error(this, "Error on releasing database '%s' in pool", e, getName());
    }

    try {
      callOnCloseListeners();
    } catch (Exception e) {
      OLogManager.instance().error(this, "Error on releasing database '%s' in pool", e, getName());
    }

    getLocalCache().clear();

    if (ownerPool != null) {
      final ODatabaseDocumentPool localCopy = ownerPool;
      ownerPool = null;
      localCopy.release(this);
    }

    ODatabaseRecordThreadLocal.instance().remove();
  }

  public void forceClose() {
    super.close();
  }

//  @Override
  protected void checkOpenness() {
    if (ownerPool == null)
      throw new ODatabaseException(
          "Database instance has been released to the pool. Get another database instance from the pool with the right username and password");

//    super.checkOpenness();
  }
}


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

package com.orientechnologies.orient.core.storage.ridbag.sbtree;

import com.orientechnologies.common.serialization.types.OIntegerSerializer;
import com.orientechnologies.orient.core.OOrientShutdownListener;
import com.orientechnologies.orient.core.OOrientStartupListener;
import com.orientechnologies.orient.core.config.OGlobalConfiguration;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.db.record.ridbag.ORidBag;
import com.orientechnologies.orient.core.exception.OAccessToSBtreeCollectionManagerIsProhibitedException;
import com.orientechnologies.orient.core.serialization.serializer.binary.impl.OLinkSerializer;
import com.orientechnologies.orient.core.storage.impl.local.OAbstractPaginatedStorage;
import com.orientechnologies.orient.core.storage.impl.local.paginated.atomicoperations.OAtomicOperation;
import com.orientechnologies.orient.core.storage.impl.local.paginated.atomicoperations.OAtomicOperationsManager;
import com.orientechnologies.orient.core.storage.index.sbtreebonsai.local.OSBTreeBonsai;
import com.orientechnologies.orient.core.storage.index.sbtreebonsai.local.OSBTreeBonsaiLocal;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * @author Artem Orobets (enisher-at-gmail.com)
 */
public class OSBTreeCollectionManagerShared extends OSBTreeCollectionManagerAbstract
    implements OOrientStartupListener, OOrientShutdownListener {
  /**
   * Message which is provided during throwing of {@link OAccessToSBtreeCollectionManagerIsProhibitedException}.
   */
  private static final String PROHIBITED_EXCEPTION_MESSAGE = "Access to the manager of RidBags which are based on B-Tree "
      + "implementation is prohibited. Typically it means that you use database under distributed cluster configuration. Please check "
      + "that following setting in your server configuration " + OGlobalConfiguration.RID_BAG_EMBEDDED_TO_SBTREEBONSAI_THRESHOLD
      .getKey() + " is set to " + Integer.MAX_VALUE;

  private final OAbstractPaginatedStorage storage;
  private volatile ThreadLocal<Map<UUID, OBonsaiCollectionPointer>> collectionPointerChanges = new CollectionPointerChangesThreadLocal();

  /**
   * If this flag is set to {@code true} then all access to the manager will be prohibited and exception
   * {@link OAccessToSBtreeCollectionManagerIsProhibitedException} will be thrown.
   */
  private volatile boolean prohibitAccess = false;

  public OSBTreeCollectionManagerShared(OAbstractPaginatedStorage storage) {
    super(storage);

    this.storage = storage;
  }

  // for testing purposes
  /* internal */ OSBTreeCollectionManagerShared(int evictionThreshold, int cacheMaxSize, OAbstractPaginatedStorage storage) {
    super(storage, evictionThreshold, cacheMaxSize);

    this.storage = storage;
  }

  @Override
  public void onShutdown() {
    collectionPointerChanges = null;
    super.onShutdown();
  }

  @Override
  public void onStartup() {
    super.onStartup();
    if (collectionPointerChanges == null) {
      collectionPointerChanges = new CollectionPointerChangesThreadLocal();
    }
  }

  /**
   * Once this method is called any attempt to load/create/delete b-tree will be resulted in exception thrown.
   */
  public void prohibitAccess() {
    prohibitAccess = true;
  }

  private void checkAccess() {
    if (prohibitAccess) {
      throw new OAccessToSBtreeCollectionManagerIsProhibitedException(PROHIBITED_EXCEPTION_MESSAGE);
    }
  }

  @Override
  public OSBTreeBonsai<OIdentifiable, Integer> createAndLoadTree(int clusterId) throws IOException {
    checkAccess();

    return super.createAndLoadTree(clusterId);
  }

  @Override
  public OSBTreeBonsai<OIdentifiable, Integer> loadSBTree(OBonsaiCollectionPointer collectionPointer) {
    return super.loadSBTree(collectionPointer);
  }

  @Override
  public void delete(OBonsaiCollectionPointer collectionPointer) {
    super.delete(collectionPointer);
  }

  @Override
  public OBonsaiCollectionPointer createSBTree(int clusterId, UUID ownerUUID) throws IOException {
    checkAccess();

    final OBonsaiCollectionPointer pointer = super.createSBTree(clusterId, ownerUUID);

    if (ownerUUID != null) {
      Map<UUID, OBonsaiCollectionPointer> changedPointers = collectionPointerChanges.get();
      if (pointer != null && pointer.isValid()) {
        changedPointers.put(ownerUUID, pointer);
      }
    }

    return pointer;
  }

  @Override
  protected OSBTreeBonsaiLocal<OIdentifiable, Integer> createTree(int clusterId) throws IOException {

    OSBTreeBonsaiLocal<OIdentifiable, Integer> tree = new OSBTreeBonsaiLocal<>(FILE_NAME_PREFIX + clusterId, DEFAULT_EXTENSION,
        storage);
    tree.create(OLinkSerializer.INSTANCE, OIntegerSerializer.INSTANCE);

    return tree;
  }

  @Override
  protected OSBTreeBonsai<OIdentifiable, Integer> loadTree(OBonsaiCollectionPointer collectionPointer) {
    String fileName;
    OAtomicOperation atomicOperation = OAtomicOperationsManager.getCurrentOperation();
    if (atomicOperation == null) {
      fileName = storage.getWriteCache().fileNameById(collectionPointer.getFileId());
    } else {
      fileName = atomicOperation.fileNameById(collectionPointer.getFileId());
    }

    OSBTreeBonsaiLocal<OIdentifiable, Integer> tree = new OSBTreeBonsaiLocal<>(
        fileName.substring(0, fileName.length() - DEFAULT_EXTENSION.length()), DEFAULT_EXTENSION, storage);

    if (tree.load(collectionPointer.getRootPointer())) {
      return tree;
    } else {
      return null;
    }
  }

  /**
   * Change UUID to null to prevent its serialization to disk.
   */
  @Override
  public UUID listenForChanges(ORidBag collection) {
    UUID ownerUUID = collection.getTemporaryId();
    if (ownerUUID != null) {
      final OBonsaiCollectionPointer pointer = collection.getPointer();

      Map<UUID, OBonsaiCollectionPointer> changedPointers = collectionPointerChanges.get();
      if (pointer != null && pointer.isValid()) {
        changedPointers.put(ownerUUID, pointer);
      }
    }

    return null;
  }

  @Override
  public void updateCollectionPointer(UUID uuid, OBonsaiCollectionPointer pointer) {
  }

  @Override
  public void clearPendingCollections() {
  }

  @Override
  public Map<UUID, OBonsaiCollectionPointer> changedIds() {
    return collectionPointerChanges.get();
  }

  @Override
  public void clearChangedIds() {
    collectionPointerChanges.get().clear();
  }

  private static class CollectionPointerChangesThreadLocal extends ThreadLocal<Map<UUID, OBonsaiCollectionPointer>> {
    @Override
    protected Map<UUID, OBonsaiCollectionPointer> initialValue() {
      return new HashMap<>();
    }
  }
}