// Copyright (C) 2022 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #ifndef QJNIOBJECT_H #define QJNIOBJECT_H #include #if defined(Q_QDOC) || defined(Q_OS_ANDROID) #include #include QT_BEGIN_NAMESPACE class QJniObjectPrivate; class Q_CORE_EXPORT QJniObject { friend class QJniArrayBase; template struct LocalFrame { mutable JNIEnv *env; bool hasFrame = false; explicit LocalFrame(JNIEnv *env = nullptr) noexcept : env(env) { } ~LocalFrame() { if (hasFrame) env->PopLocalFrame(nullptr); } template auto newLocalRef(jobject object) { if (!hasFrame) { if (jniEnv()->PushLocalFrame(sizeof...(Args)) < 0) return T{}; // JVM is out of memory, avoid making matters worse hasFrame = true; } return static_cast(jniEnv()->NewLocalRef(object)); } template auto newLocalRef(const QJniObject &object) { return newLocalRef(object.template object()); } JNIEnv *jniEnv() const { if (!env) env = QJniEnvironment::getJniEnv(); return env; } bool checkAndClearExceptions() { return env ? QJniEnvironment::checkAndClearExceptions(env) : false; } template auto convertToJni(T &&value); template auto convertFromJni(QJniObject &&object); }; public: QJniObject(); explicit QJniObject(const char *className); explicit QJniObject(const char *className, const char *signature, ...); template>...>>* = nullptr #endif > explicit QJniObject(const char *className, Args &&...args) : QJniObject(LocalFrame{}, className, std::forward(args)...) { } private: template explicit QJniObject(LocalFrame localFrame, const char *className, Args &&...args) : QJniObject(className, QtJniTypes::constructorSignature().data(), localFrame.convertToJni(std::forward(args))...) { } public: explicit QJniObject(jclass clazz); explicit QJniObject(jclass clazz, const char *signature, ...); template>...>>* = nullptr #endif > explicit QJniObject(jclass clazz, Args &&...args) : QJniObject(clazz, QtJniTypes::constructorSignature().data(), std::forward(args)...) {} QJniObject(jobject globalRef); QJniObject(const QJniObject &other) noexcept = default; QJniObject(QJniObject &&other) noexcept = default; QJniObject &operator=(const QJniObject &other) noexcept = default; QJniObject &operator=(QJniObject &&other) noexcept = default; ~QJniObject(); template static inline QJniObject construct(Args &&...args) { LocalFrame frame; return QJniObject(QtJniTypes::Traits::className().data(), QtJniTypes::constructorSignature().data(), frame.convertToJni(std::forward(args))...); } jobject object() const; template T object() const { QtJniTypes::assertObjectType(); return static_cast(javaObject()); } jclass objectClass() const; QByteArray className() const; template = true #endif > auto callMethod(const char *methodName, const char *signature, Args &&...args) const { LocalFrame frame(jniEnv()); if constexpr (QtJniTypes::isObjectType()) { return frame.template convertFromJni(callObjectMethod(methodName, signature, frame.convertToJni(std::forward(args))...)); } else { jmethodID id = getCachedMethodID(frame.jniEnv(), methodName, signature); if (id) { if constexpr (std::is_same_v) { callVoidMethodV(frame.jniEnv(), id, frame.convertToJni(std::forward(args))...); frame.checkAndClearExceptions(); } else { Ret res{}; callMethodForType(frame.jniEnv(), res, object(), id, frame.convertToJni(std::forward(args))...); if (frame.checkAndClearExceptions()) res = {}; return res; } } if constexpr (!std::is_same_v) return Ret{}; } } template = true #endif > auto callMethod(const char *methodName, Args &&...args) const { constexpr auto signature = QtJniTypes::methodSignature(); return callMethod(methodName, signature.data(), std::forward(args)...); } template = true #endif > QJniObject callObjectMethod(const char *methodName, Args &&...args) const { QtJniTypes::assertObjectType(); constexpr auto signature = QtJniTypes::methodSignature(); LocalFrame frame(jniEnv()); return frame.template convertFromJni(callObjectMethod(methodName, signature, frame.convertToJni(std::forward(args))...)); } QJniObject callObjectMethod(const char *methodName, const char *signature, ...) const; template static auto callStaticMethod(const char *className, const char *methodName, const char *signature, Args &&...args) { JNIEnv *env = QJniEnvironment::getJniEnv(); jclass clazz = QJniObject::loadClass(className, env); return callStaticMethod(clazz, methodName, signature, std::forward(args)...); } template static auto callStaticMethod(jclass clazz, const char *methodName, const char *signature, Args &&...args) { JNIEnv *env = QJniEnvironment::getJniEnv(); jmethodID id = clazz ? getMethodID(env, clazz, methodName, signature, true) : 0; return callStaticMethod(clazz, id, std::forward(args)...); } template = true #endif > static auto callStaticMethod(jclass clazz, jmethodID methodId, Args &&...args) { LocalFrame frame; if constexpr (QtJniTypes::isObjectType()) { return frame.template convertFromJni(callStaticObjectMethod(clazz, methodId, frame.convertToJni(std::forward(args))...)); } else { if (clazz && methodId) { if constexpr (std::is_same_v) { callStaticMethodForVoid(frame.jniEnv(), clazz, methodId, frame.convertToJni(std::forward(args))...); frame.checkAndClearExceptions(); } else { Ret res{}; callStaticMethodForType(frame.jniEnv(), res, clazz, methodId, frame.convertToJni(std::forward(args))...); if (frame.checkAndClearExceptions()) res = {}; return res; } } if constexpr (!std::is_same_v) return Ret{}; } } template = true #endif > static auto callStaticMethod(const char *className, const char *methodName, Args &&...args) { JNIEnv *env = QJniEnvironment::getJniEnv(); jclass clazz = QJniObject::loadClass(className, env); const jmethodID id = clazz ? getMethodID(env, clazz, methodName, QtJniTypes::methodSignature().data(), true) : 0; return callStaticMethod(clazz, id, std::forward(args)...); } template = true #endif > static auto callStaticMethod(jclass clazz, const char *methodName, Args &&...args) { constexpr auto signature = QtJniTypes::methodSignature(); return callStaticMethod(clazz, methodName, signature.data(), std::forward(args)...); } template = true #endif > static auto callStaticMethod(const char *methodName, Args &&...args) { JNIEnv *env = QJniEnvironment::getJniEnv(); const jclass clazz = QJniObject::loadClass(QtJniTypes::Traits::className().data(), env); const jmethodID id = clazz ? getMethodID(env, clazz, methodName, QtJniTypes::methodSignature().data(), true) : 0; return callStaticMethod(clazz, id, std::forward(args)...); } static QJniObject callStaticObjectMethod(const char *className, const char *methodName, const char *signature, ...); static QJniObject callStaticObjectMethod(jclass clazz, const char *methodName, const char *signature, ...); static QJniObject callStaticObjectMethod(jclass clazz, jmethodID methodId, ...); template = true #endif > static QJniObject callStaticObjectMethod(const char *className, const char *methodName, Args &&...args) { QtJniTypes::assertObjectType(); constexpr auto signature = QtJniTypes::methodSignature(); LocalFrame frame; return frame.template convertFromJni(callStaticObjectMethod(className, methodName, signature.data(), frame.convertToJni(std::forward(args))...)); } template = true #endif > static QJniObject callStaticObjectMethod(jclass clazz, const char *methodName, Args &&...args) { QtJniTypes::assertObjectType(); constexpr auto signature = QtJniTypes::methodSignature(); LocalFrame frame; return frame.template convertFromJni(callStaticObjectMethod(clazz, methodName, signature.data(), frame.convertToJni(std::forward(args))...)); } template = true #endif > auto getField(const char *fieldName) const { LocalFrame frame(jniEnv()); if constexpr (QtJniTypes::isObjectType()) { return frame.template convertFromJni(getObjectField(fieldName)); } else { T res{}; constexpr auto signature = QtJniTypes::fieldSignature(); jfieldID id = getCachedFieldID(frame.jniEnv(), fieldName, signature); if (id) { getFieldForType(frame.jniEnv(), res, object(), id); if (frame.checkAndClearExceptions()) res = {}; } return res; } } template = true #endif > static auto getStaticField(const char *className, const char *fieldName) { LocalFrame frame; if constexpr (QtJniTypes::isObjectType()) { return frame.template convertFromJni(getStaticObjectField(className, fieldName)); } else { jclass clazz = QJniObject::loadClass(className, frame.jniEnv()); if (!clazz) return T{}; return getStaticField(clazz, fieldName); } } template = true #endif > static auto getStaticField(jclass clazz, const char *fieldName) { LocalFrame frame; if constexpr (QtJniTypes::isObjectType()) { return frame.template convertFromJni(getStaticObjectField(clazz, fieldName)); } else { T res{}; constexpr auto signature = QtJniTypes::fieldSignature(); jfieldID id = getFieldID(frame.jniEnv(), clazz, fieldName, signature, true); if (id) { getStaticFieldForType(frame.jniEnv(), res, clazz, id); if (frame.checkAndClearExceptions()) res = {}; } return res; } } template = true #endif > static auto getStaticField(const char *fieldName) { return getStaticField(QtJniTypes::Traits::className(), fieldName); } template (), bool> = true #endif > QJniObject getObjectField(const char *fieldName) const { constexpr auto signature = QtJniTypes::fieldSignature(); return getObjectField(fieldName, signature); } QJniObject getObjectField(const char *fieldName, const char *signature) const; template (), bool> = true #endif > static QJniObject getStaticObjectField(const char *className, const char *fieldName) { constexpr auto signature = QtJniTypes::fieldSignature(); return getStaticObjectField(className, fieldName, signature); } static QJniObject getStaticObjectField(const char *className, const char *fieldName, const char *signature); template (), bool> = true #endif > static QJniObject getStaticObjectField(jclass clazz, const char *fieldName) { constexpr auto signature = QtJniTypes::fieldSignature(); return getStaticObjectField(clazz, fieldName, signature); } static QJniObject getStaticObjectField(jclass clazz, const char *fieldName, const char *signature); template = true #endif > void setField(const char *fieldName, T value) { constexpr auto signature = QtJniTypes::fieldSignature(); jfieldID id = getCachedFieldID(jniEnv(), fieldName, signature); if (id) { setFieldForType(jniEnv(), object(), id, value); QJniEnvironment::checkAndClearExceptions(jniEnv()); } } template = true #endif > void setField(const char *fieldName, const char *signature, T value) { jfieldID id = getCachedFieldID(jniEnv(), fieldName, signature); if (id) { setFieldForType(jniEnv(), object(), id, value); QJniEnvironment::checkAndClearExceptions(jniEnv()); } } template = true #endif > static void setStaticField(const char *className, const char *fieldName, T value) { LocalFrame frame; jclass clazz = QJniObject::loadClass(className, frame.jniEnv()); if (!clazz) return; constexpr auto signature = QtJniTypes::fieldSignature(); jfieldID id = getCachedFieldID(frame.jniEnv(), clazz, className, fieldName, signature, true); if (!id) return; setStaticFieldForType(frame.jniEnv(), clazz, id, value); frame.checkAndClearExceptions(); } template = true #endif > static void setStaticField(const char *className, const char *fieldName, const char *signature, T value) { JNIEnv *env = QJniEnvironment::getJniEnv(); jclass clazz = QJniObject::loadClass(className, env); if (!clazz) return; jfieldID id = getCachedFieldID(env, clazz, className, fieldName, signature, true); if (id) { setStaticFieldForType(env, clazz, id, value); QJniEnvironment::checkAndClearExceptions(env); } } template = true #endif > static void setStaticField(jclass clazz, const char *fieldName, const char *signature, T value) { JNIEnv *env = QJniEnvironment::getJniEnv(); jfieldID id = getFieldID(env, clazz, fieldName, signature, true); if (id) { setStaticFieldForType(env, clazz, id, value); QJniEnvironment::checkAndClearExceptions(env); } } template = true #endif > static void setStaticField(jclass clazz, const char *fieldName, T value) { setStaticField(clazz, fieldName, QtJniTypes::fieldSignature(), value); } template = true #endif > static void setStaticField(const char *fieldName, T value) { setStaticField(QtJniTypes::Traits::className(), fieldName, value); } static QJniObject fromString(const QString &string); QString toString() const; static bool isClassAvailable(const char *className); bool isValid() const; // This function takes ownership of the jobject and releases the local ref. before returning. static QJniObject fromLocalRef(jobject lref); template , bool> = true> QJniObject &operator=(T obj) { assign(static_cast(obj)); return *this; } protected: QJniObject(Qt::Initialization) {} JNIEnv *jniEnv() const noexcept; private: static jclass loadClass(const QByteArray &className, JNIEnv *env); #if QT_CORE_REMOVED_SINCE(6, 7) // these need to stay in the ABI as they were used in inline methods before 6.7 static jclass loadClass(const QByteArray &className, JNIEnv *env, bool binEncoded); static QByteArray toBinaryEncClassName(const QByteArray &className); void callVoidMethodV(JNIEnv *env, jmethodID id, va_list args) const; #endif static jfieldID getCachedFieldID(JNIEnv *env, jclass clazz, const QByteArray &className, const char *name, const char *signature, bool isStatic = false); jfieldID getCachedFieldID(JNIEnv *env, const char *name, const char *signature, bool isStatic = false) const; static jmethodID getCachedMethodID(JNIEnv *env, jclass clazz, const QByteArray &className, const char *name, const char *signature, bool isStatic = false); jmethodID getCachedMethodID(JNIEnv *env, const char *name, const char *signature, bool isStatic = false) const; static jfieldID getFieldID(JNIEnv *env, jclass clazz, const char *name, const char *signature, bool isStatic = false); static jmethodID getMethodID(JNIEnv *env, jclass clazz, const char *name, const char *signature, bool isStatic = false); void callVoidMethodV(JNIEnv *env, jmethodID id, ...) const; bool isSameObject(jobject obj) const; bool isSameObject(const QJniObject &other) const; void assign(jobject obj); jobject javaObject() const; friend bool operator==(const QJniObject &, const QJniObject &); friend bool operator!=(const QJniObject&, const QJniObject&); template static constexpr void callMethodForType(JNIEnv *env, T &res, jobject obj, jmethodID id, ...) { va_list args = {}; va_start(args, id); QtJniTypes::Caller::callMethodForType(env, res, obj, id, args); va_end(args); } template static constexpr void callStaticMethodForType(JNIEnv *env, T &res, jclass clazz, jmethodID id, ...) { if (!clazz || !id) return; va_list args = {}; va_start(args, id); QtJniTypes::Caller::callStaticMethodForType(env, res, clazz, id, args); va_end(args); } static void callStaticMethodForVoid(JNIEnv *env, jclass clazz, jmethodID id, ...) { if (!clazz || !id) return; va_list args; va_start(args, id); env->CallStaticVoidMethodV(clazz, id, args); va_end(args); } template static constexpr void getFieldForType(JNIEnv *env, T &res, jobject obj, jfieldID id) { QtJniTypes::Caller::getFieldForType(env, res, obj, id); } template static constexpr void getStaticFieldForType(JNIEnv *env, T &res, jclass clazz, jfieldID id) { QtJniTypes::Caller::getStaticFieldForType(env, res, clazz, id); } template static constexpr void setFieldForType(JNIEnv *env, jobject obj, jfieldID id, T value) { if constexpr (QtJniTypes::isObjectType()) { LocalFrame frame(env); env->SetObjectField(obj, id, static_cast(frame.convertToJni(value))); } else { QtJniTypes::Caller::setFieldForType(env, obj, id, value); } } template static constexpr void setStaticFieldForType(JNIEnv *env, jclass clazz, jfieldID id, T value) { if constexpr (QtJniTypes::isObjectType()) { LocalFrame frame(env); env->SetStaticObjectField(clazz, id, static_cast(frame.convertToJni(value))); } else { QtJniTypes::Caller::setStaticFieldForType(env, clazz, id, value); } } friend QJniObjectPrivate; QSharedPointer d; }; inline bool operator==(const QJniObject &obj1, const QJniObject &obj2) { return obj1.isSameObject(obj2); } inline bool operator!=(const QJniObject &obj1, const QJniObject &obj2) { return !obj1.isSameObject(obj2); } namespace QtJniTypes { struct JObjectBase { operator QJniObject() const { return m_object; } bool isValid() const { return m_object.isValid(); } jclass objectClass() const { return m_object.objectClass(); } QString toString() const { return m_object.toString(); } template T object() const { return m_object.object(); } protected: JObjectBase() = default; ~JObjectBase() = default; Q_IMPLICIT JObjectBase(jobject object) : m_object(object) {} Q_IMPLICIT JObjectBase(const QJniObject &object) : m_object(object) {} Q_IMPLICIT JObjectBase(QJniObject &&object) noexcept : m_object(std::move(object)) {} QJniObject m_object; }; template class JObject : public JObjectBase { public: using Class = Type; JObject() : JObjectBase{QJniObject(QtJniTypes::Traits::className())} {} Q_IMPLICIT JObject(jobject object) : JObjectBase(object) {} Q_IMPLICIT JObject(const QJniObject &object) : JObjectBase(object) {} Q_IMPLICIT JObject(QJniObject &&object) noexcept : JObjectBase(std::move(object)) {} // base class destructor is protected, so need to provide all SMFs JObject(const JObject &other) = default; JObject(JObject &&other) noexcept = default; JObject &operator=(const JObject &other) = default; JObject &operator=(JObject &&other) noexcept = default; ~JObject() = default; template, bool> = true , IfValidSignatureTypes = true > explicit JObject(Arg && arg, Args &&...args) : JObjectBase{QJniObject(QtJniTypes::Traits::className(), std::forward(arg), std::forward(args)...)} {} // named constructors avoid ambiguities static Type fromJObject(jobject object) { return Type(object); } template static Type construct(Args &&...args) { return Type(std::forward(args)...); } static Type fromLocalRef(jobject lref) { return Type(QJniObject::fromLocalRef(lref)); } static bool registerNativeMethods(std::initializer_list methods) { QJniEnvironment env; return env.registerNativeMethods(methods); } // public API forwarding to QJniObject, with the implicit Class template parameter template = true #endif > static auto callStaticMethod(const char *name, Args &&...args) { return QJniObject::callStaticMethod(name, std::forward(args)...); } template = true #endif > static auto getStaticField(const char *field) { return QJniObject::getStaticField(field); } template = true #endif > static void setStaticField(const char *field, T &&value) { QJniObject::setStaticField(field, std::forward(value)); } // keep only these overloads, the rest is made private template = true #endif > auto callMethod(const char *method, Args &&...args) const { return m_object.callMethod(method, std::forward(args)...); } template = true #endif > auto getField(const char *fieldName) const { return m_object.getField(fieldName); } template = true #endif > void setField(const char *fieldName, T &&value) { m_object.setField(fieldName, std::forward(value)); } QByteArray className() const { return QtJniTypes::Traits::className().data(); } private: friend bool comparesEqual(const JObject &lhs, const JObject &rhs) noexcept { return lhs.m_object == rhs.m_object; } Q_DECLARE_EQUALITY_COMPARABLE_LITERAL_TYPE(JObject); }; } // This cannot be included earlier as QJniArray is a QJniObject subclass, but it // must be included so that we can implement QJniObject::LocalFrame conversion. QT_END_NAMESPACE #include QT_BEGIN_NAMESPACE template template auto QJniObject::LocalFrame::convertToJni(T &&value) { using Type = q20::remove_cvref_t; if constexpr (std::is_same_v) { return newLocalRef(QJniObject::fromString(value)); } else if constexpr (QtJniTypes::IsJniArray::value) { return value.arrayObject(); } else if constexpr (QJniArrayBase::canConvert) { using QJniArrayType = decltype(QJniArrayBase::fromContainer(std::forward(value))); using ArrayType = decltype(std::declval().arrayObject()); return newLocalRef(QJniArrayBase::fromContainer(std::forward(value)).template object()); } else if constexpr (std::is_base_of_v || std::is_base_of_v) { return value.object(); } else { return std::forward(value); } } template template auto QJniObject::LocalFrame::convertFromJni(QJniObject &&object) { using Type = q20::remove_cvref_t; if constexpr (std::is_same_v) { return object.toString(); } else if constexpr (QtJniTypes::IsJniArray::value) { return T(std::move(object)); } else if constexpr (QJniArrayBase::canConvert) { // if we were to create a QJniArray from Type... using QJniArrayType = decltype(QJniArrayBase::fromContainer(std::declval())); // then that QJniArray would have elements of type using ElementType = typename QJniArrayType::Type; // construct a QJniArray from a jobject pointer of that type return QJniArray(object.template object()).toContainer(); } else if constexpr (std::is_array_v) { using ElementType = std::remove_extent_t; return QJniArray(std::move(object)); } else if constexpr (std::is_base_of_v && !std::is_same_v) { return T{std::move(object)}; } else if constexpr (std::is_base_of_v) { return T{std::move(object)}; } else { return std::move(object); } } QT_END_NAMESPACE #endif #endif // QJNIOBJECT_H