package com.orientechnologies.orient.graph.graphml;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.MappingJsonFactory;
import com.orientechnologies.common.io.OFileUtils;
import com.orientechnologies.orient.core.cache.OLocalRecordCache;
import com.orientechnologies.orient.core.command.OCommandOutputListener;
import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.db.tool.ODatabaseImportException;
import com.tinkerpop.blueprints.Graph;
import com.tinkerpop.blueprints.Vertex;
import com.tinkerpop.blueprints.util.io.graphson.ElementFactory;
import com.tinkerpop.blueprints.util.io.graphson.GraphElementFactory;
import com.tinkerpop.blueprints.util.io.graphson.GraphSONMode;
import com.tinkerpop.blueprints.util.io.graphson.GraphSONTokens;
import com.tinkerpop.blueprints.util.wrappers.batch.BatchGraph;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Set;

/**
 * GraphSONReader reads the data from a TinkerPop JSON stream to a graph.
 *
 * @author Stephen Mallette (http://stephen.genoprime.com)
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 */
public class OGraphSONReader {
  private static final JsonFactory jsonFactory = new MappingJsonFactory();
  private final Graph              graph;
  private OCommandOutputListener   output;
  private long                     inputSize;

  /**
   * @param graph
   *          the graph to populate with the JSON data
   */
  public OGraphSONReader(final Graph graph) {
    this.graph = graph;
  }

  /**
   * Input the JSON stream data into the graph. In practice, usually the provided graph is empty.
   *
   * @param jsonInputStream
   *          an InputStream of JSON data
   * @throws IOException
   *           thrown when the JSON data is not correctly formatted
   */
  public void inputGraph(final InputStream jsonInputStream) throws IOException {
    inputGraph(jsonInputStream, 1000);
  }

  /**
   * Input the JSON stream data into the graph. In practice, usually the provided graph is empty.
   *
   * @param filename
   *          name of a file of JSON data
   * @throws IOException
   *           thrown when the JSON data is not correctly formatted
   */
  public void inputGraph(final String filename) throws IOException {
    inputGraph(filename, 1000);
  }

  public void inputGraph(final InputStream jsonInputStream, int bufferSize) throws IOException {
    inputGraph(jsonInputStream, bufferSize, null, null);
  }

  public void inputGraph(final String filename, int bufferSize) throws IOException {
    inputGraph(filename, bufferSize, null, null);
  }

  /**
   * Input the JSON stream data into the graph. More control over how data is streamed is provided by this method.
   *
   * @param filename
   *          name of a file of JSON data
   * @param bufferSize
   *          the amount of elements to hold in memory before committing a transactions (only valid for TransactionalGraphs)
   * @throws IOException
   *           thrown when the JSON data is not correctly formatted
   */
  public void inputGraph(final String filename, int bufferSize, final Set<String> edgePropertyKeys,
      final Set<String> vertexPropertyKeys) throws IOException {
    final File file = new File(filename);
    if (!file.exists())
      throw new ODatabaseImportException("File '" + filename + "' not found");

    inputSize = file.length();
    final FileInputStream fis = new FileInputStream(filename);
    try {
      inputGraph(fis, bufferSize, edgePropertyKeys, vertexPropertyKeys);
    } finally {
      fis.close();
    }
  }

  /**
   * Input the JSON stream data into the graph. More control over how data is streamed is provided by this method.
   *
   * @param jsonInputStream
   *          an InputStream of JSON data
   * @param bufferSize
   *          the amount of elements to hold in memory before committing a transactions (only valid for TransactionalGraphs)
   * @throws IOException
   *           thrown when the JSON data is not correctly formatted
   */
  public void inputGraph(final InputStream jsonInputStream, int bufferSize, final Set<String> edgePropertyKeys,
      final Set<String> vertexPropertyKeys) throws IOException {

    final JsonParser jp = jsonFactory.createJsonParser(jsonInputStream);

    // if this is a transactional localGraph then we're buffering
    final BatchGraph batchGraph = BatchGraph.wrap(graph, bufferSize);

    final ElementFactory elementFactory = new GraphElementFactory(batchGraph);
    OGraphSONUtility graphson = new OGraphSONUtility(GraphSONMode.NORMAL, elementFactory, vertexPropertyKeys, edgePropertyKeys);

    long importedVertices = 0;
    long importedEdges = 0;

    while (jp.nextToken() != JsonToken.END_OBJECT) {
      final String fieldname = jp.getCurrentName() == null ? "" : jp.getCurrentName();
      if (fieldname.equals(GraphSONTokens.MODE)) {
        jp.nextToken();
        final GraphSONMode mode = GraphSONMode.valueOf(jp.getText());
        graphson = new OGraphSONUtility(mode, elementFactory, vertexPropertyKeys, edgePropertyKeys);
      } else if (fieldname.equals(GraphSONTokens.VERTICES)) {
        jp.nextToken();
        while (jp.nextToken() != JsonToken.END_ARRAY) {
          final JsonNode node = jp.readValueAsTree();
          graphson.vertexFromJson(node);
          importedVertices++;
          printStatus(jp, importedVertices, importedEdges);

          if (importedVertices % 1000 == 0)
            ODatabaseRecordThreadLocal.instance().get().getLocalCache().invalidate();
        }
      } else if (fieldname.equals(GraphSONTokens.EDGES)) {
        jp.nextToken();
        while (jp.nextToken() != JsonToken.END_ARRAY) {
          final JsonNode node = jp.readValueAsTree();
          final Vertex inV = batchGraph.getVertex(OGraphSONUtility.getTypedValueFromJsonNode(node.get(GraphSONTokens._IN_V)));
          final Vertex outV = batchGraph.getVertex(OGraphSONUtility.getTypedValueFromJsonNode(node.get(GraphSONTokens._OUT_V)));
          graphson.edgeFromJson(node, outV, inV);
          importedEdges++;
          printStatus(jp, importedVertices, importedEdges);

          if (importedEdges % 1000 == 0)
            ODatabaseRecordThreadLocal.instance().get().getLocalCache().invalidate();
        }
      }
    }

    jp.close();

    batchGraph.commit();
  }

  public OCommandOutputListener getOutput() {
    return output;
  }

  public OGraphSONReader setOutput(final OCommandOutputListener output) {
    this.output = output;
    return this;
  }

  protected void printStatus(final JsonParser jp, final long importedVertices, final long importedEdges) {
    if (output != null && (importedVertices + importedEdges) % 50000 == 0) {
      final long parsed = jp.getCurrentLocation().getByteOffset();

      if (inputSize > 0)
        output.onMessage(String.format("Imported %d graph elements: %d vertices and %d edges. Parsed %s/%s (uncompressed) (%s%%)",
            importedVertices + importedEdges, importedVertices, importedEdges, OFileUtils.getSizeAsString(parsed),
            "" + OFileUtils.getSizeAsString(inputSize), "" + parsed * 100 / inputSize));
      else
        output.onMessage(String.format("Imported %d graph elements: %d vertices and %d edges. Parsed %s (uncompressed)",
            importedVertices + importedEdges, importedVertices, importedEdges, OFileUtils.getSizeAsString(parsed)));
    }
  }
}


/*
 *
 *  *  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.orient.core.collate.OCollate;
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.metadata.schema.OClass;
import com.orientechnologies.orient.core.metadata.schema.OClassImpl;
import com.orientechnologies.orient.core.metadata.schema.OProperty;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.storage.OStorage;

import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;

/**
 * Contains helper methods for {@link OIndexDefinition} creation.
 *

 * IMPORTANT: This class designed for internal usage only.
 *
 * @author Artem Orobets
 */
public class OIndexDefinitionFactory {
  private static final Pattern FILED_NAME_PATTERN = Pattern.compile("\\s+");

  /**
   * Creates an instance of {@link OIndexDefinition} for automatic index.
   *
   * @param oClass     class which will be indexed
   * @param fieldNames list of properties which will be indexed. Format should be '<property> [by key|value]', use 'by key' or 'by
   *                   value' to describe how to index maps. By default maps indexed by key
   * @param types      types of indexed properties
   * @param collates
   * @param indexKind
   * @param algorithm
   *
   * @return index definition instance
   */
  public static OIndexDefinition createIndexDefinition(final OClass oClass, final List<String> fieldNames, final List<OType> types,
      List<OCollate> collates, String indexKind, String algorithm) {
    checkTypes(oClass, fieldNames, types);

    if (fieldNames.size() == 1)
      return createSingleFieldIndexDefinition(oClass, fieldNames.get(0), types.get(0), collates == null ? null : collates.get(0),
          indexKind, algorithm);
    else
      return createMultipleFieldIndexDefinition(oClass, fieldNames, types, collates, indexKind, algorithm);
  }

  /**
   * Extract field name from '<property> [by key|value]' field format.
   *
   * @param fieldDefinition definition of field
   *
   * @return extracted property name
   */
  public static String extractFieldName(final String fieldDefinition) {
    String[] fieldNameParts = FILED_NAME_PATTERN.split(fieldDefinition);
    if (fieldNameParts.length == 0) {
      throw new IllegalArgumentException(
          "Illegal field name format, should be '<property> [by key|value]' but was '" + fieldDefinition + '\'');
    }
    if (fieldNameParts.length == 3 && "by".equalsIgnoreCase(fieldNameParts[1]))
      return fieldNameParts[0];

    if (fieldNameParts.length == 1)
      return fieldDefinition;

    StringBuilder result = new StringBuilder();
    result.append(fieldNameParts[0]);
    for (int i = 1; i < fieldNameParts.length; i++) {
      result.append(" ");
      result.append(fieldNameParts[i]);
    }
    return result.toString();

  }

  private static OIndexDefinition createMultipleFieldIndexDefinition(final OClass oClass, final List<String> fieldsToIndex,
      final List<OType> types, List<OCollate> collates, String indexKind, String algorithm) {
    final OIndexFactory factory = OIndexes.getFactory(indexKind, algorithm);
    final String className = oClass.getName();
    final OCompositeIndexDefinition compositeIndex = new OCompositeIndexDefinition(className);

    for (int i = 0, fieldsToIndexSize = fieldsToIndex.size(); i < fieldsToIndexSize; i++) {
      OCollate collate = null;
      if (collates != null)
        collate = collates.get(i);

      compositeIndex
          .addIndex(createSingleFieldIndexDefinition(oClass, fieldsToIndex.get(i), types.get(i), collate, indexKind, algorithm));
    }

    return compositeIndex;
  }

  private static void checkTypes(OClass oClass, List<String> fieldNames, List<OType> types) {
    if (fieldNames.size() != types.size())
      throw new IllegalArgumentException(
          "Count of field names doesn't match count of field types. It was " + fieldNames.size() + " fields, but " + types.size()
              + " types.");

    for (int i = 0, fieldNamesSize = fieldNames.size(); i < fieldNamesSize; i++) {
      String fieldName = fieldNames.get(i);
      OType type = types.get(i);

      final OProperty property = oClass.getProperty(fieldName);
      if (property != null && !type.equals(property.getType())) {
        throw new IllegalArgumentException("Property type list not match with real property types");
      }
    }
  }

  private static OIndexDefinition createSingleFieldIndexDefinition(OClass oClass, final String field, final OType type,
      OCollate collate, String indexKind, String algorithm) {

    final String fieldName = OClassImpl.decodeClassName(adjustFieldName(oClass, extractFieldName(field)));
    final OIndexDefinition indexDefinition;

    final OProperty propertyToIndex = oClass.getProperty(fieldName);
    final OType indexType;
    if (type == OType.EMBEDDEDMAP || type == OType.LINKMAP) {
      final OPropertyMapIndexDefinition.INDEX_BY indexBy = extractMapIndexSpecifier(field);

      if (indexBy.equals(OPropertyMapIndexDefinition.INDEX_BY.KEY))
        indexType = OType.STRING;
      else {
        if (type == OType.LINKMAP)
          indexType = OType.LINK;
        else {
          indexType = propertyToIndex.getLinkedType();
          if (indexType == null)
            throw new OIndexException("Linked type was not provided."
                + " You should provide linked type for embedded collections that are going to be indexed.");
        }

      }

      indexDefinition = new OPropertyMapIndexDefinition(oClass.getName(), fieldName, indexType, indexBy);
    } else if (type.equals(OType.EMBEDDEDLIST) || type.equals(OType.EMBEDDEDSET) || type.equals(OType.LINKLIST) || type
        .equals(OType.LINKSET)) {
      if (type.equals(OType.LINKSET))
        indexType = OType.LINK;
      else if (type.equals(OType.LINKLIST)) {
        indexType = OType.LINK;
      } else {
        indexType = propertyToIndex.getLinkedType();
        if (indexType == null)
          throw new OIndexException("Linked type was not provided."
              + " You should provide linked type for embedded collections that are going to be indexed.");
      }

      indexDefinition = new OPropertyListIndexDefinition(oClass.getName(), fieldName, indexType);
    } else if (type.equals(OType.LINKBAG)) {
      indexDefinition = new OPropertyRidBagIndexDefinition(oClass.getName(), fieldName);
    } else
      indexDefinition = new OPropertyIndexDefinition(oClass.getName(), fieldName, type);

    if (collate == null && propertyToIndex != null)
      collate = propertyToIndex.getCollate();

    if (collate != null)
      indexDefinition.setCollate(collate);

    return indexDefinition;
  }

  private static OPropertyMapIndexDefinition.INDEX_BY extractMapIndexSpecifier(final String fieldName) {

    String[] fieldNameParts = FILED_NAME_PATTERN.split(fieldName);
    if (fieldNameParts.length == 1)
      return OPropertyMapIndexDefinition.INDEX_BY.KEY;

    if (fieldNameParts.length == 3) {
      Locale locale = getServerLocale();

      if ("by".equals(fieldNameParts[1].toLowerCase(locale)))
        try {
          return OPropertyMapIndexDefinition.INDEX_BY.valueOf(fieldNameParts[2].toUpperCase(locale));
        } catch (IllegalArgumentException iae) {
          throw new IllegalArgumentException(
              "Illegal field name format, should be '<property> [by key|value]' but was '" + fieldName + '\'', iae);
        }
    }

    throw new IllegalArgumentException(
        "Illegal field name format, should be '<property> [by key|value]' but was '" + fieldName + '\'');
  }

  private static Locale getServerLocale() {
    ODatabaseDocumentInternal db = ODatabaseRecordThreadLocal.instance().get();
    OStorage storage = db.getStorage();
    OStorageConfiguration configuration = storage.getConfiguration();
    return configuration.getLocaleInstance();
  }

  private static String adjustFieldName(final OClass clazz, final String fieldName) {
    final OProperty property = clazz.getProperty(fieldName);

    if (property != null)
      return property.getName();
    else
      return fieldName;
  }
}


/* Generated By:JJTree: Do not edit this line. OBaseExpression.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.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.OElement;
import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper;
import com.orientechnologies.orient.core.sql.executor.AggregationContext;
import com.orientechnologies.orient.core.sql.executor.OResult;
import com.orientechnologies.orient.core.sql.executor.OResultInternal;

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

public class OBaseExpression extends OMathExpression {

  protected ONumber number;

  protected OBaseIdentifier identifier;

  protected OInputParameter inputParam;

  protected String string;

  OModifier modifier;

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

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

  public OBaseExpression(OIdentifier identifier) {
    super(-1);
    this.identifier = new OBaseIdentifier(identifier);
  }

  public OBaseExpression(String string) {
    super(-1);
    this.string = "\"" + OStringSerializerHelper.encode(string) + "\"";
  }

  public OBaseExpression(OIdentifier identifier, OModifier modifier) {
    this(identifier);
    if (modifier != null) {
      this.modifier = modifier;
    }
  }

  public OBaseExpression(ORecordAttribute attr, OModifier modifier) {
    super(-1);
    this.identifier = new OBaseIdentifier(attr);
    if (modifier != null) {
      this.modifier = modifier;
    }
  }

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

  @Override
  public String toString() {
    return super.toString();
  }

  public void toString(Map<Object, Object> params, StringBuilder builder) {
    if (number != null) {
      number.toString(params, builder);
    } else if (identifier != null) {
      identifier.toString(params, builder);
    } else if (string != null) {
      builder.append(string);
    } else if (inputParam != null) {
      inputParam.toString(params, builder);
    }

    if (modifier != null) {
      modifier.toString(params, builder);
    }

  }

  public Object execute(OIdentifiable iCurrentRecord, OCommandContext ctx) {
    Object result = null;
    if (number != null) {
      result = number.getValue();
    } else if (identifier != null) {
      result = identifier.execute(iCurrentRecord, ctx);
    } else if (string != null && string.length() > 1) {
      result = OStringSerializerHelper.decode(string.substring(1, string.length() - 1));
    } else if (inputParam != null) {
      result = inputParam.getValue(ctx.getInputParameters());
    }

    if (modifier != null) {
      result = modifier.execute(iCurrentRecord, result, ctx);
    }

    return result;
  }

  public Object execute(OResult iCurrentRecord, OCommandContext ctx) {
    Object result = null;
    if (number != null) {
      result = number.getValue();
    } else if (identifier != null) {
      result = identifier.execute(iCurrentRecord, ctx);
    } else if (string != null && string.length() > 1) {
      result = OStringSerializerHelper.decode(string.substring(1, string.length() - 1));
    } else if (inputParam != null) {
      result = inputParam.getValue(ctx.getInputParameters());
    }
    if (modifier != null) {
      result = modifier.execute(iCurrentRecord, result, ctx);
    }
    return result;
  }

  @Override
  protected boolean supportsBasicCalculation() {
    return true;
  }

  @Override
  public boolean isFunctionAny() {
    if (this.identifier == null) {
      return false;
    }
    return identifier.isFunctionAny();
  }

  @Override
  public boolean isIndexedFunctionCall() {
    if (this.identifier == null) {
      return false;
    }
    return identifier.isIndexedFunctionCall();
  }

  public long estimateIndexedFunction(OFromClause target, OCommandContext context, OBinaryCompareOperator operator, Object right) {
    if (this.identifier == null) {
      return -1;
    }
    return identifier.estimateIndexedFunction(target, context, operator, right);
  }

  public Iterable<OIdentifiable> executeIndexedFunction(OFromClause target, OCommandContext context,
      OBinaryCompareOperator operator, Object right) {
    if (this.identifier == null) {
      return null;
    }
    return identifier.executeIndexedFunction(target, context, operator, right);
  }

  /**
   * tests if current expression is an indexed funciton AND that function can also be executed without using the index
   *
   * @param target   the query target
   * @param context  the execution context
   * @param operator
   * @param right
   *
   * @return true if current expression is an indexed funciton AND that function can also be executed without using the index, false
   * otherwise
   */
  public boolean canExecuteIndexedFunctionWithoutIndex(OFromClause target, OCommandContext context, OBinaryCompareOperator operator,
      Object right) {
    if (this.identifier == null) {
      return false;
    }
    return identifier.canExecuteIndexedFunctionWithoutIndex(target, context, operator, right);
  }

  /**
   * tests if current expression is an indexed function AND that function can be used on this target
   *
   * @param target   the query target
   * @param context  the execution context
   * @param operator
   * @param right
   *
   * @return true if current expression is an indexed function AND that function can be used on this target, false otherwise
   */
  public boolean allowsIndexedFunctionExecutionOnTarget(OFromClause target, OCommandContext context,
      OBinaryCompareOperator operator, Object right) {
    if (this.identifier == null) {
      return false;
    }
    return identifier.allowsIndexedFunctionExecutionOnTarget(target, context, operator, right);
  }

  /**
   * tests if current expression is an indexed function AND the function has also to be executed after the index search. In some
   * cases, the index search is accurate, so this condition can be excluded from further evaluation. In other cases the result from
   * the index is a superset of the expected result, so the function has to be executed anyway for further filtering
   *
   * @param target  the query target
   * @param context the execution context
   *
   * @return true if current expression is an indexed function AND the function has also to be executed after the index search.
   */
  public boolean executeIndexedFunctionAfterIndexSearch(OFromClause target, OCommandContext context,
      OBinaryCompareOperator operator, Object right) {
    if (this.identifier == null) {
      return false;
    }
    return identifier.executeIndexedFunctionAfterIndexSearch(target, context, operator, right);
  }

  @Override
  public boolean isBaseIdentifier() {
    return identifier != null && modifier == null && identifier.isBaseIdentifier();
  }

  @Override
  public OCollate getCollate(OResult currentRecord, OCommandContext ctx) {
    return identifier != null && modifier == null ? identifier.getCollate(currentRecord, ctx) : null;
  }

  public boolean isEarlyCalculated(OCommandContext ctx) {
    if (number != null || inputParam != null || string != null) {
      return true;
    }
    if (identifier != null && identifier.isEarlyCalculated(ctx)) {
      return true;
    }
    return false;
  }

  @Override
  public boolean isExpand() {
    if (identifier != null) {
      return identifier.isExpand();
    }
    return false;
  }

  @Override
  public OExpression getExpandContent() {
    return this.identifier.getExpandContent();
  }

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

  @Override
  public boolean isAggregate() {
    if (identifier != null && identifier.isAggregate()) {
      return true;
    }
    return false;
  }

  @Override
  public boolean isCount() {
    if (identifier != null && identifier.isCount()) {
      return true;
    }
    return false;
  }

  public SimpleNode splitForAggregation(AggregateProjectionSplit aggregateProj, OCommandContext ctx) {
    if (isAggregate()) {
      SimpleNode splitResult = identifier.splitForAggregation(aggregateProj, ctx);
      if (splitResult instanceof OBaseIdentifier) {
        OBaseExpression result = new OBaseExpression(-1);
        result.identifier = (OBaseIdentifier) splitResult;
        return result;
      }
      return splitResult;
    } else {
      return this;
    }
  }

  public AggregationContext getAggregationContext(OCommandContext ctx) {
    if (identifier != null) {
      return identifier.getAggregationContext(ctx);
    } else {
      throw new OCommandExecutionException("cannot aggregate on " + toString());
    }
  }

  @Override
  public OBaseExpression copy() {
    OBaseExpression result = new OBaseExpression(-1);
    result.number = number == null ? null : number.copy();
    result.identifier = identifier == null ? null : identifier.copy();
    result.inputParam = inputParam == null ? null : inputParam.copy();
    result.string = string;
    result.modifier = modifier == null ? null : modifier.copy();
    return result;
  }

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

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

    OBaseExpression that = (OBaseExpression) o;

    if (number != null ? !number.equals(that.number) : that.number != null)
      return false;
    if (identifier != null ? !identifier.equals(that.identifier) : that.identifier != null)
      return false;
    if (inputParam != null ? !inputParam.equals(that.inputParam) : that.inputParam != null)
      return false;
    if (string != null ? !string.equals(that.string) : that.string != null)
      return false;
    if (modifier != null ? !modifier.equals(that.modifier) : that.modifier != null)
      return false;

    return true;
  }

  @Override
  public int hashCode() {
    int result = number != null ? number.hashCode() : 0;
    result = 31 * result + (identifier != null ? identifier.hashCode() : 0);
    result = 31 * result + (inputParam != null ? inputParam.hashCode() : 0);
    result = 31 * result + (string != null ? string.hashCode() : 0);
    result = 31 * result + (modifier != null ? modifier.hashCode() : 0);
    return result;
  }

  public void setIdentifier(OBaseIdentifier identifier) {
    this.identifier = identifier;
  }

  public OBaseIdentifier getIdentifier() {
    return identifier;
  }

  public OModifier getModifier() {
    return modifier;
  }

  public List<String> getMatchPatternInvolvedAliases() {
    if (this.identifier != null && this.identifier.toString().equals("$matched")) {
      if (modifier != null && modifier.suffix != null && modifier.suffix.identifier != null) {
        return Collections.singletonList(modifier.suffix.identifier.toString());
      }
    }
    return null;
  }

  @Override
  public void applyRemove(OResultInternal result, OCommandContext ctx) {
    if (identifier != null) {
      if (modifier == null) {
        identifier.applyRemove(result, ctx);
      } else {
        Object val = identifier.execute(result, ctx);
        modifier.applyRemove(val, result, ctx);
      }
    }
  }

  public OResult serialize() {
    OResultInternal result = (OResultInternal) super.serialize();

    if (number != null) {
      result.setProperty("number", number.serialize());
    }
    if (identifier != null) {
      result.setProperty("identifier", identifier.serialize());
    }
    if (inputParam != null) {
      result.setProperty("inputParam", inputParam.serialize());
    }
    if (string != null) {
      result.setProperty("string", string);
    }
    if (modifier != null) {
      result.setProperty("modifier", modifier.serialize());
    }
    return result;
  }

  public void deserialize(OResult fromResult) {
    super.deserialize(fromResult);

    if (fromResult.getProperty("number") != null) {
      number = new ONumber(-1);
      number.deserialize(fromResult.getProperty("number"));
    }
    if (fromResult.getProperty("identifier") != null) {
      identifier = new OBaseIdentifier(-1);
      identifier.deserialize(fromResult.getProperty("identifier"));
    }
    if (fromResult.getProperty("inputParam") != null) {
      inputParam = OInputParameter.deserializeFromOResult(fromResult.getProperty("inputParam"));
    }

    if (fromResult.getProperty("string") != null) {
      string = fromResult.getProperty("string");
    }
    if (fromResult.getProperty("modifier") != null) {
      modifier = new OModifier(-1);
      modifier.deserialize(fromResult.getProperty("modifier"));
    }
  }

  @Override
  public boolean isDefinedFor(OResult currentRecord) {
    if (this.identifier != null) {
      if (modifier == null) {
        return identifier.isDefinedFor(currentRecord);
      }

    }
    return true;

  }

  @Override
  public boolean isDefinedFor(OElement currentRecord) {
    if (this.identifier != null) {
      if (modifier == null) {
        return identifier.isDefinedFor(currentRecord);
      }

    }
    return true;
  }

  public void extractSubQueries(OIdentifier letAlias, SubQueryCollector collector) {
    if (this.identifier != null) {
      this.identifier.extractSubQueries(letAlias, collector);
    }
  }

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

  public boolean isCacheable() {
    if (modifier != null && !modifier.isCacheable()) {
      return false;
    }
    if (identifier != null) {
      return identifier.isCacheable();
    }

    return true;
  }

  public void setInputParam(OInputParameter inputParam) {
    this.inputParam = inputParam;
  }

  public boolean isIndexChain(OCommandContext ctx, OClass clazz) {
    if (modifier == null) {
      return false;
    }
    if (identifier.isIndexChain(ctx, clazz)) {
      OProperty prop = clazz.getProperty(identifier.getSuffix().identifier.getStringValue());
      OClass linkedClass = prop.getLinkedClass();
      if (linkedClass != null) {
        return modifier.isIndexChain(ctx, linkedClass);
      }
    }
    return false;
  }

}

/* JavaCC - OriginalChecksum=71b3e2d1b65c923dc7cfe11f9f449d2b (do not edit this line) */


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

import com.orientechnologies.common.collection.OMultiCollectionIterator;
import com.orientechnologies.common.util.OSizeable;
import com.orientechnologies.orient.core.command.OCommandContext;
import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.index.*;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.sql.executor.OResult;
import com.orientechnologies.orient.core.sql.executor.OResultInternal;

import java.util.*;
import java.util.stream.Collectors;

public class OWhereClause extends SimpleNode {
  protected OBooleanExpression baseExpression;

  protected List<OAndBlock> flattened;

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

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

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

  public boolean matchesFilters(OIdentifiable currentRecord, OCommandContext ctx) {
    if (baseExpression == null) {
      return true;
    }
    return baseExpression.evaluate(currentRecord, ctx);
  }

  public boolean matchesFilters(OResult currentRecord, OCommandContext ctx) {
    if (baseExpression == null) {
      return true;
    }
    return baseExpression.evaluate(currentRecord, ctx);
  }

  public void toString(Map<Object, Object> params, StringBuilder builder) {
    if (baseExpression == null) {
      return;
    }
    baseExpression.toString(params, builder);
  }

  /**
   * estimates how many items of this class will be returned applying this filter
   *
   * @param oClass
   *
   * @return an estimation of the number of records of this class returned applying this filter, 0 if and only if sure that no
   * records are returned
   */
  public long estimate(OClass oClass, long threshold, OCommandContext ctx) {
    long count = oClass.count();
    if (count > 1) {
      count = count / 2;
    }
    if (count < threshold) {
      return count;
    }

    long indexesCount = 0l;
    List<OAndBlock> flattenedConditions = flatten();
    Set<OIndex<?>> indexes = oClass.getIndexes();
    for (OAndBlock condition : flattenedConditions) {

      List<OBinaryCondition> indexedFunctConditions = condition
          .getIndexedFunctionConditions(oClass, (ODatabaseDocumentInternal) ctx.getDatabase());

      long conditionEstimation = Long.MAX_VALUE;

      if (indexedFunctConditions != null) {
        for (OBinaryCondition cond : indexedFunctConditions) {
          OFromClause from = new OFromClause(-1);
          OFromItem item = new OFromItem(-1);
          from.item = item;
          from.item.setIdentifier(new OIdentifier(oClass.getName()));
          long newCount = cond.estimateIndexed(from, ctx);
          if (newCount < conditionEstimation) {
            conditionEstimation = newCount;
          }
        }
      } else {
        Map<String, Object> conditions = getEqualityOperations(condition, ctx);

        for (OIndex index : indexes) {
          if (index.getType().equals(OClass.INDEX_TYPE.FULLTEXT.name()) || index.getType()
              .equals(OClass.INDEX_TYPE.FULLTEXT_HASH_INDEX.name())) {
            continue;
          }
          List<String> indexedFields = index.getDefinition().getFields();
          int nMatchingKeys = 0;
          for (String indexedField : indexedFields) {
            if (conditions.containsKey(indexedField)) {
              nMatchingKeys++;
            } else {
              break;
            }
          }
          if (nMatchingKeys > 0) {
            long newCount = estimateFromIndex(index, conditions, nMatchingKeys);
            if (newCount < conditionEstimation) {
              conditionEstimation = newCount;
            }
          }
        }
      }
      if (conditionEstimation > count) {
        return count;
      }
      indexesCount += conditionEstimation;
    }
    return Math.min(indexesCount, count);
  }

  private long estimateFromIndex(OIndex index, Map<String, Object> conditions, int nMatchingKeys) {
    if (nMatchingKeys < 1) {
      throw new IllegalArgumentException("Cannot estimate from an index with zero keys");
    }
    OIndexDefinition definition = index.getDefinition();
    List<String> definitionFields = definition.getFields();
    Object key = null;
    if (definition instanceof OPropertyIndexDefinition) {
      key = convert(conditions.get(definitionFields.get(0)), definition.getTypes()[0]);
    } else if (definition instanceof OCompositeIndexDefinition) {
      key = new OCompositeKey();
      for (int i = 0; i < nMatchingKeys; i++) {
        Object keyValue = convert(conditions.get(definitionFields.get(i)), definition.getTypes()[i]);
        ((OCompositeKey) key).addKey(keyValue);
      }
    }
    if (key != null) {
      Object result = null;
      if (conditions.size() == definitionFields.size()) {
        result = index.get(key);
      } else if (index.supportsOrderedIterations()) {
        result = index.iterateEntriesBetween(key, true, key, true, true);
      }
      if (result instanceof OIdentifiable) {
        return 1;
      }
      if (result instanceof Collection) {
        return ((Collection) result).size();
      }
      if (result instanceof OSizeable) {
        return ((OSizeable) result).size();
      }
      if (result instanceof Iterable) {
        result = ((Iterable) result).iterator();
      }
      if (result instanceof Iterator) {
        int i = 0;
        while (((Iterator) result).hasNext()) {
          ((Iterator) result).next();
          i++;
        }
        return i;
      }
    }
    return Long.MAX_VALUE;
  }

  public Iterable fetchFromIndexes(OClass oClass, OCommandContext ctx) {

    List<OAndBlock> flattenedConditions = flatten();
    if (flattenedConditions == null || flattenedConditions.size() == 0) {
      return null;
    }
    Set<OIndex<?>> indexes = oClass.getIndexes();
    List<OIndex> bestIndexes = new ArrayList<OIndex>();
    List<Map<String, Object>> indexConditions = new ArrayList<Map<String, Object>>();
    for (OAndBlock condition : flattenedConditions) {
      Map<String, Object> conditions = getEqualityOperations(condition, ctx);
      long conditionEstimation = Long.MAX_VALUE;
      OIndex bestIndex = null;
      Map<String, Object> bestCondition = null;

      for (OIndex index : indexes) {
        List<String> indexedFields = index.getDefinition().getFields();
        int nMatchingKeys = 0;
        for (String indexedField : indexedFields) {
          if (conditions.containsKey(indexedField)) {
            nMatchingKeys++;
          } else {
            break;
          }
        }
        if (nMatchingKeys > 0) {
          long newCount = estimateFromIndex(index, conditions, nMatchingKeys);
          if (newCount >= 0 && newCount <= conditionEstimation) {
            conditionEstimation = newCount;
            bestIndex = index;
            bestCondition = conditions;
          }
        }
      }
      if (bestIndex == null) {
        return null;
      }
      bestIndexes.add(bestIndex);
      indexConditions.add(bestCondition);
    }
    OMultiCollectionIterator result = new OMultiCollectionIterator();

    for (int i = 0; i < bestIndexes.size(); i++) {
      OIndex index = bestIndexes.get(i);
      Map<String, Object> condition = indexConditions.get(i);
      result.add(fetchFromIndex(index, indexConditions.get(i)));
    }
    return result;
  }

  private Iterable fetchFromIndex(OIndex index, Map<String, Object> conditions) {
    OIndexDefinition definition = index.getDefinition();
    List<String> definitionFields = definition.getFields();
    Object key = null;
    if (definition instanceof OPropertyIndexDefinition) {
      key = convert(conditions.get(definitionFields.get(0)), definition.getTypes()[0]);
    } else if (definition instanceof OCompositeIndexDefinition) {
      key = new OCompositeKey();
      for (int i = 0; i < definitionFields.size(); i++) {
        String keyName = definitionFields.get(i);
        if (!conditions.containsKey(keyName)) {
          break;
        }
        Object keyValue = convert(conditions.get(keyName), definition.getTypes()[i]);
        ((OCompositeKey) key).addKey(conditions.get(keyName));
      }
    }
    if (key != null) {
      final Object result = index.get(key);
      if (result == null) {
        return Collections.EMPTY_LIST;
      }
      if (result instanceof Iterable) {
        return (Iterable) result;
      }
      if (result instanceof Iterator) {
        return new Iterable() {
          @Override
          public Iterator iterator() {
            return (Iterator) result;
          }
        };
      }
      return Collections.singleton(result);
    }
    return null;
  }

  private Object convert(Object o, OType oType) {
    return OType.convert(o, oType.getDefaultJavaType());
  }

  private Map<String, Object> getEqualityOperations(OAndBlock condition, OCommandContext ctx) {
    Map<String, Object> result = new HashMap<String, Object>();
    for (OBooleanExpression expression : condition.subBlocks) {
      if (expression instanceof OBinaryCondition) {
        OBinaryCondition b = (OBinaryCondition) expression;
        if (b.operator instanceof OEqualsCompareOperator) {
          if (b.left.isBaseIdentifier() && b.right.isEarlyCalculated(ctx)) {
            result.put(b.left.toString(), b.right.execute((OResult) null, ctx));
          }
        }
      }
    }
    return result;
  }

  public List<OAndBlock> flatten() {
    if (this.baseExpression == null) {
      return Collections.EMPTY_LIST;
    }
    if (flattened == null) {
      flattened = this.baseExpression.flatten();
    }
    // TODO remove false conditions (contraddictions)
    return flattened;

  }

  public List<OBinaryCondition> getIndexedFunctionConditions(OClass iSchemaClass, ODatabaseDocumentInternal database) {
    if (baseExpression == null) {
      return null;
    }
    return this.baseExpression.getIndexedFunctionConditions(iSchemaClass, database);
  }

  public boolean needsAliases(Set<String> aliases) {
    return this.baseExpression.needsAliases(aliases);
  }

  public void setBaseExpression(OBooleanExpression baseExpression) {
    this.baseExpression = baseExpression;
  }

  public OWhereClause copy() {
    OWhereClause result = new OWhereClause(-1);
    result.baseExpression = baseExpression.copy();
    result.flattened = flattened == null ? null : flattened.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;

    OWhereClause that = (OWhereClause) o;

    if (baseExpression != null ? !baseExpression.equals(that.baseExpression) : that.baseExpression != null)
      return false;
    if (flattened != null ? !flattened.equals(that.flattened) : that.flattened != null)
      return false;

    return true;
  }

  @Override
  public int hashCode() {
    int result = baseExpression != null ? baseExpression.hashCode() : 0;
    result = 31 * result + (flattened != null ? flattened.hashCode() : 0);
    return result;
  }

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

  public boolean refersToParent() {
    return baseExpression != null && baseExpression.refersToParent();
  }

  public OBooleanExpression getBaseExpression() {
    return baseExpression;
  }

  public List<OAndBlock> getFlattened() {
    return flattened;
  }

  public void setFlattened(List<OAndBlock> flattened) {
    this.flattened = flattened;
  }

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

  public void deserialize(OResult fromResult) {
    if (fromResult.getProperty("baseExpression") != null) {
      baseExpression = OBooleanExpression.deserializeFromOResult(fromResult.getProperty("baseExpression"));
    }
    if (fromResult.getProperty("flattened") != null) {
      List<OResult> ser = fromResult.getProperty("flattened");
      flattened = new ArrayList<>();
      for (OResult r : ser) {
        OAndBlock block = new OAndBlock(-1);
        block.deserialize(r);
        flattened.add(block);
      }
    }
  }

  public boolean isCacheable() {
    return baseExpression.isCacheable();
  }
}
/* JavaCC - OriginalChecksum=e8015d01ce1ab2bc337062e9e3f2603e (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.common.reflection;

import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import com.orientechnologies.common.log.OLogManager;

/**
 * Helper class to browse .class files. See also: http://forums.sun.com/thread.jspa?threadID=341935&start=15&tstart=0
 *
 * @author Antony Stubbs
 */
public class OReflectionHelper {
  private static final String CLASS_EXTENSION = ".class";

  public static List<Class<?>> getClassesFor(final Collection<String> classNames, final ClassLoader classLoader)
      throws ClassNotFoundException {
    List<Class<?>> classes = new ArrayList<Class<?>>(classNames.size());
    for (String className : classNames) {
      classes.add(Class.forName(className, true, classLoader));
    }
    return classes;
  }

  public static List<Class<?>> getClassesFor(final String iPackageName, final ClassLoader iClassLoader)
      throws ClassNotFoundException {
    // This will hold a list of directories matching the pckgname.
    // There may be more than one if a package is split over multiple jars/paths
    final List<Class<?>> classes = new ArrayList<Class<?>>();
    final ArrayList<File> directories = new ArrayList<File>();
    try {
      // Ask for all resources for the path
      final String packageUrl = iPackageName.replace('.', '/');
      Enumeration<URL> resources = iClassLoader.getResources(packageUrl);
      if (!resources.hasMoreElements()) {
        resources = iClassLoader.getResources(packageUrl + CLASS_EXTENSION);
        if (resources.hasMoreElements()) {
          throw new IllegalArgumentException(iPackageName + " does not appear to be a valid package but a class");
        }
      } else {
        while (resources.hasMoreElements()) {
          final URL res = resources.nextElement();
          if (res.getProtocol().equalsIgnoreCase("jar")) {
            final JarURLConnection conn = (JarURLConnection) res.openConnection();
            final JarFile jar = conn.getJarFile();
            for (JarEntry e : Collections.list(jar.entries())) {

              if (e.getName().startsWith(iPackageName.replace('.', '/')) && e.getName().endsWith(CLASS_EXTENSION)
                  && !e.getName().contains("$")) {
                final String className = e.getName().replace("/", ".").substring(0, e.getName().length() - 6);
                classes.add(Class.forName(className, true, iClassLoader));
              }
            }
          } else
            directories.add(new File(URLDecoder.decode(res.getPath(), "UTF-8")));
        }
      }
    } catch (NullPointerException x) {
      throw new ClassNotFoundException(iPackageName + " does not appear to be " + "a valid package (Null pointer exception)", x);
    } catch (UnsupportedEncodingException encex) {
      throw new ClassNotFoundException(iPackageName + " does not appear to be " + "a valid package (Unsupported encoding)", encex);
    } catch (IOException ioex) {
      throw new ClassNotFoundException("IOException was thrown when trying " + "to get all resources for " + iPackageName, ioex);
    }

    // For every directory identified capture all the .class files
    for (File directory : directories) {
      if (directory.exists()) {
        // Get the list of the files contained in the package
        File[] files = directory.listFiles();
        for (File file : files) {
          if (file.isDirectory()) {
            classes.addAll(findClasses(file, iPackageName, iClassLoader));
          } else {
            String className;
            if (file.getName().endsWith(CLASS_EXTENSION)) {
              className = file.getName().substring(0, file.getName().length() - CLASS_EXTENSION.length());
              classes.add(Class.forName(iPackageName + '.' + className, true, iClassLoader));
            }
          }
        }
      } else {
        throw new ClassNotFoundException(iPackageName + " (" + directory.getPath() + ") does not appear to be a valid package");
      }
    }
    return classes;
  }

  /**
   * Recursive method used to find all classes in a given directory and subdirs.
   *
   * @param iDirectory
   *          The base directory
   * @param iPackageName
   *          The package name for classes found inside the base directory
   * @return The classes
   * @throws ClassNotFoundException
   */
  private static List<Class<?>> findClasses(final File iDirectory, String iPackageName, ClassLoader iClassLoader)
      throws ClassNotFoundException {
    final List<Class<?>> classes = new ArrayList<Class<?>>();
    if (!iDirectory.exists())
      return classes;

    iPackageName += "." + iDirectory.getName();

    String className;
    final File[] files = iDirectory.listFiles();
    if (files != null)
      for (File file : files) {
        if (file.isDirectory()) {
          if (file.getName().contains("."))
            continue;
          classes.addAll(findClasses(file, iPackageName, iClassLoader));
        } else if (file.getName().endsWith(CLASS_EXTENSION)) {
          className = file.getName().substring(0, file.getName().length() - CLASS_EXTENSION.length());
          classes.add(Class.forName(iPackageName + '.' + className, true, iClassLoader));
        }
      }
    return classes;
  }

  /**
   * Filters discovered classes to see if they implement a given interface.
   *
   * @param thePackage
   * @param theInterface
   * @param iClassLoader
   * @return The list of classes that implements the requested interface
   */
  public static List<Class<?>> getClassessOfInterface(String thePackage, Class<?> theInterface, final ClassLoader iClassLoader) {
    List<Class<?>> classList = new ArrayList<Class<?>>();
    try {
      for (Class<?> discovered : getClassesFor(thePackage, iClassLoader)) {
        if (Arrays.asList(discovered.getInterfaces()).contains(theInterface)) {
          classList.add(discovered);
        }
      }
    } catch (ClassNotFoundException ex) {
      OLogManager.instance().error(null, "Error finding classes", ex);
    }

    return classList;
  }

  /**
   * Returns the declared generic types of a class.
   *
   * @param iClass
   *          Class to examine
   * @return The array of Type if any, otherwise null
   */
  public static Type[] getGenericTypes(final Class<?> iClass) {
    final Type genericType = iClass.getGenericInterfaces()[0];
    if (genericType != null && genericType instanceof ParameterizedType) {
      final ParameterizedType pt = (ParameterizedType) genericType;
      if (pt.getActualTypeArguments() != null && pt.getActualTypeArguments().length > 1)
        return pt.getActualTypeArguments();
    }
    return null;
  }

  /**
   * Returns the generic class of multi-value objects.
   *
   * @param p
   *          Field to examine
   * @return The Class<?> of generic type if any, otherwise null
   */
  public static Class<?> getGenericMultivalueType(final Field p) {
    if (p.getType() instanceof Class<?>) {
      final Type genericType = p.getGenericType();
      if (genericType != null && genericType instanceof ParameterizedType) {
        final ParameterizedType pt = (ParameterizedType) genericType;
        if (pt.getActualTypeArguments() != null && pt.getActualTypeArguments().length > 0) {
          if (((Class<?>) pt.getRawType()).isAssignableFrom(Map.class)) {
            if (pt.getActualTypeArguments()[1] instanceof Class<?>) {
              return (Class<?>) pt.getActualTypeArguments()[1];
            } else if (pt.getActualTypeArguments()[1] instanceof ParameterizedType)
              return (Class<?>) ((ParameterizedType) pt.getActualTypeArguments()[1]).getRawType();
          } else if (pt.getActualTypeArguments()[0] instanceof Class<?>) {
            return (Class<?>) pt.getActualTypeArguments()[0];
          } else if (pt.getActualTypeArguments()[0] instanceof ParameterizedType)
            return (Class<?>) ((ParameterizedType) pt.getActualTypeArguments()[0]).getRawType();
        }
      } else if (p.getType().isArray())
        return p.getType().getComponentType();
    }
    return null;
  }

  /**
   * Checks if a class is a Java type: Map, Collection,arrays, Number (extensions and primitives), String, Boolean..
   *
   * @param clazz
   *          Class<?> to examine
   * @return true if clazz is Java type, false otherwise
   */
  public static boolean isJavaType(Class<?> clazz) {
    if (clazz.isPrimitive())
      return true;
    else if (clazz.getName().startsWith("java.lang"))
      return true;
    else if (clazz.getName().startsWith("java.util"))
      return true;
    else if (clazz.isArray())
      return true;
    return false;
  }
}


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

import com.orientechnologies.common.collection.OMultiValue;
import com.orientechnologies.common.io.OIOUtils;
import com.orientechnologies.common.parser.OBaseParser;
import com.orientechnologies.orient.core.command.OCommandContext;
import com.orientechnologies.orient.core.command.OCommandExecutorNotFoundException;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.exception.OCommandExecutionException;
import com.orientechnologies.orient.core.record.ORecord;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper;
import com.orientechnologies.orient.core.sql.OCommandSQL;
import com.orientechnologies.orient.core.sql.OCommandSQLParsingException;
import com.orientechnologies.orient.core.sql.OSQLEngine;
import com.orientechnologies.orient.core.sql.OSQLHelper;
import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemAbstract;
import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemField;
import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemVariable;
import com.orientechnologies.orient.core.sql.filter.OSQLPredicate;
import com.orientechnologies.orient.core.sql.functions.OSQLFunctionRuntime;

import java.util.List;

/**
 * Wraps function managing the binding of parameters.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 *
 */
public class OSQLMethodRuntime extends OSQLFilterItemAbstract implements Comparable<OSQLMethodRuntime> {

  public OSQLMethod method;
  public Object[]   configuredParameters;
  public Object[]   runtimeParameters;

  public OSQLMethodRuntime(final OBaseParser iQueryToParse, final String iText) {
    super(iQueryToParse, iText);
  }

  public OSQLMethodRuntime(final OSQLMethod iFunction) {
    method = iFunction;
  }

  /**
   * Execute a method.
   *
   * @param iCurrentRecord
   *          Current record
   * @param iCurrentResult
   *          TODO
   * @param iContext
   * @return
   */
  public Object execute(final Object iThis, final OIdentifiable iCurrentRecord, final Object iCurrentResult,
      final OCommandContext iContext) {
    if (iThis == null)
      return null;

    if (configuredParameters != null) {
      // RESOLVE VALUES USING THE CURRENT RECORD
      for (int i = 0; i < configuredParameters.length; ++i) {
        runtimeParameters[i] = configuredParameters[i];

        if (method.evaluateParameters()) {
          if (configuredParameters[i] instanceof OSQLFilterItemField) {
            runtimeParameters[i] = ((OSQLFilterItemField) configuredParameters[i]).getValue(iCurrentRecord, iCurrentResult,
                iContext);
            if (runtimeParameters[i] == null && iCurrentResult instanceof OIdentifiable)
              // LOOK INTO THE CURRENT RESULT
              runtimeParameters[i] = ((OSQLFilterItemField) configuredParameters[i]).getValue((OIdentifiable) iCurrentResult,
                  iCurrentResult, iContext);
          } else if (configuredParameters[i] instanceof OSQLMethodRuntime)
            runtimeParameters[i] = ((OSQLMethodRuntime) configuredParameters[i]).execute(iThis, iCurrentRecord, iCurrentResult,
                iContext);
          else if (configuredParameters[i] instanceof OSQLFunctionRuntime)
            runtimeParameters[i] = ((OSQLFunctionRuntime) configuredParameters[i]).execute(iCurrentRecord, iCurrentRecord, iCurrentResult,
                iContext);
          else if (configuredParameters[i] instanceof OSQLFilterItemVariable) {
            runtimeParameters[i] = ((OSQLFilterItemVariable) configuredParameters[i]).getValue(iCurrentRecord, iCurrentResult,
                iContext);
            if (runtimeParameters[i] == null && iCurrentResult instanceof OIdentifiable)
              // LOOK INTO THE CURRENT RESULT
              runtimeParameters[i] = ((OSQLFilterItemVariable) configuredParameters[i]).getValue((OIdentifiable) iCurrentResult,
                  iCurrentResult, iContext);
          } else if (configuredParameters[i] instanceof OCommandSQL) {
            try {
              runtimeParameters[i] = ((OCommandSQL) configuredParameters[i]).setContext(iContext).execute();
            } catch (OCommandExecutorNotFoundException ignore) {
              // TRY WITH SIMPLE CONDITION
              final String text = ((OCommandSQL) configuredParameters[i]).getText();
              final OSQLPredicate pred = new OSQLPredicate(text);
              runtimeParameters[i] = pred.evaluate(iCurrentRecord instanceof ORecord ? (ORecord) iCurrentRecord : null,
                  (ODocument) iCurrentResult, iContext);
              // REPLACE ORIGINAL PARAM
              configuredParameters[i] = pred;

            }
          } else if (configuredParameters[i] instanceof OSQLPredicate)
            runtimeParameters[i] = ((OSQLPredicate) configuredParameters[i]).evaluate(iCurrentRecord.getRecord(),
                (iCurrentRecord instanceof ODocument ? (ODocument) iCurrentResult : null), iContext);
          else if (configuredParameters[i] instanceof String) {
            if (configuredParameters[i].toString().startsWith("\"") || configuredParameters[i].toString().startsWith("'"))
              runtimeParameters[i] = OIOUtils.getStringContent(configuredParameters[i]);
          }
        }
      }

      if (method.getMaxParams() == -1 || method.getMaxParams() > 0) {
        if (runtimeParameters.length < method.getMinParams()
            || (method.getMaxParams() > -1 && runtimeParameters.length > method.getMaxParams()))
          throw new OCommandExecutionException("Syntax error: function '"
              + method.getName()
              + "' needs "
              + (method.getMinParams() == method.getMaxParams() ? method.getMinParams() : method.getMinParams() + "-"
                  + method.getMaxParams()) + " argument(s) while has been received " + runtimeParameters.length);
      }
    }

    final Object functionResult = method.execute(iThis, iCurrentRecord, iContext, iCurrentResult, runtimeParameters);

    return transformValue(iCurrentRecord, iContext, functionResult);
  }

  @Override
  public Object getValue(final OIdentifiable iRecord, Object iCurrentResult, OCommandContext iContext) {
    final ODocument current = iRecord != null ? (ODocument) iRecord.getRecord() : null;
    return execute(current, current, null, iContext);
  }

  @Override
  public String getRoot() {
    return method.getName();
  }

  @Override
  protected void setRoot(final OBaseParser iQueryToParse, final String iText) {
    final int beginParenthesis = iText.indexOf('(');

    // SEARCH FOR THE FUNCTION
    final String funcName = iText.substring(0, beginParenthesis);

    final List<String> funcParamsText = OStringSerializerHelper.getParameters(iText);

    method = OSQLEngine.getMethod(funcName);
    if (method == null)
      throw new OCommandSQLParsingException("Unknown method " + funcName + "()");

    // PARSE PARAMETERS
    this.configuredParameters = new Object[funcParamsText.size()];
    for (int i = 0; i < funcParamsText.size(); ++i)
      this.configuredParameters[i] = funcParamsText.get(i);

    setParameters(configuredParameters, true);
  }

  public OSQLMethodRuntime setParameters(final Object[] iParameters, final boolean iEvaluate) {
    if (iParameters != null) {
      this.configuredParameters = new Object[iParameters.length];
      for (int i = 0; i < iParameters.length; ++i) {
        this.configuredParameters[i] = iParameters[i];

        if (iParameters[i] != null) {
          if (iParameters[i] instanceof String && !iParameters[i].toString().startsWith("[")) {
            final Object v = OSQLHelper.parseValue(null, null, iParameters[i].toString(), null);
            if (v == OSQLHelper.VALUE_NOT_PARSED
                || (v != null && OMultiValue.isMultiValue(v) && OMultiValue.getFirstValue(v) == OSQLHelper.VALUE_NOT_PARSED))
              continue;

            configuredParameters[i] = v;
          }
        } else
          this.configuredParameters[i] = null;
      }

      // COPY STATIC VALUES
      this.runtimeParameters = new Object[configuredParameters.length];
      for (int i = 0; i < configuredParameters.length; ++i) {
        if (!(configuredParameters[i] instanceof OSQLFilterItemField) && !(configuredParameters[i] instanceof OSQLMethodRuntime))
          runtimeParameters[i] = configuredParameters[i];
      }
    }

    return this;
  }

  public OSQLMethod getMethod() {
    return method;
  }

  public Object[] getConfiguredParameters() {
    return configuredParameters;
  }

  public Object[] getRuntimeParameters() {
    return runtimeParameters;
  }

  @Override
  public int compareTo(final OSQLMethodRuntime o) {
    return method.compareTo(o.getMethod());
  }
}


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

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

import com.orientechnologies.orient.core.command.OCommandContext;
import com.orientechnologies.orient.core.db.ODatabase;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.index.OIndex;
import com.orientechnologies.orient.core.index.OIndexCursor;
import com.orientechnologies.orient.core.index.OIndexCursorCollectionValue;
import com.orientechnologies.orient.core.index.OIndexCursorSingleValue;
import com.orientechnologies.orient.core.index.OIndexDefinition;
import com.orientechnologies.orient.core.index.OIndexFullText;
import com.orientechnologies.orient.core.metadata.OMetadataInternal;
import com.orientechnologies.orient.core.metadata.schema.OProperty;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.core.serialization.serializer.record.binary.ODocumentSerializer;
import com.orientechnologies.orient.core.sql.filter.OSQLFilterCondition;
import com.orientechnologies.orient.core.sql.filter.OSQLFilterItemField;

/**
 * CONTAINSTEXT operator. Look if a text is contained in a property. This is usually used with the FULLTEXT-INDEX for fast lookup at
 * piece of text.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 *
 */
public class OQueryOperatorContainsText extends OQueryTargetOperator {
  private boolean ignoreCase = true;

  public OQueryOperatorContainsText(final boolean iIgnoreCase) {
    super("CONTAINSTEXT", 5, false);
    ignoreCase = iIgnoreCase;
  }

  public OQueryOperatorContainsText() {
    super("CONTAINSTEXT", 5, false);
  }

  @Override
  public String getSyntax() {
    return "<left> CONTAINSTEXT[( noignorecase ] )] <right>";
  }

  /**
   * This is executed on non-indexed fields.
   */
  @Override
  public Object evaluateRecord(final OIdentifiable iRecord, ODocument iCurrentResult, final OSQLFilterCondition iCondition,
      final Object iLeft, final Object iRight, OCommandContext iContext, final ODocumentSerializer serializer) {
    if (iLeft == null || iRight == null)
      return false;

    return iLeft.toString().indexOf(iRight.toString()) > -1;
  }

  @SuppressWarnings({ "unchecked", "deprecation" })
  @Override
  public Collection<OIdentifiable> filterRecords(final ODatabase<?> iDatabase, final List<String> iTargetClasses,
      final OSQLFilterCondition iCondition, final Object iLeft, final Object iRight) {

    final String fieldName;
    if (iCondition.getLeft() instanceof OSQLFilterItemField)
      fieldName = iCondition.getLeft().toString();
    else
      fieldName = iCondition.getRight().toString();

    final String fieldValue;
    if (iCondition.getLeft() instanceof OSQLFilterItemField)
      fieldValue = iCondition.getRight().toString();
    else
      fieldValue = iCondition.getLeft().toString();

    final String className = iTargetClasses.get(0);

    final OProperty prop = ((OMetadataInternal) iDatabase.getMetadata()).getImmutableSchemaSnapshot().getClass(className)
        .getProperty(fieldName);
    if (prop == null)
      // NO PROPERTY DEFINED
      return null;

    OIndex<?> fullTextIndex = null;
    for (final OIndex<?> indexDefinition : prop.getIndexes()) {
      if (indexDefinition instanceof OIndexFullText) {
        fullTextIndex = indexDefinition;
        break;
      }
    }

    if (fullTextIndex == null) {
      return null;
    }

    return (Collection<OIdentifiable>) fullTextIndex.get(fieldValue);
  }

  public boolean isIgnoreCase() {
    return ignoreCase;
  }

  @Override
  public OIndexReuseType getIndexReuseType(final Object iLeft, final Object iRight) {
    return OIndexReuseType.INDEX_METHOD;
  }

  @Override
  public OIndexCursor executeIndexQuery(OCommandContext iContext, OIndex<?> index, List<Object> keyParams, boolean ascSortOrder) {

    final OIndexDefinition indexDefinition = index.getDefinition();
    if (indexDefinition.getParamCount() > 1)
      return null;

    final OIndex<?> internalIndex = index.getInternal();

    OIndexCursor cursor;
    if (internalIndex instanceof OIndexFullText) {
      final Object key = indexDefinition.createValue(keyParams);
      final Object indexResult = index.get(key);

      if (indexResult == null || indexResult instanceof OIdentifiable)
        cursor = new OIndexCursorSingleValue((OIdentifiable) indexResult, key);
      else
        cursor = new OIndexCursorCollectionValue((Collection<OIdentifiable>) indexResult, key);
    } else
      return null;

    updateProfiler(iContext, internalIndex, keyParams, indexDefinition);

    return cursor;
  }

  @Override
  public ORID getBeginRidRange(Object iLeft, Object iRight) {
    return null;
  }

  @Override
  public ORID getEndRidRange(Object iLeft, Object iRight) {
    return null;
  }
}


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

package com.orientechnologies.orient.object.enhancement.field;

import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.record.impl.ODocument;

/**
 * {@link ODocumentFieldHandlingStrategy} that delegates to the default {@link ODocument#field(String)} implementation.
 *
 * @author diegomtassis Diego Martin Tassis
 */
public class ODocumentSimpleFieldHandlingStrategy implements ODocumentFieldHandlingStrategy {

  @Override
  public ODocument store(ODocument iRecord, String fieldName, Object fieldValue, OType suggestedFieldType) {

    return iRecord.field(fieldName, fieldValue, deriveFieldType(iRecord, fieldName, suggestedFieldType));
  }

  @Override
  public Object load(ODocument iRecord, String fieldName, OType suggestedFieldType) {

    OType fieldType = deriveFieldType(iRecord, fieldName, suggestedFieldType);

    /*
     * For backward compatibility reasons this approach is kept (though it's kind of odd): asking a field value using a suggested
     * type which does not match the actual type of the field implies doing a cast and updating the type of the field in the
     * document.
     */
    if (iRecord.fieldType(fieldName) != fieldType) {
      iRecord.field(fieldName, iRecord.field(fieldName), fieldType);
    }

    return iRecord.field(fieldName);
  }

  /**
   * Derives the type of a field in a document.
   *
   * @param iRecord
   * @param fieldName
   * @param requestedFieldType
   * @return derived field type
   */
  protected OType deriveFieldType(ODocument iRecord, String fieldName, OType requestedFieldType) {

    // Schema defined types can not be ignored
    if (iRecord.getSchemaClass().existsProperty(fieldName)) {
      return iRecord.getSchemaClass().getProperty(fieldName).getType();
    }

    // New type
    if (requestedFieldType != null) {
      return requestedFieldType;
    }

    // Existing type (not fixed by the schema)
    return iRecord.fieldType(fieldName);
  }
}


/*
 *
 *  *  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.orient.core.command.OCommandRequest;
import com.orientechnologies.orient.core.command.OCommandRequestText;
import com.orientechnologies.orient.core.db.document.ODatabaseDocument;
import com.orientechnologies.orient.core.exception.OCommandExecutionException;
import com.orientechnologies.orient.core.metadata.security.ORole;

import java.util.Map;

/**
 * SQL REVOKE command: Revoke a privilege to a database role.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 */
public class OCommandExecutorSQLRevoke extends OCommandExecutorSQLPermissionAbstract {
  public static final  String KEYWORD_REVOKE = "REVOKE";
  private static final String KEYWORD_FROM   = "FROM";

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

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

      final ODatabaseDocument database = getDatabase();

      init((OCommandRequestText) iRequest);

      privilege = ORole.PERMISSION_NONE;
      resource = null;
      role = null;

      StringBuilder word = new StringBuilder();

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

      pos = nextWord(parserText, parserTextUpperCase, pos, word, true);
      if (pos == -1)
        throw new OCommandSQLParsingException("Invalid privilege", parserText, oldPos);

      parsePrivilege(word, oldPos);

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

      pos = nextWord(parserText, parserText, pos, word, true);
      if (pos == -1)
        throw new OCommandSQLParsingException("Invalid resource", parserText, oldPos);

      resource = word.toString();

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

      pos = nextWord(parserText, parserText, pos, word, true);
      if (pos == -1)
        throw new OCommandSQLParsingException("Invalid role", parserText, oldPos);

      final String roleName = word.toString();
      role = database.getMetadata().getSecurity().getRole(roleName);
      if (role == null)
        throw new OCommandSQLParsingException("Invalid role: " + roleName);
    } finally {
      textRequest.setText(originalQuery);
    }

    return this;
  }

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

    role.revoke(resource, privilege);
    role.save();

    return role;
  }

  public String getSyntax() {
    return "REVOKE <permission> ON <resource> FROM <role>";
  }
}


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

import com.orientechnologies.orient.core.Orient;
import com.orientechnologies.orient.core.config.OGlobalConfiguration;
import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.db.document.ODatabaseDocument;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.record.ORecord;
import com.orientechnologies.orient.core.record.ORecordVersionHelper;

/**
 * Local cache. it's one to one with record database instances. It is needed to avoid cases when several instances of the same
 * record will be loaded by user from the same database.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 */
public class OLocalRecordCache extends OAbstractRecordCache {
  private String CACHE_HIT;
  private String CACHE_MISS;

  public OLocalRecordCache() {
    super(Orient.instance().getLocalRecordCache().newInstance(OGlobalConfiguration.CACHE_LOCAL_IMPL.getValueAsString()));
  }

  @Override
  public void startup() {
    ODatabaseDocument db = ODatabaseRecordThreadLocal.instance().get();

    profilerPrefix = "db." + db.getName() + ".cache.level1.";
    profilerMetadataPrefix = "db.*.cache.level1.";

    CACHE_HIT = profilerPrefix + "cache.found";
    CACHE_MISS = profilerPrefix + "cache.notFound";

    super.startup();
  }

  /**
   * Pushes record to cache. Identifier of record used as access key
   *
   * @param record
   *          record that should be cached
   */
  public void updateRecord(final ORecord record) {
    if (record.getIdentity().getClusterId() != excludedCluster && record.getIdentity().isValid() && !record.isDirty()
        && !ORecordVersionHelper.isTombstone(record.getVersion())) {
      if (underlying.get(record.getIdentity()) != record)
        underlying.put(record);
    }
  }

  /**
   * Looks up for record in cache by it's identifier. Optionally look up in secondary cache and update primary with found record
   *
   * @param rid
   *          unique identifier of record
   * @return record stored in cache if any, otherwise - {@code null}
   */
  public ORecord findRecord(final ORID rid) {
    ORecord record;
    record = underlying.get(rid);

    if (record != null)
      Orient.instance().getProfiler().updateCounter(CACHE_HIT, "Record found in Level1 Cache", 1L, "db.*.cache.level1.cache.found");
    else
      Orient.instance().getProfiler().updateCounter(CACHE_MISS, "Record not found in Level1 Cache", 1L,
          "db.*.cache.level1.cache.notFound");

    return record;
  }

  /**
   * Removes record with specified identifier from both primary and secondary caches
   *
   * @param rid
   *          unique identifier of record
   */
  public void deleteRecord(final ORID rid) {
    super.deleteRecord(rid);
  }

  public void shutdown() {
    super.shutdown();
  }

  @Override
  public void clear() {
    super.clear();
  }

  /**
   * Invalidates the cache emptying all the records.
   */
  public void invalidate() {
    underlying.clear();
  }

  @Override
  public String toString() {
    return "DB level cache records = " + getSize();
  }
}


/*
  *
  *  *  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.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.exception.OCommandExecutionException;
import com.orientechnologies.orient.core.index.OIndex;

import java.util.Map;

/**
 * SQL REMOVE INDEX command: Remove an index
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 */
@SuppressWarnings("unchecked")
public class OCommandExecutorSQLDropIndex extends OCommandExecutorSQLAbstract implements OCommandDistributedReplicateRequest {
  public static final String KEYWORD_DROP  = "DROP";
  public static final String KEYWORD_INDEX = "INDEX";

  private String name;

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

      final StringBuilder word = new StringBuilder();

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

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

      oldPos = pos;
      pos = nextWord(parserText, parserTextUpperCase, oldPos, word, false);
      if (pos == -1)
        throw new OCommandSQLParsingException("Expected index name. Use " + getSyntax(), parserText, oldPos);

      name = word.toString();
    } finally {
      textRequest.setText(originalQuery);
    }

    return this;
  }

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

    if (name.equals("*")) {
      long totalIndexed = 0;
      for (OIndex<?> idx : getDatabase().getMetadata().getIndexManager().getIndexes()) {
        getDatabase().getMetadata().getIndexManager().dropIndex(idx.getName());
        totalIndexed++;
      }

      return totalIndexed;

    } else
      getDatabase().getMetadata().getIndexManager().dropIndex(name);

    return 1;
  }

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

  @Override
  public String getSyntax() {
    return "DROP INDEX <index-name>|<class>.<property>|*";
  }

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


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

import com.orientechnologies.common.serialization.types.OBinarySerializer;
import com.orientechnologies.common.util.OCommonConst;
import com.orientechnologies.orient.core.metadata.schema.OType;
import com.orientechnologies.orient.core.serialization.serializer.binary.OBinarySerializerFactory;
import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OWALChanges;

import java.nio.ByteBuffer;

/**
 * Serializer that is used for serialization of non {@link com.orientechnologies.orient.core.index.OCompositeKey} keys in index.
 *
 * @author Andrey Lomakin (a.lomakin-at-orientdb.com)
 * @since 31.03.12
 */
@SuppressWarnings({ "rawtypes", "unchecked" })
public class OSimpleKeySerializer<T extends Comparable<?>> implements OBinarySerializer<T> {

  private OType             type;
  private OBinarySerializer binarySerializer;

  public static final byte   ID   = 15;
  public static final String NAME = "bsks";

  public OSimpleKeySerializer() {
  }

  public OSimpleKeySerializer(final OType iType) {
    type = iType;

    binarySerializer = OBinarySerializerFactory.getInstance().getObjectSerializer(iType);
  }

  public int getObjectSize(T key, Object... hints) {
    init(key, hints);
    return OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE + binarySerializer.getObjectSize(key);
  }

  public void serialize(T key, byte[] stream, int startPosition, Object... hints) {
    init(key, hints);
    stream[startPosition] = binarySerializer.getId();
    startPosition += OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE;
    binarySerializer.serialize(key, stream, startPosition);
  }

  public T deserialize(byte[] stream, int startPosition) {
    final byte typeId = stream[startPosition];
    startPosition += OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE;

    init(typeId);
    return (T) binarySerializer.deserialize(stream, startPosition);
  }

  public int getObjectSize(byte[] stream, int startPosition) {
    final byte serializerId = stream[startPosition];
    init(serializerId);
    return OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE + binarySerializer
        .getObjectSize(stream, startPosition + OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE);
  }

  public byte getId() {
    return ID;
  }

  protected void init(T key, Object[] hints) {
    if (binarySerializer == null) {
      final OType[] types;

      if (hints != null && hints.length > 0)
        types = (OType[]) hints;
      else
        types = OCommonConst.EMPTY_TYPES_ARRAY;

      if (types.length > 0)
        type = types[0];
      else
        type = OType.getTypeByClass(key.getClass());

      binarySerializer = OBinarySerializerFactory.getInstance().getObjectSerializer(type);
    }
  }

  protected void init(byte serializerId) {
    if (binarySerializer == null)
      binarySerializer = OBinarySerializerFactory.getInstance().getObjectSerializer(serializerId);
  }

  public int getObjectSizeNative(byte[] stream, int startPosition) {
    final byte serializerId = stream[startPosition];
    init(serializerId);
    return OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE + binarySerializer
        .getObjectSizeNative(stream, startPosition + OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE);
  }

  public void serializeNativeObject(T key, byte[] stream, int startPosition, Object... hints) {
    init(key, hints);
    stream[startPosition] = binarySerializer.getId();
    startPosition += OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE;
    binarySerializer.serializeNativeObject(key, stream, startPosition);
  }

  public T deserializeNativeObject(byte[] stream, int startPosition) {
    final byte typeId = stream[startPosition];
    startPosition += OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE;

    init(typeId);
    return (T) binarySerializer.deserializeNativeObject(stream, startPosition);
  }

  public boolean isFixedLength() {
    return binarySerializer.isFixedLength();
  }

  public int getFixedLength() {
    return binarySerializer.getFixedLength() + OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE;
  }

  @Override
  public T preprocess(T value, Object... hints) {
    init(value, hints);

    return (T) binarySerializer.preprocess(value);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void serializeInByteBufferObject(T object, ByteBuffer buffer, Object... hints) {
    init(object, hints);
    buffer.put(binarySerializer.getId());
    binarySerializer.serializeInByteBufferObject(object, buffer);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public T deserializeFromByteBufferObject(ByteBuffer buffer) {
    final byte typeId = buffer.get();

    init(typeId);
    return (T) binarySerializer.deserializeFromByteBufferObject(buffer);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getObjectSizeInByteBuffer(ByteBuffer buffer) {
    final byte serializerId = buffer.get();
    init(serializerId);
    return OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE + binarySerializer.getObjectSizeInByteBuffer(buffer);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public T deserializeFromByteBufferObject(ByteBuffer buffer, OWALChanges walChanges, int offset) {
    final byte typeId = walChanges.getByteValue(buffer, offset++);

    init(typeId);
    return (T) binarySerializer.deserializeFromByteBufferObject(buffer, walChanges, offset);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public int getObjectSizeInByteBuffer(ByteBuffer buffer, OWALChanges walChanges, int offset) {
    return OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE + binarySerializer
        .getObjectSizeInByteBuffer(buffer, walChanges, OBinarySerializerFactory.TYPE_IDENTIFIER_SIZE + offset);
  }
}