diff options
author | Volker Hilsheimer <volker.hilsheimer@qt.io> | 2023-09-14 21:15:20 +0200 |
---|---|---|
committer | Volker Hilsheimer <volker.hilsheimer@qt.io> | 2023-10-16 18:43:16 +0200 |
commit | 80d4d55e250af1d643805bde3821987112cf09c1 (patch) | |
tree | 985611d8fcddadcdc7890479c83d8abdb9f3a5d5 | |
parent | 53f9768e6424f7b00946005f82f11d82facee35a (diff) |
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>
-rw-r--r-- | src/corelib/CMakeLists.txt | 1 | ||||
-rw-r--r-- | src/corelib/kernel/qjniarray.h | 356 | ||||
-rw-r--r-- | src/corelib/kernel/qjniobject.h | 77 | ||||
-rw-r--r-- | tests/auto/corelib/kernel/CMakeLists.txt | 1 | ||||
-rw-r--r-- | tests/auto/corelib/kernel/qjniarray/CMakeLists.txt | 13 | ||||
-rw-r--r-- | tests/auto/corelib/kernel/qjniarray/tst_qjniarray.cpp | 75 | ||||
-rw-r--r-- | tests/auto/corelib/kernel/qjniobject/testdata/src/org/qtproject/qt/android/testdata/QtJniObjectTestClass.java | 99 | ||||
-rw-r--r-- | tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp | 165 | ||||
-rw-r--r-- | tests/auto/corelib/kernel/qjnitypes/tst_qjnitypes.cpp | 8 |
9 files changed, 770 insertions, 25 deletions
diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index ca75bdf984..756d12f0bd 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -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 diff --git a/src/corelib/kernel/qjniarray.h b/src/corelib/kernel/qjniarray.h new file mode 100644 index 0000000000..ba79e27b8e --- /dev/null +++ b/src/corelib/kernel/qjniarray.h @@ -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 diff --git a/src/corelib/kernel/qjniobject.h b/src/corelib/kernel/qjniobject.h index 6fe646d50f..0ba7209b85 100644 --- a/src/corelib/kernel/qjniobject.h +++ b/src/corelib/kernel/qjniobject.h @@ -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 diff --git a/tests/auto/corelib/kernel/CMakeLists.txt b/tests/auto/corelib/kernel/CMakeLists.txt index ce0e82bae2..130fc080b6 100644 --- a/tests/auto/corelib/kernel/CMakeLists.txt +++ b/tests/auto/corelib/kernel/CMakeLists.txt @@ -49,4 +49,5 @@ if(ANDROID) add_subdirectory(qjnienvironment) add_subdirectory(qjniobject) add_subdirectory(qjnitypes) + add_subdirectory(qjniarray) endif() diff --git a/tests/auto/corelib/kernel/qjniarray/CMakeLists.txt b/tests/auto/corelib/kernel/qjniarray/CMakeLists.txt new file mode 100644 index 0000000000..308b2c6427 --- /dev/null +++ b/tests/auto/corelib/kernel/qjniarray/CMakeLists.txt @@ -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 +) diff --git a/tests/auto/corelib/kernel/qjniarray/tst_qjniarray.cpp b/tests/auto/corelib/kernel/qjniarray/tst_qjniarray.cpp new file mode 100644 index 0000000000..fa0da3ca66 --- /dev/null +++ b/tests/auto/corelib/kernel/qjniarray/tst_qjniarray.cpp @@ -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" diff --git a/tests/auto/corelib/kernel/qjniobject/testdata/src/org/qtproject/qt/android/testdata/QtJniObjectTestClass.java b/tests/auto/corelib/kernel/qjniobject/testdata/src/org/qtproject/qt/android/testdata/QtJniObjectTestClass.java index d1b4ea480e..b56447198f 100644 --- a/tests/auto/corelib/kernel/qjniobject/testdata/src/org/qtproject/qt/android/testdata/QtJniObjectTestClass.java +++ b/tests/auto/corelib/kernel/qjniobject/testdata/src/org/qtproject/qt/android/testdata/QtJniObjectTestClass.java @@ -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); diff --git a/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp b/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp index 47c3a707f0..751e5a84d8 100644 --- a/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp +++ b/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp @@ -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})); } } diff --git a/tests/auto/corelib/kernel/qjnitypes/tst_qjnitypes.cpp b/tests/auto/corelib/kernel/qjnitypes/tst_qjnitypes.cpp index a499b6f2d2..f9d9a4c13c 100644 --- a/tests/auto/corelib/kernel/qjnitypes/tst_qjnitypes.cpp +++ b/tests/auto/corelib/kernel/qjnitypes/tst_qjnitypes.cpp @@ -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")); |