Revision control

++ // Copyright (C) 2021 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
++ // Qt-Security score:significant reason:default
++
++ #include "qjniobject.h"
++
++ #include "qjnihelpers_p.h"
++
++ #include <QtCore/qbytearray.h>
++ #include <QtCore/qhash.h>
++ #include <QtCore/qreadwritelock.h>
++ #include <QtCore/qloggingcategory.h>
++
++
++ QT_BEGIN_NAMESPACE
++
++ Q_LOGGING_CATEGORY(lcJniThreadCheck, "qt.core.jni.threadcheck")
++
++ using namespace Qt::StringLiterals;
++
++ /*!
++ \class QJniObject
++ \inmodule QtCore
++ \since 6.1
++ \brief A convenience wrapper around the Java Native Interface (JNI).
++
++ The QJniObject class wraps a reference to a Java object, ensuring it isn't
++ garbage-collected and providing access to most \c JNIEnv method calls
++ (member, static) and fields (setter, getter). It eliminates much
++ boiler-plate that would normally be needed, with direct JNI access, for
++ every operation. Exceptions thrown by called Java methods are cleared by
++ default, but can since Qt 6.11 also be handled by the caller.
++
++ \note This API has been designed and tested for use with Android.
++ It has not been tested for other platforms.
++
++ \sa QJniEnvironment
++
++ \section1 Method Signatures
++
++ QJniObject provides convenience functions that will use the correct signature based on the
++ provided or deduced template arguments.
++
++ \code
++ jint x = QJniObject::callMethod<jint>("getSize");
++ QJniObject::callMethod<void>("touch");
++ jint ret = jString1.callMethod<jint>("compareToIgnoreCase", jString2.object<jstring>());
++ \endcode
++
++ These functions are variadic templates, and the compiler will deduce the
++ signature from the actual argument types. Only the return type needs to be
++ provided explicitly. QJniObject can deduce the signature string for
++ functions that take \l {JNI types}, and for types that have been declared
++ with the QtJniTypes type mapping.
++
++ \code
++ // Java class
++ package org.qtproject.qt;
++ class TestClass
++ {
++ static TestClass create() { ... }
++ static String fromNumber(int x) { ... }
++ static String[] stringArray(String s1, String s2) { ... }
++ }
++ \endcode
++
++ \code
++ // C++ code
++ Q_DECLARE_JNI_CLASS(TestClass, "org/qtproject/qt/TestClass")
++
++ // ...
++ using namespace QtJniTypes;
++ TestClass testClass = TestClass::callStaticMethod<TestClass>("create");
++ \endcode
++
++ This allows working with arbitrary Java and Android types in C++ code, without having to
++ create JNI signature strings explicitly.
++
++ \section2 Explicit JNI Signatures
++
++ It is possible to supply the signature yourself. In that case, it is important
++ that the signature matches the function you want to call.
++
++ \list
++ \li Class names need to be fully-qualified, for example: \c "java/lang/String".
++ \li Method signatures are written as \c "(ArgumentsTypes)ReturnType", see \l {JNI Types}.
++ \li All object types are returned as a QJniObject.
++ \endlist
++
++ The example below demonstrates how to call different static functions:
++
++ The signature structure is \c "(ArgumentsTypes)ReturnType". Array types in the signature
++ must have the \c {[} prefix, and the fully-qualified \c Object type names must have the
++ \c L prefix and the \c ; suffix. The signature for the \c create function is
++ \c {"()Lorg/qtproject/qt/TestClass;}. The signatures for the second and third functions
++ are \c {"(I)Ljava/lang/String;"} and
++ \c {"(Ljava/lang/String;Ljava/lang/String;)[Ljava/lang/String;"}, respectively.
++
++ We can call the \c create() function like this:
++
++ \code
++ // C++ code
++ QJniObject testClass = QJniObject::callStaticObjectMethod("org/qtproject/qt/TestClass",
++ "create",
++ "()Lorg/qtproject/qt/TestClass;");
++ \endcode
++
++ For the second and third function we can rely on QJniObject's template methods to create
++ the implicit signature string, but we can also pass the signature string explicitly:
++
++ \code
++ // C++ code
++ QJniObject stringNumber = QJniObject::callStaticObjectMethod("org/qtproject/qt/TestClass",
++ "fromNumber",
++ "(I)Ljava/lang/String;", 10);
++ \endcode
++
++ For the implicit signature creation to work we need to specify the return type explicitly:
++
++ \code
++ // C++ code
++ QJniObject string1 = QJniObject::fromString("String1");
++ QJniObject string2 = QJniObject::fromString("String2");
++ QJniObject stringArray = QJniObject::callStaticObjectMethod<jobjectArray>(
++ "org/qtproject/qt/TestClass",
++ "stringArray",
++ string1.object<jstring>(),
++ string2.object<jstring>());
++ \endcode
++
++ Note that while the first template parameter specifies the return type of the Java
++ function, the method will still return a QJniObject.
++
++ \section1 Handling Java Exceptions
++
++ After calling Java functions that might throw exceptions, it is important
++ to check for, handle and clear out any exception before continuing. All
++ QJniObject functions can handle exceptions internally by reporting and
++ clearing them. This includes JNI exceptions, for instance when trying to
++ call a method that doesn't exist, or with bad parameters; and exceptions
++ are thrown by the method as a way of reporting errors or returning failure
++ information.
++
++ From Qt 6.11 on, client code can opt in to handle exceptions explicitly in
++ each call. To do so, use \c{std::expected} from C++ 23 as the return type,
++ with the value type as the expected, and \c{jthrowable} as the error type.
++ For instance, trying to read a setting value via the
++ \c{android.provider.Settings.Secure} type might throw an exception if the
++ setting does not exist.
++
++ \code
++ Q_DECLARE_JNI_CLASS(SettingsSecure, "android/provider/Settings$Secure")
++ using namespace QtJniTypes;
++
++ QString enabledInputMethods()
++ {
++ ContentResolver resolver;
++ SettingsSecure settings;
++
++ auto defaultInputMethods = settings.callMethod<std::expected<QString, jthrowable>>(
++ "getString", resolver, u"enabled_input_methods"_s
++ );
++ if (defaultInputMethods)
++ return defaultInputMethods.value();
++ QStringList stackTrace = QJniEnvironment::stackTrace(defaultInputMethods.error());
++ }
++ \endcode
++
++ You can use any other type that behaves like \c{std::expected}, so handling
++ exceptions explicitly is possible without using C++23. The only
++ requirements are that the type declares three nested types \c{value_type},
++ \c{error_type}, and \c{unexpected_type}, can be constructed from the value
++ type, and from its \c{unexpected_type} holding a \c{jthrowable}.
++
++ \note The user must handle exceptions manually when doing JNI calls using \c JNIEnv directly.
++ It is unsafe to make other JNI calls when exceptions are pending. For more information, see
++ QJniEnvironment::checkAndClearExceptions().
++
++ \section1 Java Native Methods
++
++ Java native methods makes it possible to call native code from Java, this is done by creating a
++ function declaration in Java and prefixing it with the \c native keyword.
++ Before a native function can be called from Java, you need to map the Java native function to a
++ native function in your code. Mapping functions can be done by calling
++ QJniEnvironment::registerNativeMethods().
++
++ The example below demonstrates how this could be done.
++
++ Java implementation:
++ \snippet jni/src_qjniobject.cpp Java native methods
++
++ C++ Implementation:
++ \snippet jni/src_qjniobject.cpp C++ native methods
++
++ \section1 The Lifetime of a Java Object
++
++ Most \l{Object types}{objects} received from Java will be local references
++ and will only stay valid until you return from the native method. After that,
++ the object becomes eligible for garbage collection. If your code creates
++ many local references in a loop you should delete them manually with each
++ iteration, otherwise you might run out of memory. For more information, see
++ \l {JNI Design Overview: Global and Local References}. Local references
++ created outside a native method scope must be deleted manually, since
++ the garbage collector will not free them automatically because we are using
++ \l {Java: AttachCurrentThread}{AttachCurrentThread}. For more information, see
++ \l {JNI tips: Local and global references}.
++
++ If you want to keep a Java object alive you need to either create a new global
++ reference to the object and release it when you are done, or construct a new
++ QJniObject and let it manage the lifetime of the Java object.
++
++ \sa object()
++
++ \note The QJniObject only manages its own references, if you construct a QJniObject from a
++ global or local reference that reference will not be released by the QJniObject.
++
++ \section1 JNI Types
++
++ \section2 Object Types
++ \table
++ \header
++ \li Type
++ \li Signature
++ \row
++ \li jobject
++ \li Ljava/lang/Object;
++ \row
++ \li jclass
++ \li Ljava/lang/Class;
++ \row
++ \li jstring
++ \li Ljava/lang/String;
++ \row
++ \li jthrowable
++ \li Ljava/lang/Throwable;
++ \row
++ \li jobjectArray
++ \li [Ljava/lang/Object;
++ \row
++ \li jarray
++ \li [\e<type>
++ \row
++ \li jbooleanArray
++ \li [Z
++ \row
++ \li jbyteArray
++ \li [B
++ \row
++ \li jcharArray
++ \li [C
++ \row
++ \li jshortArray
++ \li [S
++ \row
++ \li jintArray
++ \li [I
++ \row
++ \li jlongArray
++ \li [J
++ \row
++ \li jfloatArray
++ \li [F
++ \row
++ \li jdoubleArray
++ \li [D
++ \endtable
++
++ \section2 Primitive Types
++ \table
++ \header
++ \li Type
++ \li Signature
++ \row
++ \li jboolean
++ \li Z
++ \row
++ \li jbyte
++ \li B
++ \row
++ \li jchar
++ \li C
++ \row
++ \li jshort
++ \li S
++ \row
++ \li jint
++ \li I
++ \row
++ \li jlong
++ \li J
++ \row
++ \li jfloat
++ \li F
++ \row
++ \li jdouble
++ \li D
++ \endtable
++
++ \section2 Other
++ \table
++ \header
++ \li Type
++ \li Signature
++ \row
++ \li void
++ \li V
++ \row
++ \li \e{Custom type}
++ \li L\e<fully-qualified-name>;
++ \endtable
++
++ For more information about JNI, see \l {Java Native Interface Specification}.
++ */
++
++ /*!
++ \fn bool operator==(const QJniObject &o1, const QJniObject &o2)
++
++ \relates QJniObject
++
++ Returns true if both objects, \a o1 and \a o2, are referencing the same Java object, or if both
++ are NULL. In any other cases false will be returned.
++ */
++
++ /*!
++ \fn bool operator!=(const QJniObject &o1, const QJniObject &o2)
++ \relates QJniObject
++
++ Returns true if \a o1 holds a reference to a different object than \a o2.
++ */
++
++ class QJniObjectPrivate
++ {
++ public:
++ QJniObjectPrivate()
++ {
++ }
++ ~QJniObjectPrivate() {
++ JNIEnv *env = QJniEnvironment::getJniEnv();
++ if (m_jobject)
++ env->DeleteGlobalRef(m_jobject);
++ if (m_jclass && m_own_jclass)
++ env->DeleteGlobalRef(m_jclass);
++ }
++
++ template <typename ...Args>
++ void construct(const char *signature = nullptr, Args &&...args)
++ {
++ if (m_jclass) {
++ JNIEnv *env = QJniEnvironment::getJniEnv();
++ // get default constructor
++ jmethodID constructorId = QJniObject::getCachedMethodID(env, m_jclass, m_className, "<init>",
++ signature ? signature : "()V");
++ if (constructorId) {
++ jobject obj = nullptr;
++ if constexpr (sizeof...(Args) == 0)
++ obj = env->NewObject(m_jclass, constructorId);
++ else
++ obj = env->NewObjectV(m_jclass, constructorId, std::forward<Args>(args)...);
++ if (obj) {
++ m_jobject = env->NewGlobalRef(obj);
++ env->DeleteLocalRef(obj);
++ }
++ }
++ }
++ }
++
++ bool isJString(JNIEnv *env) const;
++
++ QByteArray m_className;
++ jobject m_jobject = nullptr;
++ jclass m_jclass = nullptr;
++ bool m_own_jclass = true;
++ // Whether m_className is the name Qt resolved the class with.
++ bool m_resolvedByName = false;
++ enum class IsJString : quint8 {
++ Unknown,
++ Yes,
++ No,
++ };
++ mutable IsJString m_is_jstring = IsJString::Unknown;
++ };
++
++ // The separator keeps the concatenation unambiguous.
++ template <typename ...Args>
++ static inline QByteArray cacheKey(Args &&...args)
++ {
++ return ((QByteArrayView(":") + QByteArrayView(args)) + ...);
++ }
++
++ struct JClassHash : QHash<QByteArray, jclass>
++ {
++ jmethodID loadClassMethod = 0;
++ };
++ Q_GLOBAL_STATIC(JClassHash, cachedClasses)
++ Q_GLOBAL_STATIC(QReadWriteLock, cachedClassesLock)
++
++ static jclass getCachedClass(const QByteArray &className)
++ {
++ QReadLocker locker(cachedClassesLock);
++ const auto &it = cachedClasses->constFind(className);
++ return it != cachedClasses->constEnd() ? it.value() : nullptr;
++ }
++
++
++ bool QJniObjectPrivate::isJString(JNIEnv *env) const
++ {
++ Q_ASSERT(m_jobject);
++ if (m_is_jstring != IsJString::Unknown)
++ return m_is_jstring == IsJString::Yes;
++
++ static constexpr auto stringSignature = QtJniTypes::Traits<jstring>::className();
++ if (!m_className.isEmpty()) {
++ m_is_jstring = m_className == stringSignature
++ ? IsJString::Yes
++ : IsJString::No;
++ } else {
++ const jclass strClass = getCachedClass(stringSignature.data());
++ const bool isStringInstance = strClass
++ ? env->IsInstanceOf(m_jobject, strClass)
++ : false;
++ m_is_jstring = isStringInstance ? IsJString::Yes : IsJString::No;
++ }
++
++ return m_is_jstring == IsJString::Yes;
++ }
++
++ /*!
++ \internal
++
++ Get a JNI object from a jobject variant and do the necessary
++ exception clearing and delete the local reference before returning.
++ The JNI object can be null if there was an exception.
++ */
++ static QJniObject getCleanJniObject(jobject object, JNIEnv *env)
++ {
++ if (!object || env->ExceptionCheck())
++ return QJniObject();
++
++ return QJniObject::fromLocalRef(object);
++ }
++
++ namespace {
++
++ jclass loadClassHelper(const QByteArray &className, JNIEnv *env)
++ {
++ Q_ASSERT(env);
++ QByteArray classNameArray(className);
++ #ifdef QT_DEBUG
++ if (classNameArray.contains('.')) {
++ qWarning("QtAndroidPrivate::findClass: className '%s' should use slash separators!",
++ className.constData());
++ }
++ #endif
++ classNameArray.replace('.', '/');
++ jclass clazz = getCachedClass(classNameArray);
++ if (clazz)
++ return clazz;
++
++ QWriteLocker locker(cachedClassesLock);
++ // Check again; another thread might have added the class to the cache after
++ // our call to getCachedClass and before we acquired the lock.
++ const auto &it = cachedClasses->constFind(classNameArray);
++ if (it != cachedClasses->constEnd())
++ return it.value();
++
++ // JNIEnv::FindClass wants "a fully-qualified class name or an array type signature"
++ // which is a slash-separated class name.
++ jclass localClazz = env->FindClass(classNameArray.constData());
++ if (localClazz) {
++ clazz = static_cast<jclass>(env->NewGlobalRef(localClazz));
++ env->DeleteLocalRef(localClazz);
++ } else {
++ // Clear the exception silently; we are going to try the ClassLoader next,
++ // so no need for warning unless that fails as well.
++ env->ExceptionClear();
++ }
++
++ if (!clazz) {
++ // Wrong class loader, try our own
++ jobject classLoader = QtAndroidPrivate::classLoader();
++ if (!classLoader)
++ return nullptr;
++
++ if (!cachedClasses->loadClassMethod) {
++ jclass classLoaderClass = env->GetObjectClass(classLoader);
++ if (!classLoaderClass)
++ return nullptr;
++ cachedClasses->loadClassMethod =
++ env->GetMethodID(classLoaderClass,
++ "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
++ env->DeleteLocalRef(classLoaderClass);
++ if (!cachedClasses->loadClassMethod) {
++ qCritical("Couldn't find the 'loadClass' method in the Qt class loader");
++ return nullptr;
++ }
++ }
++
++ // ClassLoader::loadClass on the other hand wants the binary name of the class,
++ // e.g. dot-separated. In testing it works also with /, but better to stick to
++ // the specification.
++ const QString binaryClassName = QString::fromLatin1(className).replace(u'/', u'.');
++ jstring classNameObject = env->NewString(binaryClassName.utf16(), binaryClassName.length());
++ jobject classObject = env->CallObjectMethod(classLoader,
++ cachedClasses->loadClassMethod,
++ classNameObject);
++ env->DeleteLocalRef(classNameObject);
++
++ if (classObject && !env->ExceptionCheck()) {
++ clazz = static_cast<jclass>(env->NewGlobalRef(classObject));
++ env->DeleteLocalRef(classObject);
++ }
++ // Clearing the exception is the caller's responsibility (see
++ // QtAndroidPrivate::findClass()) and QJniObject::loadClass{KeepExceptions}
++ }
++
++ if (clazz)
++ cachedClasses->insert(classNameArray, clazz);
++
++ return clazz;
++ }
++
++ } // unnamed namespace
++
++ /*!
++ \internal
++ \a className must be slash-encoded
++ */
++ jclass QtAndroidPrivate::findClass(const char *className, JNIEnv *env)
++ {
++ jclass clazz = loadClassHelper(className, env);
++ if (!clazz)
++ QJniEnvironment::checkAndClearExceptions(env);
++ return clazz;
++ }
++
++ jclass QJniObject::loadClass(const QByteArray &className, JNIEnv *env)
++ {
++ return QtAndroidPrivate::findClass(className, env);
++ }
++
++ jclass QJniObject::loadClassKeepExceptions(const QByteArray &className, JNIEnv *env)
++ {
++ return loadClassHelper(className, env);
++ }
++
++
++
++ typedef QHash<QByteArray, jmethodID> JMethodIDHash;
++ Q_GLOBAL_STATIC(JMethodIDHash, cachedMethodID)
++ Q_GLOBAL_STATIC(QReadWriteLock, cachedMethodIDLock)
++
++ jmethodID QJniObject::getMethodID(JNIEnv *env,
++ jclass clazz,
++ const char *name,
++ const char *signature,
++ bool isStatic)
++ {
++ jmethodID id = isStatic ? env->GetStaticMethodID(clazz, name, signature)
++ : env->GetMethodID(clazz, name, signature);
++
++ return id;
++ }
++
++ void QJniObject::callVoidMethodV(JNIEnv *env, jmethodID id, ...) const
++ {
++ va_list args;
++ va_start(args, id);
++ env->CallVoidMethodV(d->m_jobject, id, args);
++ va_end(args);
++ }
++
++ jmethodID QJniObject::getCachedMethodID(JNIEnv *env,
++ jclass clazz,
++ const QByteArray &className,
++ const char *name,
++ const char *signature,
++ bool isStatic)
++ {
++ if (className.isEmpty())
++ return getMethodID(env, clazz, name, signature, isStatic);
++
++ const QByteArray key = cacheKey(className, name, signature,
++ isStatic ? "static" : "instance");
++ QHash<QByteArray, jmethodID>::const_iterator it;
++
++ {
++ QReadLocker locker(cachedMethodIDLock);
++ it = cachedMethodID->constFind(key);
++ if (it != cachedMethodID->constEnd())
++ return it.value();
++ }
++
++ {
++ QWriteLocker locker(cachedMethodIDLock);
++ it = cachedMethodID->constFind(key);
++ if (it != cachedMethodID->constEnd())
++ return it.value();
++
++ jmethodID id = getMethodID(env, clazz, name, signature, isStatic);
++
++ if (id)
++ cachedMethodID->insert(key, id);
++ return id;
++ }
++ }
++
++ jmethodID QJniObject::getCachedMethodID(JNIEnv *env, const char *name,
++ const char *signature, bool isStatic) const
++ {
++ // A class Qt did not resolve by name may share its name with another
++ // loader's, so it cannot share the name-keyed cache.
++ const QByteArray className = d->m_resolvedByName ? d->m_className : QByteArray();
++ return QJniObject::getCachedMethodID(env, d->m_jclass, className, name, signature, isStatic);
++ }
++
++ typedef QHash<QByteArray, jfieldID> JFieldIDHash;
++ Q_GLOBAL_STATIC(JFieldIDHash, cachedFieldID)
++ Q_GLOBAL_STATIC(QReadWriteLock, cachedFieldIDLock)
++
++ jfieldID QJniObject::getFieldID(JNIEnv *env,
++ jclass clazz,
++ const char *name,
++ const char *signature,
++ bool isStatic)
++ {
++ jfieldID id = isStatic ? env->GetStaticFieldID(clazz, name, signature)
++ : env->GetFieldID(clazz, name, signature);
++
++ return id;
++ }
++
++ jfieldID QJniObject::getCachedFieldID(JNIEnv *env,
++ jclass clazz,
++ const QByteArray &className,
++ const char *name,
++ const char *signature,
++ bool isStatic)
++ {
++ if (className.isEmpty())
++ return getFieldID(env, clazz, name, signature, isStatic);
++
++ const QByteArray key = cacheKey(className, name, signature,
++ isStatic ? "static" : "instance");
++ QHash<QByteArray, jfieldID>::const_iterator it;
++
++ {
++ QReadLocker locker(cachedFieldIDLock);
++ it = cachedFieldID->constFind(key);
++ if (it != cachedFieldID->constEnd())
++ return it.value();
++ }
++
++ {
++ QWriteLocker locker(cachedFieldIDLock);
++ it = cachedFieldID->constFind(key);
++ if (it != cachedFieldID->constEnd())
++ return it.value();
++
++ jfieldID id = getFieldID(env, clazz, name, signature, isStatic);
++
++ if (id)
++ cachedFieldID->insert(key, id);
++ return id;
++ }
++ }
++
++ jfieldID QJniObject::getCachedFieldID(JNIEnv *env,
++ const char *name,
++ const char *signature,
++ bool isStatic) const
++ {
++ const QByteArray className = d->m_resolvedByName ? d->m_className : QByteArray();
++ return QJniObject::getCachedFieldID(env, d->m_jclass, className, name, signature, isStatic);
++ }
++
++ /*!
++ \fn QJniObject::QJniObject()
++
++ Constructs an invalid JNI object.
++
++ \sa isValid()
++ */
++ QJniObject::QJniObject()
++ : d(new QJniObjectPrivate())
++ {
++ }
++
++ /*!
++ \fn QJniObject::QJniObject(const char *className)
++
++ Constructs a new JNI object by calling the default constructor of \a className.
++
++ \code
++ QJniObject myJavaString("java/lang/String");
++ \endcode
++ */
++ QJniObject::QJniObject(const char *className)
++ : d(new QJniObjectPrivate())
++ {
++ d->m_className = className;
++ d->m_jclass = loadClass(d->m_className, jniEnv());
++ d->m_own_jclass = false;
++ d->m_resolvedByName = true;
++
++ d->construct();
++ }
++
++ /*!
++ \fn QJniObject::QJniObject(const char *className, const char *signature, ...)
++
++ Constructs a new JNI object by calling the constructor of \a className with
++ \a signature specifying the types of any subsequent arguments.
++
++ \code
++ QJniEnvironment env;
++ char* str = "Hello";
++ jstring myJStringArg = env->NewStringUTF(str);
++ QJniObject myNewJavaString("java/lang/String", "(Ljava/lang/String;)V", myJStringArg);
++ \endcode
++ */
++ QJniObject::QJniObject(const char *className, const char *signature, ...)
++ : d(new QJniObjectPrivate())
++ {
++ d->m_className = className;
++ d->m_jclass = loadClass(d->m_className, jniEnv());
++ d->m_own_jclass = false;
++ d->m_resolvedByName = true;
++
++ va_list args;
++ va_start(args, signature);
++ d->construct(signature, args);
++ va_end(args);
++ }
++
++ /*!
++ \fn template<typename ...Args> QJniObject::QJniObject(const char *className, Args &&...args)
++ \since 6.4
++
++ Constructs a new JNI object by calling the constructor of \a className with
++ the arguments \a args. This constructor is only available if all \a args are
++ known \l {JNI Types}.
++
++ \code
++ QJniEnvironment env;
++ char* str = "Hello";
++ jstring myJStringArg = env->NewStringUTF(str);
++ QJniObject myNewJavaString("java/lang/String", myJStringArg);
++ \endcode
++ */
++
++ /*!
++ Constructs a new JNI object from \a clazz by calling the constructor with
++ \a signature specifying the types of any subsequent arguments.
++
++ \code
++ QJniEnvironment env;
++ jclass myClazz = env.findClass("org/qtproject/qt/TestClass");
++ QJniObject(myClazz, "(I)V", 3);
++ \endcode
++ */
++ QJniObject::QJniObject(jclass clazz, const char *signature, ...)
++ : d(new QJniObjectPrivate())
++ {
++ if (clazz) {
++ d->m_jclass = static_cast<jclass>(jniEnv()->NewGlobalRef(clazz));
++ va_list args;
++ va_start(args, signature);
++ d->construct(signature, args);
++ va_end(args);
++ }
++ }
++
++ /*!
++ \fn template<typename ...Args> QJniObject::QJniObject(jclass clazz, Args &&...args)
++ \since 6.4
++
++ Constructs a new JNI object from \a clazz by calling the constructor with
++ the arguments \a args. This constructor is only available if all \a args are
++ known \l {JNI Types}.
++
++ \code
++ QJniEnvironment env;
++ jclass myClazz = env.findClass("org/qtproject/qt/TestClass");
++ QJniObject(myClazz, 3);
++ \endcode
++ */
++
++ /*!
++ Constructs a new JNI object by calling the default constructor of \a clazz.
++
++ \note The QJniObject will create a new reference to the class \a clazz
++ and releases it again when it is destroyed. References to the class created
++ outside the QJniObject need to be managed by the caller.
++ */
++
++ QJniObject::QJniObject(jclass clazz)
++ : QJniObject(clazz, "()V")
++ {
++ }
++
++ /*!
++ Constructs a new JNI object around the Java object \a object.
++
++ \note The QJniObject will hold a reference to the Java object \a object
++ and release it when destroyed. Any references to the Java object \a object
++ outside QJniObject needs to be managed by the caller. In most cases you
++ should never call this function with a local reference unless you intend
++ to manage the local reference yourself. See QJniObject::fromLocalRef()
++ for converting a local reference to a QJniObject.
++
++ \sa fromLocalRef()
++ */
++ QJniObject::QJniObject(jobject object)
++ : d(new QJniObjectPrivate())
++ {
++ if (!object)
++ return;
++
++ JNIEnv *env = jniEnv();
++ d->m_jobject = env->NewGlobalRef(object);
++ jclass cls = env->GetObjectClass(object);
++ d->m_jclass = static_cast<jclass>(env->NewGlobalRef(cls));
++ env->DeleteLocalRef(cls);
++ }
++
++ /*!
++ \fn template<typename Class, typename ...Args> static inline QJniObject QJniObject::construct(Args &&...args)
++ \since 6.4
++
++ Constructs an instance of the Java class that is the equivalent of \c Class and
++ returns a QJniObject containing the JNI object. The arguments in \a args are
++ passed to the Java constructor.
++
++ \code
++ QJniObject javaString = QJniObject::construct<jstring>();
++ \endcode
++
++ This function is only available if all \a args are known \l {JNI Types}.
++ */
++
++ /*!
++ \fn QJniObject::~QJniObject()
++
++ Destroys the JNI object and releases any references held by the JNI object.
++ */
++ QJniObject::~QJniObject()
++ {}
++
++ /*!
++ \fn void QJniObject::swap(QJniObject &other)
++ \since 6.8
++ \memberswap{object}
++ */
++
++
++ namespace {
++ QByteArray getClassNameHelper(JNIEnv *env, const QJniObjectPrivate *d)
++ {
++ if (env->PushLocalFrame(3) != JNI_OK) // JVM out of memory
++ return QByteArray();
++
++ jmethodID mid = env->GetMethodID(d->m_jclass, "getClass", "()Ljava/lang/Class;");
++ jobject classObject = env->CallObjectMethod(d->m_jobject, mid);
++ jclass classObjectClass = env->GetObjectClass(classObject);
++ mid = env->GetMethodID(classObjectClass, "getName", "()Ljava/lang/String;");
++ jstring stringObject = static_cast<jstring>(env->CallObjectMethod(classObject, mid));
++ const jsize length = env->GetStringUTFLength(stringObject);
++ const char* nameString = env->GetStringUTFChars(stringObject, NULL);
++ const QByteArray result = QByteArray::fromRawData(nameString, length).replace('.', '/');
++ env->ReleaseStringUTFChars(stringObject, nameString);
++ env->PopLocalFrame(nullptr);
++ return result;
++ }
++ }
++
++ /*! \internal
++
++ Returns the JNIEnv of the calling thread.
++ */
++ JNIEnv *QJniObject::jniEnv() const noexcept
++ {
++ return QJniEnvironment::getJniEnv();
++ }
++
++ /*!
++ \fn jobject QJniObject::object() const
++ \fn template <typename T> T QJniObject::object() const
++
++ Returns the object held by the QJniObject either as jobject or as type T.
++ T can be one of \l {Object Types}{JNI Object Types}.
++
++ \code
++ QJniObject string = QJniObject::fromString("Hello, JNI");
++ jstring jstring = string.object<jstring>();
++ \endcode
++
++ \note The returned object is still kept alive by this QJniObject. To keep the
++ object alive beyond the lifetime of this QJniObject, for example to record it
++ for later use, the easiest approach is to store it in another QJniObject with
++ a suitable lifetime. Alternatively, you may create a new global reference to the
++ object and store it, taking care to free it when you are done with it.
++
++ \snippet jni/src_qjniobject.cpp QJniObject scope
++ */
++ jobject QJniObject::object() const
++ {
++ return javaObject();
++ }
++
++ /*!
++ \fn jclass QJniObject::objectClass() const
++
++ Returns the class object held by the QJniObject as a \c jclass.
++
++ \note The returned object is still kept alive by this QJniObject. To keep the
++ object alive beyond the lifetime of this QJniObject, for example to record it
++ for later use, the easiest approach is to store it in another QJniObject with
++ a suitable lifetime. Alternatively, you may create a new global reference to the
++ object and store it, taking care to free it when you are done with it.
++
++ \since 6.2
++ */
++ jclass QJniObject::objectClass() const
++ {
++ return d->m_jclass;
++ }
++
++ /*!
++ \fn QByteArray QJniObject::className() const
++
++ Returns the name of the class object held by the QJniObject as a \c QByteArray.
++
++ \since 6.2
++ */
++ QByteArray QJniObject::className() const
++ {
++ if (d->m_className.isEmpty() && d->m_jclass && d->m_jobject) {
++ JNIEnv *env = jniEnv();
++ d->m_className = getClassNameHelper(env, d.get());
++ }
++ return d->m_className;
++ }
++
++ /*!
++ \fn template <typename Ret, typename ...Args> auto QJniObject::callMethod(const char *methodName, const char *signature, Args &&...args) const
++ \since 6.4
++
++ Calls the object's method \a methodName with \a signature specifying the types of any
++ subsequent arguments \a args, and returns the value (unless Ret is \c void). If Ret
++ is a jobject type, then the returned value will be a QJniObject.
++
++ \code
++ QJniObject myJavaString("org/qtproject/qt/TestClass");
++ jint index = myJavaString.callMethod<jint>("indexOf", "(I)I", 0x0051);
++ \endcode
++ */
++
++ /*!
++ \fn template <typename Ret, typename ...Args> auto QJniObject::callMethod(const char *methodName, Args &&...args) const
++ \since 6.4
++
++ Calls the method \a methodName with arguments \a args and returns the value
++ (unless Ret is \c void). If Ret is a jobject type, then the returned value
++ will be a QJniObject.
++
++ \code
++ QJniObject myJavaString("org/qtproject/qt/TestClass");
++ jint size = myJavaString.callMethod<jint>("length");
++ \endcode
++
++ The method signature is deduced at compile time from Ret and the types
++ of \a args. Ret can be a \c{std::expected}-compatible type that returns
++ a value, or \l{Handling Java Exceptions}{any Java exception thrown} by the
++ called method.
++ */
++
++ /*!
++ \fn template <typename Ret, typename ...Args> auto QJniObject::callStaticMethod(const char *className, const char *methodName, const char *signature, Args &&...args)
++ \since 6.4
++
++ Calls the static method \a methodName from class \a className with \a signature
++ specifying the types of any subsequent arguments \a args. Returns the result of
++ the method (unless Ret is \c void). If Ret is a jobject type, then the
++ returned value will be a QJniObject.
++
++ \code
++ jint a = 2;
++ jint b = 4;
++ jint max = QJniObject::callStaticMethod<jint>("java/lang/Math", "max", "(II)I", a, b);
++ \endcode
++ */
++
++ /*!
++ \fn template <typename Ret, typename ...Args> auto QJniObject::callStaticMethod(const char *className, const char *methodName, Args &&...args)
++ \since 6.4
++
++ Calls the static method \a methodName on class \a className with arguments \a args,
++ and returns the value of type Ret (unless Ret is \c void). If Ret
++ is a jobject type, then the returned value will be a QJniObject.
++
++ \code
++ jint value = QJniObject::callStaticMethod<jint>("MyClass", "staticMethod");
++ \endcode
++
++ The method signature is deduced at compile time from Ret and the types
++ of \a args. Ret can be a \c{std::expected}-compatible type that returns
++ a value, or \l{Handling Java Exceptions}{any Java exception thrown} by the
++ called method.
++ */
++
++ /*!
++ \fn template <typename Ret, typename ...Args> auto QJniObject::callStaticMethod(jclass clazz, const char *methodName, const char *signature, Args &&...args)
++
++ Calls the static method \a methodName from \a clazz with \a signature
++ specifying the types of any subsequent arguments. Returns the result of
++ the method (unless Ret is \c void). If Ret is a jobject type, then the
++ returned value will be a QJniObject.
++
++ \code
++ QJniEnvironment env;
++ jclass javaMathClass = env.findClass("java/lang/Math");
++ jint a = 2;
++ jint b = 4;
++ jint max = QJniObject::callStaticMethod<jint>(javaMathClass, "max", "(II)I", a, b);
++ \endcode
++ */
++
++ /*!
++ \fn template <typename Ret, typename ...Args> auto QJniObject::callStaticMethod(jclass clazz, jmethodID methodId, Args &&...args)
++ \since 6.4
++
++ Calls the static method identified by \a methodId from the class \a clazz
++ with any subsequent arguments, and returns the value of type Ret (unless
++ Ret is \c void). If Ret is a jobject type, then the returned value will
++ be a QJniObject.
++
++ Useful when \a clazz and \a methodId are already cached from previous operations.
++
++ \code
++ QJniEnvironment env;
++ jclass javaMathClass = env.findClass("java/lang/Math");
++ jmethodID methodId = env.findStaticMethod(javaMathClass, "max", "(II)I");
++ if (methodId != 0) {
++ jint a = 2;
++ jint b = 4;
++ jint max = QJniObject::callStaticMethod<jint>(javaMathClass, methodId, a, b);
++ }
++ \endcode
++ */
++
++ /*!
++ \fn template <typename Ret, typename ...Args> auto QJniObject::callStaticMethod(jclass clazz, const char *methodName, Args &&...args)
++ \since 6.4
++
++ Calls the static method \a methodName on \a clazz and returns the value of type Ret
++ (unless Ret is \c void). If Ret is a jobject type, then the returned value will
++ be a QJniObject.
++
++ \code
++ QJniEnvironment env;
++ jclass javaMathClass = env.findClass("java/lang/Math");
++ jdouble randNr = QJniObject::callStaticMethod<jdouble>(javaMathClass, "random");
++ \endcode
++
++ The method signature is deduced at compile time from Ret and the types
++ of \a args. Ret can be a \c{std::expected}-compatible type that returns
++ a value, or \l{Handling Java Exceptions}{any Java exception thrown} by the
++ called method.
++ */
++
++ /*!
++ \fn template <typename Klass, typename Ret, typename ...Args> auto QJniObject::callStaticMethod(const char *methodName, Args &&...args)
++ \since 6.7
++
++ Calls the static method \a methodName on the class \c Klass and returns the value of type
++ Ret (unless Ret is \c void). If Ret is a jobject type, then the returned value will
++ be a QJniObject.
++
++ The method signature is deduced at compile time from Ret and the types
++ of \a args. \c Klass needs to be a C++ type with a registered type mapping
++ to a Java type. Ret can be a \c{std::expected}-compatible type that
++ returns a value, or \l{Handling Java Exceptions}{any Java exception thrown}
++ by the called method.
++
++ */
++
++ /*!
++ \fn QJniObject QJniObject::callObjectMethod(const char *methodName, const char *signature, ...) const
++
++ Calls the Java object's method \a methodName with \a signature specifying
++ the types of any subsequent arguments.
++
++ \code
++ QJniObject myJavaString = QJniObject::fromString("Hello, Java");
++ QJniObject mySubstring = myJavaString.callObjectMethod("substring",
++ "(II)Ljava/lang/String;", 7, 11);
++ \endcode
++ */
++ QJniObject QJniObject::callObjectMethod(const char *methodName, const char *signature, ...) const
++ {
++ JNIEnv *env = jniEnv();
++ jmethodID id = getCachedMethodID(env, methodName, signature);
++ va_list args;
++ va_start(args, signature);
++ // can't go back from variadic arguments to variadic templates
++ jobject object = id ? jniEnv()->CallObjectMethodV(javaObject(), id, args) : nullptr;
++ QJniObject res = getCleanJniObject(object, env);
++ va_end(args);
++
++ QJniEnvironment::checkAndClearExceptions(env);
++ return res;
++ }
++
++ /*!
++ \fn QJniObject QJniObject::callStaticObjectMethod(const char *className, const char *methodName, const char *signature, ...)
++
++ Calls the static method \a methodName from the class \a className with \a signature
++ specifying the types of any subsequent arguments.
++
++ \code
++ QJniObject thread = QJniObject::callStaticObjectMethod("java/lang/Thread", "currentThread",
++ "()Ljava/lang/Thread;");
++ QJniObject string = QJniObject::callStaticObjectMethod("java/lang/String", "valueOf",
++ "(I)Ljava/lang/String;", 10);
++ \endcode
++ */
++ QJniObject QJniObject::callStaticObjectMethod(const char *className, const char *methodName,
++ const char *signature, ...)
++ {
++ JNIEnv *env = QJniEnvironment::getJniEnv();
++ jclass clazz = QJniObject::loadClass(className, env);
++ if (clazz) {
++ jmethodID id = QJniObject::getCachedMethodID(env, clazz,
++ className,
++ methodName, signature, true);
++ if (id) {
++ va_list args;
++ va_start(args, signature);
++ QJniObject res = getCleanJniObject(env->CallStaticObjectMethodV(clazz, id, args), env);
++ va_end(args);
++ return res;
++ }
++ }
++
++ return QJniObject();
++ }
++
++ /*!
++ \fn QJniObject QJniObject::callStaticObjectMethod(jclass clazz, const char *methodName, const char *signature, ...)
++
++ Calls the static method \a methodName from class \a clazz with \a signature
++ specifying the types of any subsequent arguments.
++ */
++ QJniObject QJniObject::callStaticObjectMethod(jclass clazz, const char *methodName,
++ const char *signature, ...)
++ {
++ if (clazz) {
++ QJniEnvironment env;
++ jmethodID id = getMethodID(env.jniEnv(), clazz, methodName, signature, true);
++ if (id) {
++ va_list args;
++ va_start(args, signature);
++ QJniObject res = getCleanJniObject(env->CallStaticObjectMethodV(clazz, id, args), env.jniEnv());
++ va_end(args);
++ return res;
++ }
++ }
++
++ return QJniObject();
++ }
++
++ /*!
++ \fn QJniObject QJniObject::callStaticObjectMethod(jclass clazz, jmethodID methodId, ...)
++
++ Calls the static method identified by \a methodId from the class \a clazz
++ with any subsequent arguments. Useful when \a clazz and \a methodId are
++ already cached from previous operations.
++
++ \code
++ QJniEnvironment env;
++ jclass clazz = env.findClass("java/lang/String");
++ jmethodID methodId = env.findStaticMethod(clazz, "valueOf", "(I)Ljava/lang/String;");
++ if (methodId != 0)
++ QJniObject str = QJniObject::callStaticObjectMethod(clazz, methodId, 10);
++ \endcode
++ */
++ QJniObject QJniObject::callStaticObjectMethod(jclass clazz, jmethodID methodId, ...)
++ {
++ if (clazz && methodId) {
++ JNIEnv *env = QJniEnvironment::getJniEnv();
++ va_list args;
++ va_start(args, methodId);
++ QJniObject res = getCleanJniObject(env->CallStaticObjectMethodV(clazz, methodId, args), env);
++ va_end(args);
++ return res;
++ }
++
++ return QJniObject();
++ }
++
++ /*!
++ \fn template<typename Ret, typename ...Args> QJniObject QJniObject::callObjectMethod(const char *methodName, Args &&...args) const
++ \since 6.4
++
++ Calls the Java objects method \a methodName with arguments \a args and returns a
++ new QJniObject for the returned Java object.
++
++ \code
++ QJniObject myJavaString = QJniObject::fromString("Hello, Java");
++ QJniObject myJavaString2 = myJavaString1.callObjectMethod<jstring>("toString");
++ \endcode
++
++ The method signature is deduced at compile time from Ret and the types
++ of \a args. Ret can be a \c{std::expected}-compatible type that returns
++ a value, or \l{Handling Java Exceptions}{any Java exception thrown} by the
++ called method.
++ */
++
++ /*!
++ \fn template<typename Ret, typename ...Args> QJniObject QJniObject::callStaticObjectMethod(const char *className, const char *methodName, Args &&...args)
++ \since 6.4
++
++ Calls the static method with \a methodName on the class \a className, passing
++ arguments \a args, and returns a new QJniObject for the returned Java object.
++
++ \code
++ QJniObject string = QJniObject::callStaticObjectMethod<jstring>("CustomClass", "getClassName");
++ \endcode
++
++ The method signature is deduced at compile time from Ret and the types
++ of \a args. Ret can be a \c{std::expected}-compatible type that returns
++ a value, or \l{Handling Java Exceptions}{any Java exception thrown} by the
++ called method.
++ */
++
++ /*!
++ \fn template<typename Ret, typename ...Args> QJniObject QJniObject::callStaticObjectMethod(jclass clazz, const char *methodName, Args &&...args)
++ \since 6.4
++
++ Calls the static method with \a methodName on \a clazz, passing arguments \a args,
++ and returns a new QJniObject for the returned Java object.
++ */
++
++ /*!
++ \fn template <typename T, std::enable_if_t<std::is_convertible_v<T, jobject>, bool> = true> QJniObject &QJniObject::operator=(T object)
++
++ Replace the current object with \a object. The old Java object will be released.
++ */
++
++ /*!
++ \fn template <typename Ret, typename Type> auto QJniObject::setStaticField(const char *className, const char *fieldName, const char *signature, Type value);
++
++ Sets the static field \a fieldName on the class \a className to \a value
++ using the setter with \a signature.
++
++ */
++
++ /*!
++ \fn template <typename Ret, typename Type> auto QJniObject::setStaticField(jclass clazz, const char *fieldName, const char *signature, Type value);
++
++ Sets the static field \a fieldName on the class \a clazz to \a value using
++ the setter with \a signature.
++ */
++
++ /*!
++ \fn template<typename T> T QJniObject::getField(const char *fieldName) const
++
++ Retrieves the value of the field \a fieldName.
++
++ \code
++ QJniObject volumeControl("org/qtproject/qt/TestClass");
++ jint fieldValue = volumeControl.getField<jint>("FIELD_NAME");
++ \endcode
++ */
++
++ /*!
++ \fn template<typename T> T QJniObject::getStaticField(const char *className, const char *fieldName)
++
++ Retrieves the value from the static field \a fieldName on the class \a className.
++ */
++
++ /*!
++ \fn template<typename T> T QJniObject::getStaticField(jclass clazz, const char *fieldName)
++
++ Retrieves the value from the static field \a fieldName on \a clazz.
++ */
++
++ /*!
++ \fn template <typename Klass, typename T> auto QJniObject::getStaticField(const char *fieldName)
++
++ Retrieves the value from the static field \a fieldName for the class \c Klass.
++
++ \c Klass needs to be a C++ type with a registered type mapping to a Java type.
++ */
++
++ /*!
++ \fn template <typename Ret, typename Type> auto QJniObject::setStaticField(const char *className, const char *fieldName, Type value)
++
++ Sets the static field \a fieldName of the class \a className to \a value.
++ */
++
++ /*!
++ \fn template <typename Ret, typename Type> auto QJniObject::setStaticField(jclass clazz, const char *fieldName, Type value)
++
++ Sets the static field \a fieldName of the class \a clazz to \a value.
++ */
++
++ /*!
++ \fn template <typename Klass, typename Ret, typename Type> auto QJniObject::setStaticField(const char *fieldName, Type value)
++
++ Sets the static field \a fieldName of the class \c Klass to \a value.
++
++ \c Klass needs to be a C++ type with a registered type mapping to a Java type.
++ */
++
++ /*!
++ \fn QJniObject QJniObject::getStaticObjectField(const char *className, const char *fieldName, const char *signature)
++
++ Retrieves a JNI object from the field \a fieldName with \a signature from
++ class \a className.
++
++ \note This function can be used without a template type.
++
++ \code
++ QJniObject jobj = QJniObject::getStaticObjectField("class/with/Fields", "FIELD_NAME",
++ "Ljava/lang/String;");
++ \endcode
++ */
++ QJniObject QJniObject::getStaticObjectField(const char *className,
++ const char *fieldName,
++ const char *signature)
++ {
++ JNIEnv *env = QJniEnvironment::getJniEnv();
++ jclass clazz = QJniObject::loadClass(className, env);
++ if (!clazz)
++ return QJniObject();
++ jfieldID id = QJniObject::getCachedFieldID(env, clazz,
++ className,
++ fieldName,
++ signature, true);
++ if (!id)
++ return QJniObject();
++
++ return getCleanJniObject(env->GetStaticObjectField(clazz, id), env);
++ }
++
++ /*!
++ \fn QJniObject QJniObject::getStaticObjectField(jclass clazz, const char *fieldName, const char *signature)
++
++ Retrieves a JNI object from the field \a fieldName with \a signature from
++ class \a clazz.
++
++ \note This function can be used without a template type.
++
++ \code
++ QJniObject jobj = QJniObject::getStaticObjectField(clazz, "FIELD_NAME", "Ljava/lang/String;");
++ \endcode
++ */
++ QJniObject QJniObject::getStaticObjectField(jclass clazz, const char *fieldName,
++ const char *signature)
++ {
++ JNIEnv *env = QJniEnvironment::getJniEnv();
++ jfieldID id = getFieldID(env, clazz, fieldName, signature, true);
++
++ const auto clearExceptions = qScopeGuard([env]{
++ QJniEnvironment::checkAndClearExceptions(env);
++ });
++
++ return getCleanJniObject(getStaticObjectFieldImpl(env, clazz, id), env);
++ }
++
++ /*!
++ \fn template <typename Ret, typename Type> void QJniObject::setField(const char *fieldName, const char *signature, Type value)
++
++ Sets the value of \a fieldName with \a signature to \a value.
++
++ \code
++ QJniObject stringArray = ...;
++ QJniObject obj = ...;
++ obj.setObjectField<jobjectArray>("KEY_VALUES", "([Ljava/lang/String;)V",
++ stringArray.object<jobjectArray>())
++ \endcode
++ */
++
++ /*!
++ \fn template<typename T> QJniObject QJniObject::getObjectField(const char *fieldName) const
++
++ Retrieves a JNI object from the field \a fieldName.
++
++ \code
++ QJniObject field = jniObject.getObjectField<jstring>("FIELD_NAME");
++ \endcode
++ */
++
++ /*!
++ \fn QJniObject QJniObject::getObjectField(const char *fieldName, const char *signature) const
++
++ Retrieves a JNI object from the field \a fieldName with \a signature.
++
++ \note This function can be used without a template type.
++
++ \code
++ QJniObject field = jniObject.getObjectField("FIELD_NAME", "Ljava/lang/String;");
++ \endcode
++ */
++ QJniObject QJniObject::getObjectField(const char *fieldName, const char *signature) const
++ {
++ JNIEnv *env = jniEnv();
++ jfieldID id = getCachedFieldID(env, fieldName, signature);
++
++ const auto clearExceptions = qScopeGuard([env]{
++ QJniEnvironment::checkAndClearExceptions(env);
++ });
++
++ return getCleanJniObject(getObjectFieldImpl(env, id), env);
++ }
++
++ /*!
++ \fn template <typename Ret, typename Type> void QJniObject::setField(const char *fieldName, Type value)
++
++ Sets the value of \a fieldName to \a value.
++
++ \code
++ QJniObject obj;
++ obj.setField<jint>("AN_INT_FIELD", 10);
++ jstring myString = ...;
++ obj.setField<jstring>("A_STRING_FIELD", myString);
++ \endcode
++ */
++
++ /*!
++ \fn template<typename T> QJniObject QJniObject::getStaticObjectField(const char *className, const char *fieldName)
++
++ Retrieves the object from the field \a fieldName on the class \a className.
++
++ \code
++ QJniObject jobj = QJniObject::getStaticObjectField<jstring>("class/with/Fields", "FIELD_NAME");
++ \endcode
++ */
++
++ /*!
++ \fn template<typename T> QJniObject QJniObject::getStaticObjectField(jclass clazz, const char *fieldName)
++
++ Retrieves the object from the field \a fieldName on \a clazz.
++
++ \code
++ QJniObject jobj = QJniObject::getStaticObjectField<jstring>(clazz, "FIELD_NAME");
++ \endcode
++ */
++
++ /*!
++ \fn QJniObject QJniObject::fromString(const QString &string)
++
++ Creates a Java string from the QString \a string and returns a QJniObject holding that string.
++
++ \code
++ QString myQString = "QString";
++ QJniObject myJavaString = QJniObject::fromString(myQString);
++ \endcode
++
++ \sa toString()
++ */
++ QJniObject QJniObject::fromString(const QString &string)
++ {
++ JNIEnv *env = QJniEnvironment::getJniEnv();
++ jstring stringRef = QtJniTypes::Detail::fromQString(string, env);
++ QJniObject stringObject = getCleanJniObject(stringRef, env);
++ stringObject.d->m_className = QtJniTypes::Traits<jstring>::className();
++ stringObject.d->m_resolvedByName = true;
++ stringObject.d->m_is_jstring = QJniObjectPrivate::IsJString::Yes;
++ return stringObject;
++ }
++
++ /*!
++ \fn QString QJniObject::toString() const
++
++ Returns a QString with a string representation of the java object.
++ Calling this function on a Java String object is a convenient way of getting the actual string
++ data.
++
++ \code
++ QJniObject string = ...; // "Hello Java"
++ QString qstring = string.toString(); // "Hello Java"
++ \endcode
++
++ \sa fromString()
++ */
++ QString QJniObject::toString() const
++ {
++ if (!isValid())
++ return QString();
++
++ JNIEnv *env = jniEnv();
++ if (d->isJString(env))
++ return QtJniTypes::Detail::toQString(object<jstring>(), env);
++
++ const QJniObject string = callMethod<jstring>("toString");
++ return QtJniTypes::Detail::toQString(string.object<jstring>(), env);
++ }
++
++ /*!
++ \fn bool QJniObject::isClassAvailable(const char *className)
++
++ Returns true if the Java class \a className is available.
++
++ \code
++ if (QJniObject::isClassAvailable("java/lang/String")) {
++ // condition statement
++ }
++ \endcode
++ */
++ bool QJniObject::isClassAvailable(const char *className)
++ {
++ JNIEnv *env = QJniEnvironment::getJniEnv();
++
++ if (!env)
++ return false;
++
++ return loadClass(className, env);
++ }
++
++ /*!
++ \fn bool QJniObject::isValid() const
++
++ Returns true if this instance holds a valid Java object.
++
++ \code
++ QJniObject qjniObject; // ==> isValid() == false
++ QJniObject qjniObject(0) // ==> isValid() == false
++ QJniObject qjniObject("could/not/find/Class") // ==> isValid() == false
++ \endcode
++ */
++ bool QJniObject::isValid() const
++ {
++ return d && d->m_jobject;
++ }
++
++ /*!
++ \fn QJniObject QJniObject::fromLocalRef(jobject localRef)
++
++ Creates a QJniObject from the local JNI reference \a localRef.
++ This function takes ownership of \a localRef and frees it before returning.
++
++ \note Only call this function with a local JNI reference. For example, most raw JNI calls,
++ through the JNI environment, return local references to a java object.
++
++ \code
++ jobject localRef = env->GetObjectArrayElement(array, index);
++ QJniObject element = QJniObject::fromLocalRef(localRef);
++ \endcode
++ */
++ QJniObject QJniObject::fromLocalRef(jobject lref)
++ {
++ QJniObject obj(lref);
++ obj.jniEnv()->DeleteLocalRef(lref);
++ return obj;
++ }
++
++ bool QJniObject::isSameObject(jobject obj) const
++ {
++ if (!d)
++ return obj == nullptr;
++ if (d->m_jobject == obj)
++ return true;
++ if (!d->m_jobject || !obj)
++ return false;
++ return jniEnv()->IsSameObject(d->m_jobject, obj);
++ }
++
++ bool QJniObject::isSameObject(const QJniObject &other) const
++ {
++ if (!other.isValid())
++ return !isValid();
++ return isSameObject(other.d->m_jobject);
++ }
++
++ void QJniObject::assign(jobject obj)
++ {
++ if (d && isSameObject(obj))
++ return;
++
++ d = QSharedPointer<QJniObjectPrivate>::create();
++ if (obj) {
++ JNIEnv *env = jniEnv();
++ d->m_jobject = env->NewGlobalRef(obj);
++ jclass objectClass = env->GetObjectClass(obj);
++ d->m_jclass = static_cast<jclass>(env->NewGlobalRef(objectClass));
++ env->DeleteLocalRef(objectClass);
++ }
++ }
++
++ jobject QJniObject::javaObject() const
++ {
++ return d->m_jobject;
++ }
++
++ /*!
++ \class QtJniTypes::JObjectBase
++ \brief The JObjectBase in the QtJniTypes namespace is the base of all declared Java types.
++ \inmodule QtCore
++ \internal
++ */
++
++ /*!
++ \class QtJniTypes::JObject
++ \inmodule QtCore
++ \brief The JObject template in the QtJniTypes namespace is the base of declared Java types.
++ \since Qt 6.8
++
++ JObject\<Type\> is a template class where \a Type specifies the Java type
++ being represented.
++
++ This template gets specialized when using the Q_DECLARE_JNI_CLASS macro. The
++ specialization produces a unique type in the QtJniTypes namespace. This
++ allows the type system to deduce the correct signature in JNI calls when an
++ instance of the specialized type is passed as a parameter.
++
++ Instances can be implicitly converted to and from QJniObject and jobject,
++ and provide the same template API as QJniObject to call methods and access
++ properties. Since instances of JObject know about the Java type they hold,
++ APIs to access static methods or fields do not require the class name as an
++ explicit parameter.
++
++ \sa Q_DECLARE_JNI_CLASS
++ */
++
++ /*!
++ \fn template <typename Type> QtJniTypes::JObject<Type>::JObject()
++
++ Default-constructs the JObject instance. This also default-constructs an
++ instance of the represented Java type.
++ */
++
++ /*!
++ \fn template <typename Type> QtJniTypes::JObject<Type>::JObject(const QJniObject &other)
++
++ Constructs a JObject instance that holds a reference to the same jobject as \a other.
++ */
++
++ /*!
++ \fn template <typename Type> QtJniTypes::JObject<Type>::JObject(jobject other)
++
++ Constructs a JObject instance that holds a reference to \a other.
++ */
++
++ /*!
++ \fn template <typename Type> QtJniTypes::JObject<Type>::JObject(QJniObject &&other)
++
++ Move-constructs a JObject instance from \a other.
++ */
++
++ /*!
++ \fn template <typename Type> bool QtJniTypes::JObject<Type>::isValid() const
++
++ Returns whether the JObject instance holds a valid reference to a jobject.
++
++ \sa QJniObject::isValid()
++ */
++
++ /*!
++ \fn template <typename Type> jclass QtJniTypes::JObject<Type>::objectClass() const
++
++ Returns the Java class that this JObject is an instance of as a jclass.
++
++ \sa className(), QJniObject::objectClass()
++ */
++
++ /*!
++ \fn template <typename Type> QString QtJniTypes::JObject<Type>::toString() const
++
++ Returns a QString with a string representation of the Java object.
++
++ \sa QJniObject::toString()
++ */
++
++ /*!
++ \fn template <typename Type> QByteArray QtJniTypes::JObject<Type>::className() const
++
++ Returns the name of the Java class that this object is an instance of.
++
++ \sa objectClass(), QJniObject::className()
++ */
++
++ /*!
++ \fn template <typename Type> bool QtJniTypes::JObject<Type>::isClassAvailable()
++
++ Returns whether the class that this JObject specialization represents is
++ available.
++
++ \sa QJniObject::isClassAvailable()
++ */
++
++ /*!
++ \fn template <typename Type> JObject QtJniTypes::JObject<Type>::fromJObject(jobject object)
++
++ Constructs a JObject instance from \a object and returns that instance.
++ */
++
++ /*!
++ \fn template <typename Type> template <typename ...Args> JObject QtJniTypes::JObject<Type>::construct(Args &&...args)
++
++ Constructs a Java object from \a args and returns a JObject instance that
++ holds a reference to that Java object.
++ */
++
++ /*!
++ \fn template <typename Type> JObject QtJniTypes::JObject<Type>::fromLocalRef(jobject ref)
++
++ Constructs a JObject that holds a local reference to \a ref, and returns
++ that object.
++ */
++
++ /*!
++ \fn template <typename Type> template <typename Ret, typename ...Args> auto QtJniTypes::JObject<Type>::callStaticMethod(const char *methodName, Args &&...args)
++
++ Calls the static method \a methodName with arguments \a args, and returns
++ the result of type Ret (unless Ret is \c void). If Ret is a
++ jobject type, then the returned value will be a QJniObject.
++
++ \sa QJniObject::callStaticMethod()
++ */
++
++ /*!
++ \fn template <typename Type> bool QtJniTypes::JObject<Type>::registerNativeMethods(std::initializer_list<JNINativeMethod> methods)
++
++ Registers the Java methods in \a methods with the Java class represented by
++ the JObject specialization, and returns whether the registration was successful.
++
++ \sa QJniEnvironment::registerNativeMethods()
++ */
++
++ /*!
++ \fn template <typename Type> template <typename T> auto QtJniTypes::JObject<Type>::getStaticField(const char *field)
++
++ Returns the value of the static field \a field.
++
++ \sa QJniObject::getStaticField()
++ */
++
++ /*!
++ \fn template <typename Type> template <typename Ret, typename T> auto QtJniTypes::JObject<Type>::setStaticField(const char *field, T &&value)
++
++ Sets the static field \a field to \a value.
++
++ \sa QJniObject::setStaticField()
++ */
++
++ /*!
++ \fn template <typename Type> template <typename Ret, typename ...Args> auto QtJniTypes::JObject<Type>::callMethod(const char *method, Args &&...args) const
++
++ Calls the instance method \a method with arguments \a args, and returns
++ the result of type Ret (unless Ret is \c void). If Ret is a
++ jobject type, then the returned value will be a QJniObject.
++
++ \sa QJniObject::callMethod()
++ */
++
++ /*!
++ \fn template <typename Type> template <typename T> auto QtJniTypes::JObject<Type>::getField(const char *field) const
++
++ Returns the value of the instance field \a field.
++
++ \sa QJniObject::getField()
++ */
++
++ /*!
++ \fn template <typename Type> template <typename Ret, typename T> auto QtJniTypes::JObject<Type>::setField(const char *field, T &&value)
++
++ Sets the value of the instance field \a field to \a value.
++
++ \sa QJniObject::setField()
++ */
++
++
++ QT_END_NAMESPACE