/*
 *
 * 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.
 */
package com.orientechnologies.orient.object.metadata.schema;

import com.orientechnologies.common.exception.OException;
import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.common.reflection.OReflectionHelper;
import com.orientechnologies.orient.core.db.ODatabaseDocumentInternal;
import com.orientechnologies.orient.core.db.ODatabaseRecordThreadLocal;
import com.orientechnologies.orient.core.db.document.ODatabaseDocument;
import com.orientechnologies.orient.core.db.viewmanager.ViewCreationListener;
import com.orientechnologies.orient.core.entity.OEntityManager;
import com.orientechnologies.orient.core.exception.ODatabaseException;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.metadata.schema.*;
import com.orientechnologies.orient.core.metadata.schema.clusterselection.OClusterSelectionFactory;
import com.orientechnologies.orient.core.record.impl.OBlob;
import com.orientechnologies.orient.core.record.impl.ODocument;
import com.orientechnologies.orient.object.db.OObjectDatabaseTx;
import com.orientechnologies.orient.object.enhancement.OObjectEntitySerializer;
import javassist.util.proxy.Proxy;

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

/**
 * @author Luca Molino (molino.luca--at--gmail.com)
 */
public class OSchemaProxyObject implements OSchemaObject {

  protected OSchema underlying;

  public OSchemaProxyObject(OSchema iUnderlying) {
    underlying = iUnderlying;
  }

  @Override
  public OImmutableSchema makeSnapshot() {
    return underlying.makeSnapshot();
  }

  @Override
  public int countClasses() {
    return underlying.countClasses();
  }

  @Override
  public int countViews() {
    return underlying.countViews();
  }

  @Override
  public OClass createClass(String iClassName) {
    return underlying.createClass(iClassName);
  }

  @Override
  public OClass createClass(String iClassName, OClass iSuperClass) {
    return underlying.createClass(iClassName, iSuperClass);
  }

  @Override
  public OClass createClass(String iClassName, OClass... superClasses) {
    return underlying.createClass(iClassName, superClasses);
  }

  @Override
  public OClass createClass(String iClassName, OClass iSuperClass, int[] iClusterIds) {
    return underlying.createClass(iClassName, iSuperClass, iClusterIds);
  }

  @Override
  public OClass createClass(String className, int[] clusterIds, OClass... superClasses) {
    return underlying.createClass(className, clusterIds, superClasses);
  }

  @Override
  public OClass createAbstractClass(Class<?> iClass) {
    OClass cls = null;
    int[] clusterIds = new int[] { -1 };
    // TODO: revisit this logic: interfaces should be also taken into consideration
    final Class<?> superClass = iClass.getSuperclass();
    if (superClass != null && superClass != Object.class && existsClass(superClass.getSimpleName()))
      cls = getClass(superClass.getSimpleName());
    cls = createClass(iClass.getSimpleName(), clusterIds, cls);
    return cls;
  }

  @Override
  public OClass createAbstractClass(String iClassName) {
    return underlying.createAbstractClass(iClassName);
  }

  @Override
  public OClass createAbstractClass(String iClassName, OClass iSuperClass) {
    return underlying.createAbstractClass(iClassName, iSuperClass);
  }

  @Override
  public OClass createAbstractClass(String iClassName, OClass... superClasses) {
    return underlying.createAbstractClass(iClassName, superClasses);
  }

  @Override
  public void dropClass(String iClassName) {
    underlying.dropClass(iClassName);
  }

  @Override
  public OSchema reload() {
    return underlying.reload();
  }

  @Override
  public boolean existsClass(String iClassName) {
    return underlying.existsClass(iClassName);
  }

  @Override
  public boolean existsView(String name) {
    return underlying.existsView(name);
  }

  @Override
  public void dropView(String name) {
    underlying.dropView(name);
  }

  @Override
  public OClass getClass(Class<?> iClass) {
    return underlying.getClass(iClass);
  }

  @Override
  public OClass getClass(String iClassName) {
    return underlying.getClass(iClassName);
  }

  @Override
  public OClass getOrCreateClass(String iClassName) {
    return underlying.getOrCreateClass(iClassName);
  }

  @Override
  public OClass getOrCreateClass(String iClassName, OClass iSuperClass) {
    return underlying.getOrCreateClass(iClassName, iSuperClass);
  }

  @Override
  public OClass getOrCreateClass(String iClassName, OClass... superClasses) {
    return underlying.getOrCreateClass(iClassName, superClasses);
  }

  @Override
  public OGlobalProperty getGlobalPropertyById(int id) {
    return underlying.getGlobalPropertyById(id);
  }

  @Override
  public Collection<OClass> getClasses() {
    return underlying.getClasses();
  }

  public Collection<OView> getViews() {
    return underlying.getViews();
  }

  @Override
  public OView getView(String name) {
    return underlying.getView(name);
  }

  @Override
  public OView createView(String viewName, String statement) {
    return underlying.createView(viewName, statement);
  }

  public OView createView(ODatabaseDocumentInternal database, final String viewName, String statement, Map<String, Object> metadata) {
    return underlying.createView(database, viewName, statement, metadata);
  }

  @Override
  public OView createView(OViewConfig config) {
    return underlying.createView(config);
  }

  @Override
  public OView createView(OViewConfig config, ViewCreationListener listener) {
    return underlying.createView(config);
  }

  @Override
  public void create() {
    underlying.create();
  }

  @Override
  @Deprecated
  public int getVersion() {
    return underlying.getVersion();
  }

  @Override
  public ORID getIdentity() {
    return underlying.getIdentity();
  }

  @Override
  public Set<OClass> getClassesRelyOnCluster(String iClusterName) {
    return underlying.getClassesRelyOnCluster(iClusterName);
  }

  @Override
  public OClass createClass(String className, int clusters, OClass... superClasses) {
    return underlying.createClass(className, clusters, superClasses);
  }

  public OSchema getUnderlying() {
    return underlying;
  }

  @Override
  public OClass getClassByClusterId(int clusterId) {
    return underlying.getClassByClusterId(clusterId);
  }

  @Override
  public OView getViewByClusterId(int clusterId) {
    return underlying.getViewByClusterId(clusterId);
  }

  @Override
  public OClusterSelectionFactory getClusterSelectionFactory() {
    return underlying.getClusterSelectionFactory();
  }

  /**
   * Scans all classes accessible from the context class loader which belong to the given package and subpackages.
   *
   * @param iPackageName The base package
   */
  public synchronized void generateSchema(final String iPackageName) {
    generateSchema(iPackageName, Thread.currentThread().getContextClassLoader());
  }

  /**
   * Scans all classes accessible from the context class loader which belong to the given package and subpackages.
   *
   * @param iPackageName The base package
   */
  public synchronized void generateSchema(final String iPackageName, final ClassLoader iClassLoader) {
    OLogManager.instance().debug(this, "Generating schema inside package: %s", iPackageName);

    List<Class<?>> classes = null;
    try {
      classes = OReflectionHelper.getClassesFor(iPackageName, iClassLoader);
    } catch (ClassNotFoundException e) {
      throw OException.wrapException(new ODatabaseException("Classes cannot be loaded during schema generation"), e);
    }
    for (Class<?> c : classes) {
      generateSchema(c);
    }
  }

  /**
   * Generate/updates the SchemaClass and properties from given Class<?>.
   *
   * @param iClass :- the Class<?> to generate
   */
  public synchronized void generateSchema(final Class<?> iClass) {
    generateSchema(iClass, ODatabaseRecordThreadLocal.instance().get());
  }

  /**
   * Generate/updates the SchemaClass and properties from given Class<?>.
   *
   * @param iClass :- the Class<?> to generate
   */
  public synchronized void generateSchema(final Class<?> iClass, ODatabaseDocument database) {
    if (iClass == null || iClass.isInterface() || iClass.isPrimitive() || iClass.isEnum() || iClass.isAnonymousClass())
      return;
    OObjectEntitySerializer.registerClass(iClass);
    OClass schema = database.getMetadata().getSchema().getClass(iClass);
    if (schema == null) {
      generateOClass(iClass, database);
    }
    List<String> fields = OObjectEntitySerializer.getClassFields(iClass);
    if (fields != null)
      for (String field : fields) {
        if (schema.existsProperty(field))
          continue;
        if (OObjectEntitySerializer.isVersionField(iClass, field) || OObjectEntitySerializer.isIdField(iClass, field))
          continue;
        Field f = OObjectEntitySerializer.getField(field, iClass);
        if (f.getType().equals(Object.class) || f.getType().equals(ODocument.class) || OBlob.class.isAssignableFrom(f.getType())) {
          continue;
        }
        OType t = OObjectEntitySerializer.getTypeByClass(iClass, field, f);
        if (t == OType.CUSTOM) {
          OEntityManager entityManager = OEntityManager.getEntityManagerByDatabaseURL(database.getURL());
          // if the target type is registered as entity, it should be linked instead of custom/serialized
          if (entityManager.getEntityClass(f.getType().getSimpleName()) != null) {
            t = OType.LINK;
          }
        }
        if (t == null) {
          if (f.getType().isEnum())
            t = OType.STRING;
          else {
            t = OType.LINK;
          }
        }
        switch (t) {

        case LINK:
          Class<?> linkedClazz = OObjectEntitySerializer.getSpecifiedLinkedType(f);
          if (linkedClazz == null)
            linkedClazz = f.getType();
          generateLinkProperty(database, schema, field, t, linkedClazz);
          break;
        case LINKLIST:
        case LINKMAP:
        case LINKSET:
          linkedClazz = OObjectEntitySerializer.getSpecifiedMultiLinkedType(f);
          if (linkedClazz == null)
            linkedClazz = OReflectionHelper.getGenericMultivalueType(f);
          if (linkedClazz != null)
            generateLinkProperty(database, schema, field, t, linkedClazz);
          break;

        case EMBEDDED:
          linkedClazz = f.getType();
          if (linkedClazz == null || linkedClazz.equals(Object.class) || linkedClazz.equals(ODocument.class) || OBlob.class
              .isAssignableFrom(f.getType())) {
            continue;
          } else {
            generateLinkProperty(database, schema, field, t, linkedClazz);
          }
          break;

        case EMBEDDEDLIST:
        case EMBEDDEDSET:
        case EMBEDDEDMAP:
          linkedClazz = OReflectionHelper.getGenericMultivalueType(f);
          if (linkedClazz == null || linkedClazz.equals(Object.class) || linkedClazz.equals(ODocument.class) || OBlob.class
              .isAssignableFrom(f.getType())) {
            continue;
          } else {
            if (OReflectionHelper.isJavaType(linkedClazz)) {
              schema.createProperty(field, t, OType.getTypeByClass(linkedClazz));
            } else if (linkedClazz.isEnum()) {
              schema.createProperty(field, t, OType.STRING);
            } else {
              generateLinkProperty(database, schema, field, t, linkedClazz);
            }
          }
          break;

        default:
          schema.createProperty(field, t);
          break;
        }
      }
  }

  /**
   * Checks if all registered entities has schema generated, if not it generates it
   */
  public synchronized void synchronizeSchema() {
    OObjectDatabaseTx database = ((OObjectDatabaseTx) ODatabaseRecordThreadLocal.instance().get().getDatabaseOwner());
    Collection<Class<?>> registeredEntities = database.getEntityManager().getRegisteredEntities();
    boolean automaticSchemaGeneration = database.isAutomaticSchemaGeneration();
    boolean reloadSchema = false;
    for (Class<?> iClass : registeredEntities) {
      if (Proxy.class.isAssignableFrom(iClass) || iClass.isEnum() || OReflectionHelper.isJavaType(iClass) || iClass
          .isAnonymousClass())
        return;

      if (!database.getMetadata().getSchema().existsClass(iClass.getSimpleName())) {
        database.getMetadata().getSchema().createClass(iClass.getSimpleName());
        reloadSchema = true;
      }

      for (Class<?> currentClass = iClass; currentClass != Object.class; ) {

        if (automaticSchemaGeneration && !currentClass.equals(Object.class) && !currentClass.equals(ODocument.class)) {
          ((OSchemaProxyObject) database.getMetadata().getSchema()).generateSchema(currentClass, database.getUnderlying());
        }
        String iClassName = currentClass.getSimpleName();
        currentClass = currentClass.getSuperclass();

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

        if (database != null && !database.isClosed() && !currentClass.equals(Object.class)) {
          OClass oSuperClass;
          OClass currentOClass = database.getMetadata().getSchema().getClass(iClassName);
          if (!database.getMetadata().getSchema().existsClass(currentClass.getSimpleName())) {
            oSuperClass = database.getMetadata().getSchema().createClass(currentClass.getSimpleName());
            reloadSchema = true;
          } else {
            oSuperClass = database.getMetadata().getSchema().getClass(currentClass.getSimpleName());
            reloadSchema = true;
          }

          if (!currentOClass.getSuperClasses().contains(oSuperClass)) {
            currentOClass.setSuperClasses(Arrays.asList(oSuperClass));
            reloadSchema = true;
          }

        }
      }
    }
    if (database != null && !database.isClosed() && reloadSchema) {
      database.getMetadata().getSchema().reload();
    }
  }

  protected static void generateOClass(Class<?> iClass, ODatabaseDocument database) {
    boolean reloadSchema = false;
    for (Class<?> currentClass = iClass; currentClass != Object.class; ) {
      String iClassName = currentClass.getSimpleName();
      currentClass = currentClass.getSuperclass();

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

      if (ODatabaseRecordThreadLocal.instance().get() != null && !ODatabaseRecordThreadLocal.instance().get().isClosed()
          && !currentClass.equals(Object.class)) {
        OClass oSuperClass;
        OClass currentOClass = database.getMetadata().getSchema().getClass(iClassName);
        if (!database.getMetadata().getSchema().existsClass(currentClass.getSimpleName())) {
          oSuperClass = database.getMetadata().getSchema().createClass(currentClass.getSimpleName());
          reloadSchema = true;
        } else {
          oSuperClass = database.getMetadata().getSchema().getClass(currentClass.getSimpleName());
          reloadSchema = true;
        }

        if (!currentOClass.getSuperClasses().contains(oSuperClass)) {
          currentOClass.setSuperClasses(Arrays.asList(oSuperClass));
          reloadSchema = true;
        }

      }
    }
    if (reloadSchema) {
      database.getMetadata().getSchema().reload();
    }
  }

  protected static void generateLinkProperty(ODatabaseDocument database, OClass schema, String field, OType t,
      Class<?> linkedClazz) {
    OClass linkedClass = database.getMetadata().getSchema().getClass(linkedClazz);
    if (linkedClass == null) {
      OObjectEntitySerializer.registerClass(linkedClazz);
      linkedClass = database.getMetadata().getSchema().getClass(linkedClazz);
    }
    schema.createProperty(field, t, linkedClass);
  }

  @Override
  public List<OGlobalProperty> getGlobalProperties() {
    return underlying.getGlobalProperties();
  }

  public OGlobalProperty createGlobalProperty(String name, OType type, Integer id) {
    return underlying.createGlobalProperty(name, type, id);
  }

  public OClass createClass(final Class<?> clazz) {
    OClass result;

    final Class<?> superClass = clazz.getSuperclass();
    final OClass cls;
    if (superClass != null && superClass != Object.class && existsClass(superClass.getSimpleName()))
      cls = getClass(superClass.getSimpleName());
    else
      cls = null;

    result = createClass(clazz.getSimpleName(), cls);

    return result;
  }

}


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

import com.orientechnologies.common.concur.resource.OSharedResourceAbstract;
import com.orientechnologies.common.io.OFileUtils;
import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.common.util.OPair;
import com.orientechnologies.orient.core.OOrientStartupListener;
import com.orientechnologies.orient.core.Orient;
import com.orientechnologies.orient.core.config.OGlobalConfiguration;
import com.orientechnologies.orient.core.storage.OStorage;
import com.orientechnologies.orient.core.storage.cache.OReadCache;
import com.orientechnologies.orient.core.storage.cache.OWriteCache;
import com.orientechnologies.orient.core.storage.disk.OLocalPaginatedStorage;

import javax.management.MBeanServer;
import javax.management.ObjectName;
import java.io.File;
import java.io.PrintStream;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

public abstract class OAbstractProfiler extends OSharedResourceAbstract
    implements OProfiler, OOrientStartupListener, OProfilerMXBean {

  protected final Map<String, OProfilerHookRuntime>      hooks         = new ConcurrentHashMap<String, OProfilerHookRuntime>();
  protected final ConcurrentHashMap<String, String>      dictionary    = new ConcurrentHashMap<String, String>();
  protected final ConcurrentHashMap<String, METRIC_TYPE> types         = new ConcurrentHashMap<String, METRIC_TYPE>();
  protected       long                                   recordingFrom = -1;
  protected       TimerTask                              autoDumpTask;
  protected       List<OProfilerListener>                listeners     = new ArrayList<OProfilerListener>();

  private static long statsCreateRecords = 0;
  private static long statsReadRecords   = 0;
  private static long statsUpdateRecords = 0;
  private static long statsDeleteRecords = 0;
  private static long statsCommands      = 0;
  private static long statsTxCommit      = 0;
  private static long statsTxRollback    = 0;
  private static long statsLastAutoDump  = 0;

  public interface OProfilerHookValue {
    Object getValue();
  }

  public class OProfilerHookRuntime {
    public OProfilerHookValue hook;
    public METRIC_TYPE        type;

    public OProfilerHookRuntime(final OProfilerHookValue hook, final METRIC_TYPE type) {
      this.hook = hook;
      this.type = type;
    }
  }

  public class OProfilerHookStatic {
    public Object      value;
    public METRIC_TYPE type;

    public OProfilerHookStatic(final Object value, final METRIC_TYPE type) {
      this.value = value;
      this.type = type;
    }
  }

  private static final class MemoryChecker implements Runnable {
    @Override
    public void run() {
      try {
        final long jvmTotMemory = Runtime.getRuntime().totalMemory();
        final long jvmMaxMemory = Runtime.getRuntime().maxMemory();

        for (OStorage s : Orient.instance().getStorages()) {
          if (s instanceof OLocalPaginatedStorage) {
            final OReadCache dk = ((OLocalPaginatedStorage) s).getReadCache();
            final OWriteCache wk = ((OLocalPaginatedStorage) s).getWriteCache();
            if (dk == null || wk == null)
              // NOT YET READY
              continue;

            final long totalDiskCacheUsedMemory = (dk.getUsedMemory() + wk.getExclusiveWriteCachePagesSize()) / OFileUtils.MEGABYTE;
            final long maxDiskCacheUsedMemory = OGlobalConfiguration.DISK_CACHE_SIZE.getValueAsLong();

            // CHECK IF THERE IS MORE THAN 40% HEAP UNUSED AND DISK-CACHE IS 80% OF THE MAXIMUM SIZE
            if ((jvmTotMemory * 140 / 100) < jvmMaxMemory && (totalDiskCacheUsedMemory * 120 / 100) > maxDiskCacheUsedMemory) {

              final long suggestedMaxHeap = jvmTotMemory * 120 / 100;
              final long suggestedDiskCache =
                  OGlobalConfiguration.DISK_CACHE_SIZE.getValueAsLong() + (jvmMaxMemory - suggestedMaxHeap) / OFileUtils.MEGABYTE;

              OLogManager.instance().info(this,
                  "Database '%s' uses %,dMB/%,dMB of DISKCACHE memory, while Heap is not completely used (usedHeap=%dMB maxHeap=%dMB). To improve performance set maxHeap to %dMB and DISKCACHE to %dMB",
                  s.getName(), totalDiskCacheUsedMemory, maxDiskCacheUsedMemory, jvmTotMemory / OFileUtils.MEGABYTE,
                  jvmMaxMemory / OFileUtils.MEGABYTE, suggestedMaxHeap / OFileUtils.MEGABYTE, suggestedDiskCache);

              OLogManager.instance().info(this,
                  "-> Open server.sh (or server.bat on Windows) and change the following variables: 1) MAXHEAP=-Xmx%dM 2) MAXDISKCACHE=%d",
                  suggestedMaxHeap / OFileUtils.MEGABYTE, suggestedDiskCache);
            }
          }
        }
      } catch (Exception e) {
        OLogManager.instance().debug(this, "Error on memory checker task", e);
      } catch (Error e) {
        OLogManager.instance().debug(this, "Error on memory checker task", e);
        throw e;
      }
    }
  }

  public OAbstractProfiler() {
    this(true);
  }

  public OAbstractProfiler(boolean registerListener) {
    if (registerListener) {
      Orient.instance().registerWeakOrientStartupListener(this);
    }
  }

  public OAbstractProfiler(final OAbstractProfiler profiler) {
    hooks.putAll(profiler.hooks);
    dictionary.putAll(profiler.dictionary);
    types.putAll(profiler.types);

    Orient.instance().registerWeakOrientStartupListener(this);
  }

  protected abstract void setTip(String iMessage, AtomicInteger counter);

  protected abstract AtomicInteger getTip(String iMessage);

  public static String dumpEnvironment(final String dumpType) {
    final StringBuilder buffer = new StringBuilder();

    final Runtime runtime = Runtime.getRuntime();

    final long freeSpaceInMB = new File(".").getFreeSpace();
    final long totalSpaceInMB = new File(".").getTotalSpace();

    int stgs = 0;
    long diskCacheUsed = 0;
    long diskCacheTotal = 0;
    for (OStorage stg : Orient.instance().getStorages()) {
      if (stg instanceof OLocalPaginatedStorage) {
        diskCacheUsed += ((OLocalPaginatedStorage) stg).getReadCache().getUsedMemory();
        diskCacheTotal += OGlobalConfiguration.DISK_CACHE_SIZE.getValueAsLong() * 1024 * 1024;
        stgs++;
      }
    }
    try {
      MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
      ObjectName osMBeanName = ObjectName.getInstance(ManagementFactory.OPERATING_SYSTEM_MXBEAN_NAME);
      if (mbs.isInstanceOf(osMBeanName, "com.sun.management.OperatingSystemMXBean")) {
        final long osTotalMem = ((Number) mbs.getAttribute(osMBeanName, "TotalPhysicalMemorySize")).longValue();
        final long osUsedMem = osTotalMem - ((Number) mbs.getAttribute(osMBeanName, "FreePhysicalMemorySize")).longValue();

        buffer.append(String
            .format("OrientDB Memory profiler: HEAP=%s of %s - DISKCACHE (%s dbs)=%s of %s - OS=%s of %s - FS=%s of %s",
                OFileUtils.getSizeAsString(runtime.totalMemory() - runtime.freeMemory()),
                OFileUtils.getSizeAsString(runtime.maxMemory()), stgs, OFileUtils.getSizeAsString(diskCacheUsed),
                OFileUtils.getSizeAsString(diskCacheTotal), OFileUtils.getSizeAsString(osUsedMem),
                OFileUtils.getSizeAsString(osTotalMem), OFileUtils.getSizeAsString(freeSpaceInMB),
                OFileUtils.getSizeAsString(totalSpaceInMB)));
      }

    } catch (Exception e) {
      // JMX NOT AVAILABLE, AVOID OS DATA
      buffer.append(String.format("OrientDB Memory profiler: HEAP=%s of %s - DISKCACHE (%s dbs)=%s of %s - FS=%s of %s",
          OFileUtils.getSizeAsString(runtime.totalMemory() - runtime.freeMemory()), OFileUtils.getSizeAsString(runtime.maxMemory()),
          stgs, OFileUtils.getSizeAsString(diskCacheUsed), OFileUtils.getSizeAsString(diskCacheTotal),
          OFileUtils.getSizeAsString(freeSpaceInMB), OFileUtils.getSizeAsString(totalSpaceInMB)));
    }

    if ("performance".equalsIgnoreCase(dumpType)) {
      try {
        long lastCreateRecords = 0;
        long lastReadRecords = 0;
        long lastUpdateRecords = 0;
        long lastDeleteRecords = 0;
        long lastCommands = 0;
        long lastTxCommit = 0;
        long lastTxRollback = 0;

        if (statsLastAutoDump > 0) {
          final long msFromLastDump = System.currentTimeMillis() - statsLastAutoDump;

          final String[] hooks = Orient.instance().getProfiler().getHookAsString();
          for (String h : hooks) {
            if (h.startsWith("db.") && h.endsWith("createRecord"))
              lastCreateRecords += (Long) Orient.instance().getProfiler().getHookValue(h);
            else if (h.startsWith("db.") && h.endsWith("readRecord"))
              lastReadRecords += (Long) Orient.instance().getProfiler().getHookValue(h);
            else if (h.startsWith("db.") && h.endsWith("updateRecord"))
              lastUpdateRecords += (Long) Orient.instance().getProfiler().getHookValue(h);
            else if (h.startsWith("db.") && h.endsWith("deleteRecord"))
              lastDeleteRecords += (Long) Orient.instance().getProfiler().getHookValue(h);
            else if (h.startsWith("db.") && h.endsWith("txCommit"))
              lastTxCommit += (Long) Orient.instance().getProfiler().getHookValue(h);
            else if (h.startsWith("db.") && h.endsWith("txRollback"))
              lastTxRollback += (Long) Orient.instance().getProfiler().getHookValue(h);
          }

          final List<String> chronos = Orient.instance().getProfiler().getChronos();
          for (String c : chronos) {
            final OProfilerEntry chrono = Orient.instance().getProfiler().getChrono(c);
            if (chrono != null) {
              if (c.startsWith("db.") && c.contains(".command."))
                lastCommands += chrono.entries;
            }
          }

          long lastCreateRecordsSec = lastCreateRecords == 0 ?
              msFromLastDump < 1000 ? 1 : 0 :
              (lastCreateRecords - statsCreateRecords) / (msFromLastDump / 1000);
          long lastReadRecordsSec =
              lastReadRecords == 0 ? 0 : msFromLastDump < 1000 ? 1 : (lastReadRecords - statsReadRecords) / (msFromLastDump / 1000);
          long lastUpdateRecordsSec = lastUpdateRecords == 0 || msFromLastDump < 1000 ?
              0 :
              (lastUpdateRecords - statsUpdateRecords) / (msFromLastDump / 1000);
          long lastDeleteRecordsSec = lastDeleteRecords == 0 ?
              0 :
              msFromLastDump < 1000 ? 1 : (lastDeleteRecords - statsDeleteRecords) / (msFromLastDump / 1000);
          long lastCommandsSec =
              lastCommands == 0 ? 0 : msFromLastDump < 1000 ? 1 : (lastCommands - statsCommands) / (msFromLastDump / 1000);
          long lastTxCommitSec =
              lastTxCommit == 0 ? 0 : msFromLastDump < 1000 ? 1 : (lastTxCommit - statsTxCommit) / (msFromLastDump / 1000);
          long lastTxRollbackSec =
              lastTxRollback == 0 ? 0 : msFromLastDump < 1000 ? 1 : (lastTxRollback - statsTxRollback) / (msFromLastDump / 1000);

          buffer.append(String.format(
              "\nCRUD: C(%d %d/sec) R(%d %d/sec) U(%d %d/sec) D(%d %d/sec) - COMMANDS (%d %d/sec) - TX: COMMIT(%d %d/sec) ROLLBACK(%d %d/sec)",
              lastCreateRecords, lastCreateRecordsSec, lastReadRecords, lastReadRecordsSec, lastUpdateRecords, lastUpdateRecordsSec,
              lastDeleteRecords, lastDeleteRecordsSec, lastCommands, lastCommandsSec, lastTxCommit, lastTxCommitSec, lastTxRollback,
              lastTxRollbackSec));
        }

        statsLastAutoDump = System.currentTimeMillis();
        statsCreateRecords = lastCreateRecords;
        statsReadRecords = lastReadRecords;
        statsUpdateRecords = lastUpdateRecords;
        statsDeleteRecords = lastDeleteRecords;
        statsCommands = lastCommands;
        statsTxCommit = lastTxCommit;
        statsTxRollback = lastTxRollback;

      } catch (Exception e) {
        // IGNORE IT
      }
    }

    return buffer.toString();
  }

  @Override
  public void onStartup() {
    if (OGlobalConfiguration.PROFILER_ENABLED.getValueAsBoolean())
      // ACTIVATE RECORDING OF THE PROFILER
      startRecording();
    installMemoryChecker();
  }

  public void shutdown() {
    stopRecording();
  }

  public int reportTip(final String iMessage) {
    AtomicInteger counter = getTip(iMessage);
    if (counter == null) {
      // DUMP THE MESSAGE ONLY THE FIRST TIME
      OLogManager.instance().info(this, "[TIP] " + iMessage);

      counter = new AtomicInteger(0);
    }

    setTip(iMessage, counter);

    return counter.incrementAndGet();
  }

  public boolean startRecording() {
    if (isRecording())
      return false;

    recordingFrom = System.currentTimeMillis();
    return true;
  }

  public boolean stopRecording() {
    if (!isRecording())
      return false;

    recordingFrom = -1;
    return true;
  }

  public boolean isRecording() {
    return recordingFrom > -1;
  }

  public void updateCounter(final String iStatName, final String iDescription, final long iPlus) {
    updateCounter(iStatName, iDescription, iPlus, iStatName);
  }

  @Override
  public String getName() {
    return "profiler";
  }

  @Override
  public void startup() {
    startRecording();
  }

  @Override
  public String dump() {
    return dumpEnvironment(OGlobalConfiguration.PROFILER_AUTODUMP_TYPE.getValueAsString());
  }

  @Override
  public void dump(final PrintStream out) {
    out.println(dumpEnvironment(OGlobalConfiguration.PROFILER_AUTODUMP_TYPE.getValueAsString()));
  }

  @Override
  public String dump(final String type) {
    return dumpEnvironment(type);
  }

  @Override
  public String dumpCounters() {
    return null;
  }

  @Override
  public OProfilerEntry getChrono(String string) {
    return null;
  }

  @Override
  public long startChrono() {
    return 0;
  }

  @Override
  public long stopChrono(String iName, String iDescription, long iStartTime) {
    return 0;
  }

  @Override
  public long stopChrono(String iName, String iDescription, long iStartTime, String iDictionary) {
    return 0;
  }

  @Override
  public String dumpChronos() {
    return null;
  }

  @Override
  public String[] getCountersAsString() {
    return null;
  }

  @Override
  public String[] getChronosAsString() {
    return null;
  }

  @Override
  public Date getLastReset() {
    return null;
  }

  @Override
  public void setAutoDump(final int iSeconds) {
    if (autoDumpTask != null) {
      // CANCEL ANY PREVIOUS RUNNING TASK
      autoDumpTask.cancel();
      autoDumpTask = null;
    }

    if (iSeconds > 0) {
      OLogManager.instance().info(this, "Enabled auto dump of profiler every %d second(s)", iSeconds);

      final int ms = iSeconds * 1000;

      autoDumpTask = Orient.instance().scheduleTask(() -> {
        final StringBuilder output = new StringBuilder();

        final String dumpType = OGlobalConfiguration.PROFILER_AUTODUMP_TYPE.getValueAsString();

        output.append(
            "\n*******************************************************************************************************************************************");
        output.append("\nPROFILER AUTO DUMP '" + dumpType + "' OUTPUT (to disabled it set 'profiler.autoDump.interval' = 0):\n");
        output.append(dump(dumpType));
        output.append(
            "\n*******************************************************************************************************************************************");

        OLogManager.instance().info(null, output.toString());
      }, ms, ms);
    } else
      OLogManager.instance().info(this, "Auto dump of profiler disabled", iSeconds);

  }

  @Override
  public String metadataToJSON() {
    return null;
  }

  @Override
  public Map<String, OPair<String, METRIC_TYPE>> getMetadata() {
    final Map<String, OPair<String, METRIC_TYPE>> metadata = new HashMap<String, OPair<String, METRIC_TYPE>>();
    for (Entry<String, String> entry : dictionary.entrySet())
      metadata.put(entry.getKey(), new OPair<String, METRIC_TYPE>(entry.getValue(), types.get(entry.getKey())));
    return metadata;
  }

  @Override
  public String[] getHookAsString() {
    final List<String> keys = new ArrayList<String>(hooks.keySet());
    final String[] array = new String[keys.size()];
    return keys.toArray(array);
  }

  public void registerHookValue(final String iName, final String iDescription, final METRIC_TYPE iType,
      final OProfilerHookValue iHookValue) {
    registerHookValue(iName, iDescription, iType, iHookValue, iName);
  }

  public void registerHookValue(final String iName, final String iDescription, final METRIC_TYPE iType,
      final OProfilerHookValue iHookValue, final String iMetadataName) {
    if (iName != null) {
      unregisterHookValue(iName);
      updateMetadata(iMetadataName, iDescription, iType);
      hooks.put(iName, new OProfilerHookRuntime(iHookValue, iType));
    }
  }

  @Override
  public void unregisterHookValue(final String iName) {
    if (iName != null)
      hooks.remove(iName);
  }

  @Override
  public String getSystemMetric(final String iMetricName) {
    final StringBuilder buffer = new StringBuilder("system.".length() + iMetricName.length() + 1);
    buffer.append("system.");
    buffer.append(iMetricName);
    return buffer.toString();
  }

  @Override
  public String getProcessMetric(final String iMetricName) {
    final StringBuilder buffer = new StringBuilder("process.".length() + iMetricName.length() + 1);
    buffer.append("process.");
    buffer.append(iMetricName);
    return buffer.toString();
  }

  @Override
  public String getDatabaseMetric(final String iDatabaseName, final String iMetricName) {
    final StringBuilder buffer = new StringBuilder(128);
    buffer.append("db.");
    buffer.append(iDatabaseName != null ? iDatabaseName : "*");
    buffer.append('.');
    buffer.append(iMetricName);
    return buffer.toString();
  }

  @Override
  public String toJSON(String command, final String iPar1) {
    return null;
  }

  protected void installMemoryChecker() {
    final long memoryCheckInterval = OGlobalConfiguration.PROFILER_MEMORYCHECK_INTERVAL.getValueAsLong();

    if (memoryCheckInterval > 0)
      Orient.instance().scheduleTask(new MemoryChecker(), memoryCheckInterval, memoryCheckInterval);
  }

  /**
   * Updates the metric metadata.
   */
  protected void updateMetadata(final String iName, final String iDescription, final METRIC_TYPE iType) {
    if (iDescription != null && dictionary.putIfAbsent(iName, iDescription) == null)
      types.put(iName, iType);
  }

  @Override
  public void registerListener(OProfilerListener listener) {
    listeners.add(listener);
  }

  @Override
  public void unregisterListener(OProfilerListener listener) {
    listeners.remove(listener);
  }

  @Override
  public String threadDump() {
    final StringBuilder dump = new StringBuilder();
    dump.append("THREAD DUMP\n");
    final ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
    final ThreadInfo[] threadInfos = threadMXBean.getThreadInfo(threadMXBean.getAllThreadIds(), 100);
    for (ThreadInfo threadInfo : threadInfos) {
      dump.append('"');
      dump.append(threadInfo.getThreadName());
      dump.append("\" ");
      final Thread.State state = threadInfo.getThreadState();
      dump.append("\n   java.lang.Thread.State: ");
      dump.append(state);
      final StackTraceElement[] stackTraceElements = threadInfo.getStackTrace();
      for (final StackTraceElement stackTraceElement : stackTraceElements) {
        dump.append("\n        at ");
        dump.append(stackTraceElement);
      }
      dump.append("\n\n");
    }
    return dump.toString();
  }

  @Override
  public METRIC_TYPE getType(final String k) {
    return types.get(k);
  }
}


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

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

import java.util.Arrays;
import java.util.NoSuchElementException;

/**
 * Iterator to browse multiple clusters forward and backward. Once browsed in a direction, the iterator cannot change it. This
 * iterator with "live updates" set is able to catch updates to the cluster sizes while browsing. This is the case when concurrent
 * clients/threads insert and remove item in any cluster the iterator is browsing. If the cluster are hot removed by from the
 * database the iterator could be invalid and throw exception of cluster not found.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 */
public class ORecordIteratorClusters<REC extends ORecord> extends OIdentifiableIterator<REC> {
  protected int[]   clusterIds;
  protected int     currentClusterIdx;
  protected ORecord currentRecord;
  protected ORID    beginRange;
  protected ORID    endRange;

  public ORecordIteratorClusters(final ODatabaseDocumentInternal iDatabase, final int[] iClusterIds) {
    this(iDatabase, iClusterIds, OStorage.LOCKING_STRATEGY.NONE);
  }

  @Deprecated
  public ORecordIteratorClusters(final ODatabaseDocumentInternal iDatabase, final int[] iClusterIds, final OStorage.LOCKING_STRATEGY iLockingStrategy) {
    super(iDatabase, iLockingStrategy);

    checkForSystemClusters(iDatabase, iClusterIds);

    clusterIds = iClusterIds;

    Arrays.sort(clusterIds);

    config();
  }

  @Deprecated
  protected ORecordIteratorClusters(final ODatabaseDocumentInternal iDatabase, final OStorage.LOCKING_STRATEGY iLockingStrategy) {
    super(iDatabase, iLockingStrategy);
  }

  public ORecordIteratorClusters<REC> setRange(final ORID iBegin, final ORID iEnd) {
    final ORID oldBegin = beginRange;
    final ORID oldEnd = endRange;

    beginRange = iBegin;
    endRange = iEnd;

    if ((oldBegin == null ? iBegin == null : oldBegin.equals(iBegin)) && (oldEnd == null ? iEnd == null : oldEnd.equals(iEnd)))
      return this;

    if (currentRecord != null && outsideOfTheRange(currentRecord.getIdentity())) {
      currentRecord = null;
    }

    begin();
    return this;
  }

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

    if (currentRecord != null)
      return true;

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

    if (browsedRecords >= totalAvailableRecords)
      return false;

    if (liveUpdated)
      updateClusterRange();

    ORecord record = getRecord();

    // ITERATE UNTIL THE PREVIOUS GOOD RECORD
    while (currentClusterIdx > -1) {
      while (prevPosition()) {
        currentRecord = readCurrentRecord(record, 0);

        if (currentRecord != null)
          if (include(currentRecord))
            // FOUND
            return true;
      }

      // CLUSTER EXHAUSTED, TRY WITH THE PREVIOUS ONE
      currentClusterIdx--;

      if (currentClusterIdx < 0)
        break;

      updateClusterRange();
    }

    if (txEntries != null && txEntries.size() - (currentTxEntryPosition + 1) > 0)
      return true;

    currentRecord = null;
    return false;
  }

  public boolean hasNext() {
    checkDirection(true);

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

    if (currentRecord != null)
      return true;

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

    if (browsedRecords >= totalAvailableRecords)
      return false;

    // COMPUTE THE NUMBER OF RECORDS TO BROWSE
    if (liveUpdated)
      updateClusterRange();

    ORecord record = getRecord();

    // ITERATE UNTIL THE NEXT GOOD RECORD
    while (currentClusterIdx < clusterIds.length) {
      while (nextPosition()) {
        if (outsideOfTheRange(current))
          continue;

        try {
          currentRecord = readCurrentRecord(record, 0);
        } catch (Exception e) {
          if ((e instanceof RuntimeException) && (e instanceof OHighLevelException))
            throw (RuntimeException) e;

          OLogManager.instance().error(this, "Error during read of record", e);

          currentRecord = null;
        }

        if (currentRecord != null)
          if (include(currentRecord))
            // FOUND
            return true;
      }

      // CLUSTER EXHAUSTED, TRY WITH THE NEXT ONE
      currentClusterIdx++;
      if (currentClusterIdx >= clusterIds.length)
        break;

      updateClusterRange();
    }

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

    currentRecord = null;
    return false;
  }

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

    if (currentRecord != null)
      try {
        // RETURN LAST LOADED RECORD
        return (REC) currentRecord;
      } finally {
        currentRecord = null;
      }

    ORecord record;

    // MOVE FORWARD IN THE CURRENT CLUSTER
    while (hasNext()) {
      if (currentRecord != null)
        try {
          // RETURN LAST LOADED RECORD
          return (REC) currentRecord;
        } finally {
          currentRecord = null;
        }

      record = getTransactionEntry();
      if (record == null)
        record = readCurrentRecord(null, +1);

      if (record != null)
        // FOUND
        if (include(record))
          return (REC) record;
    }

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

    throw new NoSuchElementException(
        "Direction: forward, last position was: " + current + ", range: " + beginRange + "-" + endRange);
  }

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

    if (currentRecord != null)
      try {
        // RETURN LAST LOADED RECORD
        return (REC) currentRecord;
      } finally {
        currentRecord = null;
      }

    ORecord record = getRecord();

    // MOVE BACKWARD IN THE CURRENT CLUSTER
    while (hasPrevious()) {
      if (currentRecord != null)
        try {
          // RETURN LAST LOADED RECORD
          return (REC) currentRecord;
        } finally {
          currentRecord = null;
        }

      record = getTransactionEntry();
      if (record == null)
        record = readCurrentRecord(null, -1);

      if (record != null)
        // FOUND
        if (include(record))
          return (REC) record;
    }

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

    return null;
  }

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

    browsedRecords = 0;
    currentClusterIdx = 0;
    current.setClusterId(clusterIds[currentClusterIdx]);

    updateClusterRange();

    resetCurrentPosition();
    nextPosition();

    final ORecord record = getRecord();
    currentRecord = readCurrentRecord(record, 0);

    if (currentRecord != null && !include(currentRecord)) {
      currentRecord = null;
      hasNext();
    }

    return this;
  }

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

    browsedRecords = 0;
    currentClusterIdx = clusterIds.length - 1;

    updateClusterRange();

    current.setClusterId(clusterIds[currentClusterIdx]);

    resetCurrentPosition();
    prevPosition();

    final ORecord record = getRecord();
    currentRecord = readCurrentRecord(record, 0);

    if (currentRecord != null && !include(currentRecord)) {
      currentRecord = null;
      hasPrevious();
    }

    return this;
  }

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

    if (iLiveUpdated) {
      firstClusterEntry = 0;
      lastClusterEntry = Long.MAX_VALUE;
    } else {
      updateClusterRange();
    }

    return this;
  }

  public ORID getBeginRange() {
    return beginRange;
  }

  public ORID getEndRange() {
    return endRange;
  }

  public int[] getClusterIds() {
    return clusterIds;
  }

  @Override
  public String toString() {
    return String
        .format("ORecordIteratorCluster.clusters(%s).currentRecord(%s).range(%s-%s)", Arrays.toString(clusterIds), currentRecord,
            beginRange, endRange);
  }

  protected boolean include(final ORecord iRecord) {
    return true;
  }

  protected void updateClusterRange() {
    if (clusterIds.length == 0)
      return;

    // ADJUST IDX CHECKING BOUNDARIES
    if (currentClusterIdx >= clusterIds.length)
      currentClusterIdx = clusterIds.length - 1;
    else if (currentClusterIdx < 0)
      currentClusterIdx = 0;

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

    if (beginRange != null && beginRange.getClusterId() == current.getClusterId() && beginRange.getClusterPosition() > range[0])
      firstClusterEntry = beginRange.getClusterPosition();
    else
      firstClusterEntry = range[0];

    if (endRange != null && endRange.getClusterId() == current.getClusterId() && endRange.getClusterPosition() < range[1])
      lastClusterEntry = endRange.getClusterPosition();
    else
      lastClusterEntry = range[1];

    resetCurrentPosition();
  }

  protected void config() {
    if (clusterIds.length == 0)
      return;

    currentClusterIdx = 0; // START FROM THE FIRST CLUSTER

    updateClusterRange();

    totalAvailableRecords = database.countClusterElements(clusterIds);

    txEntries = database.getTransaction().getNewRecordEntriesByClusterIds(clusterIds);

    if (txEntries != null)
      // ADJUST TOTAL ELEMENT BASED ON CURRENT TRANSACTION'S ENTRIES
      for (ORecordOperation entry : txEntries) {
        if (!entry.getRecord().getIdentity().isPersistent() && entry.type != ORecordOperation.DELETED)
          totalAvailableRecords++;
        else if (entry.type == ORecordOperation.DELETED)
          totalAvailableRecords--;
      }

    begin();
  }

  private boolean outsideOfTheRange(ORID orid) {
    if (beginRange != null && orid.compareTo(beginRange) < 0)
      return true;

    if (endRange != null && orid.compareTo(endRange) > 0)
      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.graph.sql;

import com.orientechnologies.common.util.OPair;
import com.orientechnologies.orient.core.command.OCommandDistributedReplicateRequest;
import com.orientechnologies.orient.core.command.OCommandRequest;
import com.orientechnologies.orient.core.command.OCommandRequestText;
import com.orientechnologies.orient.core.db.document.ODatabaseDocument;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.exception.OCommandExecutionException;
import com.orientechnologies.orient.core.metadata.OMetadataInternal;
import com.orientechnologies.orient.core.metadata.schema.OClass;
import com.orientechnologies.orient.core.sql.*;
import com.orientechnologies.orient.core.sql.filter.OSQLFilterItem;
import com.orientechnologies.orient.core.sql.functions.OSQLFunctionRuntime;
import com.tinkerpop.blueprints.impls.orient.OrientBaseGraph;
import com.tinkerpop.blueprints.impls.orient.OrientEdge;
import com.tinkerpop.blueprints.impls.orient.OrientEdgeType;
import com.tinkerpop.blueprints.impls.orient.OrientVertex;

import java.util.*;

/**
 * SQL CREATE EDGE command.
 *
 * @author Luca Garulli (l.garulli--(at)--orientdb.com)
 */
public class OCommandExecutorSQLCreateEdge extends OCommandExecutorSQLSetAware implements OCommandDistributedReplicateRequest {
  public static final String  NAME          = "CREATE EDGE";
  private static final String KEYWORD_BATCH = "BATCH";

  private String                      from;
  private String                      to;
  private OClass                      clazz;
  private String                      edgeLabel;
  private String                      clusterName;
  private List<OPair<String, Object>> fields;
  private int                         batch = 100;

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

    String queryText = textRequest.getText();
    String originalQuery = queryText;
    try {
      // System.out.println("NEW PARSER FROM: " + queryText);
      queryText = preParse(queryText, iRequest);
      // System.out.println("NEW PARSER TO: " + queryText);
      textRequest.setText(queryText);

      final ODatabaseDocument database = getDatabase();

      init((OCommandRequestText) iRequest);

      parserRequiredKeyword("CREATE");
      parserRequiredKeyword("EDGE");

      String className = null;

      String tempLower = parseOptionalWord(false);
      String temp = tempLower == null ? null : tempLower.toUpperCase(Locale.ENGLISH);

      while (temp != null) {
        if (temp.equals("CLUSTER")) {
          clusterName = parserRequiredWord(false);

        } else if (temp.equals(KEYWORD_FROM)) {
          from = parserRequiredWord(false, "Syntax error", " =><,\r\n");

        } else if (temp.equals("TO")) {
          to = parserRequiredWord(false, "Syntax error", " =><,\r\n");

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

        } else if (temp.equals(KEYWORD_CONTENT)) {
          parseContent();

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

        } else if (className == null && temp.length() > 0) {
          className = tempLower;
          OrientBaseGraph graph = OrientBaseGraph.getActiveGraph();
          if (graph != null && graph.isUseClassForEdgeLabel()) {
            clazz = ((OMetadataInternal) database.getMetadata()).getImmutableSchemaSnapshot().getClass(temp);
          } else {
            clazz = ((OMetadataInternal) database.getMetadata()).getImmutableSchemaSnapshot().getClass("E");
          }
        }

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

      if (className == null) {
        // ASSIGN DEFAULT CLASS
        className = OrientEdgeType.CLASS_NAME;
        clazz = ((OMetadataInternal) database.getMetadata()).getImmutableSchemaSnapshot().getClass(className);
      }

      // GET/CHECK CLASS NAME
      if (clazz == null)
        throw new OCommandSQLParsingException("Class '" + className + "' was not found");

      edgeLabel = className;
    } finally {
      textRequest.setText(originalQuery);
    }
    return this;
  }

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

    return OGraphCommandExecutorSQLFactory.runInConfiguredTxMode(new OGraphCommandExecutorSQLFactory.GraphCallBack<List<Object>>() {
      @Override
      public List<Object> call(OrientBaseGraph graph) {

        final Set<OIdentifiable> fromIds = OSQLEngine.getInstance().parseRIDTarget(graph.getRawGraph(), from, context, iArgs);
        final Set<OIdentifiable> toIds = OSQLEngine.getInstance().parseRIDTarget(graph.getRawGraph(), to, context, iArgs);

        // CREATE EDGES
        final List<Object> edges = new ArrayList<Object>();
        for (OIdentifiable from : fromIds) {
          final OrientVertex fromVertex = graph.getVertex(from);
          if (fromVertex == null)
            throw new OCommandExecutionException("Source vertex '" + from + "' not exists");

          for (OIdentifiable to : toIds) {
            final OrientVertex toVertex;
            if (from.equals(to)) {
              toVertex = fromVertex;
            } else {
              toVertex = graph.getVertex(to);
            }

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

            OrientEdge edge = null;

            if (content != null) {
              if (fields != null)
                // MERGE CONTENT WITH FIELDS
                fields.addAll(OPair.convertFromMap(content.toMap()));
              else
                fields = OPair.convertFromMap(content.toMap());
            }

            edge = fromVertex.addEdge(null, toVertex, edgeLabel, clusterName, fields);

            if (fields != null && !fields.isEmpty()) {
              if (edge.isLightweight())
                edge.convertToDocument();

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

            edge.save(clusterName);

            edges.add(edge);

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

        if (edges.isEmpty()) {
          if (fromIds.isEmpty())
            throw new OCommandExecutionException("No edge has been created because no source vertices");
          else if (toIds.isEmpty())
            throw new OCommandExecutionException("No edge has been created because no target vertices");
          throw new OCommandExecutionException("No edge has been created between " + fromIds + " and " + toIds);
        }

        return edges;
      }
    });
  }

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

  @Override
  public Set<String> getInvolvedClusters() {
    if (clazz != null)
      return Collections.singleton(getDatabase().getClusterNameById(clazz.getClusterSelection().getCluster(clazz, null)));
    else if (clusterName != null)
      return getInvolvedClustersOfClusters(Collections.singleton(clusterName));

    return Collections.EMPTY_SET;
  }

  @Override
  public OCommandDistributedReplicateRequest.DISTRIBUTED_EXECUTION_MODE getDistributedExecutionMode() {
    return DISTRIBUTED_EXECUTION_MODE.LOCAL;
  }

  @Override
  public String getSyntax() {
    return "CREATE EDGE [<class>] [CLUSTER <cluster>] FROM <rid>|(<query>|[<rid>]*) TO <rid>|(<query>|[<rid>]*) [SET <field> = <expression>[,]*]|CONTENT {<JSON>} [RETRY <retry> [WAIT <pauseBetweenRetriesInMs]] [BATCH <batch-size>]";
  }
}


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

import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.text.ParseException;
import java.util.Arrays;

public class OJSONReader {
  public static final char   NEW_LINE          = '\n';
  public static final char[] DEFAULT_JUMP      = new char[] { ' ', '\r', '\n', '\t' };
  public static final char[] DEFAULT_SKIP      = new char[] { '\r', '\n', '\t' };
  public static final char[] BEGIN_OBJECT      = new char[] { '{' };
  public static final char[] END_OBJECT        = new char[] { '}' };
  public static final char[] FIELD_ASSIGNMENT  = new char[] { ':' };
  public static final char[] BEGIN_STRING      = new char[] { '"' };
  public static final char[] COMMA_SEPARATOR   = new char[] { ',' };
  public static final char[] NEXT_IN_OBJECT    = new char[] { ',', '}' };
  public static final char[] NEXT_IN_ARRAY     = new char[] { ',', ']' };
  public static final char[] NEXT_OBJ_IN_ARRAY = new char[] { '{', ']' };
  public static final char[] ANY_NUMBER        = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
  public static final char[] BEGIN_COLLECTION  = new char[] { '[' };
  public static final char[] END_COLLECTION    = new char[] { ']' };
  private BufferedReader     in;
  private int                cursor            = 0;
  private int                lineNumber        = 0;
  private int                columnNumber      = 0;
  private StringBuilder      buffer            = new StringBuilder(16384);                                       // 16KB
  private String             value;
  private char               c;
  private char               lastCharacter;
  private Character          missedChar;

  public OJSONReader(Reader iIn) {
    this.in = new BufferedReader(iIn);
  }

  public int getCursor() {
    return cursor;
  }

  public OJSONReader checkContent(final String iExpected) throws ParseException {
    if (!value.equals(iExpected))
      throw new ParseException("Expected content is " + iExpected + " but found " + value, cursor);
    return this;
  }

  public boolean isContent(final String iExpected) {
    return value.equals(iExpected);
  }

  public int readInteger(final char[] iUntil) throws IOException, ParseException {
    return readNumber(iUntil, false);
  }

  public int readNumber(final char[] iUntil, final boolean iInclude) throws IOException, ParseException {
    if (readNext(iUntil, iInclude) == null)
      throw new ParseException("Expected integer", cursor);

    return Integer.parseInt(value.trim());
  }

  public String readString(final char[] iUntil) throws IOException, ParseException {
    return readString(iUntil, false);
  }

  public String readString(final char[] iUntil, final boolean iInclude) throws IOException, ParseException {
    return readString(iUntil, iInclude, DEFAULT_JUMP, null);
  }

  public String readString(final char[] iUntil, final boolean iInclude, final char[] iJumpChars, final char[] iSkipChars)
      throws IOException, ParseException {
    if (readNext(iUntil, iInclude, iJumpChars, iSkipChars) == null)
      return null;

    if (!iInclude && value.startsWith("\"")) {
      return value.substring(1, value.lastIndexOf("\""));
    }

    return value;
  }

  public String readString(final char[] iUntil, final boolean iInclude, final char[] iJumpChars, final char[] iSkipChars, boolean preserveQuotes)
      throws IOException, ParseException {
    if (readNext(iUntil, iInclude, iJumpChars, iSkipChars, preserveQuotes) == null)
      return null;

    if (!iInclude && value.startsWith("\"")) {
      return value.substring(1, value.lastIndexOf("\""));
    }

    return value;
  }

  public boolean readBoolean(final char[] nextInObject) throws IOException, ParseException {
    return Boolean.parseBoolean(readString(nextInObject, false, DEFAULT_JUMP, DEFAULT_JUMP));
  }

  public OJSONReader readNext(final char[] iUntil) throws IOException, ParseException {
    readNext(iUntil, false);
    return this;
  }

  public OJSONReader readNext(final char[] iUntil, final boolean iInclude) throws IOException, ParseException {
    readNext(iUntil, iInclude, DEFAULT_JUMP, null);
    return this;
  }
  public OJSONReader readNext(final char[] iUntil, final boolean iInclude, final char[] iJumpChars, final char[] iSkipChars)
      throws IOException, ParseException {
    readNext(iUntil, iInclude, iJumpChars, iSkipChars, true);
    return this;
  }

  public OJSONReader readNext(final char[] iUntil, final boolean iInclude, final char[] iJumpChars, final char[] iSkipChars, boolean preserveQuotes)
      throws IOException, ParseException {
    if (!in.ready())
      return this;

    jump(iJumpChars);

    if (!in.ready())
      return this;

    // READ WHILE THERE IS SOMETHING OF AVAILABLE
    int openBrackets = 0;
    char beginStringChar = ' ';
    boolean encodeMode = false;
    boolean found;
    do {
      found = false;
      if (beginStringChar == ' ') {
        // NO INSIDE A STRING
        if (openBrackets == 0) {
          // FIND FOR SEPARATOR
          for (char u : iUntil) {
            if (u == c) {
              found = true;
              break;
            }
          }
        }

        if (c == '\'' || c == '"' && !encodeMode)
          // BEGIN OF STRING
          beginStringChar = c;
        else if (c == '{')
          // OPEN EMBEDDED
          openBrackets++;
        else if (c == '}' && openBrackets > 0)
          // CLOSE EMBEDDED
          openBrackets--;

        if (!found && openBrackets == 0) {
          // FIND FOR SEPARATOR
          for (char u : iUntil) {
            if (u == c) {
              found = true;
              break;
            }
          }
        }
      } else if (beginStringChar == c && !encodeMode)
        // END OF STRING
        beginStringChar = ' ';

      if (c == '\\' && !encodeMode)
        encodeMode = true;
      else
        encodeMode = false;

      if (!found) {
        final int read = nextChar();
        if (read == -1)
          break;

        // APPEND IT
        c = (char) read;

        boolean skip = false;
        if (iSkipChars != null)
          for (char j : iSkipChars) {
            if (j == c) {
              skip = true;
              break;
            }
          }

        if (!skip && (preserveQuotes || !encodeMode)) {
          lastCharacter = c;
          buffer.append(c);
        }
      }

    } while (!found && in.ready());

    if (buffer.length() == 0)
      throw new ParseException("Expected characters '" + Arrays.toString(iUntil) + "' not found", cursor);

    if (!iInclude)
      buffer.setLength(buffer.length() - 1);

    value = buffer.toString();
    return this;
  }

  public int jump(final char[] iJumpChars) throws IOException, ParseException {
    buffer.setLength(0);

    if (!in.ready())
      return 0;

    // READ WHILE THERE IS SOMETHING OF AVAILABLE
    boolean go = true;
    while (go && in.ready()) {
      int read = nextChar();
      if (read == -1)
        return -1;

      go = false;
      for (char j : iJumpChars) {
        if (j == c) {
          go = true;
          break;
        }
      }
    }

    if (!go) {
      lastCharacter = c;
      buffer.append(c);
    }

    return c;
  }

  /**
   * Returns the next character from the input stream. Handles Unicode decoding.
   */
  public int nextChar() throws IOException {
    if (missedChar != null) {
      // RETURNS THE PREVIOUS PARSED CHAR
      c = missedChar.charValue();
      missedChar = null;

    } else {
      int read = in.read();
      if (read == -1)
        return -1;

      c = (char) read;

      if (c == '\\') {
        read = in.read();
        if (read == -1)
          return -1;

        char c2 = (char) read;
        if (c2 == 'u') {
          // DECODE UNICODE CHAR
          final StringBuilder buff = new StringBuilder(8);
          for (int i = 0; i < 4; ++i) {
            read = in.read();
            if (read == -1)
              return -1;

            buff.append((char) read);
          }

          cursor += 6;

          return (char) Integer.parseInt(buff.toString(), 16);
        } else {
          // REMEMBER THE CURRENT CHAR TO RETURN NEXT TIME
          missedChar = c2;
        }
      }
    }

    cursor++;

    if (c == NEW_LINE) {
      ++lineNumber;
      columnNumber = 0;
    } else
      ++columnNumber;

    return (char) c;
  }

  public char lastChar() {
    return lastCharacter;
  }

  public String getValue() {
    return value;
  }

  public int getLineNumber() {
    return lineNumber;
  }

  public int getColumnNumber() {
    return columnNumber;
  }

  public boolean hasNext() throws IOException {
    return in.ready();
  }
}


/*
  *
  *  *  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.OCommandRequest;
import com.orientechnologies.orient.core.command.OCommandRequestText;
import com.orientechnologies.orient.core.db.record.OIdentifiable;
import com.orientechnologies.orient.core.exception.OCommandExecutionException;
import com.orientechnologies.orient.core.id.ORID;
import com.orientechnologies.orient.core.id.ORecordId;
import com.orientechnologies.orient.core.serialization.serializer.OStringSerializerHelper;

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

/**
 * FIND REFERENCES command: Finds references to records in all or part of database
 *
 * @author Luca Molino
 */
@SuppressWarnings("unchecked")
public class OCommandExecutorSQLFindReferences extends OCommandExecutorSQLEarlyResultsetAbstract {
  public static final String KEYWORD_FIND       = "FIND";
  public static final String KEYWORD_REFERENCES = "REFERENCES";

  private Set<ORID> recordIds = new HashSet<ORID>();
  private String        classList;
  private StringBuilder subQuery;

  public OCommandExecutorSQLFindReferences parse(final OCommandRequest iRequest) {
    final OCommandRequestText textRequest = (OCommandRequestText) iRequest;
    String queryText = textRequest.getText();
    String originalQuery = queryText;
    try {
      // System.out.println("NEW PARSER FROM: " + queryText);
      queryText = preParse(queryText, iRequest);
      // System.out.println("NEW PARSER TO: " + queryText);
      textRequest.setText(queryText);

      init((OCommandRequestText) iRequest);

      parserRequiredKeyword(KEYWORD_FIND);
      parserRequiredKeyword(KEYWORD_REFERENCES);
      final String target = parserRequiredWord(true, "Expected <target>", " =><,\r\n");

      if (target.charAt(0) == '(') {
        subQuery = new StringBuilder();
        parserSetCurrentPosition(OStringSerializerHelper.getEmbedded(parserText, parserGetPreviousPosition(), -1, subQuery));
      } else {
        try {
          final ORecordId rid = new ORecordId(target);
          if (!rid.isValid())
            throwParsingException("Record ID " + target + " is not valid");
          recordIds.add(rid);

        } catch (IllegalArgumentException iae) {
          throw OException
              .wrapException(new OCommandSQLParsingException("Error reading record Id", parserText, parserGetPreviousPosition()),
                  iae);
        }
      }

      parserSkipWhiteSpaces();
      classList = parserOptionalWord(true);
      if (classList != null) {
        classList = parserTextUpperCase.substring(parserGetPreviousPosition());

        if (!classList.startsWith("[") || !classList.endsWith("]")) {
          throwParsingException("Class list must be contained in []");
        }
        // GET THE CLUSTER LIST TO SEARCH, IF NULL WILL SEARCH ENTIRE DATABASE
        classList = classList.substring(1, classList.length() - 1);
      }

      return this;
    } finally {
      textRequest.setText(originalQuery);
    }
  }

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

    if (subQuery != null) {
      final List<OIdentifiable> result = new OCommandSQL(subQuery.toString()).execute();
      for (OIdentifiable id : result)
        recordIds.add(id.getIdentity());
    }

    return OFindReferenceHelper.findReferences(recordIds, classList);
  }

  @Override
  public String getSyntax() {
    return "FIND REFERENCES <rid|<sub-query>> [class-list]";
  }

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


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

import com.orientechnologies.orient.client.remote.OBinaryRequest;
import com.orientechnologies.orient.client.remote.OBinaryResponse;
import com.orientechnologies.orient.client.remote.message.*;
import com.orientechnologies.orient.core.exception.ODatabaseException;
import com.orientechnologies.orient.enterprise.channel.binary.OChannelBinaryProtocol;

import java.util.function.Function;

/**
 * Created by Enrico Risa on 05/04/17.
 */
public class ONetworkBinaryProtocolFactory {

  private static Function<Integer, OBinaryRequest<? extends OBinaryResponse>> defaultProtocol = ONetworkBinaryProtocolFactory::createRequest;

  public static Function<Integer, OBinaryRequest<? extends OBinaryResponse>> defaultProtocol() {
    return defaultProtocol;
  }

  public static Function<Integer, OBinaryRequest<? extends OBinaryResponse>> matchProtocol(short protocolVersion) {
    switch (protocolVersion) {
    case 37:
      return ONetworkBinaryProtocolFactory::createRequest37;
    default:
      return ONetworkBinaryProtocolFactory::createRequest;
    }
  }

  /**
   * Legacy Protocol < 37
   *
   * @param requestType
   *
   * @return
   */
  private static OBinaryRequest<? extends OBinaryResponse> createRequest(int requestType) {
    switch (requestType) {
    case OChannelBinaryProtocol.REQUEST_DB_OPEN:
      return new OOpenRequest();

    case OChannelBinaryProtocol.REQUEST_CONNECT:
      return new OConnectRequest();

    case OChannelBinaryProtocol.REQUEST_DB_REOPEN:
      return new OReopenRequest();

    case OChannelBinaryProtocol.REQUEST_SHUTDOWN:
      return new OShutdownRequest();

    case OChannelBinaryProtocol.REQUEST_DB_LIST:
      return new OListDatabasesRequest();

    case OChannelBinaryProtocol.REQUEST_SERVER_INFO:
      return new OServerInfoRequest();

    case OChannelBinaryProtocol.REQUEST_DB_RELOAD:
      return new OReloadRequest();

    case OChannelBinaryProtocol.REQUEST_DB_CREATE:
      return new OCreateDatabaseRequest();

    case OChannelBinaryProtocol.REQUEST_DB_CLOSE:
      return new OCloseRequest();

    case OChannelBinaryProtocol.REQUEST_DB_EXIST:
      return new OExistsDatabaseRequest();

    case OChannelBinaryProtocol.REQUEST_DB_DROP:
      return new ODropDatabaseRequest();

    case OChannelBinaryProtocol.REQUEST_DB_SIZE:
      return new OGetSizeRequest();

    case OChannelBinaryProtocol.REQUEST_DB_COUNTRECORDS:
      return new OCountRecordsRequest();

    case OChannelBinaryProtocol.REQUEST_CLUSTER:
      return new ODistributedStatusRequest();

    case OChannelBinaryProtocol.REQUEST_CLUSTER_COUNT:
      return new OCountRequest();

    case OChannelBinaryProtocol.REQUEST_CLUSTER_DATARANGE:
      return new OGetClusterDataRangeRequest();

    case OChannelBinaryProtocol.REQUEST_CLUSTER_ADD:
      return new OAddClusterRequest();

    case OChannelBinaryProtocol.REQUEST_CLUSTER_DROP:
      return new ODropClusterRequest();

    case OChannelBinaryProtocol.REQUEST_RECORD_METADATA:
      return new OGetRecordMetadataRequest();

    case OChannelBinaryProtocol.REQUEST_RECORD_LOAD:
      return new OReadRecordRequest();

    case OChannelBinaryProtocol.REQUEST_RECORD_LOAD_IF_VERSION_NOT_LATEST:
      return new OReadRecordIfVersionIsNotLatestRequest();

    case OChannelBinaryProtocol.REQUEST_RECORD_CREATE:
      return new OCreateRecordRequest();

    case OChannelBinaryProtocol.REQUEST_RECORD_UPDATE:
      return new OUpdateRecordRequest();

    case OChannelBinaryProtocol.REQUEST_RECORD_DELETE:
      return new ODeleteRecordRequest();

    case OChannelBinaryProtocol.REQUEST_RECORD_HIDE:
      return new OHideRecordRequest();

    case OChannelBinaryProtocol.REQUEST_POSITIONS_HIGHER:
      return new OHigherPhysicalPositionsRequest();

    case OChannelBinaryProtocol.REQUEST_POSITIONS_CEILING:
      return new OCeilingPhysicalPositionsRequest();

    case OChannelBinaryProtocol.REQUEST_POSITIONS_LOWER:
      return new OLowerPhysicalPositionsRequest();

    case OChannelBinaryProtocol.REQUEST_POSITIONS_FLOOR:
      return new OFloorPhysicalPositionsRequest();

    case OChannelBinaryProtocol.REQUEST_COMMAND:
      return new OCommandRequest();

    case OChannelBinaryProtocol.REQUEST_QUERY:
      return new OQueryRequest();

    case OChannelBinaryProtocol.REQUEST_CLOSE_QUERY:
      return new OCloseQueryRequest();

    case OChannelBinaryProtocol.REQUEST_QUERY_NEXT_PAGE:
      return new OQueryNextPageRequest();

    case OChannelBinaryProtocol.REQUEST_TX_COMMIT:
      return new OCommitRequest();

    case OChannelBinaryProtocol.REQUEST_CONFIG_GET:
      return new OGetGlobalConfigurationRequest();

    case OChannelBinaryProtocol.REQUEST_CONFIG_SET:
      return new OSetGlobalConfigurationRequest();

    case OChannelBinaryProtocol.REQUEST_CONFIG_LIST:
      return new OListGlobalConfigurationsRequest();

    case OChannelBinaryProtocol.REQUEST_DB_FREEZE:
      return new OFreezeDatabaseRequest();

    case OChannelBinaryProtocol.REQUEST_DB_RELEASE:
      return new OReleaseDatabaseRequest();

    case OChannelBinaryProtocol.REQUEST_RECORD_CLEAN_OUT:
      return new OCleanOutRecordRequest();

    case OChannelBinaryProtocol.REQUEST_CREATE_SBTREE_BONSAI:
      return new OSBTCreateTreeRequest();

    case OChannelBinaryProtocol.REQUEST_SBTREE_BONSAI_GET:
      return new OSBTGetRequest();

    case OChannelBinaryProtocol.REQUEST_SBTREE_BONSAI_FIRST_KEY:
      return new OSBTFirstKeyRequest();

    case OChannelBinaryProtocol.REQUEST_SBTREE_BONSAI_GET_ENTRIES_MAJOR:
      return new OSBTFetchEntriesMajorRequest<>();

    case OChannelBinaryProtocol.REQUEST_RIDBAG_GET_SIZE:
      return new OSBTGetRealBagSizeRequest();

    case OChannelBinaryProtocol.REQUEST_INCREMENTAL_BACKUP:
      return new OIncrementalBackupRequest();

    case OChannelBinaryProtocol.REQUEST_DB_IMPORT:
      return new OImportRequest();
    case OChannelBinaryProtocol.DISTRIBUTED_CONNECT:
      return new ODistributedConnectRequest();
    default:
      throw new ODatabaseException("binary protocol command with code: " + requestType);
    }
  }

  /**
   * Protocol 37
   *
   * @param requestType
   *
   * @return
   */
  public static OBinaryRequest<? extends OBinaryResponse> createRequest37(int requestType) {
    switch (requestType) {

    case OChannelBinaryProtocol.SUBSCRIBE_PUSH:
      return new OSubscribeRequest();

    case OChannelBinaryProtocol.EXPERIMENTAL:
      return new OExperimentalRequest();

    case OChannelBinaryProtocol.UNSUBSCRIBE_PUSH:
      return new OUnsubscribeRequest();

    case OChannelBinaryProtocol.REQUEST_TX_FETCH:
      return new OFetchTransactionRequest();

    case OChannelBinaryProtocol.REQUEST_TX_REBEGIN:
      return new ORebeginTransactionRequest();

    case OChannelBinaryProtocol.REQUEST_TX_BEGIN:
      return new OBeginTransactionRequest();

    case OChannelBinaryProtocol.REQUEST_TX_COMMIT:
      return new OCommit37Request();

    case OChannelBinaryProtocol.REQUEST_TX_ROLLBACK:
      return new ORollbackTransactionRequest();

    case OChannelBinaryProtocol.REQUEST_BATCH_OPERATIONS:
      return new OBatchOperationsRequest();

    case OChannelBinaryProtocol.REQUEST_DB_OPEN:
      return new OOpen37Request();

    case OChannelBinaryProtocol.REQUEST_CONNECT:
      return new OConnect37Request();

    case OChannelBinaryProtocol.REQUEST_DB_REOPEN:
      return new OReopenRequest();

    case OChannelBinaryProtocol.REQUEST_SHUTDOWN:
      return new OShutdownRequest();

    case OChannelBinaryProtocol.REQUEST_DB_LIST:
      return new OListDatabasesRequest();

    case OChannelBinaryProtocol.REQUEST_SERVER_INFO:
      return new OServerInfoRequest();

    case OChannelBinaryProtocol.REQUEST_DB_RELOAD:
      return new OReloadRequest37();

    case OChannelBinaryProtocol.REQUEST_DB_CREATE:
      return new OCreateDatabaseRequest();

    case OChannelBinaryProtocol.REQUEST_DB_CLOSE:
      return new OCloseRequest();

    case OChannelBinaryProtocol.REQUEST_DB_EXIST:
      return new OExistsDatabaseRequest();

    case OChannelBinaryProtocol.REQUEST_DB_DROP:
      return new ODropDatabaseRequest();

    case OChannelBinaryProtocol.REQUEST_DB_SIZE:
      return new OGetSizeRequest();

    case OChannelBinaryProtocol.REQUEST_DB_COUNTRECORDS:
      return new OCountRecordsRequest();

    case OChannelBinaryProtocol.REQUEST_CLUSTER:
      return new ODistributedStatusRequest();

    case OChannelBinaryProtocol.REQUEST_CLUSTER_COUNT:
      return new OCountRequest();

    case OChannelBinaryProtocol.REQUEST_CLUSTER_DATARANGE:
      return new OGetClusterDataRangeRequest();

    case OChannelBinaryProtocol.REQUEST_CLUSTER_ADD:
      return new OAddClusterRequest();

    case OChannelBinaryProtocol.REQUEST_CLUSTER_DROP:
      return new ODropClusterRequest();

    case OChannelBinaryProtocol.REQUEST_RECORD_METADATA:
      return new OGetRecordMetadataRequest();

    case OChannelBinaryProtocol.REQUEST_RECORD_LOAD:
      return new OReadRecordRequest();

    case OChannelBinaryProtocol.REQUEST_RECORD_LOAD_IF_VERSION_NOT_LATEST:
      return new OReadRecordIfVersionIsNotLatestRequest();

    case OChannelBinaryProtocol.REQUEST_RECORD_CREATE:
      return new OCreateRecordRequest();

    case OChannelBinaryProtocol.REQUEST_RECORD_UPDATE:
      return new OUpdateRecordRequest();

    case OChannelBinaryProtocol.REQUEST_RECORD_DELETE:
      return new ODeleteRecordRequest();

    case OChannelBinaryProtocol.REQUEST_RECORD_HIDE:
      return new OHideRecordRequest();

    case OChannelBinaryProtocol.REQUEST_POSITIONS_HIGHER:
      return new OHigherPhysicalPositionsRequest();

    case OChannelBinaryProtocol.REQUEST_POSITIONS_CEILING:
      return new OCeilingPhysicalPositionsRequest();

    case OChannelBinaryProtocol.REQUEST_POSITIONS_LOWER:
      return new OLowerPhysicalPositionsRequest();

    case OChannelBinaryProtocol.REQUEST_POSITIONS_FLOOR:
      return new OFloorPhysicalPositionsRequest();

    case OChannelBinaryProtocol.REQUEST_COMMAND:
      return new OCommandRequest();

    case OChannelBinaryProtocol.REQUEST_QUERY:
      return new OQueryRequest();

    case OChannelBinaryProtocol.REQUEST_CLOSE_QUERY:
      return new OCloseQueryRequest();

    case OChannelBinaryProtocol.REQUEST_QUERY_NEXT_PAGE:
      return new OQueryNextPageRequest();

    case OChannelBinaryProtocol.REQUEST_CONFIG_GET:
      return new OGetGlobalConfigurationRequest();

    case OChannelBinaryProtocol.REQUEST_CONFIG_SET:
      return new OSetGlobalConfigurationRequest();

    case OChannelBinaryProtocol.REQUEST_CONFIG_LIST:
      return new OListGlobalConfigurationsRequest();

    case OChannelBinaryProtocol.REQUEST_DB_FREEZE:
      return new OFreezeDatabaseRequest();

    case OChannelBinaryProtocol.REQUEST_DB_RELEASE:
      return new OReleaseDatabaseRequest();

    case OChannelBinaryProtocol.REQUEST_RECORD_CLEAN_OUT:
      return new OCleanOutRecordRequest();

    case OChannelBinaryProtocol.REQUEST_CREATE_SBTREE_BONSAI:
      return new OSBTCreateTreeRequest();

    case OChannelBinaryProtocol.REQUEST_SBTREE_BONSAI_GET:
      return new OSBTGetRequest();

    case OChannelBinaryProtocol.REQUEST_SBTREE_BONSAI_FIRST_KEY:
      return new OSBTFirstKeyRequest();

    case OChannelBinaryProtocol.REQUEST_SBTREE_BONSAI_GET_ENTRIES_MAJOR:
      return new OSBTFetchEntriesMajorRequest<>();

    case OChannelBinaryProtocol.REQUEST_RIDBAG_GET_SIZE:
      return new OSBTGetRealBagSizeRequest();

    case OChannelBinaryProtocol.REQUEST_INCREMENTAL_BACKUP:
      return new OIncrementalBackupRequest();

    case OChannelBinaryProtocol.REQUEST_DB_IMPORT:
      return new OImportRequest();
    case OChannelBinaryProtocol.DISTRIBUTED_CONNECT:
      return new ODistributedConnectRequest();
    default:
      throw new ODatabaseException("binary protocol command with code: " + requestType + " for protocol version 37");
    }
  }
}