#ifndef mozilla_jni_Refs_h__ #define mozilla_jni_Refs_h__ #include #include "mozilla/Move.h" #include "mozilla/jni/Utils.h" #include "nsError.h" // for nsresult #include "nsString.h" namespace mozilla { namespace jni { class Accessor; template class Constructor; template class Field; template class Method; // Wrapped object reference (e.g. jobject, jclass, etc...) template class Ref; // Wrapped local reference that inherits from Ref. template class LocalRef; // Wrapped global reference that inherits from Ref. template class GlobalRef; // Type used for a reference parameter. Default is a wrapped object // reference, but ParamImpl can be specialized to define custom behavior, // e.g. a StringParam class that automatically converts nsAString& and // nsACString& to a jstring. template struct ParamImpl { typedef Ref Type; }; template using Param = typename ParamImpl::Type; namespace detail { template struct TypeAdapter; } // namespace detail // How exception during a JNI call should be treated. enum class ExceptionMode { // Abort on unhandled excepion (default). ABORT, // Ignore the exception and return to caller. IGNORE, // Catch any exception and return a nsresult. NSRESULT, }; // 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 {}; // Base class for all JNI binding classes. // Templated so that we have one sClassRef for each class. template class Class { friend class Accessor; template friend class Constructor; template friend class Field; template friend class Method; private: static jclass sClassRef; // global reference protected: jobject mInstance; // local or global reference Class(jobject instance) : mInstance(instance) {} }; // Binding for a plain jobject. class Object : public Class { protected: Object(jobject instance) : Class(instance) {} public: typedef jni::Ref Ref; typedef jni::LocalRef LocalRef; typedef jni::GlobalRef GlobalRef; typedef const jni::Param& Param; static constexpr char name[] = "java/lang/Object"; }; // Binding for a built-in object reference other than jobject. template class TypedObject : public Class> { typedef TypedObject Self; protected: TypedObject(jobject instance) : Class>(instance) {} public: typedef jni::Ref Ref; typedef jni::LocalRef LocalRef; typedef jni::GlobalRef GlobalRef; typedef const jni::Param& Param; static const char name[]; }; // Define bindings for built-in types. typedef TypedObject String; typedef TypedObject ClassObject; typedef TypedObject Throwable; typedef TypedObject BooleanArray; typedef TypedObject ByteArray; typedef TypedObject CharArray; typedef TypedObject ShortArray; typedef TypedObject IntArray; typedef TypedObject LongArray; typedef TypedObject FloatArray; typedef TypedObject DoubleArray; typedef TypedObject ObjectArray; template<> struct ParamImpl { class Type; }; // Base class for Ref and its specializations. template class RefBase : protected Cls { typedef RefBase Self; typedef void (Self::*bool_type)() const; void non_null_reference() const {} protected: RefBase(jobject instance) : Cls(instance) {} public: // Construct a Ref form a raw JNI reference. static Ref From(JNIType obj) { return Ref(static_cast(obj)); } // Get the raw JNI reference. JNIType Get() const { return static_cast(Cls::mInstance); } bool operator==(const RefBase& other) const { // Treat two references of the same object as being the same. return Cls::mInstance == other.mInstance && GetEnvForThread()->IsSameObject( Cls::mInstance, other.mInstance) != JNI_FALSE; } bool operator!=(const RefBase& other) const { return !operator==(other); } bool operator==(decltype(nullptr)) const { return !Cls::mInstance; } bool operator!=(decltype(nullptr)) const { return !!Cls::mInstance; } Cls* operator->() { MOZ_ASSERT(Cls::mInstance); return this; } const Cls* operator->() const { MOZ_ASSERT(Cls::mInstance); return this; } // Any ref can be cast to an object ref. operator Ref() const; // Null checking (e.g. !!ref) using the safe-bool idiom. operator bool_type() const { return Cls::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; }; // Wrapped object reference (e.g. jobject, jclass, etc...) template class Ref : public RefBase { template friend class RefBase; friend struct detail::TypeAdapter>; typedef RefBase Base; protected: // 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. Ref(jobject instance) : Base(instance) {} // Protected 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& ref) : Base(ref.mInstance) {} public: MOZ_IMPLICIT Ref(decltype(nullptr)) : Base(nullptr) {} }; template RefBase::operator Ref() const { return Ref(Cls::mInstance); } template class Ref> : public RefBase, T> { friend class RefBase, T>; friend struct detail::TypeAdapter>>; typedef RefBase, T> Base; protected: Ref(jobject instance) : Base(instance) {} Ref(const Ref& ref) : Base(ref.mInstance) {} public: MOZ_IMPLICIT Ref(decltype(nullptr)) : Base(nullptr) {} }; namespace { // See explanation in LocalRef. template struct GenericObject { typedef Object Type; }; template<> struct GenericObject { typedef struct {} Type; }; template struct GenericLocalRef { template struct Type : jni::Object {}; }; template<> struct GenericLocalRef { template using Type = jni::LocalRef; }; } // namespace template class LocalRef : public Ref { template friend class LocalRef; private: // 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. typedef typename GenericObject::Type GenericObject; // 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 GenericLocalRef::template Type; static jobject NewLocalRef(JNIEnv* env, jobject obj) { if (!obj) { return nullptr; } return env->NewLocalRef(obj); } JNIEnv* const mEnv; LocalRef(JNIEnv* env, jobject instance) : Ref(instance) , mEnv(env) {} LocalRef& swap(LocalRef& other) { auto instance = other.mInstance; other.mInstance = Ref::mInstance; Ref::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(jobject instance) { return LocalRef(GetEnvForThread(), instance); } static LocalRef Adopt(JNIEnv* env, jobject instance) { return LocalRef(env, instance); } // Copy constructor. LocalRef(const LocalRef& ref) : Ref(NewLocalRef(ref.mEnv, ref.mInstance)) , mEnv(ref.mEnv) {} // Move constructor. LocalRef(LocalRef&& ref) : Ref(ref.mInstance) , mEnv(ref.mEnv) { ref.mInstance = nullptr; } explicit LocalRef(JNIEnv* env = GetEnvForThread()) : Ref(nullptr) , mEnv(env) {} // Construct a LocalRef from any Ref, // which means creating a new local reference. MOZ_IMPLICIT LocalRef(const Ref& ref) : Ref(nullptr) , mEnv(GetEnvForThread()) { Ref::mInstance = NewLocalRef(mEnv, ref.Get()); } LocalRef(JNIEnv* env, const Ref& ref) : Ref(NewLocalRef(env, ref.Get())) , mEnv(env) {} // Move a LocalRef into a LocalRef without // creating/deleting local references. MOZ_IMPLICIT LocalRef(LocalRef&& ref) : Ref(ref.mInstance) , mEnv(ref.mEnv) { ref.mInstance = nullptr; } template MOZ_IMPLICIT LocalRef(GenericLocalRef&& ref) : Ref(ref.mInstance) , mEnv(ref.mEnv) { ref.mInstance = nullptr; } // Implicitly converts nullptr to LocalRef. MOZ_IMPLICIT LocalRef(decltype(nullptr)) : Ref(nullptr) , mEnv(GetEnvForThread()) {} ~LocalRef() { if (Ref::mInstance) { mEnv->DeleteLocalRef(Ref::mInstance); Ref::mInstance = nullptr; } } // Get the JNIEnv* associated with this local reference. JNIEnv* Env() const { return mEnv; } // 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. auto Forget() -> decltype(Ref(nullptr).Get()) { const auto obj = Ref::Get(); Ref::mInstance = nullptr; return obj; } LocalRef& operator=(LocalRef ref) { return swap(ref); } LocalRef& operator=(const Ref& ref) { LocalRef newRef(mEnv, ref); return swap(newRef); } LocalRef& operator=(LocalRef&& ref) { LocalRef newRef(mozilla::Move(ref)); return swap(newRef); } template LocalRef& operator=(GenericLocalRef&& ref) { LocalRef newRef(mozilla::Move(ref)); return swap(newRef); } LocalRef& operator=(decltype(nullptr)) { LocalRef newRef(mEnv, nullptr); return swap(newRef); } }; template class GlobalRef : public Ref { private: static jobject NewGlobalRef(JNIEnv* env, jobject instance) { if (!instance) { return nullptr; } return env->NewGlobalRef(instance); } 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. auto Forget() -> decltype(Ref(nullptr).Get()) { 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=(decltype(nullptr)) { GlobalRef newRef(nullptr); return swap(newRef); } }; // Ref specialization for jstring. template<> class Ref : public RefBase { friend class RefBase; friend struct detail::TypeAdapter>; typedef RefBase, jstring> Base; protected: Ref(jobject instance) : Base(instance) {} Ref(const Ref& ref) : Base(ref.mInstance) {} public: MOZ_IMPLICIT Ref(decltype(nullptr)) : Base(nullptr) {} // Get the length of the jstring. size_t Length() const { JNIEnv* const env = GetEnvForThread(); return env->GetStringLength(Get()); } // Convert jstring to a nsString. operator nsString() const { MOZ_ASSERT(String::mInstance); JNIEnv* const env = GetEnvForThread(); const jchar* const str = env->GetStringChars(Get(), nullptr); const jsize len = env->GetStringLength(Get()); nsString result(reinterpret_cast(str), len); env->ReleaseStringChars(Get(), str); return result; } // Convert jstring to a nsCString. operator nsCString() const { return NS_ConvertUTF16toUTF8(operator nsString()); } }; // Define a custom parameter type for String, // which accepts both String::Ref and nsAString/nsACString class ParamImpl::Type : public 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()); HandleUncaughtException(env); return result; } public: MOZ_IMPLICIT Type(const String::Ref& ref) : Ref(ref.Get()) , mEnv(nullptr) {} MOZ_IMPLICIT Type(const nsAString& str, JNIEnv* env = GetEnvForThread()) : Ref(GetString(env, str)) , mEnv(env) {} MOZ_IMPLICIT Type(const char16_t* str, JNIEnv* env = GetEnvForThread()) : Ref(GetString(env, nsDependentString(str))) , mEnv(env) {} MOZ_IMPLICIT Type(const nsACString& str, JNIEnv* env = GetEnvForThread()) : Ref(GetString(env, NS_ConvertUTF8toUTF16(str))) , mEnv(env) {} MOZ_IMPLICIT Type(const char* str, JNIEnv* env = GetEnvForThread()) : Ref(GetString(env, NS_ConvertUTF8toUTF16(str))) , mEnv(env) {} ~Type() { if (mEnv) { 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 : GetEnvForThread(), *this); } }; // 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 = mozilla::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 = mozilla::Move(objRef)); } else if (clsRef) { *globalRef = clsRef; } } }; template ReturnToGlobal ReturnTo(GlobalRef* ref) { return ReturnToGlobal(ref); } } // namespace jni } // namespace mozilla #endif // mozilla_jni_Refs_h__