diff options
Diffstat (limited to 'tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp')
-rw-r--r-- | tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp | 674 |
1 files changed, 646 insertions, 28 deletions
diff --git a/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp b/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp index 8faa9b6fbb..64b464e002 100644 --- a/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp +++ b/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp @@ -1,5 +1,5 @@ // Copyright (C) 2021 The Qt Company Ltd. -// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include <jni.h> @@ -8,7 +8,11 @@ #include <QtCore/QJniObject> #include <QtTest> -static const char testClassName[] = "org/qtproject/qt/android/testdatapackage/QtJniObjectTestClass"; +using namespace Qt::StringLiterals; + +static constexpr 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; @@ -32,12 +36,16 @@ public: private slots: void initTestCase(); + void init(); void ctor(); void callMethodTest(); + void callMethodThrowsException(); void callObjectMethodTest(); void stringConvertionTest(); void compareOperatorTests(); + void className(); + void callStaticMethodThrowsException(); void callStaticObjectMethodClassName(); void callStaticObjectMethod(); void callStaticObjectMethodById(); @@ -71,6 +79,7 @@ private slots: void getStaticIntField(); void getStaticByteFieldClassName(); void getStaticByteField(); + void getStaticBooleanField(); void getStaticLongFieldClassName(); void getStaticLongField(); void getStaticDoubleFieldClassName(); @@ -106,6 +115,11 @@ private slots: void templateApiCheck(); void isClassAvailable(); void fromLocalRef(); + void largeObjectArray(); + + void callback_data(); + void callback(); + void callStaticOverloadResolution(); void cleanupTestCase(); }; @@ -118,6 +132,14 @@ void tst_QJniObject::initTestCase() { } +void tst_QJniObject::init() +{ + // Unless explicitly ignored to test error handling, warning messages + // in this test about a failure to look up a field, method, or class + // make the test fail. + QTest::failOnWarning(QRegularExpression("java.lang.NoSuch.*Error")); +} + void tst_QJniObject::cleanupTestCase() { } @@ -135,6 +157,21 @@ void tst_QJniObject::ctor() } { + QJniObject object = QJniObject::construct<jstring>(); + 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<jstring>()); QVERIFY(object.isValid()); @@ -143,7 +180,7 @@ void tst_QJniObject::ctor() { QJniObject string = QJniObject::fromString(QLatin1String("Hello, Java")); - QJniObject object("java/lang/String", string.object<jstring>()); + QJniObject object = QJniObject::construct<jstring>(string.object<jstring>()); QVERIFY(object.isValid()); QCOMPARE(string.toString(), object.toString()); } @@ -179,8 +216,10 @@ void tst_QJniObject::ctor() void tst_QJniObject::callMethodTest() { { - QJniObject jString1 = QJniObject::fromString(QLatin1String("Hello, Java")); - QJniObject jString2 = QJniObject::fromString(QLatin1String("hELLO, jAVA")); + const QString qString1 = u"Hello, Java"_s; + const QString qString2 = u"hELLO, jAVA"_s; + QJniObject jString1 = QJniObject::fromString(qString1); + QJniObject jString2 = QJniObject::fromString(qString2); QVERIFY(jString1 != jString2); const jboolean isEmpty = jString1.callMethod<jboolean>("isEmpty"); @@ -193,6 +232,10 @@ void tst_QJniObject::callMethodTest() ret = jString1.callMethod<jint>("compareToIgnoreCase", jString2.object<jstring>()); QVERIFY(0 == ret); + + // as of Qt 6.7, we can pass QString directly + ret = jString1.callMethod<jint>("compareToIgnoreCase", qString2); + QVERIFY(0 == ret); } { @@ -211,9 +254,25 @@ void tst_QJniObject::callMethodTest() QJniObject subString = jString.callMethod<jstring>("substring", 0, 4); QCOMPARE(subString.toString(), qString.mid(0, 4)); + + // and as of Qt 6.7, we can return and take QString directly + QCOMPARE(jString.callMethod<QString>("substring", 0, 4), qString.mid(0, 4)); + + QCOMPARE(jString.callMethod<jstring>("substring", 0, 7) + .callMethod<jstring>("toUpperCase") + .callMethod<QString>("concat", u"C++"_s), u"HELLO, C++"_s); } } +void tst_QJniObject::callMethodThrowsException() +{ + QtJniTypes::QtJniObjectTestClass instance; + QTest::ignoreMessage(QtWarningMsg, QRegularExpression("java.lang.Exception")); + auto res = instance.callMethod<jobject>("callMethodThrowsException"); + QVERIFY(!res.isValid()); + QVERIFY(!QJniEnvironment().checkAndClearExceptions()); +} + void tst_QJniObject::callObjectMethodTest() { const QString qString = QLatin1String("Hello, Java"); @@ -261,7 +320,7 @@ void tst_QJniObject::compareOperatorTests() QJniObject stringObject2 = QJniObject::fromString(str); QVERIFY(stringObject != stringObject2); - jstring jstrobj = 0; + jstring jstrobj = nullptr; QJniObject invalidStringObject; QVERIFY(invalidStringObject == jstrobj); @@ -270,6 +329,36 @@ void tst_QJniObject::compareOperatorTests() QVERIFY(!invalidStringObject.isValid()); } +void tst_QJniObject::className() +{ + const QString str("Hello!"); + QJniObject jString = QJniObject::fromString(str); + { + QCOMPARE(jString.className(), "java/lang/String"); + QCOMPARE(jString.toString(), str); + } + + { + 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::callStaticMethodThrowsException() +{ + QTest::ignoreMessage(QtWarningMsg, QRegularExpression("java.lang.Exception")); + auto res = QtJniTypes::QtJniObjectTestClass::callStaticMethod<jobject>( + "callStaticMethodThrowsException"); + QVERIFY(!res.isValid()); + QVERIFY(!QJniEnvironment().checkAndClearExceptions()); +} + void tst_QJniObject::callStaticObjectMethodClassName() { QJniObject formatString = QJniObject::fromString(QLatin1String("test format")); @@ -304,7 +393,8 @@ void tst_QJniObject::callStaticObjectMethod() jclass cls = env->FindClass("java/lang/String"); QVERIFY(cls != 0); - QJniObject formatString = QJniObject::fromString(QLatin1String("test format")); + const QString string = u"test format"_s; + QJniObject formatString = QJniObject::fromString(string); QVERIFY(formatString.isValid()); QJniObject returnValue = QJniObject::callStaticObjectMethod(cls, @@ -313,20 +403,14 @@ void tst_QJniObject::callStaticObjectMethod() formatString.object<jstring>(), jobjectArray(0)); QVERIFY(returnValue.isValid()); - - QString returnedString = returnValue.toString(); - - QCOMPARE(returnedString, QString::fromLatin1("test format")); + QCOMPARE(returnValue.toString(), string); returnValue = QJniObject::callStaticObjectMethod<jstring>(cls, "format", formatString.object<jstring>(), jobjectArray(0)); QVERIFY(returnValue.isValid()); - - returnedString = returnValue.toString(); - - QCOMPARE(returnedString, QString::fromLatin1("test format")); + QCOMPARE(returnValue.toString(), string); // from 6.4 on we can use callStaticMethod returnValue = QJniObject::callStaticMethod<jstring>(cls, @@ -334,10 +418,20 @@ void tst_QJniObject::callStaticObjectMethod() formatString.object<jstring>(), jobjectArray(0)); QVERIFY(returnValue.isValid()); + QCOMPARE(returnValue.toString(), string); - returnedString = returnValue.toString(); + // from 6.7 we can use callStaticMethod without specifying the class string + returnValue = QJniObject::callStaticMethod<jstring, jstring>("format", + formatString.object<jstring>(), + jobjectArray(0)); + QVERIFY(returnValue.isValid()); + QCOMPARE(returnValue.toString(), string); - QCOMPARE(returnedString, QString::fromLatin1("test format")); + // from 6.7 we can pass QString directly, both as parameters and return type + QString result = QJniObject::callStaticMethod<jstring, QString>("format", + string, + jobjectArray(0)); + QCOMPARE(result, string); } void tst_QJniObject::callStaticObjectMethodById() @@ -839,6 +933,10 @@ void tst_QJniObject::getStaticIntField() jint i = QJniObject::getStaticField<jint>(cls, "SIZE"); QCOMPARE(i, 64); + + enum class Enum { SIZE = 64 }; + Enum e = QJniObject::getStaticField<Enum>(cls, "SIZE"); + QCOMPARE(e, Enum::SIZE); } void tst_QJniObject::getStaticByteFieldClassName() @@ -855,6 +953,16 @@ void tst_QJniObject::getStaticByteField() jbyte i = QJniObject::getStaticField<jbyte>(cls, "MAX_VALUE"); QCOMPARE(i, jbyte(127)); + + enum class Enum : jbyte { MAX_VALUE = 127 }; + Enum e = QJniObject::getStaticField<Enum>(cls, "MAX_VALUE"); + QCOMPARE(e, Enum::MAX_VALUE); +} + +void tst_QJniObject::getStaticBooleanField() +{ + QCOMPARE(TestClass::getStaticField<jboolean>("S_BOOLEAN_VAR"), + TestClass::getStaticField<bool>("S_BOOLEAN_VAR")); } void tst_QJniObject::getStaticLongFieldClassName() @@ -871,6 +979,10 @@ void tst_QJniObject::getStaticLongField() jlong i = QJniObject::getStaticField<jlong>(cls, "MAX_VALUE"); QCOMPARE(i, jlong(9223372036854775807L)); + + enum class Enum : jlong { MAX_VALUE = 9223372036854775807L }; + Enum e = QJniObject::getStaticField<Enum>(cls, "MAX_VALUE"); + QCOMPARE(e, Enum::MAX_VALUE); } void tst_QJniObject::getStaticDoubleFieldClassName() @@ -923,6 +1035,9 @@ void tst_QJniObject::getStaticShortField() jshort i = QJniObject::getStaticField<jshort>(cls, "MAX_VALUE"); QCOMPARE(i, jshort(32767)); + enum class Enum : jshort { MAX_VALUE = 32767 }; + Enum e = QJniObject::getStaticField<Enum>(cls, "MAX_VALUE"); + QCOMPARE(e, Enum::MAX_VALUE); } void tst_QJniObject::getStaticCharFieldClassName() @@ -939,24 +1054,29 @@ void tst_QJniObject::getStaticCharField() jchar i = QJniObject::getStaticField<jchar>(cls, "MAX_VALUE"); QCOMPARE(i, jchar(0xffff)); + + enum class Enum : jchar { MAX_VALUE = 0xffff }; + Enum e = QJniObject::getStaticField<Enum>(cls, "MAX_VALUE"); + QCOMPARE(e, Enum::MAX_VALUE); } void tst_QJniObject::getBooleanField() { - QJniObject obj("org/qtproject/qt/android/QtActivityDelegate"); + QJniObject obj(testClassName); QVERIFY(obj.isValid()); - QVERIFY(!obj.getField<jboolean>("m_fullScreen")); + QVERIFY(obj.getField<jboolean>("BOOL_FIELD")); + QVERIFY(obj.getField<bool>("BOOL_FIELD")); } void tst_QJniObject::getIntField() { - QJniObject obj("org/qtproject/qt/android/QtActivityDelegate"); + QJniObject obj(testClassName); QVERIFY(obj.isValid()); - jint res = obj.getField<jint>("m_currentRotation"); - QCOMPARE(res, -1); + jint res = obj.getField<jint>("INT_FIELD"); + QCOMPARE(res, 123); } template <typename T> @@ -974,16 +1094,22 @@ void setField(const char *fieldName, T testValue) void tst_QJniObject::setIntField() { setField("INT_VAR", 555); + enum class Enum : jint { VALUE = 555 }; + setField("INT_VAR", Enum::VALUE); } void tst_QJniObject::setByteField() { - setField("BYTE_VAR", jbyte(555)); + setField("BYTE_VAR", jbyte(123)); + enum class Enum : jbyte { VALUE = 123 }; + setField("BYTE_VAR", Enum::VALUE); } void tst_QJniObject::setLongField() { setField("LONG_VAR", jlong(9223372036847758232L)); + enum class Enum : jlong { VALUE = 9223372036847758232L }; + setField("LONG_VAR", Enum::VALUE); } void tst_QJniObject::setDoubleField() @@ -998,17 +1124,22 @@ void tst_QJniObject::setFloatField() void tst_QJniObject::setShortField() { - setField("SHORT_VAR", jshort(123)); + setField("SHORT_VAR", jshort(555)); + enum class Enum : jshort { VALUE = 555 }; + setField("SHORT_VAR", Enum::VALUE); } void tst_QJniObject::setCharField() { setField("CHAR_VAR", jchar('A')); + enum class Enum : jchar { VALUE = 'A' }; + setField("CHAR_VAR", Enum::VALUE); } void tst_QJniObject::setBooleanField() { setField("BOOLEAN_VAR", jboolean(true)); + setField("BOOLEAN_VAR", true); } void tst_QJniObject::setObjectField() @@ -1016,11 +1147,16 @@ void tst_QJniObject::setObjectField() QJniObject obj(testClassName); QVERIFY(obj.isValid()); - QJniObject testValue = QJniObject::fromString(QStringLiteral("Hello")); + const QString qString = u"Hello"_s; + QJniObject testValue = QJniObject::fromString(qString); obj.setField("STRING_OBJECT_VAR", testValue.object<jstring>()); QJniObject res = obj.getObjectField<jstring>("STRING_OBJECT_VAR"); QCOMPARE(res.toString(), testValue.toString()); + + // as of Qt 6.7, we can set and get strings directly + obj.setField("STRING_OBJECT_VAR", qString); + QCOMPARE(obj.getField<QString>("STRING_OBJECT_VAR"), qString); } template <typename T> @@ -1030,21 +1166,33 @@ void setStaticField(const char *fieldName, T testValue) T res = QJniObject::getStaticField<T>(testClassName, fieldName); QCOMPARE(res, testValue); + + // use template overload to reset to default + T defaultValue = {}; + TestClass::setStaticField(fieldName, defaultValue); + res = TestClass::getStaticField<T>(fieldName); + QCOMPARE(res, defaultValue); } void tst_QJniObject::setStaticIntField() { setStaticField("S_INT_VAR", 555); + enum class Enum : jint { VALUE = 555 }; + setStaticField("S_INT_VAR", Enum::VALUE); } void tst_QJniObject::setStaticByteField() { - setStaticField("S_BYTE_VAR", jbyte(555)); + setStaticField("S_BYTE_VAR", jbyte(123)); + enum class Enum : jbyte { VALUE = 123 }; + setStaticField("S_BYTE_VAR", Enum::VALUE); } void tst_QJniObject::setStaticLongField() { setStaticField("S_LONG_VAR", jlong(9223372036847758232L)); + enum class Enum : jlong { VALUE = 9223372036847758232L }; + setStaticField("S_LONG_VAR", Enum::VALUE); } void tst_QJniObject::setStaticDoubleField() @@ -1059,26 +1207,37 @@ void tst_QJniObject::setStaticFloatField() void tst_QJniObject::setStaticShortField() { - setStaticField("S_SHORT_VAR", jshort(123)); + setStaticField("S_SHORT_VAR", jshort(555)); + enum class Enum : jshort { VALUE = 555 }; + setStaticField("S_SHORT_VAR", Enum::VALUE); } void tst_QJniObject::setStaticCharField() { setStaticField("S_CHAR_VAR", jchar('A')); + enum class Enum : jchar { VALUE = 'A' }; + setStaticField("S_CHAR_VAR", Enum::VALUE); } void tst_QJniObject::setStaticBooleanField() { setStaticField("S_BOOLEAN_VAR", jboolean(true)); + setStaticField("S_BOOLEAN_VAR", true); } void tst_QJniObject::setStaticObjectField() { - QJniObject testValue = QJniObject::fromString(QStringLiteral("Hello")); + const QString qString = u"Hello"_s; + QJniObject testValue = QJniObject::fromString(qString); QJniObject::setStaticField(testClassName, "S_STRING_OBJECT_VAR", testValue.object<jstring>()); QJniObject res = QJniObject::getStaticObjectField<jstring>(testClassName, "S_STRING_OBJECT_VAR"); QCOMPARE(res.toString(), testValue.toString()); + + // as of Qt 6.7, we can set and get strings directly + using namespace QtJniTypes; + QtJniObjectTestClass::setStaticField("S_STRING_OBJECT_VAR", qString); + QCOMPARE(QtJniObjectTestClass::getStaticField<QString>("S_STRING_OBJECT_VAR"), qString); } void tst_QJniObject::templateApiCheck() @@ -1365,11 +1524,42 @@ void tst_QJniObject::templateApiCheck() QJniObject res = QJniObject::callStaticObjectMethod<jobjectArray>(testClassName, "staticObjectArrayMethod"); QVERIFY(res.isValid()); + + const auto array = TestClass::callStaticMethod<jobject[]>("staticObjectArrayMethod"); + QVERIFY(array.isValid()); + QCOMPARE(array.size(), 3); + + QJniArray<jobject> newArray(QList<QJniObject>{QJniObject::fromString(u"one"_s), + QJniObject::fromString(u"two"_s), + QJniObject::fromString(u"three"_s)}); + QVERIFY(newArray.isValid()); + const auto reverse = TestClass::callStaticMethod<jobject[]>("staticReverseObjectArray", newArray); + QVERIFY(reverse.isValid()); + QCOMPARE(reverse.size(), 3); + QCOMPARE(QJniObject(reverse.at(0)).toString(), u"three"_s); + QCOMPARE(QJniObject(reverse.at(1)).toString(), u"two"_s); + QCOMPARE(QJniObject(reverse.at(2)).toString(), u"one"_s); } { QJniObject res = testClass.callObjectMethod<jobjectArray>("objectArrayMethod"); QVERIFY(res.isValid()); + + const auto array = testClass.callMethod<jobject[]>("objectArrayMethod"); + QVERIFY(array.isValid()); + QCOMPARE(array.size(), 3); + + QJniArray<jobject> newArray(QList<QJniObject>{QJniObject::fromString(u"one"_s), + QJniObject::fromString(u"two"_s), + QJniObject::fromString(u"three"_s)}); + QVERIFY(newArray.isValid()); + const auto reverse = testClass.callMethod<jobject[]>("reverseObjectArray", newArray); + QVERIFY(reverse.isValid()); + QCOMPARE(reverse.size(), 3); + // QJniArray::at returns a jobject that's a local reference; make sure we don't free it twice + QCOMPARE(QJniObject::fromLocalRef(reverse.at(0)).toString(), u"three"_s); + QCOMPARE(QJniObject::fromLocalRef(reverse.at(1)).toString(), u"two"_s); + QCOMPARE(QJniObject::fromLocalRef(reverse.at(2)).toString(), u"one"_s); } // jbooleanArray ------------------------------------------------------------------------------ @@ -1377,11 +1567,33 @@ void tst_QJniObject::templateApiCheck() QJniObject res = QJniObject::callStaticObjectMethod<jbooleanArray>(testClassName, "staticBooleanArrayMethod"); QVERIFY(res.isValid()); + + const auto array = TestClass::callStaticMethod<jboolean[]>("staticBooleanArrayMethod"); + QVERIFY(array.isValid()); + QCOMPARE(array.size(), 3); + QCOMPARE(array.toContainer(), (QList<jboolean>{true, true, true})); + + QJniArray<jboolean> newArray(QList<jboolean>{true, false, false}); + QVERIFY(newArray.isValid()); + const auto reverse = TestClass::callStaticMethod<jboolean[]>("staticReverseBooleanArray", newArray); + QVERIFY(reverse.isValid()); + QCOMPARE(reverse.toContainer(), (QList<jboolean>{false, false, true})); } { QJniObject res = testClass.callObjectMethod<jbooleanArray>("booleanArrayMethod"); QVERIFY(res.isValid()); + + const auto array = testClass.callMethod<jboolean[]>("booleanArrayMethod"); + QVERIFY(array.isValid()); + QCOMPARE(array.size(), 3); + QCOMPARE(array.toContainer(), (QList<jboolean>{true, true, true})); + + QJniArray<jboolean> newArray(QList<jboolean>{true, false, false}); + QVERIFY(newArray.isValid()); + const auto reverse = testClass.callMethod<jboolean[]>("reverseBooleanArray", newArray); + QVERIFY(reverse.isValid()); + QCOMPARE(reverse.toContainer(), (QList<jboolean>{false, false, true})); } // jbyteArray --------------------------------------------------------------------------------- @@ -1389,11 +1601,37 @@ void tst_QJniObject::templateApiCheck() QJniObject res = QJniObject::callStaticObjectMethod<jbyteArray>(testClassName, "staticByteArrayMethod"); QVERIFY(res.isValid()); + + const auto array = TestClass::callStaticMethod<jbyte[]>("staticByteArrayMethod"); + QVERIFY(array.isValid()); + QCOMPARE(array.size(), 3); + QCOMPARE(array.toContainer(), "abc"); + + QJniArray<jbyte> newArray(QByteArray{"cba"}); + QVERIFY(newArray.isValid()); + const auto reverse = TestClass::callStaticMethod<jbyte[]>("staticReverseByteArray", newArray); + QVERIFY(reverse.isValid()); + QCOMPARE(reverse.toContainer(), "abc"); + + const QByteArray reverse2 = TestClass::callStaticMethod<QByteArray>("staticReverseByteArray", + QByteArray("abc")); + QCOMPARE(reverse2, "cba"); } { QJniObject res = testClass.callObjectMethod<jbyteArray>("byteArrayMethod"); QVERIFY(res.isValid()); + + const auto array = testClass.callMethod<jbyte[]>("byteArrayMethod"); + QVERIFY(array.isValid()); + QCOMPARE(array.size(), 3); + QCOMPARE(array.toContainer(), "abc"); + + QJniArray<jbyte> newArray = QJniArrayBase::fromContainer(QByteArray{"cba"}); + QVERIFY(newArray.isValid()); + const auto reverse = testClass.callMethod<jbyte[]>("reverseByteArray", newArray); + QVERIFY(reverse.isValid()); + QCOMPARE(reverse.toContainer(), "abc"); } // jcharArray --------------------------------------------------------------------------------- @@ -1401,11 +1639,37 @@ void tst_QJniObject::templateApiCheck() QJniObject res = QJniObject::callStaticObjectMethod<jcharArray>(testClassName, "staticCharArrayMethod"); QVERIFY(res.isValid()); + + const auto array = TestClass::callStaticMethod<jchar[]>("staticCharArrayMethod"); + QVERIFY(array.isValid()); + QCOMPARE(array.size(), 3); + QCOMPARE(array.toContainer(), (QList<jchar>{u'a', u'b', u'c'})); + + QJniArray<jchar> newArray = {u'c', u'b', u'a'}; + QVERIFY(newArray.isValid()); + const auto reverse = TestClass::callStaticMethod<jchar[]>("staticReverseCharArray", newArray); + QVERIFY(reverse.isValid()); + QCOMPARE(reverse.toContainer(), (QList<jchar>{u'a', u'b', u'c'})); + + const QList<jchar> reverse2 = TestClass::callStaticMethod<QList<jchar>>("staticReverseCharArray", + (QList<jchar>{u'c', u'b', u'a'})); + QCOMPARE(reverse2, (QList<jchar>{u'a', u'b', u'c'})); } { QJniObject res = testClass.callObjectMethod<jcharArray>("charArrayMethod"); QVERIFY(res.isValid()); + + const auto array = testClass.callMethod<jchar[]>("charArrayMethod"); + QVERIFY(array.isValid()); + QCOMPARE(array.size(), 3); + QCOMPARE(array.toContainer(), (QList<jchar>{u'a', u'b', u'c'})); + + QJniArray<jchar> newArray = {u'c', u'b', u'a'}; + QVERIFY(newArray.isValid()); + const auto reverse = testClass.callMethod<jchar[]>("reverseCharArray", newArray); + QVERIFY(reverse.isValid()); + QCOMPARE(reverse.toContainer(), (QList<jchar>{u'a', u'b', u'c'})); } // jshortArray -------------------------------------------------------------------------------- @@ -1413,11 +1677,38 @@ void tst_QJniObject::templateApiCheck() QJniObject res = QJniObject::callStaticObjectMethod<jshortArray>(testClassName, "staticShortArrayMethod"); QVERIFY(res.isValid()); + + const auto array = TestClass::callStaticMethod<jshort[]>("staticShortArrayMethod"); + QVERIFY(array.isValid()); + QCOMPARE(array.size(), 3); + QCOMPARE(array.toContainer(), (QList<jshort>{3, 2, 1})); + + QJniArray<jshort> newArray = {3, 2, 1}; + QVERIFY(newArray.isValid()); + const auto reverse = TestClass::callStaticMethod<jshort[]>("staticReverseShortArray", newArray); + QVERIFY(reverse.isValid()); + QCOMPARE(reverse.toContainer(), (QList<jshort>{1, 2, 3})); + + const QList<jshort> reverse2 = TestClass::callStaticMethod<QList<jshort>>("staticReverseShortArray", + (QList<jshort>{1, 2, 3})); + QCOMPARE(reverse2, (QList<jshort>{3, 2, 1})); } { QJniObject res = testClass.callObjectMethod<jshortArray>("shortArrayMethod"); QVERIFY(res.isValid()); + + const auto array = testClass.callMethod<jshort[]>("shortArrayMethod"); + QVERIFY(array.isValid()); + QCOMPARE(array.size(), 3); + QCOMPARE(array.toContainer(), (QList<jshort>{3, 2, 1})); + + QJniArray<jshort> newArray = {3, 2, 1}; + static_assert(std::is_same_v<decltype(newArray)::Type, jshort>); + QVERIFY(newArray.isValid()); + const auto reverse = testClass.callMethod<jshort[]>("reverseShortArray", newArray); + QVERIFY(reverse.isValid()); + QCOMPARE(reverse.toContainer(), (QList<jshort>{1, 2, 3})); } // jintArray ---------------------------------------------------------------------------------- @@ -1425,11 +1716,33 @@ void tst_QJniObject::templateApiCheck() QJniObject res = QJniObject::callStaticObjectMethod<jintArray>(testClassName, "staticIntArrayMethod"); QVERIFY(res.isValid()); + + const auto array = TestClass::callStaticMethod<jint[]>("staticIntArrayMethod"); + QVERIFY(array.isValid()); + QCOMPARE(array.size(), 3); + QCOMPARE(array.toContainer(), (QList<jint>{3, 2, 1})); + + QJniArray<jint> newArray = {3, 2, 1}; + QVERIFY(newArray.isValid()); + const auto reverse = TestClass::callStaticMethod<jint[]>("staticReverseIntArray", newArray); + QVERIFY(reverse.isValid()); + QCOMPARE(reverse.toContainer(), (QList<jint>{1, 2, 3})); } { QJniObject res = testClass.callObjectMethod<jintArray>("intArrayMethod"); QVERIFY(res.isValid()); + + const auto array = testClass.callMethod<jint[]>("intArrayMethod"); + QVERIFY(array.isValid()); + QCOMPARE(array.size(), 3); + QCOMPARE(array.toContainer(), (QList<jint>{3, 2, 1})); + + QJniArray<jint> newArray = {3, 2, 1}; + QVERIFY(newArray.isValid()); + const auto reverse = testClass.callMethod<jint[]>("reverseIntArray", newArray); + QVERIFY(reverse.isValid()); + QCOMPARE(reverse.toContainer(), (QList<jint>{1, 2, 3})); } // jlongArray --------------------------------------------------------------------------------- @@ -1437,11 +1750,33 @@ void tst_QJniObject::templateApiCheck() QJniObject res = QJniObject::callStaticObjectMethod<jlongArray>(testClassName, "staticLongArrayMethod"); QVERIFY(res.isValid()); + + const auto array = TestClass::callStaticMethod<jlong[]>("staticLongArrayMethod"); + QVERIFY(array.isValid()); + QCOMPARE(array.size(), 3); + QCOMPARE(array.toContainer(), (QList<jlong>{3, 2, 1})); + + QJniArray<jlong> newArray = {3, 2, 1}; + QVERIFY(newArray.isValid()); + const auto reverse = TestClass::callStaticMethod<jlong[]>("staticReverseLongArray", newArray); + QVERIFY(reverse.isValid()); + QCOMPARE(reverse.toContainer(), (QList<jlong>{1, 2, 3})); } { QJniObject res = testClass.callObjectMethod<jlongArray>("longArrayMethod"); QVERIFY(res.isValid()); + + const auto array = testClass.callMethod<jlong[]>("longArrayMethod"); + QVERIFY(array.isValid()); + QCOMPARE(array.size(), 3); + QCOMPARE(array.toContainer(), (QList<jlong>{3, 2, 1})); + + QJniArray<jlong> newArray = {3, 2, 1}; + QVERIFY(newArray.isValid()); + const auto reverse = testClass.callMethod<jlong[]>("reverseLongArray", newArray); + QVERIFY(reverse.isValid()); + QCOMPARE(reverse.toContainer(), (QList<jlong>{1, 2, 3})); } // jfloatArray -------------------------------------------------------------------------------- @@ -1449,11 +1784,33 @@ void tst_QJniObject::templateApiCheck() QJniObject res = QJniObject::callStaticObjectMethod<jfloatArray>(testClassName, "staticFloatArrayMethod"); QVERIFY(res.isValid()); + + const auto array = TestClass::callStaticMethod<jfloat[]>("staticFloatArrayMethod"); + QVERIFY(array.isValid()); + QCOMPARE(array.size(), 3); + QCOMPARE(array.toContainer(), (QList<jfloat>{1.0f, 2.0f, 3.0f})); + + QJniArray<jfloat> newArray = {3.0f, 2.0f, 1.0f}; + QVERIFY(newArray.isValid()); + const auto reverse = TestClass::callStaticMethod<jfloat[]>("staticReverseFloatArray", newArray); + QVERIFY(reverse.isValid()); + QCOMPARE(reverse.toContainer(), (QList<jfloat>{1.0f, 2.0f, 3.0f})); } { QJniObject res = testClass.callObjectMethod<jfloatArray>("floatArrayMethod"); QVERIFY(res.isValid()); + + const auto array = testClass.callMethod<jfloat[]>("floatArrayMethod"); + QVERIFY(array.isValid()); + QCOMPARE(array.size(), 3); + QCOMPARE(array.toContainer(), (QList<jfloat>{1.0f, 2.0f, 3.0f})); + + QJniArray<jfloat> newArray = {3.0f, 2.0f, 1.0f}; + QVERIFY(newArray.isValid()); + const auto reverse = testClass.callMethod<jfloat[]>("reverseFloatArray", newArray); + QVERIFY(reverse.isValid()); + QCOMPARE(reverse.toContainer(), (QList<jfloat>{1.0f, 2.0f, 3.0f})); } // jdoubleArray ------------------------------------------------------------------------------- @@ -1461,11 +1818,33 @@ void tst_QJniObject::templateApiCheck() QJniObject res = QJniObject::callStaticObjectMethod<jdoubleArray>(testClassName, "staticDoubleArrayMethod"); QVERIFY(res.isValid()); + + const auto array = TestClass::callStaticMethod<jdouble[]>("staticDoubleArrayMethod"); + QVERIFY(array.isValid()); + QCOMPARE(array.size(), 3); + QCOMPARE(array.toContainer(), (QList<jdouble>{3.0, 2.0, 1.0})); + + QJniArray<jdouble> newArray = {3.0, 2.0, 1.0}; + QVERIFY(newArray.isValid()); + const auto reverse = TestClass::callStaticMethod<jdouble[]>("staticReverseDoubleArray", newArray); + QVERIFY(reverse.isValid()); + QCOMPARE(reverse.toContainer(), (QList<jdouble>{1.0, 2.0, 3.0})); } { QJniObject res = testClass.callObjectMethod<jdoubleArray>("doubleArrayMethod"); QVERIFY(res.isValid()); + + const auto array = testClass.callMethod<jdouble[]>("doubleArrayMethod"); + QVERIFY(array.isValid()); + QCOMPARE(array.size(), 3); + QCOMPARE(array.toContainer(), (QList<jdouble>{3.0, 2.0, 1.0})); + + QJniArray<jdouble> newArray = {3.0, 2.0, 1.0}; + QVERIFY(newArray.isValid()); + const auto reverse = testClass.callMethod<jdouble[]>("reverseDoubleArray", newArray); + QVERIFY(reverse.isValid()); + QCOMPARE(reverse.toContainer(), (QList<jdouble>{1.0, 2.0, 3.0})); } } @@ -1473,6 +1852,7 @@ void tst_QJniObject::templateApiCheck() void tst_QJniObject::isClassAvailable() { QVERIFY(QJniObject::isClassAvailable("java/lang/String")); + QTest::ignoreMessage(QtWarningMsg, QRegularExpression("java.lang.ClassNotFoundException")); QVERIFY(!QJniObject::isClassAvailable("class/not/Available")); QVERIFY(QJniObject::isClassAvailable("org/qtproject/qt/android/QtActivityDelegate")); } @@ -1485,6 +1865,244 @@ void tst_QJniObject::fromLocalRef() QJniObject o = QJniObject::fromLocalRef(env->FindClass("java/lang/String")); } +void tst_QJniObject::largeObjectArray() +{ + QJniArray<jobject> newArray(QList<QJniObject>{QJniObject::fromString(u"one"_s), + QJniObject::fromString(u"two"_s), + QJniObject::fromString(u"three"_s)}); + QVERIFY(newArray.isValid()); + const QJniArray<QJniObject> reverse = TestClass::callStaticMethod<jobject[]>( + "staticReverseObjectArray", newArray); + QVERIFY(reverse.isValid()); + QCOMPARE(reverse.size(), 3); + + // make sure we don't leak local references + for (int i = 0; i < 10000; ++i) { + QVERIFY(reverse.at(0).isValid()); + QVERIFY(reverse.at(1).isValid()); + QVERIFY(reverse.at(2).isValid()); + } +} + +enum class CallbackParameterType +{ + Object, + ObjectRef, + String, + Byte, + Boolean, + Int, + Double, + JniArray, + QList, + QStringList, +}; + +static std::optional<TestClass> calledWithObject; +static int callbackWithObject(JNIEnv *, jobject, TestClass that) +{ + calledWithObject.emplace(that); + return int(CallbackParameterType::Object); +} +Q_DECLARE_JNI_NATIVE_METHOD(callbackWithObject) +static int callbackWithObjectRef(JNIEnv *, jobject, const TestClass &that) +{ + calledWithObject.emplace(that); + return int(CallbackParameterType::ObjectRef); +} +Q_DECLARE_JNI_NATIVE_METHOD(callbackWithObjectRef) + +static std::optional<QString> calledWithString; +static int callbackWithString(JNIEnv *, jobject, const QString &string) +{ + calledWithString.emplace(string); + return int(CallbackParameterType::String); +} +Q_DECLARE_JNI_NATIVE_METHOD(callbackWithString) + +static std::optional<jbyte> calledWithByte; +static int callbackWithByte(JNIEnv *, jobject, jbyte value) +{ + calledWithByte.emplace(value); + return int(CallbackParameterType::Byte); +} +Q_DECLARE_JNI_NATIVE_METHOD(callbackWithByte) + +static std::optional<jbyte> calledWithBoolean; +static int callbackWithBoolean(JNIEnv *, jobject, bool value) +{ + calledWithBoolean.emplace(value); + return int(CallbackParameterType::Boolean); +} +Q_DECLARE_JNI_NATIVE_METHOD(callbackWithBoolean) + +static std::optional<int> calledWithInt; +static int callbackWithInt(JNIEnv *, jobject, int value) +{ + calledWithInt.emplace(value); + return int(CallbackParameterType::Int); +} +Q_DECLARE_JNI_NATIVE_METHOD(callbackWithInt) + +static std::optional<double> calledWithDouble; +static int callbackWithDouble(JNIEnv *, jobject, double value) +{ + calledWithDouble.emplace(value); + return int(CallbackParameterType::Double); +} +Q_DECLARE_JNI_NATIVE_METHOD(callbackWithDouble) + +static std::optional<QJniArray<jdouble>> calledWithJniArray; +static int callbackWithJniArray(JNIEnv *, jobject, const QJniArray<jdouble> &value) +{ + calledWithJniArray.emplace(value); + return int(CallbackParameterType::JniArray); +} +Q_DECLARE_JNI_NATIVE_METHOD(callbackWithJniArray) + +static std::optional<QList<double>> calledWithQList; +static int callbackWithQList(JNIEnv *, jobject, const QList<double> &value) +{ + calledWithQList.emplace(value); + return int(CallbackParameterType::QList); +} +Q_DECLARE_JNI_NATIVE_METHOD(callbackWithQList) + +static std::optional<QStringList> calledWithStringList; +static int callbackWithStringList(JNIEnv *, jobject, const QStringList &value) +{ + calledWithStringList.emplace(value); + return int(CallbackParameterType::QStringList); +} +Q_DECLARE_JNI_NATIVE_METHOD(callbackWithStringList) + +void tst_QJniObject::callback_data() +{ + QTest::addColumn<CallbackParameterType>("parameterType"); + + QTest::addRow("Object") << CallbackParameterType::Object; + QTest::addRow("ObjectRef") << CallbackParameterType::ObjectRef; + QTest::addRow("String") << CallbackParameterType::String; + QTest::addRow("Byte") << CallbackParameterType::Byte; + QTest::addRow("Boolean") << CallbackParameterType::Boolean; + QTest::addRow("Int") << CallbackParameterType::Int; + QTest::addRow("Double") << CallbackParameterType::Double; + QTest::addRow("JniArray") << CallbackParameterType::JniArray; + QTest::addRow("QList") << CallbackParameterType::QList; + QTest::addRow("QStringList") << CallbackParameterType::QStringList; +} + +void tst_QJniObject::callback() +{ + QFETCH(const CallbackParameterType, parameterType); + + TestClass testObject; + int result = -1; + + switch (parameterType) { + case CallbackParameterType::Object: + QVERIFY(TestClass::registerNativeMethods({ + Q_JNI_NATIVE_METHOD(callbackWithObject) + })); + result = testObject.callMethod<int>("callMeBackWithObject", testObject); + QVERIFY(calledWithObject); + QCOMPARE(calledWithObject.value(), testObject); + break; + case CallbackParameterType::ObjectRef: + QVERIFY(TestClass::registerNativeMethods({ + Q_JNI_NATIVE_METHOD(callbackWithObjectRef) + })); + result = testObject.callMethod<int>("callMeBackWithObjectRef", testObject); + QVERIFY(calledWithObject); + QCOMPARE(calledWithObject.value(), testObject); + break; + case CallbackParameterType::String: + QVERIFY(TestClass::registerNativeMethods({ + Q_JNI_NATIVE_METHOD(callbackWithString) + })); + result = testObject.callMethod<int>("callMeBackWithString", QString::number(123)); + QVERIFY(calledWithString); + QCOMPARE(calledWithString.value(), "123"); + break; + case CallbackParameterType::Byte: + QVERIFY(TestClass::registerNativeMethods({ + Q_JNI_NATIVE_METHOD(callbackWithByte) + })); + result = testObject.callMethod<int>("callMeBackWithByte", jbyte(123)); + QVERIFY(calledWithByte); + QCOMPARE(calledWithByte.value(), 123); + break; + case CallbackParameterType::Boolean: + QVERIFY(TestClass::registerNativeMethods({ + Q_JNI_NATIVE_METHOD(callbackWithBoolean) + })); + result = testObject.callMethod<int>("callMeBackWithBoolean", true); + QVERIFY(calledWithBoolean); + QCOMPARE(calledWithBoolean.value(), true); + break; + case CallbackParameterType::Int: + QVERIFY(TestClass::registerNativeMethods({ + Q_JNI_NATIVE_METHOD(callbackWithInt) + })); + result = testObject.callMethod<int>("callMeBackWithInt", 12345); + QVERIFY(calledWithInt); + QCOMPARE(calledWithInt.value(), 12345); + break; + case CallbackParameterType::Double: + QVERIFY(TestClass::registerNativeMethods({ + Q_JNI_NATIVE_METHOD(callbackWithDouble) + })); + result = testObject.callMethod<int>("callMeBackWithDouble", 1.2345); + QVERIFY(calledWithDouble); + QCOMPARE(calledWithDouble.value(), 1.2345); + break; + case CallbackParameterType::JniArray: { + QVERIFY(TestClass::registerNativeMethods({ + Q_JNI_NATIVE_METHOD(callbackWithJniArray) + })); + const QJniArray<double> doubles = { 1.2, 3.4, 5.6 }; + result = testObject.callMethod<int>("callMeBackWithJniArray", doubles); + QVERIFY(calledWithJniArray); + QCOMPARE(calledWithJniArray, doubles); + break; + } + case CallbackParameterType::QList: { + QVERIFY(TestClass::registerNativeMethods({ + Q_JNI_NATIVE_METHOD(callbackWithQList) + })); + const QList<double> doubles = { 1.2, 3.4, 5.6 }; + result = testObject.callMethod<int>("callMeBackWithQList", doubles); + QVERIFY(calledWithQList); + QCOMPARE(calledWithQList.value(), doubles); + break; + } + case CallbackParameterType::QStringList: { + QVERIFY(TestClass::registerNativeMethods({ + Q_JNI_NATIVE_METHOD(callbackWithStringList) + })); + const QStringList strings = { "one", "two" }; + result = testObject.callMethod<int>("callMeBackWithStringList", strings); + QVERIFY(calledWithStringList); + QCOMPARE(calledWithStringList.value(), strings); + break; + } + } + QCOMPARE(result, int(parameterType)); +} + +// Make sure the new callStaticMethod overload taking a class, return type, +// and argument as template parameters, doesn't break overload resolution +// and that the class name doesn't get interpreted as the function name. +void tst_QJniObject::callStaticOverloadResolution() +{ + const QString value = u"Hello World"_s; + QJniObject str = QJniObject::fromString(value); + const auto result = QJniObject::callStaticMethod<jstring, jstring>( + QtJniTypes::Traits<TestClass>::className(), + "staticEchoMethod", str.object<jstring>()).toString(); + QCOMPARE(result, value); +} + QTEST_MAIN(tst_QJniObject) #include "tst_qjniobject.moc" |