diff --git a/src/corelib/kernel/qjniobject.cpp b/src/corelib/kernel/qjniobject.cpp index 14a5f3ef7dc..6bfd4ac5968 100644 --- a/src/corelib/kernel/qjniobject.cpp +++ b/src/corelib/kernel/qjniobject.cpp @@ -1386,7 +1386,7 @@ bool QJniObject::isSameObject(const QJniObject &other) const void QJniObject::assign(jobject obj) { - if (isSameObject(obj)) + if (d && isSameObject(obj)) return; jobject jobj = static_cast(obj); diff --git a/src/corelib/kernel/qjniobject.h b/src/corelib/kernel/qjniobject.h index 4b343709236..6fe646d50fb 100644 --- a/src/corelib/kernel/qjniobject.h +++ b/src/corelib/kernel/qjniobject.h @@ -42,6 +42,8 @@ class Q_CORE_EXPORT QJniObject using Type = q20::remove_cvref_t; if constexpr (std::is_same_v) { return newLocalRef(QJniObject::fromString(value)); + } else if constexpr (std::is_base_of_v) { + return value.object(); } else { return static_cast(value); } @@ -52,8 +54,11 @@ class Q_CORE_EXPORT QJniObject using Type = q20::remove_cvref_t; if constexpr (std::is_same_v) { return object.toString(); + } else if constexpr (std::is_base_of_v + && !std::is_same_v) { + return T{std::move(object)}; } else { - return object; + return std::move(object); } } }; @@ -67,9 +72,12 @@ public: #endif > explicit QJniObject(const char *className, Args &&...args) - : QJniObject(className, QtJniTypes::constructorSignature().data(), - std::forward(args)...) - {} + : QJniObject(Qt::Uninitialized) + { + LocalFrame localFrame; + *this = QJniObject(className, QtJniTypes::constructorSignature().data(), + localFrame.convertToJni(std::forward(args))...); + } explicit QJniObject(jclass clazz); explicit QJniObject(jclass clazz, const char *signature, ...); template(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 @@ -527,6 +541,9 @@ public: return *this; } +protected: + QJniObject(Qt::Initialization) {} + private: static jclass loadClass(const QByteArray &className, JNIEnv *env); diff --git a/src/corelib/kernel/qjnitypes.h b/src/corelib/kernel/qjnitypes.h index 6007dd32975..a3346ec3f43 100644 --- a/src/corelib/kernel/qjnitypes.h +++ b/src/corelib/kernel/qjnitypes.h @@ -13,25 +13,176 @@ QT_BEGIN_NAMESPACE namespace QtJniTypes { -// A generic thin wrapper around jobject, convertible to jobject. -// We need this as a baseclass so that QJniObject can be implicitly -// constructed from the various subclasses. We can also pass instances -// of this type (or of any of the generated subclasses) as if it was -// a jobject. -struct Object +// A generic base class for specialized QJniObject types, to be used by +// subclasses via CRTP. It's implicitly convertible to and from jobject, which +// allows the QJniObject implementation to implicitly pass instances of this +// type through the variadic argument JNI APIs. +template +struct Object : QJniObject { - jobject _object; - constexpr operator jobject() const { return _object; } - operator QJniObject() const { return QJniObject(_object); } + using Class = Type; + operator jobject() const noexcept { return object(); } + + Q_IMPLICIT Object(jobject object) : QJniObject(object) {} + Q_IMPLICIT Object(const QJniObject &object) : QJniObject(object) {} + Q_IMPLICIT Object(QJniObject &&object) : QJniObject(std::move(object)) {} + + // Compiler-generated copy/move semantics based on QJniObject's shared d-pointer are fine! + Object(const Object &other) = default; + Object(Object &&other) = default; + Object &operator=(const Object &other) = default; + Object &operator=(Object &&other) = default; + + // avoid ambiguities with deleted const char * constructor + Q_IMPLICIT Object(std::nullptr_t) : QJniObject() {} + + // this intentionally includes the default constructor + template = true + > + explicit Object(Args &&...args) + : QJniObject(Qt::Initialization::Uninitialized) + { + *this = Object{QJniObject::construct(std::forward(args)...)}; + } + + // named constructors avoid ambiguities + static Object fromJObject(jobject object) { return Object(object); } + template + static Object construct(Args &&...args) + { + return Object{QJniObject::construct(std::forward(args)...)}; + } + + // 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 QJniObject::callMethod(method, std::forward(args)...); + } + template = true +#endif + > + auto getField(const char *fieldName) const + { + return QJniObject::getField(fieldName); + } + + template = true +#endif + > + void setField(const char *fieldName, T &&value) + { + QJniObject::setField(fieldName, std::forward(value)); + } + +private: + // The following declutters the API of these types compared to the QJniObject API. + // 1) 'Object' methods; the functions we have have return type auto and will return + // the type specified by the template parameter. + using QJniObject::callObjectMethod; + using QJniObject::callStaticObjectMethod; + using QJniObject::getObjectField; + using QJniObject::getStaticObjectField; + + // 2) Constructors that take a class name, signature string, or class template argument + explicit Object(const char *className) = delete; + explicit Object(const char *className, const char *signature, ...) = delete; + template + explicit Object(const char *className, Args &&...args) = delete; + explicit Object(jclass clazz, const char *signature, ...) = delete; + template + static QJniObject construct(Args &&...args) = delete; + + // 3) Overloads that take a class name/jclass, methodID, signature string, or an + // explicit class template argument + template + auto callMethod(const char *methodName, const char *signature, Args &&...args) const = delete; + template + + static auto callStaticMethod(const char *className, const char *methodName, + const char *signature, Args &&...args) = delete; + template + static auto callStaticMethod(jclass clazz, const char *methodName, + const char *signature, Args &&...args) = delete; + template + static auto callStaticMethod(jclass clazz, jmethodID methodId, Args &&...args) = delete; + template + static auto callStaticMethod(const char *className, const char *methodName, + Args &&...args) = delete; + template + static auto callStaticMethod(jclass clazz, const char *methodName, Args &&...args) = delete; + template + static auto callStaticMethod(const char *methodName, Args &&...args) = delete; + + template + static auto getStaticField(const char *className, const char *fieldName) = delete; + template + static auto getStaticField(jclass clazz, const char *fieldName) = delete; + template + static auto getStaticField(const char *fieldName) = delete; + + template + void setField(const char *fieldName, const char *signature, T value) = delete; + template + static void setStaticField(const char *className, const char *fieldName, T value) = delete; + template + static void setStaticField(const char *className, const char *fieldName, + const char *signature, T value) = delete; + template + static void setStaticField(jclass clazz, const char *fieldName, + const char *signature, T value) = delete; + template + static void setStaticField(jclass clazz, const char *fieldName, T value) = delete; + template + static void setStaticField(const char *fieldName, T value) = delete; }; } // namespace QtJniTypes #define Q_DECLARE_JNI_TYPE_HELPER(Type) \ namespace QtJniTypes { \ -struct Type : Object \ +struct Type : Object \ { \ - constexpr Type(jobject o) noexcept : Object{o} {} \ + using Object::Object; \ }; \ } \ diff --git a/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp b/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp index 36f6f5923e5..49a814f1222 100644 --- a/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp +++ b/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp @@ -12,6 +12,7 @@ using namespace Qt::StringLiterals; static const char testClassName[] = "org/qtproject/qt/android/testdatapackage/QtJniObjectTestClass"; Q_DECLARE_JNI_CLASS(QtJniObjectTestClass, testClassName) +using TestClass = QtJniTypes::QtJniObjectTestClass; static const jbyte A_BYTE_VALUE = 127; static const jshort A_SHORT_VALUE = 32767; @@ -152,6 +153,16 @@ void tst_QJniObject::ctor() QVERIFY(object.isValid()); } + { + // from Qt 6.7 on we can construct declared classes through the helper type + QJniObject object = TestClass::construct(); + QVERIFY(object.isValid()); + + // or even directly + TestClass testObject; + QVERIFY(testObject.isValid()); + } + { QJniObject string = QJniObject::fromString(QLatin1String("Hello, Java")); QJniObject object("java/lang/String", "(Ljava/lang/String;)V", string.object()); @@ -311,10 +322,15 @@ void tst_QJniObject::className() } { - QJniObject strObject = QJniObject("java/lang/String", jString.object()); + QJniObject strObject = QJniObject("java/lang/String", str); QCOMPARE(strObject.className(), "java/lang/String"); QCOMPARE(strObject.toString(), str); } + + { + TestClass test; + QCOMPARE(test.className(), testClassName); + } } void tst_QJniObject::callStaticObjectMethodClassName() @@ -1119,8 +1135,8 @@ void setStaticField(const char *fieldName, T testValue) // use template overload to reset to default T defaultValue = {}; - QJniObject::setStaticField(fieldName, defaultValue); - res = QJniObject::getStaticField(fieldName); + TestClass::setStaticField(fieldName, defaultValue); + res = TestClass::getStaticField(fieldName); QCOMPARE(res, defaultValue); } @@ -1185,8 +1201,8 @@ void tst_QJniObject::setStaticObjectField() // as of Qt 6.7, we can set and get strings directly using namespace QtJniTypes; - QJniObject::setStaticField("S_STRING_OBJECT_VAR", qString); - QCOMPARE((QJniObject::getStaticField("S_STRING_OBJECT_VAR")), qString); + QtJniObjectTestClass::setStaticField("S_STRING_OBJECT_VAR", qString); + QCOMPARE(QtJniObjectTestClass::getStaticField("S_STRING_OBJECT_VAR"), qString); } void tst_QJniObject::templateApiCheck() @@ -1473,11 +1489,17 @@ void tst_QJniObject::templateApiCheck() QJniObject res = QJniObject::callStaticObjectMethod(testClassName, "staticObjectArrayMethod"); QVERIFY(res.isValid()); + + const auto array = TestClass::callStaticMethod("staticObjectArrayMethod"); + QVERIFY(array.isValid()); } { QJniObject res = testClass.callObjectMethod("objectArrayMethod"); QVERIFY(res.isValid()); + + const auto array = testClass.callMethod("objectArrayMethod"); + QVERIFY(array.isValid()); } // jbooleanArray ------------------------------------------------------------------------------ @@ -1485,11 +1507,17 @@ void tst_QJniObject::templateApiCheck() QJniObject res = QJniObject::callStaticObjectMethod(testClassName, "staticBooleanArrayMethod"); QVERIFY(res.isValid()); + + const auto array = TestClass::callStaticMethod("staticBooleanArrayMethod"); + QVERIFY(array.isValid()); } { QJniObject res = testClass.callObjectMethod("booleanArrayMethod"); QVERIFY(res.isValid()); + + const auto array = testClass.callMethod("booleanArrayMethod"); + QVERIFY(array.isValid()); } // jbyteArray --------------------------------------------------------------------------------- @@ -1497,11 +1525,17 @@ void tst_QJniObject::templateApiCheck() QJniObject res = QJniObject::callStaticObjectMethod(testClassName, "staticByteArrayMethod"); QVERIFY(res.isValid()); + + const auto array = TestClass::callStaticMethod("staticByteArrayMethod"); + QVERIFY(array.isValid()); } { QJniObject res = testClass.callObjectMethod("byteArrayMethod"); QVERIFY(res.isValid()); + + const auto array = testClass.callMethod("byteArrayMethod"); + QVERIFY(array.isValid()); } // jcharArray --------------------------------------------------------------------------------- @@ -1509,11 +1543,17 @@ void tst_QJniObject::templateApiCheck() QJniObject res = QJniObject::callStaticObjectMethod(testClassName, "staticCharArrayMethod"); QVERIFY(res.isValid()); + + const auto array = TestClass::callStaticMethod("staticCharArrayMethod"); + QVERIFY(array.isValid()); } { QJniObject res = testClass.callObjectMethod("charArrayMethod"); QVERIFY(res.isValid()); + + const auto array = testClass.callMethod("charArrayMethod"); + QVERIFY(array.isValid()); } // jshortArray -------------------------------------------------------------------------------- @@ -1521,11 +1561,17 @@ void tst_QJniObject::templateApiCheck() QJniObject res = QJniObject::callStaticObjectMethod(testClassName, "staticShortArrayMethod"); QVERIFY(res.isValid()); + + const auto array = TestClass::callStaticMethod("staticShortArrayMethod"); + QVERIFY(array.isValid()); } { QJniObject res = testClass.callObjectMethod("shortArrayMethod"); QVERIFY(res.isValid()); + + const auto array = testClass.callMethod("shortArrayMethod"); + QVERIFY(array.isValid()); } // jintArray ---------------------------------------------------------------------------------- @@ -1533,11 +1579,17 @@ void tst_QJniObject::templateApiCheck() QJniObject res = QJniObject::callStaticObjectMethod(testClassName, "staticIntArrayMethod"); QVERIFY(res.isValid()); + + const auto array = TestClass::callStaticMethod("staticIntArrayMethod"); + QVERIFY(array.isValid()); } { QJniObject res = testClass.callObjectMethod("intArrayMethod"); QVERIFY(res.isValid()); + + const auto array = testClass.callMethod("intArrayMethod"); + QVERIFY(array.isValid()); } // jlongArray --------------------------------------------------------------------------------- @@ -1545,11 +1597,17 @@ void tst_QJniObject::templateApiCheck() QJniObject res = QJniObject::callStaticObjectMethod(testClassName, "staticLongArrayMethod"); QVERIFY(res.isValid()); + + const auto array = TestClass::callStaticMethod("staticLongArrayMethod"); + QVERIFY(array.isValid()); } { QJniObject res = testClass.callObjectMethod("longArrayMethod"); QVERIFY(res.isValid()); + + const auto array = testClass.callMethod("longArrayMethod"); + QVERIFY(array.isValid()); } // jfloatArray -------------------------------------------------------------------------------- @@ -1557,11 +1615,17 @@ void tst_QJniObject::templateApiCheck() QJniObject res = QJniObject::callStaticObjectMethod(testClassName, "staticFloatArrayMethod"); QVERIFY(res.isValid()); + + const auto array = TestClass::callStaticMethod("staticFloatArrayMethod"); + QVERIFY(array.isValid()); } { QJniObject res = testClass.callObjectMethod("floatArrayMethod"); QVERIFY(res.isValid()); + + const auto array = testClass.callMethod("floatArrayMethod"); + QVERIFY(array.isValid()); } // jdoubleArray ------------------------------------------------------------------------------- @@ -1569,11 +1633,17 @@ void tst_QJniObject::templateApiCheck() QJniObject res = QJniObject::callStaticObjectMethod(testClassName, "staticDoubleArrayMethod"); QVERIFY(res.isValid()); + + const auto array = TestClass::callStaticMethod("staticDoubleArrayMethod"); + QVERIFY(array.isValid()); } { QJniObject res = testClass.callObjectMethod("doubleArrayMethod"); QVERIFY(res.isValid()); + + const auto array = testClass.callMethod("doubleArrayMethod"); + QVERIFY(array.isValid()); } } diff --git a/tests/auto/corelib/kernel/qjnitypes/tst_qjnitypes.cpp b/tests/auto/corelib/kernel/qjnitypes/tst_qjnitypes.cpp index 856368355c7..0ce2295092d 100644 --- a/tests/auto/corelib/kernel/qjnitypes/tst_qjnitypes.cpp +++ b/tests/auto/corelib/kernel/qjnitypes/tst_qjnitypes.cpp @@ -5,6 +5,8 @@ #include +using namespace Qt::StringLiterals; + class tst_QJniTypes : public QObject { Q_OBJECT @@ -15,6 +17,7 @@ public: private slots: void initTestCase(); void nativeMethod(); + void construct(); }; struct QtJavaWrapper {}; @@ -159,6 +162,35 @@ void tst_QJniTypes::nativeMethod() QCOMPARE(method.signature, "(ILjava/lang/String;J)Z"); } +void tst_QJniTypes::construct() +{ + using namespace QtJniTypes; + + const QString text = u"Java String"_s; + String str(text); + QVERIFY(str.isValid()); + QCOMPARE(str.toString(), text); + + jobject jref = nullptr; // must be jobject, not jstring + { + // if jref would be a jstring, then this would call the + // Java String copy constructor! + String jstr(jref); + QVERIFY(!jstr.isValid()); + } + jref = str.object(); + { + String jstr(jref); + QVERIFY(jstr.isValid()); + QCOMPARE(jstr.toString(), text); + } + + String str2 = str; + QCOMPARE(str.toString(), text); + String str3 = std::move(str2); + QCOMPARE(str3.toString(), text); +} + QTEST_MAIN(tst_QJniTypes) #include "tst_qjnitypes.moc"