Global Metrics

path: .metrics.nargs.average
old: 0.8157894736842105
new: 0.6891891891891891

path: .metrics.nargs.sum
old: 31.0
new: 102.0

path: .metrics.cognitive.average
old: 8.763157894736842
new: 0.10810810810810811

path: .metrics.cognitive.sum
old: 333.0
new: 16.0

path: .metrics.loc.cloc
old: 178.0
new: 98.0

path: .metrics.loc.lloc
old: 440.0
new: 165.0

path: .metrics.loc.blank
old: 159.0
new: 200.0

path: .metrics.loc.sloc
old: 1330.0
new: 1016.0

path: .metrics.loc.ploc
old: 993.0
new: 718.0

path: .metrics.mi.mi_original
old: -51.39169165403017
new: -46.23464614846904

path: .metrics.mi.mi_sei
old: -100.70028528432344
new: -96.8634584270384

path: .metrics.nexits.sum
old: 63.0
new: 81.0

path: .metrics.nexits.average
old: 1.6578947368421053
new: 0.5472972972972973

path: .metrics.nom.functions
old: 38.0
new: 148.0

path: .metrics.nom.total
old: 38.0
new: 148.0

path: .metrics.cyclomatic.average
old: 4.760869565217392
new: 1.1702127659574468

path: .metrics.cyclomatic.sum
old: 219.0
new: 220.0

path: .metrics.halstead.vocabulary
old: 410.0
new: 246.0

path: .metrics.halstead.effort
old: 5180297.318668378
new: 4298536.836314733

path: .metrics.halstead.N1
old: 2867.0
new: 2714.0

path: .metrics.halstead.length
old: 4971.0
new: 4461.0

path: .metrics.halstead.level
old: 0.008328806807894261
new: 0.00824270177447052

path: .metrics.halstead.volume
old: 43145.69557464157
new: 35431.55720831835

path: .metrics.halstead.bugs
old: 9.97964753294615
new: 8.812365748173269

path: .metrics.halstead.time
old: 287794.2954815765
new: 238807.6020174852

path: .metrics.halstead.n1
old: 42.0
new: 30.0

path: .metrics.halstead.estimated_program_length
old: 3363.148131585688
new: 1822.2624183355647

path: .metrics.halstead.difficulty
old: 120.06521739130434
new: 121.31944444444444

path: .metrics.halstead.N2
old: 2104.0
new: 1747.0

path: .metrics.halstead.n2
old: 368.0
new: 216.0

path: .metrics.halstead.purity_ratio
old: 0.6765536374141397
new: 0.408487428454509

Spaces Data

Minimal test - lines (19, 1014)

path: .spaces[0].metrics.nargs.average
old: null
new: 0.6891891891891891

path: .spaces[0].metrics.nargs.sum
old: 0.0
new: 102.0

path: .spaces[0].metrics.nom.functions
old: 0.0
new: 148.0

path: .spaces[0].metrics.nom.total
old: 0.0
new: 148.0

path: .spaces[0].metrics.mi.mi_visual_studio
old: 73.3099929416517
new: 0.0

path: .spaces[0].metrics.mi.mi_original
old: 125.3600879302244
new: -45.65613729602259

path: .spaces[0].metrics.mi.mi_sei
old: 105.25734504973084
new: -96.8181999773116

path: .spaces[0].metrics.cyclomatic.sum
old: 1.0
new: 219.0

path: .spaces[0].metrics.cyclomatic.average
old: 1.0
new: 1.1711229946524064

path: .spaces[0].metrics.cognitive.average
old: null
new: 0.10810810810810811

path: .spaces[0].metrics.cognitive.sum
old: 0.0
new: 16.0

path: .spaces[0].metrics.loc.lloc
old: 0.0
new: 165.0

path: .spaces[0].metrics.loc.cloc
old: 0.0
new: 90.0

path: .spaces[0].metrics.loc.ploc
old: 5.0
new: 709.0

path: .spaces[0].metrics.loc.sloc
old: 5.0
new: 996.0

path: .spaces[0].metrics.loc.blank
old: 0.0
new: 197.0

path: .spaces[0].metrics.halstead.effort
old: 72.1157937828126
new: 4363051.861823224

path: .spaces[0].metrics.halstead.N2
old: 7.0
new: 1741.0

path: .spaces[0].metrics.halstead.length
old: 13.0
new: 4455.0

path: .spaces[0].metrics.halstead.level
old: 0.5714285714285714
new: 0.008079647712042887

path: .spaces[0].metrics.halstead.bugs
old: 0.005775181940550018
new: 8.90032083409357

path: .spaces[0].metrics.halstead.purity_ratio
old: 1.558820192806954
new: 0.39873370295970184

path: .spaces[0].metrics.halstead.volume
old: 41.209025018750054
new: 35251.92199290448

path: .spaces[0].metrics.halstead.difficulty
old: 1.75
new: 123.76777251184834

path: .spaces[0].metrics.halstead.n2
old: 6.0
new: 211.0

path: .spaces[0].metrics.halstead.estimated_program_length
old: 20.264662506490403
new: 1776.3586466854715

path: .spaces[0].metrics.halstead.n1
old: 3.0
new: 30.0

path: .spaces[0].metrics.halstead.vocabulary
old: 9.0
new: 241.0

path: .spaces[0].metrics.halstead.time
old: 4.0064329879340335
new: 242391.77010129025

path: .spaces[0].metrics.halstead.N1
old: 6.0
new: 2714.0

path: .spaces[0].metrics.nexits.average
old: null
new: 0.5472972972972973

path: .spaces[0].metrics.nexits.sum
old: 0.0
new: 81.0

Code

namespace mozilla {
namespace jni {

// Wrapped object reference (e.g. jobject, jclass, etc...)
template 
class Ref;
// Represents a calling context for JNI methods.
template 
class Context;
// Wrapped local reference that inherits from Ref.
template 
class LocalRef;
// Wrapped global reference that inherits from Ref.
template 
class GlobalRef;
// Wrapped weak reference that inherits from Ref.
template 
class WeakRef;
// Wrapped dangling reference that's owned by someone else.
template 
class DependentRef;

// Class to hold the native types of a method's arguments.
// For example, if a method has signature (ILjava/lang/String;)V,
// its arguments class would be jni::Args
template 
struct Args {};

class Object;

// Base class for Ref and its specializations.
template 
class Ref {
  template 
  friend class Ref;

  using Self = Ref;
  using bool_type = void (Self::*)() const;
  void non_null_reference() const {}

  // A Cls-derivative that allows copying
  // (e.g. when acting as a return value).
  struct CopyableCtx : public Context {
    CopyableCtx(JNIEnv* env, Type instance)
        : Context(env, instance) {}

    CopyableCtx(const CopyableCtx& cls)
        : Context(cls.Env(), cls.Get()) {}
  };

  // Private copy constructor so that there's no danger of assigning a
  // temporary LocalRef/GlobalRef to a Ref, and potentially use the Ref
  // after the source had been freed.
  Ref(const Ref&) = default;

 protected:
  static JNIEnv* FindEnv() {
    return Cls::callingThread == CallingThread::GECKO ? GetGeckoThreadEnv()
                                                      : GetEnvForThread();
  }

  Type mInstance;

  // Protected jobject constructor because outside code should be using
  // Ref::From. Using Ref::From makes it very easy to see which code is using
  // raw JNI types for future refactoring.
  explicit Ref(Type instance) : mInstance(instance) {}

 public:
  using JNIType = Type;

  class AutoLock {
    friend class Ref;

    JNIEnv* const mEnv;
    Type mInstance;

    explicit AutoLock(Type aInstance)
        : mEnv(FindEnv()), mInstance(mEnv->NewLocalRef(aInstance)) {
      mEnv->MonitorEnter(mInstance);
      MOZ_CATCH_JNI_EXCEPTION(mEnv);
    }

   public:
    AutoLock(AutoLock&& aOther)
        : mEnv(aOther.mEnv), mInstance(aOther.mInstance) {
      aOther.mInstance = nullptr;
    }

    ~AutoLock() { Unlock(); }

    void Unlock() {
      if (mInstance) {
        mEnv->MonitorExit(mInstance);
        mEnv->DeleteLocalRef(mInstance);
        MOZ_CATCH_JNI_EXCEPTION(mEnv);
        mInstance = nullptr;
      }
    }
  };

  // Construct a Ref form a raw JNI reference.
  static Ref From(JNIType obj) { return Ref(obj); }

  // Construct a Ref form a generic object reference.
  static Ref From(const Ref& obj) {
    return Ref(JNIType(obj.Get()));
  }

  MOZ_IMPLICIT Ref(decltype(nullptr)) : mInstance(nullptr) {}

  // Get the raw JNI reference.
  JNIType Get() const { return mInstance; }

  template 
  bool IsInstanceOf() const {
    return FindEnv()->IsInstanceOf(mInstance, typename T::Context().ClassRef());
  }

  template 
  typename T::Ref Cast() const {
#ifdef MOZ_CHECK_JNI
    MOZ_RELEASE_ASSERT(FindEnv()->IsAssignableFrom(
        Context().ClassRef(), typename T::Context().ClassRef()));
#endif
    return T::Ref::From(*this);
  }

  AutoLock Lock() const { return AutoLock(mInstance); }

  bool operator==(const Ref& other) const {
    // Treat two references of the same object as being the same.
    return mInstance == other.mInstance ||
           JNI_FALSE != FindEnv()->IsSameObject(mInstance, other.mInstance);
  }

  bool operator!=(const Ref& other) const { return !operator==(other); }

  bool operator==(decltype(nullptr)) const { return !mInstance; }

  bool operator!=(decltype(nullptr)) const { return !!mInstance; }

  CopyableCtx operator->() const { return CopyableCtx(FindEnv(), mInstance); }

  CopyableCtx operator*() const { return operator->(); }

  // Any ref can be cast to an object ref.
  operator Ref() const {
    return Ref(mInstance);
  }

  // Null checking (e.g. !!ref) using the safe-bool idiom.
  operator bool_type() const {
    return mInstance ? &Self::non_null_reference : nullptr;
  }

  // We don't allow implicit conversion to jobject because that can lead
  // to easy mistakes such as assigning a temporary LocalRef to a jobject,
  // and using the jobject after the LocalRef has been freed.

  // We don't allow explicit conversion, to make outside code use Ref::Get.
  // Using Ref::Get makes it very easy to see which code is using raw JNI
  // types to make future refactoring easier.

  // operator JNIType() const = delete;
};

// Represents a calling context for JNI methods.
template 
class Context : public Ref {
  using Ref = jni::Ref;

  static jclass sClassRef;  // global reference

 protected:
  JNIEnv* const mEnv;

 public:
  Context() : Ref(nullptr), mEnv(Ref::FindEnv()) {}

  Context(JNIEnv* env, Type instance) : Ref(instance), mEnv(env) {}

  jclass ClassRef() const {
    if (!sClassRef) {
      const jclass cls = GetClassRef(mEnv, Cls::name);
      sClassRef = jclass(mEnv->NewGlobalRef(cls));
      mEnv->DeleteLocalRef(cls);
    }
    return sClassRef;
  }

  JNIEnv* Env() const { return mEnv; }

  template 
  bool IsInstanceOf() const {
    return mEnv->IsInstanceOf(Ref::mInstance,
                              typename T::Context(mEnv, nullptr).ClassRef());
  }

  bool operator==(const Ref& other) const {
    // Treat two references of the same object as being the same.
    return Ref::mInstance == other.Get() ||
           JNI_FALSE != mEnv->IsSameObject(Ref::mInstance, other.Get());
  }

  bool operator!=(const Ref& other) const { return !operator==(other); }

  bool operator==(decltype(nullptr)) const { return !Ref::mInstance; }

  bool operator!=(decltype(nullptr)) const { return !!Ref::mInstance; }

  Cls operator->() const {
    MOZ_ASSERT(Ref::mInstance, "Null jobject");
    return Cls(*this);
  }

  const Context& operator*() const { return *this; }
};

template 
jclass Context::sClassRef;

template 
class ObjectBase {
 protected:
  const jni::Context& mCtx;

  jclass ClassRef() const { return mCtx.ClassRef(); }
  JNIEnv* Env() const { return mCtx.Env(); }
  Type Instance() const { return mCtx.Get(); }

 public:
  using Ref = jni::Ref;
  using Context = jni::Context;
  using LocalRef = jni::LocalRef;
  using GlobalRef = jni::GlobalRef;
  using WeakRef = jni::WeakRef;
  using Param = const Ref&;

  static const CallingThread callingThread = CallingThread::ANY;
  static const char name[];

  explicit ObjectBase(const Context& ctx) : mCtx(ctx) {}

  Cls* operator->() { return static_cast(this); }
};

// Binding for a plain jobject.
class Object : public ObjectBase {
 public:
  explicit Object(const Context& ctx) : ObjectBase(ctx) {}
};

// Binding for a built-in object reference other than jobject.
template 
class TypedObject : public ObjectBase, T> {
 public:
  explicit TypedObject(const Context, T>& ctx)
      : ObjectBase, T>(ctx) {}
};

// Binding for a boxed primitive object.
template 
class BoxedObject : public ObjectBase, jobject> {
 public:
  explicit BoxedObject(const Context, jobject>& ctx)
      : ObjectBase, jobject>(ctx) {}
};

template <>
const char ObjectBase::name[];
template <>
const char ObjectBase, jstring>::name[];
template <>
const char ObjectBase, jclass>::name[];
template <>
const char ObjectBase, jthrowable>::name[];
template <>
const char ObjectBase, jobject>::name[];
template <>
const char ObjectBase, jobject>::name[];
template <>
const char ObjectBase, jobject>::name[];
template <>
const char ObjectBase, jobject>::name[];
template <>
const char ObjectBase, jobject>::name[];
template <>
const char ObjectBase, jobject>::name[];
template <>
const char ObjectBase, jobject>::name[];
template <>
const char ObjectBase, jobject>::name[];
template <>
const char ObjectBase, jbooleanArray>::name[];
template <>
const char ObjectBase, jbyteArray>::name[];
template <>
const char ObjectBase, jcharArray>::name[];
template <>
const char ObjectBase, jshortArray>::name[];
template <>
const char ObjectBase, jintArray>::name[];
template <>
const char ObjectBase, jlongArray>::name[];
template <>
const char ObjectBase, jfloatArray>::name[];
template <>
const char ObjectBase, jdoubleArray>::name[];
template <>
const char ObjectBase, jobjectArray>::name[];

// Define bindings for built-in types.
using String = TypedObject;
using Class = TypedObject;
using Throwable = TypedObject;

using Boolean = BoxedObject;
using Byte = BoxedObject;
using Character = BoxedObject;
using Short = BoxedObject;
using Integer = BoxedObject;
using Long = BoxedObject;
using Float = BoxedObject;
using Double = BoxedObject;

using BooleanArray = TypedObject;
using ByteArray = TypedObject;
using CharArray = TypedObject;
using ShortArray = TypedObject;
using IntArray = TypedObject;
using LongArray = TypedObject;
using FloatArray = TypedObject;
using DoubleArray = TypedObject;
using ObjectArray = TypedObject;

namespace detail {

// See explanation in LocalRef.
template 
struct GenericObject {
  using Type = Object;
};
template <>
struct GenericObject {
  struct Type {
    using Ref = jni::Ref;
    using Context = jni::Context;
  };
};
template 
struct GenericLocalRef {
  template 
  struct Type : jni::Object {};
};
template <>
struct GenericLocalRef {
  template 
  using Type = jni::LocalRef;
};

}  // namespace detail

template 
class LocalRef : public Cls::Context {
  template 
  friend class LocalRef;

  using Ctx = typename Cls::Context;
  using Ref = typename Cls::Ref;
  using JNIType = typename Ref::JNIType;

  // In order to be able to convert LocalRef to LocalRef, we
  // need constructors and copy assignment operators that take in a
  // LocalRef argument. However, if Cls *is* Object, we would have
  // duplicated constructors and operators with LocalRef arguments. To
  // avoid this conflict, we use GenericObject, which is defined as Object for
  // LocalRef and defined as a dummy class for LocalRef.
  using GenericObject = typename detail::GenericObject::Type;

  // Similarly, GenericLocalRef is useed to convert LocalRef to,
  // LocalRef. It's defined as LocalRef for Cls == Object,
  // and defined as a dummy template class for Cls != Object.
  template 
  using GenericLocalRef =
      typename detail::GenericLocalRef::template Type;

  static JNIType NewLocalRef(JNIEnv* env, JNIType obj) {
    return JNIType(obj ? env->NewLocalRef(obj) : nullptr);
  }

  LocalRef(JNIEnv* env, JNIType instance) : Ctx(env, instance) {}

  LocalRef& swap(LocalRef& other) {
    auto instance = other.mInstance;
    other.mInstance = Ctx::mInstance;
    Ctx::mInstance = instance;
    return *this;
  }

 public:
  // Construct a LocalRef from a raw JNI local reference. Unlike Ref::From,
  // LocalRef::Adopt returns a LocalRef that will delete the local reference
  // when going out of scope.
  static LocalRef Adopt(JNIType instance) {
    return LocalRef(Ref::FindEnv(), instance);
  }

  static LocalRef Adopt(JNIEnv* env, JNIType instance) {
    return LocalRef(env, instance);
  }

  // Copy constructor.
  LocalRef(const LocalRef& ref)
      : Ctx(ref.mEnv, NewLocalRef(ref.mEnv, ref.mInstance)) {}

  // Move constructor.
  LocalRef(LocalRef&& ref) : Ctx(ref.mEnv, ref.mInstance) {
    ref.mInstance = nullptr;
  }

  explicit LocalRef(JNIEnv* env = Ref::FindEnv()) : Ctx(env, nullptr) {}

  // Construct a LocalRef from any Ref,
  // which means creating a new local reference.
  MOZ_IMPLICIT LocalRef(const Ref& ref) : Ctx(Ref::FindEnv(), nullptr) {
    Ctx::mInstance = NewLocalRef(Ctx::mEnv, ref.Get());
  }

  LocalRef(JNIEnv* env, const Ref& ref)
      : Ctx(env, NewLocalRef(env, ref.Get())) {}

  // Move a LocalRef into a LocalRef without
  // creating/deleting local references.
  MOZ_IMPLICIT LocalRef(LocalRef&& ref)
      : Ctx(ref.mEnv, JNIType(ref.mInstance)) {
    ref.mInstance = nullptr;
  }

  template 
  MOZ_IMPLICIT LocalRef(GenericLocalRef&& ref)
      : Ctx(ref.mEnv, ref.mInstance) {
    ref.mInstance = nullptr;
  }

  // Implicitly converts nullptr to LocalRef.
  MOZ_IMPLICIT LocalRef(decltype(nullptr)) : Ctx(Ref::FindEnv(), nullptr) {}

  ~LocalRef() {
    if (Ctx::mInstance) {
      Ctx::mEnv->DeleteLocalRef(Ctx::mInstance);
      Ctx::mInstance = nullptr;
    }
  }

  // Get the raw JNI reference that can be used as a return value.
  // Returns the same JNI type (jobject, jstring, etc.) as the underlying Ref.
  typename Ref::JNIType Forget() {
    const auto obj = Ctx::Get();
    Ctx::mInstance = nullptr;
    return obj;
  }

  LocalRef& operator=(LocalRef ref) & { return swap(ref); }

  LocalRef& operator=(const Ref& ref) & {
    LocalRef newRef(Ctx::mEnv, ref);
    return swap(newRef);
  }

  LocalRef& operator=(LocalRef&& ref) & {
    LocalRef newRef(std::move(ref));
    return swap(newRef);
  }

  template 
  LocalRef& operator=(GenericLocalRef&& ref) & {
    LocalRef newRef(std::move(ref));
    return swap(newRef);
  }

  LocalRef& operator=(decltype(nullptr)) & {
    LocalRef newRef(Ctx::mEnv, nullptr);
    return swap(newRef);
  }
};

template 
class GlobalRef : public Cls::Ref {
  using Ref = typename Cls::Ref;
  using JNIType = typename Ref::JNIType;

  static JNIType NewGlobalRef(JNIEnv* env, JNIType instance) {
    return JNIType(instance ? env->NewGlobalRef(instance) : nullptr);
  }

  GlobalRef& swap(GlobalRef& other) {
    auto instance = other.mInstance;
    other.mInstance = Ref::mInstance;
    Ref::mInstance = instance;
    return *this;
  }

 public:
  GlobalRef() : Ref(nullptr) {}

  // Copy constructor
  GlobalRef(const GlobalRef& ref)
      : Ref(NewGlobalRef(GetEnvForThread(), ref.mInstance)) {}

  // Move constructor
  GlobalRef(GlobalRef&& ref) : Ref(ref.mInstance) { ref.mInstance = nullptr; }

  MOZ_IMPLICIT GlobalRef(const Ref& ref)
      : Ref(NewGlobalRef(GetEnvForThread(), ref.Get())) {}

  GlobalRef(JNIEnv* env, const Ref& ref) : Ref(NewGlobalRef(env, ref.Get())) {}

  MOZ_IMPLICIT GlobalRef(const LocalRef& ref)
      : Ref(NewGlobalRef(ref.Env(), ref.Get())) {}

  // Implicitly converts nullptr to GlobalRef.
  MOZ_IMPLICIT GlobalRef(decltype(nullptr)) : Ref(nullptr) {}

  ~GlobalRef() {
    if (Ref::mInstance) {
      Clear(GetEnvForThread());
    }
  }

  // Get the raw JNI reference that can be used as a return value.
  // Returns the same JNI type (jobject, jstring, etc.) as the underlying Ref.
  typename Ref::JNIType Forget() {
    const auto obj = Ref::Get();
    Ref::mInstance = nullptr;
    return obj;
  }

  void Clear(JNIEnv* env) {
    if (Ref::mInstance) {
      env->DeleteGlobalRef(Ref::mInstance);
      Ref::mInstance = nullptr;
    }
  }

  GlobalRef& operator=(GlobalRef ref) & { return swap(ref); }

  GlobalRef& operator=(const Ref& ref) & {
    GlobalRef newRef(ref);
    return swap(newRef);
  }

  GlobalRef& operator=(const LocalRef& ref) & {
    GlobalRef newRef(ref);
    return swap(newRef);
  }

  GlobalRef& operator=(decltype(nullptr)) & {
    GlobalRef newRef(nullptr);
    return swap(newRef);
  }
};

template 
class WeakRef : public Ref {
  using Ref = Ref;
  using JNIType = typename Ref::JNIType;

  static JNIType NewWeakRef(JNIEnv* env, JNIType instance) {
    return JNIType(instance ? env->NewWeakGlobalRef(instance) : nullptr);
  }

  WeakRef& swap(WeakRef& other) {
    auto instance = other.mInstance;
    other.mInstance = Ref::mInstance;
    Ref::mInstance = instance;
    return *this;
  }

 public:
  WeakRef() : Ref(nullptr) {}

  // Copy constructor
  WeakRef(const WeakRef& ref)
      : Ref(NewWeakRef(GetEnvForThread(), ref.mInstance)) {}

  // Move constructor
  WeakRef(WeakRef&& ref) : Ref(ref.mInstance) { ref.mInstance = nullptr; }

  MOZ_IMPLICIT WeakRef(const Ref& ref)
      : Ref(NewWeakRef(GetEnvForThread(), ref.Get())) {}

  WeakRef(JNIEnv* env, const Ref& ref) : Ref(NewWeakRef(env, ref.Get())) {}

  MOZ_IMPLICIT WeakRef(const LocalRef& ref)
      : Ref(NewWeakRef(ref.Env(), ref.Get())) {}

  // Implicitly converts nullptr to WeakRef.
  MOZ_IMPLICIT WeakRef(decltype(nullptr)) : Ref(nullptr) {}

  ~WeakRef() {
    if (Ref::mInstance) {
      Clear(GetEnvForThread());
    }
  }

  // Get the raw JNI reference that can be used as a return value.
  // Returns the same JNI type (jobject, jstring, etc.) as the underlying Ref.
  typename Ref::JNIType Forget() {
    const auto obj = Ref::Get();
    Ref::mInstance = nullptr;
    return obj;
  }

  void Clear(JNIEnv* env) {
    if (Ref::mInstance) {
      env->DeleteWeakGlobalRef(Ref::mInstance);
      Ref::mInstance = nullptr;
    }
  }

  WeakRef& operator=(WeakRef ref) & { return swap(ref); }

  WeakRef& operator=(const Ref& ref) & {
    WeakRef newRef(ref);
    return swap(newRef);
  }

  WeakRef& operator=(const LocalRef& ref) & {
    WeakRef newRef(ref);
    return swap(newRef);
  }

  WeakRef& operator=(decltype(nullptr)) & {
    WeakRef newRef(nullptr);
    return swap(newRef);
  }

  void operator->() const = delete;
  void operator*() const = delete;
};

template 
class DependentRef : public Cls::Ref {
  using Ref = typename Cls::Ref;

 public:
  explicit DependentRef(typename Ref::JNIType instance) : Ref(instance) {}

  DependentRef(const DependentRef& ref) : Ref(ref.Get()) {}
};

class StringParam;

template <>
class TypedObject : public ObjectBase, jstring> {
  using Base = ObjectBase, jstring>;

 public:
  using Param = const StringParam&;

  explicit TypedObject(const Context& ctx) : Base(ctx) {}

  size_t Length() const {
    const size_t ret = Base::Env()->GetStringLength(Base::Instance());
    MOZ_CATCH_JNI_EXCEPTION(Base::Env());
    return ret;
  }

  nsString ToString() const {
    const jchar* const str =
        Base::Env()->GetStringChars(Base::Instance(), nullptr);
    const jsize len = Base::Env()->GetStringLength(Base::Instance());

    nsString result(reinterpret_cast(str), len);
    Base::Env()->ReleaseStringChars(Base::Instance(), str);
    return result;
  }

  nsCString ToCString() const { return NS_ConvertUTF16toUTF8(ToString()); }

  // Convert jstring to a nsString.
  operator nsString() const { return ToString(); }

  // Convert jstring to a nsCString.
  operator nsCString() const { return ToCString(); }
};

// Define a custom parameter type for String,
// which accepts both String::Ref and nsAString/nsACString
class StringParam : public String::Ref {
  using Ref = String::Ref;

 private:
  // Not null if we should delete ref on destruction.
  JNIEnv* const mEnv;

  static jstring GetString(JNIEnv* env, const nsAString& str) {
    const jstring result = env->NewString(
        reinterpret_cast(str.BeginReading()), str.Length());
    if (!result) {
      NS_ABORT_OOM(str.Length() * sizeof(char16_t));
    }
    MOZ_CATCH_JNI_EXCEPTION(env);
    return result;
  }

 public:
  MOZ_IMPLICIT StringParam(decltype(nullptr)) : Ref(nullptr), mEnv(nullptr) {}

  MOZ_IMPLICIT StringParam(const Ref& ref) : Ref(ref.Get()), mEnv(nullptr) {}

  MOZ_IMPLICIT StringParam(const nsAString& str, JNIEnv* env = Ref::FindEnv())
      : Ref(GetString(env, str)), mEnv(env) {}

  MOZ_IMPLICIT StringParam(const nsLiteralString& str,
                           JNIEnv* env = Ref::FindEnv())
      : Ref(GetString(env, str)), mEnv(env) {}

  MOZ_IMPLICIT StringParam(const char16_t* str, JNIEnv* env = Ref::FindEnv())
      : Ref(GetString(env, nsDependentString(str))), mEnv(env) {}

  MOZ_IMPLICIT StringParam(const nsACString& str, JNIEnv* env = Ref::FindEnv())
      : Ref(GetString(env, NS_ConvertUTF8toUTF16(str))), mEnv(env) {}

  MOZ_IMPLICIT StringParam(const nsLiteralCString& str,
                           JNIEnv* env = Ref::FindEnv())
      : Ref(GetString(env, NS_ConvertUTF8toUTF16(str))), mEnv(env) {}

  MOZ_IMPLICIT StringParam(const char* str, JNIEnv* env = Ref::FindEnv())
      : Ref(GetString(env, NS_ConvertUTF8toUTF16(str))), mEnv(env) {}

  StringParam(StringParam&& other) : Ref(other.Get()), mEnv(other.mEnv) {
    other.mInstance = nullptr;
  }

  ~StringParam() {
    if (mEnv && Get()) {
      mEnv->DeleteLocalRef(Get());
    }
  }

  operator String::LocalRef() const {
    // We can't return our existing ref because the returned
    // LocalRef could be freed first, so we need a new local ref.
    return String::LocalRef(mEnv ? mEnv : Ref::FindEnv(), *this);
  }
};

namespace detail {
template 
struct TypeAdapter;
}

// Ref specialization for arrays.
template 
class ArrayRefBase : public ObjectBase, JNIType> {
  using Base = ObjectBase, JNIType>;

 public:
  explicit ArrayRefBase(const Context, JNIType>& ctx)
      : Base(ctx) {}

  static typename Base::LocalRef New(const ElementType* data, size_t length) {
    using JNIElemType = typename detail::TypeAdapter::JNIType;
    static_assert(sizeof(ElementType) == sizeof(JNIElemType),
                  "Size of native type must match size of JNI type");
    JNIEnv* const jenv = mozilla::jni::GetEnvForThread();
    auto result = (jenv->*detail::TypeAdapter::NewArray)(length);
    MOZ_CATCH_JNI_EXCEPTION(jenv);
    (jenv->*detail::TypeAdapter::SetArray)(
        result, jsize(0), length, reinterpret_cast(data));
    MOZ_CATCH_JNI_EXCEPTION(jenv);
    return Base::LocalRef::Adopt(jenv, result);
  }

  size_t Length() const {
    const size_t ret = Base::Env()->GetArrayLength(Base::Instance());
    MOZ_CATCH_JNI_EXCEPTION(Base::Env());
    return ret;
  }

  ElementType GetElement(size_t index) const {
    using JNIElemType = typename detail::TypeAdapter::JNIType;
    static_assert(sizeof(ElementType) == sizeof(JNIElemType),
                  "Size of native type must match size of JNI type");

    ElementType ret;
    (Base::Env()->*detail::TypeAdapter::GetArray)(
        Base::Instance(), jsize(index), 1,
        reinterpret_cast(&ret));
    MOZ_CATCH_JNI_EXCEPTION(Base::Env());
    return ret;
  }

  nsTArray GetElements() const {
    using JNIElemType = typename detail::TypeAdapter::JNIType;
    static_assert(sizeof(ElementType) == sizeof(JNIElemType),
                  "Size of native type must match size of JNI type");

    const size_t len = size_t(Base::Env()->GetArrayLength(Base::Instance()));

    nsTArray array(len);
    array.SetLength(len);
    CopyTo(array.Elements(), len);
    return array;
  }

  // returns number of elements copied
  size_t CopyTo(ElementType* buffer, size_t size) const {
    using JNIElemType = typename detail::TypeAdapter::JNIType;
    static_assert(sizeof(ElementType) == sizeof(JNIElemType),
                  "Size of native type must match size of JNI type");

    const size_t len = size_t(Base::Env()->GetArrayLength(Base::Instance()));
    const size_t amountToCopy = (len > size ? size : len);
    (Base::Env()->*detail::TypeAdapter::GetArray)(
        Base::Instance(), 0, jsize(amountToCopy),
        reinterpret_cast(buffer));
    return amountToCopy;
  }

  ElementType operator[](size_t index) const { return GetElement(index); }

  operator nsTArray() const { return GetElements(); }
};

#define DEFINE_PRIMITIVE_ARRAY_REF(JNIType, ElementType)                   \
  template <>                                                              \
  class TypedObject : public ArrayRefBase { \
   public:                                                                 \
    explicit TypedObject(const Context& ctx)                               \
        : ArrayRefBase(ctx) {}                       \
  }

DEFINE_PRIMITIVE_ARRAY_REF(jbooleanArray, bool);
DEFINE_PRIMITIVE_ARRAY_REF(jbyteArray, int8_t);
DEFINE_PRIMITIVE_ARRAY_REF(jcharArray, char16_t);
DEFINE_PRIMITIVE_ARRAY_REF(jshortArray, int16_t);
DEFINE_PRIMITIVE_ARRAY_REF(jintArray, int32_t);
DEFINE_PRIMITIVE_ARRAY_REF(jlongArray, int64_t);
DEFINE_PRIMITIVE_ARRAY_REF(jfloatArray, float);
DEFINE_PRIMITIVE_ARRAY_REF(jdoubleArray, double);

#undef DEFINE_PRIMITIVE_ARRAY_REF

class ByteBuffer : public ObjectBase {
 public:
  explicit ByteBuffer(const Context& ctx)
      : ObjectBase(ctx) {}

  static LocalRef New(void* data, size_t capacity) {
    JNIEnv* const env = GetEnvForThread();
    const auto ret =
        LocalRef::Adopt(env, env->NewDirectByteBuffer(data, jlong(capacity)));
    MOZ_CATCH_JNI_EXCEPTION(env);
    return ret;
  }

  void* Address() {
    void* const ret = Env()->GetDirectBufferAddress(Instance());
    MOZ_CATCH_JNI_EXCEPTION(Env());
    return ret;
  }

  size_t Capacity() {
    const size_t ret = size_t(Env()->GetDirectBufferCapacity(Instance()));
    MOZ_CATCH_JNI_EXCEPTION(Env());
    return ret;
  }
};

template <>
const char ObjectBase::name[];

template <>
class TypedObject
    : public ObjectBase, jobjectArray> {
  using Base = ObjectBase, jobjectArray>;

 public:
  template 
  static Base::LocalRef New(size_t length,
                            typename Cls::Param initialElement = nullptr) {
    JNIEnv* const env = GetEnvForThread();
    jobjectArray array = env->NewObjectArray(
        jsize(length), typename Cls::Context(env, nullptr).ClassRef(),
        initialElement.Get());
    MOZ_CATCH_JNI_EXCEPTION(env);
    return Base::LocalRef::Adopt(env, array);
  }

  explicit TypedObject(const Context& ctx) : Base(ctx) {}

  size_t Length() const {
    const size_t ret = Base::Env()->GetArrayLength(Base::Instance());
    MOZ_CATCH_JNI_EXCEPTION(Base::Env());
    return ret;
  }

  Object::LocalRef GetElement(size_t index) const {
    auto ret = Object::LocalRef::Adopt(
        Base::Env(),
        Base::Env()->GetObjectArrayElement(Base::Instance(), jsize(index)));
    MOZ_CATCH_JNI_EXCEPTION(Base::Env());
    return ret;
  }

  nsTArray GetElements() const {
    const jsize len = size_t(Base::Env()->GetArrayLength(Base::Instance()));

    nsTArray array((size_t(len)));
    for (jsize i = 0; i < len; i++) {
      array.AppendElement(Object::LocalRef::Adopt(
          Base::Env(),
          Base::Env()->GetObjectArrayElement(Base::Instance(), i)));
      MOZ_CATCH_JNI_EXCEPTION(Base::Env());
    }
    return array;
  }

  Object::LocalRef operator[](size_t index) const { return GetElement(index); }

  operator nsTArray() const { return GetElements(); }

  void SetElement(size_t index, Object::Param element) const {
    Base::Env()->SetObjectArrayElement(Base::Instance(), jsize(index),
                                       element.Get());
    MOZ_CATCH_JNI_EXCEPTION(Base::Env());
  }
};

// Support conversion from LocalRef* to LocalRef*:
//   LocalRef foo;
//   Foo::GetFoo(&foo); // error because parameter type is LocalRef*.
//   Foo::GetFoo(ReturnTo(&foo)); // OK because ReturnTo converts the argument.
template 
class ReturnToLocal {
 private:
  LocalRef* const localRef;
  LocalRef objRef;

 public:
  explicit ReturnToLocal(LocalRef* ref) : localRef(ref) {}
  operator LocalRef*() { return &objRef; }

  ~ReturnToLocal() {
    if (objRef) {
      *localRef = std::move(objRef);
    }
  }
};

template 
ReturnToLocal ReturnTo(LocalRef* ref) {
  return ReturnToLocal(ref);
}

// Support conversion from GlobalRef* to LocalRef*:
//   GlobalRef foo;
//   Foo::GetFoo(&foo); // error because parameter type is LocalRef*.
//   Foo::GetFoo(ReturnTo(&foo)); // OK because ReturnTo converts the argument.
template 
class ReturnToGlobal {
 private:
  GlobalRef* const globalRef;
  LocalRef objRef;
  LocalRef clsRef;

 public:
  explicit ReturnToGlobal(GlobalRef* ref) : globalRef(ref) {}
  operator LocalRef*() { return &objRef; }
  operator LocalRef*() { return &clsRef; }

  ~ReturnToGlobal() {
    if (objRef) {
      *globalRef = (clsRef = std::move(objRef));
    } else if (clsRef) {
      *globalRef = clsRef;
    }
  }
};

template 
ReturnToGlobal ReturnTo(GlobalRef* ref) {
  return ReturnToGlobal(ref);
}

// Make a LocalRef from any other Ref
template 
LocalRef ToLocalRef(const Ref& aRef) {
  return LocalRef(aRef);
}

}  // namespace jni
}  // namespace mozilla