Revision control
Copy as Markdown
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <jni.h>
#include <QString>
#include <QtCore/QJniEnvironment>
#include <QtCore/QJniObject>
#include <QTest>
#if defined(__cpp_lib_expected)
# include <expected>
template <typename T>
using QJniReturnValue = std::expected<T, jthrowable>;
using BadAccessException = std::bad_expected_access<jthrowable>;
// even with __cpp_lib_expected >= 202211L, monadic functions seem to be rather
// broken or not reliably available
#define EXPECTED_HAS_MONADIC (__cpp_lib_expected >= 202211L)
static_assert(QtJniTypes::Traits<QJniReturnValue<int>>::signature() ==
QtJniTypes::Traits<int>::signature());
static_assert(QtJniTypes::Traits<QJniReturnValue<QString>>::signature() ==
QtJniTypes::Traits<QString>::signature());
#endif
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
static constexpr const char testClassName[] = "org/qtproject/qt/android/testdatapackage/QtJniObjectTestClass";
Q_DECLARE_JNI_CLASS(QtJniObjectTestClass, testClassName)
Q_DECLARE_JNI_CLASS(NoSuchClass, "no/such/Class")
using TestClass = QtJniTypes::QtJniObjectTestClass;
static const jbyte A_BYTE_VALUE = 127;
static const jshort A_SHORT_VALUE = 32767;
static const jint A_INT_VALUE = 060701;
static const jlong A_LONG_VALUE = 060701;
static const jfloat A_FLOAT_VALUE = 1.0;
static const jdouble A_DOUBLE_VALUE = 1.0;
static const jchar A_CHAR_VALUE = 'Q';
static QString A_STRING_OBJECT()
{
return QStringLiteral("TEST_DATA_STRING");
}
class tst_QJniObject : public QObject
{
Q_OBJECT
public:
tst_QJniObject();
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();
void callStaticBooleanMethodClassName();
void callStaticBooleanMethod();
void callStaticBooleanMethodById();
void callStaticCharMethodClassName();
void callStaticCharMethod();
void callStaticCharMethodById();
void callStaticIntMethodClassName();
void callStaticIntMethod();
void callStaticIntMethodById();
void callStaticByteMethodClassName();
void callStaticByteMethod();
void callStaticByteMethodById();
void callStaticDoubleMethodClassName();
void callStaticDoubleMethod();
void callStaticDoubleMethodById();
void callStaticFloatMethodClassName();
void callStaticFloatMethod();
void callStaticFloatMethodById();
void callStaticLongMethodClassName();
void callStaticLongMethod();
void callStaticLongMethodById();
void callStaticShortMethodClassName();
void callStaticShortMethod();
void callStaticShortMethodById();
void getStaticObjectFieldClassName();
void getStaticObjectField();
void getStaticIntFieldClassName();
void getStaticIntField();
void getStaticByteFieldClassName();
void getStaticByteField();
void getStaticBooleanField();
void getStaticLongFieldClassName();
void getStaticLongField();
void getStaticDoubleFieldClassName();
void getStaticDoubleField();
void getStaticFloatFieldClassName();
void getStaticFloatField();
void getStaticShortFieldClassName();
void getStaticShortField();
void getStaticCharFieldClassName();
void getStaticCharField();
void getBooleanField();
void getIntField();
void setIntField();
void setByteField();
void setLongField();
void setDoubleField();
void setFloatField();
void setShortField();
void setCharField();
void setBooleanField();
void setObjectField();
void setStaticIntField();
void setStaticByteField();
void setStaticLongField();
void setStaticDoubleField();
void setStaticFloatField();
void setStaticShortField();
void setStaticCharField();
void setStaticBooleanField();
void setStaticObjectField();
void templateApiCheck();
void defaultTemplateApiCheck();
void isClassAvailable();
void fromLocalRef();
void largeObjectArray();
void arrayLifetime();
void callback_data();
void callback();
void callStaticOverloadResolution();
void cachedMethodIdKeyCollision();
void cachedFieldIdKeyIgnoresKind();
void implicitExceptionHandling_construct();
void implicitExceptionHandling_callMethod();
void implicitExceptionHandling_callStaticMethod();
void implicitExceptionHandling_getField();
void implicitExceptionHandling_setField();
void implicitExceptionHandling_getStaticField();
void implicitExceptionHandling_setStaticField();
void constructWithException();
void callMethodWithException();
void callMethodWithMonadic();
void callMethodWithTryCatch();
void callStaticMethodWithException();
void getFieldWithException();
void setFieldWithException();
void getStaticFieldWithException();
void setStaticFieldWithException();
void cleanupTestCase();
};
tst_QJniObject::tst_QJniObject()
{
}
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()
{
}
void tst_QJniObject::ctor()
{
{
QJniObject object;
QVERIFY(!object.isValid());
}
{
QJniObject object("java/lang/String");
QVERIFY(object.isValid());
}
{
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());
QCOMPARE(string.toString(), object.toString());
}
{
QJniObject string = QJniObject::fromString(QLatin1String("Hello, Java"));
QJniObject object = QJniObject::construct<jstring>(string.object<jstring>());
QVERIFY(object.isValid());
QCOMPARE(string.toString(), object.toString());
}
{
QJniEnvironment env;
jclass javaStringClass = env->FindClass("java/lang/String");
QJniObject string(javaStringClass);
QVERIFY(string.isValid());
}
{
QJniEnvironment env;
const QString qString = QLatin1String("Hello, Java");
jclass javaStringClass = env->FindClass("java/lang/String");
QJniObject string = QJniObject::fromString(qString);
QJniObject stringCpy(javaStringClass, "(Ljava/lang/String;)V", string.object<jstring>());
QVERIFY(stringCpy.isValid());
QCOMPARE(qString, stringCpy.toString());
}
{
QJniEnvironment env;
const QString qString = QLatin1String("Hello, Java");
jclass javaStringClass = env->FindClass("java/lang/String");
QJniObject string = QJniObject::fromString(qString);
QJniObject stringCpy(javaStringClass, string.object<jstring>());
QVERIFY(stringCpy.isValid());
QCOMPARE(qString, stringCpy.toString());
}
}
void tst_QJniObject::callMethodTest()
{
{
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");
QVERIFY(!isEmpty);
jint ret = jString1.callMethod<jint>("compareToIgnoreCase",
"(Ljava/lang/String;)I",
jString2.object<jstring>());
QVERIFY(0 == ret);
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);
}
{
jlong jLong = 100;
QJniObject longObject("java/lang/Long", "(J)V", jLong);
jlong ret = longObject.callMethod<jlong>("longValue");
QCOMPARE(ret, jLong);
}
// as of Qt 6.4, callMethod works with an object type as well!
{
const QString qString = QLatin1String("Hello, Java");
QJniObject jString = QJniObject::fromString(qString);
const QString qStringRet = jString.callMethod<jstring>("toUpperCase").toString();
QCOMPARE(qString.toUpper(), qStringRet);
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");
QJniObject jString = QJniObject::fromString(qString);
const QString qStringRet = jString.callObjectMethod<jstring>("toUpperCase").toString();
QCOMPARE(qString.toUpper(), qStringRet);
QJniObject subString = jString.callObjectMethod("substring",
"(II)Ljava/lang/String;",
0, 4);
QCOMPARE(subString.toString(), qString.mid(0, 4));
subString = jString.callObjectMethod<jstring>("substring", 0, 4);
QCOMPARE(subString.toString(), qString.mid(0, 4));
}
void tst_QJniObject::stringConvertionTest()
{
const QString qString(QLatin1String("Hello, Java"));
QJniObject jString = QJniObject::fromString(qString);
QVERIFY(jString.isValid());
QString qStringRet = jString.toString();
QCOMPARE(qString, qStringRet);
}
void tst_QJniObject::compareOperatorTests()
{
QString str("hello!");
QJniObject stringObject = QJniObject::fromString(str);
jobject obj = stringObject.object();
jobject jobj = stringObject.object<jobject>();
jstring jsobj = stringObject.object<jstring>();
QVERIFY(obj == stringObject);
QVERIFY(jobj == stringObject);
QVERIFY(stringObject == jobj);
QVERIFY(jsobj == stringObject);
QVERIFY(stringObject == jsobj);
QJniObject stringObject3 = stringObject.object<jstring>();
QVERIFY(stringObject3 == stringObject);
QJniObject stringObject2 = QJniObject::fromString(str);
QVERIFY(stringObject != stringObject2);
jstring jstrobj = nullptr;
QJniObject invalidStringObject;
QVERIFY(invalidStringObject == jstrobj);
QVERIFY(jstrobj != stringObject);
QVERIFY(stringObject != jstrobj);
QVERIFY(!invalidStringObject.isValid());
QJniObject movedTo(std::move(stringObject3));
QVERIFY(!stringObject3.isValid());
QCOMPARE_NE(movedTo, stringObject3);
QCOMPARE(invalidStringObject, stringObject3);
}
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"));
QVERIFY(formatString.isValid());
QVERIFY(QJniObject::isClassAvailable("java/lang/String"));
QJniObject returnValue = QJniObject::callStaticObjectMethod("java/lang/String",
"format",
"(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;",
formatString.object<jstring>(),
jobjectArray(0));
QVERIFY(returnValue.isValid());
QString returnedString = returnValue.toString();
QCOMPARE(returnedString, QString::fromLatin1("test format"));
returnValue = QJniObject::callStaticObjectMethod<jstring>("java/lang/String",
"format",
formatString.object<jstring>(),
jobjectArray(0));
QVERIFY(returnValue.isValid());
returnedString = returnValue.toString();
QCOMPARE(returnedString, QString::fromLatin1("test format"));
}
void tst_QJniObject::callStaticObjectMethod()
{
QJniEnvironment env;
jclass cls = env->FindClass("java/lang/String");
QVERIFY(cls != 0);
const QString string = u"test format"_s;
QJniObject formatString = QJniObject::fromString(string);
QVERIFY(formatString.isValid());
QJniObject returnValue = QJniObject::callStaticObjectMethod(cls,
"format",
"(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;",
formatString.object<jstring>(),
jobjectArray(0));
QVERIFY(returnValue.isValid());
QCOMPARE(returnValue.toString(), string);
returnValue = QJniObject::callStaticObjectMethod<jstring>(cls,
"format",
formatString.object<jstring>(),
jobjectArray(0));
QVERIFY(returnValue.isValid());
QCOMPARE(returnValue.toString(), string);
// from 6.4 on we can use callStaticMethod
returnValue = QJniObject::callStaticMethod<jstring>(cls,
"format",
formatString.object<jstring>(),
jobjectArray(0));
QVERIFY(returnValue.isValid());
QCOMPARE(returnValue.toString(), string);
// 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);
// 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()
{
QJniEnvironment env;
jclass cls = env.findClass("java/lang/String");
QVERIFY(cls != 0);
jmethodID id = env.findStaticMethod(
cls, "format", "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;");
QVERIFY(id != 0);
QJniObject formatString = QJniObject::fromString(QLatin1String("test format"));
QVERIFY(formatString.isValid());
QJniObject returnValue = QJniObject::callStaticObjectMethod(
cls, id, formatString.object<jstring>(), jobjectArray(0));
QVERIFY(returnValue.isValid());
QString returnedString = returnValue.toString();
QCOMPARE(returnedString, QString::fromLatin1("test format"));
// from Qt 6.4 on we can use callStaticMethod as well
returnValue = QJniObject::callStaticMethod<jstring>(
cls, id, formatString.object<jstring>(), jobjectArray(0));
QVERIFY(returnValue.isValid());
returnedString = returnValue.toString();
QCOMPARE(returnedString, QString::fromLatin1("test format"));
}
void tst_QJniObject::callStaticBooleanMethod()
{
QJniEnvironment env;
jclass cls = env->FindClass("java/lang/Boolean");
QVERIFY(cls != 0);
{
QJniObject parameter = QJniObject::fromString("true");
QVERIFY(parameter.isValid());
jboolean b = QJniObject::callStaticMethod<jboolean>(cls,
"parseBoolean",
"(Ljava/lang/String;)Z",
parameter.object<jstring>());
QVERIFY(b);
b = QJniObject::callStaticMethod<jboolean>(cls, "parseBoolean", parameter.object<jstring>());
QVERIFY(b);
}
{
QJniObject parameter = QJniObject::fromString("false");
QVERIFY(parameter.isValid());
jboolean b = QJniObject::callStaticMethod<jboolean>(cls,
"parseBoolean",
"(Ljava/lang/String;)Z",
parameter.object<jstring>());
QVERIFY(!b);
b = QJniObject::callStaticMethod<jboolean>(cls, "parseBoolean", parameter.object<jstring>());
QVERIFY(!b);
}
}
void tst_QJniObject::callStaticBooleanMethodById()
{
QJniEnvironment env;
jclass cls = env.findClass("java/lang/Boolean");
QVERIFY(cls != 0);
jmethodID id = env.findStaticMethod(cls, "parseBoolean", "(Ljava/lang/String;)Z");
QVERIFY(id != 0);
{
QJniObject parameter = QJniObject::fromString("true");
QVERIFY(parameter.isValid());
jboolean b = QJniObject::callStaticMethod<jboolean>(cls, id, parameter.object<jstring>());
QVERIFY(b);
}
{
QJniObject parameter = QJniObject::fromString("false");
QVERIFY(parameter.isValid());
jboolean b = QJniObject::callStaticMethod<jboolean>(cls, id, parameter.object<jstring>());
QVERIFY(!b);
}
}
void tst_QJniObject::callStaticBooleanMethodClassName()
{
{
QJniObject parameter = QJniObject::fromString("true");
QVERIFY(parameter.isValid());
jboolean b = QJniObject::callStaticMethod<jboolean>("java/lang/Boolean",
"parseBoolean",
"(Ljava/lang/String;)Z",
parameter.object<jstring>());
QVERIFY(b);
b = QJniObject::callStaticMethod<jboolean>("java/lang/Boolean",
"parseBoolean",
parameter.object<jstring>());
QVERIFY(b);
}
{
QJniObject parameter = QJniObject::fromString("false");
QVERIFY(parameter.isValid());
jboolean b = QJniObject::callStaticMethod<jboolean>("java/lang/Boolean",
"parseBoolean",
"(Ljava/lang/String;)Z",
parameter.object<jstring>());
QVERIFY(!b);
b = QJniObject::callStaticMethod<jboolean>("java/lang/Boolean",
"parseBoolean",
parameter.object<jstring>());
QVERIFY(!b);
}
}
void tst_QJniObject::callStaticByteMethodClassName()
{
QString number = QString::number(123);
QJniObject parameter = QJniObject::fromString(number);
jbyte returnValue = QJniObject::callStaticMethod<jbyte>("java/lang/Byte",
"parseByte",
parameter.object<jstring>());
QCOMPARE(returnValue, jbyte(number.toInt()));
}
void tst_QJniObject::callStaticByteMethod()
{
QJniEnvironment env;
jclass cls = env->FindClass("java/lang/Byte");
QVERIFY(cls != 0);
QString number = QString::number(123);
QJniObject parameter = QJniObject::fromString(number);
jbyte returnValue = QJniObject::callStaticMethod<jbyte>(cls,
"parseByte",
parameter.object<jstring>());
QCOMPARE(returnValue, jbyte(number.toInt()));
}
void tst_QJniObject::callStaticByteMethodById()
{
QJniEnvironment env;
jclass cls = env.findClass("java/lang/Byte");
QVERIFY(cls != 0);
jmethodID id = env.findStaticMethod(cls, "parseByte", "(Ljava/lang/String;)B");
QVERIFY(id != 0);
QString number = QString::number(123);
QJniObject parameter = QJniObject::fromString(number);
jbyte returnValue = QJniObject::callStaticMethod<jbyte>(cls, id, parameter.object<jstring>());
QCOMPARE(returnValue, jbyte(number.toInt()));
}
void tst_QJniObject::callStaticIntMethodClassName()
{
QString number = QString::number(123);
QJniObject parameter = QJniObject::fromString(number);
jint returnValue = QJniObject::callStaticMethod<jint>("java/lang/Integer",
"parseInt",
parameter.object<jstring>());
QCOMPARE(returnValue, number.toInt());
}
void tst_QJniObject::callStaticIntMethod()
{
QJniEnvironment env;
jclass cls = env->FindClass("java/lang/Integer");
QVERIFY(cls != 0);
QString number = QString::number(123);
QJniObject parameter = QJniObject::fromString(number);
jint returnValue = QJniObject::callStaticMethod<jint>(cls,
"parseInt",
parameter.object<jstring>());
QCOMPARE(returnValue, number.toInt());
}
void tst_QJniObject::callStaticIntMethodById()
{
QJniEnvironment env;
jclass cls = env.findClass("java/lang/Integer");
QVERIFY(cls != 0);
jmethodID id = env.findStaticMethod(cls, "parseInt", "(Ljava/lang/String;)I");
QVERIFY(id != 0);
QString number = QString::number(123);
QJniObject parameter = QJniObject::fromString(number);
jint returnValue = QJniObject::callStaticMethod<jint>(cls, id, parameter.object<jstring>());
QCOMPARE(returnValue, number.toInt());
}
void tst_QJniObject::callStaticCharMethodClassName()
{
jchar returnValue = QJniObject::callStaticMethod<jchar>("java/lang/Character",
"toUpperCase",
jchar('a'));
QCOMPARE(returnValue, jchar('A'));
}
void tst_QJniObject::callStaticCharMethod()
{
QJniEnvironment env;
jclass cls = env->FindClass("java/lang/Character");
QVERIFY(cls != 0);
jchar returnValue = QJniObject::callStaticMethod<jchar>(cls,
"toUpperCase",
jchar('a'));
QCOMPARE(returnValue, jchar('A'));
}
void tst_QJniObject::callStaticCharMethodById()
{
QJniEnvironment env;
jclass cls = env.findClass("java/lang/Character");
QVERIFY(cls != 0);
jmethodID id = env.findStaticMethod(cls, "toUpperCase", "(C)C");
QVERIFY(id != 0);
jchar returnValue = QJniObject::callStaticMethod<jchar>(cls, id, jchar('a'));
QCOMPARE(returnValue, jchar('A'));
}
void tst_QJniObject::callStaticDoubleMethodClassName ()
{
QString number = QString::number(123.45);
QJniObject parameter = QJniObject::fromString(number);
jdouble returnValue = QJniObject::callStaticMethod<jdouble>("java/lang/Double",
"parseDouble",
parameter.object<jstring>());
QCOMPARE(returnValue, number.toDouble());
}
void tst_QJniObject::callStaticDoubleMethod()
{
QJniEnvironment env;
jclass cls = env->FindClass("java/lang/Double");
QVERIFY(cls != 0);
QString number = QString::number(123.45);
QJniObject parameter = QJniObject::fromString(number);
jdouble returnValue = QJniObject::callStaticMethod<jdouble>(cls,
"parseDouble",
parameter.object<jstring>());
QCOMPARE(returnValue, number.toDouble());
}
void tst_QJniObject::callStaticDoubleMethodById()
{
QJniEnvironment env;
jclass cls = env.findClass("java/lang/Double");
QVERIFY(cls != 0);
jmethodID id = env.findStaticMethod(cls, "parseDouble", "(Ljava/lang/String;)D");
QVERIFY(id != 0);
QString number = QString::number(123.45);
QJniObject parameter = QJniObject::fromString(number);
jdouble returnValue =
QJniObject::callStaticMethod<jdouble>(cls, id, parameter.object<jstring>());
QCOMPARE(returnValue, number.toDouble());
}
void tst_QJniObject::callStaticFloatMethodClassName()
{
QString number = QString::number(123.45);
QJniObject parameter = QJniObject::fromString(number);
jfloat returnValue = QJniObject::callStaticMethod<jfloat>("java/lang/Float",
"parseFloat",
parameter.object<jstring>());
QCOMPARE(returnValue, number.toFloat());
}
void tst_QJniObject::callStaticFloatMethod()
{
QJniEnvironment env;
jclass cls = env->FindClass("java/lang/Float");
QVERIFY(cls != 0);
QString number = QString::number(123.45);
QJniObject parameter = QJniObject::fromString(number);
jfloat returnValue = QJniObject::callStaticMethod<jfloat>(cls,
"parseFloat",
parameter.object<jstring>());
QCOMPARE(returnValue, number.toFloat());
}
void tst_QJniObject::callStaticFloatMethodById()
{
QJniEnvironment env;
jclass cls = env.findClass("java/lang/Float");
QVERIFY(cls != 0);
jmethodID id = env.findStaticMethod(cls, "parseFloat", "(Ljava/lang/String;)F");
QVERIFY(id != 0);
QString number = QString::number(123.45);
QJniObject parameter = QJniObject::fromString(number);
jfloat returnValue = QJniObject::callStaticMethod<jfloat>(cls, id, parameter.object<jstring>());
QCOMPARE(returnValue, number.toFloat());
}
void tst_QJniObject::callStaticShortMethodClassName()
{
QString number = QString::number(123);
QJniObject parameter = QJniObject::fromString(number);
jshort returnValue = QJniObject::callStaticMethod<jshort>("java/lang/Short",
"parseShort",
parameter.object<jstring>());
QCOMPARE(returnValue, number.toShort());
}
void tst_QJniObject::callStaticShortMethod()
{
QJniEnvironment env;
jclass cls = env->FindClass("java/lang/Short");
QVERIFY(cls != 0);
QString number = QString::number(123);
QJniObject parameter = QJniObject::fromString(number);
jshort returnValue = QJniObject::callStaticMethod<jshort>(cls,
"parseShort",
parameter.object<jstring>());
QCOMPARE(returnValue, number.toShort());
}
void tst_QJniObject::callStaticShortMethodById()
{
QJniEnvironment env;
jclass cls = env.findClass("java/lang/Short");
QVERIFY(cls != 0);
jmethodID id = env.findStaticMethod(cls, "parseShort", "(Ljava/lang/String;)S");
QVERIFY(id != 0);
QString number = QString::number(123);
QJniObject parameter = QJniObject::fromString(number);
jshort returnValue = QJniObject::callStaticMethod<jshort>(cls, id, parameter.object<jstring>());
QCOMPARE(returnValue, number.toShort());
}
void tst_QJniObject::callStaticLongMethodClassName()
{
QString number = QString::number(123);
QJniObject parameter = QJniObject::fromString(number);
jlong returnValue = QJniObject::callStaticMethod<jlong>("java/lang/Long",
"parseLong",
parameter.object<jstring>());
QCOMPARE(returnValue, jlong(number.toLong()));
}
void tst_QJniObject::callStaticLongMethod()
{
QJniEnvironment env;
jclass cls = env->FindClass("java/lang/Long");
QVERIFY(cls != 0);
QString number = QString::number(123);
QJniObject parameter = QJniObject::fromString(number);
jlong returnValue = QJniObject::callStaticMethod<jlong>(cls,
"parseLong",
parameter.object<jstring>());
QCOMPARE(returnValue, jlong(number.toLong()));
}
void tst_QJniObject::callStaticLongMethodById()
{
QJniEnvironment env;
jclass cls = env.findClass("java/lang/Long");
QVERIFY(cls != 0);
jmethodID id = env.findStaticMethod(cls, "parseLong", "(Ljava/lang/String;)J");
QVERIFY(id != 0);
QString number = QString::number(123);
QJniObject parameter = QJniObject::fromString(number);
jlong returnValue = QJniObject::callStaticMethod<jlong>(cls, id, parameter.object<jstring>());
QCOMPARE(returnValue, jlong(number.toLong()));
}
void tst_QJniObject::getStaticObjectFieldClassName()
{
{
QJniObject boolObject = QJniObject::getStaticObjectField("java/lang/Boolean",
"FALSE",
"Ljava/lang/Boolean;");
QVERIFY(boolObject.isValid());
jboolean booleanValue = boolObject.callMethod<jboolean>("booleanValue");
QVERIFY(!booleanValue);
}
{
QJniObject boolObject = QJniObject::getStaticObjectField("java/lang/Boolean",
"TRUE",
"Ljava/lang/Boolean;");
QVERIFY(boolObject.isValid());
jboolean booleanValue = boolObject.callMethod<jboolean>("booleanValue");
QVERIFY(booleanValue);
}
{
QJniObject boolObject = QJniObject::getStaticObjectField("java/lang/Boolean",
"FALSE",
"Ljava/lang/Boolean;");
QVERIFY(boolObject.isValid());
jboolean booleanValue = boolObject.callMethod<jboolean>("booleanValue");
QVERIFY(!booleanValue);
}
}
void tst_QJniObject::getStaticObjectField()
{
QJniEnvironment env;
jclass cls = env->FindClass("java/lang/Boolean");
QVERIFY(cls != 0);
{
QJniObject boolObject = QJniObject::getStaticObjectField(cls,
"FALSE",
"Ljava/lang/Boolean;");
QVERIFY(boolObject.isValid());
jboolean booleanValue = boolObject.callMethod<jboolean>("booleanValue");
QVERIFY(!booleanValue);
}
{
QJniObject boolObject = QJniObject::getStaticObjectField(cls,
"TRUE",
"Ljava/lang/Boolean;");
QVERIFY(boolObject.isValid());
jboolean booleanValue = boolObject.callMethod<jboolean>("booleanValue");
QVERIFY(booleanValue);
}
{
QJniObject boolObject = QJniObject::getStaticObjectField(cls,
"FALSE",
"Ljava/lang/Boolean;");
QVERIFY(boolObject.isValid());
jboolean booleanValue = boolObject.callMethod<jboolean>("booleanValue");
QVERIFY(!booleanValue);
}
}
void tst_QJniObject::getStaticIntFieldClassName()
{
jint i = QJniObject::getStaticField<jint>("java/lang/Double", "SIZE");
QCOMPARE(i, 64);
}
void tst_QJniObject::getStaticIntField()
{
QJniEnvironment env;
jclass cls = env->FindClass("java/lang/Double");
QVERIFY(cls != 0);
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()
{
jbyte i = QJniObject::getStaticField<jbyte>("java/lang/Byte", "MAX_VALUE");
QCOMPARE(i, jbyte(127));
}
void tst_QJniObject::getStaticByteField()
{
QJniEnvironment env;
jclass cls = env->FindClass("java/lang/Byte");
QVERIFY(cls != 0);
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()
{
jlong i = QJniObject::getStaticField<jlong>("java/lang/Long", "MAX_VALUE");
QCOMPARE(i, jlong(9223372036854775807L));
}
void tst_QJniObject::getStaticLongField()
{
QJniEnvironment env;
jclass cls = env->FindClass("java/lang/Long");
QVERIFY(cls != 0);
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()
{
jdouble i = QJniObject::getStaticField<jdouble>("java/lang/Double", "NaN");
jlong *k = reinterpret_cast<jlong*>(&i);
QCOMPARE(*k, jlong(0x7ff8000000000000L));
}
void tst_QJniObject::getStaticDoubleField()
{
QJniEnvironment env;
jclass cls = env->FindClass("java/lang/Double");
QVERIFY(cls != 0);
jdouble i = QJniObject::getStaticField<jdouble>(cls, "NaN");
jlong *k = reinterpret_cast<jlong*>(&i);
QCOMPARE(*k, jlong(0x7ff8000000000000L));
}
void tst_QJniObject::getStaticFloatFieldClassName()
{
jfloat i = QJniObject::getStaticField<jfloat>("java/lang/Float", "NaN");
unsigned *k = reinterpret_cast<unsigned*>(&i);
QCOMPARE(*k, unsigned(0x7fc00000));
}
void tst_QJniObject::getStaticFloatField()
{
QJniEnvironment env;
jclass cls = env->FindClass("java/lang/Float");
QVERIFY(cls != 0);
jfloat i = QJniObject::getStaticField<jfloat>(cls, "NaN");
unsigned *k = reinterpret_cast<unsigned*>(&i);
QCOMPARE(*k, unsigned(0x7fc00000));
}
void tst_QJniObject::getStaticShortFieldClassName()
{
jshort i = QJniObject::getStaticField<jshort>("java/lang/Short", "MAX_VALUE");
QCOMPARE(i, jshort(32767));
}
void tst_QJniObject::getStaticShortField()
{
QJniEnvironment env;
jclass cls = env->FindClass("java/lang/Short");
QVERIFY(cls != 0);
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()
{
jchar i = QJniObject::getStaticField<jchar>("java/lang/Character", "MAX_VALUE");
QCOMPARE(i, jchar(0xffff));
}
void tst_QJniObject::getStaticCharField()
{
QJniEnvironment env;
jclass cls = env->FindClass("java/lang/Character");
QVERIFY(cls != 0);
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(testClassName);
QVERIFY(obj.isValid());
QVERIFY(obj.getField<jboolean>("BOOL_FIELD"));
QVERIFY(obj.getField<bool>("BOOL_FIELD"));
}
void tst_QJniObject::getIntField()
{
QJniObject obj(testClassName);
QVERIFY(obj.isValid());
jint res = obj.getField<jint>("INT_FIELD");
QCOMPARE(res, 123);
}
template <typename T>
void setField(const char *fieldName, T testValue)
{
QJniObject obj(testClassName);
QVERIFY(obj.isValid());
obj.setField(fieldName, testValue);
T res = obj.getField<T>(fieldName);
QCOMPARE(res, 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(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()
{
setField("DOUBLE_VAR", jdouble(1.2));
}
void tst_QJniObject::setFloatField()
{
setField("FLOAT_VAR", jfloat(1.2));
}
void tst_QJniObject::setShortField()
{
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()
{
QJniObject obj(testClassName);
QVERIFY(obj.isValid());
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>
void setStaticField(const char *fieldName, T testValue)
{
QJniObject::setStaticField(testClassName, fieldName, 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(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()
{
setStaticField("S_DOUBLE_VAR", jdouble(1.2));
}
void tst_QJniObject::setStaticFloatField()
{
setStaticField("S_FLOAT_VAR", jfloat(1.2));
}
void tst_QJniObject::setStaticShortField()
{
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()
{
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()
{
QJniObject testClass(testClassName);
QVERIFY(testClass.isValid());
// void ---------------------------------------------------------------------------------------
QJniObject::callStaticMethod<void>(testClassName, "staticVoidMethod");
QJniObject::callStaticMethod<void>(testClassName,
"staticVoidMethodWithArgs",
"(IZC)V",
1,
true,
'c');
QJniObject::callStaticMethod<void>(testClassName,
"staticVoidMethodWithArgs",
1,
true,
'c');
testClass.callMethod<void>("voidMethod");
testClass.callMethod<void>("voidMethodWithArgs", "(IZC)V", 1, true, 'c');
testClass.callMethod<void>("voidMethodWithArgs", 1, true, 'c');
// jboolean -----------------------------------------------------------------------------------
QVERIFY(QJniObject::callStaticMethod<jboolean>(testClassName, "staticBooleanMethod"));
QVERIFY(QJniObject::callStaticMethod<jboolean>(testClassName,
"staticBooleanMethodWithArgs",
"(ZZZ)Z",
true,
true,
true));
QVERIFY(QJniObject::callStaticMethod<jboolean>(testClassName,
"staticBooleanMethodWithArgs",
true,
true,
true));
QVERIFY(testClass.callMethod<jboolean>("booleanMethod"));
QVERIFY(testClass.callMethod<jboolean>("booleanMethodWithArgs",
"(ZZZ)Z",
true,
true,
true));
QVERIFY(testClass.callMethod<jboolean>("booleanMethodWithArgs",
true,
true,
true));
// jbyte --------------------------------------------------------------------------------------
QVERIFY(QJniObject::callStaticMethod<jbyte>(testClassName,
"staticByteMethod") == A_BYTE_VALUE);
QVERIFY(QJniObject::callStaticMethod<jbyte>(testClassName,
"staticByteMethodWithArgs",
"(BBB)B",
1,
1,
1) == A_BYTE_VALUE);
QVERIFY(QJniObject::callStaticMethod<jbyte>(testClassName,
"staticByteMethodWithArgs",
jbyte(1),
jbyte(1),
jbyte(1)) == A_BYTE_VALUE);
QVERIFY(testClass.callMethod<jbyte>("byteMethod") == A_BYTE_VALUE);
QVERIFY(testClass.callMethod<jbyte>("byteMethodWithArgs", "(BBB)B", 1, 1, 1) == A_BYTE_VALUE);
QVERIFY(testClass.callMethod<jbyte>("byteMethodWithArgs", jbyte(1), jbyte(1), jbyte(1)) == A_BYTE_VALUE);
// jchar --------------------------------------------------------------------------------------
QVERIFY(QJniObject::callStaticMethod<jchar>(testClassName,
"staticCharMethod") == A_CHAR_VALUE);
QVERIFY(QJniObject::callStaticMethod<jchar>(testClassName,
"staticCharMethodWithArgs",
"(CCC)C",
jchar(1),
jchar(1),
jchar(1)) == A_CHAR_VALUE);
QVERIFY(QJniObject::callStaticMethod<jchar>(testClassName,
"staticCharMethodWithArgs",
jchar(1),
jchar(1),
jchar(1)) == A_CHAR_VALUE);
QVERIFY(testClass.callMethod<jchar>("charMethod") == A_CHAR_VALUE);
QVERIFY(testClass.callMethod<jchar>("charMethodWithArgs",
"(CCC)C",
jchar(1),
jchar(1),
jchar(1)) == A_CHAR_VALUE);
QVERIFY(testClass.callMethod<jchar>("charMethodWithArgs",
jchar(1),
jchar(1),
jchar(1)) == A_CHAR_VALUE);
// jshort -------------------------------------------------------------------------------------
QVERIFY(QJniObject::callStaticMethod<jshort>(testClassName,
"staticShortMethod") == A_SHORT_VALUE);
QVERIFY(QJniObject::callStaticMethod<jshort>(testClassName,
"staticShortMethodWithArgs",
"(SSS)S",
jshort(1),
jshort(1),
jshort(1)) == A_SHORT_VALUE);
QVERIFY(QJniObject::callStaticMethod<jshort>(testClassName,
"staticShortMethodWithArgs",
jshort(1),
jshort(1),
jshort(1)) == A_SHORT_VALUE);
QVERIFY(testClass.callMethod<jshort>("shortMethod") == A_SHORT_VALUE);
QVERIFY(testClass.callMethod<jshort>("shortMethodWithArgs",
"(SSS)S",
jshort(1),
jshort(1),
jshort(1)) == A_SHORT_VALUE);
QVERIFY(testClass.callMethod<jshort>("shortMethodWithArgs",
jshort(1),
jshort(1),
jshort(1)) == A_SHORT_VALUE);
// jint ---------------------------------------------------------------------------------------
QVERIFY(QJniObject::callStaticMethod<jint>(testClassName,
"staticIntMethod") == A_INT_VALUE);
QVERIFY(QJniObject::callStaticMethod<jint>(testClassName,
"staticIntMethodWithArgs",
"(III)I",
jint(1),
jint(1),
jint(1)) == A_INT_VALUE);
QVERIFY(QJniObject::callStaticMethod<jint>(testClassName,
"staticIntMethodWithArgs",
jint(1),
jint(1),
jint(1)) == A_INT_VALUE);
QVERIFY(testClass.callMethod<jint>("intMethod") == A_INT_VALUE);
QVERIFY(testClass.callMethod<jint>("intMethodWithArgs",
"(III)I",
jint(1),
jint(1),
jint(1)) == A_INT_VALUE);
QVERIFY(testClass.callMethod<jint>("intMethodWithArgs",
jint(1),
jint(1),
jint(1)) == A_INT_VALUE);
// jlong --------------------------------------------------------------------------------------
QVERIFY(QJniObject::callStaticMethod<jlong>(testClassName,
"staticLongMethod") == A_LONG_VALUE);
QVERIFY(QJniObject::callStaticMethod<jlong>(testClassName,
"staticLongMethodWithArgs",
"(JJJ)J",
jlong(1),
jlong(1),
jlong(1)) == A_LONG_VALUE);
QVERIFY(QJniObject::callStaticMethod<jlong>(testClassName,
"staticLongMethodWithArgs",
jlong(1),
jlong(1),
jlong(1)) == A_LONG_VALUE);
QVERIFY(testClass.callMethod<jlong>("longMethod") == A_LONG_VALUE);
QVERIFY(testClass.callMethod<jlong>("longMethodWithArgs",
"(JJJ)J",
jlong(1),
jlong(1),
jlong(1)) == A_LONG_VALUE);
QVERIFY(testClass.callMethod<jlong>("longMethodWithArgs",
jlong(1),
jlong(1),
jlong(1)) == A_LONG_VALUE);
// jfloat -------------------------------------------------------------------------------------
QVERIFY(QJniObject::callStaticMethod<jfloat>(testClassName,
"staticFloatMethod") == A_FLOAT_VALUE);
QVERIFY(QJniObject::callStaticMethod<jfloat>(testClassName,
"staticFloatMethodWithArgs",
"(FFF)F",
jfloat(1.1),
jfloat(1.1),
jfloat(1.1)) == A_FLOAT_VALUE);
QVERIFY(QJniObject::callStaticMethod<jfloat>(testClassName,
"staticFloatMethodWithArgs",
jfloat(1.1),
jfloat(1.1),
jfloat(1.1)) == A_FLOAT_VALUE);
QVERIFY(testClass.callMethod<jfloat>("floatMethod") == A_FLOAT_VALUE);
QVERIFY(testClass.callMethod<jfloat>("floatMethodWithArgs",
"(FFF)F",
jfloat(1.1),
jfloat(1.1),
jfloat(1.1)) == A_FLOAT_VALUE);
QVERIFY(testClass.callMethod<jfloat>("floatMethodWithArgs",
jfloat(1.1),
jfloat(1.1),
jfloat(1.1)) == A_FLOAT_VALUE);
// jdouble ------------------------------------------------------------------------------------
QVERIFY(QJniObject::callStaticMethod<jdouble>(testClassName,
"staticDoubleMethod") == A_DOUBLE_VALUE);
QVERIFY(QJniObject::callStaticMethod<jdouble>(testClassName,
"staticDoubleMethodWithArgs",
"(DDD)D",
jdouble(1.1),
jdouble(1.1),
jdouble(1.1)) == A_DOUBLE_VALUE);
QVERIFY(QJniObject::callStaticMethod<jdouble>(testClassName,
"staticDoubleMethodWithArgs",
jdouble(1.1),
jdouble(1.1),
jdouble(1.1)) == A_DOUBLE_VALUE);
QVERIFY(testClass.callMethod<jdouble>("doubleMethod") == A_DOUBLE_VALUE);
QVERIFY(testClass.callMethod<jdouble>("doubleMethodWithArgs",
"(DDD)D",
jdouble(1.1),
jdouble(1.1),
jdouble(1.1)) == A_DOUBLE_VALUE);
QVERIFY(testClass.callMethod<jdouble>("doubleMethodWithArgs",
jdouble(1.1),
jdouble(1.1),
jdouble(1.1)) == A_DOUBLE_VALUE);
// jobject ------------------------------------------------------------------------------------
{
QJniObject res = QJniObject::callStaticObjectMethod<jobject>(testClassName,
"staticObjectMethod");
QVERIFY(res.isValid());
}
{
QJniObject res = testClass.callObjectMethod<jobject>("objectMethod");
QVERIFY(res.isValid());
}
// jclass -------------------------------------------------------------------------------------
{
QJniObject res = QJniObject::callStaticObjectMethod<jclass>(testClassName,
"staticClassMethod");
QVERIFY(res.isValid());
QJniEnvironment env;
QVERIFY(env->IsInstanceOf(testClass.object(), res.object<jclass>()));
}
{
QJniObject res = testClass.callObjectMethod<jclass>("classMethod");
QVERIFY(res.isValid());
QJniEnvironment env;
QVERIFY(env->IsInstanceOf(testClass.object(), res.object<jclass>()));
}
// jstring ------------------------------------------------------------------------------------
{
QJniObject res = QJniObject::callStaticObjectMethod<jstring>(testClassName,
"staticStringMethod");
QVERIFY(res.isValid());
QVERIFY(res.toString() == A_STRING_OBJECT());
}
{
QJniObject res = testClass.callObjectMethod<jstring>("stringMethod");
QVERIFY(res.isValid());
QVERIFY(res.toString() == A_STRING_OBJECT());
}
// jthrowable ---------------------------------------------------------------------------------
{
// The Throwable object the same message (see: "getMessage()") as A_STRING_OBJECT
QJniObject res = QJniObject::callStaticObjectMethod<jthrowable>(testClassName,
"staticThrowableMethod");
QVERIFY(res.isValid());
QVERIFY(res.callObjectMethod<jstring>("getMessage").toString() == A_STRING_OBJECT());
}
{
QJniObject res = testClass.callObjectMethod<jthrowable>("throwableMethod");
QVERIFY(res.isValid());
QVERIFY(res.callObjectMethod<jstring>("getMessage").toString() == A_STRING_OBJECT());
}
// jobjectArray -------------------------------------------------------------------------------
{
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);
}
// jstringArray ------------------------------------------------------------------------------
{
const QStringList strings{"First", "Second", "Third"};
const auto array = TestClass::callStaticMethod<QJniArray<QtJniTypes::String>>("staticStringArrayMethod");
QVERIFY(array.isValid());
QCOMPARE(array.size(), 3);
QCOMPARE(array.at(0).toString(), strings.first());
QCOMPARE(array.toContainer<QStringList>(), strings);
}
// jstringArray via implicit QString support -------------------------------------------------
{
const QStringList strings{"First", "Second", "Third"};
const auto array = TestClass::callStaticMethod<QJniArray<QString>>("staticStringArrayMethod");
QVERIFY(array.isValid());
QCOMPARE(array.size(), 3);
QCOMPARE(array.at(0), strings.first());
QCOMPARE(array.toContainer(), strings);
}
// jbooleanArray ------------------------------------------------------------------------------
{
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 ---------------------------------------------------------------------------------
{
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 newArray = QJniArray(QByteArray{"cba"});
QVERIFY(newArray.isValid());
const auto reverse = testClass.callMethod<jbyte[]>("reverseByteArray", newArray);
QVERIFY(reverse.isValid());
QCOMPARE(reverse.toContainer(), "abc");
}
// jcharArray ---------------------------------------------------------------------------------
{
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 --------------------------------------------------------------------------------
{
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 ----------------------------------------------------------------------------------
{
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 ---------------------------------------------------------------------------------
{
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 --------------------------------------------------------------------------------
{
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 -------------------------------------------------------------------------------
{
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}));
}
}
void tst_QJniObject::defaultTemplateApiCheck()
{
// static QJniObject calls --------------------------------------------------------------------
QJniObject::callStaticMethod(testClassName, "staticVoidMethod");
QJniObject::callStaticMethod(testClassName, "staticVoidMethodWithArgs", "(IZC)V", 1, true, 'c');
QJniObject::callStaticMethod(testClassName, "staticVoidMethodWithArgs", 1, true, 'c');
// instance QJniObject calls ------------------------------------------------------------------
QJniObject testClass(testClassName);
QVERIFY(testClass.isValid());
testClass.callMethod("voidMethod");
testClass.callMethod("voidMethodWithArgs", "(IZC)V", 1, true, 'c');
testClass.callMethod("voidMethodWithArgs", 1, true, 'c');
// static QtJniType calls ---------------------------------------------------------------------
TestClass::callStaticMethod("staticVoidMethod");
TestClass::callStaticMethod("staticVoidMethodWithArgs", 1, true, 'c');
// instance QtJniType calls -------------------------------------------------------------------
TestClass instance;
instance.callMethod("voidMethod");
instance.callMethod("voidMethodWithArgs", 1, true, 'c');
}
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"));
}
void tst_QJniObject::fromLocalRef()
{
const int limit = 512 + 1;
QJniEnvironment env;
for (int i = 0; i != limit; ++i)
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());
}
}
void tst_QJniObject::arrayLifetime()
{
const auto stringData = A_STRING_OBJECT();
QJniArray oldChars = TestClass::callStaticMethod<jchar[]>("getStaticCharArray");
QVERIFY(oldChars.isValid());
QCOMPARE(oldChars.size(), stringData.size());
QCOMPARE(QChar(oldChars.toContainer().at(0)), stringData.at(0));
QJniArray<jchar> newChars{'a', 'b', 'c'};
// replace the first three characters in the array
TestClass::callStaticMethod<void>("mutateStaticCharArray", newChars);
// the old jcharArray is still valid and the size is unchanged
QVERIFY(oldChars.isValid());
QCOMPARE(oldChars.size(), A_STRING_OBJECT().size());
QCOMPARE(oldChars.toContainer().at(0), jchar('a'));
// get a second reference to the Java array
QJniArray updatedChars = TestClass::getStaticField<jchar[]>("S_CHAR_ARRAY");
// the two QJniArrays reference the same jobject
QCOMPARE(updatedChars.size(), oldChars.size());
QCOMPARE(updatedChars, oldChars);
// replace the Java array; the old jcharArray is still valid and unchanged
TestClass::callStaticMethod<void>("replaceStaticCharArray", newChars);
// the old jcharArray is still valid and unchanged
QVERIFY(oldChars.isValid());
QCOMPARE(oldChars.size(), stringData.size());
QCOMPARE(oldChars, updatedChars);
// we get the same object that we set
updatedChars = TestClass::getStaticField<jchar[]>("S_CHAR_ARRAY");
QCOMPARE(updatedChars, newChars);
QCOMPARE_NE(updatedChars, oldChars);
}
enum class CallbackParameterType
{
Object,
ObjectRef,
String,
Byte,
Boolean,
Int,
Double,
Float,
JniArray,
RawArray,
QList,
QStringList,
Null,
Many,
};
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<float> calledWithFloat;
static int callbackWithFloat(JNIEnv *, jobject, float value)
{
calledWithFloat.emplace(value);
return int(CallbackParameterType::Float);
}
Q_DECLARE_JNI_NATIVE_METHOD(callbackWithFloat)
static std::optional<float> calledWithFloatStatic;
static int callbackWithFloatStatic(JNIEnv *, jclass, float value)
{
calledWithFloatStatic.emplace(value);
return int(CallbackParameterType::Float);
}
Q_DECLARE_JNI_NATIVE_METHOD(callbackWithFloatStatic)
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<QJniObject> calledWithRawArray;
static int callbackWithRawArray(JNIEnv *, jobject, jobjectArray value)
{
calledWithRawArray.emplace(QJniObject(value));
return int(CallbackParameterType::RawArray);
}
Q_DECLARE_JNI_NATIVE_METHOD(callbackWithRawArray)
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)
static std::optional<QString> calledWithNull;
static int callbackWithNull(JNIEnv *, jobject, const QString &str)
{
calledWithNull.emplace(str);
return int(CallbackParameterType::Null);
}
Q_DECLARE_JNI_NATIVE_METHOD(callbackWithNull)
static std::optional<bool> calledWithMany;
static int callbackWithMany(JNIEnv *, jobject, jbyte byte_val, short short_val, int int_val,
jlong long_val, float float_val, double double_val, bool bool_val,
jchar char_val, QString string_val)
{
auto diagnose = qScopeGuard([=]{
qCritical() << "Received values: "
<< byte_val << short_val << int_val << long_val
<< float_val << double_val << bool_val
<< char_val << string_val;
});
calledWithMany.emplace(TestClass::getStaticField<jbyte>("A_BYTE_VALUE") == byte_val
&& TestClass::getStaticField<short>("A_SHORT_VALUE") == short_val
&& TestClass::getStaticField<int>("A_INT_VALUE") == int_val
&& TestClass::getStaticField<jlong>("A_LONG_VALUE") == long_val
&& TestClass::getStaticField<float>("A_FLOAT_VALUE") == float_val
&& TestClass::getStaticField<double>("A_DOUBLE_VALUE") == double_val
&& TestClass::getStaticField<bool>("A_BOOLEAN_VALUE") == bool_val
&& TestClass::getStaticField<jchar>("A_CHAR_VALUE") == char_val
&& TestClass::getStaticField<QString>("A_STRING_OBJECT") == string_val
);
if (*calledWithMany)
diagnose.dismiss();
return int(CallbackParameterType::Many);
}
Q_DECLARE_JNI_NATIVE_METHOD(callbackWithMany)
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("Float") << CallbackParameterType::Float;
QTest::addRow("JniArray") << CallbackParameterType::JniArray;
QTest::addRow("RawArray") << CallbackParameterType::RawArray;
QTest::addRow("QList") << CallbackParameterType::QList;
QTest::addRow("QStringList") << CallbackParameterType::QStringList;
QTest::addRow("Null") << CallbackParameterType::Null;
QTest::addRow("More than 8") << CallbackParameterType::Many;
}
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::Float:
QVERIFY(TestClass::registerNativeMethods({
Q_JNI_NATIVE_METHOD(callbackWithFloat),
Q_JNI_NATIVE_METHOD(callbackWithFloatStatic),
}));
result = testObject.callMethod<int>("callMeBackWithFloat", 1.2345f);
QVERIFY(calledWithFloat);
QCOMPARE(calledWithFloat.value(), 1.2345f);
result = testObject.callMethod<int>("callMeBackWithFloatStatic", 1.2345f);
QVERIFY(calledWithFloatStatic);
QCOMPARE(calledWithFloatStatic.value(), 1.2345f);
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::RawArray: {
QVERIFY(TestClass::registerNativeMethods({
Q_JNI_NATIVE_METHOD(callbackWithRawArray)
}));
const QStringList strings{"a", "b", "c"};
const QJniArray stringArray(strings);
result = testObject.callMethod<int>("callMeBackWithRawArray",
stringArray.object<jobjectArray>());
QVERIFY(calledWithRawArray);
const auto stringsReceived = QJniArray<QString>(*calledWithRawArray).toContainer();
QCOMPARE(stringsReceived, strings);
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;
}
case CallbackParameterType::Null: {
QVERIFY(TestClass::registerNativeMethods({
Q_JNI_NATIVE_METHOD(callbackWithNull)
}));
result = testObject.callMethod<int>("callMeBackWithNull");
QVERIFY(calledWithNull);
QCOMPARE(calledWithNull.value(), QString());
break;
}
case CallbackParameterType::Many: {
QVERIFY(TestClass::registerNativeMethods({
Q_JNI_NATIVE_METHOD(callbackWithMany)
}));
result = testObject.callMethod<int>("callMeBackWithMany");
QVERIFY(calledWithMany);
QVERIFY(calledWithMany.value());
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);
}
void tst_QJniObject::implicitExceptionHandling_construct()
{
QJniEnvironment env;
const QRegularExpression invalidClass("java.lang.ClassNotFoundException: .*");
const QRegularExpression invalidMethod("java.lang.NoSuchMethodError: .*");
// Constructor, including named constructor
{
QTest::ignoreMessage(QtWarningMsg, invalidClass);
QJniObject testObject("NoSuchClass");
QVERIFY(!env.checkAndClearExceptions());
}
{
QTest::ignoreMessage(QtWarningMsg, invalidMethod);
QJniObject testObject = QJniObject::construct<TestClass>(u"NoSuchConstructor"_s);
QVERIFY(!env.checkAndClearExceptions());
}
}
void tst_QJniObject::implicitExceptionHandling_callMethod()
{
QJniEnvironment env;
const QRegularExpression invalidMethod("java.lang.NoSuchMethodError: .*");
const QRegularExpression throwingMethod("java.lang.Throwable: .*");
QJniObject testObject = QJniObject::construct<TestClass>();
QTest::ignoreMessage(QtWarningMsg, invalidMethod);
testObject.callMethod<void>("noSuchMethod1");
QVERIFY(!env.checkAndClearExceptions());
QTest::ignoreMessage(QtWarningMsg, invalidMethod);
testObject.callMethod<void>("noSuchMethod2", "()V");
QVERIFY(!env.checkAndClearExceptions());
QTest::ignoreMessage(QtWarningMsg, throwingMethod);
testObject.callMethod<void>("throwingMethod");
QVERIFY(!env.checkAndClearExceptions());
QTest::ignoreMessage(QtWarningMsg, invalidMethod);
(void)testObject.callObjectMethod<jstring>("noSuchMethod");
QVERIFY(!env.checkAndClearExceptions());
QTest::ignoreMessage(QtWarningMsg, invalidMethod);
testObject.callObjectMethod("noSuchMethod", "()V");
QVERIFY(!env.checkAndClearExceptions());
}
void tst_QJniObject::implicitExceptionHandling_callStaticMethod()
{
QJniEnvironment env;
const QRegularExpression invalidClass("java.lang.ClassNotFoundException: .*");
const QRegularExpression invalidMethod("java.lang.NoSuchMethodError: .*");
const QRegularExpression throwingMethod("java.lang.Throwable: .*");
const jclass classId = env.findClass<TestClass>();
QVERIFY(classId != nullptr);
QTest::ignoreMessage(QtWarningMsg, invalidMethod);
TestClass::callStaticMethod<void>("noSuchStaticMethod");
QVERIFY(!env.checkAndClearExceptions());
QTest::ignoreMessage(QtWarningMsg, invalidMethod);
QJniObject::callStaticMethod<void>(QtJniTypes::Traits<TestClass>::className(),
"noSuchStaticMethod2", "()V");
QVERIFY(!env.checkAndClearExceptions());
QTest::ignoreMessage(QtWarningMsg, invalidClass);
QJniObject::callStaticMethod<void>("noSuchClass", "noSuchStaticMethod2", "()V");
QVERIFY(!env.checkAndClearExceptions());
QTest::ignoreMessage(QtWarningMsg, throwingMethod);
jmethodID methodId = env.findStaticMethod<void>(classId, "staticThrowingMethod");
QVERIFY(methodId != nullptr);
QJniObject::callStaticMethod<void>(classId, methodId);
QVERIFY(!env.checkAndClearExceptions());
QTest::ignoreMessage(QtWarningMsg, invalidClass);
QJniObject::callStaticObjectMethod("noSuchClass", "noSuchStaticMethod", "()V");
QVERIFY(!env.checkAndClearExceptions());
QTest::ignoreMessage(QtWarningMsg, invalidMethod);
QJniObject::callStaticObjectMethod(classId, "noSuchStaticMethod", "()V");
QVERIFY(!env.checkAndClearExceptions());
}
void tst_QJniObject::implicitExceptionHandling_getField()
{
QJniEnvironment env;
const QRegularExpression invalidField("java.lang.NoSuchFieldError: .*");
QJniObject testObject = QJniObject::construct<TestClass>();
QTest::ignoreMessage(QtWarningMsg, invalidField);
(void)testObject.getField<int>("noSuchField");
QVERIFY(!env.checkAndClearExceptions());
QTest::ignoreMessage(QtWarningMsg, invalidField);
(void)testObject.getObjectField<jobject>("noSuchObjectField");
QVERIFY(!env.checkAndClearExceptions());
QTest::ignoreMessage(QtWarningMsg, invalidField);
(void)testObject.getObjectField("noSuchObjectField2", "Ljava/lang/Object;");
QVERIFY(!env.checkAndClearExceptions());
}
void tst_QJniObject::implicitExceptionHandling_setField()
{
QJniEnvironment env;
const QRegularExpression invalidField("java.lang.NoSuchFieldError: .*");
QJniObject testObject = QJniObject::construct<TestClass>();
QTest::ignoreMessage(QtWarningMsg, invalidField);
testObject.setField("noSuchField", 123);
QVERIFY(!env.checkAndClearExceptions());
QTest::ignoreMessage(QtWarningMsg, invalidField);
testObject.setField("BOOLEAN_VAR", "I", 123);
QVERIFY(!env.checkAndClearExceptions());
// make sure that code specifying deducible type explicitly still works
static_assert(std::is_same_v<decltype(testObject.setField<jboolean>("BOOLEAN_VAR", true)),
void>);
}
void tst_QJniObject::implicitExceptionHandling_getStaticField()
{
QJniEnvironment env;
const QRegularExpression invalidClass("java.lang.ClassNotFoundException: .*");
const QRegularExpression invalidField("java.lang.NoSuchFieldError: .*");
const jclass classId = env.findClass<TestClass>();
QVERIFY(classId != nullptr);
QTest::ignoreMessage(QtWarningMsg, invalidField);
(void)TestClass::getStaticField<int>("noSuchStaticField");
QVERIFY(!env.checkAndClearExceptions());
QTest::ignoreMessage(QtWarningMsg, invalidField);
(void)QJniObject::getStaticField<int>(classId, "noSuchStaticField");
QVERIFY(!env.checkAndClearExceptions());
QTest::ignoreMessage(QtWarningMsg, invalidClass);
(void)QJniObject::getStaticObjectField("noSuchClass", "noSuchStaticField", "I");
QVERIFY(!env.checkAndClearExceptions());
QTest::ignoreMessage(QtWarningMsg, invalidField);
(void)QJniObject::getStaticObjectField(classId, "S_BOOLEAN_VAR", "I");
QVERIFY(!env.checkAndClearExceptions());
}
void tst_QJniObject::implicitExceptionHandling_setStaticField()
{
QJniEnvironment env;
const QRegularExpression invalidClass("java.lang.ClassNotFoundException: .*");
const QRegularExpression invalidField("java.lang.NoSuchFieldError: .*");
const jclass classId = env.findClass<TestClass>();
QVERIFY(classId != nullptr);
QTest::ignoreMessage(QtWarningMsg, invalidField);
TestClass::setStaticField("noSuchStaticField", 123);
QVERIFY(!env.checkAndClearExceptions());
QTest::ignoreMessage(QtWarningMsg, invalidField);
QJniObject::setStaticField(classId, "noSuchStaticField", 123);
QVERIFY(!env.checkAndClearExceptions());
QTest::ignoreMessage(QtWarningMsg, invalidClass);
QJniObject::setStaticField("noSuchClass", "noSuchStaticField", 123);
QVERIFY(!env.checkAndClearExceptions());
QTest::ignoreMessage(QtWarningMsg, invalidField);
QJniObject::setStaticField(QtJniTypes::Traits<TestClass>::className(),
"S_BOOLEAN_VAR", "I", 123);
QVERIFY(!env.checkAndClearExceptions());
// make sure that code specifying deducible type explicitly still works
static_assert(std::is_same_v<decltype(TestClass::setStaticField<jboolean>("S_BOOLEAN_VAR", true)),
void>);
}
void tst_QJniObject::constructWithException()
{
#ifndef __cpp_lib_expected
QSKIP("std::expected not available.");
#else
qInfo() << "Testing explicit exception handling with std::expected" << __cpp_lib_expected;
const QRegularExpression invalidClass("java.lang.ClassNotFoundException: .*");
{
QTest::ignoreMessage(QtWarningMsg, invalidClass);
QJniObject invalid = QJniObject::construct<QtJniTypes::NoSuchClass>();
}
QVERIFY(!QJniEnvironment().checkAndClearExceptions());
{
// can only handle exceptions when using the named constructor
auto result = QJniObject::construct<QJniReturnValue<TestClass>>();
QVERIFY(result);
result = QJniObject::construct<QJniReturnValue<TestClass>>(u"123"_s);
QVERIFY(!result);
QVERIFY(result.error());
}
QVERIFY(!QJniEnvironment().checkAndClearExceptions());
{
const auto result = QJniObject::construct<QJniReturnValue<QtJniTypes::NoSuchClass>>();
QVERIFY(!result);
QVERIFY(result.error());
}
QVERIFY(!QJniEnvironment().checkAndClearExceptions());
{
// no way to prevent implicit exception here
QTest::ignoreMessage(QtWarningMsg, invalidClass);
QtJniTypes::NoSuchClass noSuchClass;
QVERIFY(!noSuchClass.isValid());
}
QVERIFY(!QJniEnvironment().checkAndClearExceptions());
#endif
}
void tst_QJniObject::callMethodWithException()
{
#ifndef __cpp_lib_expected
QSKIP("std::expected not available.");
#else
TestClass testObject;
{
auto result = testObject.callMethod<QJniReturnValue<void>>("voidMethod");
QVERIFY(result);
result = testObject.callMethod<QJniReturnValue<void>>("voidMethod", 123);
QVERIFY(!result);
QVERIFY(result.error());
}
{
auto result = testObject.callMethod<QJniReturnValue<jint>>("intMethod");
QVERIFY(result);
QCOMPARE(result.value(), A_INT_VALUE);
result = testObject.callMethod<QJniReturnValue<jint>>("intMethod", 123);
QVERIFY(!result);
QVERIFY(result.error());
QCOMPARE(result.value_or(456), 456);
}
{
auto result = testObject.callMethod<QJniReturnValue<jstring>>("stringMethod");
QVERIFY(result);
QVERIFY(result.value());
result = testObject.callMethod<QJniReturnValue<jstring>>("stringMethod", 123);
QVERIFY(!result);
QVERIFY(result.error());
}
{
auto result = testObject.callMethod<QJniReturnValue<QString>>("stringMethod");
QVERIFY(result);
QVERIFY(!result.value().isEmpty());
result = testObject.callMethod<QJniReturnValue<QString>>("stringMethod", 123);
QVERIFY(!result);
QVERIFY(result.error());
QCOMPARE(result.value_or(u"Default"_s), u"Default"_s);
}
{
QJniArray<jboolean> newArray(QList<jboolean>{true, false, false});
auto result = testObject.callMethod<QJniReturnValue<QJniArray<jboolean>>>("reverseBooleanArray", newArray);
// this shorthand cannot work with e.g. std::expected
// result = testObject.callMethod<QJniReturnValue<jboolean[]>>("reverseBooleanArray", newArray);
QVERIFY(result);
QCOMPARE(result.value().toContainer(), (QList<jboolean>{false, false, true}));
result = testObject.callMethod<QJniReturnValue<QJniArray<jboolean>>>("reverseBooleanArray", 123);
QVERIFY(!result);
QVERIFY(result.error());
}
// throwing method - QJniObject cleans the exception and prints qWarning
{
QTest::ignoreMessage(QtWarningMsg, QRegularExpression(u"java.lang.Throwable: "_s + A_STRING_OBJECT() + u".*"_s));
testObject.callMethod<void>("throwingMethod");
auto result = testObject.callMethod<QJniReturnValue<void>>("throwingMethod");
QVERIFY(!result);
const QStringList stackTrace = QJniEnvironment::stackTrace(result.error());
QCOMPARE_GE(stackTrace.size(), 1);
QCOMPARE(stackTrace.at(0), u"java.lang.Throwable: "_s + A_STRING_OBJECT());
}
#endif
}
void tst_QJniObject::callMethodWithMonadic()
{
#ifndef __cpp_lib_expected
QSKIP("std::expected not available.");
#elif !EXPECTED_HAS_MONADIC
QSKIP("Used version of std::expected does not have monadic functions");
#else
enum Monadic {
AndThen,
OrElse,
Transform,
};
TestClass testObject;
{
QList<Monadic> flow;
const auto result = testObject.callMethod<QJniReturnValue<void>>("voidMethod")
.and_then([&flow]{
flow << AndThen;
return QJniReturnValue<void>();
})
.or_else([&flow](jthrowable error){
if (error)
flow << OrElse;
else
qWarning("Invalid call to or_else monadic");
return QJniReturnValue<void>();
})
.transform([&flow](){
flow << Transform;
return 42;
});
QCOMPARE(flow, (QList<Monadic>{AndThen, Transform}));
QVERIFY(result);
QCOMPARE(*result, 42);
}
{
QList<Monadic> flow;
const auto result = testObject.callMethod<QJniReturnValue<void>>("voidMethod", 123)
.and_then([&flow]{
flow << AndThen;
return QJniReturnValue<void>();
})
.or_else([&flow](jthrowable error){
if (error)
flow << OrElse;
else
qWarning("Invalid call to or_else monadic");
return QJniReturnValue<void>(typename QJniReturnValue<void>::unexpected_type(error));
})
.transform([&flow](){
flow << Transform;
return 42;
});
QCOMPARE(flow, (QList<Monadic>{OrElse}));
QVERIFY(!result);
}
{
QList<Monadic> flow;
const auto result = testObject.callMethod<QJniReturnValue<jobject>>("objectMethod")
.and_then([&flow](auto &&obj){
flow << AndThen;
return QJniReturnValue<jobject>(obj);
})
.or_else([&flow](jthrowable error){
if (error)
flow << OrElse;
else
qWarning("Invalid call to or_else monadic");
return QJniReturnValue<jobject>(typename QJniReturnValue<void>::unexpected_type(error));
})
.transform([&flow](auto &&obj){
flow << Transform;
return QJniObject(obj).template getField<int>("INT_FIELD");
})
.and_then([&flow](auto value){
flow << AndThen;
return QJniReturnValue<int>(value * 2);
});
QCOMPARE(flow, (QList<Monadic>{AndThen, Transform, AndThen}));
QVERIFY(result);
QCOMPARE(*result, 246);
}
{
QList<Monadic> flow;
const auto result = testObject.callMethod<QJniReturnValue<jobject>>("objectMethod", 123)
.and_then([&flow](const QJniObject &obj){
flow << AndThen;
return QJniReturnValue<jobject>(obj.object());
})
.or_else([&flow](jthrowable error){
if (error)
flow << OrElse;
else
qWarning("Invalid call to or_else monadic");
return QJniReturnValue<jobject>(typename QJniReturnValue<jobject>::unexpected_type(error));
})
.transform([&flow](const QJniObject &obj){
flow << Transform;
return obj.getField<int>("INT_FIELD");
});
QCOMPARE(flow, (QList<Monadic>{OrElse}));
QVERIFY(!result);
}
#endif
}
void tst_QJniObject::callMethodWithTryCatch()
{
#ifndef __cpp_lib_expected
QSKIP("std::expected not available.");
#else
TestClass testObject;
const QRegularExpression invalidMethod("java.lang.NoSuchMethodError: .*");
QTest::ignoreMessage(QtWarningMsg, invalidMethod);
try {
const auto result = testObject.callMethod<QJniReturnValue<QJniObject>>("objectMethod", 123);
result.value().getField<int>("INT_FIELD");
}
catch (BadAccessException &e) {
qWarning().noquote() << QJniEnvironment::stackTrace(e.error()).join('\n');
}
#endif
}
void tst_QJniObject::callStaticMethodWithException()
{
#ifndef __cpp_lib_expected
QSKIP("std::expected not available.");
#else
{
auto result = TestClass::callStaticMethod<QJniReturnValue<int>>("staticIntMethod");
QVERIFY(result);
QCOMPARE(*result, A_INT_VALUE);
result = TestClass::callStaticMethod<QJniReturnValue<int>>("staticIntMethod", 123);
QVERIFY(!result && result.error());
}
{
auto result = TestClass::callStaticMethod<QJniReturnValue<QString>>("staticStringMethod");
QVERIFY(result);
QCOMPARE(*result, A_STRING_OBJECT());
result = TestClass::callStaticMethod<QJniReturnValue<QString>>("staticStringMethod", 123);
QVERIFY(!result && result.error());
}
// throwing method
{
const auto result = TestClass::callStaticMethod<QJniReturnValue<void>>("staticThrowingMethod");
QVERIFY(!result);
QStringList stackTrace = QJniEnvironment::stackTrace(result.error());
QCOMPARE_GE(stackTrace.size(), 1);
QCOMPARE(stackTrace.at(0), u"java.lang.Throwable: "_s + A_STRING_OBJECT());
}
#endif
}
void tst_QJniObject::getFieldWithException()
{
#ifndef __cpp_lib_expected
QSKIP("std::expected not available.");
#else
TestClass testObject;
{
auto result = testObject.getField<QJniReturnValue<jboolean>>("BOOL_FIELD");
QVERIFY(result);
result = testObject.getField<QJniReturnValue<jboolean>>("INVALID_BOOL");
QVERIFY(!result && result.error());
}
{
auto result = testObject.getField<QJniReturnValue<QString>>("STRING_OBJECT_VAR");
QVERIFY(result);
result = testObject.getField<QJniReturnValue<QString>>("INVALID_STRING");
QVERIFY(!result && result.error());
}
#endif
}
void tst_QJniObject::setFieldWithException()
{
#ifndef __cpp_lib_expected
QSKIP("std::expected not available.");
#else
TestClass testObject;
{
auto result = testObject.setField<QJniReturnValue<jboolean>>("BOOL_FIELD", true);
QVERIFY(result);
result = testObject.setField<QJniReturnValue<jboolean>>("SET_INVALID_BOOL", true);
QVERIFY(!result);
QVERIFY(result.error());
}
{
auto result = testObject.setField<QJniReturnValue<QString>>("STRING_OBJECT_VAR", u"test"_s);
QVERIFY(result);
result = testObject.setField<QJniReturnValue<QString>>("SET_INVALID_STRING", u"test"_s);
QVERIFY(!result);
QVERIFY(result.error());
}
#endif
}
void tst_QJniObject::getStaticFieldWithException()
{
#ifndef __cpp_lib_expected
QSKIP("std::expected not available.");
#else
{
auto result = TestClass::getStaticField<QJniReturnValue<jshort>>("S_SHORT_VAR");
QVERIFY(result);
result = TestClass::getStaticField<QJniReturnValue<jshort>>("S_INVALID_SHORT");
QVERIFY(!result);
QVERIFY(result.error());
}
{
auto result = TestClass::getStaticField<QJniReturnValue<QString>>("S_STRING_OBJECT_VAR");
QVERIFY(result);
result = TestClass::getStaticField<QJniReturnValue<QString>>("S_INVALID_STRING");
QVERIFY(!result);
QVERIFY(result.error());
}
#endif
}
void tst_QJniObject::setStaticFieldWithException()
{
#ifndef __cpp_lib_expected
QSKIP("std::expected not available.");
#else
{
auto result = TestClass::setStaticField<QJniReturnValue<jboolean>>("S_BOOLEAN_VAR", true);
QVERIFY(result);
result = TestClass::setStaticField<QJniReturnValue<jboolean>>("SET_S_INVALID_BOOL", true);
QVERIFY(!result);
QVERIFY(result.error());
}
{
auto result = TestClass::setStaticField<QJniReturnValue<QString>>("S_STRING_OBJECT_VAR", u"test"_s);
QVERIFY(result);
result = TestClass::setStaticField<QJniReturnValue<QString>>("SET_S_INVALID_STRING", u"test"_s);
QVERIFY(!result);
QVERIFY(result.error());
}
#endif
}
static constexpr const char taskClassName[] =
"org/qtproject/qt/android/testdatapackage/Task";
static constexpr const char tasksClassName[] =
"org/qtproject/qt/android/testdatapackage/Tasks";
static constexpr const char fieldItemClassName[] =
"org/qtproject/qt/android/testdatapackage/FieldItem";
void tst_QJniObject::cachedMethodIdKeyCollision()
{
// Task/send and Tasks/end concatenate to the same string, so the two
// lookups shared a method ID cache entry while the key had no separator.
QJniObject task(taskClassName);
QVERIFY(task.isValid());
QJniObject tasks(tasksClassName);
QVERIFY(tasks.isValid());
QCOMPARE(task.callMethod<jint>("send", 1), 11);
QCOMPARE(tasks.callMethod<jint>("end", 1), 21);
// The reverse order populates the shared entry from the other side.
QCOMPARE(tasks.callMethod<jint>("end", 2), 22);
QCOMPARE(task.callMethod<jint>("send", 2), 12);
}
void tst_QJniObject::cachedFieldIdKeyIgnoresKind()
{
// Both ids resolve through FieldItem and shared a cache entry while the
// key left the kind out. Only the setters go through the field cache.
QJniObject item(fieldItemClassName);
QVERIFY(item.isValid());
QCOMPARE(item.getField<jint>("id"), 200);
item.setField<jint>("id", 7);
QJniObject::setStaticField<jint>(fieldItemClassName, "id", 9);
QCOMPARE(item.getField<jint>("id"), 7);
QCOMPARE(QJniObject::getStaticField<jint>(fieldItemClassName, "id"), 9);
}
QTEST_MAIN(tst_QJniObject)
QT_END_NAMESPACE
#include "tst_qjniobject.moc"