// Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
//  This source code is licensed under both the GPLv2 (found in the
//  COPYING file in the root directory) and Apache 2.0 License
//  (found in the LICENSE.Apache file in the root directory).

// This file is designed for caching those frequently used IDs and provide
// efficient portal (i.e, a set of static functions) to access java code
// from c++.

#ifndef JAVA_ROCKSJNI_PORTAL_H_
#define JAVA_ROCKSJNI_PORTAL_H_

#include <jni.h>

#include <algorithm>
#include <cstring>
#include <functional>
#include <iostream>
#include <iterator>
#include <limits>
#include <memory>
#include <set>
#include <string>
#include <type_traits>
#include <vector>

#include "rocksdb/convenience.h"
#include "rocksdb/db.h"
#include "rocksdb/filter_policy.h"
#include "rocksdb/rate_limiter.h"
#include "rocksdb/status.h"
#include "rocksdb/table.h"
#include "rocksdb/utilities/backupable_db.h"
#include "rocksdb/utilities/memory_util.h"
#include "rocksdb/utilities/transaction_db.h"
#include "rocksdb/utilities/write_batch_with_index.h"
#include "rocksjni/compaction_filter_factory_jnicallback.h"
#include "rocksjni/comparatorjnicallback.h"
#include "rocksjni/event_listener_jnicallback.h"
#include "rocksjni/loggerjnicallback.h"
#include "rocksjni/table_filter_jnicallback.h"
#include "rocksjni/trace_writer_jnicallback.h"
#include "rocksjni/transaction_notifier_jnicallback.h"
#include "rocksjni/wal_filter_jnicallback.h"
#include "rocksjni/writebatchhandlerjnicallback.h"

// Remove macro on windows
#ifdef DELETE
#undef DELETE
#endif

namespace ROCKSDB_NAMESPACE {

class JavaClass {
 public:
  /**
   * Gets and initializes a Java Class
   *
   * @param env A pointer to the Java environment
   * @param jclazz_name The fully qualified JNI name of the Java Class
   *     e.g. "java/lang/String"
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env, const char* jclazz_name) {
    jclass jclazz = env->FindClass(jclazz_name);
    assert(jclazz != nullptr);
    return jclazz;
  }
};

// Native class template
template<class PTR, class DERIVED> class RocksDBNativeClass : public JavaClass {
};

// Native class template for sub-classes of RocksMutableObject
template<class PTR, class DERIVED> class NativeRocksMutableObject
    : public RocksDBNativeClass<PTR, DERIVED> {
 public:

  /**
   * Gets the Java Method ID for the
   * RocksMutableObject#setNativeHandle(long, boolean) method
   *
   * @param env A pointer to the Java environment
   * @return The Java Method ID or nullptr the RocksMutableObject class cannot
   *     be accessed, or if one of the NoSuchMethodError,
   *     ExceptionInInitializerError or OutOfMemoryError exceptions is thrown
   */
  static jmethodID getSetNativeHandleMethod(JNIEnv* env) {
    static jclass jclazz = DERIVED::getJClass(env);
    if(jclazz == nullptr) {
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(
        jclazz, "setNativeHandle", "(JZ)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Sets the C++ object pointer handle in the Java object
   *
   * @param env A pointer to the Java environment
   * @param jobj The Java object on which to set the pointer handle
   * @param ptr The C++ object pointer
   * @param java_owns_handle JNI_TRUE if ownership of the C++ object is
   *     managed by the Java object
   *
   * @return true if a Java exception is pending, false otherwise
   */
  static bool setHandle(JNIEnv* env, jobject jobj, PTR ptr,
      jboolean java_owns_handle) {
    assert(jobj != nullptr);
    static jmethodID mid = getSetNativeHandleMethod(env);
    if(mid == nullptr) {
      return true;  // signal exception
    }

    env->CallVoidMethod(jobj, mid, reinterpret_cast<jlong>(ptr), java_owns_handle);
    if(env->ExceptionCheck()) {
      return true;  // signal exception
    }

    return false;
  }
};

// Java Exception template
template<class DERIVED> class JavaException : public JavaClass {
 public:
  /**
   * Create and throw a java exception with the provided message
   *
   * @param env A pointer to the Java environment
   * @param msg The message for the exception
   *
   * @return true if an exception was thrown, false otherwise
   */
  static bool ThrowNew(JNIEnv* env, const std::string& msg) {
    jclass jclazz = DERIVED::getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      std::cerr << "JavaException::ThrowNew - Error: unexpected exception!" << std::endl;
      return env->ExceptionCheck();
    }

    const jint rs = env->ThrowNew(jclazz, msg.c_str());
    if(rs != JNI_OK) {
      // exception could not be thrown
      std::cerr << "JavaException::ThrowNew - Fatal: could not throw exception!" << std::endl;
      return env->ExceptionCheck();
    }

    return true;
  }
};

// The portal class for java.lang.IllegalArgumentException
class IllegalArgumentExceptionJni :
    public JavaException<IllegalArgumentExceptionJni> {
 public:
  /**
   * Get the Java Class java.lang.IllegalArgumentException
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return JavaException::getJClass(env, "java/lang/IllegalArgumentException");
  }

  /**
   * Create and throw a Java IllegalArgumentException with the provided status
   *
   * If s.ok() == true, then this function will not throw any exception.
   *
   * @param env A pointer to the Java environment
   * @param s The status for the exception
   *
   * @return true if an exception was thrown, false otherwise
   */
  static bool ThrowNew(JNIEnv* env, const Status& s) {
    assert(!s.ok());
    if (s.ok()) {
      return false;
    }

    // get the IllegalArgumentException class
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      std::cerr << "IllegalArgumentExceptionJni::ThrowNew/class - Error: unexpected exception!" << std::endl;
      return env->ExceptionCheck();
    }

    return JavaException::ThrowNew(env, s.ToString());
  }
};

// The portal class for org.rocksdb.Status.Code
class CodeJni : public JavaClass {
 public:
  /**
   * Get the Java Class org.rocksdb.Status.Code
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "org/rocksdb/Status$Code");
  }

  /**
   * Get the Java Method: Status.Code#getValue
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getValueMethod(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid =
        env->GetMethodID(jclazz, "getValue", "()b");
    assert(mid != nullptr);
    return mid;
  }
};

// The portal class for org.rocksdb.Status.SubCode
class SubCodeJni : public JavaClass {
 public:
  /**
   * Get the Java Class org.rocksdb.Status.SubCode
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "org/rocksdb/Status$SubCode");
  }

  /**
   * Get the Java Method: Status.SubCode#getValue
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getValueMethod(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid =
        env->GetMethodID(jclazz, "getValue", "()b");
    assert(mid != nullptr);
    return mid;
  }

  static ROCKSDB_NAMESPACE::Status::SubCode toCppSubCode(
      const jbyte jsub_code) {
    switch (jsub_code) {
      case 0x0:
        return ROCKSDB_NAMESPACE::Status::SubCode::kNone;
      case 0x1:
        return ROCKSDB_NAMESPACE::Status::SubCode::kMutexTimeout;
      case 0x2:
        return ROCKSDB_NAMESPACE::Status::SubCode::kLockTimeout;
      case 0x3:
        return ROCKSDB_NAMESPACE::Status::SubCode::kLockLimit;
      case 0x4:
        return ROCKSDB_NAMESPACE::Status::SubCode::kNoSpace;
      case 0x5:
        return ROCKSDB_NAMESPACE::Status::SubCode::kDeadlock;
      case 0x6:
        return ROCKSDB_NAMESPACE::Status::SubCode::kStaleFile;
      case 0x7:
        return ROCKSDB_NAMESPACE::Status::SubCode::kMemoryLimit;

      case 0x7F:
      default:
        return ROCKSDB_NAMESPACE::Status::SubCode::kNone;
    }
  }
};

// The portal class for org.rocksdb.Status
class StatusJni
    : public RocksDBNativeClass<ROCKSDB_NAMESPACE::Status*, StatusJni> {
 public:
  /**
   * Get the Java Class org.rocksdb.Status
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return RocksDBNativeClass::getJClass(env, "org/rocksdb/Status");
  }

  /**
   * Get the Java Method: Status#getCode
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getCodeMethod(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid =
        env->GetMethodID(jclazz, "getCode", "()Lorg/rocksdb/Status$Code;");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: Status#getSubCode
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getSubCodeMethod(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid =
        env->GetMethodID(jclazz, "getSubCode", "()Lorg/rocksdb/Status$SubCode;");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: Status#getState
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getStateMethod(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid =
        env->GetMethodID(jclazz, "getState", "()Ljava/lang/String;");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Create a new Java org.rocksdb.Status object with the same properties as
   * the provided C++ ROCKSDB_NAMESPACE::Status object
   *
   * @param env A pointer to the Java environment
   * @param status The ROCKSDB_NAMESPACE::Status object
   *
   * @return A reference to a Java org.rocksdb.Status object, or nullptr
   *     if an an exception occurs
   */
  static jobject construct(JNIEnv* env, const Status& status) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    jmethodID mid =
        env->GetMethodID(jclazz, "<init>", "(BBLjava/lang/String;)V");
    if(mid == nullptr) {
      // exception thrown: NoSuchMethodException or OutOfMemoryError
      return nullptr;
    }

    // convert the Status state for Java
    jstring jstate = nullptr;
    if (status.getState() != nullptr) {
      const char* const state = status.getState();
      jstate = env->NewStringUTF(state);
      if(env->ExceptionCheck()) {
        if(jstate != nullptr) {
          env->DeleteLocalRef(jstate);
        }
        return nullptr;
      }
    }

    jobject jstatus =
        env->NewObject(jclazz, mid, toJavaStatusCode(status.code()),
            toJavaStatusSubCode(status.subcode()), jstate);
    if(env->ExceptionCheck()) {
      // exception occurred
      if(jstate != nullptr) {
        env->DeleteLocalRef(jstate);
      }
      return nullptr;
    }

    if(jstate != nullptr) {
      env->DeleteLocalRef(jstate);
    }

    return jstatus;
  }

  static jobject construct(JNIEnv* env, const Status* status) {
    return construct(env, *status);
  }

  // Returns the equivalent org.rocksdb.Status.Code for the provided
  // C++ ROCKSDB_NAMESPACE::Status::Code enum
  static jbyte toJavaStatusCode(const ROCKSDB_NAMESPACE::Status::Code& code) {
    switch (code) {
      case ROCKSDB_NAMESPACE::Status::Code::kOk:
        return 0x0;
      case ROCKSDB_NAMESPACE::Status::Code::kNotFound:
        return 0x1;
      case ROCKSDB_NAMESPACE::Status::Code::kCorruption:
        return 0x2;
      case ROCKSDB_NAMESPACE::Status::Code::kNotSupported:
        return 0x3;
      case ROCKSDB_NAMESPACE::Status::Code::kInvalidArgument:
        return 0x4;
      case ROCKSDB_NAMESPACE::Status::Code::kIOError:
        return 0x5;
      case ROCKSDB_NAMESPACE::Status::Code::kMergeInProgress:
        return 0x6;
      case ROCKSDB_NAMESPACE::Status::Code::kIncomplete:
        return 0x7;
      case ROCKSDB_NAMESPACE::Status::Code::kShutdownInProgress:
        return 0x8;
      case ROCKSDB_NAMESPACE::Status::Code::kTimedOut:
        return 0x9;
      case ROCKSDB_NAMESPACE::Status::Code::kAborted:
        return 0xA;
      case ROCKSDB_NAMESPACE::Status::Code::kBusy:
        return 0xB;
      case ROCKSDB_NAMESPACE::Status::Code::kExpired:
        return 0xC;
      case ROCKSDB_NAMESPACE::Status::Code::kTryAgain:
        return 0xD;
      case ROCKSDB_NAMESPACE::Status::Code::kColumnFamilyDropped:
        return 0xE;
      default:
        return 0x7F;  // undefined
    }
  }

  // Returns the equivalent org.rocksdb.Status.SubCode for the provided
  // C++ ROCKSDB_NAMESPACE::Status::SubCode enum
  static jbyte toJavaStatusSubCode(
      const ROCKSDB_NAMESPACE::Status::SubCode& subCode) {
    switch (subCode) {
      case ROCKSDB_NAMESPACE::Status::SubCode::kNone:
        return 0x0;
      case ROCKSDB_NAMESPACE::Status::SubCode::kMutexTimeout:
        return 0x1;
      case ROCKSDB_NAMESPACE::Status::SubCode::kLockTimeout:
        return 0x2;
      case ROCKSDB_NAMESPACE::Status::SubCode::kLockLimit:
        return 0x3;
      case ROCKSDB_NAMESPACE::Status::SubCode::kNoSpace:
        return 0x4;
      case ROCKSDB_NAMESPACE::Status::SubCode::kDeadlock:
        return 0x5;
      case ROCKSDB_NAMESPACE::Status::SubCode::kStaleFile:
        return 0x6;
      case ROCKSDB_NAMESPACE::Status::SubCode::kMemoryLimit:
        return 0x7;
      default:
        return 0x7F;  // undefined
    }
  }

  static std::unique_ptr<ROCKSDB_NAMESPACE::Status> toCppStatus(
      const jbyte jcode_value, const jbyte jsub_code_value) {
    std::unique_ptr<ROCKSDB_NAMESPACE::Status> status;
    switch (jcode_value) {
      case 0x0:
        //Ok
        status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
            new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::OK()));
        break;
      case 0x1:
        //NotFound
        status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
            new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::NotFound(
                ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(jsub_code_value))));
        break;
      case 0x2:
        //Corruption
        status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
            new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::Corruption(
                ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(jsub_code_value))));
        break;
      case 0x3:
        //NotSupported
        status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
            new ROCKSDB_NAMESPACE::Status(
                ROCKSDB_NAMESPACE::Status::NotSupported(
                    ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(
                        jsub_code_value))));
        break;
      case 0x4:
        //InvalidArgument
        status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
            new ROCKSDB_NAMESPACE::Status(
                ROCKSDB_NAMESPACE::Status::InvalidArgument(
                    ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(
                        jsub_code_value))));
        break;
      case 0x5:
        //IOError
        status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
            new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::IOError(
                ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(jsub_code_value))));
        break;
      case 0x6:
        //MergeInProgress
        status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
            new ROCKSDB_NAMESPACE::Status(
                ROCKSDB_NAMESPACE::Status::MergeInProgress(
                    ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(
                        jsub_code_value))));
        break;
      case 0x7:
        //Incomplete
        status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
            new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::Incomplete(
                ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(jsub_code_value))));
        break;
      case 0x8:
        //ShutdownInProgress
        status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
            new ROCKSDB_NAMESPACE::Status(
                ROCKSDB_NAMESPACE::Status::ShutdownInProgress(
                    ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(
                        jsub_code_value))));
        break;
      case 0x9:
        //TimedOut
        status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
            new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::TimedOut(
                ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(jsub_code_value))));
        break;
      case 0xA:
        //Aborted
        status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
            new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::Aborted(
                ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(jsub_code_value))));
        break;
      case 0xB:
        //Busy
        status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
            new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::Busy(
                ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(jsub_code_value))));
        break;
      case 0xC:
        //Expired
        status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
            new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::Expired(
                ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(jsub_code_value))));
        break;
      case 0xD:
        //TryAgain
        status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
            new ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Status::TryAgain(
                ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(jsub_code_value))));
        break;
      case 0xE:
        // ColumnFamilyDropped
        status = std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
            new ROCKSDB_NAMESPACE::Status(
                ROCKSDB_NAMESPACE::Status::ColumnFamilyDropped(
                    ROCKSDB_NAMESPACE::SubCodeJni::toCppSubCode(
                        jsub_code_value))));
        break;
      case 0x7F:
      default:
        return nullptr;
    }
    return status;
  }

  // Returns the equivalent ROCKSDB_NAMESPACE::Status for the Java
  // org.rocksdb.Status
  static std::unique_ptr<ROCKSDB_NAMESPACE::Status> toCppStatus(
      JNIEnv* env, const jobject jstatus) {
    jmethodID mid_code = getCodeMethod(env);
    if (mid_code == nullptr) {
      // exception occurred
      return nullptr;
    }
    jobject jcode = env->CallObjectMethod(jstatus, mid_code);
    if (env->ExceptionCheck()) {
      // exception occurred
      return nullptr;
    }

    jmethodID mid_code_value = ROCKSDB_NAMESPACE::CodeJni::getValueMethod(env);
    if (mid_code_value == nullptr) {
      // exception occurred
      return nullptr;
    }
    jbyte jcode_value = env->CallByteMethod(jcode, mid_code_value);
    if (env->ExceptionCheck()) {
      // exception occurred
      if (jcode != nullptr) {
        env->DeleteLocalRef(jcode);
      }
      return nullptr;
    }

    jmethodID mid_subCode = getSubCodeMethod(env);
    if (mid_subCode == nullptr) {
      // exception occurred
      return nullptr;
    }
    jobject jsubCode = env->CallObjectMethod(jstatus, mid_subCode);
    if (env->ExceptionCheck()) {
      // exception occurred
      if (jcode != nullptr) {
        env->DeleteLocalRef(jcode);
      }
      return nullptr;
    }

    jbyte jsub_code_value = 0x0;  // None
    if (jsubCode != nullptr) {
      jmethodID mid_subCode_value =
          ROCKSDB_NAMESPACE::SubCodeJni::getValueMethod(env);
      if (mid_subCode_value == nullptr) {
        // exception occurred
        return nullptr;
      }
      jsub_code_value = env->CallByteMethod(jsubCode, mid_subCode_value);
      if (env->ExceptionCheck()) {
        // exception occurred
        if (jcode != nullptr) {
          env->DeleteLocalRef(jcode);
        }
        return nullptr;
      }
    }

    jmethodID mid_state = getStateMethod(env);
    if (mid_state == nullptr) {
      // exception occurred
      return nullptr;
    }
    jobject jstate = env->CallObjectMethod(jstatus, mid_state);
    if (env->ExceptionCheck()) {
      // exception occurred
      if (jsubCode != nullptr) {
        env->DeleteLocalRef(jsubCode);
      }
      if (jcode != nullptr) {
        env->DeleteLocalRef(jcode);
      }
      return nullptr;
    }

    std::unique_ptr<ROCKSDB_NAMESPACE::Status> status =
        toCppStatus(jcode_value, jsub_code_value);

    // delete all local refs
    if (jstate != nullptr) {
      env->DeleteLocalRef(jstate);
    }
    if (jsubCode != nullptr) {
      env->DeleteLocalRef(jsubCode);
    }
    if (jcode != nullptr) {
      env->DeleteLocalRef(jcode);
    }

    return status;
  }
};

// The portal class for org.rocksdb.RocksDBException
class RocksDBExceptionJni :
    public JavaException<RocksDBExceptionJni> {
 public:
  /**
   * Get the Java Class org.rocksdb.RocksDBException
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return JavaException::getJClass(env, "org/rocksdb/RocksDBException");
  }

  /**
   * Create and throw a Java RocksDBException with the provided message
   *
   * @param env A pointer to the Java environment
   * @param msg The message for the exception
   *
   * @return true if an exception was thrown, false otherwise
   */
  static bool ThrowNew(JNIEnv* env, const std::string& msg) {
    return JavaException::ThrowNew(env, msg);
  }

  /**
   * Create and throw a Java RocksDBException with the provided status
   *
   * If s->ok() == true, then this function will not throw any exception.
   *
   * @param env A pointer to the Java environment
   * @param s The status for the exception
   *
   * @return true if an exception was thrown, false otherwise
   */
  static bool ThrowNew(JNIEnv* env, std::unique_ptr<Status>& s) {
    return ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, *(s.get()));
  }

  /**
   * Create and throw a Java RocksDBException with the provided status
   *
   * If s.ok() == true, then this function will not throw any exception.
   *
   * @param env A pointer to the Java environment
   * @param s The status for the exception
   *
   * @return true if an exception was thrown, false otherwise
   */
  static bool ThrowNew(JNIEnv* env, const Status& s) {
    if (s.ok()) {
      return false;
    }

    // get the RocksDBException class
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      std::cerr << "RocksDBExceptionJni::ThrowNew/class - Error: unexpected exception!" << std::endl;
      return env->ExceptionCheck();
    }

    // get the constructor of org.rocksdb.RocksDBException
    jmethodID mid =
        env->GetMethodID(jclazz, "<init>", "(Lorg/rocksdb/Status;)V");
    if(mid == nullptr) {
      // exception thrown: NoSuchMethodException or OutOfMemoryError
      std::cerr << "RocksDBExceptionJni::ThrowNew/cstr - Error: unexpected exception!" << std::endl;
      return env->ExceptionCheck();
    }

    // get the Java status object
    jobject jstatus = StatusJni::construct(env, s);
    if(jstatus == nullptr) {
      // exception occcurred
      std::cerr << "RocksDBExceptionJni::ThrowNew/StatusJni - Error: unexpected exception!" << std::endl;
      return env->ExceptionCheck();
    }

    // construct the RocksDBException
    jthrowable rocksdb_exception = reinterpret_cast<jthrowable>(env->NewObject(jclazz, mid, jstatus));
    if(env->ExceptionCheck()) {
      if(jstatus != nullptr) {
        env->DeleteLocalRef(jstatus);
      }
      if(rocksdb_exception != nullptr) {
        env->DeleteLocalRef(rocksdb_exception);
      }
      std::cerr << "RocksDBExceptionJni::ThrowNew/NewObject - Error: unexpected exception!" << std::endl;
      return true;
    }

    // throw the RocksDBException
    const jint rs = env->Throw(rocksdb_exception);
    if(rs != JNI_OK) {
      // exception could not be thrown
      std::cerr << "RocksDBExceptionJni::ThrowNew - Fatal: could not throw exception!" << std::endl;
      if(jstatus != nullptr) {
        env->DeleteLocalRef(jstatus);
      }
      if(rocksdb_exception != nullptr) {
        env->DeleteLocalRef(rocksdb_exception);
      }
      return env->ExceptionCheck();
    }

    if(jstatus != nullptr) {
      env->DeleteLocalRef(jstatus);
    }
    if(rocksdb_exception != nullptr) {
      env->DeleteLocalRef(rocksdb_exception);
    }

    return true;
  }

  /**
   * Create and throw a Java RocksDBException with the provided message
   * and status
   *
   * If s.ok() == true, then this function will not throw any exception.
   *
   * @param env A pointer to the Java environment
   * @param msg The message for the exception
   * @param s The status for the exception
   *
   * @return true if an exception was thrown, false otherwise
   */
  static bool ThrowNew(JNIEnv* env, const std::string& msg, const Status& s) {
    assert(!s.ok());
    if (s.ok()) {
      return false;
    }

    // get the RocksDBException class
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      std::cerr << "RocksDBExceptionJni::ThrowNew/class - Error: unexpected exception!" << std::endl;
      return env->ExceptionCheck();
    }

    // get the constructor of org.rocksdb.RocksDBException
    jmethodID mid =
        env->GetMethodID(jclazz, "<init>", "(Ljava/lang/String;Lorg/rocksdb/Status;)V");
    if(mid == nullptr) {
      // exception thrown: NoSuchMethodException or OutOfMemoryError
      std::cerr << "RocksDBExceptionJni::ThrowNew/cstr - Error: unexpected exception!" << std::endl;
      return env->ExceptionCheck();
    }

    jstring jmsg = env->NewStringUTF(msg.c_str());
    if(jmsg == nullptr) {
      // exception thrown: OutOfMemoryError
      std::cerr << "RocksDBExceptionJni::ThrowNew/msg - Error: unexpected exception!" << std::endl;
      return env->ExceptionCheck();
    }

    // get the Java status object
    jobject jstatus = StatusJni::construct(env, s);
    if(jstatus == nullptr) {
      // exception occcurred
      std::cerr << "RocksDBExceptionJni::ThrowNew/StatusJni - Error: unexpected exception!" << std::endl;
      if(jmsg != nullptr) {
        env->DeleteLocalRef(jmsg);
      }
      return env->ExceptionCheck();
    }

    // construct the RocksDBException
    jthrowable rocksdb_exception = reinterpret_cast<jthrowable>(env->NewObject(jclazz, mid, jmsg, jstatus));
    if(env->ExceptionCheck()) {
      if(jstatus != nullptr) {
        env->DeleteLocalRef(jstatus);
      }
      if(jmsg != nullptr) {
        env->DeleteLocalRef(jmsg);
      }
      if(rocksdb_exception != nullptr) {
        env->DeleteLocalRef(rocksdb_exception);
      }
      std::cerr << "RocksDBExceptionJni::ThrowNew/NewObject - Error: unexpected exception!" << std::endl;
      return true;
    }

    // throw the RocksDBException
    const jint rs = env->Throw(rocksdb_exception);
    if(rs != JNI_OK) {
      // exception could not be thrown
      std::cerr << "RocksDBExceptionJni::ThrowNew - Fatal: could not throw exception!" << std::endl;
      if(jstatus != nullptr) {
        env->DeleteLocalRef(jstatus);
      }
      if(jmsg != nullptr) {
        env->DeleteLocalRef(jmsg);
      }
      if(rocksdb_exception != nullptr) {
        env->DeleteLocalRef(rocksdb_exception);
      }
      return env->ExceptionCheck();
    }

    if(jstatus != nullptr) {
      env->DeleteLocalRef(jstatus);
    }
    if(jmsg != nullptr) {
      env->DeleteLocalRef(jmsg);
    }
    if(rocksdb_exception != nullptr) {
      env->DeleteLocalRef(rocksdb_exception);
    }

    return true;
  }

  /**
   * Get the Java Method: RocksDBException#getStatus
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getStatusMethod(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid =
        env->GetMethodID(jclazz, "getStatus", "()Lorg/rocksdb/Status;");
    assert(mid != nullptr);
    return mid;
  }

  static std::unique_ptr<ROCKSDB_NAMESPACE::Status> toCppStatus(
      JNIEnv* env, jthrowable jrocksdb_exception) {
    if(!env->IsInstanceOf(jrocksdb_exception, getJClass(env))) {
      // not an instance of RocksDBException
      return nullptr;
    }

    // get the java status object
    jmethodID mid = getStatusMethod(env);
    if(mid == nullptr) {
      // exception occurred accessing class or method
      return nullptr;
    }

    jobject jstatus = env->CallObjectMethod(jrocksdb_exception, mid);
    if(env->ExceptionCheck()) {
      // exception occurred
      return nullptr;
    }

    if(jstatus == nullptr) {
      return nullptr;   // no status available
    }

    return ROCKSDB_NAMESPACE::StatusJni::toCppStatus(env, jstatus);
  }
};

// The portal class for java.util.List
class ListJni : public JavaClass {
 public:
  /**
   * Get the Java Class java.util.List
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getListClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "java/util/List");
  }

  /**
   * Get the Java Class java.util.ArrayList
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getArrayListClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "java/util/ArrayList");
  }

  /**
   * Get the Java Class java.util.Iterator
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getIteratorClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "java/util/Iterator");
  }

  /**
   * Get the Java Method: List#iterator
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getIteratorMethod(JNIEnv* env) {
    jclass jlist_clazz = getListClass(env);
    if(jlist_clazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid =
        env->GetMethodID(jlist_clazz, "iterator", "()Ljava/util/Iterator;");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: Iterator#hasNext
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getHasNextMethod(JNIEnv* env) {
    jclass jiterator_clazz = getIteratorClass(env);
    if(jiterator_clazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(jiterator_clazz, "hasNext", "()Z");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: Iterator#next
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getNextMethod(JNIEnv* env) {
    jclass jiterator_clazz = getIteratorClass(env);
    if(jiterator_clazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid =
        env->GetMethodID(jiterator_clazz, "next", "()Ljava/lang/Object;");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: ArrayList constructor
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getArrayListConstructorMethodId(JNIEnv* env) {
    jclass jarray_list_clazz = getArrayListClass(env);
    if(jarray_list_clazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }
    static jmethodID mid =
        env->GetMethodID(jarray_list_clazz, "<init>", "(I)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: List#add
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getListAddMethodId(JNIEnv* env) {
    jclass jlist_clazz = getListClass(env);
    if(jlist_clazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid =
        env->GetMethodID(jlist_clazz, "add", "(Ljava/lang/Object;)Z");
    assert(mid != nullptr);
    return mid;
  }
};

// The portal class for java.lang.Byte
class ByteJni : public JavaClass {
 public:
  /**
   * Get the Java Class java.lang.Byte
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "java/lang/Byte");
  }

  /**
   * Get the Java Class byte[]
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getArrayJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "[B");
  }

  /**
   * Creates a new 2-dimensional Java Byte Array byte[][]
   *
   * @param env A pointer to the Java environment
   * @param len The size of the first dimension
   *
   * @return A reference to the Java byte[][] or nullptr if an exception occurs
   */
  static jobjectArray new2dByteArray(JNIEnv* env, const jsize len) {
    jclass clazz = getArrayJClass(env);
    if(clazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    return env->NewObjectArray(len, clazz, nullptr);
  }

  /**
   * Get the Java Method: Byte#byteValue
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getByteValueMethod(JNIEnv* env) {
    jclass clazz = getJClass(env);
    if(clazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(clazz, "byteValue", "()B");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Calls the Java Method: Byte#valueOf, returning a constructed Byte jobject
   *
   * @param env A pointer to the Java environment
   *
   * @return A constructing Byte object or nullptr if the class or method id could not
   *     be retrieved, or an exception occurred
   */
  static jobject valueOf(JNIEnv* env, jbyte jprimitive_byte) {
    jclass clazz = getJClass(env);
    if (clazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid =
        env->GetStaticMethodID(clazz, "valueOf", "(B)Ljava/lang/Byte;");
    if (mid == nullptr) {
      // exception thrown: NoSuchMethodException or OutOfMemoryError
      return nullptr;
    }

    const jobject jbyte_obj =
        env->CallStaticObjectMethod(clazz, mid, jprimitive_byte);
    if (env->ExceptionCheck()) {
      // exception occurred
      return nullptr;
    }

    return jbyte_obj;
  }

};

// The portal class for java.nio.ByteBuffer
class ByteBufferJni : public JavaClass {
 public:
  /**
   * Get the Java Class java.nio.ByteBuffer
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "java/nio/ByteBuffer");
  }

  /**
   * Get the Java Method: ByteBuffer#allocate
   *
   * @param env A pointer to the Java environment
   * @param jbytebuffer_clazz if you have a reference to a ByteBuffer class, or
   * nullptr
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getAllocateMethodId(JNIEnv* env,
      jclass jbytebuffer_clazz = nullptr) {
    const jclass jclazz =
        jbytebuffer_clazz == nullptr ? getJClass(env) : jbytebuffer_clazz;
    if (jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetStaticMethodID(
        jclazz, "allocate", "(I)Ljava/nio/ByteBuffer;");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: ByteBuffer#array
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getArrayMethodId(JNIEnv* env,
      jclass jbytebuffer_clazz = nullptr) {
    const jclass jclazz =
        jbytebuffer_clazz == nullptr ? getJClass(env) : jbytebuffer_clazz;
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(jclazz, "array", "()[B");
    assert(mid != nullptr);
    return mid;
  }

  static jobject construct(
      JNIEnv* env, const bool direct, const size_t capacity,
      jclass jbytebuffer_clazz = nullptr) {
    return constructWith(env, direct, nullptr, capacity, jbytebuffer_clazz);
  }

  static jobject constructWith(JNIEnv* env, const bool direct, const char* buf,
                               const size_t capacity,
                               jclass jbytebuffer_clazz = nullptr) {
    if (direct) {
      bool allocated = false;
      if (buf == nullptr) {
        buf = new char[capacity];
        allocated = true;
      }
      jobject jbuf = env->NewDirectByteBuffer(const_cast<char*>(buf), static_cast<jlong>(capacity));
      if (jbuf == nullptr) {
        // exception occurred
        if (allocated) {
          delete[] static_cast<const char*>(buf);
        }
        return nullptr;
      }
      return jbuf;
    } else {
      const jclass jclazz =
        jbytebuffer_clazz == nullptr ? getJClass(env) : jbytebuffer_clazz;
      if (jclazz == nullptr) {
        // exception occurred accessing class
        return nullptr;
      }
      const jmethodID jmid_allocate = getAllocateMethodId(env, jbytebuffer_clazz);
      if (jmid_allocate == nullptr) {
        // exception occurred accessing class, or NoSuchMethodException or OutOfMemoryError
        return nullptr;
      }
      const jobject jbuf = env->CallStaticObjectMethod(
          jclazz, jmid_allocate, static_cast<jint>(capacity));
      if (env->ExceptionCheck()) {
        // exception occurred
        return nullptr;
      }

      // set buffer data?
      if (buf != nullptr) {
        jbyteArray jarray = array(env, jbuf, jbytebuffer_clazz);
        if (jarray == nullptr) {
          // exception occurred
          env->DeleteLocalRef(jbuf);
          return nullptr;
        }

        jboolean is_copy = JNI_FALSE;
        jbyte* ja = reinterpret_cast<jbyte*>(
            env->GetPrimitiveArrayCritical(jarray, &is_copy));
        if (ja == nullptr) {
          // exception occurred
           env->DeleteLocalRef(jarray);
           env->DeleteLocalRef(jbuf);
           return nullptr;
        }

        memcpy(ja, const_cast<char*>(buf), capacity);

        env->ReleasePrimitiveArrayCritical(jarray, ja, 0);

        env->DeleteLocalRef(jarray);
      }

      return jbuf;
    }
  }

  static jbyteArray array(JNIEnv* env, const jobject& jbyte_buffer,
      jclass jbytebuffer_clazz = nullptr) {
    const jmethodID mid = getArrayMethodId(env, jbytebuffer_clazz);
    if (mid == nullptr) {
      // exception occurred accessing class, or NoSuchMethodException or OutOfMemoryError
      return nullptr;
    }
    const jobject jarray = env->CallObjectMethod(jbyte_buffer, mid);
    if (env->ExceptionCheck()) {
      // exception occurred
      return nullptr;
    }
    return static_cast<jbyteArray>(jarray);
  }
};

// The portal class for java.lang.Integer
class IntegerJni : public JavaClass {
 public:
  /**
   * Get the Java Class java.lang.Integer
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "java/lang/Integer");
  }

  static jobject valueOf(JNIEnv* env, jint jprimitive_int) {
    jclass jclazz = getJClass(env);
    if (jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    jmethodID mid =
        env->GetStaticMethodID(jclazz, "valueOf", "(I)Ljava/lang/Integer;");
    if (mid == nullptr) {
      // exception thrown: NoSuchMethodException or OutOfMemoryError
      return nullptr;
    }

    const jobject jinteger_obj =
        env->CallStaticObjectMethod(jclazz, mid, jprimitive_int);
    if (env->ExceptionCheck()) {
      // exception occurred
      return nullptr;
    }

    return jinteger_obj;
  }
};

// The portal class for java.lang.Long
class LongJni : public JavaClass {
 public:
  /**
   * Get the Java Class java.lang.Long
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "java/lang/Long");
  }

  static jobject valueOf(JNIEnv* env, jlong jprimitive_long) {
    jclass jclazz = getJClass(env);
    if (jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    jmethodID mid =
        env->GetStaticMethodID(jclazz, "valueOf", "(J)Ljava/lang/Long;");
    if (mid == nullptr) {
      // exception thrown: NoSuchMethodException or OutOfMemoryError
      return nullptr;
    }

    const jobject jlong_obj =
        env->CallStaticObjectMethod(jclazz, mid, jprimitive_long);
    if (env->ExceptionCheck()) {
      // exception occurred
      return nullptr;
    }

    return jlong_obj;
  }
};

// The portal class for java.lang.StringBuilder
class StringBuilderJni : public JavaClass {
  public:
  /**
   * Get the Java Class java.lang.StringBuilder
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "java/lang/StringBuilder");
  }

  /**
   * Get the Java Method: StringBuilder#append
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getListAddMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid =
        env->GetMethodID(jclazz, "append",
            "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Appends a C-style string to a StringBuilder
   *
   * @param env A pointer to the Java environment
   * @param jstring_builder Reference to a java.lang.StringBuilder
   * @param c_str A C-style string to append to the StringBuilder
   *
   * @return A reference to the updated StringBuilder, or a nullptr if
   *     an exception occurs
   */
  static jobject append(JNIEnv* env, jobject jstring_builder,
      const char* c_str) {
    jmethodID mid = getListAddMethodId(env);
    if(mid == nullptr) {
      // exception occurred accessing class or method
      return nullptr;
    }

    jstring new_value_str = env->NewStringUTF(c_str);
    if(new_value_str == nullptr) {
      // exception thrown: OutOfMemoryError
      return nullptr;
    }

    jobject jresult_string_builder =
        env->CallObjectMethod(jstring_builder, mid, new_value_str);
    if(env->ExceptionCheck()) {
      // exception occurred
      env->DeleteLocalRef(new_value_str);
      return nullptr;
    }

    return jresult_string_builder;
  }
};

// various utility functions for working with RocksDB and JNI
class JniUtil {
 public:
    /**
     * Detect if jlong overflows size_t
     *
     * @param jvalue the jlong value
     *
     * @return
     */
    inline static Status check_if_jlong_fits_size_t(const jlong& jvalue) {
      Status s = Status::OK();
      if (static_cast<uint64_t>(jvalue) > std::numeric_limits<size_t>::max()) {
        s = Status::InvalidArgument(Slice("jlong overflows 32 bit value."));
      }
      return s;
    }

    /**
     * Obtains a reference to the JNIEnv from
     * the JVM
     *
     * If the current thread is not attached to the JavaVM
     * then it will be attached so as to retrieve the JNIEnv
     *
     * If a thread is attached, it must later be manually
     * released by calling JavaVM::DetachCurrentThread.
     * This can be handled by always matching calls to this
     * function with calls to {@link JniUtil::releaseJniEnv(JavaVM*, jboolean)}
     *
     * @param jvm (IN) A pointer to the JavaVM instance
     * @param attached (OUT) A pointer to a boolean which
     *     will be set to JNI_TRUE if we had to attach the thread
     *
     * @return A pointer to the JNIEnv or nullptr if a fatal error
     *     occurs and the JNIEnv cannot be retrieved
     */
    static JNIEnv* getJniEnv(JavaVM* jvm, jboolean* attached) {
      assert(jvm != nullptr);

      JNIEnv *env;
      const jint env_rs = jvm->GetEnv(reinterpret_cast<void**>(&env),
          JNI_VERSION_1_6);

      if(env_rs == JNI_OK) {
        // current thread is already attached, return the JNIEnv
        *attached = JNI_FALSE;
        return env;
      } else if(env_rs == JNI_EDETACHED) {
        // current thread is not attached, attempt to attach
        const jint rs_attach = jvm->AttachCurrentThread(reinterpret_cast<void**>(&env), NULL);
        if(rs_attach == JNI_OK) {
          *attached = JNI_TRUE;
          return env;
        } else {
          // error, could not attach the thread
          std::cerr << "JniUtil::getJniEnv - Fatal: could not attach current thread to JVM!" << std::endl;
          return nullptr;
        }
      } else if(env_rs == JNI_EVERSION) {
        // error, JDK does not support JNI_VERSION_1_6+
        std::cerr << "JniUtil::getJniEnv - Fatal: JDK does not support JNI_VERSION_1_6" << std::endl;
        return nullptr;
      } else {
        std::cerr << "JniUtil::getJniEnv - Fatal: Unknown error: env_rs=" << env_rs << std::endl;
        return nullptr;
      }
    }

    /**
     * Counterpart to {@link JniUtil::getJniEnv(JavaVM*, jboolean*)}
     *
     * Detachess the current thread from the JVM if it was previously
     * attached
     *
     * @param jvm (IN) A pointer to the JavaVM instance
     * @param attached (IN) JNI_TRUE if we previously had to attach the thread
     *     to the JavaVM to get the JNIEnv
     */
    static void releaseJniEnv(JavaVM* jvm, jboolean& attached) {
      assert(jvm != nullptr);
      if(attached == JNI_TRUE) {
        const jint rs_detach = jvm->DetachCurrentThread();
        assert(rs_detach == JNI_OK);
        if(rs_detach != JNI_OK) {
          std::cerr << "JniUtil::getJniEnv - Warn: Unable to detach current thread from JVM!" << std::endl;
        }
      }
    }

    /**
     * Copies a Java String[] to a C++ std::vector<std::string>
     *
     * @param env (IN) A pointer to the java environment
     * @param jss (IN) The Java String array to copy
     * @param has_exception (OUT) will be set to JNI_TRUE
     *     if an OutOfMemoryError or ArrayIndexOutOfBoundsException
     *     exception occurs
     *
     * @return A std::vector<std:string> containing copies of the Java strings
     */
    static std::vector<std::string> copyStrings(JNIEnv* env,
        jobjectArray jss, jboolean* has_exception) {
      return ROCKSDB_NAMESPACE::JniUtil::copyStrings(
          env, jss, env->GetArrayLength(jss), has_exception);
    }

    /**
     * Copies a Java String[] to a C++ std::vector<std::string>
     *
     * @param env (IN) A pointer to the java environment
     * @param jss (IN) The Java String array to copy
     * @param jss_len (IN) The length of the Java String array to copy
     * @param has_exception (OUT) will be set to JNI_TRUE
     *     if an OutOfMemoryError or ArrayIndexOutOfBoundsException
     *     exception occurs
     *
     * @return A std::vector<std:string> containing copies of the Java strings
     */
    static std::vector<std::string> copyStrings(JNIEnv* env,
        jobjectArray jss, const jsize jss_len, jboolean* has_exception) {
      std::vector<std::string> strs;
      strs.reserve(jss_len);
      for (jsize i = 0; i < jss_len; i++) {
        jobject js = env->GetObjectArrayElement(jss, i);
        if(env->ExceptionCheck()) {
          // exception thrown: ArrayIndexOutOfBoundsException
          *has_exception = JNI_TRUE;
          return strs;
        }

        jstring jstr = static_cast<jstring>(js);
        const char* str = env->GetStringUTFChars(jstr, nullptr);
        if(str == nullptr) {
          // exception thrown: OutOfMemoryError
          env->DeleteLocalRef(js);
          *has_exception = JNI_TRUE;
          return strs;
        }

        strs.push_back(std::string(str));

        env->ReleaseStringUTFChars(jstr, str);
        env->DeleteLocalRef(js);
      }

      *has_exception = JNI_FALSE;
      return strs;
    }

    /**
     * Copies a jstring to a C-style null-terminated byte string
     * and releases the original jstring
     *
     * The jstring is copied as UTF-8
     *
     * If an exception occurs, then JNIEnv::ExceptionCheck()
     * will have been called
     *
     * @param env (IN) A pointer to the java environment
     * @param js (IN) The java string to copy
     * @param has_exception (OUT) will be set to JNI_TRUE
     *     if an OutOfMemoryError exception occurs
     *
     * @return A pointer to the copied string, or a
     *     nullptr if has_exception == JNI_TRUE
     */
    static std::unique_ptr<char[]> copyString(JNIEnv* env, jstring js,
        jboolean* has_exception) {
      const char *utf = env->GetStringUTFChars(js, nullptr);
      if(utf == nullptr) {
        // exception thrown: OutOfMemoryError
        env->ExceptionCheck();
        *has_exception = JNI_TRUE;
        return nullptr;
      } else if(env->ExceptionCheck()) {
        // exception thrown
        env->ReleaseStringUTFChars(js, utf);
        *has_exception = JNI_TRUE;
        return nullptr;
      }

      const jsize utf_len = env->GetStringUTFLength(js);
      std::unique_ptr<char[]> str(new char[utf_len + 1]);  // Note: + 1 is needed for the c_str null terminator
      std::strcpy(str.get(), utf);
      env->ReleaseStringUTFChars(js, utf);
      *has_exception = JNI_FALSE;
      return str;
    }

    /**
     * Copies a jstring to a std::string
     * and releases the original jstring
     *
     * If an exception occurs, then JNIEnv::ExceptionCheck()
     * will have been called
     *
     * @param env (IN) A pointer to the java environment
     * @param js (IN) The java string to copy
     * @param has_exception (OUT) will be set to JNI_TRUE
     *     if an OutOfMemoryError exception occurs
     *
     * @return A std:string copy of the jstring, or an
     *     empty std::string if has_exception == JNI_TRUE
     */
    static std::string copyStdString(JNIEnv* env, jstring js,
      jboolean* has_exception) {
      const char *utf = env->GetStringUTFChars(js, nullptr);
      if(utf == nullptr) {
        // exception thrown: OutOfMemoryError
        env->ExceptionCheck();
        *has_exception = JNI_TRUE;
        return std::string();
      } else if(env->ExceptionCheck()) {
        // exception thrown
        env->ReleaseStringUTFChars(js, utf);
        *has_exception = JNI_TRUE;
        return std::string();
      }

      std::string name(utf);
      env->ReleaseStringUTFChars(js, utf);
      *has_exception = JNI_FALSE;
      return name;
    }

    /**
     * Copies bytes from a std::string to a jByteArray
     *
     * @param env A pointer to the java environment
     * @param bytes The bytes to copy
     *
     * @return the Java byte[], or nullptr if an exception occurs
     *
     * @throws RocksDBException thrown
     *   if memory size to copy exceeds general java specific array size limitation.
     */
    static jbyteArray copyBytes(JNIEnv* env, std::string bytes) {
      return createJavaByteArrayWithSizeCheck(env, bytes.c_str(), bytes.size());
    }

    /**
     * Given a Java byte[][] which is an array of java.lang.Strings
     * where each String is a byte[], the passed function `string_fn`
     * will be called on each String, the result is the collected by
     * calling the passed function `collector_fn`
     *
     * @param env (IN) A pointer to the java environment
     * @param jbyte_strings (IN) A Java array of Strings expressed as bytes
     * @param string_fn (IN) A transform function to call for each String
     * @param collector_fn (IN) A collector which is called for the result
     *     of each `string_fn`
     * @param has_exception (OUT) will be set to JNI_TRUE
     *     if an ArrayIndexOutOfBoundsException or OutOfMemoryError
     *     exception occurs
     */
    template <typename T> static void byteStrings(JNIEnv* env,
        jobjectArray jbyte_strings,
        std::function<T(const char*, const size_t)> string_fn,
        std::function<void(size_t, T)> collector_fn,
        jboolean *has_exception) {
      const jsize jlen = env->GetArrayLength(jbyte_strings);

      for(jsize i = 0; i < jlen; i++) {
        jobject jbyte_string_obj = env->GetObjectArrayElement(jbyte_strings, i);
        if(env->ExceptionCheck()) {
          // exception thrown: ArrayIndexOutOfBoundsException
          *has_exception = JNI_TRUE;  // signal error
          return;
        }

        jbyteArray jbyte_string_ary =
            reinterpret_cast<jbyteArray>(jbyte_string_obj);
        T result = byteString(env, jbyte_string_ary, string_fn, has_exception);

        env->DeleteLocalRef(jbyte_string_obj);

        if(*has_exception == JNI_TRUE) {
          // exception thrown: OutOfMemoryError
          return;
        }

        collector_fn(i, result);
      }

      *has_exception = JNI_FALSE;
    }

    /**
     * Given a Java String which is expressed as a Java Byte Array byte[],
     * the passed function `string_fn` will be called on the String
     * and the result returned
     *
     * @param env (IN) A pointer to the java environment
     * @param jbyte_string_ary (IN) A Java String expressed in bytes
     * @param string_fn (IN) A transform function to call on the String
     * @param has_exception (OUT) will be set to JNI_TRUE
     *     if an OutOfMemoryError exception occurs
     */
    template <typename T> static T byteString(JNIEnv* env,
        jbyteArray jbyte_string_ary,
        std::function<T(const char*, const size_t)> string_fn,
        jboolean* has_exception) {
      const jsize jbyte_string_len = env->GetArrayLength(jbyte_string_ary);
      return byteString<T>(env, jbyte_string_ary, jbyte_string_len, string_fn,
          has_exception);
    }

    /**
     * Given a Java String which is expressed as a Java Byte Array byte[],
     * the passed function `string_fn` will be called on the String
     * and the result returned
     *
     * @param env (IN) A pointer to the java environment
     * @param jbyte_string_ary (IN) A Java String expressed in bytes
     * @param jbyte_string_len (IN) The length of the Java String
     *     expressed in bytes
     * @param string_fn (IN) A transform function to call on the String
     * @param has_exception (OUT) will be set to JNI_TRUE
     *     if an OutOfMemoryError exception occurs
     */
    template <typename T> static T byteString(JNIEnv* env,
        jbyteArray jbyte_string_ary, const jsize jbyte_string_len,
        std::function<T(const char*, const size_t)> string_fn,
        jboolean* has_exception) {
      jbyte* jbyte_string =
          env->GetByteArrayElements(jbyte_string_ary, nullptr);
      if(jbyte_string == nullptr) {
        // exception thrown: OutOfMemoryError
        *has_exception = JNI_TRUE;
        return nullptr;  // signal error
      }

      T result =
          string_fn(reinterpret_cast<char *>(jbyte_string), jbyte_string_len);

      env->ReleaseByteArrayElements(jbyte_string_ary, jbyte_string, JNI_ABORT);

      *has_exception = JNI_FALSE;
      return result;
    }

    /**
     * Converts a std::vector<string> to a Java byte[][] where each Java String
     * is expressed as a Java Byte Array byte[].
     *
     * @param env A pointer to the java environment
     * @param strings A vector of Strings
     *
     * @return A Java array of Strings expressed as bytes,
     *     or nullptr if an exception is thrown
     */
    static jobjectArray stringsBytes(JNIEnv* env, std::vector<std::string> strings) {
      jclass jcls_ba = ByteJni::getArrayJClass(env);
      if(jcls_ba == nullptr) {
        // exception occurred
        return nullptr;
      }

      const jsize len = static_cast<jsize>(strings.size());

      jobjectArray jbyte_strings = env->NewObjectArray(len, jcls_ba, nullptr);
      if(jbyte_strings == nullptr) {
        // exception thrown: OutOfMemoryError
        return nullptr;
      }

      for (jsize i = 0; i < len; i++) {
        std::string *str = &strings[i];
        const jsize str_len = static_cast<jsize>(str->size());

        jbyteArray jbyte_string_ary = env->NewByteArray(str_len);
        if(jbyte_string_ary == nullptr) {
          // exception thrown: OutOfMemoryError
          env->DeleteLocalRef(jbyte_strings);
          return nullptr;
        }

        env->SetByteArrayRegion(
          jbyte_string_ary, 0, str_len,
          const_cast<jbyte*>(reinterpret_cast<const jbyte*>(str->c_str())));
        if(env->ExceptionCheck()) {
          // exception thrown: ArrayIndexOutOfBoundsException
          env->DeleteLocalRef(jbyte_string_ary);
          env->DeleteLocalRef(jbyte_strings);
          return nullptr;
        }

        env->SetObjectArrayElement(jbyte_strings, i, jbyte_string_ary);
        if(env->ExceptionCheck()) {
          // exception thrown: ArrayIndexOutOfBoundsException
          // or ArrayStoreException
          env->DeleteLocalRef(jbyte_string_ary);
          env->DeleteLocalRef(jbyte_strings);
          return nullptr;
        }

        env->DeleteLocalRef(jbyte_string_ary);
      }

      return jbyte_strings;
    }

     /**
     * Converts a std::vector<std::string> to a Java String[].
     *
     * @param env A pointer to the java environment
     * @param strings A vector of Strings
     *
     * @return A Java array of Strings,
     *     or nullptr if an exception is thrown
     */
    static jobjectArray toJavaStrings(JNIEnv* env,
        const std::vector<std::string>* strings) {
      jclass jcls_str = env->FindClass("java/lang/String");
      if(jcls_str == nullptr) {
        // exception occurred
        return nullptr;
      }

      const jsize len = static_cast<jsize>(strings->size());

      jobjectArray jstrings = env->NewObjectArray(len, jcls_str, nullptr);
      if(jstrings == nullptr) {
        // exception thrown: OutOfMemoryError
        return nullptr;
      }

      for (jsize i = 0; i < len; i++) {
        const std::string *str = &((*strings)[i]);
        jstring js = ROCKSDB_NAMESPACE::JniUtil::toJavaString(env, str);
        if (js == nullptr) {
          env->DeleteLocalRef(jstrings);
          return nullptr;
        }

        env->SetObjectArrayElement(jstrings, i, js);
        if(env->ExceptionCheck()) {
          // exception thrown: ArrayIndexOutOfBoundsException
          // or ArrayStoreException
          env->DeleteLocalRef(js);
          env->DeleteLocalRef(jstrings);
          return nullptr;
        }
      }

      return jstrings;
    }

    /**
     * Creates a Java UTF String from a C++ std::string
     *
     * @param env A pointer to the java environment
     * @param string the C++ std::string
     * @param treat_empty_as_null true if empty strings should be treated as null
     *
     * @return the Java UTF string, or nullptr if the provided string
     *     is null (or empty and treat_empty_as_null is set), or if an
     *     exception occurs allocating the Java String.
     */
    static jstring toJavaString(JNIEnv* env, const std::string* string,
        const bool treat_empty_as_null = false) {
      if (string == nullptr) {
        return nullptr;
      }

      if (treat_empty_as_null && string->empty()) {
        return nullptr;
      }

      return env->NewStringUTF(string->c_str());
    }

    /**
      * Copies bytes to a new jByteArray with the check of java array size limitation.
      *
      * @param bytes pointer to memory to copy to a new jByteArray
      * @param size number of bytes to copy
      *
      * @return the Java byte[], or nullptr if an exception occurs
      *
      * @throws RocksDBException thrown
      *   if memory size to copy exceeds general java array size limitation to avoid overflow.
      */
    static jbyteArray createJavaByteArrayWithSizeCheck(JNIEnv* env, const char* bytes, const size_t size) {
      // Limitation for java array size is vm specific
      // In general it cannot exceed Integer.MAX_VALUE (2^31 - 1)
      // Current HotSpot VM limitation for array size is Integer.MAX_VALUE - 5 (2^31 - 1 - 5)
      // It means that the next call to env->NewByteArray can still end with
      // OutOfMemoryError("Requested array size exceeds VM limit") coming from VM
      static const size_t MAX_JARRAY_SIZE = (static_cast<size_t>(1)) << 31;
      if(size > MAX_JARRAY_SIZE) {
        ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(
            env, "Requested array size exceeds VM limit");
        return nullptr;
      }

      const jsize jlen = static_cast<jsize>(size);
      jbyteArray jbytes = env->NewByteArray(jlen);
      if(jbytes == nullptr) {
        // exception thrown: OutOfMemoryError
        return nullptr;
      }

      env->SetByteArrayRegion(jbytes, 0, jlen,
        const_cast<jbyte*>(reinterpret_cast<const jbyte*>(bytes)));
      if(env->ExceptionCheck()) {
        // exception thrown: ArrayIndexOutOfBoundsException
        env->DeleteLocalRef(jbytes);
        return nullptr;
      }

      return jbytes;
    }

    /**
     * Copies bytes from a ROCKSDB_NAMESPACE::Slice to a jByteArray
     *
     * @param env A pointer to the java environment
     * @param bytes The bytes to copy
     *
     * @return the Java byte[] or nullptr if an exception occurs
     *
     * @throws RocksDBException thrown
     *   if memory size to copy exceeds general java specific array size
     * limitation.
     */
    static jbyteArray copyBytes(JNIEnv* env, const Slice& bytes) {
      return createJavaByteArrayWithSizeCheck(env, bytes.data(), bytes.size());
    }

    /*
     * Helper for operations on a key and value
     * for example WriteBatch->Put
     *
     * TODO(AR) could be used for RocksDB->Put etc.
     */
    static std::unique_ptr<ROCKSDB_NAMESPACE::Status> kv_op(
        std::function<ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Slice,
                                                ROCKSDB_NAMESPACE::Slice)>
            op,
        JNIEnv* env, jobject /*jobj*/, jbyteArray jkey, jint jkey_len,
        jbyteArray jvalue, jint jvalue_len) {
      jbyte* key = env->GetByteArrayElements(jkey, nullptr);
      if(env->ExceptionCheck()) {
        // exception thrown: OutOfMemoryError
        return nullptr;
      }

      jbyte* value = env->GetByteArrayElements(jvalue, nullptr);
      if(env->ExceptionCheck()) {
        // exception thrown: OutOfMemoryError
        if(key != nullptr) {
          env->ReleaseByteArrayElements(jkey, key, JNI_ABORT);
        }
        return nullptr;
      }

      ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast<char*>(key),
                                         jkey_len);
      ROCKSDB_NAMESPACE::Slice value_slice(reinterpret_cast<char*>(value),
                                           jvalue_len);

      auto status = op(key_slice, value_slice);

      if(value != nullptr) {
        env->ReleaseByteArrayElements(jvalue, value, JNI_ABORT);
      }
      if(key != nullptr) {
        env->ReleaseByteArrayElements(jkey, key, JNI_ABORT);
      }

      return std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
          new ROCKSDB_NAMESPACE::Status(status));
    }

    /*
     * Helper for operations on a key
     * for example WriteBatch->Delete
     *
     * TODO(AR) could be used for RocksDB->Delete etc.
     */
    static std::unique_ptr<ROCKSDB_NAMESPACE::Status> k_op(
        std::function<ROCKSDB_NAMESPACE::Status(ROCKSDB_NAMESPACE::Slice)> op,
        JNIEnv* env, jobject /*jobj*/, jbyteArray jkey, jint jkey_len) {
      jbyte* key = env->GetByteArrayElements(jkey, nullptr);
      if(env->ExceptionCheck()) {
        // exception thrown: OutOfMemoryError
        return nullptr;
      }

      ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast<char*>(key),
                                         jkey_len);

      auto status = op(key_slice);

      if(key != nullptr) {
        env->ReleaseByteArrayElements(jkey, key, JNI_ABORT);
      }

      return std::unique_ptr<ROCKSDB_NAMESPACE::Status>(
          new ROCKSDB_NAMESPACE::Status(status));
    }

    /*
     * Helper for operations on a value
     * for example WriteBatchWithIndex->GetFromBatch
     */
    static jbyteArray v_op(std::function<ROCKSDB_NAMESPACE::Status(
                               ROCKSDB_NAMESPACE::Slice, std::string*)>
                               op,
                           JNIEnv* env, jbyteArray jkey, jint jkey_len) {
      jbyte* key = env->GetByteArrayElements(jkey, nullptr);
      if(env->ExceptionCheck()) {
        // exception thrown: OutOfMemoryError
        return nullptr;
      }

      ROCKSDB_NAMESPACE::Slice key_slice(reinterpret_cast<char*>(key),
                                         jkey_len);

      std::string value;
      ROCKSDB_NAMESPACE::Status s = op(key_slice, &value);

      if(key != nullptr) {
        env->ReleaseByteArrayElements(jkey, key, JNI_ABORT);
      }

      if (s.IsNotFound()) {
        return nullptr;
      }

      if (s.ok()) {
        jbyteArray jret_value =
            env->NewByteArray(static_cast<jsize>(value.size()));
        if(jret_value == nullptr) {
          // exception thrown: OutOfMemoryError
          return nullptr;
        }

        env->SetByteArrayRegion(jret_value, 0, static_cast<jsize>(value.size()),
                                const_cast<jbyte*>(reinterpret_cast<const jbyte*>(value.c_str())));
        if(env->ExceptionCheck()) {
          // exception thrown: ArrayIndexOutOfBoundsException
          if(jret_value != nullptr) {
            env->DeleteLocalRef(jret_value);
          }
          return nullptr;
        }

        return jret_value;
      }

      ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(env, s);
      return nullptr;
    }

    /**
     * Creates a vector<T*> of C++ pointers from
     *     a Java array of C++ pointer addresses.
     *
     * @param env (IN) A pointer to the java environment
     * @param pointers (IN) A Java array of C++ pointer addresses
     * @param has_exception (OUT) will be set to JNI_TRUE
     *     if an ArrayIndexOutOfBoundsException or OutOfMemoryError
     *     exception occurs.
     *
     * @return A vector of C++ pointers.
     */
    template<typename T> static std::vector<T*> fromJPointers(
        JNIEnv* env, jlongArray jptrs, jboolean *has_exception) {
      const jsize jptrs_len = env->GetArrayLength(jptrs);
      std::vector<T*> ptrs;
      jlong* jptr = env->GetLongArrayElements(jptrs, nullptr);
      if (jptr == nullptr) {
        // exception thrown: OutOfMemoryError
        *has_exception = JNI_TRUE;
        return ptrs;
      }
      ptrs.reserve(jptrs_len);
      for (jsize i = 0; i < jptrs_len; i++) {
        ptrs.push_back(reinterpret_cast<T*>(jptr[i]));
      }
      env->ReleaseLongArrayElements(jptrs, jptr, JNI_ABORT);
      return ptrs;
    }

    /**
     * Creates a Java array of C++ pointer addresses
     *     from a vector of C++ pointers.
     *
     * @param env (IN) A pointer to the java environment
     * @param pointers (IN) A vector of C++ pointers
     * @param has_exception (OUT) will be set to JNI_TRUE
     *     if an ArrayIndexOutOfBoundsException or OutOfMemoryError
     *     exception occurs
     *
     * @return Java array of C++ pointer addresses.
     */
    template<typename T> static jlongArray toJPointers(JNIEnv* env,
        const std::vector<T*> &pointers,
        jboolean *has_exception) {
      const jsize len = static_cast<jsize>(pointers.size());
      std::unique_ptr<jlong[]> results(new jlong[len]);
      std::transform(pointers.begin(), pointers.end(), results.get(), [](T* pointer) -> jlong {
        return reinterpret_cast<jlong>(pointer);
      });

      jlongArray jpointers = env->NewLongArray(len);
      if (jpointers == nullptr) {
        // exception thrown: OutOfMemoryError
        *has_exception = JNI_TRUE;
        return nullptr;
      }

      env->SetLongArrayRegion(jpointers, 0, len, results.get());
      if (env->ExceptionCheck()) {
        // exception thrown: ArrayIndexOutOfBoundsException
        *has_exception = JNI_TRUE;
        env->DeleteLocalRef(jpointers);
        return nullptr;
      }

      *has_exception = JNI_FALSE;

      return jpointers;
    }

    /*
     * Helper for operations on a key and value
     * for example WriteBatch->Put
     *
     * TODO(AR) could be extended to cover returning ROCKSDB_NAMESPACE::Status
     * from `op` and used for RocksDB->Put etc.
     */
    static void kv_op_direct(std::function<void(ROCKSDB_NAMESPACE::Slice&,
                                                ROCKSDB_NAMESPACE::Slice&)>
                                 op,
                             JNIEnv* env, jobject jkey, jint jkey_off,
                             jint jkey_len, jobject jval, jint jval_off,
                             jint jval_len) {
      char* key = reinterpret_cast<char*>(env->GetDirectBufferAddress(jkey));
      if (key == nullptr ||
          env->GetDirectBufferCapacity(jkey) < (jkey_off + jkey_len)) {
        ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(
            env, "Invalid key argument");
        return;
      }

      char* value = reinterpret_cast<char*>(env->GetDirectBufferAddress(jval));
      if (value == nullptr ||
          env->GetDirectBufferCapacity(jval) < (jval_off + jval_len)) {
        ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(
            env, "Invalid value argument");
        return;
      }

      key += jkey_off;
      value += jval_off;

      ROCKSDB_NAMESPACE::Slice key_slice(key, jkey_len);
      ROCKSDB_NAMESPACE::Slice value_slice(value, jval_len);

      op(key_slice, value_slice);
    }

    /*
     * Helper for operations on a key and value
     * for example WriteBatch->Delete
     *
     * TODO(AR) could be extended to cover returning ROCKSDB_NAMESPACE::Status
     * from `op` and used for RocksDB->Delete etc.
     */
    static void k_op_direct(std::function<void(ROCKSDB_NAMESPACE::Slice&)> op,
                            JNIEnv* env, jobject jkey, jint jkey_off,
                            jint jkey_len) {
      char* key = reinterpret_cast<char*>(env->GetDirectBufferAddress(jkey));
      if (key == nullptr ||
          env->GetDirectBufferCapacity(jkey) < (jkey_off + jkey_len)) {
        ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(
            env, "Invalid key argument");
        return;
      }

      key += jkey_off;

      ROCKSDB_NAMESPACE::Slice key_slice(key, jkey_len);

      return op(key_slice);
    }

    template <class T>
    static jint copyToDirect(JNIEnv* env, T& source, jobject jtarget,
                             jint jtarget_off, jint jtarget_len) {
      char* target =
          reinterpret_cast<char*>(env->GetDirectBufferAddress(jtarget));
      if (target == nullptr ||
          env->GetDirectBufferCapacity(jtarget) < (jtarget_off + jtarget_len)) {
        ROCKSDB_NAMESPACE::RocksDBExceptionJni::ThrowNew(
            env, "Invalid target argument");
        return 0;
      }

      target += jtarget_off;

      const jint cvalue_len = static_cast<jint>(source.size());
      const jint length = std::min(jtarget_len, cvalue_len);

      memcpy(target, source.data(), length);

      return cvalue_len;
    }
};

class MapJni : public JavaClass {
 public:
  /**
   * Get the Java Class java.util.Map
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "java/util/Map");
  }

  /**
   * Get the Java Method: Map#put
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getMapPutMethodId(JNIEnv* env) {
    jclass jlist_clazz = getJClass(env);
    if(jlist_clazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid =
        env->GetMethodID(jlist_clazz, "put", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
    assert(mid != nullptr);
    return mid;
  }
};

class HashMapJni : public JavaClass {
 public:
  /**
   * Get the Java Class java.util.HashMap
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "java/util/HashMap");
  }

  /**
   * Create a new Java java.util.HashMap object.
   *
   * @param env A pointer to the Java environment
   *
   * @return A reference to a Java java.util.HashMap object, or
   * nullptr if an an exception occurs
   */
  static jobject construct(JNIEnv* env, const uint32_t initial_capacity = 16) {
    jclass jclazz = getJClass(env);
    if (jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    jmethodID mid = env->GetMethodID(jclazz, "<init>", "(I)V");
    if (mid == nullptr) {
      // exception thrown: NoSuchMethodException or OutOfMemoryError
      return nullptr;
    }

    jobject jhash_map = env->NewObject(jclazz, mid, static_cast<jint>(initial_capacity));
    if (env->ExceptionCheck()) {
      return nullptr;
    }

    return jhash_map;
  }

  /**
   * A function which maps a std::pair<K,V> to a std::pair<JK, JV>
   *
   * @return Either a pointer to a std::pair<jobject, jobject>, or nullptr
   *     if an error occurs during the mapping
   */
  template <typename K, typename V, typename JK, typename JV>
  using FnMapKV = std::function<std::unique_ptr<std::pair<JK, JV>> (const std::pair<K, V>&)>;

  // template <class I, typename K, typename V, typename K1, typename V1, typename std::enable_if<std::is_same<typename std::iterator_traits<I>::value_type, std::pair<const K,V>>::value, int32_t>::type = 0>
  // static void putAll(JNIEnv* env, const jobject jhash_map, I iterator, const FnMapKV<const K,V,K1,V1> &fn_map_kv) {
  /**
   * Returns true if it succeeds, false if an error occurs
   */
  template<class iterator_type, typename K, typename V>
  static bool putAll(JNIEnv* env, const jobject jhash_map, iterator_type iterator, iterator_type end, const FnMapKV<K, V, jobject, jobject> &fn_map_kv) {
    const jmethodID jmid_put =
        ROCKSDB_NAMESPACE::MapJni::getMapPutMethodId(env);
    if (jmid_put == nullptr) {
      return false;
    }

    for (auto it = iterator; it != end; ++it) {
      const std::unique_ptr<std::pair<jobject, jobject>> result = fn_map_kv(*it);
      if (result == nullptr) {
          // an error occurred during fn_map_kv
          return false;
      }
      env->CallObjectMethod(jhash_map, jmid_put, result->first, result->second);
      if (env->ExceptionCheck()) {
        // exception occurred
        env->DeleteLocalRef(result->second);
        env->DeleteLocalRef(result->first);
        return false;
      }

      // release local references
      env->DeleteLocalRef(result->second);
      env->DeleteLocalRef(result->first);
    }

    return true;
  }

  /**
   * Creates a java.util.Map<String, String> from a std::map<std::string, std::string>
   *
   * @param env A pointer to the Java environment
   * @param map the Cpp map
   *
   * @return a reference to the Java java.util.Map object, or nullptr if an exception occcurred
   */
  static jobject fromCppMap(JNIEnv* env, const std::map<std::string, std::string>* map) {
    if (map == nullptr) {
      return nullptr;
    }

    jobject jhash_map = construct(env, static_cast<uint32_t>(map->size()));
    if (jhash_map == nullptr) {
      // exception occurred
      return nullptr;
    }

    const ROCKSDB_NAMESPACE::HashMapJni::FnMapKV<
        const std::string, const std::string, jobject, jobject>
        fn_map_kv =
            [env](const std::pair<const std::string, const std::string>& kv) {
              jstring jkey = ROCKSDB_NAMESPACE::JniUtil::toJavaString(
                  env, &(kv.first), false);
              if (env->ExceptionCheck()) {
                // an error occurred
                return std::unique_ptr<std::pair<jobject, jobject>>(nullptr);
              }

              jstring jvalue = ROCKSDB_NAMESPACE::JniUtil::toJavaString(
                  env, &(kv.second), true);
              if (env->ExceptionCheck()) {
                // an error occurred
                env->DeleteLocalRef(jkey);
                return std::unique_ptr<std::pair<jobject, jobject>>(nullptr);
              }

              return std::unique_ptr<std::pair<jobject, jobject>>(
                  new std::pair<jobject, jobject>(
                      static_cast<jobject>(jkey),
                      static_cast<jobject>(jvalue)));
            };

    if (!putAll(env, jhash_map, map->begin(), map->end(), fn_map_kv)) {
      // exception occurred
      return nullptr;
    }

    return jhash_map;
  }

  /**
   * Creates a java.util.Map<String, Long> from a std::map<std::string, uint32_t>
   *
   * @param env A pointer to the Java environment
   * @param map the Cpp map
   *
   * @return a reference to the Java java.util.Map object, or nullptr if an exception occcurred
   */
  static jobject fromCppMap(JNIEnv* env, const std::map<std::string, uint32_t>* map) {
    if (map == nullptr) {
      return nullptr;
    }

    if (map == nullptr) {
      return nullptr;
    }

    jobject jhash_map = construct(env, static_cast<uint32_t>(map->size()));
    if (jhash_map == nullptr) {
      // exception occurred
      return nullptr;
    }

    const ROCKSDB_NAMESPACE::HashMapJni::FnMapKV<
        const std::string, const uint32_t, jobject, jobject>
        fn_map_kv =
            [env](const std::pair<const std::string, const uint32_t>& kv) {
              jstring jkey = ROCKSDB_NAMESPACE::JniUtil::toJavaString(
                  env, &(kv.first), false);
              if (env->ExceptionCheck()) {
                // an error occurred
                return std::unique_ptr<std::pair<jobject, jobject>>(nullptr);
              }

              jobject jvalue = ROCKSDB_NAMESPACE::IntegerJni::valueOf(
                  env, static_cast<jint>(kv.second));
              if (env->ExceptionCheck()) {
                // an error occurred
                env->DeleteLocalRef(jkey);
                return std::unique_ptr<std::pair<jobject, jobject>>(nullptr);
              }

              return std::unique_ptr<std::pair<jobject, jobject>>(
                  new std::pair<jobject, jobject>(static_cast<jobject>(jkey),
                                                  jvalue));
            };

    if (!putAll(env, jhash_map, map->begin(), map->end(), fn_map_kv)) {
      // exception occurred
      return nullptr;
    }

    return jhash_map;
  }

  /**
   * Creates a java.util.Map<String, Long> from a std::map<std::string, uint64_t>
   *
   * @param env A pointer to the Java environment
   * @param map the Cpp map
   *
   * @return a reference to the Java java.util.Map object, or nullptr if an exception occcurred
   */
  static jobject fromCppMap(JNIEnv* env, const std::map<std::string, uint64_t>* map) {
    if (map == nullptr) {
      return nullptr;
    }

    jobject jhash_map = construct(env, static_cast<uint32_t>(map->size()));
    if (jhash_map == nullptr) {
      // exception occurred
      return nullptr;
    }

    const ROCKSDB_NAMESPACE::HashMapJni::FnMapKV<
        const std::string, const uint64_t, jobject, jobject>
        fn_map_kv =
            [env](const std::pair<const std::string, const uint64_t>& kv) {
              jstring jkey = ROCKSDB_NAMESPACE::JniUtil::toJavaString(
                  env, &(kv.first), false);
              if (env->ExceptionCheck()) {
                // an error occurred
                return std::unique_ptr<std::pair<jobject, jobject>>(nullptr);
              }

              jobject jvalue = ROCKSDB_NAMESPACE::LongJni::valueOf(
                  env, static_cast<jlong>(kv.second));
              if (env->ExceptionCheck()) {
                // an error occurred
                env->DeleteLocalRef(jkey);
                return std::unique_ptr<std::pair<jobject, jobject>>(nullptr);
              }

              return std::unique_ptr<std::pair<jobject, jobject>>(
                  new std::pair<jobject, jobject>(static_cast<jobject>(jkey),
                                                  jvalue));
            };

    if (!putAll(env, jhash_map, map->begin(), map->end(), fn_map_kv)) {
      // exception occurred
      return nullptr;
    }

    return jhash_map;
  }

    /**
   * Creates a java.util.Map<String, Long> from a std::map<uint32_t, uint64_t>
   *
   * @param env A pointer to the Java environment
   * @param map the Cpp map
   *
   * @return a reference to the Java java.util.Map object, or nullptr if an exception occcurred
   */
  static jobject fromCppMap(JNIEnv* env, const std::map<uint32_t, uint64_t>* map) {
    if (map == nullptr) {
      return nullptr;
    }

    jobject jhash_map = construct(env, static_cast<uint32_t>(map->size()));
    if (jhash_map == nullptr) {
      // exception occurred
      return nullptr;
    }

    const ROCKSDB_NAMESPACE::HashMapJni::FnMapKV<const uint32_t, const uint64_t,
                                                 jobject, jobject>
        fn_map_kv = [env](const std::pair<const uint32_t, const uint64_t>& kv) {
          jobject jkey = ROCKSDB_NAMESPACE::IntegerJni::valueOf(
              env, static_cast<jint>(kv.first));
          if (env->ExceptionCheck()) {
            // an error occurred
            return std::unique_ptr<std::pair<jobject, jobject>>(nullptr);
          }

          jobject jvalue = ROCKSDB_NAMESPACE::LongJni::valueOf(
              env, static_cast<jlong>(kv.second));
          if (env->ExceptionCheck()) {
            // an error occurred
            env->DeleteLocalRef(jkey);
            return std::unique_ptr<std::pair<jobject, jobject>>(nullptr);
          }

          return std::unique_ptr<std::pair<jobject, jobject>>(
              new std::pair<jobject, jobject>(static_cast<jobject>(jkey),
                                              jvalue));
        };

    if (!putAll(env, jhash_map, map->begin(), map->end(), fn_map_kv)) {
      // exception occurred
      return nullptr;
    }

    return jhash_map;
  }
};

// The portal class for org.rocksdb.RocksDB
class RocksDBJni
    : public RocksDBNativeClass<ROCKSDB_NAMESPACE::DB*, RocksDBJni> {
 public:
  /**
   * Get the Java Class org.rocksdb.RocksDB
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return RocksDBNativeClass::getJClass(env, "org/rocksdb/RocksDB");
  }
};

// The portal class for org.rocksdb.Options
class OptionsJni
    : public RocksDBNativeClass<ROCKSDB_NAMESPACE::Options*, OptionsJni> {
 public:
  /**
   * Get the Java Class org.rocksdb.Options
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return RocksDBNativeClass::getJClass(env, "org/rocksdb/Options");
  }
};

// The portal class for org.rocksdb.DBOptions
class DBOptionsJni
    : public RocksDBNativeClass<ROCKSDB_NAMESPACE::DBOptions*, DBOptionsJni> {
 public:
  /**
   * Get the Java Class org.rocksdb.DBOptions
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return RocksDBNativeClass::getJClass(env, "org/rocksdb/DBOptions");
  }
};

// The portal class for org.rocksdb.ColumnFamilyOptions
class ColumnFamilyOptionsJni
    : public RocksDBNativeClass<ROCKSDB_NAMESPACE::ColumnFamilyOptions*,
                                ColumnFamilyOptionsJni> {
 public:
  /**
   * Get the Java Class org.rocksdb.ColumnFamilyOptions
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return RocksDBNativeClass::getJClass(env,
                                         "org/rocksdb/ColumnFamilyOptions");
  }

  /**
   * Create a new Java org.rocksdb.ColumnFamilyOptions object with the same
   * properties as the provided C++ ROCKSDB_NAMESPACE::ColumnFamilyOptions
   * object
   *
   * @param env A pointer to the Java environment
   * @param cfoptions A pointer to ROCKSDB_NAMESPACE::ColumnFamilyOptions object
   *
   * @return A reference to a Java org.rocksdb.ColumnFamilyOptions object, or
   * nullptr if an an exception occurs
   */
  static jobject construct(JNIEnv* env, const ColumnFamilyOptions* cfoptions) {
    auto* cfo = new ROCKSDB_NAMESPACE::ColumnFamilyOptions(*cfoptions);
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    jmethodID mid = env->GetMethodID(jclazz, "<init>", "(J)V");
    if (mid == nullptr) {
      // exception thrown: NoSuchMethodException or OutOfMemoryError
      return nullptr;
    }

    jobject jcfd = env->NewObject(jclazz, mid, reinterpret_cast<jlong>(cfo));
    if (env->ExceptionCheck()) {
      return nullptr;
    }

    return jcfd;
  }
};

// The portal class for org.rocksdb.WriteOptions
class WriteOptionsJni
    : public RocksDBNativeClass<ROCKSDB_NAMESPACE::WriteOptions*,
                                WriteOptionsJni> {
 public:
  /**
   * Get the Java Class org.rocksdb.WriteOptions
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return RocksDBNativeClass::getJClass(env, "org/rocksdb/WriteOptions");
  }
};

// The portal class for org.rocksdb.ReadOptions
class ReadOptionsJni
    : public RocksDBNativeClass<ROCKSDB_NAMESPACE::ReadOptions*,
                                ReadOptionsJni> {
 public:
  /**
   * Get the Java Class org.rocksdb.ReadOptions
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return RocksDBNativeClass::getJClass(env, "org/rocksdb/ReadOptions");
  }
};

// The portal class for org.rocksdb.WriteBatch
class WriteBatchJni
    : public RocksDBNativeClass<ROCKSDB_NAMESPACE::WriteBatch*, WriteBatchJni> {
 public:
  /**
   * Get the Java Class org.rocksdb.WriteBatch
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return RocksDBNativeClass::getJClass(env, "org/rocksdb/WriteBatch");
  }

  /**
   * Create a new Java org.rocksdb.WriteBatch object
   *
   * @param env A pointer to the Java environment
   * @param wb A pointer to ROCKSDB_NAMESPACE::WriteBatch object
   *
   * @return A reference to a Java org.rocksdb.WriteBatch object, or
   * nullptr if an an exception occurs
   */
  static jobject construct(JNIEnv* env, const WriteBatch* wb) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    jmethodID mid = env->GetMethodID(jclazz, "<init>", "(J)V");
    if (mid == nullptr) {
      // exception thrown: NoSuchMethodException or OutOfMemoryError
      return nullptr;
    }

    jobject jwb = env->NewObject(jclazz, mid, reinterpret_cast<jlong>(wb));
    if (env->ExceptionCheck()) {
      return nullptr;
    }

    return jwb;
  }
};

// The portal class for org.rocksdb.WriteBatch.Handler
class WriteBatchHandlerJni
    : public RocksDBNativeClass<
          const ROCKSDB_NAMESPACE::WriteBatchHandlerJniCallback*,
          WriteBatchHandlerJni> {
 public:
  /**
   * Get the Java Class org.rocksdb.WriteBatch.Handler
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return RocksDBNativeClass::getJClass(env,
        "org/rocksdb/WriteBatch$Handler");
  }

  /**
   * Get the Java Method: WriteBatch.Handler#put
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getPutCfMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(jclazz, "put", "(I[B[B)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: WriteBatch.Handler#put
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getPutMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(jclazz, "put", "([B[B)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: WriteBatch.Handler#merge
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getMergeCfMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(jclazz, "merge", "(I[B[B)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: WriteBatch.Handler#merge
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getMergeMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(jclazz, "merge", "([B[B)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: WriteBatch.Handler#delete
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getDeleteCfMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(jclazz, "delete", "(I[B)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: WriteBatch.Handler#delete
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getDeleteMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(jclazz, "delete", "([B)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: WriteBatch.Handler#singleDelete
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getSingleDeleteCfMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(jclazz, "singleDelete", "(I[B)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: WriteBatch.Handler#singleDelete
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getSingleDeleteMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(jclazz, "singleDelete", "([B)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: WriteBatch.Handler#deleteRange
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getDeleteRangeCfMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if (jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(jclazz, "deleteRange", "(I[B[B)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: WriteBatch.Handler#deleteRange
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getDeleteRangeMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if (jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(jclazz, "deleteRange", "([B[B)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: WriteBatch.Handler#logData
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getLogDataMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(jclazz, "logData", "([B)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: WriteBatch.Handler#putBlobIndex
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getPutBlobIndexCfMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(jclazz, "putBlobIndex", "(I[B[B)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: WriteBatch.Handler#markBeginPrepare
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getMarkBeginPrepareMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(jclazz, "markBeginPrepare", "()V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: WriteBatch.Handler#markEndPrepare
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getMarkEndPrepareMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(jclazz, "markEndPrepare", "([B)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: WriteBatch.Handler#markNoop
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getMarkNoopMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(jclazz, "markNoop", "(Z)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: WriteBatch.Handler#markRollback
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getMarkRollbackMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(jclazz, "markRollback", "([B)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: WriteBatch.Handler#markCommit
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getMarkCommitMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(jclazz, "markCommit", "([B)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: WriteBatch.Handler#shouldContinue
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getContinueMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(jclazz, "shouldContinue", "()Z");
    assert(mid != nullptr);
    return mid;
  }
};

class WriteBatchSavePointJni : public JavaClass {
 public:
  /**
   * Get the Java Class org.rocksdb.WriteBatch.SavePoint
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "org/rocksdb/WriteBatch$SavePoint");
  }

  /**
   * Get the Java Method: HistogramData constructor
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getConstructorMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(jclazz, "<init>", "(JJJ)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Create a new Java org.rocksdb.WriteBatch.SavePoint object
   *
   * @param env A pointer to the Java environment
   * @param savePoint A pointer to ROCKSDB_NAMESPACE::WriteBatch::SavePoint
   * object
   *
   * @return A reference to a Java org.rocksdb.WriteBatch.SavePoint object, or
   * nullptr if an an exception occurs
   */
  static jobject construct(JNIEnv* env, const SavePoint &save_point) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    jmethodID mid = getConstructorMethodId(env);
    if (mid == nullptr) {
      // exception thrown: NoSuchMethodException or OutOfMemoryError
      return nullptr;
    }

    jobject jsave_point = env->NewObject(jclazz, mid,
        static_cast<jlong>(save_point.size),
        static_cast<jlong>(save_point.count),
        static_cast<jlong>(save_point.content_flags));
    if (env->ExceptionCheck()) {
      return nullptr;
    }

    return jsave_point;
  }
};

// The portal class for org.rocksdb.WriteBatchWithIndex
class WriteBatchWithIndexJni
    : public RocksDBNativeClass<ROCKSDB_NAMESPACE::WriteBatchWithIndex*,
                                WriteBatchWithIndexJni> {
 public:
  /**
   * Get the Java Class org.rocksdb.WriteBatchWithIndex
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return RocksDBNativeClass::getJClass(env,
        "org/rocksdb/WriteBatchWithIndex");
  }
};

// The portal class for org.rocksdb.HistogramData
class HistogramDataJni : public JavaClass {
 public:
  /**
   * Get the Java Class org.rocksdb.HistogramData
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "org/rocksdb/HistogramData");
  }

  /**
   * Get the Java Method: HistogramData constructor
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getConstructorMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(jclazz, "<init>", "(DDDDDDJJD)V");
    assert(mid != nullptr);
    return mid;
  }
};

// The portal class for org.rocksdb.BackupableDBOptions
class BackupableDBOptionsJni
    : public RocksDBNativeClass<ROCKSDB_NAMESPACE::BackupableDBOptions*,
                                BackupableDBOptionsJni> {
 public:
  /**
   * Get the Java Class org.rocksdb.BackupableDBOptions
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return RocksDBNativeClass::getJClass(env,
        "org/rocksdb/BackupableDBOptions");
  }
};

// The portal class for org.rocksdb.BackupEngine
class BackupEngineJni
    : public RocksDBNativeClass<ROCKSDB_NAMESPACE::BackupEngine*,
                                BackupEngineJni> {
 public:
  /**
   * Get the Java Class org.rocksdb.BackupableEngine
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return RocksDBNativeClass::getJClass(env, "org/rocksdb/BackupEngine");
  }
};

// The portal class for org.rocksdb.RocksIterator
class IteratorJni
    : public RocksDBNativeClass<ROCKSDB_NAMESPACE::Iterator*, IteratorJni> {
 public:
  /**
   * Get the Java Class org.rocksdb.RocksIterator
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return RocksDBNativeClass::getJClass(env, "org/rocksdb/RocksIterator");
  }
};

// The portal class for org.rocksdb.Filter
class FilterJni
    : public RocksDBNativeClass<
          std::shared_ptr<ROCKSDB_NAMESPACE::FilterPolicy>*, FilterJni> {
 public:
  /**
   * Get the Java Class org.rocksdb.Filter
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return RocksDBNativeClass::getJClass(env, "org/rocksdb/Filter");
  }
};

// The portal class for org.rocksdb.ColumnFamilyHandle
class ColumnFamilyHandleJni
    : public RocksDBNativeClass<ROCKSDB_NAMESPACE::ColumnFamilyHandle*,
                                ColumnFamilyHandleJni> {
 public:
  static jobject fromCppColumnFamilyHandle(
      JNIEnv* env, const ROCKSDB_NAMESPACE::ColumnFamilyHandle* info) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID ctor = getConstructorMethodId(env, jclazz);
    assert(ctor != nullptr);
    return env->NewObject(jclazz, ctor, reinterpret_cast<jlong>(info));
  }

  static jmethodID getConstructorMethodId(JNIEnv* env, jclass clazz) {
    return env->GetMethodID(clazz, "<init>", "(J)V");
  }

  /**
   * Get the Java Class org.rocksdb.ColumnFamilyHandle
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return RocksDBNativeClass::getJClass(env,
        "org/rocksdb/ColumnFamilyHandle");
  }
};

// The portal class for org.rocksdb.FlushOptions
class FlushOptionsJni
    : public RocksDBNativeClass<ROCKSDB_NAMESPACE::FlushOptions*,
                                FlushOptionsJni> {
 public:
  /**
   * Get the Java Class org.rocksdb.FlushOptions
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return RocksDBNativeClass::getJClass(env, "org/rocksdb/FlushOptions");
  }
};

// The portal class for org.rocksdb.ComparatorOptions
class ComparatorOptionsJni
    : public RocksDBNativeClass<
          ROCKSDB_NAMESPACE::ComparatorJniCallbackOptions*,
          ComparatorOptionsJni> {
 public:
  /**
   * Get the Java Class org.rocksdb.ComparatorOptions
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return RocksDBNativeClass::getJClass(env, "org/rocksdb/ComparatorOptions");
  }
};

// The portal class for org.rocksdb.AbstractCompactionFilterFactory
class AbstractCompactionFilterFactoryJni
    : public RocksDBNativeClass<
          const ROCKSDB_NAMESPACE::CompactionFilterFactoryJniCallback*,
          AbstractCompactionFilterFactoryJni> {
 public:
  /**
   * Get the Java Class org.rocksdb.AbstractCompactionFilterFactory
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return RocksDBNativeClass::getJClass(env,
        "org/rocksdb/AbstractCompactionFilterFactory");
  }

  /**
   * Get the Java Method: AbstractCompactionFilterFactory#name
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getNameMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(
        jclazz, "name", "()Ljava/lang/String;");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: AbstractCompactionFilterFactory#createCompactionFilter
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getCreateCompactionFilterMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(jclazz,
      "createCompactionFilter",
      "(ZZ)J");
    assert(mid != nullptr);
    return mid;
  }
};

// The portal class for org.rocksdb.AbstractTransactionNotifier
class AbstractTransactionNotifierJni
    : public RocksDBNativeClass<
          const ROCKSDB_NAMESPACE::TransactionNotifierJniCallback*,
          AbstractTransactionNotifierJni> {
 public:
  static jclass getJClass(JNIEnv* env) {
    return RocksDBNativeClass::getJClass(env,
        "org/rocksdb/AbstractTransactionNotifier");
  }

  // Get the java method `snapshotCreated`
  // of org.rocksdb.AbstractTransactionNotifier.
  static jmethodID getSnapshotCreatedMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(jclazz, "snapshotCreated", "(J)V");
    assert(mid != nullptr);
    return mid;
  }
};

// The portal class for org.rocksdb.AbstractComparatorJniBridge
class AbstractComparatorJniBridge : public JavaClass {
 public:
  /**
   * Get the Java Class org.rocksdb.AbstractComparatorJniBridge
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env,
        "org/rocksdb/AbstractComparatorJniBridge");
  }

  /**
   * Get the Java Method: Comparator#compareInternal
   *
   * @param env A pointer to the Java environment
   * @param jclazz the AbstractComparatorJniBridge class
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getCompareInternalMethodId(JNIEnv* env, jclass jclazz) {
    static jmethodID mid =
        env->GetStaticMethodID(jclazz, "compareInternal",
            "(Lorg/rocksdb/AbstractComparator;Ljava/nio/ByteBuffer;ILjava/nio/ByteBuffer;I)I");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: Comparator#findShortestSeparatorInternal
   *
   * @param env A pointer to the Java environment
   * @param jclazz the AbstractComparatorJniBridge class
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getFindShortestSeparatorInternalMethodId(JNIEnv* env, jclass jclazz) {
    static jmethodID mid =
        env->GetStaticMethodID(jclazz, "findShortestSeparatorInternal",
            "(Lorg/rocksdb/AbstractComparator;Ljava/nio/ByteBuffer;ILjava/nio/ByteBuffer;I)I");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: Comparator#findShortSuccessorInternal
   *
   * @param env A pointer to the Java environment
   * @param jclazz the AbstractComparatorJniBridge class
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getFindShortSuccessorInternalMethodId(JNIEnv* env, jclass jclazz) {
    static jmethodID mid =
        env->GetStaticMethodID(jclazz, "findShortSuccessorInternal",
            "(Lorg/rocksdb/AbstractComparator;Ljava/nio/ByteBuffer;I)I");
    assert(mid != nullptr);
    return mid;
  }
};

// The portal class for org.rocksdb.AbstractComparator
class AbstractComparatorJni
    : public RocksDBNativeClass<const ROCKSDB_NAMESPACE::ComparatorJniCallback*,
                                AbstractComparatorJni> {
 public:
  /**
   * Get the Java Class org.rocksdb.AbstractComparator
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return RocksDBNativeClass::getJClass(env,
        "org/rocksdb/AbstractComparator");
  }

  /**
   * Get the Java Method: Comparator#name
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getNameMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid =
        env->GetMethodID(jclazz, "name", "()Ljava/lang/String;");
    assert(mid != nullptr);
    return mid;
  }
};

// The portal class for org.rocksdb.AbstractSlice
class AbstractSliceJni
    : public NativeRocksMutableObject<const ROCKSDB_NAMESPACE::Slice*,
                                      AbstractSliceJni> {
 public:
  /**
   * Get the Java Class org.rocksdb.AbstractSlice
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return RocksDBNativeClass::getJClass(env, "org/rocksdb/AbstractSlice");
  }
};

// The portal class for org.rocksdb.Slice
class SliceJni
    : public NativeRocksMutableObject<const ROCKSDB_NAMESPACE::Slice*,
                                      AbstractSliceJni> {
 public:
  /**
   * Get the Java Class org.rocksdb.Slice
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return RocksDBNativeClass::getJClass(env, "org/rocksdb/Slice");
  }

  /**
   * Constructs a Slice object
   *
   * @param env A pointer to the Java environment
   *
   * @return A reference to a Java Slice object, or a nullptr if an
   *     exception occurs
   */
  static jobject construct0(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(jclazz, "<init>", "()V");
    if(mid == nullptr) {
      // exception occurred accessing method
      return nullptr;
    }

    jobject jslice = env->NewObject(jclazz, mid);
    if(env->ExceptionCheck()) {
      return nullptr;
    }

    return jslice;
  }
};

// The portal class for org.rocksdb.DirectSlice
class DirectSliceJni
    : public NativeRocksMutableObject<const ROCKSDB_NAMESPACE::Slice*,
                                      AbstractSliceJni> {
 public:
  /**
   * Get the Java Class org.rocksdb.DirectSlice
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return RocksDBNativeClass::getJClass(env, "org/rocksdb/DirectSlice");
  }

  /**
   * Constructs a DirectSlice object
   *
   * @param env A pointer to the Java environment
   *
   * @return A reference to a Java DirectSlice object, or a nullptr if an
   *     exception occurs
   */
  static jobject construct0(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(jclazz, "<init>", "()V");
    if(mid == nullptr) {
      // exception occurred accessing method
      return nullptr;
    }

    jobject jdirect_slice = env->NewObject(jclazz, mid);
    if(env->ExceptionCheck()) {
      return nullptr;
    }

    return jdirect_slice;
  }
};

// The portal class for org.rocksdb.BackupInfo
class BackupInfoJni : public JavaClass {
 public:
  /**
   * Get the Java Class org.rocksdb.BackupInfo
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "org/rocksdb/BackupInfo");
  }

  /**
   * Constructs a BackupInfo object
   *
   * @param env A pointer to the Java environment
   * @param backup_id id of the backup
   * @param timestamp timestamp of the backup
   * @param size size of the backup
   * @param number_files number of files related to the backup
   * @param app_metadata application specific metadata
   *
   * @return A reference to a Java BackupInfo object, or a nullptr if an
   *     exception occurs
   */
  static jobject construct0(JNIEnv* env, uint32_t backup_id, int64_t timestamp,
                            uint64_t size, uint32_t number_files,
                            const std::string& app_metadata) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid =
        env->GetMethodID(jclazz, "<init>", "(IJJILjava/lang/String;)V");
    if(mid == nullptr) {
      // exception occurred accessing method
      return nullptr;
    }

    jstring japp_metadata = nullptr;
    if (app_metadata != nullptr) {
      japp_metadata = env->NewStringUTF(app_metadata.c_str());
      if (japp_metadata == nullptr) {
        // exception occurred creating java string
        return nullptr;
      }
    }

    jobject jbackup_info = env->NewObject(jclazz, mid, backup_id, timestamp,
                                          size, number_files, japp_metadata);
    if(env->ExceptionCheck()) {
      env->DeleteLocalRef(japp_metadata);
      return nullptr;
    }

    return jbackup_info;
  }
};

class BackupInfoListJni {
 public:
  /**
   * Converts a C++ std::vector<BackupInfo> object to
   * a Java ArrayList<org.rocksdb.BackupInfo> object
   *
   * @param env A pointer to the Java environment
   * @param backup_infos A vector of BackupInfo
   *
   * @return Either a reference to a Java ArrayList object, or a nullptr
   *     if an exception occurs
   */
  static jobject getBackupInfo(JNIEnv* env,
      std::vector<BackupInfo> backup_infos) {
    jclass jarray_list_clazz =
        ROCKSDB_NAMESPACE::ListJni::getArrayListClass(env);
    if(jarray_list_clazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    jmethodID cstr_mid =
        ROCKSDB_NAMESPACE::ListJni::getArrayListConstructorMethodId(env);
    if(cstr_mid == nullptr) {
      // exception occurred accessing method
      return nullptr;
    }

    jmethodID add_mid = ROCKSDB_NAMESPACE::ListJni::getListAddMethodId(env);
    if(add_mid == nullptr) {
      // exception occurred accessing method
      return nullptr;
    }

    // create java list
    jobject jbackup_info_handle_list =
        env->NewObject(jarray_list_clazz, cstr_mid, backup_infos.size());
    if(env->ExceptionCheck()) {
      // exception occurred constructing object
      return nullptr;
    }

    // insert in java list
    auto end = backup_infos.end();
    for (auto it = backup_infos.begin(); it != end; ++it) {
      auto backup_info = *it;

      jobject obj = ROCKSDB_NAMESPACE::BackupInfoJni::construct0(
          env, backup_info.backup_id, backup_info.timestamp, backup_info.size,
          backup_info.number_files, backup_info.app_metadata);
      if(env->ExceptionCheck()) {
        // exception occurred constructing object
        if(obj != nullptr) {
          env->DeleteLocalRef(obj);
        }
        if(jbackup_info_handle_list != nullptr) {
          env->DeleteLocalRef(jbackup_info_handle_list);
        }
        return nullptr;
      }

      jboolean rs =
          env->CallBooleanMethod(jbackup_info_handle_list, add_mid, obj);
      if(env->ExceptionCheck() || rs == JNI_FALSE) {
        // exception occurred calling method, or could not add
        if(obj != nullptr) {
          env->DeleteLocalRef(obj);
        }
        if(jbackup_info_handle_list != nullptr) {
          env->DeleteLocalRef(jbackup_info_handle_list);
        }
        return nullptr;
      }
    }

    return jbackup_info_handle_list;
  }
};

// The portal class for org.rocksdb.WBWIRocksIterator
class WBWIRocksIteratorJni : public JavaClass {
 public:
  /**
   * Get the Java Class org.rocksdb.WBWIRocksIterator
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "org/rocksdb/WBWIRocksIterator");
  }

  /**
   * Get the Java Field: WBWIRocksIterator#entry
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Field ID or nullptr if the class or field id could not
   *     be retrieved
   */
  static jfieldID getWriteEntryField(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jfieldID fid =
        env->GetFieldID(jclazz, "entry",
            "Lorg/rocksdb/WBWIRocksIterator$WriteEntry;");
    assert(fid != nullptr);
    return fid;
  }

  /**
   * Gets the value of the WBWIRocksIterator#entry
   *
   * @param env A pointer to the Java environment
   * @param jwbwi_rocks_iterator A reference to a WBWIIterator
   *
   * @return A reference to a Java WBWIRocksIterator.WriteEntry object, or
   *     a nullptr if an exception occurs
   */
  static jobject getWriteEntry(JNIEnv* env, jobject jwbwi_rocks_iterator) {
    assert(jwbwi_rocks_iterator != nullptr);

    jfieldID jwrite_entry_field = getWriteEntryField(env);
    if(jwrite_entry_field == nullptr) {
      // exception occurred accessing the field
      return nullptr;
    }

    jobject jwe = env->GetObjectField(jwbwi_rocks_iterator, jwrite_entry_field);
    assert(jwe != nullptr);
    return jwe;
  }
};

// The portal class for org.rocksdb.WBWIRocksIterator.WriteType
class WriteTypeJni : public JavaClass {
 public:
  /**
   * Get the PUT enum field value of WBWIRocksIterator.WriteType
   *
   * @param env A pointer to the Java environment
   *
   * @return A reference to the enum field value or a nullptr if
   *     the enum field value could not be retrieved
   */
  static jobject PUT(JNIEnv* env) {
    return getEnum(env, "PUT");
  }

  /**
   * Get the MERGE enum field value of WBWIRocksIterator.WriteType
   *
   * @param env A pointer to the Java environment
   *
   * @return A reference to the enum field value or a nullptr if
   *     the enum field value could not be retrieved
   */
  static jobject MERGE(JNIEnv* env) {
    return getEnum(env, "MERGE");
  }

  /**
   * Get the DELETE enum field value of WBWIRocksIterator.WriteType
   *
   * @param env A pointer to the Java environment
   *
   * @return A reference to the enum field value or a nullptr if
   *     the enum field value could not be retrieved
   */
  static jobject DELETE(JNIEnv* env) {
    return getEnum(env, "DELETE");
  }

  /**
   * Get the LOG enum field value of WBWIRocksIterator.WriteType
   *
   * @param env A pointer to the Java environment
   *
   * @return A reference to the enum field value or a nullptr if
   *     the enum field value could not be retrieved
   */
  static jobject LOG(JNIEnv* env) {
    return getEnum(env, "LOG");
  }

  // Returns the equivalent org.rocksdb.WBWIRocksIterator.WriteType for the
  // provided C++ ROCKSDB_NAMESPACE::WriteType enum
  static jbyte toJavaWriteType(const ROCKSDB_NAMESPACE::WriteType& writeType) {
    switch (writeType) {
      case ROCKSDB_NAMESPACE::WriteType::kPutRecord:
        return 0x0;
      case ROCKSDB_NAMESPACE::WriteType::kMergeRecord:
        return 0x1;
      case ROCKSDB_NAMESPACE::WriteType::kDeleteRecord:
        return 0x2;
      case ROCKSDB_NAMESPACE::WriteType::kSingleDeleteRecord:
        return 0x3;
      case ROCKSDB_NAMESPACE::WriteType::kDeleteRangeRecord:
        return 0x4;
      case ROCKSDB_NAMESPACE::WriteType::kLogDataRecord:
        return 0x5;
      case ROCKSDB_NAMESPACE::WriteType::kXIDRecord:
        return 0x6;
      default:
        return 0x7F;  // undefined
    }
  }

 private:
  /**
   * Get the Java Class org.rocksdb.WBWIRocksIterator.WriteType
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "org/rocksdb/WBWIRocksIterator$WriteType");
  }

  /**
   * Get an enum field of org.rocksdb.WBWIRocksIterator.WriteType
   *
   * @param env A pointer to the Java environment
   * @param name The name of the enum field
   *
   * @return A reference to the enum field value or a nullptr if
   *     the enum field value could not be retrieved
   */
  static jobject getEnum(JNIEnv* env, const char name[]) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    jfieldID jfid =
        env->GetStaticFieldID(jclazz, name,
            "Lorg/rocksdb/WBWIRocksIterator$WriteType;");
    if(env->ExceptionCheck()) {
      // exception occurred while getting field
      return nullptr;
    } else if(jfid == nullptr) {
      return nullptr;
    }

    jobject jwrite_type = env->GetStaticObjectField(jclazz, jfid);
    assert(jwrite_type != nullptr);
    return jwrite_type;
  }
};

// The portal class for org.rocksdb.WBWIRocksIterator.WriteEntry
class WriteEntryJni : public JavaClass {
 public:
  /**
   * Get the Java Class org.rocksdb.WBWIRocksIterator.WriteEntry
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
    static jclass getJClass(JNIEnv* env) {
      return JavaClass::getJClass(env, "org/rocksdb/WBWIRocksIterator$WriteEntry");
    }
};

// The portal class for org.rocksdb.InfoLogLevel
class InfoLogLevelJni : public JavaClass {
 public:
    /**
     * Get the DEBUG_LEVEL enum field value of InfoLogLevel
     *
     * @param env A pointer to the Java environment
     *
     * @return A reference to the enum field value or a nullptr if
     *     the enum field value could not be retrieved
     */
    static jobject DEBUG_LEVEL(JNIEnv* env) {
      return getEnum(env, "DEBUG_LEVEL");
    }

    /**
     * Get the INFO_LEVEL enum field value of InfoLogLevel
     *
     * @param env A pointer to the Java environment
     *
     * @return A reference to the enum field value or a nullptr if
     *     the enum field value could not be retrieved
     */
    static jobject INFO_LEVEL(JNIEnv* env) {
      return getEnum(env, "INFO_LEVEL");
    }

    /**
     * Get the WARN_LEVEL enum field value of InfoLogLevel
     *
     * @param env A pointer to the Java environment
     *
     * @return A reference to the enum field value or a nullptr if
     *     the enum field value could not be retrieved
     */
    static jobject WARN_LEVEL(JNIEnv* env) {
      return getEnum(env, "WARN_LEVEL");
    }

    /**
     * Get the ERROR_LEVEL enum field value of InfoLogLevel
     *
     * @param env A pointer to the Java environment
     *
     * @return A reference to the enum field value or a nullptr if
     *     the enum field value could not be retrieved
     */
    static jobject ERROR_LEVEL(JNIEnv* env) {
      return getEnum(env, "ERROR_LEVEL");
    }

    /**
     * Get the FATAL_LEVEL enum field value of InfoLogLevel
     *
     * @param env A pointer to the Java environment
     *
     * @return A reference to the enum field value or a nullptr if
     *     the enum field value could not be retrieved
     */
    static jobject FATAL_LEVEL(JNIEnv* env) {
      return getEnum(env, "FATAL_LEVEL");
    }

    /**
     * Get the HEADER_LEVEL enum field value of InfoLogLevel
     *
     * @param env A pointer to the Java environment
     *
     * @return A reference to the enum field value or a nullptr if
     *     the enum field value could not be retrieved
     */
    static jobject HEADER_LEVEL(JNIEnv* env) {
      return getEnum(env, "HEADER_LEVEL");
    }

 private:
  /**
   * Get the Java Class org.rocksdb.InfoLogLevel
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "org/rocksdb/InfoLogLevel");
  }

  /**
   * Get an enum field of org.rocksdb.InfoLogLevel
   *
   * @param env A pointer to the Java environment
   * @param name The name of the enum field
   *
   * @return A reference to the enum field value or a nullptr if
   *     the enum field value could not be retrieved
   */
  static jobject getEnum(JNIEnv* env, const char name[]) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    jfieldID jfid =
        env->GetStaticFieldID(jclazz, name, "Lorg/rocksdb/InfoLogLevel;");
    if(env->ExceptionCheck()) {
      // exception occurred while getting field
      return nullptr;
    } else if(jfid == nullptr) {
      return nullptr;
    }

    jobject jinfo_log_level = env->GetStaticObjectField(jclazz, jfid);
    assert(jinfo_log_level != nullptr);
    return jinfo_log_level;
  }
};

// The portal class for org.rocksdb.Logger
class LoggerJni
    : public RocksDBNativeClass<
          std::shared_ptr<ROCKSDB_NAMESPACE::LoggerJniCallback>*, LoggerJni> {
 public:
  /**
   * Get the Java Class org/rocksdb/Logger
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return RocksDBNativeClass::getJClass(env, "org/rocksdb/Logger");
  }

  /**
   * Get the Java Method: Logger#log
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getLogMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid =
        env->GetMethodID(jclazz, "log",
            "(Lorg/rocksdb/InfoLogLevel;Ljava/lang/String;)V");
    assert(mid != nullptr);
    return mid;
  }
};

// The portal class for org.rocksdb.TransactionLogIterator.BatchResult
class BatchResultJni : public JavaClass {
  public:
  /**
   * Get the Java Class org.rocksdb.TransactionLogIterator.BatchResult
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env,
        "org/rocksdb/TransactionLogIterator$BatchResult");
  }

  /**
   * Create a new Java org.rocksdb.TransactionLogIterator.BatchResult object
   * with the same properties as the provided C++ ROCKSDB_NAMESPACE::BatchResult
   * object
   *
   * @param env A pointer to the Java environment
   * @param batch_result The ROCKSDB_NAMESPACE::BatchResult object
   *
   * @return A reference to a Java
   *     org.rocksdb.TransactionLogIterator.BatchResult object,
   *     or nullptr if an an exception occurs
   */
  static jobject construct(JNIEnv* env,
                           ROCKSDB_NAMESPACE::BatchResult& batch_result) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    jmethodID mid = env->GetMethodID(
      jclazz, "<init>", "(JJ)V");
    if(mid == nullptr) {
      // exception thrown: NoSuchMethodException or OutOfMemoryError
      return nullptr;
    }

    jobject jbatch_result = env->NewObject(jclazz, mid,
      batch_result.sequence, batch_result.writeBatchPtr.get());
    if(jbatch_result == nullptr) {
      // exception thrown: InstantiationException or OutOfMemoryError
      return nullptr;
    }

    batch_result.writeBatchPtr.release();
    return jbatch_result;
  }
};

// The portal class for org.rocksdb.BottommostLevelCompaction
class BottommostLevelCompactionJni {
 public:
  // Returns the equivalent org.rocksdb.BottommostLevelCompaction for the
  // provided C++ ROCKSDB_NAMESPACE::BottommostLevelCompaction enum
  static jint toJavaBottommostLevelCompaction(
      const ROCKSDB_NAMESPACE::BottommostLevelCompaction&
          bottommost_level_compaction) {
    switch(bottommost_level_compaction) {
      case ROCKSDB_NAMESPACE::BottommostLevelCompaction::kSkip:
        return 0x0;
      case ROCKSDB_NAMESPACE::BottommostLevelCompaction::
          kIfHaveCompactionFilter:
        return 0x1;
      case ROCKSDB_NAMESPACE::BottommostLevelCompaction::kForce:
        return 0x2;
      case ROCKSDB_NAMESPACE::BottommostLevelCompaction::kForceOptimized:
        return 0x3;
      default:
        return 0x7F;  // undefined
    }
  }

  // Returns the equivalent C++ ROCKSDB_NAMESPACE::BottommostLevelCompaction
  // enum for the provided Java org.rocksdb.BottommostLevelCompaction
  static ROCKSDB_NAMESPACE::BottommostLevelCompaction
  toCppBottommostLevelCompaction(jint bottommost_level_compaction) {
    switch(bottommost_level_compaction) {
      case 0x0:
        return ROCKSDB_NAMESPACE::BottommostLevelCompaction::kSkip;
      case 0x1:
        return ROCKSDB_NAMESPACE::BottommostLevelCompaction::
            kIfHaveCompactionFilter;
      case 0x2:
        return ROCKSDB_NAMESPACE::BottommostLevelCompaction::kForce;
      case 0x3:
        return ROCKSDB_NAMESPACE::BottommostLevelCompaction::kForceOptimized;
      default:
        // undefined/default
        return ROCKSDB_NAMESPACE::BottommostLevelCompaction::
            kIfHaveCompactionFilter;
    }
  }
};

// The portal class for org.rocksdb.CompactionStopStyle
class CompactionStopStyleJni {
 public:
  // Returns the equivalent org.rocksdb.CompactionStopStyle for the provided
  // C++ ROCKSDB_NAMESPACE::CompactionStopStyle enum
  static jbyte toJavaCompactionStopStyle(
      const ROCKSDB_NAMESPACE::CompactionStopStyle& compaction_stop_style) {
    switch(compaction_stop_style) {
      case ROCKSDB_NAMESPACE::CompactionStopStyle::
          kCompactionStopStyleSimilarSize:
        return 0x0;
      case ROCKSDB_NAMESPACE::CompactionStopStyle::
          kCompactionStopStyleTotalSize:
        return 0x1;
      default:
        return 0x7F;  // undefined
    }
  }

  // Returns the equivalent C++ ROCKSDB_NAMESPACE::CompactionStopStyle enum for
  // the provided Java org.rocksdb.CompactionStopStyle
  static ROCKSDB_NAMESPACE::CompactionStopStyle toCppCompactionStopStyle(
      jbyte jcompaction_stop_style) {
    switch(jcompaction_stop_style) {
      case 0x0:
        return ROCKSDB_NAMESPACE::CompactionStopStyle::
            kCompactionStopStyleSimilarSize;
      case 0x1:
        return ROCKSDB_NAMESPACE::CompactionStopStyle::
            kCompactionStopStyleTotalSize;
      default:
        // undefined/default
        return ROCKSDB_NAMESPACE::CompactionStopStyle::
            kCompactionStopStyleSimilarSize;
    }
  }
};

// The portal class for org.rocksdb.CompressionType
class CompressionTypeJni {
 public:
  // Returns the equivalent org.rocksdb.CompressionType for the provided
  // C++ ROCKSDB_NAMESPACE::CompressionType enum
  static jbyte toJavaCompressionType(
      const ROCKSDB_NAMESPACE::CompressionType& compression_type) {
    switch(compression_type) {
      case ROCKSDB_NAMESPACE::CompressionType::kNoCompression:
        return 0x0;
      case ROCKSDB_NAMESPACE::CompressionType::kSnappyCompression:
        return 0x1;
      case ROCKSDB_NAMESPACE::CompressionType::kZlibCompression:
        return 0x2;
      case ROCKSDB_NAMESPACE::CompressionType::kBZip2Compression:
        return 0x3;
      case ROCKSDB_NAMESPACE::CompressionType::kLZ4Compression:
        return 0x4;
      case ROCKSDB_NAMESPACE::CompressionType::kLZ4HCCompression:
        return 0x5;
      case ROCKSDB_NAMESPACE::CompressionType::kXpressCompression:
        return 0x6;
      case ROCKSDB_NAMESPACE::CompressionType::kZSTD:
        return 0x7;
      case ROCKSDB_NAMESPACE::CompressionType::kDisableCompressionOption:
      default:
        return 0x7F;
    }
  }

  // Returns the equivalent C++ ROCKSDB_NAMESPACE::CompressionType enum for the
  // provided Java org.rocksdb.CompressionType
  static ROCKSDB_NAMESPACE::CompressionType toCppCompressionType(
      jbyte jcompression_type) {
    switch(jcompression_type) {
      case 0x0:
        return ROCKSDB_NAMESPACE::CompressionType::kNoCompression;
      case 0x1:
        return ROCKSDB_NAMESPACE::CompressionType::kSnappyCompression;
      case 0x2:
        return ROCKSDB_NAMESPACE::CompressionType::kZlibCompression;
      case 0x3:
        return ROCKSDB_NAMESPACE::CompressionType::kBZip2Compression;
      case 0x4:
        return ROCKSDB_NAMESPACE::CompressionType::kLZ4Compression;
      case 0x5:
        return ROCKSDB_NAMESPACE::CompressionType::kLZ4HCCompression;
      case 0x6:
        return ROCKSDB_NAMESPACE::CompressionType::kXpressCompression;
      case 0x7:
        return ROCKSDB_NAMESPACE::CompressionType::kZSTD;
      case 0x7F:
      default:
        return ROCKSDB_NAMESPACE::CompressionType::kDisableCompressionOption;
    }
  }
};

// The portal class for org.rocksdb.CompactionPriority
class CompactionPriorityJni {
 public:
  // Returns the equivalent org.rocksdb.CompactionPriority for the provided
  // C++ ROCKSDB_NAMESPACE::CompactionPri enum
  static jbyte toJavaCompactionPriority(
      const ROCKSDB_NAMESPACE::CompactionPri& compaction_priority) {
    switch(compaction_priority) {
      case ROCKSDB_NAMESPACE::CompactionPri::kByCompensatedSize:
        return 0x0;
      case ROCKSDB_NAMESPACE::CompactionPri::kOldestLargestSeqFirst:
        return 0x1;
      case ROCKSDB_NAMESPACE::CompactionPri::kOldestSmallestSeqFirst:
        return 0x2;
      case ROCKSDB_NAMESPACE::CompactionPri::kMinOverlappingRatio:
        return 0x3;
      default:
        return 0x0;  // undefined
    }
  }

  // Returns the equivalent C++ ROCKSDB_NAMESPACE::CompactionPri enum for the
  // provided Java org.rocksdb.CompactionPriority
  static ROCKSDB_NAMESPACE::CompactionPri toCppCompactionPriority(
      jbyte jcompaction_priority) {
    switch(jcompaction_priority) {
      case 0x0:
        return ROCKSDB_NAMESPACE::CompactionPri::kByCompensatedSize;
      case 0x1:
        return ROCKSDB_NAMESPACE::CompactionPri::kOldestLargestSeqFirst;
      case 0x2:
        return ROCKSDB_NAMESPACE::CompactionPri::kOldestSmallestSeqFirst;
      case 0x3:
        return ROCKSDB_NAMESPACE::CompactionPri::kMinOverlappingRatio;
      default:
        // undefined/default
        return ROCKSDB_NAMESPACE::CompactionPri::kByCompensatedSize;
    }
  }
};

// The portal class for org.rocksdb.AccessHint
class AccessHintJni {
 public:
  // Returns the equivalent org.rocksdb.AccessHint for the provided
  // C++ ROCKSDB_NAMESPACE::DBOptions::AccessHint enum
  static jbyte toJavaAccessHint(
      const ROCKSDB_NAMESPACE::DBOptions::AccessHint& access_hint) {
    switch(access_hint) {
      case ROCKSDB_NAMESPACE::DBOptions::AccessHint::NONE:
        return 0x0;
      case ROCKSDB_NAMESPACE::DBOptions::AccessHint::NORMAL:
        return 0x1;
      case ROCKSDB_NAMESPACE::DBOptions::AccessHint::SEQUENTIAL:
        return 0x2;
      case ROCKSDB_NAMESPACE::DBOptions::AccessHint::WILLNEED:
        return 0x3;
      default:
        // undefined/default
        return 0x1;
    }
  }

  // Returns the equivalent C++ ROCKSDB_NAMESPACE::DBOptions::AccessHint enum
  // for the provided Java org.rocksdb.AccessHint
  static ROCKSDB_NAMESPACE::DBOptions::AccessHint toCppAccessHint(
      jbyte jaccess_hint) {
    switch(jaccess_hint) {
      case 0x0:
        return ROCKSDB_NAMESPACE::DBOptions::AccessHint::NONE;
      case 0x1:
        return ROCKSDB_NAMESPACE::DBOptions::AccessHint::NORMAL;
      case 0x2:
        return ROCKSDB_NAMESPACE::DBOptions::AccessHint::SEQUENTIAL;
      case 0x3:
        return ROCKSDB_NAMESPACE::DBOptions::AccessHint::WILLNEED;
      default:
        // undefined/default
        return ROCKSDB_NAMESPACE::DBOptions::AccessHint::NORMAL;
    }
  }
};

// The portal class for org.rocksdb.WALRecoveryMode
class WALRecoveryModeJni {
 public:
  // Returns the equivalent org.rocksdb.WALRecoveryMode for the provided
  // C++ ROCKSDB_NAMESPACE::WALRecoveryMode enum
  static jbyte toJavaWALRecoveryMode(
      const ROCKSDB_NAMESPACE::WALRecoveryMode& wal_recovery_mode) {
    switch(wal_recovery_mode) {
      case ROCKSDB_NAMESPACE::WALRecoveryMode::kTolerateCorruptedTailRecords:
        return 0x0;
      case ROCKSDB_NAMESPACE::WALRecoveryMode::kAbsoluteConsistency:
        return 0x1;
      case ROCKSDB_NAMESPACE::WALRecoveryMode::kPointInTimeRecovery:
        return 0x2;
      case ROCKSDB_NAMESPACE::WALRecoveryMode::kSkipAnyCorruptedRecords:
        return 0x3;
      default:
        // undefined/default
        return 0x2;
    }
  }

  // Returns the equivalent C++ ROCKSDB_NAMESPACE::WALRecoveryMode enum for the
  // provided Java org.rocksdb.WALRecoveryMode
  static ROCKSDB_NAMESPACE::WALRecoveryMode toCppWALRecoveryMode(
      jbyte jwal_recovery_mode) {
    switch(jwal_recovery_mode) {
      case 0x0:
        return ROCKSDB_NAMESPACE::WALRecoveryMode::
            kTolerateCorruptedTailRecords;
      case 0x1:
        return ROCKSDB_NAMESPACE::WALRecoveryMode::kAbsoluteConsistency;
      case 0x2:
        return ROCKSDB_NAMESPACE::WALRecoveryMode::kPointInTimeRecovery;
      case 0x3:
        return ROCKSDB_NAMESPACE::WALRecoveryMode::kSkipAnyCorruptedRecords;
      default:
        // undefined/default
        return ROCKSDB_NAMESPACE::WALRecoveryMode::kPointInTimeRecovery;
    }
  }
};

// The portal class for org.rocksdb.TickerType
class TickerTypeJni {
 public:
  // Returns the equivalent org.rocksdb.TickerType for the provided
  // C++ ROCKSDB_NAMESPACE::Tickers enum
  static jbyte toJavaTickerType(const ROCKSDB_NAMESPACE::Tickers& tickers) {
    switch(tickers) {
      case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_MISS:
        return 0x0;
      case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_HIT:
        return 0x1;
      case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_ADD:
        return 0x2;
      case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_ADD_FAILURES:
        return 0x3;
      case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_INDEX_MISS:
        return 0x4;
      case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_INDEX_HIT:
        return 0x5;
      case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_INDEX_ADD:
        return 0x6;
      case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_INDEX_BYTES_INSERT:
        return 0x7;
      case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_INDEX_BYTES_EVICT:
        return 0x8;
      case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_FILTER_MISS:
        return 0x9;
      case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_FILTER_HIT:
        return 0xA;
      case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_FILTER_ADD:
        return 0xB;
      case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_FILTER_BYTES_INSERT:
        return 0xC;
      case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_FILTER_BYTES_EVICT:
        return 0xD;
      case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_DATA_MISS:
        return 0xE;
      case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_DATA_HIT:
        return 0xF;
      case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_DATA_ADD:
        return 0x10;
      case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_DATA_BYTES_INSERT:
        return 0x11;
      case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_BYTES_READ:
        return 0x12;
      case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_BYTES_WRITE:
        return 0x13;
      case ROCKSDB_NAMESPACE::Tickers::BLOOM_FILTER_USEFUL:
        return 0x14;
      case ROCKSDB_NAMESPACE::Tickers::PERSISTENT_CACHE_HIT:
        return 0x15;
      case ROCKSDB_NAMESPACE::Tickers::PERSISTENT_CACHE_MISS:
        return 0x16;
      case ROCKSDB_NAMESPACE::Tickers::SIM_BLOCK_CACHE_HIT:
        return 0x17;
      case ROCKSDB_NAMESPACE::Tickers::SIM_BLOCK_CACHE_MISS:
        return 0x18;
      case ROCKSDB_NAMESPACE::Tickers::MEMTABLE_HIT:
        return 0x19;
      case ROCKSDB_NAMESPACE::Tickers::MEMTABLE_MISS:
        return 0x1A;
      case ROCKSDB_NAMESPACE::Tickers::GET_HIT_L0:
        return 0x1B;
      case ROCKSDB_NAMESPACE::Tickers::GET_HIT_L1:
        return 0x1C;
      case ROCKSDB_NAMESPACE::Tickers::GET_HIT_L2_AND_UP:
        return 0x1D;
      case ROCKSDB_NAMESPACE::Tickers::COMPACTION_KEY_DROP_NEWER_ENTRY:
        return 0x1E;
      case ROCKSDB_NAMESPACE::Tickers::COMPACTION_KEY_DROP_OBSOLETE:
        return 0x1F;
      case ROCKSDB_NAMESPACE::Tickers::COMPACTION_KEY_DROP_RANGE_DEL:
        return 0x20;
      case ROCKSDB_NAMESPACE::Tickers::COMPACTION_KEY_DROP_USER:
        return 0x21;
      case ROCKSDB_NAMESPACE::Tickers::COMPACTION_RANGE_DEL_DROP_OBSOLETE:
        return 0x22;
      case ROCKSDB_NAMESPACE::Tickers::NUMBER_KEYS_WRITTEN:
        return 0x23;
      case ROCKSDB_NAMESPACE::Tickers::NUMBER_KEYS_READ:
        return 0x24;
      case ROCKSDB_NAMESPACE::Tickers::NUMBER_KEYS_UPDATED:
        return 0x25;
      case ROCKSDB_NAMESPACE::Tickers::BYTES_WRITTEN:
        return 0x26;
      case ROCKSDB_NAMESPACE::Tickers::BYTES_READ:
        return 0x27;
      case ROCKSDB_NAMESPACE::Tickers::NUMBER_DB_SEEK:
        return 0x28;
      case ROCKSDB_NAMESPACE::Tickers::NUMBER_DB_NEXT:
        return 0x29;
      case ROCKSDB_NAMESPACE::Tickers::NUMBER_DB_PREV:
        return 0x2A;
      case ROCKSDB_NAMESPACE::Tickers::NUMBER_DB_SEEK_FOUND:
        return 0x2B;
      case ROCKSDB_NAMESPACE::Tickers::NUMBER_DB_NEXT_FOUND:
        return 0x2C;
      case ROCKSDB_NAMESPACE::Tickers::NUMBER_DB_PREV_FOUND:
        return 0x2D;
      case ROCKSDB_NAMESPACE::Tickers::ITER_BYTES_READ:
        return 0x2E;
      case ROCKSDB_NAMESPACE::Tickers::NO_FILE_CLOSES:
        return 0x2F;
      case ROCKSDB_NAMESPACE::Tickers::NO_FILE_OPENS:
        return 0x30;
      case ROCKSDB_NAMESPACE::Tickers::NO_FILE_ERRORS:
        return 0x31;
      case ROCKSDB_NAMESPACE::Tickers::STALL_L0_SLOWDOWN_MICROS:
        return 0x32;
      case ROCKSDB_NAMESPACE::Tickers::STALL_MEMTABLE_COMPACTION_MICROS:
        return 0x33;
      case ROCKSDB_NAMESPACE::Tickers::STALL_L0_NUM_FILES_MICROS:
        return 0x34;
      case ROCKSDB_NAMESPACE::Tickers::STALL_MICROS:
        return 0x35;
      case ROCKSDB_NAMESPACE::Tickers::DB_MUTEX_WAIT_MICROS:
        return 0x36;
      case ROCKSDB_NAMESPACE::Tickers::RATE_LIMIT_DELAY_MILLIS:
        return 0x37;
      case ROCKSDB_NAMESPACE::Tickers::NO_ITERATORS:
        return 0x38;
      case ROCKSDB_NAMESPACE::Tickers::NUMBER_MULTIGET_CALLS:
        return 0x39;
      case ROCKSDB_NAMESPACE::Tickers::NUMBER_MULTIGET_KEYS_READ:
        return 0x3A;
      case ROCKSDB_NAMESPACE::Tickers::NUMBER_MULTIGET_BYTES_READ:
        return 0x3B;
      case ROCKSDB_NAMESPACE::Tickers::NUMBER_FILTERED_DELETES:
        return 0x3C;
      case ROCKSDB_NAMESPACE::Tickers::NUMBER_MERGE_FAILURES:
        return 0x3D;
      case ROCKSDB_NAMESPACE::Tickers::BLOOM_FILTER_PREFIX_CHECKED:
        return 0x3E;
      case ROCKSDB_NAMESPACE::Tickers::BLOOM_FILTER_PREFIX_USEFUL:
        return 0x3F;
      case ROCKSDB_NAMESPACE::Tickers::NUMBER_OF_RESEEKS_IN_ITERATION:
        return 0x40;
      case ROCKSDB_NAMESPACE::Tickers::GET_UPDATES_SINCE_CALLS:
        return 0x41;
      case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_COMPRESSED_MISS:
        return 0x42;
      case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_COMPRESSED_HIT:
        return 0x43;
      case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_COMPRESSED_ADD:
        return 0x44;
      case ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_COMPRESSED_ADD_FAILURES:
        return 0x45;
      case ROCKSDB_NAMESPACE::Tickers::WAL_FILE_SYNCED:
        return 0x46;
      case ROCKSDB_NAMESPACE::Tickers::WAL_FILE_BYTES:
        return 0x47;
      case ROCKSDB_NAMESPACE::Tickers::WRITE_DONE_BY_SELF:
        return 0x48;
      case ROCKSDB_NAMESPACE::Tickers::WRITE_DONE_BY_OTHER:
        return 0x49;
      case ROCKSDB_NAMESPACE::Tickers::WRITE_TIMEDOUT:
        return 0x4A;
      case ROCKSDB_NAMESPACE::Tickers::WRITE_WITH_WAL:
        return 0x4B;
      case ROCKSDB_NAMESPACE::Tickers::COMPACT_READ_BYTES:
        return 0x4C;
      case ROCKSDB_NAMESPACE::Tickers::COMPACT_WRITE_BYTES:
        return 0x4D;
      case ROCKSDB_NAMESPACE::Tickers::FLUSH_WRITE_BYTES:
        return 0x4E;
      case ROCKSDB_NAMESPACE::Tickers::NUMBER_DIRECT_LOAD_TABLE_PROPERTIES:
        return 0x4F;
      case ROCKSDB_NAMESPACE::Tickers::NUMBER_SUPERVERSION_ACQUIRES:
        return 0x50;
      case ROCKSDB_NAMESPACE::Tickers::NUMBER_SUPERVERSION_RELEASES:
        return 0x51;
      case ROCKSDB_NAMESPACE::Tickers::NUMBER_SUPERVERSION_CLEANUPS:
        return 0x52;
      case ROCKSDB_NAMESPACE::Tickers::NUMBER_BLOCK_COMPRESSED:
        return 0x53;
      case ROCKSDB_NAMESPACE::Tickers::NUMBER_BLOCK_DECOMPRESSED:
        return 0x54;
      case ROCKSDB_NAMESPACE::Tickers::NUMBER_BLOCK_NOT_COMPRESSED:
        return 0x55;
      case ROCKSDB_NAMESPACE::Tickers::MERGE_OPERATION_TOTAL_TIME:
        return 0x56;
      case ROCKSDB_NAMESPACE::Tickers::FILTER_OPERATION_TOTAL_TIME:
        return 0x57;
      case ROCKSDB_NAMESPACE::Tickers::ROW_CACHE_HIT:
        return 0x58;
      case ROCKSDB_NAMESPACE::Tickers::ROW_CACHE_MISS:
        return 0x59;
      case ROCKSDB_NAMESPACE::Tickers::READ_AMP_ESTIMATE_USEFUL_BYTES:
        return 0x5A;
      case ROCKSDB_NAMESPACE::Tickers::READ_AMP_TOTAL_READ_BYTES:
        return 0x5B;
      case ROCKSDB_NAMESPACE::Tickers::NUMBER_RATE_LIMITER_DRAINS:
        return 0x5C;
      case ROCKSDB_NAMESPACE::Tickers::NUMBER_ITER_SKIP:
        return 0x5D;
      case ROCKSDB_NAMESPACE::Tickers::NUMBER_MULTIGET_KEYS_FOUND:
        return 0x5E;
      case ROCKSDB_NAMESPACE::Tickers::NO_ITERATOR_CREATED:
        // -0x01 so we can skip over the already taken 0x5F (TICKER_ENUM_MAX).
        return -0x01;
      case ROCKSDB_NAMESPACE::Tickers::NO_ITERATOR_DELETED:
        return 0x60;
      case ROCKSDB_NAMESPACE::Tickers::COMPACTION_OPTIMIZED_DEL_DROP_OBSOLETE:
        return 0x61;
      case ROCKSDB_NAMESPACE::Tickers::COMPACTION_CANCELLED:
        return 0x62;
      case ROCKSDB_NAMESPACE::Tickers::BLOOM_FILTER_FULL_POSITIVE:
        return 0x63;
      case ROCKSDB_NAMESPACE::Tickers::BLOOM_FILTER_FULL_TRUE_POSITIVE:
        return 0x64;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_PUT:
        return 0x65;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_WRITE:
        return 0x66;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_GET:
        return 0x67;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_MULTIGET:
        return 0x68;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_SEEK:
        return 0x69;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_NEXT:
        return 0x6A;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_PREV:
        return 0x6B;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_KEYS_WRITTEN:
        return 0x6C;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_KEYS_READ:
        return 0x6D;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BYTES_WRITTEN:
        return 0x6E;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BYTES_READ:
        return 0x6F;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_WRITE_INLINED:
        return 0x70;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_WRITE_INLINED_TTL:
        return 0x71;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_WRITE_BLOB:
        return 0x72;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_WRITE_BLOB_TTL:
        return 0x73;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_FILE_BYTES_WRITTEN:
        return 0x74;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_FILE_BYTES_READ:
        return 0x75;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_FILE_SYNCED:
        return 0x76;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_INDEX_EXPIRED_COUNT:
        return 0x77;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_INDEX_EXPIRED_SIZE:
        return 0x78;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_INDEX_EVICTED_COUNT:
        return 0x79;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_INDEX_EVICTED_SIZE:
        return 0x7A;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_NUM_FILES:
        return 0x7B;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_NUM_NEW_FILES:
        return 0x7C;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_FAILURES:
        return 0x7D;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_NUM_KEYS_OVERWRITTEN:
        return 0x7E;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_NUM_KEYS_EXPIRED:
        return 0x7F;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_NUM_KEYS_RELOCATED:
        return -0x02;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_BYTES_OVERWRITTEN:
        return -0x03;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_BYTES_EXPIRED:
        return -0x04;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_BYTES_RELOCATED:
        return -0x05;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_FIFO_NUM_FILES_EVICTED:
        return -0x06;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_FIFO_NUM_KEYS_EVICTED:
        return -0x07;
      case ROCKSDB_NAMESPACE::Tickers::BLOB_DB_FIFO_BYTES_EVICTED:
        return -0x08;
      case ROCKSDB_NAMESPACE::Tickers::TXN_PREPARE_MUTEX_OVERHEAD:
        return -0x09;
      case ROCKSDB_NAMESPACE::Tickers::TXN_OLD_COMMIT_MAP_MUTEX_OVERHEAD:
        return -0x0A;
      case ROCKSDB_NAMESPACE::Tickers::TXN_DUPLICATE_KEY_OVERHEAD:
        return -0x0B;
      case ROCKSDB_NAMESPACE::Tickers::TXN_SNAPSHOT_MUTEX_OVERHEAD:
        return -0x0C;
      case ROCKSDB_NAMESPACE::Tickers::TXN_GET_TRY_AGAIN:
        return -0x0D;
      case ROCKSDB_NAMESPACE::Tickers::FILES_MARKED_TRASH:
        return -0x0E;
      case ROCKSDB_NAMESPACE::Tickers::FILES_DELETED_IMMEDIATELY:
        return -0X0F;
      case ROCKSDB_NAMESPACE::Tickers::COMPACT_READ_BYTES_MARKED:
        return -0x10;
      case ROCKSDB_NAMESPACE::Tickers::COMPACT_READ_BYTES_PERIODIC:
        return -0x11;
      case ROCKSDB_NAMESPACE::Tickers::COMPACT_READ_BYTES_TTL:
        return -0x12;
      case ROCKSDB_NAMESPACE::Tickers::COMPACT_WRITE_BYTES_MARKED:
        return -0x13;
      case ROCKSDB_NAMESPACE::Tickers::COMPACT_WRITE_BYTES_PERIODIC:
        return -0x14;
      case ROCKSDB_NAMESPACE::Tickers::COMPACT_WRITE_BYTES_TTL:
        return -0x15;
      case ROCKSDB_NAMESPACE::Tickers::ERROR_HANDLER_BG_ERROR_COUNT:
        return -0x16;
      case ROCKSDB_NAMESPACE::Tickers::ERROR_HANDLER_BG_IO_ERROR_COUNT:
        return -0x17;
      case ROCKSDB_NAMESPACE::Tickers::
          ERROR_HANDLER_BG_RETRYABLE_IO_ERROR_COUNT:
        return -0x18;
      case ROCKSDB_NAMESPACE::Tickers::ERROR_HANDLER_AUTORESUME_COUNT:
        return -0x19;
      case ROCKSDB_NAMESPACE::Tickers::
          ERROR_HANDLER_AUTORESUME_RETRY_TOTAL_COUNT:
        return -0x1A;
      case ROCKSDB_NAMESPACE::Tickers::ERROR_HANDLER_AUTORESUME_SUCCESS_COUNT:
        return -0x1B;
      case ROCKSDB_NAMESPACE::Tickers::MEMTABLE_PAYLOAD_BYTES_AT_FLUSH:
        return -0x1C;
      case ROCKSDB_NAMESPACE::Tickers::MEMTABLE_GARBAGE_BYTES_AT_FLUSH:
        return -0x1D;
      case ROCKSDB_NAMESPACE::Tickers::SECONDARY_CACHE_HITS:
        return -0x1E;
      case ROCKSDB_NAMESPACE::Tickers::VERIFY_CHECKSUM_READ_BYTES:
        return -0x1F;
      case ROCKSDB_NAMESPACE::Tickers::BACKUP_READ_BYTES:
        return -0x20;
      case ROCKSDB_NAMESPACE::Tickers::BACKUP_WRITE_BYTES:
        return -0x21;
      case ROCKSDB_NAMESPACE::Tickers::TICKER_ENUM_MAX:
        // 0x5F was the max value in the initial copy of tickers to Java.
        // Since these values are exposed directly to Java clients, we keep
        // the value the same forever.
        //
        // TODO: This particular case seems confusing and unnecessary to pin the
        // value since it's meant to be the number of tickers, not an actual
        // ticker value. But we aren't yet in a position to fix it since the
        // number of tickers doesn't fit in the Java representation (jbyte).
        return 0x5F;
      default:
        // undefined/default
        return 0x0;
    }
  }

  // Returns the equivalent C++ ROCKSDB_NAMESPACE::Tickers enum for the
  // provided Java org.rocksdb.TickerType
  static ROCKSDB_NAMESPACE::Tickers toCppTickers(jbyte jticker_type) {
    switch(jticker_type) {
      case 0x0:
        return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_MISS;
      case 0x1:
        return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_HIT;
      case 0x2:
        return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_ADD;
      case 0x3:
        return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_ADD_FAILURES;
      case 0x4:
        return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_INDEX_MISS;
      case 0x5:
        return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_INDEX_HIT;
      case 0x6:
        return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_INDEX_ADD;
      case 0x7:
        return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_INDEX_BYTES_INSERT;
      case 0x8:
        return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_INDEX_BYTES_EVICT;
      case 0x9:
        return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_FILTER_MISS;
      case 0xA:
        return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_FILTER_HIT;
      case 0xB:
        return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_FILTER_ADD;
      case 0xC:
        return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_FILTER_BYTES_INSERT;
      case 0xD:
        return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_FILTER_BYTES_EVICT;
      case 0xE:
        return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_DATA_MISS;
      case 0xF:
        return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_DATA_HIT;
      case 0x10:
        return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_DATA_ADD;
      case 0x11:
        return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_DATA_BYTES_INSERT;
      case 0x12:
        return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_BYTES_READ;
      case 0x13:
        return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_BYTES_WRITE;
      case 0x14:
        return ROCKSDB_NAMESPACE::Tickers::BLOOM_FILTER_USEFUL;
      case 0x15:
        return ROCKSDB_NAMESPACE::Tickers::PERSISTENT_CACHE_HIT;
      case 0x16:
        return ROCKSDB_NAMESPACE::Tickers::PERSISTENT_CACHE_MISS;
      case 0x17:
        return ROCKSDB_NAMESPACE::Tickers::SIM_BLOCK_CACHE_HIT;
      case 0x18:
        return ROCKSDB_NAMESPACE::Tickers::SIM_BLOCK_CACHE_MISS;
      case 0x19:
        return ROCKSDB_NAMESPACE::Tickers::MEMTABLE_HIT;
      case 0x1A:
        return ROCKSDB_NAMESPACE::Tickers::MEMTABLE_MISS;
      case 0x1B:
        return ROCKSDB_NAMESPACE::Tickers::GET_HIT_L0;
      case 0x1C:
        return ROCKSDB_NAMESPACE::Tickers::GET_HIT_L1;
      case 0x1D:
        return ROCKSDB_NAMESPACE::Tickers::GET_HIT_L2_AND_UP;
      case 0x1E:
        return ROCKSDB_NAMESPACE::Tickers::COMPACTION_KEY_DROP_NEWER_ENTRY;
      case 0x1F:
        return ROCKSDB_NAMESPACE::Tickers::COMPACTION_KEY_DROP_OBSOLETE;
      case 0x20:
        return ROCKSDB_NAMESPACE::Tickers::COMPACTION_KEY_DROP_RANGE_DEL;
      case 0x21:
        return ROCKSDB_NAMESPACE::Tickers::COMPACTION_KEY_DROP_USER;
      case 0x22:
        return ROCKSDB_NAMESPACE::Tickers::COMPACTION_RANGE_DEL_DROP_OBSOLETE;
      case 0x23:
        return ROCKSDB_NAMESPACE::Tickers::NUMBER_KEYS_WRITTEN;
      case 0x24:
        return ROCKSDB_NAMESPACE::Tickers::NUMBER_KEYS_READ;
      case 0x25:
        return ROCKSDB_NAMESPACE::Tickers::NUMBER_KEYS_UPDATED;
      case 0x26:
        return ROCKSDB_NAMESPACE::Tickers::BYTES_WRITTEN;
      case 0x27:
        return ROCKSDB_NAMESPACE::Tickers::BYTES_READ;
      case 0x28:
        return ROCKSDB_NAMESPACE::Tickers::NUMBER_DB_SEEK;
      case 0x29:
        return ROCKSDB_NAMESPACE::Tickers::NUMBER_DB_NEXT;
      case 0x2A:
        return ROCKSDB_NAMESPACE::Tickers::NUMBER_DB_PREV;
      case 0x2B:
        return ROCKSDB_NAMESPACE::Tickers::NUMBER_DB_SEEK_FOUND;
      case 0x2C:
        return ROCKSDB_NAMESPACE::Tickers::NUMBER_DB_NEXT_FOUND;
      case 0x2D:
        return ROCKSDB_NAMESPACE::Tickers::NUMBER_DB_PREV_FOUND;
      case 0x2E:
        return ROCKSDB_NAMESPACE::Tickers::ITER_BYTES_READ;
      case 0x2F:
        return ROCKSDB_NAMESPACE::Tickers::NO_FILE_CLOSES;
      case 0x30:
        return ROCKSDB_NAMESPACE::Tickers::NO_FILE_OPENS;
      case 0x31:
        return ROCKSDB_NAMESPACE::Tickers::NO_FILE_ERRORS;
      case 0x32:
        return ROCKSDB_NAMESPACE::Tickers::STALL_L0_SLOWDOWN_MICROS;
      case 0x33:
        return ROCKSDB_NAMESPACE::Tickers::STALL_MEMTABLE_COMPACTION_MICROS;
      case 0x34:
        return ROCKSDB_NAMESPACE::Tickers::STALL_L0_NUM_FILES_MICROS;
      case 0x35:
        return ROCKSDB_NAMESPACE::Tickers::STALL_MICROS;
      case 0x36:
        return ROCKSDB_NAMESPACE::Tickers::DB_MUTEX_WAIT_MICROS;
      case 0x37:
        return ROCKSDB_NAMESPACE::Tickers::RATE_LIMIT_DELAY_MILLIS;
      case 0x38:
        return ROCKSDB_NAMESPACE::Tickers::NO_ITERATORS;
      case 0x39:
        return ROCKSDB_NAMESPACE::Tickers::NUMBER_MULTIGET_CALLS;
      case 0x3A:
        return ROCKSDB_NAMESPACE::Tickers::NUMBER_MULTIGET_KEYS_READ;
      case 0x3B:
        return ROCKSDB_NAMESPACE::Tickers::NUMBER_MULTIGET_BYTES_READ;
      case 0x3C:
        return ROCKSDB_NAMESPACE::Tickers::NUMBER_FILTERED_DELETES;
      case 0x3D:
        return ROCKSDB_NAMESPACE::Tickers::NUMBER_MERGE_FAILURES;
      case 0x3E:
        return ROCKSDB_NAMESPACE::Tickers::BLOOM_FILTER_PREFIX_CHECKED;
      case 0x3F:
        return ROCKSDB_NAMESPACE::Tickers::BLOOM_FILTER_PREFIX_USEFUL;
      case 0x40:
        return ROCKSDB_NAMESPACE::Tickers::NUMBER_OF_RESEEKS_IN_ITERATION;
      case 0x41:
        return ROCKSDB_NAMESPACE::Tickers::GET_UPDATES_SINCE_CALLS;
      case 0x42:
        return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_COMPRESSED_MISS;
      case 0x43:
        return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_COMPRESSED_HIT;
      case 0x44:
        return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_COMPRESSED_ADD;
      case 0x45:
        return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_COMPRESSED_ADD_FAILURES;
      case 0x46:
        return ROCKSDB_NAMESPACE::Tickers::WAL_FILE_SYNCED;
      case 0x47:
        return ROCKSDB_NAMESPACE::Tickers::WAL_FILE_BYTES;
      case 0x48:
        return ROCKSDB_NAMESPACE::Tickers::WRITE_DONE_BY_SELF;
      case 0x49:
        return ROCKSDB_NAMESPACE::Tickers::WRITE_DONE_BY_OTHER;
      case 0x4A:
        return ROCKSDB_NAMESPACE::Tickers::WRITE_TIMEDOUT;
      case 0x4B:
        return ROCKSDB_NAMESPACE::Tickers::WRITE_WITH_WAL;
      case 0x4C:
        return ROCKSDB_NAMESPACE::Tickers::COMPACT_READ_BYTES;
      case 0x4D:
        return ROCKSDB_NAMESPACE::Tickers::COMPACT_WRITE_BYTES;
      case 0x4E:
        return ROCKSDB_NAMESPACE::Tickers::FLUSH_WRITE_BYTES;
      case 0x4F:
        return ROCKSDB_NAMESPACE::Tickers::NUMBER_DIRECT_LOAD_TABLE_PROPERTIES;
      case 0x50:
        return ROCKSDB_NAMESPACE::Tickers::NUMBER_SUPERVERSION_ACQUIRES;
      case 0x51:
        return ROCKSDB_NAMESPACE::Tickers::NUMBER_SUPERVERSION_RELEASES;
      case 0x52:
        return ROCKSDB_NAMESPACE::Tickers::NUMBER_SUPERVERSION_CLEANUPS;
      case 0x53:
        return ROCKSDB_NAMESPACE::Tickers::NUMBER_BLOCK_COMPRESSED;
      case 0x54:
        return ROCKSDB_NAMESPACE::Tickers::NUMBER_BLOCK_DECOMPRESSED;
      case 0x55:
        return ROCKSDB_NAMESPACE::Tickers::NUMBER_BLOCK_NOT_COMPRESSED;
      case 0x56:
        return ROCKSDB_NAMESPACE::Tickers::MERGE_OPERATION_TOTAL_TIME;
      case 0x57:
        return ROCKSDB_NAMESPACE::Tickers::FILTER_OPERATION_TOTAL_TIME;
      case 0x58:
        return ROCKSDB_NAMESPACE::Tickers::ROW_CACHE_HIT;
      case 0x59:
        return ROCKSDB_NAMESPACE::Tickers::ROW_CACHE_MISS;
      case 0x5A:
        return ROCKSDB_NAMESPACE::Tickers::READ_AMP_ESTIMATE_USEFUL_BYTES;
      case 0x5B:
        return ROCKSDB_NAMESPACE::Tickers::READ_AMP_TOTAL_READ_BYTES;
      case 0x5C:
        return ROCKSDB_NAMESPACE::Tickers::NUMBER_RATE_LIMITER_DRAINS;
      case 0x5D:
        return ROCKSDB_NAMESPACE::Tickers::NUMBER_ITER_SKIP;
      case 0x5E:
        return ROCKSDB_NAMESPACE::Tickers::NUMBER_MULTIGET_KEYS_FOUND;
      case -0x01:
        // -0x01 so we can skip over the already taken 0x5F (TICKER_ENUM_MAX).
        return ROCKSDB_NAMESPACE::Tickers::NO_ITERATOR_CREATED;
      case 0x60:
        return ROCKSDB_NAMESPACE::Tickers::NO_ITERATOR_DELETED;
      case 0x61:
        return ROCKSDB_NAMESPACE::Tickers::
            COMPACTION_OPTIMIZED_DEL_DROP_OBSOLETE;
      case 0x62:
        return ROCKSDB_NAMESPACE::Tickers::COMPACTION_CANCELLED;
      case 0x63:
        return ROCKSDB_NAMESPACE::Tickers::BLOOM_FILTER_FULL_POSITIVE;
      case 0x64:
        return ROCKSDB_NAMESPACE::Tickers::BLOOM_FILTER_FULL_TRUE_POSITIVE;
      case 0x65:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_PUT;
      case 0x66:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_WRITE;
      case 0x67:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_GET;
      case 0x68:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_MULTIGET;
      case 0x69:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_SEEK;
      case 0x6A:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_NEXT;
      case 0x6B:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_PREV;
      case 0x6C:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_KEYS_WRITTEN;
      case 0x6D:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_NUM_KEYS_READ;
      case 0x6E:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BYTES_WRITTEN;
      case 0x6F:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BYTES_READ;
      case 0x70:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_WRITE_INLINED;
      case 0x71:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_WRITE_INLINED_TTL;
      case 0x72:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_WRITE_BLOB;
      case 0x73:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_WRITE_BLOB_TTL;
      case 0x74:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_FILE_BYTES_WRITTEN;
      case 0x75:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_FILE_BYTES_READ;
      case 0x76:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_FILE_SYNCED;
      case 0x77:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_INDEX_EXPIRED_COUNT;
      case 0x78:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_INDEX_EXPIRED_SIZE;
      case 0x79:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_INDEX_EVICTED_COUNT;
      case 0x7A:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_BLOB_INDEX_EVICTED_SIZE;
      case 0x7B:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_NUM_FILES;
      case 0x7C:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_NUM_NEW_FILES;
      case 0x7D:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_FAILURES;
      case 0x7E:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_NUM_KEYS_OVERWRITTEN;
      case 0x7F:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_NUM_KEYS_EXPIRED;
      case -0x02:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_NUM_KEYS_RELOCATED;
      case -0x03:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_BYTES_OVERWRITTEN;
      case -0x04:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_BYTES_EXPIRED;
      case -0x05:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_GC_BYTES_RELOCATED;
      case -0x06:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_FIFO_NUM_FILES_EVICTED;
      case -0x07:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_FIFO_NUM_KEYS_EVICTED;
      case -0x08:
        return ROCKSDB_NAMESPACE::Tickers::BLOB_DB_FIFO_BYTES_EVICTED;
      case -0x09:
        return ROCKSDB_NAMESPACE::Tickers::TXN_PREPARE_MUTEX_OVERHEAD;
      case -0x0A:
        return ROCKSDB_NAMESPACE::Tickers::TXN_OLD_COMMIT_MAP_MUTEX_OVERHEAD;
      case -0x0B:
        return ROCKSDB_NAMESPACE::Tickers::TXN_DUPLICATE_KEY_OVERHEAD;
      case -0x0C:
        return ROCKSDB_NAMESPACE::Tickers::TXN_SNAPSHOT_MUTEX_OVERHEAD;
      case -0x0D:
        return ROCKSDB_NAMESPACE::Tickers::TXN_GET_TRY_AGAIN;
      case -0x0E:
        return ROCKSDB_NAMESPACE::Tickers::FILES_MARKED_TRASH;
      case -0x0F:
        return ROCKSDB_NAMESPACE::Tickers::FILES_DELETED_IMMEDIATELY;
      case -0x10:
        return ROCKSDB_NAMESPACE::Tickers::COMPACT_READ_BYTES_MARKED;
      case -0x11:
        return ROCKSDB_NAMESPACE::Tickers::COMPACT_READ_BYTES_PERIODIC;
      case -0x12:
        return ROCKSDB_NAMESPACE::Tickers::COMPACT_READ_BYTES_TTL;
      case -0x13:
        return ROCKSDB_NAMESPACE::Tickers::COMPACT_WRITE_BYTES_MARKED;
      case -0x14:
        return ROCKSDB_NAMESPACE::Tickers::COMPACT_WRITE_BYTES_PERIODIC;
      case -0x15:
        return ROCKSDB_NAMESPACE::Tickers::COMPACT_WRITE_BYTES_TTL;
      case -0x16:
        return ROCKSDB_NAMESPACE::Tickers::ERROR_HANDLER_BG_ERROR_COUNT;
      case -0x17:
        return ROCKSDB_NAMESPACE::Tickers::ERROR_HANDLER_BG_IO_ERROR_COUNT;
      case -0x18:
        return ROCKSDB_NAMESPACE::Tickers::
            ERROR_HANDLER_BG_RETRYABLE_IO_ERROR_COUNT;
      case -0x19:
        return ROCKSDB_NAMESPACE::Tickers::ERROR_HANDLER_AUTORESUME_COUNT;
      case -0x1A:
        return ROCKSDB_NAMESPACE::Tickers::
            ERROR_HANDLER_AUTORESUME_RETRY_TOTAL_COUNT;
      case -0x1B:
        return ROCKSDB_NAMESPACE::Tickers::
            ERROR_HANDLER_AUTORESUME_SUCCESS_COUNT;
      case -0x1C:
        return ROCKSDB_NAMESPACE::Tickers::MEMTABLE_PAYLOAD_BYTES_AT_FLUSH;
      case -0x1D:
        return ROCKSDB_NAMESPACE::Tickers::MEMTABLE_GARBAGE_BYTES_AT_FLUSH;
      case -0x1E:
        return ROCKSDB_NAMESPACE::Tickers::SECONDARY_CACHE_HITS;
      case -0x1F:
        return ROCKSDB_NAMESPACE::Tickers::VERIFY_CHECKSUM_READ_BYTES;
      case -0x20:
        return ROCKSDB_NAMESPACE::Tickers::BACKUP_READ_BYTES;
      case -0x21:
        return ROCKSDB_NAMESPACE::Tickers::BACKUP_WRITE_BYTES;
      case 0x5F:
        // 0x5F was the max value in the initial copy of tickers to Java.
        // Since these values are exposed directly to Java clients, we keep
        // the value the same forever.
        //
        // TODO: This particular case seems confusing and unnecessary to pin the
        // value since it's meant to be the number of tickers, not an actual
        // ticker value. But we aren't yet in a position to fix it since the
        // number of tickers doesn't fit in the Java representation (jbyte).
        return ROCKSDB_NAMESPACE::Tickers::TICKER_ENUM_MAX;

      default:
        // undefined/default
        return ROCKSDB_NAMESPACE::Tickers::BLOCK_CACHE_MISS;
    }
  }
};

// The portal class for org.rocksdb.HistogramType
class HistogramTypeJni {
 public:
  // Returns the equivalent org.rocksdb.HistogramType for the provided
  // C++ ROCKSDB_NAMESPACE::Histograms enum
  static jbyte toJavaHistogramsType(
      const ROCKSDB_NAMESPACE::Histograms& histograms) {
    switch(histograms) {
      case ROCKSDB_NAMESPACE::Histograms::DB_GET:
        return 0x0;
      case ROCKSDB_NAMESPACE::Histograms::DB_WRITE:
        return 0x1;
      case ROCKSDB_NAMESPACE::Histograms::COMPACTION_TIME:
        return 0x2;
      case ROCKSDB_NAMESPACE::Histograms::SUBCOMPACTION_SETUP_TIME:
        return 0x3;
      case ROCKSDB_NAMESPACE::Histograms::TABLE_SYNC_MICROS:
        return 0x4;
      case ROCKSDB_NAMESPACE::Histograms::COMPACTION_OUTFILE_SYNC_MICROS:
        return 0x5;
      case ROCKSDB_NAMESPACE::Histograms::WAL_FILE_SYNC_MICROS:
        return 0x6;
      case ROCKSDB_NAMESPACE::Histograms::MANIFEST_FILE_SYNC_MICROS:
        return 0x7;
      case ROCKSDB_NAMESPACE::Histograms::TABLE_OPEN_IO_MICROS:
        return 0x8;
      case ROCKSDB_NAMESPACE::Histograms::DB_MULTIGET:
        return 0x9;
      case ROCKSDB_NAMESPACE::Histograms::READ_BLOCK_COMPACTION_MICROS:
        return 0xA;
      case ROCKSDB_NAMESPACE::Histograms::READ_BLOCK_GET_MICROS:
        return 0xB;
      case ROCKSDB_NAMESPACE::Histograms::WRITE_RAW_BLOCK_MICROS:
        return 0xC;
      case ROCKSDB_NAMESPACE::Histograms::STALL_L0_SLOWDOWN_COUNT:
        return 0xD;
      case ROCKSDB_NAMESPACE::Histograms::STALL_MEMTABLE_COMPACTION_COUNT:
        return 0xE;
      case ROCKSDB_NAMESPACE::Histograms::STALL_L0_NUM_FILES_COUNT:
        return 0xF;
      case ROCKSDB_NAMESPACE::Histograms::HARD_RATE_LIMIT_DELAY_COUNT:
        return 0x10;
      case ROCKSDB_NAMESPACE::Histograms::SOFT_RATE_LIMIT_DELAY_COUNT:
        return 0x11;
      case ROCKSDB_NAMESPACE::Histograms::NUM_FILES_IN_SINGLE_COMPACTION:
        return 0x12;
      case ROCKSDB_NAMESPACE::Histograms::DB_SEEK:
        return 0x13;
      case ROCKSDB_NAMESPACE::Histograms::WRITE_STALL:
        return 0x14;
      case ROCKSDB_NAMESPACE::Histograms::SST_READ_MICROS:
        return 0x15;
      case ROCKSDB_NAMESPACE::Histograms::NUM_SUBCOMPACTIONS_SCHEDULED:
        return 0x16;
      case ROCKSDB_NAMESPACE::Histograms::BYTES_PER_READ:
        return 0x17;
      case ROCKSDB_NAMESPACE::Histograms::BYTES_PER_WRITE:
        return 0x18;
      case ROCKSDB_NAMESPACE::Histograms::BYTES_PER_MULTIGET:
        return 0x19;
      case ROCKSDB_NAMESPACE::Histograms::BYTES_COMPRESSED:
        return 0x1A;
      case ROCKSDB_NAMESPACE::Histograms::BYTES_DECOMPRESSED:
        return 0x1B;
      case ROCKSDB_NAMESPACE::Histograms::COMPRESSION_TIMES_NANOS:
        return 0x1C;
      case ROCKSDB_NAMESPACE::Histograms::DECOMPRESSION_TIMES_NANOS:
        return 0x1D;
      case ROCKSDB_NAMESPACE::Histograms::READ_NUM_MERGE_OPERANDS:
        return 0x1E;
      // 0x20 to skip 0x1F so TICKER_ENUM_MAX remains unchanged for minor version compatibility.
      case ROCKSDB_NAMESPACE::Histograms::FLUSH_TIME:
        return 0x20;
      case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_KEY_SIZE:
        return 0x21;
      case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_VALUE_SIZE:
        return 0x22;
      case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_WRITE_MICROS:
        return 0x23;
      case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_GET_MICROS:
        return 0x24;
      case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_MULTIGET_MICROS:
        return 0x25;
      case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_SEEK_MICROS:
        return 0x26;
      case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_NEXT_MICROS:
        return 0x27;
      case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_PREV_MICROS:
        return 0x28;
      case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_BLOB_FILE_WRITE_MICROS:
        return 0x29;
      case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_BLOB_FILE_READ_MICROS:
        return 0x2A;
      case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_BLOB_FILE_SYNC_MICROS:
        return 0x2B;
      case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_GC_MICROS:
        return 0x2C;
      case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_COMPRESSION_MICROS:
        return 0x2D;
      case ROCKSDB_NAMESPACE::Histograms::BLOB_DB_DECOMPRESSION_MICROS:
        return 0x2E;
      case ROCKSDB_NAMESPACE::Histograms::
          NUM_INDEX_AND_FILTER_BLOCKS_READ_PER_LEVEL:
        return 0x2F;
      case ROCKSDB_NAMESPACE::Histograms::NUM_DATA_BLOCKS_READ_PER_LEVEL:
        return 0x30;
      case ROCKSDB_NAMESPACE::Histograms::NUM_SST_READ_PER_LEVEL:
        return 0x31;
      case ROCKSDB_NAMESPACE::Histograms::ERROR_HANDLER_AUTORESUME_RETRY_COUNT:
        return 0x31;
      case ROCKSDB_NAMESPACE::Histograms::HISTOGRAM_ENUM_MAX:
        // 0x1F for backwards compatibility on current minor version.
        return 0x1F;

      default:
        // undefined/default
        return 0x0;
    }
  }

  // Returns the equivalent C++ ROCKSDB_NAMESPACE::Histograms enum for the
  // provided Java org.rocksdb.HistogramsType
  static ROCKSDB_NAMESPACE::Histograms toCppHistograms(jbyte jhistograms_type) {
    switch(jhistograms_type) {
      case 0x0:
        return ROCKSDB_NAMESPACE::Histograms::DB_GET;
      case 0x1:
        return ROCKSDB_NAMESPACE::Histograms::DB_WRITE;
      case 0x2:
        return ROCKSDB_NAMESPACE::Histograms::COMPACTION_TIME;
      case 0x3:
        return ROCKSDB_NAMESPACE::Histograms::SUBCOMPACTION_SETUP_TIME;
      case 0x4:
        return ROCKSDB_NAMESPACE::Histograms::TABLE_SYNC_MICROS;
      case 0x5:
        return ROCKSDB_NAMESPACE::Histograms::COMPACTION_OUTFILE_SYNC_MICROS;
      case 0x6:
        return ROCKSDB_NAMESPACE::Histograms::WAL_FILE_SYNC_MICROS;
      case 0x7:
        return ROCKSDB_NAMESPACE::Histograms::MANIFEST_FILE_SYNC_MICROS;
      case 0x8:
        return ROCKSDB_NAMESPACE::Histograms::TABLE_OPEN_IO_MICROS;
      case 0x9:
        return ROCKSDB_NAMESPACE::Histograms::DB_MULTIGET;
      case 0xA:
        return ROCKSDB_NAMESPACE::Histograms::READ_BLOCK_COMPACTION_MICROS;
      case 0xB:
        return ROCKSDB_NAMESPACE::Histograms::READ_BLOCK_GET_MICROS;
      case 0xC:
        return ROCKSDB_NAMESPACE::Histograms::WRITE_RAW_BLOCK_MICROS;
      case 0xD:
        return ROCKSDB_NAMESPACE::Histograms::STALL_L0_SLOWDOWN_COUNT;
      case 0xE:
        return ROCKSDB_NAMESPACE::Histograms::STALL_MEMTABLE_COMPACTION_COUNT;
      case 0xF:
        return ROCKSDB_NAMESPACE::Histograms::STALL_L0_NUM_FILES_COUNT;
      case 0x10:
        return ROCKSDB_NAMESPACE::Histograms::HARD_RATE_LIMIT_DELAY_COUNT;
      case 0x11:
        return ROCKSDB_NAMESPACE::Histograms::SOFT_RATE_LIMIT_DELAY_COUNT;
      case 0x12:
        return ROCKSDB_NAMESPACE::Histograms::NUM_FILES_IN_SINGLE_COMPACTION;
      case 0x13:
        return ROCKSDB_NAMESPACE::Histograms::DB_SEEK;
      case 0x14:
        return ROCKSDB_NAMESPACE::Histograms::WRITE_STALL;
      case 0x15:
        return ROCKSDB_NAMESPACE::Histograms::SST_READ_MICROS;
      case 0x16:
        return ROCKSDB_NAMESPACE::Histograms::NUM_SUBCOMPACTIONS_SCHEDULED;
      case 0x17:
        return ROCKSDB_NAMESPACE::Histograms::BYTES_PER_READ;
      case 0x18:
        return ROCKSDB_NAMESPACE::Histograms::BYTES_PER_WRITE;
      case 0x19:
        return ROCKSDB_NAMESPACE::Histograms::BYTES_PER_MULTIGET;
      case 0x1A:
        return ROCKSDB_NAMESPACE::Histograms::BYTES_COMPRESSED;
      case 0x1B:
        return ROCKSDB_NAMESPACE::Histograms::BYTES_DECOMPRESSED;
      case 0x1C:
        return ROCKSDB_NAMESPACE::Histograms::COMPRESSION_TIMES_NANOS;
      case 0x1D:
        return ROCKSDB_NAMESPACE::Histograms::DECOMPRESSION_TIMES_NANOS;
      case 0x1E:
        return ROCKSDB_NAMESPACE::Histograms::READ_NUM_MERGE_OPERANDS;
      // 0x20 to skip 0x1F so TICKER_ENUM_MAX remains unchanged for minor version compatibility.
      case 0x20:
        return ROCKSDB_NAMESPACE::Histograms::FLUSH_TIME;
      case 0x21:
        return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_KEY_SIZE;
      case 0x22:
        return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_VALUE_SIZE;
      case 0x23:
        return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_WRITE_MICROS;
      case 0x24:
        return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_GET_MICROS;
      case 0x25:
        return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_MULTIGET_MICROS;
      case 0x26:
        return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_SEEK_MICROS;
      case 0x27:
        return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_NEXT_MICROS;
      case 0x28:
        return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_PREV_MICROS;
      case 0x29:
        return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_BLOB_FILE_WRITE_MICROS;
      case 0x2A:
        return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_BLOB_FILE_READ_MICROS;
      case 0x2B:
        return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_BLOB_FILE_SYNC_MICROS;
      case 0x2C:
        return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_GC_MICROS;
      case 0x2D:
        return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_COMPRESSION_MICROS;
      case 0x2E:
        return ROCKSDB_NAMESPACE::Histograms::BLOB_DB_DECOMPRESSION_MICROS;
      case 0x2F:
        return ROCKSDB_NAMESPACE::Histograms::
            NUM_INDEX_AND_FILTER_BLOCKS_READ_PER_LEVEL;
      case 0x30:
        return ROCKSDB_NAMESPACE::Histograms::NUM_DATA_BLOCKS_READ_PER_LEVEL;
      case 0x31:
        return ROCKSDB_NAMESPACE::Histograms::NUM_SST_READ_PER_LEVEL;
      case 0x32:
        return ROCKSDB_NAMESPACE::Histograms::
            ERROR_HANDLER_AUTORESUME_RETRY_COUNT;
      case 0x1F:
        // 0x1F for backwards compatibility on current minor version.
        return ROCKSDB_NAMESPACE::Histograms::HISTOGRAM_ENUM_MAX;

      default:
        // undefined/default
        return ROCKSDB_NAMESPACE::Histograms::DB_GET;
    }
  }
};

// The portal class for org.rocksdb.StatsLevel
class StatsLevelJni {
 public:
  // Returns the equivalent org.rocksdb.StatsLevel for the provided
  // C++ ROCKSDB_NAMESPACE::StatsLevel enum
  static jbyte toJavaStatsLevel(
      const ROCKSDB_NAMESPACE::StatsLevel& stats_level) {
    switch(stats_level) {
      case ROCKSDB_NAMESPACE::StatsLevel::kExceptDetailedTimers:
        return 0x0;
      case ROCKSDB_NAMESPACE::StatsLevel::kExceptTimeForMutex:
        return 0x1;
      case ROCKSDB_NAMESPACE::StatsLevel::kAll:
        return 0x2;

      default:
        // undefined/default
        return 0x0;
    }
  }

  // Returns the equivalent C++ ROCKSDB_NAMESPACE::StatsLevel enum for the
  // provided Java org.rocksdb.StatsLevel
  static ROCKSDB_NAMESPACE::StatsLevel toCppStatsLevel(jbyte jstats_level) {
    switch(jstats_level) {
      case 0x0:
        return ROCKSDB_NAMESPACE::StatsLevel::kExceptDetailedTimers;
      case 0x1:
        return ROCKSDB_NAMESPACE::StatsLevel::kExceptTimeForMutex;
      case 0x2:
        return ROCKSDB_NAMESPACE::StatsLevel::kAll;

      default:
        // undefined/default
        return ROCKSDB_NAMESPACE::StatsLevel::kExceptDetailedTimers;
    }
  }
};

// The portal class for org.rocksdb.RateLimiterMode
class RateLimiterModeJni {
 public:
  // Returns the equivalent org.rocksdb.RateLimiterMode for the provided
  // C++ ROCKSDB_NAMESPACE::RateLimiter::Mode enum
  static jbyte toJavaRateLimiterMode(
      const ROCKSDB_NAMESPACE::RateLimiter::Mode& rate_limiter_mode) {
    switch(rate_limiter_mode) {
      case ROCKSDB_NAMESPACE::RateLimiter::Mode::kReadsOnly:
        return 0x0;
      case ROCKSDB_NAMESPACE::RateLimiter::Mode::kWritesOnly:
        return 0x1;
      case ROCKSDB_NAMESPACE::RateLimiter::Mode::kAllIo:
        return 0x2;

      default:
        // undefined/default
        return 0x1;
    }
  }

  // Returns the equivalent C++ ROCKSDB_NAMESPACE::RateLimiter::Mode enum for
  // the provided Java org.rocksdb.RateLimiterMode
  static ROCKSDB_NAMESPACE::RateLimiter::Mode toCppRateLimiterMode(
      jbyte jrate_limiter_mode) {
    switch(jrate_limiter_mode) {
      case 0x0:
        return ROCKSDB_NAMESPACE::RateLimiter::Mode::kReadsOnly;
      case 0x1:
        return ROCKSDB_NAMESPACE::RateLimiter::Mode::kWritesOnly;
      case 0x2:
        return ROCKSDB_NAMESPACE::RateLimiter::Mode::kAllIo;

      default:
        // undefined/default
        return ROCKSDB_NAMESPACE::RateLimiter::Mode::kWritesOnly;
    }
  }
};

// The portal class for org.rocksdb.MemoryUsageType
class MemoryUsageTypeJni {
public:
 // Returns the equivalent org.rocksdb.MemoryUsageType for the provided
 // C++ ROCKSDB_NAMESPACE::MemoryUtil::UsageType enum
 static jbyte toJavaMemoryUsageType(
     const ROCKSDB_NAMESPACE::MemoryUtil::UsageType& usage_type) {
   switch (usage_type) {
     case ROCKSDB_NAMESPACE::MemoryUtil::UsageType::kMemTableTotal:
       return 0x0;
     case ROCKSDB_NAMESPACE::MemoryUtil::UsageType::kMemTableUnFlushed:
       return 0x1;
     case ROCKSDB_NAMESPACE::MemoryUtil::UsageType::kTableReadersTotal:
       return 0x2;
     case ROCKSDB_NAMESPACE::MemoryUtil::UsageType::kCacheTotal:
       return 0x3;
     default:
       // undefined: use kNumUsageTypes
       return 0x4;
   }
 }

 // Returns the equivalent C++ ROCKSDB_NAMESPACE::MemoryUtil::UsageType enum for
 // the provided Java org.rocksdb.MemoryUsageType
 static ROCKSDB_NAMESPACE::MemoryUtil::UsageType toCppMemoryUsageType(
     jbyte usage_type) {
   switch (usage_type) {
     case 0x0:
       return ROCKSDB_NAMESPACE::MemoryUtil::UsageType::kMemTableTotal;
     case 0x1:
       return ROCKSDB_NAMESPACE::MemoryUtil::UsageType::kMemTableUnFlushed;
     case 0x2:
       return ROCKSDB_NAMESPACE::MemoryUtil::UsageType::kTableReadersTotal;
     case 0x3:
       return ROCKSDB_NAMESPACE::MemoryUtil::UsageType::kCacheTotal;
     default:
       // undefined/default: use kNumUsageTypes
       return ROCKSDB_NAMESPACE::MemoryUtil::UsageType::kNumUsageTypes;
   }
 }
};

// The portal class for org.rocksdb.Transaction
class TransactionJni : public JavaClass {
 public:
  /**
   * Get the Java Class org.rocksdb.Transaction
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env,
        "org/rocksdb/Transaction");
  }

  /**
   * Create a new Java org.rocksdb.Transaction.WaitingTransactions object
   *
   * @param env A pointer to the Java environment
   * @param jtransaction A Java org.rocksdb.Transaction object
   * @param column_family_id The id of the column family
   * @param key The key
   * @param transaction_ids The transaction ids
   *
   * @return A reference to a Java
   *     org.rocksdb.Transaction.WaitingTransactions object,
   *     or nullptr if an an exception occurs
   */
  static jobject newWaitingTransactions(JNIEnv* env, jobject jtransaction,
      const uint32_t column_family_id, const std::string &key,
      const std::vector<TransactionID> &transaction_ids) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    jmethodID mid = env->GetMethodID(
      jclazz, "newWaitingTransactions", "(JLjava/lang/String;[J)Lorg/rocksdb/Transaction$WaitingTransactions;");
    if(mid == nullptr) {
      // exception thrown: NoSuchMethodException or OutOfMemoryError
      return nullptr;
    }

    jstring jkey = env->NewStringUTF(key.c_str());
    if(jkey == nullptr) {
      // exception thrown: OutOfMemoryError
      return nullptr;
    }

    const size_t len = transaction_ids.size();
    jlongArray jtransaction_ids = env->NewLongArray(static_cast<jsize>(len));
    if(jtransaction_ids == nullptr) {
      // exception thrown: OutOfMemoryError
      env->DeleteLocalRef(jkey);
      return nullptr;
    }

    jboolean is_copy;
    jlong* body = env->GetLongArrayElements(jtransaction_ids, &is_copy);
    if(body == nullptr) {
        // exception thrown: OutOfMemoryError
        env->DeleteLocalRef(jkey);
        env->DeleteLocalRef(jtransaction_ids);
        return nullptr;
    }
    for(size_t i = 0; i < len; ++i) {
      body[i] = static_cast<jlong>(transaction_ids[i]);
    }
    env->ReleaseLongArrayElements(jtransaction_ids, body,
                                  is_copy == JNI_TRUE ? 0 : JNI_ABORT);

    jobject jwaiting_transactions = env->CallObjectMethod(jtransaction,
      mid, static_cast<jlong>(column_family_id), jkey, jtransaction_ids);
    if(env->ExceptionCheck()) {
      // exception thrown: InstantiationException or OutOfMemoryError
      env->DeleteLocalRef(jkey);
      env->DeleteLocalRef(jtransaction_ids);
      return nullptr;
    }

    return jwaiting_transactions;
  }
};

// The portal class for org.rocksdb.TransactionDB
class TransactionDBJni : public JavaClass {
 public:
 /**
  * Get the Java Class org.rocksdb.TransactionDB
  *
  * @param env A pointer to the Java environment
  *
  * @return The Java Class or nullptr if one of the
  *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
  *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
  */
  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env,
       "org/rocksdb/TransactionDB");
  }

  /**
   * Create a new Java org.rocksdb.TransactionDB.DeadlockInfo object
   *
   * @param env A pointer to the Java environment
   * @param jtransaction A Java org.rocksdb.Transaction object
   * @param column_family_id The id of the column family
   * @param key The key
   * @param transaction_ids The transaction ids
   *
   * @return A reference to a Java
   *     org.rocksdb.Transaction.WaitingTransactions object,
   *     or nullptr if an an exception occurs
   */
  static jobject newDeadlockInfo(
      JNIEnv* env, jobject jtransaction_db,
      const ROCKSDB_NAMESPACE::TransactionID transaction_id,
      const uint32_t column_family_id, const std::string& waiting_key,
      const bool exclusive) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    jmethodID mid = env->GetMethodID(
        jclazz, "newDeadlockInfo", "(JJLjava/lang/String;Z)Lorg/rocksdb/TransactionDB$DeadlockInfo;");
    if(mid == nullptr) {
      // exception thrown: NoSuchMethodException or OutOfMemoryError
      return nullptr;
    }

    jstring jwaiting_key = env->NewStringUTF(waiting_key.c_str());
    if(jwaiting_key == nullptr) {
      // exception thrown: OutOfMemoryError
      return nullptr;
    }

    // resolve the column family id to a ColumnFamilyHandle
    jobject jdeadlock_info = env->CallObjectMethod(jtransaction_db,
        mid, transaction_id, static_cast<jlong>(column_family_id),
        jwaiting_key, exclusive);
    if(env->ExceptionCheck()) {
      // exception thrown: InstantiationException or OutOfMemoryError
      env->DeleteLocalRef(jwaiting_key);
      return nullptr;
    }

    return jdeadlock_info;
  }
};

// The portal class for org.rocksdb.TxnDBWritePolicy
class TxnDBWritePolicyJni {
 public:
  // Returns the equivalent org.rocksdb.TxnDBWritePolicy for the provided
  // C++ ROCKSDB_NAMESPACE::TxnDBWritePolicy enum
  static jbyte toJavaTxnDBWritePolicy(
      const ROCKSDB_NAMESPACE::TxnDBWritePolicy& txndb_write_policy) {
    switch (txndb_write_policy) {
      case ROCKSDB_NAMESPACE::TxnDBWritePolicy::WRITE_COMMITTED:
        return 0x0;
      case ROCKSDB_NAMESPACE::TxnDBWritePolicy::WRITE_PREPARED:
        return 0x1;
      case ROCKSDB_NAMESPACE::TxnDBWritePolicy::WRITE_UNPREPARED:
        return 0x2;
      default:
        return 0x7F;  // undefined
    }
  }

  // Returns the equivalent C++ ROCKSDB_NAMESPACE::TxnDBWritePolicy enum for the
  // provided Java org.rocksdb.TxnDBWritePolicy
  static ROCKSDB_NAMESPACE::TxnDBWritePolicy toCppTxnDBWritePolicy(
      jbyte jtxndb_write_policy) {
    switch (jtxndb_write_policy) {
      case 0x0:
        return ROCKSDB_NAMESPACE::TxnDBWritePolicy::WRITE_COMMITTED;
      case 0x1:
        return ROCKSDB_NAMESPACE::TxnDBWritePolicy::WRITE_PREPARED;
      case 0x2:
        return ROCKSDB_NAMESPACE::TxnDBWritePolicy::WRITE_UNPREPARED;
      default:
        // undefined/default
        return ROCKSDB_NAMESPACE::TxnDBWritePolicy::WRITE_COMMITTED;
    }
  }
};

// The portal class for org.rocksdb.TransactionDB.KeyLockInfo
class KeyLockInfoJni : public JavaClass {
 public:
  /**
   * Get the Java Class org.rocksdb.TransactionDB.KeyLockInfo
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env,
        "org/rocksdb/TransactionDB$KeyLockInfo");
  }

  /**
   * Create a new Java org.rocksdb.TransactionDB.KeyLockInfo object
   * with the same properties as the provided C++ ROCKSDB_NAMESPACE::KeyLockInfo
   * object
   *
   * @param env A pointer to the Java environment
   * @param key_lock_info The ROCKSDB_NAMESPACE::KeyLockInfo object
   *
   * @return A reference to a Java
   *     org.rocksdb.TransactionDB.KeyLockInfo object,
   *     or nullptr if an an exception occurs
   */
  static jobject construct(
      JNIEnv* env, const ROCKSDB_NAMESPACE::KeyLockInfo& key_lock_info) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    jmethodID mid = env->GetMethodID(
      jclazz, "<init>", "(Ljava/lang/String;[JZ)V");
    if (mid == nullptr) {
      // exception thrown: NoSuchMethodException or OutOfMemoryError
      return nullptr;
    }

    jstring jkey = env->NewStringUTF(key_lock_info.key.c_str());
    if (jkey == nullptr) {
      // exception thrown: OutOfMemoryError
      return nullptr;
    }

    const jsize jtransaction_ids_len = static_cast<jsize>(key_lock_info.ids.size());
    jlongArray jtransactions_ids = env->NewLongArray(jtransaction_ids_len);
    if (jtransactions_ids == nullptr) {
      // exception thrown: OutOfMemoryError
      env->DeleteLocalRef(jkey);
      return nullptr;
    }

    const jobject jkey_lock_info = env->NewObject(jclazz, mid,
      jkey, jtransactions_ids, key_lock_info.exclusive);
    if(jkey_lock_info == nullptr) {
      // exception thrown: InstantiationException or OutOfMemoryError
      env->DeleteLocalRef(jtransactions_ids);
      env->DeleteLocalRef(jkey);
      return nullptr;
    }

    return jkey_lock_info;
  }
};

// The portal class for org.rocksdb.TransactionDB.DeadlockInfo
class DeadlockInfoJni : public JavaClass {
 public:
  /**
   * Get the Java Class org.rocksdb.TransactionDB.DeadlockInfo
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
   static jclass getJClass(JNIEnv* env) {
     return JavaClass::getJClass(env,"org/rocksdb/TransactionDB$DeadlockInfo");
  }
};

// The portal class for org.rocksdb.TransactionDB.DeadlockPath
class DeadlockPathJni : public JavaClass {
 public:
  /**
   * Get the Java Class org.rocksdb.TransactionDB.DeadlockPath
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env,
        "org/rocksdb/TransactionDB$DeadlockPath");
  }

  /**
   * Create a new Java org.rocksdb.TransactionDB.DeadlockPath object
   *
   * @param env A pointer to the Java environment
   *
   * @return A reference to a Java
   *     org.rocksdb.TransactionDB.DeadlockPath object,
   *     or nullptr if an an exception occurs
   */
  static jobject construct(JNIEnv* env,
    const jobjectArray jdeadlock_infos, const bool limit_exceeded) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    jmethodID mid = env->GetMethodID(
      jclazz, "<init>", "([LDeadlockInfo;Z)V");
    if (mid == nullptr) {
      // exception thrown: NoSuchMethodException or OutOfMemoryError
      return nullptr;
    }

    const jobject jdeadlock_path = env->NewObject(jclazz, mid,
      jdeadlock_infos, limit_exceeded);
    if(jdeadlock_path == nullptr) {
      // exception thrown: InstantiationException or OutOfMemoryError
      return nullptr;
    }

    return jdeadlock_path;
  }
};

class AbstractTableFilterJni
    : public RocksDBNativeClass<
          const ROCKSDB_NAMESPACE::TableFilterJniCallback*,
          AbstractTableFilterJni> {
 public:
  /**
   * Get the Java Method: TableFilter#filter(TableProperties)
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getFilterMethod(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid =
        env->GetMethodID(jclazz, "filter", "(Lorg/rocksdb/TableProperties;)Z");
    assert(mid != nullptr);
    return mid;
  }

 private:
  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "org/rocksdb/TableFilter");
  }
};

class TablePropertiesJni : public JavaClass {
 public:
  /**
   * Create a new Java org.rocksdb.TableProperties object.
   *
   * @param env A pointer to the Java environment
   * @param table_properties A Cpp table properties object
   *
   * @return A reference to a Java org.rocksdb.TableProperties object, or
   * nullptr if an an exception occurs
   */
  static jobject fromCppTableProperties(
      JNIEnv* env, const ROCKSDB_NAMESPACE::TableProperties& table_properties) {
    jclass jclazz = getJClass(env);
    if (jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    jmethodID mid = env->GetMethodID(
        jclazz, "<init>",
        "(JJJJJJJJJJJJJJJJJJJJJ[BLjava/lang/String;Ljava/lang/String;Ljava/"
        "lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/"
        "String;Ljava/util/Map;Ljava/util/Map;Ljava/util/Map;)V");
    if (mid == nullptr) {
      // exception thrown: NoSuchMethodException or OutOfMemoryError
      return nullptr;
    }

    jbyteArray jcolumn_family_name = ROCKSDB_NAMESPACE::JniUtil::copyBytes(
        env, table_properties.column_family_name);
    if (jcolumn_family_name == nullptr) {
      // exception occurred creating java string
      return nullptr;
    }

    jstring jfilter_policy_name = ROCKSDB_NAMESPACE::JniUtil::toJavaString(
        env, &table_properties.filter_policy_name, true);
    if (env->ExceptionCheck()) {
      // exception occurred creating java string
      env->DeleteLocalRef(jcolumn_family_name);
      return nullptr;
    }

    jstring jcomparator_name = ROCKSDB_NAMESPACE::JniUtil::toJavaString(
        env, &table_properties.comparator_name, true);
    if (env->ExceptionCheck()) {
      // exception occurred creating java string
      env->DeleteLocalRef(jcolumn_family_name);
      env->DeleteLocalRef(jfilter_policy_name);
      return nullptr;
    }

    jstring jmerge_operator_name = ROCKSDB_NAMESPACE::JniUtil::toJavaString(
        env, &table_properties.merge_operator_name, true);
    if (env->ExceptionCheck()) {
      // exception occurred creating java string
      env->DeleteLocalRef(jcolumn_family_name);
      env->DeleteLocalRef(jfilter_policy_name);
      env->DeleteLocalRef(jcomparator_name);
      return nullptr;
    }

    jstring jprefix_extractor_name = ROCKSDB_NAMESPACE::JniUtil::toJavaString(
        env, &table_properties.prefix_extractor_name, true);
    if (env->ExceptionCheck()) {
      // exception occurred creating java string
      env->DeleteLocalRef(jcolumn_family_name);
      env->DeleteLocalRef(jfilter_policy_name);
      env->DeleteLocalRef(jcomparator_name);
      env->DeleteLocalRef(jmerge_operator_name);
      return nullptr;
    }

    jstring jproperty_collectors_names =
        ROCKSDB_NAMESPACE::JniUtil::toJavaString(
            env, &table_properties.property_collectors_names, true);
    if (env->ExceptionCheck()) {
      // exception occurred creating java string
      env->DeleteLocalRef(jcolumn_family_name);
      env->DeleteLocalRef(jfilter_policy_name);
      env->DeleteLocalRef(jcomparator_name);
      env->DeleteLocalRef(jmerge_operator_name);
      env->DeleteLocalRef(jprefix_extractor_name);
      return nullptr;
    }

    jstring jcompression_name = ROCKSDB_NAMESPACE::JniUtil::toJavaString(
        env, &table_properties.compression_name, true);
    if (env->ExceptionCheck()) {
      // exception occurred creating java string
      env->DeleteLocalRef(jcolumn_family_name);
      env->DeleteLocalRef(jfilter_policy_name);
      env->DeleteLocalRef(jcomparator_name);
      env->DeleteLocalRef(jmerge_operator_name);
      env->DeleteLocalRef(jprefix_extractor_name);
      env->DeleteLocalRef(jproperty_collectors_names);
      return nullptr;
    }

    // Map<String, String>
    jobject juser_collected_properties =
        ROCKSDB_NAMESPACE::HashMapJni::fromCppMap(
            env, &table_properties.user_collected_properties);
    if (env->ExceptionCheck()) {
      // exception occurred creating java map
      env->DeleteLocalRef(jcolumn_family_name);
      env->DeleteLocalRef(jfilter_policy_name);
      env->DeleteLocalRef(jcomparator_name);
      env->DeleteLocalRef(jmerge_operator_name);
      env->DeleteLocalRef(jprefix_extractor_name);
      env->DeleteLocalRef(jproperty_collectors_names);
      env->DeleteLocalRef(jcompression_name);
      return nullptr;
    }

    // Map<String, String>
    jobject jreadable_properties = ROCKSDB_NAMESPACE::HashMapJni::fromCppMap(
        env, &table_properties.readable_properties);
    if (env->ExceptionCheck()) {
      // exception occurred creating java map
      env->DeleteLocalRef(jcolumn_family_name);
      env->DeleteLocalRef(jfilter_policy_name);
      env->DeleteLocalRef(jcomparator_name);
      env->DeleteLocalRef(jmerge_operator_name);
      env->DeleteLocalRef(jprefix_extractor_name);
      env->DeleteLocalRef(jproperty_collectors_names);
      env->DeleteLocalRef(jcompression_name);
      env->DeleteLocalRef(juser_collected_properties);
      return nullptr;
    }

    // Map<String, Long>
    jobject jproperties_offsets = ROCKSDB_NAMESPACE::HashMapJni::fromCppMap(
        env, &table_properties.properties_offsets);
    if (env->ExceptionCheck()) {
      // exception occurred creating java map
      env->DeleteLocalRef(jcolumn_family_name);
      env->DeleteLocalRef(jfilter_policy_name);
      env->DeleteLocalRef(jcomparator_name);
      env->DeleteLocalRef(jmerge_operator_name);
      env->DeleteLocalRef(jprefix_extractor_name);
      env->DeleteLocalRef(jproperty_collectors_names);
      env->DeleteLocalRef(jcompression_name);
      env->DeleteLocalRef(juser_collected_properties);
      env->DeleteLocalRef(jreadable_properties);
      return nullptr;
    }

    jobject jtable_properties = env->NewObject(
        jclazz, mid, static_cast<jlong>(table_properties.data_size),
        static_cast<jlong>(table_properties.index_size),
        static_cast<jlong>(table_properties.index_partitions),
        static_cast<jlong>(table_properties.top_level_index_size),
        static_cast<jlong>(table_properties.index_key_is_user_key),
        static_cast<jlong>(table_properties.index_value_is_delta_encoded),
        static_cast<jlong>(table_properties.filter_size),
        static_cast<jlong>(table_properties.raw_key_size),
        static_cast<jlong>(table_properties.raw_value_size),
        static_cast<jlong>(table_properties.num_data_blocks),
        static_cast<jlong>(table_properties.num_entries),
        static_cast<jlong>(table_properties.num_deletions),
        static_cast<jlong>(table_properties.num_merge_operands),
        static_cast<jlong>(table_properties.num_range_deletions),
        static_cast<jlong>(table_properties.format_version),
        static_cast<jlong>(table_properties.fixed_key_len),
        static_cast<jlong>(table_properties.column_family_id),
        static_cast<jlong>(table_properties.creation_time),
        static_cast<jlong>(table_properties.oldest_key_time),
        static_cast<jlong>(
            table_properties.slow_compression_estimated_data_size),
        static_cast<jlong>(
            table_properties.fast_compression_estimated_data_size),
        jcolumn_family_name, jfilter_policy_name, jcomparator_name,
        jmerge_operator_name, jprefix_extractor_name,
        jproperty_collectors_names, jcompression_name,
        juser_collected_properties, jreadable_properties, jproperties_offsets);

    if (env->ExceptionCheck()) {
      return nullptr;
    }

    return jtable_properties;
  }

 private:
  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "org/rocksdb/TableProperties");
  }
};

class ColumnFamilyDescriptorJni : public JavaClass {
 public:
  /**
   * Get the Java Class org.rocksdb.ColumnFamilyDescriptor
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "org/rocksdb/ColumnFamilyDescriptor");
  }

  /**
   * Create a new Java org.rocksdb.ColumnFamilyDescriptor object with the same
   * properties as the provided C++ ROCKSDB_NAMESPACE::ColumnFamilyDescriptor
   * object
   *
   * @param env A pointer to the Java environment
   * @param cfd A pointer to ROCKSDB_NAMESPACE::ColumnFamilyDescriptor object
   *
   * @return A reference to a Java org.rocksdb.ColumnFamilyDescriptor object, or
   * nullptr if an an exception occurs
   */
  static jobject construct(JNIEnv* env, ColumnFamilyDescriptor* cfd) {
    jbyteArray jcf_name = JniUtil::copyBytes(env, cfd->name);
    jobject cfopts = ColumnFamilyOptionsJni::construct(env, &(cfd->options));

    jclass jclazz = getJClass(env);
    if (jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    jmethodID mid = env->GetMethodID(jclazz, "<init>",
                                     "([BLorg/rocksdb/ColumnFamilyOptions;)V");
    if (mid == nullptr) {
      // exception thrown: NoSuchMethodException or OutOfMemoryError
      env->DeleteLocalRef(jcf_name);
      return nullptr;
    }

    jobject jcfd = env->NewObject(jclazz, mid, jcf_name, cfopts);
    if (env->ExceptionCheck()) {
      env->DeleteLocalRef(jcf_name);
      return nullptr;
    }

    return jcfd;
  }

  /**
   * Get the Java Method: ColumnFamilyDescriptor#columnFamilyName
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getColumnFamilyNameMethod(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if (jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(jclazz, "columnFamilyName", "()[B");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: ColumnFamilyDescriptor#columnFamilyOptions
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getColumnFamilyOptionsMethod(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if (jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(
        jclazz, "columnFamilyOptions", "()Lorg/rocksdb/ColumnFamilyOptions;");
    assert(mid != nullptr);
    return mid;
  }
};

// The portal class for org.rocksdb.IndexType
class IndexTypeJni {
 public:
  // Returns the equivalent org.rocksdb.IndexType for the provided
  // C++ ROCKSDB_NAMESPACE::IndexType enum
  static jbyte toJavaIndexType(
      const ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexType& index_type) {
    switch (index_type) {
      case ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexType::kBinarySearch:
        return 0x0;
      case ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexType::kHashSearch:
        return 0x1;
      case ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexType::
          kTwoLevelIndexSearch:
        return 0x2;
      case ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexType::
          kBinarySearchWithFirstKey:
        return 0x3;
      default:
        return 0x7F;  // undefined
    }
  }

  // Returns the equivalent C++ ROCKSDB_NAMESPACE::IndexType enum for the
  // provided Java org.rocksdb.IndexType
  static ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexType toCppIndexType(
      jbyte jindex_type) {
    switch (jindex_type) {
      case 0x0:
        return ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexType::
            kBinarySearch;
      case 0x1:
        return ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexType::
            kHashSearch;
      case 0x2:
        return ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexType::
            kTwoLevelIndexSearch;
      case 0x3:
        return ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexType::
            kBinarySearchWithFirstKey;
      default:
        // undefined/default
        return ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexType::
            kBinarySearch;
    }
  }
};

// The portal class for org.rocksdb.DataBlockIndexType
class DataBlockIndexTypeJni {
 public:
  // Returns the equivalent org.rocksdb.DataBlockIndexType for the provided
  // C++ ROCKSDB_NAMESPACE::DataBlockIndexType enum
  static jbyte toJavaDataBlockIndexType(
      const ROCKSDB_NAMESPACE::BlockBasedTableOptions::DataBlockIndexType&
          index_type) {
    switch (index_type) {
      case ROCKSDB_NAMESPACE::BlockBasedTableOptions::DataBlockIndexType::
          kDataBlockBinarySearch:
        return 0x0;
      case ROCKSDB_NAMESPACE::BlockBasedTableOptions::DataBlockIndexType::
          kDataBlockBinaryAndHash:
        return 0x1;
      default:
        return 0x7F;  // undefined
    }
  }

  // Returns the equivalent C++ ROCKSDB_NAMESPACE::DataBlockIndexType enum for
  // the provided Java org.rocksdb.DataBlockIndexType
  static ROCKSDB_NAMESPACE::BlockBasedTableOptions::DataBlockIndexType
  toCppDataBlockIndexType(jbyte jindex_type) {
    switch (jindex_type) {
      case 0x0:
        return ROCKSDB_NAMESPACE::BlockBasedTableOptions::DataBlockIndexType::
            kDataBlockBinarySearch;
      case 0x1:
        return ROCKSDB_NAMESPACE::BlockBasedTableOptions::DataBlockIndexType::
            kDataBlockBinaryAndHash;
      default:
        // undefined/default
        return ROCKSDB_NAMESPACE::BlockBasedTableOptions::DataBlockIndexType::
            kDataBlockBinarySearch;
    }
  }
};

// The portal class for org.rocksdb.ChecksumType
class ChecksumTypeJni {
 public:
  // Returns the equivalent org.rocksdb.ChecksumType for the provided
  // C++ ROCKSDB_NAMESPACE::ChecksumType enum
  static jbyte toJavaChecksumType(
      const ROCKSDB_NAMESPACE::ChecksumType& checksum_type) {
    switch (checksum_type) {
      case ROCKSDB_NAMESPACE::ChecksumType::kNoChecksum:
        return 0x0;
      case ROCKSDB_NAMESPACE::ChecksumType::kCRC32c:
        return 0x1;
      case ROCKSDB_NAMESPACE::ChecksumType::kxxHash:
        return 0x2;
      case ROCKSDB_NAMESPACE::ChecksumType::kxxHash64:
        return 0x3;
      default:
        return 0x7F;  // undefined
    }
  }

  // Returns the equivalent C++ ROCKSDB_NAMESPACE::ChecksumType enum for the
  // provided Java org.rocksdb.ChecksumType
  static ROCKSDB_NAMESPACE::ChecksumType toCppChecksumType(
      jbyte jchecksum_type) {
    switch (jchecksum_type) {
      case 0x0:
        return ROCKSDB_NAMESPACE::ChecksumType::kNoChecksum;
      case 0x1:
        return ROCKSDB_NAMESPACE::ChecksumType::kCRC32c;
      case 0x2:
        return ROCKSDB_NAMESPACE::ChecksumType::kxxHash;
      case 0x3:
        return ROCKSDB_NAMESPACE::ChecksumType::kxxHash64;
      default:
        // undefined/default
        return ROCKSDB_NAMESPACE::ChecksumType::kCRC32c;
    }
  }
};

// The portal class for org.rocksdb.IndexShorteningMode
class IndexShorteningModeJni {
 public:
  // Returns the equivalent org.rocksdb.IndexShorteningMode for the provided
  // C++ ROCKSDB_NAMESPACE::IndexShorteningMode enum
  static jbyte toJavaIndexShorteningMode(
      const ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexShorteningMode&
          index_shortening_mode) {
    switch (index_shortening_mode) {
      case ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexShorteningMode::
          kNoShortening:
        return 0x0;
      case ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexShorteningMode::
          kShortenSeparators:
        return 0x1;
      case ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexShorteningMode::
          kShortenSeparatorsAndSuccessor:
        return 0x2;
      default:
        return 0x7F;  // undefined
    }
  }

  // Returns the equivalent C++ ROCKSDB_NAMESPACE::IndexShorteningMode enum for
  // the provided Java org.rocksdb.IndexShorteningMode
  static ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexShorteningMode
  toCppIndexShorteningMode(jbyte jindex_shortening_mode) {
    switch (jindex_shortening_mode) {
      case 0x0:
        return ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexShorteningMode::
            kNoShortening;
      case 0x1:
        return ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexShorteningMode::
            kShortenSeparators;
      case 0x2:
        return ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexShorteningMode::
            kShortenSeparatorsAndSuccessor;
      default:
        // undefined/default
        return ROCKSDB_NAMESPACE::BlockBasedTableOptions::IndexShorteningMode::
            kShortenSeparators;
    }
  }
};

// The portal class for org.rocksdb.Priority
class PriorityJni {
 public:
  // Returns the equivalent org.rocksdb.Priority for the provided
  // C++ ROCKSDB_NAMESPACE::Env::Priority enum
  static jbyte toJavaPriority(
      const ROCKSDB_NAMESPACE::Env::Priority& priority) {
    switch (priority) {
      case ROCKSDB_NAMESPACE::Env::Priority::BOTTOM:
        return 0x0;
      case ROCKSDB_NAMESPACE::Env::Priority::LOW:
        return 0x1;
      case ROCKSDB_NAMESPACE::Env::Priority::HIGH:
        return 0x2;
      case ROCKSDB_NAMESPACE::Env::Priority::TOTAL:
        return 0x3;
      default:
        return 0x7F;  // undefined
    }
  }

  // Returns the equivalent C++ ROCKSDB_NAMESPACE::env::Priority enum for the
  // provided Java org.rocksdb.Priority
  static ROCKSDB_NAMESPACE::Env::Priority toCppPriority(jbyte jpriority) {
    switch (jpriority) {
      case 0x0:
        return ROCKSDB_NAMESPACE::Env::Priority::BOTTOM;
      case 0x1:
        return ROCKSDB_NAMESPACE::Env::Priority::LOW;
      case 0x2:
        return ROCKSDB_NAMESPACE::Env::Priority::HIGH;
      case 0x3:
        return ROCKSDB_NAMESPACE::Env::Priority::TOTAL;
      default:
        // undefined/default
        return ROCKSDB_NAMESPACE::Env::Priority::LOW;
    }
  }
};

// The portal class for org.rocksdb.ThreadType
class ThreadTypeJni {
 public:
  // Returns the equivalent org.rocksdb.ThreadType for the provided
  // C++ ROCKSDB_NAMESPACE::ThreadStatus::ThreadType enum
  static jbyte toJavaThreadType(
      const ROCKSDB_NAMESPACE::ThreadStatus::ThreadType& thread_type) {
    switch (thread_type) {
      case ROCKSDB_NAMESPACE::ThreadStatus::ThreadType::HIGH_PRIORITY:
        return 0x0;
      case ROCKSDB_NAMESPACE::ThreadStatus::ThreadType::LOW_PRIORITY:
        return 0x1;
      case ROCKSDB_NAMESPACE::ThreadStatus::ThreadType::USER:
        return 0x2;
      case ROCKSDB_NAMESPACE::ThreadStatus::ThreadType::BOTTOM_PRIORITY:
        return 0x3;
      default:
        return 0x7F;  // undefined
    }
  }

  // Returns the equivalent C++ ROCKSDB_NAMESPACE::ThreadStatus::ThreadType enum
  // for the provided Java org.rocksdb.ThreadType
  static ROCKSDB_NAMESPACE::ThreadStatus::ThreadType toCppThreadType(
      jbyte jthread_type) {
    switch (jthread_type) {
      case 0x0:
        return ROCKSDB_NAMESPACE::ThreadStatus::ThreadType::HIGH_PRIORITY;
      case 0x1:
        return ROCKSDB_NAMESPACE::ThreadStatus::ThreadType::LOW_PRIORITY;
      case 0x2:
        return ThreadStatus::ThreadType::USER;
      case 0x3:
        return ROCKSDB_NAMESPACE::ThreadStatus::ThreadType::BOTTOM_PRIORITY;
      default:
        // undefined/default
        return ROCKSDB_NAMESPACE::ThreadStatus::ThreadType::LOW_PRIORITY;
    }
  }
};

// The portal class for org.rocksdb.OperationType
class OperationTypeJni {
 public:
  // Returns the equivalent org.rocksdb.OperationType for the provided
  // C++ ROCKSDB_NAMESPACE::ThreadStatus::OperationType enum
  static jbyte toJavaOperationType(
      const ROCKSDB_NAMESPACE::ThreadStatus::OperationType& operation_type) {
    switch (operation_type) {
      case ROCKSDB_NAMESPACE::ThreadStatus::OperationType::OP_UNKNOWN:
        return 0x0;
      case ROCKSDB_NAMESPACE::ThreadStatus::OperationType::OP_COMPACTION:
        return 0x1;
      case ROCKSDB_NAMESPACE::ThreadStatus::OperationType::OP_FLUSH:
        return 0x2;
      default:
        return 0x7F;  // undefined
    }
  }

  // Returns the equivalent C++ ROCKSDB_NAMESPACE::ThreadStatus::OperationType
  // enum for the provided Java org.rocksdb.OperationType
  static ROCKSDB_NAMESPACE::ThreadStatus::OperationType toCppOperationType(
      jbyte joperation_type) {
    switch (joperation_type) {
      case 0x0:
        return ROCKSDB_NAMESPACE::ThreadStatus::OperationType::OP_UNKNOWN;
      case 0x1:
        return ROCKSDB_NAMESPACE::ThreadStatus::OperationType::OP_COMPACTION;
      case 0x2:
        return ROCKSDB_NAMESPACE::ThreadStatus::OperationType::OP_FLUSH;
      default:
        // undefined/default
        return ROCKSDB_NAMESPACE::ThreadStatus::OperationType::OP_UNKNOWN;
    }
  }
};

// The portal class for org.rocksdb.OperationStage
class OperationStageJni {
 public:
  // Returns the equivalent org.rocksdb.OperationStage for the provided
  // C++ ROCKSDB_NAMESPACE::ThreadStatus::OperationStage enum
  static jbyte toJavaOperationStage(
      const ROCKSDB_NAMESPACE::ThreadStatus::OperationStage& operation_stage) {
    switch (operation_stage) {
      case ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::STAGE_UNKNOWN:
        return 0x0;
      case ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::STAGE_FLUSH_RUN:
        return 0x1;
      case ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::
          STAGE_FLUSH_WRITE_L0:
        return 0x2;
      case ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::
          STAGE_COMPACTION_PREPARE:
        return 0x3;
      case ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::
          STAGE_COMPACTION_RUN:
        return 0x4;
      case ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::
          STAGE_COMPACTION_PROCESS_KV:
        return 0x5;
      case ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::
          STAGE_COMPACTION_INSTALL:
        return 0x6;
      case ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::
          STAGE_COMPACTION_SYNC_FILE:
        return 0x7;
      case ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::
          STAGE_PICK_MEMTABLES_TO_FLUSH:
        return 0x8;
      case ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::
          STAGE_MEMTABLE_ROLLBACK:
        return 0x9;
      case ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::
          STAGE_MEMTABLE_INSTALL_FLUSH_RESULTS:
        return 0xA;
      default:
        return 0x7F;  // undefined
    }
  }

  // Returns the equivalent C++ ROCKSDB_NAMESPACE::ThreadStatus::OperationStage
  // enum for the provided Java org.rocksdb.OperationStage
  static ROCKSDB_NAMESPACE::ThreadStatus::OperationStage toCppOperationStage(
      jbyte joperation_stage) {
    switch (joperation_stage) {
      case 0x0:
        return ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::STAGE_UNKNOWN;
      case 0x1:
        return ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::STAGE_FLUSH_RUN;
      case 0x2:
        return ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::
            STAGE_FLUSH_WRITE_L0;
      case 0x3:
        return ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::
            STAGE_COMPACTION_PREPARE;
      case 0x4:
        return ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::
            STAGE_COMPACTION_RUN;
      case 0x5:
        return ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::
            STAGE_COMPACTION_PROCESS_KV;
      case 0x6:
        return ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::
            STAGE_COMPACTION_INSTALL;
      case 0x7:
        return ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::
            STAGE_COMPACTION_SYNC_FILE;
      case 0x8:
        return ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::
            STAGE_PICK_MEMTABLES_TO_FLUSH;
      case 0x9:
        return ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::
            STAGE_MEMTABLE_ROLLBACK;
      case 0xA:
        return ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::
            STAGE_MEMTABLE_INSTALL_FLUSH_RESULTS;
      default:
        // undefined/default
        return ROCKSDB_NAMESPACE::ThreadStatus::OperationStage::STAGE_UNKNOWN;
    }
  }
};

// The portal class for org.rocksdb.StateType
class StateTypeJni {
 public:
  // Returns the equivalent org.rocksdb.StateType for the provided
  // C++ ROCKSDB_NAMESPACE::ThreadStatus::StateType enum
  static jbyte toJavaStateType(
      const ROCKSDB_NAMESPACE::ThreadStatus::StateType& state_type) {
    switch (state_type) {
      case ROCKSDB_NAMESPACE::ThreadStatus::StateType::STATE_UNKNOWN:
        return 0x0;
      case ROCKSDB_NAMESPACE::ThreadStatus::StateType::STATE_MUTEX_WAIT:
        return 0x1;
      default:
        return 0x7F;  // undefined
    }
  }

  // Returns the equivalent C++ ROCKSDB_NAMESPACE::ThreadStatus::StateType enum
  // for the provided Java org.rocksdb.StateType
  static ROCKSDB_NAMESPACE::ThreadStatus::StateType toCppStateType(
      jbyte jstate_type) {
    switch (jstate_type) {
      case 0x0:
        return ROCKSDB_NAMESPACE::ThreadStatus::StateType::STATE_UNKNOWN;
      case 0x1:
        return ROCKSDB_NAMESPACE::ThreadStatus::StateType::STATE_MUTEX_WAIT;
      default:
        // undefined/default
        return ROCKSDB_NAMESPACE::ThreadStatus::StateType::STATE_UNKNOWN;
    }
  }
};

// The portal class for org.rocksdb.ThreadStatus
class ThreadStatusJni : public JavaClass {
 public:
  /**
   * Get the Java Class org.rocksdb.ThreadStatus
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env,
        "org/rocksdb/ThreadStatus");
  }

  /**
   * Create a new Java org.rocksdb.ThreadStatus object with the same
   * properties as the provided C++ ROCKSDB_NAMESPACE::ThreadStatus object
   *
   * @param env A pointer to the Java environment
   * @param thread_status A pointer to ROCKSDB_NAMESPACE::ThreadStatus object
   *
   * @return A reference to a Java org.rocksdb.ColumnFamilyOptions object, or
   * nullptr if an an exception occurs
   */
  static jobject construct(
      JNIEnv* env, const ROCKSDB_NAMESPACE::ThreadStatus* thread_status) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    jmethodID mid = env->GetMethodID(jclazz, "<init>", "(JBLjava/lang/String;Ljava/lang/String;BJB[JB)V");
    if (mid == nullptr) {
      // exception thrown: NoSuchMethodException or OutOfMemoryError
      return nullptr;
    }

    jstring jdb_name =
        JniUtil::toJavaString(env, &(thread_status->db_name), true);
    if (env->ExceptionCheck()) {
        // an error occurred
        return nullptr;
    }

    jstring jcf_name =
        JniUtil::toJavaString(env, &(thread_status->cf_name), true);
    if (env->ExceptionCheck()) {
        // an error occurred
        env->DeleteLocalRef(jdb_name);
        return nullptr;
    }

    // long[]
    const jsize len = static_cast<jsize>(
        ROCKSDB_NAMESPACE::ThreadStatus::kNumOperationProperties);
    jlongArray joperation_properties =
        env->NewLongArray(len);
    if (joperation_properties == nullptr) {
      // an exception occurred
      env->DeleteLocalRef(jdb_name);
      env->DeleteLocalRef(jcf_name);
      return nullptr;
    }
    jboolean is_copy;
    jlong* body = env->GetLongArrayElements(joperation_properties, &is_copy);
    if (body == nullptr) {
        // exception thrown: OutOfMemoryError
        env->DeleteLocalRef(jdb_name);
        env->DeleteLocalRef(jcf_name);
        env->DeleteLocalRef(joperation_properties);
        return nullptr;
    }
    for (size_t i = 0; i < len; ++i) {
      body[i] = static_cast<jlong>(thread_status->op_properties[i]);
    }
    env->ReleaseLongArrayElements(joperation_properties, body,
                                  is_copy == JNI_TRUE ? 0 : JNI_ABORT);

    jobject jcfd = env->NewObject(jclazz, mid,
        static_cast<jlong>(thread_status->thread_id),
        ThreadTypeJni::toJavaThreadType(thread_status->thread_type),
        jdb_name,
        jcf_name,
        OperationTypeJni::toJavaOperationType(thread_status->operation_type),
        static_cast<jlong>(thread_status->op_elapsed_micros),
        OperationStageJni::toJavaOperationStage(thread_status->operation_stage),
        joperation_properties,
        StateTypeJni::toJavaStateType(thread_status->state_type));
    if (env->ExceptionCheck()) {
      // exception occurred
        env->DeleteLocalRef(jdb_name);
        env->DeleteLocalRef(jcf_name);
        env->DeleteLocalRef(joperation_properties);
      return nullptr;
    }

    // cleanup
    env->DeleteLocalRef(jdb_name);
    env->DeleteLocalRef(jcf_name);
    env->DeleteLocalRef(joperation_properties);

    return jcfd;
  }
};

// The portal class for org.rocksdb.CompactionStyle
class CompactionStyleJni {
 public:
  // Returns the equivalent org.rocksdb.CompactionStyle for the provided
  // C++ ROCKSDB_NAMESPACE::CompactionStyle enum
  static jbyte toJavaCompactionStyle(
      const ROCKSDB_NAMESPACE::CompactionStyle& compaction_style) {
    switch (compaction_style) {
      case ROCKSDB_NAMESPACE::CompactionStyle::kCompactionStyleLevel:
        return 0x0;
      case ROCKSDB_NAMESPACE::CompactionStyle::kCompactionStyleUniversal:
        return 0x1;
      case ROCKSDB_NAMESPACE::CompactionStyle::kCompactionStyleFIFO:
        return 0x2;
      case ROCKSDB_NAMESPACE::CompactionStyle::kCompactionStyleNone:
        return 0x3;
      default:
        return 0x7F;  // undefined
    }
  }

  // Returns the equivalent C++ ROCKSDB_NAMESPACE::CompactionStyle enum for the
  // provided Java org.rocksdb.CompactionStyle
  static ROCKSDB_NAMESPACE::CompactionStyle toCppCompactionStyle(
      jbyte jcompaction_style) {
    switch (jcompaction_style) {
      case 0x0:
        return ROCKSDB_NAMESPACE::CompactionStyle::kCompactionStyleLevel;
      case 0x1:
        return ROCKSDB_NAMESPACE::CompactionStyle::kCompactionStyleUniversal;
      case 0x2:
        return ROCKSDB_NAMESPACE::CompactionStyle::kCompactionStyleFIFO;
      case 0x3:
        return ROCKSDB_NAMESPACE::CompactionStyle::kCompactionStyleNone;
      default:
        // undefined/default
        return ROCKSDB_NAMESPACE::CompactionStyle::kCompactionStyleLevel;
    }
  }
};

// The portal class for org.rocksdb.CompactionReason
class CompactionReasonJni {
 public:
  // Returns the equivalent org.rocksdb.CompactionReason for the provided
  // C++ ROCKSDB_NAMESPACE::CompactionReason enum
  static jbyte toJavaCompactionReason(
      const ROCKSDB_NAMESPACE::CompactionReason& compaction_reason) {
    switch (compaction_reason) {
      case ROCKSDB_NAMESPACE::CompactionReason::kUnknown:
        return 0x0;
      case ROCKSDB_NAMESPACE::CompactionReason::kLevelL0FilesNum:
        return 0x1;
      case ROCKSDB_NAMESPACE::CompactionReason::kLevelMaxLevelSize:
        return 0x2;
      case ROCKSDB_NAMESPACE::CompactionReason::kUniversalSizeAmplification:
        return 0x3;
      case ROCKSDB_NAMESPACE::CompactionReason::kUniversalSizeRatio:
        return 0x4;
      case ROCKSDB_NAMESPACE::CompactionReason::kUniversalSortedRunNum:
        return 0x5;
      case ROCKSDB_NAMESPACE::CompactionReason::kFIFOMaxSize:
        return 0x6;
      case ROCKSDB_NAMESPACE::CompactionReason::kFIFOReduceNumFiles:
        return 0x7;
      case ROCKSDB_NAMESPACE::CompactionReason::kFIFOTtl:
        return 0x8;
      case ROCKSDB_NAMESPACE::CompactionReason::kManualCompaction:
        return 0x9;
      case ROCKSDB_NAMESPACE::CompactionReason::kFilesMarkedForCompaction:
        return 0x10;
      case ROCKSDB_NAMESPACE::CompactionReason::kBottommostFiles:
        return 0x0A;
      case ROCKSDB_NAMESPACE::CompactionReason::kTtl:
        return 0x0B;
      case ROCKSDB_NAMESPACE::CompactionReason::kFlush:
        return 0x0C;
      case ROCKSDB_NAMESPACE::CompactionReason::kExternalSstIngestion:
        return 0x0D;
      default:
        return 0x7F;  // undefined
    }
  }

  // Returns the equivalent C++ ROCKSDB_NAMESPACE::CompactionReason enum for the
  // provided Java org.rocksdb.CompactionReason
  static ROCKSDB_NAMESPACE::CompactionReason toCppCompactionReason(
      jbyte jcompaction_reason) {
    switch (jcompaction_reason) {
      case 0x0:
        return ROCKSDB_NAMESPACE::CompactionReason::kUnknown;
      case 0x1:
        return ROCKSDB_NAMESPACE::CompactionReason::kLevelL0FilesNum;
      case 0x2:
        return ROCKSDB_NAMESPACE::CompactionReason::kLevelMaxLevelSize;
      case 0x3:
        return ROCKSDB_NAMESPACE::CompactionReason::kUniversalSizeAmplification;
      case 0x4:
        return ROCKSDB_NAMESPACE::CompactionReason::kUniversalSizeRatio;
      case 0x5:
        return ROCKSDB_NAMESPACE::CompactionReason::kUniversalSortedRunNum;
      case 0x6:
        return ROCKSDB_NAMESPACE::CompactionReason::kFIFOMaxSize;
      case 0x7:
        return ROCKSDB_NAMESPACE::CompactionReason::kFIFOReduceNumFiles;
      case 0x8:
        return ROCKSDB_NAMESPACE::CompactionReason::kFIFOTtl;
      case 0x9:
        return ROCKSDB_NAMESPACE::CompactionReason::kManualCompaction;
      case 0x10:
        return ROCKSDB_NAMESPACE::CompactionReason::kFilesMarkedForCompaction;
      case 0x0A:
        return ROCKSDB_NAMESPACE::CompactionReason::kBottommostFiles;
      case 0x0B:
        return ROCKSDB_NAMESPACE::CompactionReason::kTtl;
      case 0x0C:
        return ROCKSDB_NAMESPACE::CompactionReason::kFlush;
      case 0x0D:
        return ROCKSDB_NAMESPACE::CompactionReason::kExternalSstIngestion;
      case 0x0E:
        return ROCKSDB_NAMESPACE::CompactionReason::kPeriodicCompaction;
      case 0x0F:
        return ROCKSDB_NAMESPACE::CompactionReason::kChangeTemperature;
      default:
        // undefined/default
        return ROCKSDB_NAMESPACE::CompactionReason::kUnknown;
    }
  }
};

// The portal class for org.rocksdb.WalFileType
class WalFileTypeJni {
 public:
  // Returns the equivalent org.rocksdb.WalFileType for the provided
  // C++ ROCKSDB_NAMESPACE::WalFileType enum
  static jbyte toJavaWalFileType(
      const ROCKSDB_NAMESPACE::WalFileType& wal_file_type) {
    switch (wal_file_type) {
      case ROCKSDB_NAMESPACE::WalFileType::kArchivedLogFile:
        return 0x0;
      case ROCKSDB_NAMESPACE::WalFileType::kAliveLogFile:
        return 0x1;
      default:
        return 0x7F;  // undefined
    }
  }

  // Returns the equivalent C++ ROCKSDB_NAMESPACE::WalFileType enum for the
  // provided Java org.rocksdb.WalFileType
  static ROCKSDB_NAMESPACE::WalFileType toCppWalFileType(jbyte jwal_file_type) {
    switch (jwal_file_type) {
      case 0x0:
        return ROCKSDB_NAMESPACE::WalFileType::kArchivedLogFile;
      case 0x1:
        return ROCKSDB_NAMESPACE::WalFileType::kAliveLogFile;
      default:
        // undefined/default
        return ROCKSDB_NAMESPACE::WalFileType::kAliveLogFile;
    }
  }
};

class LogFileJni : public JavaClass {
 public:
  /**
   * Create a new Java org.rocksdb.LogFile object.
   *
   * @param env A pointer to the Java environment
   * @param log_file A Cpp log file object
   *
   * @return A reference to a Java org.rocksdb.LogFile object, or
   * nullptr if an an exception occurs
   */
  static jobject fromCppLogFile(JNIEnv* env,
                                ROCKSDB_NAMESPACE::LogFile* log_file) {
    jclass jclazz = getJClass(env);
    if (jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    jmethodID mid = env->GetMethodID(jclazz, "<init>", "(Ljava/lang/String;JBJJ)V");
    if (mid == nullptr) {
      // exception thrown: NoSuchMethodException or OutOfMemoryError
      return nullptr;
    }

    std::string path_name = log_file->PathName();
    jstring jpath_name =
        ROCKSDB_NAMESPACE::JniUtil::toJavaString(env, &path_name, true);
    if (env->ExceptionCheck()) {
      // exception occurred creating java string
      return nullptr;
    }

    jobject jlog_file = env->NewObject(
        jclazz, mid, jpath_name, static_cast<jlong>(log_file->LogNumber()),
        ROCKSDB_NAMESPACE::WalFileTypeJni::toJavaWalFileType(log_file->Type()),
        static_cast<jlong>(log_file->StartSequence()),
        static_cast<jlong>(log_file->SizeFileBytes()));

    if (env->ExceptionCheck()) {
      env->DeleteLocalRef(jpath_name);
      return nullptr;
    }

    // cleanup
    env->DeleteLocalRef(jpath_name);

    return jlog_file;
  }

  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "org/rocksdb/LogFile");
  }
};

class LiveFileMetaDataJni : public JavaClass {
 public:
  /**
   * Create a new Java org.rocksdb.LiveFileMetaData object.
   *
   * @param env A pointer to the Java environment
   * @param live_file_meta_data A Cpp live file meta data object
   *
   * @return A reference to a Java org.rocksdb.LiveFileMetaData object, or
   * nullptr if an an exception occurs
   */
  static jobject fromCppLiveFileMetaData(
      JNIEnv* env, ROCKSDB_NAMESPACE::LiveFileMetaData* live_file_meta_data) {
    jclass jclazz = getJClass(env);
    if (jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    jmethodID mid = env->GetMethodID(jclazz, "<init>", "([BILjava/lang/String;Ljava/lang/String;JJJ[B[BJZJJ)V");
    if (mid == nullptr) {
      // exception thrown: NoSuchMethodException or OutOfMemoryError
      return nullptr;
    }

    jbyteArray jcolumn_family_name = ROCKSDB_NAMESPACE::JniUtil::copyBytes(
        env, live_file_meta_data->column_family_name);
    if (jcolumn_family_name == nullptr) {
      // exception occurred creating java byte array
      return nullptr;
    }

    jstring jfile_name = ROCKSDB_NAMESPACE::JniUtil::toJavaString(
        env, &live_file_meta_data->name, true);
    if (env->ExceptionCheck()) {
      // exception occurred creating java string
      env->DeleteLocalRef(jcolumn_family_name);
      return nullptr;
    }

    jstring jpath = ROCKSDB_NAMESPACE::JniUtil::toJavaString(
        env, &live_file_meta_data->db_path, true);
    if (env->ExceptionCheck()) {
      // exception occurred creating java string
      env->DeleteLocalRef(jcolumn_family_name);
      env->DeleteLocalRef(jfile_name);
      return nullptr;
    }

    jbyteArray jsmallest_key = ROCKSDB_NAMESPACE::JniUtil::copyBytes(
        env, live_file_meta_data->smallestkey);
    if (jsmallest_key == nullptr) {
      // exception occurred creating java byte array
      env->DeleteLocalRef(jcolumn_family_name);
      env->DeleteLocalRef(jfile_name);
      env->DeleteLocalRef(jpath);
      return nullptr;
    }

    jbyteArray jlargest_key = ROCKSDB_NAMESPACE::JniUtil::copyBytes(
        env, live_file_meta_data->largestkey);
    if (jlargest_key == nullptr) {
      // exception occurred creating java byte array
      env->DeleteLocalRef(jcolumn_family_name);
      env->DeleteLocalRef(jfile_name);
      env->DeleteLocalRef(jpath);
      env->DeleteLocalRef(jsmallest_key);
      return nullptr;
    }

    jobject jlive_file_meta_data = env->NewObject(jclazz, mid,
        jcolumn_family_name,
        static_cast<jint>(live_file_meta_data->level),
        jfile_name,
        jpath,
        static_cast<jlong>(live_file_meta_data->size),
        static_cast<jlong>(live_file_meta_data->smallest_seqno),
        static_cast<jlong>(live_file_meta_data->largest_seqno),
        jsmallest_key,
        jlargest_key,
        static_cast<jlong>(live_file_meta_data->num_reads_sampled),
        static_cast<jboolean>(live_file_meta_data->being_compacted),
        static_cast<jlong>(live_file_meta_data->num_entries),
        static_cast<jlong>(live_file_meta_data->num_deletions)
    );

    if (env->ExceptionCheck()) {
      env->DeleteLocalRef(jcolumn_family_name);
      env->DeleteLocalRef(jfile_name);
      env->DeleteLocalRef(jpath);
      env->DeleteLocalRef(jsmallest_key);
      env->DeleteLocalRef(jlargest_key);
      return nullptr;
    }

    // cleanup
    env->DeleteLocalRef(jcolumn_family_name);
    env->DeleteLocalRef(jfile_name);
    env->DeleteLocalRef(jpath);
    env->DeleteLocalRef(jsmallest_key);
    env->DeleteLocalRef(jlargest_key);

    return jlive_file_meta_data;
  }

  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "org/rocksdb/LiveFileMetaData");
  }
};

class SstFileMetaDataJni : public JavaClass {
 public:
  /**
   * Create a new Java org.rocksdb.SstFileMetaData object.
   *
   * @param env A pointer to the Java environment
   * @param sst_file_meta_data A Cpp sst file meta data object
   *
   * @return A reference to a Java org.rocksdb.SstFileMetaData object, or
   * nullptr if an an exception occurs
   */
  static jobject fromCppSstFileMetaData(
      JNIEnv* env,
      const ROCKSDB_NAMESPACE::SstFileMetaData* sst_file_meta_data) {
    jclass jclazz = getJClass(env);
    if (jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    jmethodID mid = env->GetMethodID(jclazz, "<init>", "(Ljava/lang/String;Ljava/lang/String;JJJ[B[BJZJJ)V");
    if (mid == nullptr) {
      // exception thrown: NoSuchMethodException or OutOfMemoryError
      return nullptr;
    }

    jstring jfile_name = ROCKSDB_NAMESPACE::JniUtil::toJavaString(
        env, &sst_file_meta_data->name, true);
    if (jfile_name == nullptr) {
      // exception occurred creating java byte array
      return nullptr;
    }

    jstring jpath = ROCKSDB_NAMESPACE::JniUtil::toJavaString(
        env, &sst_file_meta_data->db_path, true);
    if (jpath == nullptr) {
      // exception occurred creating java byte array
      env->DeleteLocalRef(jfile_name);
      return nullptr;
    }

    jbyteArray jsmallest_key = ROCKSDB_NAMESPACE::JniUtil::copyBytes(
        env, sst_file_meta_data->smallestkey);
    if (jsmallest_key == nullptr) {
      // exception occurred creating java byte array
      env->DeleteLocalRef(jfile_name);
      env->DeleteLocalRef(jpath);
      return nullptr;
    }

    jbyteArray jlargest_key = ROCKSDB_NAMESPACE::JniUtil::copyBytes(
        env, sst_file_meta_data->largestkey);
    if (jlargest_key == nullptr) {
      // exception occurred creating java byte array
      env->DeleteLocalRef(jfile_name);
      env->DeleteLocalRef(jpath);
      env->DeleteLocalRef(jsmallest_key);
      return nullptr;
    }

    jobject jsst_file_meta_data = env->NewObject(jclazz, mid,
        jfile_name,
        jpath,
        static_cast<jlong>(sst_file_meta_data->size),
        static_cast<jint>(sst_file_meta_data->smallest_seqno),
        static_cast<jlong>(sst_file_meta_data->largest_seqno),
        jsmallest_key,
        jlargest_key,
        static_cast<jlong>(sst_file_meta_data->num_reads_sampled),
        static_cast<jboolean>(sst_file_meta_data->being_compacted),
        static_cast<jlong>(sst_file_meta_data->num_entries),
        static_cast<jlong>(sst_file_meta_data->num_deletions)
    );

    if (env->ExceptionCheck()) {
      env->DeleteLocalRef(jfile_name);
      env->DeleteLocalRef(jpath);
      env->DeleteLocalRef(jsmallest_key);
      env->DeleteLocalRef(jlargest_key);
      return nullptr;
    }

    // cleanup
      env->DeleteLocalRef(jfile_name);
      env->DeleteLocalRef(jpath);
      env->DeleteLocalRef(jsmallest_key);
      env->DeleteLocalRef(jlargest_key);

    return jsst_file_meta_data;
  }

  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "org/rocksdb/SstFileMetaData");
  }
};

class LevelMetaDataJni : public JavaClass {
 public:
  /**
   * Create a new Java org.rocksdb.LevelMetaData object.
   *
   * @param env A pointer to the Java environment
   * @param level_meta_data A Cpp level meta data object
   *
   * @return A reference to a Java org.rocksdb.LevelMetaData object, or
   * nullptr if an an exception occurs
   */
  static jobject fromCppLevelMetaData(
      JNIEnv* env, const ROCKSDB_NAMESPACE::LevelMetaData* level_meta_data) {
    jclass jclazz = getJClass(env);
    if (jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    jmethodID mid = env->GetMethodID(jclazz, "<init>", "(IJ[Lorg/rocksdb/SstFileMetaData;)V");
    if (mid == nullptr) {
      // exception thrown: NoSuchMethodException or OutOfMemoryError
      return nullptr;
    }

    const jsize jlen =
        static_cast<jsize>(level_meta_data->files.size());
    jobjectArray jfiles = env->NewObjectArray(jlen, SstFileMetaDataJni::getJClass(env), nullptr);
    if (jfiles == nullptr) {
      // exception thrown: OutOfMemoryError
      return nullptr;
    }

    jsize i = 0;
    for (auto it = level_meta_data->files.begin();
        it != level_meta_data->files.end(); ++it) {
      jobject jfile = SstFileMetaDataJni::fromCppSstFileMetaData(env, &(*it));
      if (jfile == nullptr) {
        // exception occurred
        env->DeleteLocalRef(jfiles);
        return nullptr;
      }
      env->SetObjectArrayElement(jfiles, i++, jfile);
    }

    jobject jlevel_meta_data = env->NewObject(jclazz, mid,
        static_cast<jint>(level_meta_data->level),
        static_cast<jlong>(level_meta_data->size),
        jfiles
    );

    if (env->ExceptionCheck()) {
      env->DeleteLocalRef(jfiles);
      return nullptr;
    }

    // cleanup
    env->DeleteLocalRef(jfiles);

    return jlevel_meta_data;
  }

  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "org/rocksdb/LevelMetaData");
  }
};

class ColumnFamilyMetaDataJni : public JavaClass {
 public:
  /**
   * Create a new Java org.rocksdb.ColumnFamilyMetaData object.
   *
   * @param env A pointer to the Java environment
   * @param column_famly_meta_data A Cpp live file meta data object
   *
   * @return A reference to a Java org.rocksdb.ColumnFamilyMetaData object, or
   * nullptr if an an exception occurs
   */
  static jobject fromCppColumnFamilyMetaData(
      JNIEnv* env,
      const ROCKSDB_NAMESPACE::ColumnFamilyMetaData* column_famly_meta_data) {
    jclass jclazz = getJClass(env);
    if (jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    jmethodID mid = env->GetMethodID(jclazz, "<init>", "(JJ[B[Lorg/rocksdb/LevelMetaData;)V");
    if (mid == nullptr) {
      // exception thrown: NoSuchMethodException or OutOfMemoryError
      return nullptr;
    }

    jbyteArray jname = ROCKSDB_NAMESPACE::JniUtil::copyBytes(
        env, column_famly_meta_data->name);
    if (jname == nullptr) {
      // exception occurred creating java byte array
      return nullptr;
    }

    const jsize jlen =
        static_cast<jsize>(column_famly_meta_data->levels.size());
    jobjectArray jlevels = env->NewObjectArray(jlen, LevelMetaDataJni::getJClass(env), nullptr);
    if(jlevels == nullptr) {
      // exception thrown: OutOfMemoryError
      env->DeleteLocalRef(jname);
      return nullptr;
    }

    jsize i = 0;
    for (auto it = column_famly_meta_data->levels.begin();
        it != column_famly_meta_data->levels.end(); ++it) {
      jobject jlevel = LevelMetaDataJni::fromCppLevelMetaData(env, &(*it));
      if (jlevel == nullptr) {
        // exception occurred
        env->DeleteLocalRef(jname);
        env->DeleteLocalRef(jlevels);
        return nullptr;
      }
      env->SetObjectArrayElement(jlevels, i++, jlevel);
    }

    jobject jcolumn_family_meta_data = env->NewObject(jclazz, mid,
        static_cast<jlong>(column_famly_meta_data->size),
        static_cast<jlong>(column_famly_meta_data->file_count),
        jname,
        jlevels
    );

    if (env->ExceptionCheck()) {
      env->DeleteLocalRef(jname);
      env->DeleteLocalRef(jlevels);
      return nullptr;
    }

    // cleanup
    env->DeleteLocalRef(jname);
    env->DeleteLocalRef(jlevels);

    return jcolumn_family_meta_data;
  }

  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "org/rocksdb/ColumnFamilyMetaData");
  }
};

// The portal class for org.rocksdb.AbstractTraceWriter
class AbstractTraceWriterJni
    : public RocksDBNativeClass<
          const ROCKSDB_NAMESPACE::TraceWriterJniCallback*,
          AbstractTraceWriterJni> {
 public:
  /**
   * Get the Java Class org.rocksdb.AbstractTraceWriter
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return RocksDBNativeClass::getJClass(env,
        "org/rocksdb/AbstractTraceWriter");
  }

  /**
   * Get the Java Method: AbstractTraceWriter#write
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getWriteProxyMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(
        jclazz, "writeProxy", "(J)S");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: AbstractTraceWriter#closeWriter
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getCloseWriterProxyMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(
        jclazz, "closeWriterProxy", "()S");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: AbstractTraceWriter#getFileSize
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getGetFileSizeMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(
        jclazz, "getFileSize", "()J");
    assert(mid != nullptr);
    return mid;
  }
};

// The portal class for org.rocksdb.AbstractWalFilter
class AbstractWalFilterJni
    : public RocksDBNativeClass<const ROCKSDB_NAMESPACE::WalFilterJniCallback*,
                                AbstractWalFilterJni> {
 public:
  /**
   * Get the Java Class org.rocksdb.AbstractWalFilter
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return RocksDBNativeClass::getJClass(env,
        "org/rocksdb/AbstractWalFilter");
  }

  /**
   * Get the Java Method: AbstractWalFilter#columnFamilyLogNumberMap
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getColumnFamilyLogNumberMapMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(
        jclazz, "columnFamilyLogNumberMap",
        "(Ljava/util/Map;Ljava/util/Map;)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: AbstractTraceWriter#logRecordFoundProxy
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getLogRecordFoundProxyMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(
        jclazz, "logRecordFoundProxy", "(JLjava/lang/String;JJ)S");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: AbstractTraceWriter#name
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID or nullptr if the class or method id could not
   *     be retrieved
   */
  static jmethodID getNameMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    if(jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }

    static jmethodID mid = env->GetMethodID(
        jclazz, "name", "()Ljava/lang/String;");
    assert(mid != nullptr);
    return mid;
  }
};

// The portal class for org.rocksdb.WalProcessingOption
class WalProcessingOptionJni {
 public:
  // Returns the equivalent org.rocksdb.WalProcessingOption for the provided
  // C++ ROCKSDB_NAMESPACE::WalFilter::WalProcessingOption enum
  static jbyte toJavaWalProcessingOption(
      const ROCKSDB_NAMESPACE::WalFilter::WalProcessingOption&
          wal_processing_option) {
    switch (wal_processing_option) {
      case ROCKSDB_NAMESPACE::WalFilter::WalProcessingOption::
          kContinueProcessing:
        return 0x0;
      case ROCKSDB_NAMESPACE::WalFilter::WalProcessingOption::
          kIgnoreCurrentRecord:
        return 0x1;
      case ROCKSDB_NAMESPACE::WalFilter::WalProcessingOption::kStopReplay:
        return 0x2;
      case ROCKSDB_NAMESPACE::WalFilter::WalProcessingOption::kCorruptedRecord:
        return 0x3;
      default:
        return 0x7F;  // undefined
    }
  }

  // Returns the equivalent C++
  // ROCKSDB_NAMESPACE::WalFilter::WalProcessingOption enum for the provided
  // Java org.rocksdb.WalProcessingOption
  static ROCKSDB_NAMESPACE::WalFilter::WalProcessingOption
  toCppWalProcessingOption(jbyte jwal_processing_option) {
    switch (jwal_processing_option) {
      case 0x0:
        return ROCKSDB_NAMESPACE::WalFilter::WalProcessingOption::
            kContinueProcessing;
      case 0x1:
        return ROCKSDB_NAMESPACE::WalFilter::WalProcessingOption::
            kIgnoreCurrentRecord;
      case 0x2:
        return ROCKSDB_NAMESPACE::WalFilter::WalProcessingOption::kStopReplay;
      case 0x3:
        return ROCKSDB_NAMESPACE::WalFilter::WalProcessingOption::
            kCorruptedRecord;
      default:
        // undefined/default
        return ROCKSDB_NAMESPACE::WalFilter::WalProcessingOption::
            kCorruptedRecord;
    }
  }
};

// The portal class for org.rocksdb.ReusedSynchronisationType
class ReusedSynchronisationTypeJni {
 public:
  // Returns the equivalent org.rocksdb.ReusedSynchronisationType for the
  // provided C++ ROCKSDB_NAMESPACE::ReusedSynchronisationType enum
  static jbyte toJavaReusedSynchronisationType(
      const ROCKSDB_NAMESPACE::ReusedSynchronisationType&
          reused_synchronisation_type) {
    switch(reused_synchronisation_type) {
      case ROCKSDB_NAMESPACE::ReusedSynchronisationType::MUTEX:
        return 0x0;
      case ROCKSDB_NAMESPACE::ReusedSynchronisationType::ADAPTIVE_MUTEX:
        return 0x1;
      case ROCKSDB_NAMESPACE::ReusedSynchronisationType::THREAD_LOCAL:
        return 0x2;
      default:
        return 0x7F;  // undefined
    }
  }

  // Returns the equivalent C++ ROCKSDB_NAMESPACE::ReusedSynchronisationType
  // enum for the provided Java org.rocksdb.ReusedSynchronisationType
  static ROCKSDB_NAMESPACE::ReusedSynchronisationType
  toCppReusedSynchronisationType(jbyte reused_synchronisation_type) {
    switch(reused_synchronisation_type) {
      case 0x0:
        return ROCKSDB_NAMESPACE::ReusedSynchronisationType::MUTEX;
      case 0x1:
        return ROCKSDB_NAMESPACE::ReusedSynchronisationType::ADAPTIVE_MUTEX;
      case 0x2:
        return ROCKSDB_NAMESPACE::ReusedSynchronisationType::THREAD_LOCAL;
      default:
        // undefined/default
        return ROCKSDB_NAMESPACE::ReusedSynchronisationType::ADAPTIVE_MUTEX;
    }
  }
};
// The portal class for org.rocksdb.SanityLevel
class SanityLevelJni {
 public:
  // Returns the equivalent org.rocksdb.SanityLevel for the provided
  // C++ ROCKSDB_NAMESPACE::ConfigOptions::SanityLevel enum
  static jbyte toJavaSanityLevel(
      const ROCKSDB_NAMESPACE::ConfigOptions::SanityLevel &sanity_level) {
    switch (sanity_level) {
      case ROCKSDB_NAMESPACE::ConfigOptions::SanityLevel::kSanityLevelNone:
        return 0x0;
      case ROCKSDB_NAMESPACE::ConfigOptions::SanityLevel::
          kSanityLevelLooselyCompatible:
        return 0x1;
      case ROCKSDB_NAMESPACE::ConfigOptions::SanityLevel::
          kSanityLevelExactMatch:
        return -0x01;
      default:
        return -0x01;  // undefined
    }
  }

  // Returns the equivalent C++ ROCKSDB_NAMESPACE::ConfigOptions::SanityLevel enum for
  // the provided Java org.rocksdb.SanityLevel
  static ROCKSDB_NAMESPACE::ConfigOptions::SanityLevel toCppSanityLevel(
      jbyte sanity_level) {
    switch (sanity_level) {
      case 0x0:
        return ROCKSDB_NAMESPACE::ConfigOptions::kSanityLevelNone;
      case 0x1:
        return ROCKSDB_NAMESPACE::ConfigOptions::kSanityLevelLooselyCompatible;
      default:
        // undefined/default
        return ROCKSDB_NAMESPACE::ConfigOptions::kSanityLevelExactMatch;
    }
  }
};

// The portal class for org.rocksdb.AbstractListener.EnabledEventCallback
class EnabledEventCallbackJni {
 public:
  // Returns the set of equivalent C++
  // ROCKSDB_NAMESPACE::EnabledEventCallbackJni::EnabledEventCallback enums for
  // the provided Java jenabled_event_callback_values
  static std::set<EnabledEventCallback> toCppEnabledEventCallbacks(
      jlong jenabled_event_callback_values) {
    std::set<EnabledEventCallback> enabled_event_callbacks;
    for (size_t i = 0; i < EnabledEventCallback::NUM_ENABLED_EVENT_CALLBACK;
         ++i) {
      if (((1ULL << i) & jenabled_event_callback_values) > 0) {
        enabled_event_callbacks.emplace(static_cast<EnabledEventCallback>(i));
      }
    }
    return enabled_event_callbacks;
  }
};

// The portal class for org.rocksdb.AbstractEventListener
class AbstractEventListenerJni
    : public RocksDBNativeClass<
          const ROCKSDB_NAMESPACE::EventListenerJniCallback*,
          AbstractEventListenerJni> {
 public:
  /**
   * Get the Java Class org.rocksdb.AbstractEventListener
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Class or nullptr if one of the
   *     ClassFormatError, ClassCircularityError, NoClassDefFoundError,
   *     OutOfMemoryError or ExceptionInInitializerError exceptions is thrown
   */
  static jclass getJClass(JNIEnv* env) {
    return RocksDBNativeClass::getJClass(env,
                                         "org/rocksdb/AbstractEventListener");
  }

  /**
   * Get the Java Method: AbstractEventListener#onFlushCompletedProxy
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID
   */
  static jmethodID getOnFlushCompletedProxyMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID mid = env->GetMethodID(jclazz, "onFlushCompletedProxy",
                                            "(JLorg/rocksdb/FlushJobInfo;)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: AbstractEventListener#onFlushBeginProxy
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID
   */
  static jmethodID getOnFlushBeginProxyMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID mid = env->GetMethodID(jclazz, "onFlushBeginProxy",
                                            "(JLorg/rocksdb/FlushJobInfo;)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: AbstractEventListener#onTableFileDeleted
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID
   */
  static jmethodID getOnTableFileDeletedMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID mid = env->GetMethodID(
        jclazz, "onTableFileDeleted", "(Lorg/rocksdb/TableFileDeletionInfo;)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: AbstractEventListener#onCompactionBeginProxy
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID
   */
  static jmethodID getOnCompactionBeginProxyMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID mid =
        env->GetMethodID(jclazz, "onCompactionBeginProxy",
                         "(JLorg/rocksdb/CompactionJobInfo;)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: AbstractEventListener#onCompactionCompletedProxy
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID
   */
  static jmethodID getOnCompactionCompletedProxyMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID mid =
        env->GetMethodID(jclazz, "onCompactionCompletedProxy",
                         "(JLorg/rocksdb/CompactionJobInfo;)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: AbstractEventListener#onTableFileCreated
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID
   */
  static jmethodID getOnTableFileCreatedMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID mid = env->GetMethodID(
        jclazz, "onTableFileCreated", "(Lorg/rocksdb/TableFileCreationInfo;)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: AbstractEventListener#onTableFileCreationStarted
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID
   */
  static jmethodID getOnTableFileCreationStartedMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID mid =
        env->GetMethodID(jclazz, "onTableFileCreationStarted",
                         "(Lorg/rocksdb/TableFileCreationBriefInfo;)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: AbstractEventListener#onMemTableSealed
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID
   */
  static jmethodID getOnMemTableSealedMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID mid = env->GetMethodID(jclazz, "onMemTableSealed",
                                            "(Lorg/rocksdb/MemTableInfo;)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method:
   * AbstractEventListener#onColumnFamilyHandleDeletionStarted
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID
   */
  static jmethodID getOnColumnFamilyHandleDeletionStartedMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID mid =
        env->GetMethodID(jclazz, "onColumnFamilyHandleDeletionStarted",
                         "(Lorg/rocksdb/ColumnFamilyHandle;)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: AbstractEventListener#onExternalFileIngestedProxy
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID
   */
  static jmethodID getOnExternalFileIngestedProxyMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID mid =
        env->GetMethodID(jclazz, "onExternalFileIngestedProxy",
                         "(JLorg/rocksdb/ExternalFileIngestionInfo;)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: AbstractEventListener#onBackgroundError
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID
   */
  static jmethodID getOnBackgroundErrorProxyMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID mid = env->GetMethodID(jclazz, "onBackgroundErrorProxy",
                                            "(BLorg/rocksdb/Status;)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: AbstractEventListener#onStallConditionsChanged
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID
   */
  static jmethodID getOnStallConditionsChangedMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID mid = env->GetMethodID(jclazz, "onStallConditionsChanged",
                                            "(Lorg/rocksdb/WriteStallInfo;)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: AbstractEventListener#onFileReadFinish
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID
   */
  static jmethodID getOnFileReadFinishMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID mid = env->GetMethodID(
        jclazz, "onFileReadFinish", "(Lorg/rocksdb/FileOperationInfo;)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: AbstractEventListener#onFileWriteFinish
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID
   */
  static jmethodID getOnFileWriteFinishMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID mid = env->GetMethodID(
        jclazz, "onFileWriteFinish", "(Lorg/rocksdb/FileOperationInfo;)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: AbstractEventListener#onFileFlushFinish
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID
   */
  static jmethodID getOnFileFlushFinishMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID mid = env->GetMethodID(
        jclazz, "onFileFlushFinish", "(Lorg/rocksdb/FileOperationInfo;)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: AbstractEventListener#onFileSyncFinish
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID
   */
  static jmethodID getOnFileSyncFinishMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID mid = env->GetMethodID(
        jclazz, "onFileSyncFinish", "(Lorg/rocksdb/FileOperationInfo;)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: AbstractEventListener#onFileRangeSyncFinish
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID
   */
  static jmethodID getOnFileRangeSyncFinishMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID mid = env->GetMethodID(
        jclazz, "onFileRangeSyncFinish", "(Lorg/rocksdb/FileOperationInfo;)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: AbstractEventListener#onFileTruncateFinish
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID
   */
  static jmethodID getOnFileTruncateFinishMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID mid = env->GetMethodID(
        jclazz, "onFileTruncateFinish", "(Lorg/rocksdb/FileOperationInfo;)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: AbstractEventListener#onFileCloseFinish
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID
   */
  static jmethodID getOnFileCloseFinishMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID mid = env->GetMethodID(
        jclazz, "onFileCloseFinish", "(Lorg/rocksdb/FileOperationInfo;)V");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: AbstractEventListener#shouldBeNotifiedOnFileIO
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID
   */
  static jmethodID getShouldBeNotifiedOnFileIOMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID mid =
        env->GetMethodID(jclazz, "shouldBeNotifiedOnFileIO", "()Z");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: AbstractEventListener#onErrorRecoveryBeginProxy
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID
   */
  static jmethodID getOnErrorRecoveryBeginProxyMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID mid = env->GetMethodID(jclazz, "onErrorRecoveryBeginProxy",
                                            "(BLorg/rocksdb/Status;)Z");
    assert(mid != nullptr);
    return mid;
  }

  /**
   * Get the Java Method: AbstractEventListener#onErrorRecoveryCompleted
   *
   * @param env A pointer to the Java environment
   *
   * @return The Java Method ID
   */
  static jmethodID getOnErrorRecoveryCompletedMethodId(JNIEnv* env) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID mid = env->GetMethodID(jclazz, "onErrorRecoveryCompleted",
                                            "(Lorg/rocksdb/Status;)V");
    assert(mid != nullptr);
    return mid;
  }
};

class FlushJobInfoJni : public JavaClass {
 public:
  /**
   * Create a new Java org.rocksdb.FlushJobInfo object.
   *
   * @param env A pointer to the Java environment
   * @param flush_job_info A Cpp flush job info object
   *
   * @return A reference to a Java org.rocksdb.FlushJobInfo object, or
   * nullptr if an an exception occurs
   */
  static jobject fromCppFlushJobInfo(
      JNIEnv* env, const ROCKSDB_NAMESPACE::FlushJobInfo* flush_job_info) {
    jclass jclazz = getJClass(env);
    if (jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }
    static jmethodID ctor = getConstructorMethodId(env, jclazz);
    assert(ctor != nullptr);
    jstring jcf_name = JniUtil::toJavaString(env, &flush_job_info->cf_name);
    if (env->ExceptionCheck()) {
      return nullptr;
    }
    jstring jfile_path = JniUtil::toJavaString(env, &flush_job_info->file_path);
    if (env->ExceptionCheck()) {
      env->DeleteLocalRef(jfile_path);
      return nullptr;
    }
    jobject jtable_properties = TablePropertiesJni::fromCppTableProperties(
        env, flush_job_info->table_properties);
    if (jtable_properties == nullptr) {
      env->DeleteLocalRef(jcf_name);
      env->DeleteLocalRef(jfile_path);
      return nullptr;
    }
    return env->NewObject(
        jclazz, ctor, static_cast<jlong>(flush_job_info->cf_id), jcf_name,
        jfile_path, static_cast<jlong>(flush_job_info->thread_id),
        static_cast<jint>(flush_job_info->job_id),
        static_cast<jboolean>(flush_job_info->triggered_writes_slowdown),
        static_cast<jboolean>(flush_job_info->triggered_writes_stop),
        static_cast<jlong>(flush_job_info->smallest_seqno),
        static_cast<jlong>(flush_job_info->largest_seqno), jtable_properties,
        static_cast<jbyte>(flush_job_info->flush_reason));
  }

  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "org/rocksdb/FlushJobInfo");
  }

  static jmethodID getConstructorMethodId(JNIEnv* env, jclass clazz) {
    return env->GetMethodID(clazz, "<init>",
                            "(JLjava/lang/String;Ljava/lang/String;JIZZJJLorg/"
                            "rocksdb/TableProperties;B)V");
  }
};

class TableFileDeletionInfoJni : public JavaClass {
 public:
  /**
   * Create a new Java org.rocksdb.TableFileDeletionInfo object.
   *
   * @param env A pointer to the Java environment
   * @param file_del_info A Cpp table file deletion info object
   *
   * @return A reference to a Java org.rocksdb.TableFileDeletionInfo object, or
   * nullptr if an an exception occurs
   */
  static jobject fromCppTableFileDeletionInfo(
      JNIEnv* env,
      const ROCKSDB_NAMESPACE::TableFileDeletionInfo* file_del_info) {
    jclass jclazz = getJClass(env);
    if (jclazz == nullptr) {
      // exception occurred accessing class
      return nullptr;
    }
    static jmethodID ctor = getConstructorMethodId(env, jclazz);
    assert(ctor != nullptr);
    jstring jdb_name = JniUtil::toJavaString(env, &file_del_info->db_name);
    if (env->ExceptionCheck()) {
      return nullptr;
    }
    jobject jstatus = StatusJni::construct(env, file_del_info->status);
    if (jstatus == nullptr) {
      env->DeleteLocalRef(jdb_name);
      return nullptr;
    }
    return env->NewObject(jclazz, ctor, jdb_name,
                          JniUtil::toJavaString(env, &file_del_info->file_path),
                          static_cast<jint>(file_del_info->job_id), jstatus);
  }

  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "org/rocksdb/TableFileDeletionInfo");
  }

  static jmethodID getConstructorMethodId(JNIEnv* env, jclass clazz) {
    return env->GetMethodID(
        clazz, "<init>",
        "(Ljava/lang/String;Ljava/lang/String;ILorg/rocksdb/Status;)V");
  }
};

class CompactionJobInfoJni : public JavaClass {
 public:
  static jobject fromCppCompactionJobInfo(
      JNIEnv* env,
      const ROCKSDB_NAMESPACE::CompactionJobInfo* compaction_job_info) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID ctor = getConstructorMethodId(env, jclazz);
    assert(ctor != nullptr);
    return env->NewObject(jclazz, ctor,
                          reinterpret_cast<jlong>(compaction_job_info));
  }

  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "org/rocksdb/CompactionJobInfo");
  }

  static jmethodID getConstructorMethodId(JNIEnv* env, jclass clazz) {
    return env->GetMethodID(clazz, "<init>", "(J)V");
  }
};

class TableFileCreationInfoJni : public JavaClass {
 public:
  static jobject fromCppTableFileCreationInfo(
      JNIEnv* env, const ROCKSDB_NAMESPACE::TableFileCreationInfo* info) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID ctor = getConstructorMethodId(env, jclazz);
    assert(ctor != nullptr);
    jstring jdb_name = JniUtil::toJavaString(env, &info->db_name);
    if (env->ExceptionCheck()) {
      return nullptr;
    }
    jstring jcf_name = JniUtil::toJavaString(env, &info->cf_name);
    if (env->ExceptionCheck()) {
      env->DeleteLocalRef(jdb_name);
      return nullptr;
    }
    jstring jfile_path = JniUtil::toJavaString(env, &info->file_path);
    if (env->ExceptionCheck()) {
      env->DeleteLocalRef(jdb_name);
      env->DeleteLocalRef(jcf_name);
      return nullptr;
    }
    jobject jtable_properties =
        TablePropertiesJni::fromCppTableProperties(env, info->table_properties);
    if (jtable_properties == nullptr) {
      env->DeleteLocalRef(jdb_name);
      env->DeleteLocalRef(jcf_name);
      return nullptr;
    }
    jobject jstatus = StatusJni::construct(env, info->status);
    if (jstatus == nullptr) {
      env->DeleteLocalRef(jdb_name);
      env->DeleteLocalRef(jcf_name);
      env->DeleteLocalRef(jtable_properties);
      return nullptr;
    }
    return env->NewObject(jclazz, ctor, static_cast<jlong>(info->file_size),
                          jtable_properties, jstatus, jdb_name, jcf_name,
                          jfile_path, static_cast<jint>(info->job_id),
                          static_cast<jbyte>(info->reason));
  }

  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "org/rocksdb/TableFileCreationInfo");
  }

  static jmethodID getConstructorMethodId(JNIEnv* env, jclass clazz) {
    return env->GetMethodID(
        clazz, "<init>",
        "(JLorg/rocksdb/TableProperties;Lorg/rocksdb/Status;Ljava/lang/"
        "String;Ljava/lang/String;Ljava/lang/String;IB)V");
  }
};

class TableFileCreationBriefInfoJni : public JavaClass {
 public:
  static jobject fromCppTableFileCreationBriefInfo(
      JNIEnv* env, const ROCKSDB_NAMESPACE::TableFileCreationBriefInfo* info) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID ctor = getConstructorMethodId(env, jclazz);
    assert(ctor != nullptr);
    jstring jdb_name = JniUtil::toJavaString(env, &info->db_name);
    if (env->ExceptionCheck()) {
      return nullptr;
    }
    jstring jcf_name = JniUtil::toJavaString(env, &info->cf_name);
    if (env->ExceptionCheck()) {
      env->DeleteLocalRef(jdb_name);
      return nullptr;
    }
    jstring jfile_path = JniUtil::toJavaString(env, &info->file_path);
    if (env->ExceptionCheck()) {
      env->DeleteLocalRef(jdb_name);
      env->DeleteLocalRef(jcf_name);
      return nullptr;
    }
    return env->NewObject(jclazz, ctor, jdb_name, jcf_name, jfile_path,
                          static_cast<jint>(info->job_id),
                          static_cast<jbyte>(info->reason));
  }

  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "org/rocksdb/TableFileCreationBriefInfo");
  }

  static jmethodID getConstructorMethodId(JNIEnv* env, jclass clazz) {
    return env->GetMethodID(
        clazz, "<init>",
        "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;IB)V");
  }
};

class MemTableInfoJni : public JavaClass {
 public:
  static jobject fromCppMemTableInfo(
      JNIEnv* env, const ROCKSDB_NAMESPACE::MemTableInfo* info) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID ctor = getConstructorMethodId(env, jclazz);
    assert(ctor != nullptr);
    jstring jcf_name = JniUtil::toJavaString(env, &info->cf_name);
    if (env->ExceptionCheck()) {
      return nullptr;
    }
    return env->NewObject(jclazz, ctor, jcf_name,
                          static_cast<jlong>(info->first_seqno),
                          static_cast<jlong>(info->earliest_seqno),
                          static_cast<jlong>(info->num_entries),
                          static_cast<jlong>(info->num_deletes));
  }

  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "org/rocksdb/MemTableInfo");
  }

  static jmethodID getConstructorMethodId(JNIEnv* env, jclass clazz) {
    return env->GetMethodID(clazz, "<init>", "(Ljava/lang/String;JJJJ)V");
  }
};

class ExternalFileIngestionInfoJni : public JavaClass {
 public:
  static jobject fromCppExternalFileIngestionInfo(
      JNIEnv* env, const ROCKSDB_NAMESPACE::ExternalFileIngestionInfo* info) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID ctor = getConstructorMethodId(env, jclazz);
    assert(ctor != nullptr);
    jstring jcf_name = JniUtil::toJavaString(env, &info->cf_name);
    if (env->ExceptionCheck()) {
      return nullptr;
    }
    jstring jexternal_file_path =
        JniUtil::toJavaString(env, &info->external_file_path);
    if (env->ExceptionCheck()) {
      env->DeleteLocalRef(jcf_name);
      return nullptr;
    }
    jstring jinternal_file_path =
        JniUtil::toJavaString(env, &info->internal_file_path);
    if (env->ExceptionCheck()) {
      env->DeleteLocalRef(jcf_name);
      env->DeleteLocalRef(jexternal_file_path);
      return nullptr;
    }
    jobject jtable_properties =
        TablePropertiesJni::fromCppTableProperties(env, info->table_properties);
    if (jtable_properties == nullptr) {
      env->DeleteLocalRef(jcf_name);
      env->DeleteLocalRef(jexternal_file_path);
      env->DeleteLocalRef(jinternal_file_path);
      return nullptr;
    }
    return env->NewObject(
        jclazz, ctor, jcf_name, jexternal_file_path, jinternal_file_path,
        static_cast<jlong>(info->global_seqno), jtable_properties);
  }

  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "org/rocksdb/ExternalFileIngestionInfo");
  }

  static jmethodID getConstructorMethodId(JNIEnv* env, jclass clazz) {
    return env->GetMethodID(clazz, "<init>",
                            "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/"
                            "String;JLorg/rocksdb/TableProperties;)V");
  }
};

class WriteStallInfoJni : public JavaClass {
 public:
  static jobject fromCppWriteStallInfo(
      JNIEnv* env, const ROCKSDB_NAMESPACE::WriteStallInfo* info) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID ctor = getConstructorMethodId(env, jclazz);
    assert(ctor != nullptr);
    jstring jcf_name = JniUtil::toJavaString(env, &info->cf_name);
    if (env->ExceptionCheck()) {
      return nullptr;
    }
    return env->NewObject(jclazz, ctor, jcf_name,
                          static_cast<jbyte>(info->condition.cur),
                          static_cast<jbyte>(info->condition.prev));
  }

  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "org/rocksdb/WriteStallInfo");
  }

  static jmethodID getConstructorMethodId(JNIEnv* env, jclass clazz) {
    return env->GetMethodID(clazz, "<init>", "(Ljava/lang/String;BB)V");
  }
};

class FileOperationInfoJni : public JavaClass {
 public:
  static jobject fromCppFileOperationInfo(
      JNIEnv* env, const ROCKSDB_NAMESPACE::FileOperationInfo* info) {
    jclass jclazz = getJClass(env);
    assert(jclazz != nullptr);
    static jmethodID ctor = getConstructorMethodId(env, jclazz);
    assert(ctor != nullptr);
    jstring jpath = JniUtil::toJavaString(env, &info->path);
    if (env->ExceptionCheck()) {
      return nullptr;
    }
    jobject jstatus = StatusJni::construct(env, info->status);
    if (jstatus == nullptr) {
      env->DeleteLocalRef(jpath);
      return nullptr;
    }
    return env->NewObject(
        jclazz, ctor, jpath, static_cast<jlong>(info->offset),
        static_cast<jlong>(info->length),
        static_cast<jlong>(info->start_ts.time_since_epoch().count()),
        static_cast<jlong>(info->duration.count()), jstatus);
  }

  static jclass getJClass(JNIEnv* env) {
    return JavaClass::getJClass(env, "org/rocksdb/FileOperationInfo");
  }

  static jmethodID getConstructorMethodId(JNIEnv* env, jclass clazz) {
    return env->GetMethodID(clazz, "<init>",
                            "(Ljava/lang/String;JJJJLorg/rocksdb/Status;)V");
  }
};
}  // namespace ROCKSDB_NAMESPACE
#endif  // JAVA_ROCKSJNI_PORTAL_H_