// 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 #if defined(Q_QDOC) || defined(Q_OS_ANDROID) #include #include #include #include #include #include #if defined(Q_QDOC) using jsize = qint32; using jarray = jobject; #endif QT_BEGIN_NAMESPACE template class QJniArray; template struct QJniArrayMutableIterator; // forward declare here so that we don't have to include the private header namespace QtAndroidPrivate { Q_CORE_EXPORT jclass findClass(const char *className, JNIEnv *env); } template struct QJniArrayIterator { private: using VT = std::remove_const_t; friend class QJniArray; friend struct QJniArrayMutableIterator; // Since QJniArray doesn't hold values, we need a wrapper to be able to hand // out a pointer to a value. struct QJniArrayValueRef { T ref; const T *operator->() const { return &ref; } }; public: QJniArrayIterator() = default; constexpr QJniArrayIterator(const QJniArrayMutableIterator &other) noexcept : m_index(other.m_index), m_array(other.m_array) {} constexpr QJniArrayIterator(QJniArrayMutableIterator &&other) noexcept : m_index(std::exchange(other.m_index, -1)), m_array(std::exchange(other.m_array, nullptr)) {} constexpr QJniArrayIterator(const QJniArrayIterator &other) noexcept = default; constexpr QJniArrayIterator(QJniArrayIterator &&other) noexcept = default; constexpr QJniArrayIterator &operator=(const QJniArrayIterator &other) noexcept = default; constexpr QJniArrayIterator &operator=(QJniArrayIterator &&other) noexcept = default; using difference_type = jsize; using value_type = T; using pointer = QJniArrayValueRef; using reference = T; // difference to container requirements using const_reference = reference; using iterator_category = std::random_access_iterator_tag; const_reference operator*() const { return m_array->at(m_index); } QJniArrayValueRef operator->() const { return {m_array->at(m_index)}; } const_reference operator[](difference_type n) const { return m_array->at(m_index + n); } friend QJniArrayIterator &operator++(QJniArrayIterator &that) noexcept { ++that.m_index; return that; } friend QJniArrayIterator operator++(QJniArrayIterator &that, int) noexcept { auto copy = that; ++that; return copy; } friend QJniArrayIterator operator+(const QJniArrayIterator &that, difference_type n) noexcept { return {that.m_index + n, that.m_array}; } friend QJniArrayIterator operator+(difference_type n, const QJniArrayIterator &that) noexcept { return that + n; } friend QJniArrayIterator &operator+=(QJniArrayIterator &that, difference_type n) noexcept { that.m_index += n; return that; } friend QJniArrayIterator &operator--(QJniArrayIterator &that) noexcept { --that.m_index; return that; } friend QJniArrayIterator operator--(QJniArrayIterator &that, int) noexcept { auto copy = that; --that; return copy; } friend QJniArrayIterator operator-(const QJniArrayIterator &that, difference_type n) noexcept { return {that.m_index - n, that.m_array}; } friend QJniArrayIterator operator-(difference_type n, const QJniArrayIterator &that) noexcept { return {n - that.m_index, that.m_array}; } friend QJniArrayIterator &operator-=(QJniArrayIterator &that, difference_type n) noexcept { that.m_index -= n; return that; } friend difference_type operator-(const QJniArrayIterator &lhs, const QJniArrayIterator &rhs) { Q_ASSERT(lhs.m_array == rhs.m_array); return lhs.m_index - rhs.m_index; } void swap(QJniArrayIterator &other) noexcept { std::swap(m_index, other.m_index); qt_ptr_swap(m_array, other.m_array); } private: friend constexpr bool comparesEqual(const QJniArrayIterator &lhs, const QJniArrayIterator &rhs) noexcept { Q_ASSERT(lhs.m_array == rhs.m_array); return lhs.m_index == rhs.m_index; } friend constexpr Qt::strong_ordering compareThreeWay(const QJniArrayIterator &lhs, const QJniArrayIterator &rhs) noexcept { Q_ASSERT(lhs.m_array == rhs.m_array); return Qt::compareThreeWay(lhs.m_index, rhs.m_index); } Q_DECLARE_STRONGLY_ORDERED(QJniArrayIterator) qsizetype m_index = 0; const QJniArray *m_array = nullptr; QJniArrayIterator(qsizetype index, const QJniArray *array) : m_index(index), m_array(array) {} }; template // need to specialize traits for it, so can't be nested struct QJniArrayMutableValueRef; template struct QJniArrayMutableIterator { private: friend struct QJniArrayIterator; friend struct QJniArrayMutableValueRef; public: constexpr QJniArrayMutableIterator() noexcept = default; constexpr QJniArrayMutableIterator(const QJniArrayIterator &other) noexcept : m_index(other.m_index), m_array(other.m_array) {} constexpr QJniArrayMutableIterator(QJniArrayIterator &&other) noexcept : m_index(std::exchange(other.m_index, -1)), m_array(std::exchange(other.m_array, nullptr)) {} constexpr QJniArrayMutableIterator(const QJniArrayMutableIterator &other) noexcept = default; constexpr QJniArrayMutableIterator(QJniArrayMutableIterator &&other) noexcept = default; constexpr QJniArrayMutableIterator &operator=(const QJniArrayMutableIterator &other) noexcept = default; constexpr QJniArrayMutableIterator &operator=(QJniArrayMutableIterator &&other) noexcept = default; using difference_type = jsize; using value_type = T; using pointer = QJniArrayMutableValueRef; using reference = QJniArrayMutableValueRef; // difference to container requirements using const_reference = T; using iterator_category = std::random_access_iterator_tag; const_reference operator*() const { return m_array->at(m_index); } reference operator*() { return {m_array->at(m_index), *this}; } const pointer operator->() const { return {m_array->at(m_index)}; } pointer operator->() { return {m_array->at(m_index), *this}; } const_reference operator[](difference_type n) const { return m_array->at(m_index + n); } reference operator[](difference_type n) { return {m_array->at(m_index + n), *this}; } friend QJniArrayMutableIterator &operator++(QJniArrayMutableIterator &that) noexcept { ++that.m_index; return that; } friend QJniArrayMutableIterator operator++(QJniArrayMutableIterator &that, int) noexcept { auto copy = that; ++that; return copy; } friend QJniArrayMutableIterator operator+(const QJniArrayMutableIterator &that, difference_type n) noexcept { return {that.m_index + n, that.m_array}; } friend QJniArrayMutableIterator operator+(difference_type n, const QJniArrayMutableIterator &that) noexcept { return that + n; } friend QJniArrayMutableIterator &operator+=(QJniArrayMutableIterator &that, difference_type n) noexcept { that.m_index += n; return that; } friend QJniArrayMutableIterator &operator--(QJniArrayMutableIterator &that) noexcept { --that.m_index; return that; } friend QJniArrayMutableIterator operator--(QJniArrayMutableIterator &that, int) noexcept { auto copy = that; --that; return copy; } friend QJniArrayMutableIterator operator-(const QJniArrayMutableIterator &that, difference_type n) noexcept { return {that.m_index - n, that.m_array}; } friend QJniArrayMutableIterator operator-(difference_type n, const QJniArrayMutableIterator &that) noexcept { return {n - that.m_index, that.m_array}; } friend QJniArrayMutableIterator &operator-=(QJniArrayMutableIterator &that, difference_type n) noexcept { that.m_index -= n; return that; } friend difference_type operator-(const QJniArrayMutableIterator &lhs, const QJniArrayMutableIterator &rhs) { Q_ASSERT(lhs.m_array == rhs.m_array); return lhs.m_index - rhs.m_index; } void swap(QJniArrayMutableIterator &other) noexcept { std::swap(m_index, other.m_index); qt_ptr_swap(m_array, other.m_array); } private: friend constexpr bool comparesEqual(const QJniArrayMutableIterator &lhs, const QJniArrayMutableIterator &rhs) { Q_ASSERT(lhs.m_array == rhs.m_array); return lhs.m_index == rhs.m_index; } friend constexpr bool comparesEqual(const QJniArrayMutableIterator &lhs, const QJniArrayIterator &rhs) { Q_ASSERT(lhs.m_array == rhs.m_array); return lhs.m_index == rhs.m_index; } friend constexpr Qt::strong_ordering compareThreeWay(const QJniArrayMutableIterator &lhs, const QJniArrayMutableIterator &rhs) { Q_ASSERT(lhs.m_array == rhs.m_array); return Qt::compareThreeWay(lhs.m_index, rhs.m_index); } friend constexpr Qt::strong_ordering compareThreeWay(const QJniArrayMutableIterator &lhs, const QJniArrayIterator &rhs) { Q_ASSERT(lhs.m_array == rhs.m_array); return Qt::compareThreeWay(lhs.m_index, rhs.m_index); } Q_DECLARE_STRONGLY_ORDERED_NON_NOEXCEPT(QJniArrayMutableIterator) Q_DECLARE_STRONGLY_ORDERED_NON_NOEXCEPT(QJniArrayMutableIterator, QJniArrayIterator) using VT = std::remove_const_t; friend class QJniArray; qsizetype m_index = 0; QJniArray *m_array = nullptr; QJniArrayMutableIterator(qsizetype index, QJniArray *array) : m_index(index), m_array(array) {} }; template // need to specialize traits for it, so can't be nested struct QJniArrayMutableValueRef { T value; QJniArrayMutableIterator back = {-1, nullptr}; operator T() const { return value; } const T &operator*() const { return value; } T &operator*() { return value; } const T *operator->() const { return &value; } T *operator->() = delete; // no write-back, so delete explicitly QJniArrayMutableValueRef &operator=(const QJniArrayMutableValueRef &other) { return *this = *other; } QJniArrayMutableValueRef &operator=(QJniArrayMutableValueRef &&other) { return *this = std::move(*other); } QJniArrayMutableValueRef &operator=(const T &v) { Q_ASSERT(back.m_array); value = v; back.m_array->setValue(back.m_index, value); return *this; } QJniArrayMutableValueRef &operator=(T &&v) { Q_ASSERT(back.m_array); value = std::move(v); back.m_array->setValue(back.m_index, value); return *this; } }; class QJniArrayBase { // for SFINAE'ing out the fromContainer named constructor template struct IsSequentialContainerHelper : std::false_type { static constexpr bool isForwardIterable = false; }; template struct IsSequentialContainerHelper::iterator_category, typename C::value_type, decltype(std::size(std::declval())) > > : std::true_type { static constexpr bool isForwardIterable = std::is_convertible_v< typename std::iterator_traits::iterator_category, std::forward_iterator_tag >; }; template <> struct IsSequentialContainerHelper { static constexpr bool isForwardIterable = true; }; template struct IsContiguousContainerHelper : std::false_type {}; template struct IsContiguousContainerHelper())), decltype(std::size(std::declval())), typename C::value_type > > : std::true_type {}; template struct HasEmplaceBackTest : std::false_type {}; template struct HasEmplaceBackTest().emplace_back(std::declval()))> > : std::true_type {}; protected: // these are used in QJniArray template struct ElementTypeHelper { static constexpr bool isObject = false; static constexpr bool isPrimitive = false; }; template struct ElementTypeHelper> { using E = typename C::value_type; static constexpr bool isObject = QtJniTypes::isObjectType(); static constexpr bool isPrimitive = QtJniTypes::isPrimitiveType(); }; template > static constexpr bool isContiguousContainer = IsContiguousContainerHelper::value; template using if_convertible = std::enable_if_t::value, bool>; template using unless_convertible = std::enable_if_t::value, bool>; // helpers for toContainer template struct ToContainerHelper { using type = QList; }; template <> struct ToContainerHelper { using type = QStringList; }; template <> struct ToContainerHelper { using type = QByteArray; }; template <> struct ToContainerHelper { using type = QByteArray; }; template using ToContainerType = typename ToContainerHelper::type; template > static constexpr bool isCompatibleTargetContainer = (QtPrivate::AreArgumentsConvertibleWithoutNarrowingBase::value || QtPrivate::AreArgumentsConvertibleWithoutNarrowingBase::value_type, typename C::value_type>::value || (std::is_base_of_v && std::is_same_v)) && (qxp::is_detected_v || (isContiguousContainer && ElementTypeHelper::isPrimitive)); public: using size_type = jsize; using difference_type = size_type; operator QJniObject() const { return m_object; } template T object() const { return m_object.object(); } bool isValid() const { return m_object.isValid(); } bool isEmpty() const { return size() == 0; } size_type size() const { if (jarray array = m_object.object()) return jniEnv()->GetArrayLength(array); return 0; } // We can create an array from any forward-iterable container, and optimize // for contiguous containers holding primitive elements. QJniArray is a // forward-iterable container, so explicitly remove that from the overload // set so that the copy constructors get used instead. // Used also in the deduction guide, so must be public template using IsSequentialOrContiguous = std::bool_constant< IsSequentialContainerHelper::isForwardIterable || (isContiguousContainer && ElementTypeHelper::isPrimitive) >; template > static constexpr bool isCompatibleSourceContainer = std::conjunction_v< std::negation>, IsSequentialOrContiguous, std::negation> >; template using if_compatible_source_container = std::enable_if_t, bool>; template using if_compatible_target_container = std::enable_if_t, bool>; template = true> static auto fromContainer(Container &&container) { verifySize(std::size(container)); using ElementType = typename std::remove_reference_t::value_type; if constexpr (std::is_base_of_v, std::remove_pointer_t>) { return makeObjectArray(std::forward(container)); } else if constexpr (std::disjunction_v, std::is_same, std::is_base_of >) { return QJniArray(makeObjectArray(std::forward(container)).arrayObject()); } else if constexpr (QtJniTypes::sameTypeForJni) { return makeArray(std::forward(container), &JNIEnv::NewFloatArray, &JNIEnv::SetFloatArrayRegion); } else if constexpr (QtJniTypes::sameTypeForJni) { return makeArray(std::forward(container), &JNIEnv::NewDoubleArray, &JNIEnv::SetDoubleArrayRegion); } else if constexpr (QtJniTypes::sameTypeForJni) { return makeArray(std::forward(container), &JNIEnv::NewBooleanArray, &JNIEnv::SetBooleanArrayRegion); } else if constexpr (QtJniTypes::sameTypeForJni || std::is_same_v) { return makeArray(std::forward(container), &JNIEnv::NewByteArray, &JNIEnv::SetByteArrayRegion); } else if constexpr (std::disjunction_v, std::is_same>) { return makeArray(std::forward(container), &JNIEnv::NewCharArray, &JNIEnv::SetCharArrayRegion); } else if constexpr (QtJniTypes::sameTypeForJni) { return makeArray(std::forward(container), &JNIEnv::NewShortArray, &JNIEnv::SetShortArrayRegion); } else if constexpr (QtJniTypes::sameTypeForJni) { return makeArray(std::forward(container), &JNIEnv::NewIntArray, &JNIEnv::SetIntArrayRegion); } else if constexpr (QtJniTypes::sameTypeForJni) { return makeArray(std::forward(container), &JNIEnv::NewLongArray, &JNIEnv::SetLongArrayRegion); } else { static_assert(QtPrivate::type_dependent_false(), "Don't know how to make QJniArray for this element type"); } } protected: QJniArrayBase() = default; ~QJniArrayBase() = default; explicit QJniArrayBase(const QJniArrayBase &other) = default; explicit QJniArrayBase(QJniArrayBase &&other) noexcept = default; QJniArrayBase &operator=(const QJniArrayBase &other) = default; QJniArrayBase &operator=(QJniArrayBase &&other) noexcept = default; explicit QJniArrayBase(jarray array) : m_object(static_cast(array)) { } explicit QJniArrayBase(const QJniObject &object) : m_object(object) {} explicit QJniArrayBase(QJniObject &&object) noexcept : m_object(std::move(object)) {} QJniArrayBase &operator=(const QJniObject &object) { m_object = object; return *this; } QJniArrayBase &operator=(QJniObject &&object) noexcept { m_object = std::move(object); return *this; } JNIEnv *jniEnv() const noexcept { return QJniEnvironment::getJniEnv(); } template static auto makeArray(List &&list, NewFn &&newArray, SetFn &&setRegion); template static auto makeObjectArray(List &&list); template static auto makeEmptyArray(size_type size) { auto env = QJniEnvironment(); if constexpr (std::disjunction_v, std::remove_pointer_t>, std::is_same, std::is_same, std::is_base_of >) { using ResultType = decltype(QtJniTypes::Traits::convertToJni(nullptr, {})); const auto className = QtJniTypes::Traits::className(); jclass elementClass = env.findClass(className); if (!elementClass) { env.checkAndClearExceptions(); return jobjectArray(nullptr); } return env->NewObjectArray(size, elementClass, nullptr); } else if constexpr (QtJniTypes::sameTypeForJni) { return env->NewFloatArray(size); } else if constexpr (QtJniTypes::sameTypeForJni) { return env->NewDoubleArray(size); } else if constexpr (QtJniTypes::sameTypeForJni) { return env->NewBooleanArray(size); } else if constexpr (std::disjunction_v, std::is_same>) { return env->NewByteArray(size); } else if constexpr (std::disjunction_v, std::is_same>) { return env->NewCharArray(size); } else if constexpr (QtJniTypes::sameTypeForJni) { return env->NewShortArray(size); } else if constexpr (QtJniTypes::sameTypeForJni) { return env->NewIntArray(size); } else if constexpr (QtJniTypes::sameTypeForJni) { return env->NewLongArray(size); } else { static_assert(QtPrivate::type_dependent_false(), "Don't know how to make QJniArray for this element type"); } } void swap(QJniArrayBase &other) noexcept { m_object.swap(other.m_object); } private: template static void verifySize(container_size_type size) { if (!q20::in_range(size)) qWarning("QJniArray::fromContainer: Container is too large for Java and will be truncated!"); } QJniObject m_object; }; template class QJniArray : public QJniArrayBase { friend struct QJniArrayIterator; template using CanReserveTest = decltype(std::declval().reserve(0)); template static constexpr bool canReserve = qxp::is_detected_v; public: using Type = T; using value_type = T; using iterator = QJniArrayMutableIterator; using reverse_iterator = std::reverse_iterator; using const_iterator = QJniArrayIterator; using const_reverse_iterator = std::reverse_iterator; using reference = typename iterator::reference; using const_reference = typename const_iterator::const_reference; QJniArray() = default; explicit QJniArray(jarray array) : QJniArrayBase(array) {} explicit QJniArray(const QJniObject &object) : QJniArrayBase(object) {} explicit QJniArray(QJniObject &&object) noexcept : QJniArrayBase(std::move(object)) {} template = true> QJniArray(const QJniArray &other) : QJniArrayBase(other) { } template = true> QJniArray(QJniArray &&other) noexcept : QJniArrayBase(std::move(other)) { } template = true> QJniArray &operator=(const QJniArray &other) { QJniArrayBase::operator=(QJniObject(other)); return *this; } template = true> QJniArray &operator=(QJniArray &&other) noexcept { QJniArray moved(std::move(other)); swap(moved); return *this; } // explicitly delete to disable detour via operator QJniObject() template = true> QJniArray(const QJniArray &other) = delete; template = true> QJniArray(QJniArray &&other) noexcept = delete; template = true> explicit QJniArray(Container &&container) : QJniArrayBase(QJniArrayBase::fromContainer(std::forward(container))) { } Q_IMPLICIT inline QJniArray(std::initializer_list list) : QJniArrayBase(QJniArrayBase::fromContainer(list)) { } explicit QJniArray(size_type size) : QJniArrayBase(makeEmptyArray(size)) {} ~QJniArray() = default; auto arrayObject() const { if constexpr (QtJniTypes::isObjectType()) return object(); else if constexpr (QtJniTypes::sameTypeForJni) return object(); else if constexpr (QtJniTypes::sameTypeForJni) return object(); else if constexpr (QtJniTypes::sameTypeForJni) return object(); else if constexpr (QtJniTypes::sameTypeForJni) return object(); else if constexpr (QtJniTypes::sameTypeForJni) return object(); else if constexpr (QtJniTypes::sameTypeForJni) return object(); else if constexpr (QtJniTypes::sameTypeForJni) return object(); else if constexpr (QtJniTypes::sameTypeForJni) return object(); else return object(); } const_iterator begin() const noexcept { return {0, this}; } const_iterator constBegin() const noexcept { return begin(); } const_iterator cbegin() const noexcept { return begin(); } const_iterator end() const noexcept { return {size(), this}; } const_iterator constEnd() const noexcept { return {end()}; } const_iterator cend() const noexcept { return {end()}; } iterator begin() noexcept { return {0, this}; } iterator end() noexcept { return {size(), this}; } const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); } const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); } const_reverse_iterator crbegin() const noexcept { return const_reverse_iterator(end()); } const_reverse_iterator crend() const noexcept { return const_reverse_iterator(begin()); } reverse_iterator rbegin() noexcept { return reverse_iterator(end()); } reverse_iterator rend() noexcept { return reverse_iterator(begin()); } const_reference operator[](size_type i) const { return at(i); } reference operator[](size_type i) { return reference{at(i), iterator{i, this}}; } const_reference at(size_type i) const { JNIEnv *env = jniEnv(); if constexpr (QtJniTypes::isObjectType()) { jobject element = env->GetObjectArrayElement(object(), i); if constexpr (std::is_base_of_v, std::remove_pointer_t>) return static_cast(element); else return QtJniTypes::Traits::convertFromJni(QJniObject::fromLocalRef(element)); } else { T res = {}; if constexpr (QtJniTypes::sameTypeForJni) env->GetByteArrayRegion(object(), i, 1, &res); else if constexpr (QtJniTypes::sameTypeForJni) env->GetCharArrayRegion(object(), i, 1, &res); else if constexpr (QtJniTypes::sameTypeForJni) env->GetBooleanArrayRegion(object(), i, 1, &res); else if constexpr (QtJniTypes::sameTypeForJni) env->GetShortArrayRegion(object(), i, 1, &res); else if constexpr (QtJniTypes::sameTypeForJni) env->GetIntArrayRegion(object(), i, 1, &res); else if constexpr (QtJniTypes::sameTypeForJni) env->GetLongArrayRegion(object(), i, 1, &res); else if constexpr (QtJniTypes::sameTypeForJni) env->GetFloatArrayRegion(object(), i, 1, &res); else if constexpr (QtJniTypes::sameTypeForJni) env->GetDoubleArrayRegion(object(), i, 1, &res); return res; } } void setValue(size_type i, const_reference &val) { JNIEnv *env = jniEnv(); if constexpr (QtJniTypes::isObjectType()) { QtJniTypes::Detail::LocalFrame frame(env); jobject element = frame.template convertToJni(val); env->SetObjectArrayElement(object(), i, element); } else { // primitive types if constexpr (QtJniTypes::sameTypeForJni) env->SetByteArrayRegion(object(), i, 1, &val); else if constexpr (QtJniTypes::sameTypeForJni) env->SetCharArrayRegion(object(), i, 1, &val); else if constexpr (QtJniTypes::sameTypeForJni) env->SetBooleanArrayRegion(object(), i, 1, &val); else if constexpr (QtJniTypes::sameTypeForJni) env->SetShortArrayRegion(object(), i, 1, &val); else if constexpr (QtJniTypes::sameTypeForJni) env->SetIntArrayRegion(object(), i, 1, &val); else if constexpr (QtJniTypes::sameTypeForJni) env->SetLongArrayRegion(object(), i, 1, &val); else if constexpr (QtJniTypes::sameTypeForJni) env->SetFloatArrayRegion(object(), i, 1, &val); else if constexpr (QtJniTypes::sameTypeForJni) env->SetDoubleArrayRegion(object(), i, 1, &val); } } template , if_compatible_target_container = true> Container toContainer(Container &&container = {}) const { const qsizetype sz = size(); if (!sz) return std::forward(container); JNIEnv *env = jniEnv(); using ContainerType = q20::remove_cvref_t; if constexpr (canReserve) container.reserve(sz); if constexpr (std::is_same_v) { for (auto element : *this) { if constexpr (std::is_same_v) { container.emplace_back(element); } else if constexpr (std::is_same_v) { container.emplace_back(element ? QtJniTypes::Detail::toQString(element, env) : QString{}); } else { container.emplace_back(QJniObject(element).toString()); } } } else if constexpr (std::is_base_of_v, std::remove_pointer_t>) { for (auto element : *this) container.emplace_back(element); } else if constexpr (isContiguousContainer) { container.resize(sz); if constexpr (QtJniTypes::sameTypeForJni) { env->GetByteArrayRegion(object(), 0, sz, reinterpret_cast(container.data())); } else if constexpr (QtJniTypes::sameTypeForJni) { env->GetCharArrayRegion(object(), 0, sz, container.data()); } else if constexpr (QtJniTypes::sameTypeForJni) { env->GetBooleanArrayRegion(object(), 0, sz, container.data()); } else if constexpr (QtJniTypes::sameTypeForJni) { env->GetShortArrayRegion(object(), 0, sz, container.data()); } else if constexpr (QtJniTypes::sameTypeForJni) { env->GetIntArrayRegion(object(), 0, sz, container.data()); } else if constexpr (QtJniTypes::sameTypeForJni) { env->GetLongArrayRegion(object(), 0, sz, container.data()); } else if constexpr (QtJniTypes::sameTypeForJni) { env->GetFloatArrayRegion(object(), 0, sz, container.data()); } else if constexpr (QtJniTypes::sameTypeForJni) { env->GetDoubleArrayRegion(object(), 0, sz, container.data()); } else { static_assert(QtPrivate::type_dependent_false(), "Don't know how to copy data from a QJniArray of this type"); } } else { for (auto e : *this) container.emplace_back(e); } return std::forward(container); } }; // Deduction guide so that we can construct as 'QJniArray list(Container)'. Since // fromContainer() maps several C++ types to the same JNI type (e.g. both jboolean and // bool become QJniArray), we have to deduce to what fromContainer() would // give us. template = true> QJniArray(Container) -> QJniArray()))::value_type>; template auto QJniArrayBase::makeArray(List &&list, NewFn &&newArray, SetFn &&setRegion) { const size_type length = size_type(std::size(list)); JNIEnv *env = QJniEnvironment::getJniEnv(); auto localArray = (env->*newArray)(length); if (QJniEnvironment::checkAndClearExceptions(env)) { if (localArray) env->DeleteLocalRef(localArray); return QJniArray(); } if (length) { // can't use static_cast here because we have signed/unsigned mismatches if constexpr (isContiguousContainer) { (env->*setRegion)(localArray, 0, length, reinterpret_cast(std::data(std::as_const(list)))); } else { size_type i = 0; for (const auto &e : std::as_const(list)) (env->*setRegion)(localArray, i++, 1, reinterpret_cast(&e)); } } return QJniArray(QJniObject::fromLocalRef(localArray)); }; template auto QJniArrayBase::makeObjectArray(List &&list) { using ElementType = typename q20::remove_cvref_t::value_type; using ResultType = QJniArray::convertToJni(nullptr, {}))>; if (std::size(list) == 0) return ResultType(); JNIEnv *env = QJniEnvironment::getJniEnv(); const size_type length = q26::saturate_cast(std::size(list)); // this assumes that all objects in the list have the same class jclass elementClass = nullptr; if constexpr (std::disjunction_v, std::is_base_of>) { elementClass = std::begin(list)->objectClass(); } else if constexpr (std::is_same_v) { elementClass = QtAndroidPrivate::findClass("java/lang/String", env); } else { elementClass = env->GetObjectClass(*std::begin(list)); } auto localArray = env->NewObjectArray(length, elementClass, nullptr); if (QJniEnvironment::checkAndClearExceptions(env)) { if (localArray) env->DeleteLocalRef(localArray); return ResultType(); } // explicitly manage the frame for local references in chunks of 100 constexpr jint frameCapacity = 100; qsizetype i = 0; for (const auto &element : std::as_const(list)) { if (i % frameCapacity == 0) { if (i) env->PopLocalFrame(nullptr); if (env->PushLocalFrame(frameCapacity) != 0) return ResultType{}; } jobject object = QtJniTypes::Traits::convertToJni(env, element); env->SetObjectArrayElement(localArray, i, object); ++i; } if (i) env->PopLocalFrame(nullptr); return ResultType(QJniObject::fromLocalRef(localArray)); } namespace QtJniTypes { template struct Traits> { template = true> static constexpr auto signature() { return CTString("[") + Traits::signature(); } static auto convertToJni(JNIEnv *, const QJniArray &value) { return value.arrayObject(); } static auto convertFromJni(QJniObject &&object) { return QJniArray(std::move(object)); } }; template struct Traits> : public Traits {}; template struct Traits>> { // QByteArray::value_type is char, which maps to 'C'; we need 'B', i.e. jbyte using ElementType = std::conditional_t, jbyte, typename T::value_type>; template = true> static constexpr auto signature() { return CTString("[") + Traits::signature(); } static auto convertToJni(JNIEnv *env, const T &value) { using QJniArrayType = decltype(QJniArrayBase::fromContainer(value)); using ArrayType = decltype(std::declval().arrayObject()); return static_cast(env->NewLocalRef(QJniArray(value).arrayObject())); } static auto convertFromJni(QJniObject &&object) { // if we were to create a QJniArray from Type... using QJniArrayType = decltype(QJniArrayBase::fromContainer(std::declval())); // then that QJniArray would have elements of type using ArrayType = typename QJniArrayType::Type; // construct a QJniArray from a jobject pointer of that type return QJniArray(object.template object()).toContainer(); } }; template struct Traits>> { using ElementType = std::remove_extent_t; template = true> static constexpr auto signature() { static_assert(!std::is_array_v, "Traits::signature() does not handle multi-dimensional arrays"); return CTString("[") + Traits::signature(); } static constexpr auto convertFromJni(QJniObject &&object) { return QJniArray(std::move(object)); } }; } QT_END_NAMESPACE #endif #endif // QJNIARRAY_H