// 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 QT_BEGIN_NAMESPACE template class QJniArray; template struct QJniArrayIterator { 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 = T *; using reference = T; // difference to container requirements using const_reference = reference; using iterator_category = std::bidirectional_iterator_tag; friend bool operator==(const QJniArrayIterator &lhs, const QJniArrayIterator &rhs) noexcept { return lhs.m_array == rhs.m_array && lhs.m_index == rhs.m_index; } friend bool operator!=(const QJniArrayIterator &lhs, const QJniArrayIterator &rhs) noexcept { return !(lhs == rhs); } const_reference operator*() const { return m_array->at(m_index); } 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--(QJniArrayIterator &that) noexcept { --that.m_index; return that; } friend QJniArrayIterator operator--(QJniArrayIterator &that, int) noexcept { auto copy = that; --that; return copy; } void swap(QJniArrayIterator &other) noexcept { std::swap(m_index, other.m_index); qt_ptr_swap(m_array, other.m_array); } private: 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 CanConvertHelper : std::false_type {}; template struct CanConvertHelper())), decltype(std::size(std::declval())), typename Container::value_type > > : std::true_type {}; 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(); } size_type size() const { if (jarray array = m_object.object()) return jniEnv()->GetArrayLength(array); return 0; } template static constexpr bool canConvert = CanConvertHelper>::value; template using IfCanConvert = 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::disjunction_v, std::is_same, std::is_same, std::is_base_of >) { return makeObjectArray(std::forward(container)); } else if constexpr (std::is_same_v) { return makeArray(std::forward(container), &JNIEnv::NewFloatArray, &JNIEnv::SetFloatArrayRegion); } else if constexpr (std::is_same_v) { return makeArray(std::forward(container), &JNIEnv::NewDoubleArray, &JNIEnv::SetDoubleArrayRegion); } else if constexpr (std::disjunction_v, std::is_same>) { return makeArray(std::forward(container), &JNIEnv::NewBooleanArray, &JNIEnv::SetBooleanArrayRegion); } else if constexpr (std::disjunction_v, std::is_same>) { 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 (std::is_same_v || sizeof(ElementType) == sizeof(jshort)) { return makeArray(std::forward(container), &JNIEnv::NewShortArray, &JNIEnv::SetShortArrayRegion); } else if constexpr (std::is_same_v || sizeof(ElementType) == sizeof(jint)) { return makeArray(std::forward(container), &JNIEnv::NewIntArray, &JNIEnv::SetIntArrayRegion); } else if constexpr (std::is_same_v || sizeof(ElementType) == sizeof(jlong)) { return makeArray(std::forward(container), &JNIEnv::NewLongArray, &JNIEnv::SetLongArrayRegion); } } protected: QJniArrayBase() = default; ~QJniArrayBase() = 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)) {} JNIEnv *jniEnv() const noexcept { return QJniEnvironment::getJniEnv(); } template static auto makeArray(List &&list, NewFn &&newArray, SetFn &&setRegion); template static auto makeObjectArray(List &&list); private: QJniObject m_object; }; template class QJniArray : public QJniArrayBase { friend struct QJniArrayIterator; 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)) {} // base class destructor is protected, so need to provide all SMFs QJniArray(const QJniArray &other) = default; QJniArray(QJniArray &&other) noexcept = default; QJniArray &operator=(const QJniArray &other) = default; QJniArray &operator=(QJniArray &&other) noexcept = default; template = true > explicit QJniArray(Container &&container) : QJniArrayBase(QJniArrayBase::fromContainer(std::forward(container))) { } template > = true > Q_IMPLICIT inline QJniArray(std::initializer_list list) : QJniArrayBase(QJniArrayBase::fromContainer(list)) { } template , bool> = true> QJniArray(QJniArray &&other) : QJniArrayBase(std::forward>(other)) { } ~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 (std::is_same_v) return object(); else if constexpr (std::is_same_v) return object(); else if constexpr (std::is_same_v) return object(); else if constexpr (std::is_same_v) return object(); else if constexpr (std::is_same_v) return object(); else if constexpr (std::is_same_v) return object(); else if constexpr (std::is_same_v) 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_base_of_v, std::remove_pointer_t>) { // jstring, jclass etc return static_cast(env->GetObjectArrayElement(object(), i)); } else { T res = {}; if constexpr (std::is_same_v) env->GetByteArrayRegion(object(), i, 1, &res); else if constexpr (std::is_same_v) env->GetCharArrayRegion(object(), i, 1, &res); else if constexpr (std::is_same_v) env->GetBooleanArrayRegion(object(), i, 1, &res); else if constexpr (std::is_same_v) env->GetShortArrayRegion(object(), i, 1, &res); else if constexpr (std::is_same_v) env->GetIntArrayRegion(object(), i, 1, &res); else if constexpr (std::is_same_v) env->GetLongArrayRegion(object(), i, 1, &res); else if constexpr (std::is_same_v) env->GetFloatArrayRegion(object(), i, 1, &res); else if constexpr (std::is_same_v) env->GetDoubleArrayRegion(object(), i, 1, &res); return res; } } auto toContainer() const { JNIEnv *env = jniEnv(); if constexpr (std::is_same_v) { QList res; res.reserve(size()); for (auto element : *this) res.append(element); return res; } else if constexpr (std::is_same_v) { QStringList res; res.reserve(size()); for (auto element : *this) res.append(QJniObject(element).toString()); return res; } else if constexpr (std::is_same_v) { const qsizetype bytecount = size(); QByteArray res(bytecount, Qt::Initialization::Uninitialized); env->GetByteArrayRegion(object(), 0, bytecount, reinterpret_cast(res.data())); return res; } else { QList res; res.resize(size()); if constexpr (std::is_same_v) { env->GetCharArrayRegion(object(), 0, res.size(), res.data()); } else if constexpr (std::is_same_v) { env->GetBooleanArrayRegion(object(), 0, res.size(), res.data()); } else if constexpr (std::is_same_v) { env->GetShortArrayRegion(object(), 0, res.size(), res.data()); } else if constexpr (std::is_same_v) { env->GetIntArrayRegion(object(), 0, res.size(), res.data()); } else if constexpr (std::is_same_v) { env->GetLongArrayRegion(object(), 0, res.size(), res.data()); } else if constexpr (std::is_same_v) { env->GetFloatArrayRegion(object(), 0, res.size(), res.data()); } else if constexpr (std::is_same_v) { env->GetDoubleArrayRegion(object(), 0, res.size(), res.data()); } else { res.clear(); } return res; } } }; 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)) return QJniArray(); // can't use static_cast here because we have signed/unsigned mismatches if (length) { (env->*setRegion)(localArray, 0, length, reinterpret_cast(std::data(std::as_const(list)))); } return QJniArray(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)) return ResultType(); // explicitly manage the frame for local references in chunks of 100 QJniObject::LocalFrame frame(env); 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); return ResultType(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