#ifndef mozilla_jni_Refs_h__
#define mozilla_jni_Refs_h__

#include <jni.h>

#include "mozilla/Move.h"
#include "mozilla/jni/Utils.h"

#include "nsError.h" // for nsresult
#include "nsString.h"
#include "nsTArray.h"

namespace mozilla {
namespace jni {

// Wrapped object reference (e.g. jobject, jclass, etc...)
template<class Cls, typename JNIType> class Ref;
// Represents a calling context for JNI methods.
template<class Cls, typename JNIType> class Context;
// Wrapped local reference that inherits from Ref.
template<class Cls> class LocalRef;
// Wrapped global reference that inherits from Ref.
template<class Cls> class GlobalRef;
// Wrapped dangling reference that's owned by someone else.
template<class Cls> 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<int32_t, jni::String::Param>
template<typename...>
struct Args {};


class Object;

// Base class for Ref and its specializations.
template<class Cls, typename Type>
class Ref
{
    template<class C, typename T> friend class Ref;

    using Self = Ref<Cls, Type>;
    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<Cls, Type>
    {
        CopyableCtx(JNIEnv* env, Type instance)
            : Context<Cls, Type>(env, instance)
        {}

        CopyableCtx(const CopyableCtx& cls)
            : Context<Cls, Type>(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;

    // Construct a Ref form a raw JNI reference.
    static Ref<Cls, Type> From(JNIType obj)
    {
        return Ref<Cls, Type>(obj);
    }

    // Construct a Ref form a generic object reference.
    static Ref<Cls, Type> From(const Ref<Object, jobject>& obj)
    {
        return Ref<Cls, Type>(JNIType(obj.Get()));
    }

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

    // Get the raw JNI reference.
    JNIType Get() const
    {
        return 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);
    }

    // Any ref can be cast to an object ref.
    operator Ref<Object, jobject>() const
    {
        return Ref<Object, jobject>(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 Cls, typename Type>
class Context : public Ref<Cls, Type>
{
    using Ref = jni::Ref<Cls, Type>;

    static jclass sClassRef; // global reference

protected:
    JNIEnv* const mEnv;

public:
    static jclass RawClassRef()
    {
        return sClassRef;
    }

    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;
    }

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

    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);
    }
};


template<class Cls, typename Type = jobject>
class ObjectBase
{
protected:
    const jni::Context<Cls, Type>& mCtx;

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

public:
    using Ref = jni::Ref<Cls, Type>;
    using Context = jni::Context<Cls, Type>;
    using LocalRef = jni::LocalRef<Cls>;
    using GlobalRef = jni::GlobalRef<Cls>;
    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<Cls*>(this);
    }
};

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

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


// Define bindings for built-in types.
using String = TypedObject<jstring>;
using Class = TypedObject<jclass>;
using Throwable = TypedObject<jthrowable>;

using BooleanArray = TypedObject<jbooleanArray>;
using ByteArray = TypedObject<jbyteArray>;
using CharArray = TypedObject<jcharArray>;
using ShortArray = TypedObject<jshortArray>;
using IntArray = TypedObject<jintArray>;
using LongArray = TypedObject<jlongArray>;
using FloatArray = TypedObject<jfloatArray>;
using DoubleArray = TypedObject<jdoubleArray>;
using ObjectArray = TypedObject<jobjectArray>;


namespace detail {

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

} // namespace

template<class Cls>
class LocalRef : public Cls::Context
{
    template<class C> 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<Object> to LocalRef<Cls>, we
    // need constructors and copy assignment operators that take in a
    // LocalRef<Object> argument. However, if Cls *is* Object, we would have
    // duplicated constructors and operators with LocalRef<Object> arguments. To
    // avoid this conflict, we use GenericObject, which is defined as Object for
    // LocalRef<non-Object> and defined as a dummy class for LocalRef<Object>.
    using GenericObject = typename detail::GenericObject<Cls>::Type;

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

    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<Cls>& ref)
        : Ctx(ref.mEnv, NewLocalRef(ref.mEnv, ref.mInstance))
    {}

    // Move constructor.
    LocalRef(LocalRef<Cls>&& 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<Object> into a LocalRef<Cls> without
    // creating/deleting local references.
    MOZ_IMPLICIT LocalRef(LocalRef<GenericObject>&& ref)
        : Ctx(ref.mEnv, JNIType(ref.mInstance))
    {
        ref.mInstance = nullptr;
    }

    template<class C>
    MOZ_IMPLICIT LocalRef(GenericLocalRef<C>&& 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<Cls>& operator=(LocalRef<Cls> ref)
    {
        return swap(ref);
    }

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

    LocalRef<Cls>& operator=(LocalRef<GenericObject>&& ref)
    {
        LocalRef<Cls> newRef(mozilla::Move(ref));
        return swap(newRef);
    }

    template<class C>
    LocalRef<Cls>& operator=(GenericLocalRef<C>&& ref)
    {
        LocalRef<Cls> newRef(mozilla::Move(ref));
        return swap(newRef);
    }

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


template<class Cls>
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<Cls>& 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<Cls>& operator=(GlobalRef<Cls> ref)
    {
        return swap(ref);
    }

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

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

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


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

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

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


class StringParam;

template<>
class TypedObject<jstring> : public ObjectBase<TypedObject<jstring>, jstring>
{
    using Base = ObjectBase<TypedObject<jstring>, 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<const char16_t*>(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<const jchar*>(str.BeginReading()),
                str.Length());
        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 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 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<typename T> struct TypeAdapter;
}

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

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

    static typename Base::LocalRef New(const ElementType* data, size_t length) {
        using JNIElemType = typename detail::TypeAdapter<ElementType>::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<ElementType>::NewArray)(length);
        MOZ_CATCH_JNI_EXCEPTION(jenv);
        (jenv->*detail::TypeAdapter<ElementType>::SetArray)(
                result, jsize(0), length,
                reinterpret_cast<const JNIElemType*>(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<ElementType>::JNIType;
        static_assert(sizeof(ElementType) == sizeof(JNIElemType),
                      "Size of native type must match size of JNI type");

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

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

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

        nsTArray<ElementType> array((size_t(len)));
        array.SetLength(size_t(len));
        (Base::Env()->*detail::TypeAdapter<ElementType>::GetArray)(
                Base::Instance(), 0, len,
                reinterpret_cast<JNIElemType*>(array.Elements()));
        return array;
    }

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

    operator nsTArray<ElementType>() const
    {
        return GetElements();
    }
};

#define DEFINE_PRIMITIVE_ARRAY_REF(JNIType, ElementType) \
    template<> \
    class TypedObject<JNIType> : public ArrayRefBase<JNIType, ElementType> \
    { \
    public: \
        explicit TypedObject(const Context& ctx) \
            : ArrayRefBase<JNIType, ElementType>(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<ByteBuffer, jobject>
{
public:
    explicit ByteBuffer(const Context& ctx)
        : ObjectBase<ByteBuffer, jobject>(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<>
class TypedObject<jobjectArray>
    : public ObjectBase<TypedObject<jobjectArray>, jobjectArray>
{
    using Base = ObjectBase<TypedObject<jobjectArray>, jobjectArray>;

public:
    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<Object::LocalRef> GetElements() const
    {
        const jsize len = size_t(Base::Env()->GetArrayLength(Base::Instance()));

        nsTArray<Object::LocalRef> 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<Object::LocalRef>() 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<T>* to LocalRef<Object>*:
//   LocalRef<Foo> foo;
//   Foo::GetFoo(&foo); // error because parameter type is LocalRef<Object>*.
//   Foo::GetFoo(ReturnTo(&foo)); // OK because ReturnTo converts the argument.
template<class Cls>
class ReturnToLocal
{
private:
    LocalRef<Cls>* const localRef;
    LocalRef<Object> objRef;

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

    ~ReturnToLocal()
    {
        if (objRef) {
            *localRef = mozilla::Move(objRef);
        }
    }
};

template<class Cls>
ReturnToLocal<Cls> ReturnTo(LocalRef<Cls>* ref)
{
    return ReturnToLocal<Cls>(ref);
}


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

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

    ~ReturnToGlobal()
    {
        if (objRef) {
            *globalRef = (clsRef = mozilla::Move(objRef));
        } else if (clsRef) {
            *globalRef = clsRef;
        }
    }
};

template<class Cls>
ReturnToGlobal<Cls> ReturnTo(GlobalRef<Cls>* ref)
{
    return ReturnToGlobal<Cls>(ref);
}

} // namespace jni
} // namespace mozilla

#endif // mozilla_jni_Refs_h__
