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

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

import com.orientechnologies.common.io.OIOUtils;
import com.orientechnologies.common.serialization.types.ODecimalSerializer;
import com.orientechnologies.orient.core.collate.OCollate;
import com.orientechnologies.orient.core.collate.ODefaultCollate;
import com.orientechnologies.orient.core.config.OStorageConfiguration;
import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal;
import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.id.ORecordId;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.util.ODateHelper;

import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimeZone;

import static com.orientechnologies.orient.core.serialization.serializer.record.binary.HelperClasses.*;

/**
 * Implementation v0 of comparator based on protocol v0.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 */
public class OBinaryComparatorV0 implements OBinaryComparator {

  public OBinaryComparatorV0() {
  }

  public boolean isBinaryComparable(final OType iType) {
    switch (iType) {
    case INTEGER:
    case LONG:
    case DATETIME:
    case SHORT:
    case STRING:
    case DOUBLE:
    case FLOAT:
    case BYTE:
    case BOOLEAN:
    case DATE:
    case BINARY:
    case LINK:
    case DECIMAL:
      return true;
    default:
      return false;
    }
  }

  /**
   * Compares if 2 field values are the same.
   *
   * @param iField1 First value to compare
   * @param iField2 Second value to compare
   *
   * @return true if they match, otherwise false
   */
  @Override
  public boolean isEqual(final OBinaryField iField1, final OBinaryField iField2) {
    final BytesContainer fieldValue1 = iField1.bytes;
    final int offset1 = fieldValue1.offset;

    final BytesContainer fieldValue2 = iField2.bytes;
    final int offset2 = fieldValue2.offset;

    try {
      switch (iField1.type) {
      case INTEGER: {
        final int value1 = OVarIntSerializer.readAsInteger(fieldValue1);

        switch (iField2.type) {
        case INTEGER: {
          final int value2 = OVarIntSerializer.readAsInteger(fieldValue2);
          return value1 == value2;
        }
        case LONG:
        case DATETIME: {
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2);
          return value1 == value2;
        }
        case DATE: {
          final long value2 = (OVarIntSerializer.readAsLong(fieldValue2) * MILLISEC_PER_DAY);
          return value1 == value2;
        }
        case SHORT: {
          final short value2 = OVarIntSerializer.readAsShort(fieldValue2);
          return value1 == value2;
        }
        case BYTE: {
          final byte value2 = readByte(fieldValue2);
          return value1 == value2;
        }
        case FLOAT: {
          final float value2 = Float.intBitsToFloat(readInteger(fieldValue2));
          return value1 == value2;
        }
        case DOUBLE: {
          final double value2 = Double.longBitsToDouble(readLong(fieldValue2));
          return value1 == value2;
        }
        case STRING: {
          return Integer.parseInt(readString(fieldValue2)) == value1;
        }
        case DECIMAL: {
          final BigDecimal value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset);
          return value1 == value2.intValue();
        }
        }
        break;
      }

      case LONG: {
        final long value1 = OVarIntSerializer.readAsLong(fieldValue1);

        switch (iField2.type) {
        case INTEGER: {
          final int value2 = OVarIntSerializer.readAsInteger(fieldValue2);
          return value1 == value2;
        }
        case LONG:
        case DATETIME: {
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2);
          return value1 == value2;
        }
        case DATE: {
          final long value2 = (OVarIntSerializer.readAsLong(fieldValue2) * MILLISEC_PER_DAY);
          return value1 == value2;
        }
        case SHORT: {
          final short value2 = OVarIntSerializer.readAsShort(fieldValue2);
          return value1 == value2;
        }
        case BYTE: {
          final byte value2 = readByte(fieldValue2);
          return value1 == value2;
        }
        case FLOAT: {
          final float value2 = Float.intBitsToFloat(readInteger(fieldValue2));
          return value1 == value2;
        }
        case DOUBLE: {
          final double value2 = Double.longBitsToDouble(readLong(fieldValue2));
          return value1 == value2;
        }
        case STRING: {
          return Long.parseLong(readString(fieldValue2)) == value1;
        }
        case DECIMAL: {
          final BigDecimal value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset);
          return value1 == value2.longValue();
        }
        }
        break;
      }

      case SHORT: {
        final short value1 = OVarIntSerializer.readAsShort(fieldValue1);

        switch (iField2.type) {
        case INTEGER: {
          final int value2 = OVarIntSerializer.readAsInteger(fieldValue2);
          return value1 == value2;
        }
        case LONG:
        case DATETIME: {
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2);
          return value1 == value2;
        }
        case DATE: {
          final long value2 = (OVarIntSerializer.readAsLong(fieldValue2) * MILLISEC_PER_DAY);
          return value1 == value2;
        }
        case SHORT: {
          final short value2 = OVarIntSerializer.readAsShort(fieldValue2);
          return value1 == value2;
        }
        case BYTE: {
          final byte value2 = readByte(fieldValue2);
          return value1 == value2;
        }
        case FLOAT: {
          final float value2 = Float.intBitsToFloat(readInteger(fieldValue2));
          return value1 == value2;
        }
        case DOUBLE: {
          final double value2 = Double.longBitsToDouble(readLong(fieldValue2));
          return value1 == value2;
        }
        case STRING: {
          return Short.parseShort(readString(fieldValue2)) == value1;
        }
        case DECIMAL: {
          final BigDecimal value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset);
          return value1 == value2.shortValue();
        }
        }
        break;
      }

      case STRING: {
        switch (iField2.type) {
        case INTEGER: {
          final int value2 = OVarIntSerializer.readAsInteger(fieldValue2);
          return Integer.parseInt(readString(fieldValue1)) == value2;
        }
        case LONG:
        case DATETIME: {
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2);
          return Long.parseLong(readString(fieldValue1)) == value2;
        }
        case DATE: {
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2) * MILLISEC_PER_DAY;
          return Long.parseLong(readString(fieldValue1)) == value2;
        }
        case SHORT: {
          final short value2 = OVarIntSerializer.readAsShort(fieldValue2);
          return Short.parseShort(readString(fieldValue1)) == value2;
        }
        case BYTE: {
          final byte value2 = readByte(fieldValue2);
          return Byte.parseByte(readString(fieldValue1)) == value2;
        }
        case FLOAT: {
          final float value2 = Float.intBitsToFloat(readInteger(fieldValue2));
          return Float.parseFloat(readString(fieldValue1)) == value2;
        }
        case DOUBLE: {
          final double value2 = Double.longBitsToDouble(readLong(fieldValue2));
          return Double.parseDouble(readString(fieldValue1)) == value2;
        }
        case STRING: {
          final int len1 = OVarIntSerializer.readAsInteger(fieldValue1);
          final int len2 = OVarIntSerializer.readAsInteger(fieldValue2);

          if (len1 != len2)
            return false;

          final OCollate collate = (iField1.collate != null && !ODefaultCollate.NAME.equals(iField1.collate.getName())) ?
              iField1.collate :
              (iField2.collate != null && !ODefaultCollate.NAME.equals(iField2.collate.getName()) ? iField2.collate : null);

          if (collate != null) {
            final String str1 = (String) collate.transform(stringFromBytes(fieldValue1.bytes, fieldValue1.offset, len1));
            final String str2 = (String) collate.transform(stringFromBytes(fieldValue2.bytes, fieldValue2.offset, len2));

            return str1.equals(str2);

          } else {
            for (int i = 0; i < len1; ++i) {
              if (fieldValue1.bytes[fieldValue1.offset + i] != fieldValue2.bytes[fieldValue2.offset + i])
                return false;
            }
          }
          return true;
        }
        case DECIMAL: {
          final BigDecimal value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset);
          return new BigDecimal(readString(fieldValue1)).equals(value2);
        }
        case BOOLEAN: {
          final boolean value2 = readByte(fieldValue2) == 1;
          return Boolean.parseBoolean(readString(fieldValue1)) == value2;
        }
        }
        break;
      }

      case DOUBLE: {
        final long value1AsLong = readLong(fieldValue1);

        switch (iField2.type) {
        case INTEGER: {
          final double value1 = Double.longBitsToDouble(value1AsLong);
          final int value2 = OVarIntSerializer.readAsInteger(fieldValue2);
          return value1 == value2;
        }
        case LONG:
        case DATETIME: {
          final double value1 = Double.longBitsToDouble(value1AsLong);
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2);
          return value1 == value2;
        }
        case SHORT: {
          final double value1 = Double.longBitsToDouble(value1AsLong);
          final short value2 = OVarIntSerializer.readAsShort(fieldValue2);
          return value1 == value2;
        }
        case BYTE: {
          final double value1 = Double.longBitsToDouble(value1AsLong);
          final byte value2 = readByte(fieldValue2);
          return value1 == value2;
        }
        case FLOAT: {
          final double value1 = Double.longBitsToDouble(value1AsLong);
          final float value2 = Float.intBitsToFloat(readInteger(fieldValue2));
          return value1 == value2;
        }
        case DOUBLE: {
          final double value2AsLong = readLong(fieldValue2);
          return value1AsLong == value2AsLong;
        }
        case STRING: {
          final double value1 = Double.longBitsToDouble(value1AsLong);
          return Double.parseDouble(readString(fieldValue2)) == value1;
        }
        case DECIMAL: {
          final double value1 = Double.longBitsToDouble(value1AsLong);
          final BigDecimal value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset);
          return value1 == value2.doubleValue();
        }
        }
        break;
      }

      case FLOAT: {
        final int value1AsInt = readInteger(fieldValue1);

        switch (iField2.type) {
        case INTEGER: {
          final float value1 = Float.intBitsToFloat(value1AsInt);
          final int value2 = OVarIntSerializer.readAsInteger(fieldValue2);
          return value1 == value2;
        }
        case LONG:
        case DATETIME: {
          final float value1 = Float.intBitsToFloat(value1AsInt);
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2);
          return value1 == value2;
        }
        case SHORT: {
          final float value1 = Float.intBitsToFloat(value1AsInt);
          final short value2 = OVarIntSerializer.readAsShort(fieldValue2);
          return value1 == value2;
        }
        case BYTE: {
          final float value1 = Float.intBitsToFloat(value1AsInt);
          final byte value2 = readByte(fieldValue2);
          return value1 == value2;
        }
        case FLOAT: {
          final float value2AsInt = readInteger(fieldValue2);
          return value1AsInt == value2AsInt;
        }
        case DOUBLE: {
          final float value1 = Float.intBitsToFloat(value1AsInt);
          final double value2 = Double.longBitsToDouble(readLong(fieldValue2));
          return value1 == value2;
        }
        case STRING: {
          final float value1 = Float.intBitsToFloat(value1AsInt);
          return Float.parseFloat(readString(fieldValue2)) == value1;
        }
        case DECIMAL: {
          final float value1 = Float.intBitsToFloat(value1AsInt);
          final BigDecimal value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset);
          return value1 == value2.floatValue();
        }
        }
        break;
      }

      case BYTE: {
        final byte value1 = readByte(fieldValue1);

        switch (iField2.type) {
        case INTEGER: {
          final int value2 = OVarIntSerializer.readAsInteger(fieldValue2);
          return value1 == value2;
        }
        case LONG:
        case DATETIME: {
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2);
          return value1 == value2;
        }
        case SHORT: {
          final short value2 = OVarIntSerializer.readAsShort(fieldValue2);
          return value1 == value2;
        }
        case BYTE: {
          final byte value2 = readByte(fieldValue2);
          return value1 == value2;
        }
        case FLOAT: {
          final float value2 = Float.intBitsToFloat(readInteger(fieldValue2));
          return value1 == value2;
        }
        case DOUBLE: {
          final double value2 = Double.longBitsToDouble(readLong(fieldValue2));
          return value1 == value2;
        }
        case STRING: {
          final byte value2 = Byte.parseByte((readString(fieldValue2)));
          return value1 == value2;
        }
        case DECIMAL: {
          final BigDecimal value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset);
          return value1 == value2.byteValue();
        }
        }
        break;
      }

      case BOOLEAN: {
        final boolean value1 = readByte(fieldValue1) == 1;

        switch (iField2.type) {
        case BOOLEAN: {
          final boolean value2 = readByte(fieldValue2) == 1;
          return value1 == value2;
        }
        case STRING: {
          final String str = readString(fieldValue2);
          return Boolean.parseBoolean(str) == value1;
        }
        }
        break;
      }

      case DATE: {
        final long value1 = OVarIntSerializer.readAsLong(fieldValue1) * MILLISEC_PER_DAY;

        switch (iField2.type) {
        case INTEGER: {
          final int value2 = OVarIntSerializer.readAsInteger(fieldValue2);
          return value1 == value2;
        }
        case LONG:
        case DATETIME: {
          long value2 = OVarIntSerializer.readAsLong(fieldValue2);
          value2 = convertDayToTimezone(ODateHelper.getDatabaseTimeZone(), TimeZone.getTimeZone("GMT"), value2);
          return value1 == value2;
        }
        case DATE: {
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2) * MILLISEC_PER_DAY;
          return value1 == value2;
        }
        case SHORT: {
          final short value2 = OVarIntSerializer.readAsShort(fieldValue2);
          return value1 == value2;
        }
        case FLOAT: {
          final float value2 = Float.intBitsToFloat(readInteger(fieldValue2));
          return value1 == value2;
        }
        case DOUBLE: {
          final double value2 = Double.longBitsToDouble(readLong(fieldValue2));
          return value1 == value2;
        }
        case STRING: {

        }
        case DECIMAL: {
          final BigDecimal value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset);
          return value1 == value2.longValue();
        }
        }
        break;
      }

      case DATETIME: {
        final long value1 = OVarIntSerializer.readAsLong(fieldValue1);

        switch (iField2.type) {
        case INTEGER: {
          final int value2 = OVarIntSerializer.readAsInteger(fieldValue2);
          return value1 == value2;
        }
        case LONG:
        case DATETIME: {
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2);
          return value1 == value2;
        }
        case DATE: {
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2) * MILLISEC_PER_DAY;
          return value1 == value2;
        }
        case SHORT: {
          final short value2 = OVarIntSerializer.readAsShort(fieldValue2);
          return value1 == value2;
        }
        case FLOAT: {
          final float value2 = Float.intBitsToFloat(readInteger(fieldValue2));
          return value1 == value2;
        }
        case DOUBLE: {
          final double value2 = Double.longBitsToDouble(readLong(fieldValue2));
          return value1 == value2;
        }
        case STRING: {
          final String value2AsString = readString(fieldValue2);

          if (OIOUtils.isLong(value2AsString)) {
            final long value2 = Long.parseLong(value2AsString);
            return value1 == value2;
          }

          final ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.instance().getIfDefined();
          try {
            final SimpleDateFormat dateFormat = db != null ?
                db.getStorage().getConfiguration().getDateTimeFormatInstance() :
                new SimpleDateFormat(OStorageConfiguration.DEFAULT_DATETIME_FORMAT);

            final Date value2AsDate = dateFormat.parse(value2AsString);
            final long value2 = value2AsDate.getTime();
            return value1 == value2;
          } catch (ParseException ignore) {
            try {
              final SimpleDateFormat dateFormat = db != null ?
                  db.getStorage().getConfiguration().getDateFormatInstance() :
                  new SimpleDateFormat(OStorageConfiguration.DEFAULT_DATE_FORMAT);

              final Date value2AsDate = dateFormat.parse(value2AsString);
              final long value2 = value2AsDate.getTime();
              return value1 == value2;
            } catch (ParseException ignored) {
              return new Date(value1).toString().equals(value2AsString);
            }
          }
        }
        case DECIMAL: {
          final BigDecimal value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset);
          return value1 == value2.longValue();
        }
        }
        break;
      }

      case BINARY: {
        switch (iField2.type) {
        case BINARY: {
          final int length1 = OVarIntSerializer.readAsInteger(fieldValue1);
          final int length2 = OVarIntSerializer.readAsInteger(fieldValue2);
          if (length1 != length2)
            return false;

          for (int i = 0; i < length1; ++i) {
            if (fieldValue1.bytes[fieldValue1.offset + i] != fieldValue2.bytes[fieldValue2.offset + i])
              return false;
          }
          return true;
        }
        }
        break;
      }

      case LINK: {
        switch (iField2.type) {
        case LINK: {
          final int clusterId1 = OVarIntSerializer.readAsInteger(fieldValue1);
          final int clusterId2 = OVarIntSerializer.readAsInteger(fieldValue2);
          if (clusterId1 != clusterId2)
            return false;

          final long clusterPos1 = OVarIntSerializer.readAsLong(fieldValue1);
          final long clusterPos2 = OVarIntSerializer.readAsLong(fieldValue2);
          if (clusterPos1 == clusterPos2)
            return true;
          break;
        }
        case STRING: {
          return readOptimizedLink(fieldValue1, false).toString().equals(readString(fieldValue2));
        }
        }
        break;
      }

      case DECIMAL: {
        final BigDecimal value1 = ODecimalSerializer.INSTANCE.deserialize(fieldValue1.bytes, fieldValue1.offset);

        switch (iField2.type) {
        case INTEGER: {
          final int value2 = OVarIntSerializer.readAsInteger(fieldValue2);
          return value1.equals(new BigDecimal(value2));
        }
        case LONG:
        case DATETIME: {
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2);
          return value1.equals(new BigDecimal(value2));
        }
        case SHORT: {
          final short value2 = OVarIntSerializer.readAsShort(fieldValue2);
          return value1.equals(new BigDecimal(value2));
        }
        case FLOAT: {
          final float value2 = Float.intBitsToFloat(readInteger(fieldValue2));
          return value1.equals(new BigDecimal(value2));
        }
        case DOUBLE: {
          final double value2 = Double.longBitsToDouble(readLong(fieldValue2));
          return value1.equals(new BigDecimal(value2));
        }
        case STRING: {
          return value1.toString().equals(readString(fieldValue2));
        }
        case DECIMAL: {
          final BigDecimal value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset);
          return value1.equals(value2);
        }
        }
        break;
      }
      }
    } finally {
      fieldValue1.offset = offset1;
      fieldValue2.offset = offset2;
    }

    return false;
  }

  /**
   * Compares two values executing also conversion between types.
   *
   * @param iField1 First value to compare
   * @param iField2 Second value to compare
   *
   * @return 0 if they matches, >0 if first value is major than second, <0 in case is minor
   */
  @Override
  public int compare(final OBinaryField iField1, final OBinaryField iField2) {
    final BytesContainer fieldValue1 = iField1.bytes;
    final int offset1 = fieldValue1.offset;

    final BytesContainer fieldValue2 = iField2.bytes;
    final int offset2 = fieldValue2.offset;

    try {
      switch (iField1.type) {
      case INTEGER: {
        final int value1 = OVarIntSerializer.readAsInteger(fieldValue1);

        switch (iField2.type) {
        case INTEGER: {
          final int value2 = OVarIntSerializer.readAsInteger(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case LONG:
        case DATETIME: {
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case DATE: {
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2) * MILLISEC_PER_DAY;
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case SHORT: {
          final short value2 = OVarIntSerializer.readAsShort(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case BYTE: {
          final byte value2 = readByte(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case FLOAT: {
          final float value2 = Float.intBitsToFloat(readInteger(fieldValue2));
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case DOUBLE: {
          final double value2 = Double.longBitsToDouble(readLong(fieldValue2));
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case STRING: {
          final String value2 = readString(fieldValue2);
          return Integer.toString(value1).compareTo(value2);
        }
        case DECIMAL: {
          final int value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset).intValue();
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        }
        break;
      }

      case LONG: {
        final long value1 = OVarIntSerializer.readAsLong(fieldValue1);

        switch (iField2.type) {
        case INTEGER: {
          final int value2 = OVarIntSerializer.readAsInteger(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case LONG:
        case DATETIME: {
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case DATE: {
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2) * MILLISEC_PER_DAY;
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case SHORT: {
          final short value2 = OVarIntSerializer.readAsShort(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case BYTE: {
          final byte value2 = readByte(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case FLOAT: {
          final float value2 = Float.intBitsToFloat(readInteger(fieldValue2));
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case DOUBLE: {
          final double value2 = Double.longBitsToDouble(readLong(fieldValue2));
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case STRING: {
          final String value2 = readString(fieldValue2);
          return Long.toString(value1).compareTo(value2);
        }
        case DECIMAL: {
          final long value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset).longValue();
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        }
        break;
      }

      case SHORT: {
        final short value1 = OVarIntSerializer.readAsShort(fieldValue1);

        switch (iField2.type) {
        case INTEGER: {
          final int value2 = OVarIntSerializer.readAsInteger(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case LONG:
        case DATETIME: {
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case DATE: {
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2) * MILLISEC_PER_DAY;
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case SHORT: {
          final short value2 = OVarIntSerializer.readAsShort(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case BYTE: {
          final byte value2 = readByte(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case FLOAT: {
          final float value2 = Float.intBitsToFloat(readInteger(fieldValue2));
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case DOUBLE: {
          final double value2 = Double.longBitsToDouble(readLong(fieldValue2));
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case STRING: {
          final String value2 = readString(fieldValue2);
          return Short.toString(value1).compareTo(value2);
        }
        case DECIMAL: {
          final short value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset).shortValue();
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        }
        break;
      }

      case STRING: {
        final String value1 = readString(fieldValue1);

        switch (iField2.type) {
        case INTEGER: {
          final int value2 = OVarIntSerializer.readAsInteger(fieldValue2);
          return value1.compareTo(Integer.toString(value2));
        }
        case LONG:
        case DATETIME: {
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2);
          return value1.compareTo(Long.toString(value2));
        }
        case DATE: {
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2) * MILLISEC_PER_DAY;
          return value1.compareTo(Long.toString(value2));
        }
        case SHORT: {
          final short value2 = OVarIntSerializer.readAsShort(fieldValue2);
          return value1.compareTo(Short.toString(value2));
        }
        case BYTE: {
          final byte value2 = readByte(fieldValue2);
          return value1.compareTo(Byte.toString(value2));
        }
        case FLOAT: {
          final float value2 = Float.intBitsToFloat(readInteger(fieldValue2));
          return value1.compareTo(Float.toString(value2));
        }
        case DOUBLE: {
          final double value2 = Double.longBitsToDouble(readLong(fieldValue2));
          return value1.compareTo(Double.toString(value2));
        }
        case STRING: {
          final String value2 = readString(fieldValue2);

          final OCollate collate = (iField1.collate != null && !ODefaultCollate.NAME.equals(iField1.collate.getName())) ?
              iField1.collate :
              (iField2.collate != null && !ODefaultCollate.NAME.equals(iField2.collate.getName()) ? iField2.collate : null);

          if (collate != null) {
            final String str1 = (String) collate.transform(value1);
            final String str2 = (String) collate.transform(value2);
            return str1.compareTo(str2);
          }

          return value1.compareTo(value2);
        }
        case BOOLEAN: {
          final boolean value2 = readByte(fieldValue2) == 1;
          return value1.compareTo(Boolean.toString(value2));
        }
        case DECIMAL: {
          final BigDecimal value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset);
          return new BigDecimal(value1).compareTo(value2);
        }
        }
        break;
      }

      case DOUBLE: {
        final double value1 = Double.longBitsToDouble(readLong(fieldValue1));

        switch (iField2.type) {
        case INTEGER: {
          final int value2 = OVarIntSerializer.readAsInteger(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case LONG:
        case DATETIME: {
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case SHORT: {
          final short value2 = OVarIntSerializer.readAsShort(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case BYTE: {
          final byte value2 = readByte(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case FLOAT: {
          final float value2 = Float.intBitsToFloat(readInteger(fieldValue2));
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case DOUBLE: {
          final double value2 = Double.longBitsToDouble(readLong(fieldValue2));
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case STRING: {
          final String value2 = readString(fieldValue2);
          return Double.toString(value1).compareTo(value2);
        }
        case DECIMAL: {
          final double value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset).doubleValue();
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        }
        break;
      }

      case FLOAT: {
        final float value1 = Float.intBitsToFloat(readInteger(fieldValue1));

        switch (iField2.type) {
        case INTEGER: {
          final int value2 = OVarIntSerializer.readAsInteger(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case LONG:
        case DATETIME: {
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case SHORT: {
          final short value2 = OVarIntSerializer.readAsShort(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case BYTE: {
          final byte value2 = readByte(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case FLOAT: {
          final float value2 = Float.intBitsToFloat(readInteger(fieldValue2));
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case DOUBLE: {
          final double value2 = Double.longBitsToDouble(readLong(fieldValue2));
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case STRING: {
          final String value2 = readString(fieldValue2);
          return Float.toString(value1).compareTo(value2);
        }
        case DECIMAL: {
          final String value2 = readString(fieldValue2);
          return Float.toString(value1).compareTo(value2);
        }
        }
        break;
      }

      case BYTE: {
        final byte value1 = readByte(fieldValue1);

        switch (iField2.type) {
        case INTEGER: {
          final int value2 = OVarIntSerializer.readAsInteger(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case LONG:
        case DATETIME: {
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case SHORT: {
          final short value2 = OVarIntSerializer.readAsShort(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case BYTE: {
          final byte value2 = readByte(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case FLOAT: {
          final float value2 = Float.intBitsToFloat(readInteger(fieldValue2));
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case DOUBLE: {
          final double value2 = Double.longBitsToDouble(readLong(fieldValue2));
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case STRING: {
          final String value2 = readString(fieldValue2);
          return Byte.toString(value1).compareTo(value2);
        }
        case DECIMAL: {
          final byte value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset).byteValue();
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        }
        break;
      }

      case BOOLEAN: {
        final boolean value1 = readByte(fieldValue1) == 1;

        switch (iField2.type) {
        case BOOLEAN: {
          final boolean value2 = readByte(fieldValue2) == 1;
          return (value1 == value2) ? 0 : value1 ? 1 : -1;
        }
        case STRING: {
          final boolean value2 = Boolean.parseBoolean(readString(fieldValue2));
          return (value1 == value2) ? 0 : value1 ? 1 : -1;
        }
        }
        break;
      }

      case DATETIME: {
        final long value1 = OVarIntSerializer.readAsLong(fieldValue1);

        switch (iField2.type) {
        case INTEGER: {
          final int value2 = OVarIntSerializer.readAsInteger(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case LONG:
        case DATETIME: {
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case DATE: {
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2) * MILLISEC_PER_DAY;
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case SHORT: {
          final short value2 = OVarIntSerializer.readAsShort(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case BYTE: {
          final byte value2 = readByte(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case FLOAT: {
          final float value2 = Float.intBitsToFloat(readInteger(fieldValue2));
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case DOUBLE: {
          final double value2 = Double.longBitsToDouble(readLong(fieldValue2));
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case STRING: {
          final String value2AsString = readString(fieldValue2);

          if (OIOUtils.isLong(value2AsString)) {
            final long value2 = Long.parseLong(value2AsString);
            return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
          }

          final ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.instance().getIfDefined();
          try {
            final SimpleDateFormat dateFormat = db != null ?
                db.getStorage().getConfiguration().getDateTimeFormatInstance() :
                new SimpleDateFormat(OStorageConfiguration.DEFAULT_DATETIME_FORMAT);

            final Date value2AsDate = dateFormat.parse(value2AsString);
            final long value2 = value2AsDate.getTime();
            return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
          } catch (ParseException ignored) {
            try {
              final SimpleDateFormat dateFormat = db != null ?
                  db.getStorage().getConfiguration().getDateFormatInstance() :
                  new SimpleDateFormat(OStorageConfiguration.DEFAULT_DATE_FORMAT);

              final Date value2AsDate = dateFormat.parse(value2AsString);
              final long value2 = value2AsDate.getTime();
              return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
            } catch (ParseException ignore) {
              return new Date(value1).toString().compareTo(value2AsString);
            }
          }
        }
        case DECIMAL: {
          final long value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset).longValue();
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        }
        break;
      }

      case DATE: {
        final long value1 = OVarIntSerializer.readAsLong(fieldValue1) * MILLISEC_PER_DAY;

        switch (iField2.type) {
        case INTEGER: {
          final int value2 = OVarIntSerializer.readAsInteger(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case LONG:
        case DATETIME: {
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case DATE: {
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2) * MILLISEC_PER_DAY;
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case SHORT: {
          final short value2 = OVarIntSerializer.readAsShort(fieldValue2);
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case FLOAT: {
          final float value2 = Float.intBitsToFloat(readInteger(fieldValue2));
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case DOUBLE: {
          final double value2 = Double.longBitsToDouble(readLong(fieldValue2));
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        case STRING: {
          final String value2AsString = readString(fieldValue2);

          if (OIOUtils.isLong(value2AsString)) {
            final long value2 = Long.parseLong(value2AsString);
            return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
          }

          final ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.instance().getIfDefined();
          try {
            final SimpleDateFormat dateFormat = db != null ?
                db.getStorage().getConfiguration().getDateFormatInstance() :
                new SimpleDateFormat(OStorageConfiguration.DEFAULT_DATE_FORMAT);
            final Date value2AsDate = dateFormat.parse(value2AsString);
            long value2 = value2AsDate.getTime();
            value2 = convertDayToTimezone(ODateHelper.getDatabaseTimeZone(), TimeZone.getTimeZone("GMT"), value2);
            return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
          } catch (ParseException ignore) {
            try {
              final SimpleDateFormat dateFormat = db != null ?
                  db.getStorage().getConfiguration().getDateFormatInstance() :
                  new SimpleDateFormat(OStorageConfiguration.DEFAULT_DATETIME_FORMAT);

              final Date value2AsDate = dateFormat.parse(value2AsString);
              long value2 = value2AsDate.getTime();
              value2 = convertDayToTimezone(ODateHelper.getDatabaseTimeZone(), TimeZone.getTimeZone("GMT"), value2);
              return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
            } catch (ParseException ignored) {
              return new Date(value1).toString().compareTo(value2AsString);
            }
          }
        }
        case DECIMAL: {
          final long value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset).longValue();
          return (value1 < value2) ? -1 : ((value1 == value2) ? 0 : 1);
        }
        }
        break;
      }

      case BINARY: {
        switch (iField2.type) {
        case BINARY: {
          final int length1 = OVarIntSerializer.readAsInteger(fieldValue1);
          final int length2 = OVarIntSerializer.readAsInteger(fieldValue2);

          final int max = Math.min(length1, length2);
          for (int i = 0; i < max; ++i) {
            final byte b1 = fieldValue1.bytes[fieldValue1.offset + i];
            final byte b2 = fieldValue2.bytes[fieldValue2.offset + i];

            if (b1 > b2)
              return 1;
            else if (b2 > b1)
              return -1;
          }

          if (length1 > length2)
            return 1;
          else if (length2 > length1)
            return -1;

          // EQUALS
          return 0;
        }
        }
        break;
      }

      case LINK: {
        switch (iField2.type) {
        case LINK: {
          final int clusterId1 = OVarIntSerializer.readAsInteger(fieldValue1);
          final int clusterId2 = OVarIntSerializer.readAsInteger(fieldValue2);
          if (clusterId1 > clusterId2)
            return 1;
          else if (clusterId1 < clusterId2)
            return -1;
          else {
            final long clusterPos1 = OVarIntSerializer.readAsLong(fieldValue1);
            final long clusterPos2 = OVarIntSerializer.readAsLong(fieldValue2);
            if (clusterPos1 > clusterPos2)
              return 1;
            else if (clusterPos1 < clusterPos2)
              return -1;
            return 0;
          }
        }

        case STRING: {
          return readOptimizedLink(fieldValue1, false).compareTo(new ORecordId(readString(fieldValue2)));
        }
        }
        break;
      }

      case DECIMAL: {
        final BigDecimal value1 = ODecimalSerializer.INSTANCE.deserialize(fieldValue1.bytes, fieldValue1.offset);

        switch (iField2.type) {
        case INTEGER: {
          final int value2 = OVarIntSerializer.readAsInteger(fieldValue2);
          return value1.compareTo(new BigDecimal(value2));
        }
        case LONG:
        case DATETIME: {
          final long value2 = OVarIntSerializer.readAsLong(fieldValue2);
          return value1.compareTo(new BigDecimal(value2));
        }
        case SHORT: {
          final short value2 = OVarIntSerializer.readAsShort(fieldValue2);
          return value1.compareTo(new BigDecimal(value2));
        }
        case FLOAT: {
          final float value2 = Float.intBitsToFloat(readInteger(fieldValue2));
          return value1.compareTo(new BigDecimal(value2));
        }
        case DOUBLE: {
          final double value2 = Double.longBitsToDouble(readLong(fieldValue2));
          return value1.compareTo(new BigDecimal(value2));
        }
        case STRING: {
          final String value2 = readString(fieldValue2);
          return value1.toString().compareTo(value2);
        }
        case DECIMAL: {
          final BigDecimal value2 = ODecimalSerializer.INSTANCE.deserialize(fieldValue2.bytes, fieldValue2.offset);
          return value1.compareTo(value2);
        }
        case BYTE: {
          final byte value2 = readByte(fieldValue2);
          return value1.compareTo(new BigDecimal(value2));
        }
        }
        break;
      }
      }
    } finally {
      fieldValue1.offset = offset1;
      fieldValue2.offset = offset2;
    }

    // NO COMPARE SUPPORTED, RETURN NON EQUALS
    return 1;
  }
}


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

import com.orientechnologies.common.exception.OException;
import com.orientechnologies.orient.core.collate.OCollate;
import com.orientechnologies.orient.core.db.record.OMultiValueChangeEvent;
import com.orientechnologies.orient.core.db.record.ORecordElement;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.sql.OCommandExecutorSQLCreateIndex;

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

/**
 * Index that consist of several indexDefinitions like {@link OPropertyIndexDefinition}.
 */

public class OCompositeIndexDefinition extends OAbstractIndexDefinition {
  private static final long serialVersionUID = -885861736290603016L;
  private final List<OIndexDefinition> indexDefinitions;
  private       String                 className;
  private int               multiValueDefinitionIndex = -1;
  private OCompositeCollate collate                   = new OCompositeCollate(this);

  public OCompositeIndexDefinition() {
    indexDefinitions = new ArrayList<OIndexDefinition>(5);
  }

  /**
   * Constructor for new index creation.
   *
   * @param iClassName - name of class which is owner of this index
   */
  public OCompositeIndexDefinition(final String iClassName) {
    super();

    indexDefinitions = new ArrayList<OIndexDefinition>(5);
    className = iClassName;
  }

  /**
   * Constructor for new index creation.
   *
   * @param iClassName - name of class which is owner of this index
   * @param iIndexes   List of indexDefinitions to add in given index.
   */
  public OCompositeIndexDefinition(final String iClassName, final List<? extends OIndexDefinition> iIndexes, int version) {
    super();

    indexDefinitions = new ArrayList<OIndexDefinition>(5);
    for (OIndexDefinition indexDefinition : iIndexes) {
      indexDefinitions.add(indexDefinition);
      collate.addCollate(indexDefinition.getCollate());

      if (indexDefinition instanceof OIndexDefinitionMultiValue)
        if (multiValueDefinitionIndex == -1)
          multiValueDefinitionIndex = indexDefinitions.size() - 1;
        else
          throw new OIndexException("Composite key cannot contain more than one collection item");
    }

    className = iClassName;
  }

  /**
   * {@inheritDoc}
   */
  public String getClassName() {
    return className;
  }

  /**
   * Add new indexDefinition in current composite.
   *
   * @param indexDefinition Index to add.
   */
  public void addIndex(final OIndexDefinition indexDefinition) {
    indexDefinitions.add(indexDefinition);
    if (indexDefinition instanceof OIndexDefinitionMultiValue) {
      if (multiValueDefinitionIndex == -1)
        multiValueDefinitionIndex = indexDefinitions.size() - 1;
      else
        throw new OIndexException("Composite key cannot contain more than one collection item");
    }

    collate.addCollate(indexDefinition.getCollate());
  }

  /**
   * {@inheritDoc}
   */
  public List<String> getFields() {
    final List<String> fields = new LinkedList<String>();
    for (final OIndexDefinition indexDefinition : indexDefinitions) {
      fields.addAll(indexDefinition.getFields());
    }
    return Collections.unmodifiableList(fields);
  }

  /**
   * {@inheritDoc}
   */
  public List<String> getFieldsToIndex() {
    final List<String> fields = new LinkedList<String>();
    for (final OIndexDefinition indexDefinition : indexDefinitions) {
      fields.addAll(indexDefinition.getFieldsToIndex());
    }
    return Collections.unmodifiableList(fields);
  }

  /**
   * {@inheritDoc}
   */
  public Object getDocumentValueToIndex(final ODocument iDocument) {
    final List<OCompositeKey> compositeKeys = new ArrayList<OCompositeKey>(10);
    final OCompositeKey firstKey = new OCompositeKey();
    boolean containsCollection = false;

    compositeKeys.add(firstKey);

    for (final OIndexDefinition indexDefinition : indexDefinitions) {
      final Object result = indexDefinition.getDocumentValueToIndex(iDocument);

      if (result == null && isNullValuesIgnored())
        return null;

      //for empty collections we add null key in index
      if (result instanceof Collection && ((Collection) result).isEmpty() && isNullValuesIgnored())
        return null;

      containsCollection = addKey(firstKey, compositeKeys, containsCollection, result);
    }

    if (!containsCollection)
      return firstKey;

    return compositeKeys;
  }

  public int getMultiValueDefinitionIndex() {
    return multiValueDefinitionIndex;
  }

  public String getMultiValueField() {
    if (multiValueDefinitionIndex >= 0)
      return indexDefinitions.get(multiValueDefinitionIndex).getFields().get(0);

    return null;
  }

  /**
   * {@inheritDoc}
   */
  public Object createValue(final List<?> params) {
    int currentParamIndex = 0;
    final OCompositeKey firstKey = new OCompositeKey();

    final List<OCompositeKey> compositeKeys = new ArrayList<OCompositeKey>(10);
    compositeKeys.add(firstKey);

    boolean containsCollection = false;

    for (final OIndexDefinition indexDefinition : indexDefinitions) {
      if (currentParamIndex + 1 > params.size())
        break;

      final int endIndex;
      if (currentParamIndex + indexDefinition.getParamCount() > params.size())
        endIndex = params.size();
      else
        endIndex = currentParamIndex + indexDefinition.getParamCount();

      final List<?> indexParams = params.subList(currentParamIndex, endIndex);
      currentParamIndex += indexDefinition.getParamCount();

      final Object keyValue = indexDefinition.createValue(indexParams);

      if (keyValue == null && isNullValuesIgnored())
        return null;

      //for empty collections we add null key in index
      if (keyValue instanceof Collection && ((Collection) keyValue).isEmpty() && isNullValuesIgnored())
        return null;

      containsCollection = addKey(firstKey, compositeKeys, containsCollection, keyValue);
    }

    if (!containsCollection)
      return firstKey;

    return compositeKeys;
  }

  public OIndexDefinitionMultiValue getMultiValueDefinition() {
    if (multiValueDefinitionIndex > -1)
      return (OIndexDefinitionMultiValue) indexDefinitions.get(multiValueDefinitionIndex);

    return null;
  }

  public OCompositeKey createSingleValue(final List<?> params) {
    final OCompositeKey compositeKey = new OCompositeKey();
    int currentParamIndex = 0;

    for (final OIndexDefinition indexDefinition : indexDefinitions) {
      if (currentParamIndex + 1 > params.size())
        break;

      final int endIndex;
      if (currentParamIndex + indexDefinition.getParamCount() > params.size())
        endIndex = params.size();
      else
        endIndex = currentParamIndex + indexDefinition.getParamCount();

      final List<?> indexParams = params.subList(currentParamIndex, endIndex);
      currentParamIndex += indexDefinition.getParamCount();

      final Object keyValue;

      if (indexDefinition instanceof OIndexDefinitionMultiValue)
        keyValue = ((OIndexDefinitionMultiValue) indexDefinition).createSingleValue(indexParams.toArray());
      else
        keyValue = indexDefinition.createValue(indexParams);

      if (keyValue == null && isNullValuesIgnored())
        return null;

      compositeKey.addKey(keyValue);
    }

    return compositeKey;
  }

  private static boolean addKey(OCompositeKey firstKey, List<OCompositeKey> compositeKeys, boolean containsCollection,
      Object keyValue) {
    //in case of collection we split single composite key on several composite keys
    //each of those composite keys contain single collection item.
    //we can not contain more than single collection item in index
    if (keyValue instanceof Collection) {
      final Collection<?> collectionKey = (Collection<?>) keyValue;
      final int collectionSize;

      //we insert null if collection is empty
      if (collectionKey.isEmpty())
        collectionSize = 1;
      else
        collectionSize = collectionKey.size();

      //if that is first collection we split single composite key on several keys, each of those
      //composite keys contain single item from collection
      if (!containsCollection)
        //sure we need to expand collection only if collection size more than one, otherwise
        //collection of composite keys already contains original composite key
        for (int i = 1; i < collectionSize; i++) {
          final OCompositeKey compositeKey = new OCompositeKey(firstKey.getKeys());
          compositeKeys.add(compositeKey);
        }
      else
        throw new OIndexException("Composite key cannot contain more than one collection item");

      int compositeIndex = 0;
      if (!collectionKey.isEmpty()) {
        for (final Object keyItem : collectionKey) {
          final OCompositeKey compositeKey = compositeKeys.get(compositeIndex);
          compositeKey.addKey(keyItem);

          compositeIndex++;
        }
      } else {
        firstKey.addKey(null);
      }

      containsCollection = true;
    } else if (containsCollection)
      for (final OCompositeKey compositeKey : compositeKeys)
        compositeKey.addKey(keyValue);
    else
      firstKey.addKey(keyValue);

    return containsCollection;
  }

  /**
   * {@inheritDoc}
   */
  public Object createValue(final Object... params) {
    if (params.length == 1 && params[0] instanceof Collection)
      return params[0];

    return createValue(Arrays.asList(params));
  }

  public void processChangeEvent(OMultiValueChangeEvent<?, ?> changeEvent, Map<OCompositeKey, Integer> keysToAdd,
      Map<OCompositeKey, Integer> keysToRemove, Object... params) {

    final OIndexDefinitionMultiValue indexDefinitionMultiValue = (OIndexDefinitionMultiValue) indexDefinitions
        .get(multiValueDefinitionIndex);

    final CompositeWrapperMap compositeWrapperKeysToAdd = new CompositeWrapperMap(keysToAdd, indexDefinitions, params,
        multiValueDefinitionIndex);

    final CompositeWrapperMap compositeWrapperKeysToRemove = new CompositeWrapperMap(keysToRemove, indexDefinitions, params,
        multiValueDefinitionIndex);

    indexDefinitionMultiValue.processChangeEvent(changeEvent, compositeWrapperKeysToAdd, compositeWrapperKeysToRemove);
  }

  /**
   * {@inheritDoc}
   */
  public int getParamCount() {
    int total = 0;
    for (final OIndexDefinition indexDefinition : indexDefinitions)
      total += indexDefinition.getParamCount();
    return total;
  }

  /**
   * {@inheritDoc}
   */
  public OType[] getTypes() {
    final List<OType> types = new LinkedList<OType>();
    for (final OIndexDefinition indexDefinition : indexDefinitions)
      Collections.addAll(types, indexDefinition.getTypes());

    return types.toArray(new OType[types.size()]);
  }

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

    final OCompositeIndexDefinition that = (OCompositeIndexDefinition) o;

    if (!className.equals(that.className))
      return false;
    if (!indexDefinitions.equals(that.indexDefinitions))
      return false;

    return true;
  }

  @Override
  public int hashCode() {
    int result = indexDefinitions.hashCode();
    result = 31 * result + className.hashCode();
    return result;
  }

  @Override
  public String toString() {
    return "OCompositeIndexDefinition{" + "indexDefinitions=" + indexDefinitions + ", className='" + className + '\'' + '}';
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public ODocument toStream() {
    document.setInternalStatus(ORecordElement.STATUS.UNMARSHALLING);
    try {
      serializeToStream();
    } finally {
      document.setInternalStatus(ORecordElement.STATUS.LOADED);
    }

    return document;
  }

  @Override
  protected void serializeToStream() {
    super.serializeToStream();

    final List<ODocument> inds = new ArrayList<ODocument>(indexDefinitions.size());
    final List<String> indClasses = new ArrayList<String>(indexDefinitions.size());

    document.field("className", className);
    for (final OIndexDefinition indexDefinition : indexDefinitions) {
      final ODocument indexDocument = indexDefinition.toStream();
      inds.add(indexDocument);

      indClasses.add(indexDefinition.getClass().getName());
    }
    document.field("indexDefinitions", inds, OType.EMBEDDEDLIST);
    document.field("indClasses", indClasses, OType.EMBEDDEDLIST);
    document.field("nullValuesIgnored", isNullValuesIgnored());
  }

  /**
   * {@inheritDoc}
   */
  public String toCreateIndexDDL(final String indexName, final String indexType, String engine) {
    final StringBuilder ddl = new StringBuilder("create index ");
    ddl.append(indexName).append(" on ").append(className).append(" ( ");

    final Iterator<String> fieldIterator = getFieldsToIndex().iterator();
    if (fieldIterator.hasNext()) {
      ddl.append(fieldIterator.next());
      while (fieldIterator.hasNext()) {
        ddl.append(", ").append(fieldIterator.next());
      }
    }
    ddl.append(" ) ").append(indexType).append(' ');

    if (engine != null)
      ddl.append(OCommandExecutorSQLCreateIndex.KEYWORD_ENGINE + " " + engine).append(' ');

    if (multiValueDefinitionIndex == -1) {
      boolean first = true;
      for (OType oType : getTypes()) {
        if (first)
          first = false;
        else
          ddl.append(", ");

        ddl.append(oType.name());
      }
    }

    return ddl.toString();
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void fromStream() {
    serializeFromStream();
  }

  @Override
  protected void serializeFromStream() {
    super.serializeFromStream();

    try {
      className = document.field("className");

      final List<ODocument> inds = document.field("indexDefinitions");
      final List<String> indClasses = document.field("indClasses");

      indexDefinitions.clear();

      collate = new OCompositeCollate(this);

      for (int i = 0; i < indClasses.size(); i++) {
        final Class<?> clazz = Class.forName(indClasses.get(i));
        final ODocument indDoc = inds.get(i);

        final OIndexDefinition indexDefinition = (OIndexDefinition) clazz.getDeclaredConstructor().newInstance();
        indexDefinition.fromStream(indDoc);

        indexDefinitions.add(indexDefinition);
        collate.addCollate(indexDefinition.getCollate());

        if (indexDefinition instanceof OIndexDefinitionMultiValue)
          multiValueDefinitionIndex = indexDefinitions.size() - 1;
      }

      setNullValuesIgnored(!Boolean.FALSE.equals(document.<Boolean>field("nullValuesIgnored")));
    } catch (final ClassNotFoundException e) {
      throw OException.wrapException(new OIndexException("Error during composite index deserialization"), e);
    } catch (final NoSuchMethodException e) {
      throw OException.wrapException(new OIndexException("Error during composite index deserialization"), e);
    } catch (final InvocationTargetException e) {
      throw OException.wrapException(new OIndexException("Error during composite index deserialization"), e);
    } catch (final InstantiationException e) {
      throw OException.wrapException(new OIndexException("Error during composite index deserialization"), e);
    } catch (final IllegalAccessException e) {
      throw OException.wrapException(new OIndexException("Error during composite index deserialization"), e);
    }
  }

  @Override
  public OCollate getCollate() {
    return collate;
  }

  @Override
  public void setCollate(OCollate collate) {
    throw new UnsupportedOperationException();
  }

  private static final class CompositeWrapperMap implements Map<Object, Integer> {
    private final Map<OCompositeKey, Integer> underlying;
    private final Object[]                    params;
    private final List<OIndexDefinition>      indexDefinitions;
    private final int                         multiValueIndex;

    private CompositeWrapperMap(Map<OCompositeKey, Integer> underlying, List<OIndexDefinition> indexDefinitions, Object[] params,
        int multiValueIndex) {
      this.underlying = underlying;
      this.params = params;
      this.multiValueIndex = multiValueIndex;
      this.indexDefinitions = indexDefinitions;
    }

    public int size() {
      return underlying.size();
    }

    public boolean isEmpty() {
      return underlying.isEmpty();
    }

    public boolean containsKey(Object key) {
      final OCompositeKey compositeKey = convertToCompositeKey(key);

      return underlying.containsKey(compositeKey);
    }

    public boolean containsValue(Object value) {
      return underlying.containsValue(value);
    }

    public Integer get(Object key) {
      return underlying.get(convertToCompositeKey(key));
    }

    public Integer put(Object key, Integer value) {
      final OCompositeKey compositeKey = convertToCompositeKey(key);
      return underlying.put(compositeKey, value);
    }

    public Integer remove(Object key) {
      return underlying.remove(convertToCompositeKey(key));
    }

    public void putAll(Map<? extends Object, ? extends Integer> m) {
      throw new UnsupportedOperationException("Unsupported because of performance reasons");
    }

    public void clear() {
      underlying.clear();
    }

    public Set<Object> keySet() {
      throw new UnsupportedOperationException("Unsupported because of performance reasons");
    }

    public Collection<Integer> values() {
      return underlying.values();
    }

    public Set<Entry<Object, Integer>> entrySet() {
      throw new UnsupportedOperationException();
    }

    private OCompositeKey convertToCompositeKey(Object key) {
      final OCompositeKey compositeKey = new OCompositeKey();

      int paramsIndex = 0;
      for (int i = 0; i < indexDefinitions.size(); i++) {
        final OIndexDefinition indexDefinition = indexDefinitions.get(i);
        if (i != multiValueIndex) {
          compositeKey.addKey(indexDefinition.createValue(params[paramsIndex]));
          paramsIndex++;
        } else
          compositeKey.addKey(((OIndexDefinitionMultiValue) indexDefinition).createSingleValue(key));
      }
      return compositeKey;
    }
  }

  @Override
  public boolean isAutomatic() {
    return indexDefinitions.get(0).isAutomatic();
  }
}


/*
 *
 *  *  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.exception.OException;
import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest;
import com.orientechnologies.orient.core.command.OCommandRequest;
import com.orientechnologies.orient.core.command.OCommandRequestText;
import com.orientechnologies.orient.core.config.OGlobalConfiguration;
import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal;
import com.orientechnologies.orient.core.exception.OCommandExecutionException;
import com.orientechnologies.orient.core.id.ORecordId;
import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper;
import com.orientechnologies.orient.core.storage.OStorageOperationResult;

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

/**
 * SQL TRUNCATE RECORD command: Truncates a record without loading it. Useful when the record is dirty in any way and cannot be
 * loaded correctly.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 *
 */
public class OCommandExecutorSQLTruncateRecord extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest {
  public static final String KEYWORD_TRUNCATE = "TRUNCATE";
  public static final String KEYWORD_RECORD   = "RECORD";
  private Set<String>        records          = new HashSet<String>();

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

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

      init((OCommandRequestText) iRequest);

      StringBuilder word = new StringBuilder();

      int oldPos = 0;
      int pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true);
      if (pos == -1 || !word.toString().equals(KEYWORD_TRUNCATE))
        throw new OCommandSQLParsingException("Keyword " + KEYWORD_TRUNCATE + " not found. Use " + getSyntax(), parserText, oldPos);

      oldPos = pos;
      pos = nextWord(parserText, parserTextUpperCase, oldPos, word, true);
      if (pos == -1 || !word.toString().equals(KEYWORD_RECORD))
        throw new OCommandSQLParsingException("Keyword " + KEYWORD_RECORD + " not found. Use " + getSyntax(), parserText, oldPos);

      oldPos = pos;
      pos = nextWord(parserText, parserText, oldPos, word, true);
      if (pos == -1)
        throw new OCommandSQLParsingException("Expected one or more records. Use " + getSyntax(), parserText, oldPos);

      if (word.charAt(0) == '[')
        // COLLECTION
        OStringSerializerHelper.getCollection(parserText, oldPos, records);
      else {
        records.add(word.toString());
      }

      if (records.isEmpty())
        throw new OCommandSQLParsingException("Missed record(s). Use " + getSyntax(), parserText, oldPos);
    } finally {
      textRequest.setText(originalQuery);
    }
    return this;
  }

  /**
   * Execute the command.
   */
  public Object execute(final Map<Object, Object> iArgs) {
    if (records.isEmpty())
      throw new OCommandExecutionException("Cannot execute the command because it has not been parsed yet");

    int deleted = 0;

    final ODatabaseDocumentInternal database = getDatabase();
    for (String rec : records) {
      try {
        final ORecordId rid = new ORecordId(rec);
        final OStorageOperationResult<Boolean> result = database.getStorage().deleteRecord(rid, -1, 0, null);
        database.getLocalCache().deleteRecord(rid);

        if (result.getResult())
          deleted++;

      } catch (Exception e) {
        throw OException.wrapException(new OCommandExecutionException("Error on executing command"), e);
      }
    }

    return deleted;
  }

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

  @Override
  public String getSyntax() {
    return "TRUNCATE RECORD <rid>*";
  }

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


/* Generated By:JJTree: Do not edit this line. OProjectionItem.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.orient.core.command.OCommandContext;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.db.record.ridbag.ORidBag;
import com.orientechnologies.orient.core.exception.OCommandExecutionException;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.record.impl.OEdgeToVertexIterable;
import com.orientechnologies.orient.core.record.impl.OEdgeToVertexIterator;
import com.orientechnologies.orient.core.sql.executor.AggregationContext;
import com.orientechnologies.orient.core.sql.executor.OInternalResultSet;
import com.orientechnologies.orient.core.sql.executor.OResult;
import com.orientechnologies.orient.core.sql.executor.OResultInternal;

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

public class OProjectionItem extends SimpleNode {

  protected boolean all = false;

  protected OIdentifier alias;

  protected OExpression expression;

  protected Boolean aggregate;

  protected ONestedProjection nestedProjection;

  public OProjectionItem(OExpression expression, OIdentifier alias, ONestedProjection nestedProjection) {
    super(-1);
    this.expression = expression;
    this.alias = alias;
    this.nestedProjection = nestedProjection;
  }

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

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

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

  public boolean isAll() {
    if (all) {
      return true;
    }
    if (expression != null && "*".equals(expression.toString())) {
      return true;
    }
    return false;
  }

  public void setAll(boolean all) {
    this.all = all;
  }

  public OIdentifier getAlias() {
    return alias;
  }

  public void setAlias(OIdentifier alias) {
    this.alias = alias;
  }

  public OExpression getExpression() {
    return expression;
  }

  public void setExpression(OExpression expression) {
    this.expression = expression;
  }

  public void toString(Map<Object, Object> params, StringBuilder builder) {
    if (all) {
      builder.append("*");
    } else {
      if (expression != null) {
        expression.toString(params, builder);
      }
      if (nestedProjection != null) {
        builder.append(" ");
        nestedProjection.toString(params, builder);
      }
      if (alias != null) {

        builder.append(" AS ");
        alias.toString(params, builder);
      }
    }
  }

  public Object execute(OIdentifiable iCurrentRecord, OCommandContext ctx) {
    Object result;
    if (all) {
      result = iCurrentRecord;
    } else {
      result = expression.execute(iCurrentRecord, ctx);
    }
    if (nestedProjection != null) {
      result = nestedProjection.apply(expression, result, ctx);
    }
    return convert(result);
  }

  public Object convert(Object value) {
    if (value instanceof ORidBag) {
      List result = new ArrayList();
      ((ORidBag) value).forEach(x -> result.add(x));
      return result;
    }
    if (value instanceof OEdgeToVertexIterable) {
      value = ((OEdgeToVertexIterable) value).iterator();
    }
    if (value instanceof OEdgeToVertexIterator) {
      List<ORID> result = new ArrayList<>();
      while (((OEdgeToVertexIterator) value).hasNext()) {
        result.add(((OEdgeToVertexIterator) value).next().getIdentity());
      }
      return result;
    }
    if (value instanceof OInternalResultSet) {
      ((OInternalResultSet) value).reset();
      value = ((OInternalResultSet) value).stream().collect(Collectors.toList());
    }
    return value;
  }

  public Object execute(OResult iCurrentRecord, OCommandContext ctx) {
    Object result;
    if (all) {
      result = iCurrentRecord;
    } else {
      result = expression.execute(iCurrentRecord, ctx);
    }
    if (nestedProjection != null) {
      if (result instanceof ODocument && ((ODocument) result).isEmpty()) {
        ((ODocument) result).load(null);
      }
      result = nestedProjection.apply(expression, result, ctx);
    }
    return convert(result);
  }

  /**
   * returns the final alias for this projection item (the explicit alias, if defined, or the default alias)
   *
   * @return the final alias for this projection item
   */
  public String getProjectionAliasAsString() {
    return getProjectionAlias().getStringValue();
  }

  public OIdentifier getProjectionAlias() {
    if (alias != null) {
      return alias;
    }
    OIdentifier result;
    if (all) {
      result = new OIdentifier("*");
    } else {
      result = expression.getDefaultAlias();
    }
    return result;
  }

  public boolean isExpand() {
    return expression.isExpand();
  }

  public OProjectionItem getExpandContent() {
    OProjectionItem result = new OProjectionItem(-1);
    result.setExpression(expression.getExpandContent());
    return result;
  }

  public boolean isAggregate() {
    if (aggregate != null) {
      return aggregate;
    }
    if (all) {
      aggregate = false;
      return false;
    }
    if (expression.isAggregate()) {
      aggregate = true;
      return true;
    }
    aggregate = false;
    return false;
  }

  /**
   * INTERNAL USE ONLY this has to be invoked ONLY if the item is aggregate!!!
   *
   * @param aggregateSplit
   */
  public OProjectionItem splitForAggregation(AggregateProjectionSplit aggregateSplit, OCommandContext ctx) {
    if (isAggregate()) {
      OProjectionItem result = new OProjectionItem(-1);
      result.alias = getProjectionAlias();
      result.expression = expression.splitForAggregation(aggregateSplit, ctx);
      result.nestedProjection = nestedProjection;
      return result;
    } else {
      return this;
    }
  }

  public AggregationContext getAggregationContext(OCommandContext ctx) {
    if (expression == null) {
      throw new OCommandExecutionException("Cannot aggregate on this projection: " + toString());
    }
    return expression.getAggregationContext(ctx);
  }

  public OProjectionItem copy() {
    OProjectionItem result = new OProjectionItem(-1);
    result.all = all;
    result.alias = alias == null ? null : alias.copy();
    result.expression = expression == null ? null : expression.copy();
    result.nestedProjection = nestedProjection == null ? null : nestedProjection.copy();
    result.aggregate = aggregate;
    return result;
  }

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

    OProjectionItem that = (OProjectionItem) o;

    if (all != that.all)
      return false;
    if (alias != null ? !alias.equals(that.alias) : that.alias != null)
      return false;
    if (expression != null ? !expression.equals(that.expression) : that.expression != null)
      return false;
    if (nestedProjection != null ? !nestedProjection.equals(that.nestedProjection) : that.nestedProjection != null)
      return false;
    if (aggregate != null ? !aggregate.equals(that.aggregate) : that.aggregate != null)
      return false;

    return true;
  }

  @Override
  public int hashCode() {
    int result = (all ? 1 : 0);
    result = 31 * result + (alias != null ? alias.hashCode() : 0);
    result = 31 * result + (expression != null ? expression.hashCode() : 0);
    result = 31 * result + (nestedProjection != null ? nestedProjection.hashCode() : 0);
    result = 31 * result + (aggregate != null ? aggregate.hashCode() : 0);
    return result;
  }

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

  public boolean refersToParent() {
    if (expression != null) {
      return expression.refersToParent();
    }
    return false;
  }

  public OResult serialize() {
    OResultInternal result = new OResultInternal();
    result.setProperty("all", all);
    if (alias != null) {
      result.setProperty("alias", alias.serialize());
    }
    if (expression != null) {
      result.setProperty("expression", expression.serialize());
    }
    result.setProperty("aggregate", aggregate);
    if (nestedProjection != null) {
      result.setProperty("nestedProjection", nestedProjection.serialize());
    }
    return result;
  }

  public void deserialize(OResult fromResult) {
    all = fromResult.getProperty("all");
    if (fromResult.getProperty("alias") != null) {
      alias = OIdentifier.deserialize(fromResult.getProperty("alias"));
    }
    if (fromResult.getProperty("expression") != null) {
      expression = new OExpression(-1);
      expression.deserialize(fromResult.getProperty("expression"));
    }
    aggregate = fromResult.getProperty("aggregate");
    if (fromResult.getProperty("nestedProjection") != null) {
      nestedProjection = new ONestedProjection(-1);
      nestedProjection.deserialize(fromResult.getProperty("nestedProjection"));
    }
  }

  public void setNestedProjection(ONestedProjection nestedProjection) {
    this.nestedProjection = nestedProjection;
  }

  public boolean isCacheable() {
    if (expression != null) {
      return expression.isCacheable();
    }
    return true;
  }
}
/* JavaCC - OriginalChecksum=6d6010734c7434a6f516e2eac308e9ce (do not edit this line) */


/* Generated By:JJTree: Do not edit this line. OJson.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.orient.core.command.OCommandContext;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.record.impl.ODocumentHelper;
import com.orientechnologies.orient.core.sql.executor.OResult;
import com.orientechnologies.orient.core.sql.executor.OResultInternal;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class OJson extends SimpleNode {

  protected List<OJsonItem> items = new ArrayList<OJsonItem>();

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

  public OJson(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) {
    builder.append("{");
    boolean first = true;
    for (OJsonItem item : items) {
      if (!first) {
        builder.append(", ");
      }
      item.toString(params, builder);

      first = false;
    }
    builder.append("}");
  }

  public ODocument toDocument(OIdentifiable source, OCommandContext ctx) {
    String className = getClassNameForDocument(ctx);
    ODocument doc;
    if (className != null) {
      doc = new ODocument(className);
    } else {
      doc = new ODocument();
    }
    for (OJsonItem item : items) {
      String name = item.getLeftValue();
      if (name == null) {
        continue;
      }
      Object value;
      if (item.right.value instanceof OJson) {
        value = ((OJson) item.right.value).toDocument(source, ctx);
      } else {
        value = item.right.execute(source, ctx);
      }
      doc.field(name, value);
    }

    return doc;
  }

  private ODocument toDocument(OResult source, OCommandContext ctx, String className){
    ODocument retDoc = new ODocument(className);
    for (OJsonItem item : items) {
      String name = item.getLeftValue();
      if (name == null || ODocumentHelper.getReservedAttributes().contains(name.toLowerCase(Locale.ENGLISH))) {
        continue;
      }
      Object value = item.right.execute(source, ctx);
      retDoc.field(name, value);
    }
    return retDoc;
  }

  /**
   * choosing return type is based on existence of @class and @type field in JSON
   * @param source
   * @param ctx
   * @return
   */
  public Object toObjectDetermineType(OResult source, OCommandContext ctx){
    String className = getClassNameForDocument(ctx);
    String type = getTypeForDocument(ctx);
    if (className != null || (type != null && "d".equalsIgnoreCase(type))) {
      return toDocument(source, ctx, className);
    }
    else{
      return toMap(source, ctx);
    }
  }

  public Map<String, Object> toMap(OIdentifiable source, OCommandContext ctx) {
    Map<String, Object> doc = new LinkedHashMap<String, Object>();
    for (OJsonItem item : items) {
      String name = item.getLeftValue();
      if (name == null) {
        continue;
      }
      Object value = item.right.execute(source, ctx);
      doc.put(name, value);
    }

    return doc;
  }

  public Map<String, Object> toMap(OResult source, OCommandContext ctx) {
    Map<String, Object> doc = new LinkedHashMap<String, Object>();
    for (OJsonItem item : items) {
      String name = item.getLeftValue();
      if (name == null) {
        continue;
      }
      Object value = item.right.execute(source, ctx);
      doc.put(name, value);
    }

    return doc;
  }

  private String getClassNameForDocument(OCommandContext ctx) {
    for (OJsonItem item : items) {
      String left = item.getLeftValue();
      if (left != null && left.toLowerCase(Locale.ENGLISH).equals("@class")) {
        return "" + item.right.execute((OResult) null, ctx);
      }
    }
    return null;
  }

  private String getTypeForDocument(OCommandContext ctx) {
    for (OJsonItem item : items) {
      String left = item.getLeftValue();
      if (left != null && left.toLowerCase(Locale.ENGLISH).equals("@type")) {
        return "" + item.right.execute((OResult) null, ctx);
      }
    }
    return null;
  }

  public boolean needsAliases(Set<String> aliases) {
    for (OJsonItem item : items) {
      if (item.needsAliases(aliases)) {
        return true;
      }
    }
    return false;
  }

  public boolean isAggregate() {
    for (OJsonItem item : items) {
      if (item.isAggregate()) {
        return true;
      }
    }
    return false;
  }

  public OJson splitForAggregation(AggregateProjectionSplit aggregateSplit, OCommandContext ctx) {
    if (isAggregate()) {
      OJson result = new OJson(-1);
      for (OJsonItem item : items) {
        result.items.add(item.splitForAggregation(aggregateSplit, ctx));
      }
      return result;
    } else {
      return this;
    }
  }

  public OJson copy() {
    OJson result = new OJson(-1);
    result.items = items.stream().map(x -> x.copy()).collect(Collectors.toList());
    return result;
  }

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

    OJson oJson = (OJson) o;

    if (items != null ? !items.equals(oJson.items) : oJson.items != null)
      return false;

    return true;
  }

  @Override
  public int hashCode() {
    return items != null ? items.hashCode() : 0;
  }

  public void extractSubQueries(SubQueryCollector collector) {
    for (OJsonItem item : items) {
      item.extractSubQueries(collector);
    }
  }

  public boolean refersToParent() {
    for (OJsonItem item : items) {
      if (item.refersToParent()) {
        return true;
      }
    }
    return false;
  }

  public OResult serialize() {
    OResultInternal result = new OResultInternal();
    if (items != null) {
      result.setProperty("items", items.stream().map(x -> x.serialize()).collect(Collectors.toList()));
    }
    return result;
  }

  public void deserialize(OResult fromResult) {

    if (fromResult.getProperty("items") != null) {
      List<OResult> ser = fromResult.getProperty("items");
      items = new ArrayList<>();
      for (OResult r : ser) {
        OJsonItem exp = new OJsonItem();
        exp.deserialize(r);
        items.add(exp);
      }
    }
  }

  public boolean isCacheable() {
    return false;//TODO optimize
  }
}
/* JavaCC - OriginalChecksum=3beec9f6db486de944498588b51a505d (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.sql.filter;

import com.orientechnologies.common.io.OIOUtils;
import com.orientechnologies.common.parser.OBaseParser;
import com.orientechnologies.common.util.OPair;
import com.orientechnologies.orient.core.collate.OCollate;
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.metadata.schema.OClass;
import com.orientechnologies.orient.core.metadata.schema.OProperty;
import com.orientechnologies.orient.core.record.ORecord;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.serialization.serializer.record.binary.BytesContainer;
import com.orientechnologies.orient.core.serialization.serializer.record.binary.OBinaryField;
import com.orientechnologies.orient.core.serialization.serializer.record.binary.ODocumentSerializer;
import com.orientechnologies.orient.core.serialization.serializer.record.binary.ORecordSerializerBinary;
import com.orientechnologies.orient.core.sql.method.OSQLMethodRuntime;
import com.orientechnologies.orient.core.sql.method.misc.OSQLMethodField;

import java.util.Set;

/**
 * Represent an object field as value in the query condition.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 */
public class OSQLFilterItemField extends OSQLFilterItemAbstract {

  protected Set<String> preLoadedFields;
  protected String[]    preLoadedFieldsArray;
  protected String      name;
  protected OCollate    collate;
  private boolean       collatePreset = false;
  private String        stringValue;

  /**
   * Represents filter item as chain of fields. Provide interface to work with this chain like with sequence of field names.
   */
  public class FieldChain {
    private FieldChain() {
    }

    public String getItemName(int fieldIndex) {
      if (fieldIndex == 0) {
        return name;
      } else {
        return operationsChain.get(fieldIndex - 1).getValue()[0].toString();
      }
    }

    public int getItemCount() {
      if (operationsChain == null) {
        return 1;
      } else {
        return operationsChain.size() + 1;
      }
    }

    /**
     * Field chain is considered as long chain if it contains more than one item.
     *
     * @return true if this chain is long and false in another case.
     */
    public boolean isLong() {
      return operationsChain != null && operationsChain.size() > 0;
    }

    public boolean belongsTo(OSQLFilterItemField filterItemField) {
      return OSQLFilterItemField.this == filterItemField;
    }
  }

  public OSQLFilterItemField(final String iName, final OClass iClass) {
    this.name = OIOUtils.getStringContent(iName);
    collate = getCollateForField(iClass, name);
    if (iClass != null) {
      collatePreset = true;
    }
  }

  public OSQLFilterItemField(final OBaseParser iQueryToParse, final String iName, final OClass iClass) {
    super(iQueryToParse, iName);
    collate = getCollateForField(iClass, iName);
    if (iClass != null) {
      collatePreset = true;
    }
  }

  public Object getValue(final OIdentifiable iRecord, final Object iCurrentResult, final OCommandContext iContext) {
    if (iRecord == null)
      throw new OCommandExecutionException("expression item '" + name + "' cannot be resolved because current record is NULL");

    if (preLoadedFields != null && preLoadedFields.size() == 1) {
      if ("@rid".equalsIgnoreCase(preLoadedFields.iterator().next()))
        return iRecord.getIdentity();
    }

    final ODocument doc = (ODocument) iRecord.getRecord();

    if (preLoadedFieldsArray == null && preLoadedFields != null && preLoadedFields.size() > 0 && preLoadedFields.size() < 5) {
      // TRANSFORM THE SET IN ARRAY ONLY THE FIRST TIME AND IF FIELDS ARE MORE THAN ONE, OTHERWISE GO WITH THE DEFAULT BEHAVIOR
      preLoadedFieldsArray = new String[preLoadedFields.size()];
      preLoadedFields.toArray(preLoadedFieldsArray);
    }

    // UNMARSHALL THE SINGLE FIELD
    if (preLoadedFieldsArray != null && !doc.deserializeFields(preLoadedFieldsArray))
      return null;

    final Object v = stringValue == null ? doc.rawField(name) : stringValue;

    if (!collatePreset && doc != null) {
      OClass schemaClass = doc.getSchemaClass();
      if (schemaClass != null) {
        collate = getCollateForField(schemaClass, name);
      }
    }

    return transformValue(iRecord, iContext, v);
  }

  public OBinaryField getBinaryField(final OIdentifiable iRecord) {
    if (iRecord == null)
      throw new OCommandExecutionException("expression item '" + name + "' cannot be resolved because current record is NULL");

    if (operationsChain != null && operationsChain.size() > 0)
      // CANNOT USE BINARY FIELDS
      return null;

    final ORecord rec = iRecord.getRecord();
    BytesContainer serialized = new BytesContainer(rec.toStream());
    byte version = serialized.bytes[serialized.offset++];
    ODocumentSerializer serializer = ORecordSerializerBinary.INSTANCE.getSerializer(version);

    //check for embedded objects, they have invalid ID and they are serialized with class name
    if (!serializer.isSerializingClassNameByDefault()){
      return ORecordSerializerBinary.INSTANCE.getSerializer(version).deserializeField(serialized,
          rec instanceof ODocument ? ((ODocument) rec).getSchemaClass() : null, name);
    }
    else{
      return serializer.deserializeFieldWithClassName(serialized,
          rec instanceof ODocument ? ((ODocument) rec).getSchemaClass() : null, name);
    }
  }

  public String getRoot() {
    return name;
  }

  public void setRoot(final OBaseParser iQueryToParse, final String iRoot) {
    if (isStringLiteral(iRoot)) {
      this.stringValue = OIOUtils.getStringContent(iRoot);
    }
    //TODO support all the basic types
    this.name = OIOUtils.getStringContent(iRoot);
  }

  private boolean isStringLiteral(String iRoot) {
    if (iRoot.startsWith("'") && iRoot.endsWith("'")) {
      return true;
    }
    if (iRoot.startsWith("\"") && iRoot.endsWith("\"")) {
      return true;
    }
    return false;
  }

  /**
   * Check whether or not this filter item is chain of fields (e.g. "field1.field2.field3"). Return true if filter item contains
   * only field projections operators, if field item contains any other projection operator the method returns false. When filter
   * item does not contains any chain operator, it is also field chain consist of one field.
   *
   * @return whether or not this filter item can be represented as chain of fields.
   */
  public boolean isFieldChain() {
    if (operationsChain == null) {
      return true;
    }

    for (OPair<OSQLMethodRuntime, Object[]> pair : operationsChain) {
      if (!pair.getKey().getMethod().getName().equals(OSQLMethodField.NAME)) {
        return false;
      }
    }

    return true;
  }

  /**
   * Creates {@code FieldChain} in case when filter item can have such representation.
   *
   * @return {@code FieldChain} representation of this filter item.
   * @throws IllegalStateException if this filter item cannot be represented as {@code FieldChain}.
   */
  public FieldChain getFieldChain() {
    if (!isFieldChain()) {
      throw new IllegalStateException("Filter item field contains not only field operators");
    }

    return new FieldChain();
  }

  public void setPreLoadedFields(final Set<String> iPrefetchedFieldList) {
    this.preLoadedFields = iPrefetchedFieldList;
  }

  public OCollate getCollate() {
    return collate;
  }

  /**
   * get the collate of this expression, based on the fully evaluated field chain starting from the passed object.
   *
   * @param doc the root element (document?) of this field chain
   * @return the collate, null if no collate is defined
   */
  public OCollate getCollate(Object doc) {
    if (collate != null || operationsChain == null || !isFieldChain()) {
      return collate;
    }
    if (!(doc instanceof OIdentifiable)) {
      return null;
    }
    FieldChain chain = getFieldChain();
    ODocument lastDoc = ((OIdentifiable) doc).getRecord();
    for (int i = 0; i < chain.getItemCount() - 1; i++) {
      if (lastDoc == null) {
        return null;
      }
      Object nextDoc = lastDoc.field(chain.getItemName(i));
      if (nextDoc == null || !(nextDoc instanceof OIdentifiable)) {
        return null;
      }
      lastDoc = ((OIdentifiable) nextDoc).getRecord();
    }
    if (lastDoc == null) {
      return null;
    }
    OClass schemaClass = lastDoc.getSchemaClass();
    if (schemaClass == null) {
      return null;
    }
    OProperty property = schemaClass.getProperty(chain.getItemName(chain.getItemCount() - 1));
    if (property == null) {
      return null;
    }
    return property.getCollate();
  }
}