JNI: add QJniArray class for easier working with arrays

Implements an iterator API and other standard member access functions
for sequential containers so that we can use ranged-for over an object
that is a jarray. Provides read-only access to individual elements
(which is mostly relevant for arrays of objects), or the entire data()
as a contiguous memory block (which is useful for arrays of primitive
types).

QJniObject call functions can return QJniArray<T> when the return type
is either explicitly QJniArray<T> or T[], or their Qt equivalent (e.g.
a jbyteArray can be taken or returned as a QByteArray). If the return
type is a jarray type, then a QJniObject is returned as before.

Arrays can be created from a Qt container through a constructor or the
generic fromData named constructor in the QJniArrayBase class, which
implements the generic logic.

Not documented as public API yet.

Added a compile-time test to verify that types are mapped correctly.
The function test coverage is added to the QJniObject auto-test, as
that already provides the Java test class with functions taking and
returning arrays of different types.

Change-Id: I0750fc4f4cce7314df3b10e122eafbcfd68297b6
Reviewed-by: Assam Boudjelthia <assam.boudjelthia@qt.io>
bb10
Volker Hilsheimer 2023-09-14 21:15:20 +02:00
parent 53f9768e64
commit 80d4d55e25
9 changed files with 770 additions and 25 deletions

View File

@ -1000,6 +1000,7 @@ qt_internal_extend_target(Core CONDITION ANDROID
SOURCES
io/qstandardpaths_android.cpp
io/qstorageinfo_linux.cpp io/qstorageinfo_linux_p.h
kernel/qjniarray.h
kernel/qjnitypes.h kernel/qjnitypes_impl.h
kernel/qjnienvironment.cpp kernel/qjnienvironment.h
kernel/qjniobject.cpp kernel/qjniobject.h

View File

@ -0,0 +1,356 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QJNIARRAY_H
#define QJNIARRAY_H
#include <QtCore/qlist.h>
#if defined(Q_QDOC) || defined(Q_OS_ANDROID)
#include <QtCore/qbytearray.h>
#include <QtCore/qjniobject.h>
#include <utility>
QT_BEGIN_NAMESPACE
template <typename T> class QJniArray;
template <typename T>
struct QJniArrayIterator
{
QJniArrayIterator(const QJniArrayIterator &other) = default;
QJniArrayIterator(QJniArrayIterator &&other) noexcept = default;
QJniArrayIterator &operator=(const QJniArrayIterator &other) = default;
QJniArrayIterator &operator=(QJniArrayIterator &&other) noexcept = default;
friend bool operator==(const QJniArrayIterator<T> &lhs, const QJniArrayIterator<T> &rhs) noexcept
{
return lhs.m_array == rhs.m_array && lhs.m_index == rhs.m_index;
}
friend bool operator!=(const QJniArrayIterator<T> &lhs, const QJniArrayIterator<T> &rhs) noexcept
{
return !(lhs == rhs);
}
const T operator*() const
{
return m_array->at(m_index);
}
friend QJniArrayIterator<T> &operator++(QJniArrayIterator<T> &that) noexcept
{
++that.m_index;
return that;
}
void swap(QJniArrayIterator<T> &other) noexcept
{
std::swap(m_index, other.m_index);
std::swap(m_array, other.m_array);
}
private:
friend class QJniArray<T>;
qsizetype m_index = 0;
const QJniArray<T> *m_array = nullptr;
QJniArrayIterator() = delete;
QJniArrayIterator(qsizetype index, const QJniArray<T> *array)
: m_index(index), m_array(array)
{}
};
class QJniArrayBase : public QJniObject
{
// for SFINAE'ing out the fromContainer named constructor
template <typename Container, typename = void> struct CanConvertHelper : std::false_type {};
template <typename Container>
struct CanConvertHelper<Container, std::void_t<decltype(std::declval<Container>().data()),
decltype(std::declval<Container>().size())
>
> : std::true_type {};
public:
QJniArrayBase(jarray array)
: QJniObject(static_cast<jobject>(array))
{
static_assert(sizeof(QJniArrayBase) == sizeof(QJniObject),
"QJniArrayBase must have the same size as QJniObject!");
}
QJniArrayBase(const QJniObject &object)
: QJniObject(object)
{}
QJniArrayBase(QJniObject &&object) noexcept
: QJniObject(std::move(object))
{}
qsizetype size() const
{
if (jarray array = object<jarray>())
return QJniEnvironment()->GetArrayLength(array);
return 0;
}
template <typename Container>
static constexpr bool CanConvert = CanConvertHelper<Container>::value;
template <typename Container
, std::enable_if_t<CanConvert<Container>, bool> = true
>
static auto fromContainer(Container &&container)
{
using ElementType = typename Container::value_type;
if constexpr (std::disjunction_v<std::is_same<ElementType, jobject>,
std::is_same<ElementType, QJniObject>>) {
return makeObjectArray(std::forward<Container>(container));
} else if constexpr (std::is_same_v<ElementType, jfloat>) {
return makeArray<jfloat>(std::forward<Container>(container), &JNIEnv::NewFloatArray,
&JNIEnv::SetFloatArrayRegion);
} else if constexpr (std::is_same_v<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>>) {
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>>) {
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)) {
return makeArray<jshort>(std::forward<Container>(container), &JNIEnv::NewShortArray,
&JNIEnv::SetShortArrayRegion);
} else if constexpr (std::is_same_v<ElementType, jint>
|| sizeof(ElementType) == sizeof(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)) {
return makeArray<jlong>(std::forward<Container>(container), &JNIEnv::NewLongArray,
&JNIEnv::SetLongArrayRegion);
}
}
protected:
QJniArrayBase() = default;
template <typename ElementType, typename List, typename NewFn, typename SetFn>
static auto makeArray(List &&list, NewFn &&newArray, SetFn &&setRegion);
template <typename List>
static auto makeObjectArray(List &&list);
};
template <typename T>
class QJniArray : public QJniArrayBase
{
friend struct QJniArrayIterator<T>;
public:
using Type = T;
using const_iterator = const QJniArrayIterator<T>;
using QJniArrayBase::QJniArrayBase;
template <typename Container
, std::enable_if_t<QJniArrayBase::CanConvert<Container>, bool> = true
>
explicit QJniArray(Container &&container);
~QJniArray() = default;
auto arrayObject() const
{
if constexpr (std::is_convertible_v<jobject, T>)
return object<jobjectArray>();
else if constexpr (std::is_same_v<T, jbyte>)
return object<jbyteArray>();
else if constexpr (std::is_same_v<T, jchar>)
return object<jcharArray>();
else if constexpr (std::is_same_v<T, jboolean>)
return object<jbooleanArray>();
else if constexpr (std::is_same_v<T, jshort>)
return object<jshortArray>();
else if constexpr (std::is_same_v<T, jint>)
return object<jintArray>();
else if constexpr (std::is_same_v<T, jlong>)
return object<jlongArray>();
else if constexpr (std::is_same_v<T, jfloat>)
return object<jfloatArray>();
else if constexpr (std::is_same_v<T, jdouble>)
return object<jdoubleArray>();
else
return object<jarray>();
}
const_iterator begin() const { return {0, this}; }
const_iterator constBegin() const { return begin(); }
const_iterator cbegin() const { return begin(); }
const_iterator end() const { return {size(), this}; }
const_iterator constEnd() const { return {end()}; }
const_iterator cend() const { return {end()}; }
const T operator[](qsizetype i) const { return at(i); } // const return value to disallow assignment
T at(qsizetype i) const
{
QJniEnvironment env;
if constexpr (std::is_convertible_v<jobject, T>) {
return T{env->GetObjectArrayElement(object<jobjectArray>(), i)};
} else {
T res = {};
if constexpr (std::is_same_v<T, jbyte>)
env->GetByteArrayRegion(object<jbyteArray>(), i, 1, &res);
else if constexpr (std::is_same_v<T, jchar>)
env->GetCharArrayRegion(object<jcharArray>(), i, 1, &res);
else if constexpr (std::is_same_v<T, jboolean>)
env->GetBooleanArrayRegion(object<jbooleanArray>(), i, 1, &res);
else if constexpr (std::is_same_v<T, jshort>)
env->GetShortArrayRegion(object<jshortArray>(), i, 1, &res);
else if constexpr (std::is_same_v<T, jint>)
env->GetIntArrayRegion(object<jbyteArray>(), i, 1, &res);
else if constexpr (std::is_same_v<T, jlong>)
env->GetLongArrayRegion(object<jlongArray>(), i, 1, &res);
else if constexpr (std::is_same_v<T, jfloat>)
env->GetFloatArrayRegion(object<jfloatArray>(), i, 1, &res);
else if constexpr (std::is_same_v<T, jdouble>)
env->GetDoubleArrayRegion(object<jdoubleArray>(), i, 1, &res);
return res;
}
}
auto asContainer() const
{
QJniEnvironment env;
if constexpr (std::is_same_v<T, jobject>) {
QList<jobject> res;
res.reserve(size());
for (auto &&element : *this)
res.append(element);
return res;
} else if constexpr (std::is_same_v<T, jbyte>) {
const qsizetype bytecount = size();
QByteArray res(bytecount, Qt::Initialization::Uninitialized);
env->GetByteArrayRegion(object<jbyteArray>(),
0, bytecount, reinterpret_cast<jbyte *>(res.data()));
return res;
} else {
QList<T> res;
res.resize(size());
if constexpr (std::is_same_v<T, jchar>) {
env->GetCharArrayRegion(object<jcharArray>(),
0, res.size(), res.data());
} else if constexpr (std::is_same_v<T, jboolean>) {
env->GetBooleanArrayRegion(object<jbooleanArray>(),
0, res.size(), res.data());
} else if constexpr (std::is_same_v<T, jshort>) {
env->GetShortArrayRegion(object<jshortArray>(),
0, res.size(), res.data());
} else if constexpr (std::is_same_v<T, jint>) {
env->GetIntArrayRegion(object<jintArray>(),
0, res.size(), res.data());
} else if constexpr (std::is_same_v<T, jlong>) {
env->GetLongArrayRegion(object<jlongArray>(),
0, res.size(), res.data());
} else if constexpr (std::is_same_v<T, jfloat>) {
env->GetFloatArrayRegion(object<jfloatArray>(),
0, res.size(), res.data());
} else if constexpr (std::is_same_v<T, jdouble>) {
env->GetDoubleArrayRegion(object<jdoubleArray>(),
0, res.size(), res.data());
} else {
res.clear();
}
return res;
}
}
};
template <typename ElementType, typename List, typename NewFn, typename SetFn>
auto QJniArrayBase::makeArray(List &&list, NewFn &&newArray, SetFn &&setRegion)
{
const int length = int(list.size());
QJniEnvironment env;
JNIEnv *jenv = env.jniEnv();
auto localArray = (jenv->*newArray)(length);
if (env.checkAndClearExceptions())
return QJniArray<ElementType>();
// can't use static_cast here because we have signed/unsigned mismatches
if (length) {
(jenv->*setRegion)(localArray, 0, length,
reinterpret_cast<const ElementType *>(std::as_const(list).data()));
}
return QJniArray<ElementType>(localArray);
};
template <typename List>
auto QJniArrayBase::makeObjectArray(List &&list)
{
using ElementType = typename List::value_type;
if (list.isEmpty())
return QJniArray<jobject>();
QJniEnvironment env;
const int length = int(list.size());
// this assumes that all objects in the list have the same class
jclass elementClass = nullptr;
if constexpr (std::is_same_v<ElementType, QJniObject>)
elementClass = list.first().objectClass();
else
elementClass = env->GetObjectClass(list.first());
auto localArray = env->NewObjectArray(length, elementClass, nullptr);
if (env.checkAndClearExceptions())
return QJniArray<jobject>();
for (int i = 0; i < length; ++i) {
jobject object;
if constexpr (std::is_same_v<ElementType, QJniObject>)
object = list.at(i).object();
else
object = list.at(i);
env->SetObjectArrayElement(localArray, i, object);
}
return QJniArray<jobject>(localArray);
}
template <typename T>
template <typename Container
, std::enable_if_t<QJniArrayBase::CanConvert<Container>, bool>
>
QJniArray<T>::QJniArray(Container &&container)
: QJniArrayBase()
{
*this = QJniArrayBase::fromContainer(std::forward<Container>(container));
}
namespace QtJniTypes
{
template <typename T> struct IsJniArray: std::false_type {};
template <typename T> struct IsJniArray<QJniArray<T>> : std::true_type {};
template <typename T> struct Traits<QJniArray<T>> {
template <ValidFieldType<T> = true>
static constexpr auto signature()
{
return CTString("[") + Traits<T>::signature();
}
};
template <typename T> struct Traits<QList<T>> {
template <ValidFieldType<T> = true>
static constexpr auto signature()
{
return CTString("[") + Traits<T>::signature();
}
};
template <> struct Traits<QByteArray> {
static constexpr auto signature()
{
return CTString("[B");
}
};
}
QT_END_NAMESPACE
#endif
#endif // QJNIARRAY_H

View File

@ -37,30 +37,9 @@ class Q_CORE_EXPORT QJniObject
bool checkAndClearExceptions() { return env.checkAndClearExceptions(); }
template <typename T>
auto convertToJni(T &&value)
{
using Type = q20::remove_cvref_t<T>;
if constexpr (std::is_same_v<Type, QString>) {
return newLocalRef<jstring>(QJniObject::fromString(value));
} else if constexpr (std::is_base_of_v<QJniObject, Type>) {
return value.object();
} else {
return static_cast<T &&>(value);
}
}
auto convertToJni(T &&value);
template <typename T>
auto convertFromJni(QJniObject &&object)
{
using Type = q20::remove_cvref_t<T>;
if constexpr (std::is_same_v<Type, QString>) {
return object.toString();
} else if constexpr (std::is_base_of_v<QJniObject, Type>
&& !std::is_same_v<QJniObject, Type>) {
return T{std::move(object)};
} else {
return std::move(object);
}
}
auto convertFromJni(QJniObject &&object);
};
public:
QJniObject();
@ -779,6 +758,58 @@ inline bool operator!=(const QJniObject &obj1, const QJniObject &obj2)
return !obj1.isSameObject(obj2);
}
// This cannot be included earlier as QJniArray is a QJniObject subclass, but it
// must be included so that we can implement QJniObject::LocalFrame conversion.
#include <QtCore/qjniarray.h>
template <typename ...Args>
template <typename T>
auto QJniObject::LocalFrame<Args...>::convertToJni(T &&value)
{
using Type = q20::remove_cvref_t<T>;
if constexpr (std::is_same_v<Type, QString>) {
return newLocalRef<jstring>(QJniObject::fromString(value));
} else if constexpr (QtJniTypes::IsJniArray<Type>::value) {
return value.arrayObject();
} else if constexpr (QJniArrayBase::CanConvert<T>) {
using QJniArrayType = decltype(QJniArrayBase::fromContainer(std::forward<T>(value)));
using ArrayType = decltype(std::declval<QJniArrayType>().arrayObject());
return newLocalRef<ArrayType>(QJniArrayBase::fromContainer(std::forward<T>(value)).template object<jobject>());
} else if constexpr (std::is_base_of_v<QJniObject, Type>) {
return value.object();
} else {
return static_cast<T &&>(value);
}
}
template <typename ...Args>
template <typename T>
auto QJniObject::LocalFrame<Args...>::convertFromJni(QJniObject &&object)
{
using Type = q20::remove_cvref_t<T>;
if constexpr (std::is_same_v<Type, QString>) {
return object.toString();
} else if constexpr (QtJniTypes::IsJniArray<Type>::value) {
return T{object};
} else if constexpr (QJniArrayBase::CanConvert<Type>) {
// if we were to create a QJniArray from Type...
using QJniArrayType = decltype(QJniArrayBase::fromContainer(std::declval<Type>()));
// then that QJniArray would have elements of type
using ElementType = typename QJniArrayType::Type;
// construct a QJniArray from a jobject pointer of that type
return QJniArray<ElementType>(object.template object<jarray>()).asContainer();
} else if constexpr (std::is_array_v<Type>) {
using ElementType = std::remove_extent_t<Type>;
return QJniArray<ElementType>{object};
} else if constexpr (std::is_base_of_v<QJniObject, Type>
&& !std::is_same_v<QJniObject, Type>) {
return T{std::move(object)};
} else {
return std::move(object);
}
}
QT_END_NAMESPACE
#endif

View File

@ -49,4 +49,5 @@ if(ANDROID)
add_subdirectory(qjnienvironment)
add_subdirectory(qjniobject)
add_subdirectory(qjnitypes)
add_subdirectory(qjniarray)
endif()

View File

@ -0,0 +1,13 @@
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
cmake_minimum_required(VERSION 3.16)
project(tst_qjnitypes LANGUAGES CXX)
find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
endif()
qt_internal_add_test(tst_qjniarray
SOURCES
tst_qjniarray.cpp
)

View File

@ -0,0 +1,75 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QtTest>
#include <QtCore/qjnitypes.h>
#include <QtCore/qjniarray.h>
using namespace Qt::StringLiterals;
class tst_QJniArray : public QObject
{
Q_OBJECT
public:
tst_QJniArray() = default;
};
using namespace QtJniTypes;
// fake type so that we can compile-time test and assert correct type mappings
Q_DECLARE_JNI_CLASS(List, "qt/test/List");
// verify that we get the return type we expect for the specified return type
#define VERIFY_RETURN_FOR_TYPE(In, Out) \
static_assert(std::is_same_v<decltype(List::callStaticMethod<In>("toByteArray")), Out>)
VERIFY_RETURN_FOR_TYPE(jobjectArray, QJniObject);
VERIFY_RETURN_FOR_TYPE(jobject[], QJniArray<jobject>);
//VERIFY_RETURN_FOR_TYPE(QList<QJniObject>, QList<QJniObject>);
VERIFY_RETURN_FOR_TYPE(QList<jobject>, QList<jobject>);
VERIFY_RETURN_FOR_TYPE(jbyteArray, QJniObject);
VERIFY_RETURN_FOR_TYPE(jbyte[], QJniArray<jbyte>);
VERIFY_RETURN_FOR_TYPE(QByteArray, QByteArray);
VERIFY_RETURN_FOR_TYPE(jbooleanArray, QJniObject);
VERIFY_RETURN_FOR_TYPE(jboolean[], QJniArray<jboolean>);
VERIFY_RETURN_FOR_TYPE(QList<jboolean>, QList<jboolean>);
VERIFY_RETURN_FOR_TYPE(QList<bool>, QList<jboolean>);
VERIFY_RETURN_FOR_TYPE(jcharArray, QJniObject);
VERIFY_RETURN_FOR_TYPE(jchar[], QJniArray<jchar>);
VERIFY_RETURN_FOR_TYPE(QList<jchar>, QList<jchar>);
VERIFY_RETURN_FOR_TYPE(jshortArray, QJniObject);
VERIFY_RETURN_FOR_TYPE(jshort[], QJniArray<jshort>);
VERIFY_RETURN_FOR_TYPE(QList<jshort>, QList<jshort>);
VERIFY_RETURN_FOR_TYPE(QList<short>, QList<short>);
VERIFY_RETURN_FOR_TYPE(jintArray, QJniObject);
VERIFY_RETURN_FOR_TYPE(jint[], QJniArray<jint>);
VERIFY_RETURN_FOR_TYPE(QList<jint>, QList<jint>);
VERIFY_RETURN_FOR_TYPE(QList<int>, QList<int>);
VERIFY_RETURN_FOR_TYPE(jlongArray, QJniObject);
VERIFY_RETURN_FOR_TYPE(jlong[], QJniArray<jlong>);
VERIFY_RETURN_FOR_TYPE(QList<jlong>, QList<jlong>);
// VERIFY_RETURN_FOR_TYPE(QList<long>, QList<long>); // assumes long is 64bit
VERIFY_RETURN_FOR_TYPE(jfloatArray, QJniObject);
VERIFY_RETURN_FOR_TYPE(jfloat[], QJniArray<jfloat>);
VERIFY_RETURN_FOR_TYPE(QList<jfloat>, QList<jfloat>);
VERIFY_RETURN_FOR_TYPE(QList<float>, QList<float>);
VERIFY_RETURN_FOR_TYPE(jdoubleArray, QJniObject);
VERIFY_RETURN_FOR_TYPE(jdouble[], QJniArray<jdouble>);
VERIFY_RETURN_FOR_TYPE(QList<jdouble>, QList<jdouble>);
VERIFY_RETURN_FOR_TYPE(QList<double>, QList<double>);
VERIFY_RETURN_FOR_TYPE(QString, QString);
QTEST_MAIN(tst_QJniArray)
#include "tst_qjniarray.moc"

View File

@ -135,44 +135,143 @@ public class QtJniObjectTestClass
public static Object[] staticObjectArrayMethod()
{ Object[] array = { new Object(), new Object(), new Object() }; return array; }
public Object[] objectArrayMethod() { return staticObjectArrayMethod(); }
public static Object[] staticReverseObjectArray(Object[] array)
{
for (int i = 0; i < array.length / 2; ++i) {
Object old = array[array.length - i - 1];
array[array.length - i - 1] = array[i];
array[i] = old;
}
return array;
}
public Object[] reverseObjectArray(Object[] array)
{ return staticReverseObjectArray(array); }
// --------------------------------------------------------------------------------------------
public static boolean[] staticBooleanArrayMethod()
{ boolean[] array = { true, true, true }; return array; }
public boolean[] booleanArrayMethod() { return staticBooleanArrayMethod(); }
public static boolean[] staticReverseBooleanArray(boolean[] array)
{
for (int i = 0; i < array.length / 2; ++i) {
boolean old = array[array.length - i - 1];
array[array.length - i - 1] = array[i];
array[i] = old;
}
return array;
}
public boolean[] reverseBooleanArray(boolean[] array)
{ return staticReverseBooleanArray(array); }
// --------------------------------------------------------------------------------------------
public static byte[] staticByteArrayMethod()
{ byte[] array = { 'a', 'b', 'c' }; return array; }
public byte[] byteArrayMethod() { return staticByteArrayMethod(); }
public static byte[] staticReverseByteArray(byte[] array)
{
for (int i = 0; i < array.length / 2; ++i) {
byte old = array[array.length - i - 1];
array[array.length - i - 1] = array[i];
array[i] = old;
}
return array;
}
public byte[] reverseByteArray(byte[] array)
{ return staticReverseByteArray(array); }
// --------------------------------------------------------------------------------------------
public static char[] staticCharArrayMethod()
{ char[] array = { 'a', 'b', 'c' }; return array; }
public char[] charArrayMethod() { return staticCharArrayMethod(); }
public static char[] staticReverseCharArray(char[] array)
{
for (int i = 0; i < array.length / 2; ++i) {
char old = array[array.length - i - 1];
array[array.length - i - 1] = array[i];
array[i] = old;
}
return array;
}
public char[] reverseCharArray(char[] array)
{ return staticReverseCharArray(array); }
// --------------------------------------------------------------------------------------------
public static short[] staticShortArrayMethod() { short[] array = { 3, 2, 1 }; return array; }
public short[] shortArrayMethod() { return staticShortArrayMethod(); }
public static short[] staticReverseShortArray(short[] array)
{
for (int i = 0; i < array.length / 2; ++i) {
short old = array[array.length - i - 1];
array[array.length - i - 1] = array[i];
array[i] = old;
}
return array;
}
public short[] reverseShortArray(short[] array)
{ return staticReverseShortArray(array); }
// --------------------------------------------------------------------------------------------
public static int[] staticIntArrayMethod() { int[] array = { 3, 2, 1 }; return array; }
public int[] intArrayMethod() { return staticIntArrayMethod(); }
public static int[] staticReverseIntArray(int[] array)
{
for (int i = 0; i < array.length / 2; ++i) {
int old = array[array.length - i - 1];
array[array.length - i - 1] = array[i];
array[i] = old;
}
return array;
}
public int[] reverseIntArray(int[] array)
{ return staticReverseIntArray(array); }
// --------------------------------------------------------------------------------------------
public static long[] staticLongArrayMethod()
{ long[] array = { 3, 2, 1 }; return array; }
public long[] longArrayMethod() { return staticLongArrayMethod(); }
public static long[] staticReverseLongArray(long[] array)
{
for (int i = 0; i < array.length / 2; ++i) {
long old = array[array.length - i - 1];
array[array.length - i - 1] = array[i];
array[i] = old;
}
return array;
}
public long[] reverseLongArray(long[] array)
{ return staticReverseLongArray(array); }
// --------------------------------------------------------------------------------------------
public static float[] staticFloatArrayMethod()
{ float[] array = { 1.0f, 2.0f, 3.0f }; return array; }
public float[] floatArrayMethod() { return staticFloatArrayMethod(); }
public static float[] staticReverseFloatArray(float[] array)
{
for (int i = 0; i < array.length / 2; ++i) {
float old = array[array.length - i - 1];
array[array.length - i - 1] = array[i];
array[i] = old;
}
return array;
}
public float[] reverseFloatArray(float[] array)
{ return staticReverseFloatArray(array); }
// --------------------------------------------------------------------------------------------
public static double[] staticDoubleArrayMethod()
{ double[] array = { 3.0, 2.0, 1.0 }; return array; }
public double[] doubleArrayMethod() { return staticDoubleArrayMethod(); }
public static double[] staticReverseDoubleArray(double[] array)
{
for (int i = 0; i < array.length / 2; ++i) {
double old = array[array.length - i - 1];
array[array.length - i - 1] = array[i];
array[i] = old;
}
return array;
}
public double[] reverseDoubleArray(double[] array)
{ return staticReverseDoubleArray(array); }
// --------------------------------------------------------------------------------------------
native public int callbackWithObject(QtJniObjectTestClass that);

View File

@ -1495,6 +1495,18 @@ void tst_QJniObject::templateApiCheck()
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);
}
{
@ -1503,6 +1515,18 @@ void tst_QJniObject::templateApiCheck()
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);
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);
}
// jbooleanArray ------------------------------------------------------------------------------
@ -1513,6 +1537,14 @@ void tst_QJniObject::templateApiCheck()
const auto array = TestClass::callStaticMethod<jboolean[]>("staticBooleanArrayMethod");
QVERIFY(array.isValid());
QCOMPARE(array.size(), 3);
QCOMPARE(array.asContainer(), (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.asContainer(), (QList<jboolean>{false, false, true}));
}
{
@ -1521,6 +1553,14 @@ void tst_QJniObject::templateApiCheck()
const auto array = testClass.callMethod<jboolean[]>("booleanArrayMethod");
QVERIFY(array.isValid());
QCOMPARE(array.size(), 3);
QCOMPARE(array.asContainer(), (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.asContainer(), (QList<jboolean>{false, false, true}));
}
// jbyteArray ---------------------------------------------------------------------------------
@ -1531,6 +1571,18 @@ void tst_QJniObject::templateApiCheck()
const auto array = TestClass::callStaticMethod<jbyte[]>("staticByteArrayMethod");
QVERIFY(array.isValid());
QCOMPARE(array.size(), 3);
QCOMPARE(array.asContainer(), "abc");
QJniArray<jbyte> newArray(QByteArray{"cba"});
QVERIFY(newArray.isValid());
const auto reverse = TestClass::callStaticMethod<jbyte[]>("staticReverseByteArray", newArray);
QVERIFY(reverse.isValid());
QCOMPARE(reverse.asContainer(), "abc");
const QByteArray reverse2 = TestClass::callStaticMethod<QByteArray>("staticReverseByteArray",
QByteArray("abc"));
QCOMPARE(reverse2, "cba");
}
{
@ -1539,6 +1591,14 @@ void tst_QJniObject::templateApiCheck()
const auto array = testClass.callMethod<jbyte[]>("byteArrayMethod");
QVERIFY(array.isValid());
QCOMPARE(array.size(), 3);
QCOMPARE(array.asContainer(), "abc");
QJniArray<jbyte> newArray = QJniArrayBase::fromContainer(QByteArray{"cba"});
QVERIFY(newArray.isValid());
const auto reverse = testClass.callMethod<jbyte[]>("reverseByteArray", newArray);
QVERIFY(reverse.isValid());
QCOMPARE(reverse.asContainer(), "abc");
}
// jcharArray ---------------------------------------------------------------------------------
@ -1549,6 +1609,18 @@ void tst_QJniObject::templateApiCheck()
const auto array = TestClass::callStaticMethod<jchar[]>("staticCharArrayMethod");
QVERIFY(array.isValid());
QCOMPARE(array.size(), 3);
QCOMPARE(array.asContainer(), (QList<jchar>{u'a', u'b', u'c'}));
QJniArray<jchar> newArray(QList<jchar>{u'c', u'b', u'a'});
QVERIFY(newArray.isValid());
const auto reverse = TestClass::callStaticMethod<jchar[]>("staticReverseCharArray", newArray);
QVERIFY(reverse.isValid());
QCOMPARE(reverse.asContainer(), (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'}));
}
{
@ -1557,6 +1629,14 @@ void tst_QJniObject::templateApiCheck()
const auto array = testClass.callMethod<jchar[]>("charArrayMethod");
QVERIFY(array.isValid());
QCOMPARE(array.size(), 3);
QCOMPARE(array.asContainer(), (QList<jchar>{u'a', u'b', u'c'}));
QJniArray<jchar> newArray = QJniArrayBase::fromContainer(QList<jchar>{u'c', u'b', u'a'});
QVERIFY(newArray.isValid());
const auto reverse = testClass.callMethod<jchar[]>("reverseCharArray", newArray);
QVERIFY(reverse.isValid());
QCOMPARE(reverse.asContainer(), (QList<jchar>{u'a', u'b', u'c'}));
}
// jshortArray --------------------------------------------------------------------------------
@ -1567,6 +1647,18 @@ void tst_QJniObject::templateApiCheck()
const auto array = TestClass::callStaticMethod<jshort[]>("staticShortArrayMethod");
QVERIFY(array.isValid());
QCOMPARE(array.size(), 3);
QCOMPARE(array.asContainer(), (QList<jshort>{3, 2, 1}));
QJniArray<jshort> newArray(QList<jshort>{3, 2, 1});
QVERIFY(newArray.isValid());
const auto reverse = TestClass::callStaticMethod<jshort[]>("staticReverseShortArray", newArray);
QVERIFY(reverse.isValid());
QCOMPARE(reverse.asContainer(), (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}));
}
{
@ -1575,6 +1667,15 @@ void tst_QJniObject::templateApiCheck()
const auto array = testClass.callMethod<jshort[]>("shortArrayMethod");
QVERIFY(array.isValid());
QCOMPARE(array.size(), 3);
QCOMPARE(array.asContainer(), (QList<jshort>{3, 2, 1}));
QJniArray<jshort> newArray = QJniArrayBase::fromContainer(QList<jshort>{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.asContainer(), (QList<jshort>{1, 2, 3}));
}
// jintArray ----------------------------------------------------------------------------------
@ -1585,6 +1686,14 @@ void tst_QJniObject::templateApiCheck()
const auto array = TestClass::callStaticMethod<jint[]>("staticIntArrayMethod");
QVERIFY(array.isValid());
QCOMPARE(array.size(), 3);
QCOMPARE(array.asContainer(), (QList<jint>{3, 2, 1}));
QJniArray<jint> newArray(QList<jint>{3, 2, 1});
QVERIFY(newArray.isValid());
const auto reverse = TestClass::callStaticMethod<jint[]>("staticReverseIntArray", newArray);
QVERIFY(reverse.isValid());
QCOMPARE(reverse.asContainer(), (QList<jint>{1, 2, 3}));
}
{
@ -1593,6 +1702,14 @@ void tst_QJniObject::templateApiCheck()
const auto array = testClass.callMethod<jint[]>("intArrayMethod");
QVERIFY(array.isValid());
QCOMPARE(array.size(), 3);
QCOMPARE(array.asContainer(), (QList<jint>{3, 2, 1}));
QJniArray<jint> newArray = QJniArrayBase::fromContainer(QList<jint>{3, 2, 1});
QVERIFY(newArray.isValid());
const auto reverse = testClass.callMethod<jint[]>("reverseIntArray", newArray);
QVERIFY(reverse.isValid());
QCOMPARE(reverse.asContainer(), (QList<jint>{1, 2, 3}));
}
// jlongArray ---------------------------------------------------------------------------------
@ -1603,6 +1720,14 @@ void tst_QJniObject::templateApiCheck()
const auto array = TestClass::callStaticMethod<jlong[]>("staticLongArrayMethod");
QVERIFY(array.isValid());
QCOMPARE(array.size(), 3);
QCOMPARE(array.asContainer(), (QList<jlong>{3, 2, 1}));
QJniArray<jlong> newArray(QList<jlong>{3, 2, 1});
QVERIFY(newArray.isValid());
const auto reverse = TestClass::callStaticMethod<jlong[]>("staticReverseLongArray", newArray);
QVERIFY(reverse.isValid());
QCOMPARE(reverse.asContainer(), (QList<jlong>{1, 2, 3}));
}
{
@ -1611,6 +1736,14 @@ void tst_QJniObject::templateApiCheck()
const auto array = testClass.callMethod<jlong[]>("longArrayMethod");
QVERIFY(array.isValid());
QCOMPARE(array.size(), 3);
QCOMPARE(array.asContainer(), (QList<jlong>{3, 2, 1}));
QJniArray<jlong> newArray = QJniArrayBase::fromContainer(QList<jlong>{3, 2, 1});
QVERIFY(newArray.isValid());
const auto reverse = testClass.callMethod<jlong[]>("reverseLongArray", newArray);
QVERIFY(reverse.isValid());
QCOMPARE(reverse.asContainer(), (QList<jlong>{1, 2, 3}));
}
// jfloatArray --------------------------------------------------------------------------------
@ -1621,6 +1754,14 @@ void tst_QJniObject::templateApiCheck()
const auto array = TestClass::callStaticMethod<jfloat[]>("staticFloatArrayMethod");
QVERIFY(array.isValid());
QCOMPARE(array.size(), 3);
QCOMPARE(array.asContainer(), (QList<jfloat>{1.0f, 2.0f, 3.0f}));
QJniArray<jfloat> newArray(QList<jfloat>{3.0f, 2.0f, 1.0f});
QVERIFY(newArray.isValid());
const auto reverse = TestClass::callStaticMethod<jfloat[]>("staticReverseFloatArray", newArray);
QVERIFY(reverse.isValid());
QCOMPARE(reverse.asContainer(), (QList<jfloat>{1.0f, 2.0f, 3.0f}));
}
{
@ -1629,6 +1770,14 @@ void tst_QJniObject::templateApiCheck()
const auto array = testClass.callMethod<jfloat[]>("floatArrayMethod");
QVERIFY(array.isValid());
QCOMPARE(array.size(), 3);
QCOMPARE(array.asContainer(), (QList<jfloat>{1.0f, 2.0f, 3.0f}));
QJniArray<jfloat> newArray = QJniArrayBase::fromContainer(QList<jfloat>{3.0f, 2.0f, 1.0f});
QVERIFY(newArray.isValid());
const auto reverse = testClass.callMethod<jfloat[]>("reverseFloatArray", newArray);
QVERIFY(reverse.isValid());
QCOMPARE(reverse.asContainer(), (QList<jfloat>{1.0f, 2.0f, 3.0f}));
}
// jdoubleArray -------------------------------------------------------------------------------
@ -1639,6 +1788,14 @@ void tst_QJniObject::templateApiCheck()
const auto array = TestClass::callStaticMethod<jdouble[]>("staticDoubleArrayMethod");
QVERIFY(array.isValid());
QCOMPARE(array.size(), 3);
QCOMPARE(array.asContainer(), (QList<jdouble>{3.0, 2.0, 1.0}));
QJniArray<jdouble> newArray(QList<jdouble>{3.0, 2.0, 1.0});
QVERIFY(newArray.isValid());
const auto reverse = TestClass::callStaticMethod<jdouble[]>("staticReverseDoubleArray", newArray);
QVERIFY(reverse.isValid());
QCOMPARE(reverse.asContainer(), (QList<jdouble>{1.0, 2.0, 3.0}));
}
{
@ -1647,6 +1804,14 @@ void tst_QJniObject::templateApiCheck()
const auto array = testClass.callMethod<jdouble[]>("doubleArrayMethod");
QVERIFY(array.isValid());
QCOMPARE(array.size(), 3);
QCOMPARE(array.asContainer(), (QList<jdouble>{3.0, 2.0, 1.0}));
QJniArray<jdouble> newArray = QJniArrayBase::fromContainer(QList<jdouble>{3.0, 2.0, 1.0});
QVERIFY(newArray.isValid());
const auto reverse = testClass.callMethod<jdouble[]>("reverseDoubleArray", newArray);
QVERIFY(reverse.isValid());
QCOMPARE(reverse.asContainer(), (QList<jdouble>{1.0, 2.0, 3.0}));
}
}

View File

@ -4,6 +4,7 @@
#include <QtTest>
#include <QtCore/qjnitypes.h>
#include <QtCore/qjniarray.h>
using namespace Qt::StringLiterals;
@ -59,8 +60,7 @@ static_assert(!(QtJniTypes::Traits<QtJavaWrapper>::signature() == "X"));
Q_DECLARE_JNI_CLASS(JavaType, "org/qtproject/qt/JavaType");
static_assert(QtJniTypes::Traits<QtJniTypes::JavaType>::signature() == "Lorg/qtproject/qt/JavaType;");
Q_DECLARE_JNI_TYPE(ArrayType, "[Lorg/qtproject/qt/ArrayType;")
static_assert(QtJniTypes::Traits<QtJniTypes::ArrayType>::signature() == "[Lorg/qtproject/qt/ArrayType;");
static_assert(QtJniTypes::Traits<QtJniTypes::JavaType[]>::signature() == "[Lorg/qtproject/qt/JavaType;");
Q_DECLARE_JNI_CLASS(String, "java/lang/String");
static_assert(QtJniTypes::Traits<jstring>::className() == "java/lang/String");
@ -119,6 +119,10 @@ static_assert(!QtJniTypes::CTString("ABCDE").startsWith("9AB"));
static_assert(QtJniTypes::CTString("ABCDE").startsWith('A'));
static_assert(!QtJniTypes::CTString("ABCDE").startsWith('B'));
static_assert(QtJniTypes::Traits<QJniArray<jobject>>::signature() == "[Ljava/lang/Object;");
static_assert(QtJniTypes::Traits<QJniArray<jbyte>>::signature() == "[B");
static_assert(QtJniTypes::isObjectType<QJniArray<jbyte>>());
static_assert(QtJniTypes::CTString("ABCDE").endsWith("CDE"));
static_assert(QtJniTypes::CTString("ABCDE").endsWith("E"));
static_assert(QtJniTypes::CTString("ABCDE").endsWith("ABCDE"));