JNI: establish API symmetry across QJniObject and QJniArray

We implicitly support QString, QStringList, and QByteArray, as well as
C++ types that are equivalent to JNI primitive types (such as bool for
jboolean, or int for jint) in QJniObject and some QJniArray APIs. Make
this symmetrical for QJniArray::to/fromContainer.

Add more compile- and run-time time test coverage.

Change-Id: I8cc84e6181a93f889282d2d3f0a05207416c4dbe
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
(cherry picked from commit d9dd8c4986373789b8bd250d0c2b36b660fe210f)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
Volker Hilsheimer 2024-06-06 11:27:33 +02:00
parent eedbcad7a4
commit 708b84be11
5 changed files with 135 additions and 46 deletions

View File

@ -132,42 +132,43 @@ public:
"QJniArray::fromContainer", "Container is too large for a Java array");
using ElementType = typename std::remove_reference_t<Container>::value_type;
if constexpr (std::disjunction_v<std::is_same<ElementType, jobject>,
std::is_same<ElementType, QJniObject>,
std::is_same<ElementType, QString>,
std::is_base_of<QtJniTypes::JObjectBase, ElementType>
>) {
if constexpr (std::is_base_of_v<std::remove_pointer_t<jobject>,
std::remove_pointer_t<ElementType>>) {
return makeObjectArray(std::forward<Container>(container));
} else if constexpr (std::is_same_v<ElementType, jfloat>) {
} else if constexpr (std::disjunction_v<std::is_same<ElementType, QJniObject>,
std::is_same<ElementType, QString>,
std::is_base_of<QtJniTypes::JObjectBase, ElementType>
>) {
return QJniArray<ElementType>(makeObjectArray(std::forward<Container>(container)));
} else if constexpr (QtJniTypes::sameTypeForJni<ElementType, jfloat>) {
return makeArray<jfloat>(std::forward<Container>(container), &JNIEnv::NewFloatArray,
&JNIEnv::SetFloatArrayRegion);
} else if constexpr (std::is_same_v<ElementType, jdouble>) {
} else if constexpr (QtJniTypes::sameTypeForJni<ElementType, jdouble>) {
return makeArray<jdouble>(std::forward<Container>(container), &JNIEnv::NewDoubleArray,
&JNIEnv::SetDoubleArrayRegion);
} else if constexpr (std::disjunction_v<std::is_same<ElementType, jboolean>,
std::is_same<ElementType, bool>>) {
} else if constexpr (QtJniTypes::sameTypeForJni<ElementType, jboolean>) {
return makeArray<jboolean>(std::forward<Container>(container), &JNIEnv::NewBooleanArray,
&JNIEnv::SetBooleanArrayRegion);
} else if constexpr (std::disjunction_v<std::is_same<ElementType, jbyte>,
std::is_same<ElementType, char>>) {
} else if constexpr (QtJniTypes::sameTypeForJni<ElementType, jbyte>
|| std::is_same_v<ElementType, char>) {
return makeArray<jbyte>(std::forward<Container>(container), &JNIEnv::NewByteArray,
&JNIEnv::SetByteArrayRegion);
} else if constexpr (std::disjunction_v<std::is_same<ElementType, jchar>,
std::is_same<ElementType, QChar>>) {
return makeArray<jchar>(std::forward<Container>(container), &JNIEnv::NewCharArray,
&JNIEnv::SetCharArrayRegion);
} else if constexpr (std::is_same_v<ElementType, jshort>
|| sizeof(ElementType) == sizeof(jshort)) {
} else if constexpr (QtJniTypes::sameTypeForJni<ElementType, jshort>) {
return makeArray<jshort>(std::forward<Container>(container), &JNIEnv::NewShortArray,
&JNIEnv::SetShortArrayRegion);
} else if constexpr (std::is_same_v<ElementType, jint>
|| sizeof(ElementType) == sizeof(jint)) {
} else if constexpr (QtJniTypes::sameTypeForJni<ElementType, jint>) {
return makeArray<jint>(std::forward<Container>(container), &JNIEnv::NewIntArray,
&JNIEnv::SetIntArrayRegion);
} else if constexpr (std::is_same_v<ElementType, jlong>
|| sizeof(ElementType) == sizeof(jlong)) {
} else if constexpr (QtJniTypes::sameTypeForJni<ElementType, jlong>) {
return makeArray<jlong>(std::forward<Container>(container), &JNIEnv::NewLongArray,
&JNIEnv::SetLongArrayRegion);
} else {
static_assert(QtPrivate::type_dependent_false<ElementType>(),
"Don't know how to make QJniArray for this element type");
}
}
@ -205,6 +206,8 @@ class QJniArray : public QJniArrayBase
template <typename E> struct ToContainerHelper { using type = QList<E>; };
template <> struct ToContainerHelper<jstring> { using type = QStringList; };
template <> struct ToContainerHelper<jbyte> { using type = QByteArray; };
template <> struct ToContainerHelper<char> { using type = QByteArray; };
template <typename E>
using ToContainerType = typename ToContainerHelper<E>::type;
@ -255,21 +258,23 @@ public:
{
if constexpr (std::is_convertible_v<jobject, T>)
return object<jobjectArray>();
else if constexpr (std::is_same_v<T, jbyte>)
else if constexpr (std::is_same_v<T, QString>)
return object<jobjectArray>();
else if constexpr (QtJniTypes::sameTypeForJni<T, jbyte>)
return object<jbyteArray>();
else if constexpr (std::is_same_v<T, jchar>)
else if constexpr (QtJniTypes::sameTypeForJni<T, jchar>)
return object<jcharArray>();
else if constexpr (std::is_same_v<T, jboolean>)
else if constexpr (QtJniTypes::sameTypeForJni<T, jboolean>)
return object<jbooleanArray>();
else if constexpr (std::is_same_v<T, jshort>)
else if constexpr (QtJniTypes::sameTypeForJni<T, jshort>)
return object<jshortArray>();
else if constexpr (std::is_same_v<T, jint>)
else if constexpr (QtJniTypes::sameTypeForJni<T, jint>)
return object<jintArray>();
else if constexpr (std::is_same_v<T, jlong>)
else if constexpr (QtJniTypes::sameTypeForJni<T, jlong>)
return object<jlongArray>();
else if constexpr (std::is_same_v<T, jfloat>)
else if constexpr (QtJniTypes::sameTypeForJni<T, jfloat>)
return object<jfloatArray>();
else if constexpr (std::is_same_v<T, jdouble>)
else if constexpr (QtJniTypes::sameTypeForJni<T, jdouble>)
return object<jdoubleArray>();
else
return object<jarray>();
@ -299,26 +304,33 @@ public:
return T::fromLocalRef(element);
else
return T{element};
} else if constexpr (std::is_same_v<QString, T>) {
jstring string = static_cast<jstring>(env->GetObjectArrayElement(arrayObject(), i));
const auto length = env->GetStringLength(string);
QString res(length, Qt::Uninitialized);
env->GetStringRegion(string, 0, length, reinterpret_cast<jchar *>(res.data()));
env->DeleteLocalRef(string);
return res;
} else if constexpr (std::is_base_of_v<std::remove_pointer_t<jobject>, std::remove_pointer_t<T>>) {
// jstring, jclass etc
return static_cast<T>(env->GetObjectArrayElement(object<jobjectArray>(), i));
} else {
T res = {};
if constexpr (std::is_same_v<T, jbyte>)
if constexpr (QtJniTypes::sameTypeForJni<T, jbyte>)
env->GetByteArrayRegion(object<jbyteArray>(), i, 1, &res);
else if constexpr (std::is_same_v<T, jchar>)
else if constexpr (QtJniTypes::sameTypeForJni<T, jchar>)
env->GetCharArrayRegion(object<jcharArray>(), i, 1, &res);
else if constexpr (std::is_same_v<T, jboolean>)
else if constexpr (QtJniTypes::sameTypeForJni<T, jboolean>)
env->GetBooleanArrayRegion(object<jbooleanArray>(), i, 1, &res);
else if constexpr (std::is_same_v<T, jshort>)
else if constexpr (QtJniTypes::sameTypeForJni<T, jshort>)
env->GetShortArrayRegion(object<jshortArray>(), i, 1, &res);
else if constexpr (std::is_same_v<T, jint>)
else if constexpr (QtJniTypes::sameTypeForJni<T, jint>)
env->GetIntArrayRegion(object<jintArray>(), i, 1, &res);
else if constexpr (std::is_same_v<T, jlong>)
else if constexpr (QtJniTypes::sameTypeForJni<T, jlong>)
env->GetLongArrayRegion(object<jlongArray>(), i, 1, &res);
else if constexpr (std::is_same_v<T, jfloat>)
else if constexpr (QtJniTypes::sameTypeForJni<T, jfloat>)
env->GetFloatArrayRegion(object<jfloatArray>(), i, 1, &res);
else if constexpr (std::is_same_v<T, jdouble>)
else if constexpr (QtJniTypes::sameTypeForJni<T, jdouble>)
env->GetDoubleArrayRegion(object<jdoubleArray>(), i, 1, &res);
return res;
}
@ -336,36 +348,40 @@ public:
container.reserve(sz);
if constexpr (std::is_same_v<typename ContainerType::value_type, QString>) {
for (auto element : *this)
container.emplace_back(QJniObject(element).toString());
for (auto element : *this) {
if constexpr (std::is_same_v<decltype(element), QString>)
container.emplace_back(element);
else
container.emplace_back(QJniObject(element).toString());
}
} else if constexpr (std::is_base_of_v<std::remove_pointer_t<jobject>, std::remove_pointer_t<T>>) {
for (auto element : *this)
container.emplace_back(element);
} else if constexpr (QJniArrayBase::isContiguousContainer<ContainerType>) {
container.resize(sz);
if constexpr (std::is_same_v<T, jbyte>) {
if constexpr (QtJniTypes::sameTypeForJni<T, jbyte>) {
env->GetByteArrayRegion(object<jbyteArray>(),
0, sz,
reinterpret_cast<jbyte *>(container.data()));
} else if constexpr (std::is_same_v<T, jchar>) {
} else if constexpr (QtJniTypes::sameTypeForJni<T, jchar>) {
env->GetCharArrayRegion(object<jcharArray>(),
0, sz, container.data());
} else if constexpr (std::is_same_v<T, jboolean>) {
} else if constexpr (QtJniTypes::sameTypeForJni<T, jboolean>) {
env->GetBooleanArrayRegion(object<jbooleanArray>(),
0, sz, container.data());
} else if constexpr (std::is_same_v<T, jshort>) {
} else if constexpr (QtJniTypes::sameTypeForJni<T, jshort>) {
env->GetShortArrayRegion(object<jshortArray>(),
0, sz, container.data());
} else if constexpr (std::is_same_v<T, jint>) {
} else if constexpr (QtJniTypes::sameTypeForJni<T, jint>) {
env->GetIntArrayRegion(object<jintArray>(),
0, sz, container.data());
} else if constexpr (std::is_same_v<T, jlong>) {
} else if constexpr (QtJniTypes::sameTypeForJni<T, jlong>) {
env->GetLongArrayRegion(object<jlongArray>(),
0, sz, container.data());
} else if constexpr (std::is_same_v<T, jfloat>) {
} else if constexpr (QtJniTypes::sameTypeForJni<T, jfloat>) {
env->GetFloatArrayRegion(object<jfloatArray>(),
0, sz, container.data());
} else if constexpr (std::is_same_v<T, jdouble>) {
} else if constexpr (QtJniTypes::sameTypeForJni<T, jdouble>) {
env->GetDoubleArrayRegion(object<jdoubleArray>(),
0, sz, container.data());
} else {

View File

@ -165,8 +165,14 @@
\li QJniArray<jbyte>
\li QByteArray
\row
\li QJniArray<char>
\li QByteArray
\row
\li QJniArray<jstring>
\li QStringList
\row
\li QJniArray<QString>
\li QStringList
\endtable
//! [type-mapping]

View File

@ -76,21 +76,61 @@ VERIFY_RETURN_FOR_TYPE(QList<jdouble>, QList<jdouble>);
VERIFY_RETURN_FOR_TYPE(QList<double>, QList<double>);
VERIFY_RETURN_FOR_TYPE(QString, QString);
VERIFY_RETURN_FOR_TYPE(QJniArray<QString>, QJniArray<QString>);
VERIFY_RETURN_FOR_TYPE(List, List);
VERIFY_RETURN_FOR_TYPE(List[], QJniArray<List>);
VERIFY_RETURN_FOR_TYPE(QJniArray<List>, QJniArray<List>);
#define VERIFY_CONTAINER_FOR_TYPE(In, Out) \
static_assert(std::is_same_v<decltype(std::declval<In>().toContainer()), Out>)
static_assert(std::is_same_v<decltype(std::declval<In>().toContainer()), Out>);
#define VERIFY_ARRAY_FOR_CONTAINER(In, Out) \
static_assert(std::is_same_v<decltype(QJniArrayBase::fromContainer(std::declval<In>())), Out>);
// primitive types, both JNI and C++ equivalent types
VERIFY_CONTAINER_FOR_TYPE(QJniArray<jchar>, QList<jchar>);
VERIFY_ARRAY_FOR_CONTAINER(QList<jchar>, QJniArray<jchar>);
VERIFY_CONTAINER_FOR_TYPE(QJniArray<jint>, QList<jint>);
VERIFY_ARRAY_FOR_CONTAINER(QList<jint>, QJniArray<jint>);
VERIFY_CONTAINER_FOR_TYPE(QJniArray<int>, QList<int>);
VERIFY_ARRAY_FOR_CONTAINER(QList<int>, QJniArray<int>);
VERIFY_CONTAINER_FOR_TYPE(QJniArray<jfloat>, QList<jfloat>);
VERIFY_CONTAINER_FOR_TYPE(QJniArray<jbyte>, QByteArray);
VERIFY_CONTAINER_FOR_TYPE(QJniArray<jstring>, QStringList);
VERIFY_ARRAY_FOR_CONTAINER(QList<jfloat>, QJniArray<jfloat>);
VERIFY_CONTAINER_FOR_TYPE(QJniArray<float>, QList<float>);
VERIFY_ARRAY_FOR_CONTAINER(QList<float>, QJniArray<float>);
VERIFY_CONTAINER_FOR_TYPE(QJniArray<jdouble>, QList<jdouble>);
VERIFY_ARRAY_FOR_CONTAINER(QList<jdouble>, QJniArray<jdouble>);
VERIFY_CONTAINER_FOR_TYPE(QJniArray<double>, QList<double>);
VERIFY_ARRAY_FOR_CONTAINER(QList<double>, QJniArray<double>);
// jobject and derivatives
VERIFY_CONTAINER_FOR_TYPE(QJniArray<jobject>, QList<jobject>);
VERIFY_ARRAY_FOR_CONTAINER(QList<jobject>, QJniArray<jobject>);
VERIFY_CONTAINER_FOR_TYPE(QJniArray<jclass>, QList<jclass>);
VERIFY_ARRAY_FOR_CONTAINER(QList<jclass>, QJniArray<jclass>);
VERIFY_CONTAINER_FOR_TYPE(QJniArray<jthrowable>, QList<jthrowable>);
VERIFY_ARRAY_FOR_CONTAINER(QList<jthrowable>, QJniArray<jthrowable>);
// QJniObject-ish classes
VERIFY_CONTAINER_FOR_TYPE(QJniArray<QJniObject>, QList<QJniObject>);
VERIFY_ARRAY_FOR_CONTAINER(QList<QJniObject>, QJniArray<QJniObject>);
VERIFY_CONTAINER_FOR_TYPE(QJniArray<List>, QList<List>);
VERIFY_ARRAY_FOR_CONTAINER(QList<List>, QJniArray<List>);
// Special case: jbyte, (u)char, and QByteArray
VERIFY_CONTAINER_FOR_TYPE(QJniArray<jbyte>, QByteArray);
VERIFY_ARRAY_FOR_CONTAINER(QByteArray, QJniArray<jbyte>);
VERIFY_ARRAY_FOR_CONTAINER(QList<jbyte>, QJniArray<jbyte>);
VERIFY_CONTAINER_FOR_TYPE(QJniArray<char>, QByteArray);
VERIFY_ARRAY_FOR_CONTAINER(QList<char>, QJniArray<jbyte>);
VERIFY_CONTAINER_FOR_TYPE(QJniArray<uchar>, QList<uchar>);
// Special case: jstring, QString, and QStringList
VERIFY_CONTAINER_FOR_TYPE(QJniArray<jstring>, QStringList);
VERIFY_ARRAY_FOR_CONTAINER(QStringList, QJniArray<QString>);
VERIFY_CONTAINER_FOR_TYPE(QJniArray<QString>, QStringList);
VERIFY_ARRAY_FOR_CONTAINER(QList<jstring>, QJniArray<jstring>);
void tst_QJniArray::construct()
{
@ -101,6 +141,8 @@ void tst_QJniArray::construct()
strings << QString::number(i);
QJniArray<QString> list(strings);
QCOMPARE(list.size(), 10000);
QCOMPARE(list.at(500), QString::number(500));
QCOMPARE(list.toContainer(), strings);
}
{
QJniArray bytes = QJniArrayBase::fromContainer(QByteArray("abc"));

View File

@ -152,6 +152,11 @@ public class QtJniObjectTestClass
public Object[] reverseObjectArray(Object[] array)
{ return staticReverseObjectArray(array); }
// --------------------------------------------------------------------------------------------
public static String[] staticStringArrayMethod()
{ String[] array = { "First", "Second", "Third" }; return array; }
public String[] stringArrayMethod() { return staticStringArrayMethod(); }
// --------------------------------------------------------------------------------------------
public static boolean[] staticBooleanArrayMethod()
{ boolean[] array = { true, true, true }; return array; }

View File

@ -1563,6 +1563,26 @@ void tst_QJniObject::templateApiCheck()
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,