// 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 #if defined(Q_QDOC) using jsize = qint32; using jarray = jobject; #endif QT_BEGIN_NAMESPACE template class QJniArray; template struct QJniArrayIterator { private: // 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 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, difference_type) 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, difference_type) 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) using VT = std::remove_const_t; friend class QJniArray; qsizetype m_index = 0; const QJniArray *m_array = nullptr; QJniArrayIterator(qsizetype index, const QJniArray *array) : m_index(index), m_array(array) {} }; 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 > static constexpr bool isCompatibleSourceContainer = (IsSequentialContainerHelper::isForwardIterable || (isContiguousContainer && ElementTypeHelper::isPrimitive)) && !std::is_base_of_v; 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) { Q_ASSERT_X(size_t(std::size(container)) <= size_t((std::numeric_limits::max)()), "QJniArray::fromContainer", "Container is too large for a Java array"); 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); void swap(QJniArrayBase &other) noexcept { m_object.swap(other.m_object); } private: 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 reference = T; using const_reference = const reference; // read-only container, so no iterator typedef using const_iterator = QJniArrayIterator; using const_reverse_iterator = std::reverse_iterator; 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)) { } ~QJniArray() = default; auto arrayObject() const { if constexpr (std::is_convertible_v) return object(); else if constexpr (std::is_same_v) 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()}; } 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()); } const_reference operator[](size_type i) const { return at(i); } const_reference at(size_type i) const { JNIEnv *env = jniEnv(); if constexpr (std::is_convertible_v) { jobject element = env->GetObjectArrayElement(object(), i); if constexpr (std::is_base_of_v) return QJniObject::fromLocalRef(element); else if constexpr (std::is_base_of_v) return T::fromLocalRef(element); else return T{element}; } else if constexpr (std::is_same_v) { jstring string = static_cast(env->GetObjectArrayElement(arrayObject(), i)); const auto length = env->GetStringLength(string); QString res(length, Qt::Uninitialized); env->GetStringRegion(string, 0, length, reinterpret_cast(res.data())); env->DeleteLocalRef(string); return res; } else if constexpr (std::is_base_of_v, std::remove_pointer_t>) { // jstring, jclass etc return static_cast(env->GetObjectArrayElement(object(), i)); } 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; } } 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 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( std::declval())) >; if (std::size(list) == 0) return ResultType(); JNIEnv *env = QJniEnvironment::getJniEnv(); const size_type length = size_type(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 = env->FindClass("java/lang/String"); } 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 QJniObject::LocalFrame frame(env); frame.hasFrame = true; 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 = frame.convertToJni(element); env->SetObjectArrayElement(localArray, i, object); ++i; } if (i) env->PopLocalFrame(nullptr); frame.hasFrame = false; return ResultType(QJniObject::fromLocalRef(localArray)); } namespace QtJniTypes { template struct IsJniArray: std::false_type {}; template struct IsJniArray> : std::true_type {}; template struct Traits> { template = true> static constexpr auto signature() { return CTString("[") + Traits::signature(); } }; template struct Traits> { template = true> static constexpr auto signature() { return CTString("[") + Traits::signature(); } }; template <> struct Traits { static constexpr auto signature() { return CTString("[B"); } }; } QT_END_NAMESPACE #endif #endif // QJNIARRAY_H