summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/corelib/kernel/qjniarray.h4
-rw-r--r--src/corelib/kernel/qjnienvironment.cpp8
-rw-r--r--src/corelib/kernel/qjnienvironment.h1
-rw-r--r--src/corelib/kernel/qjniobject.cpp135
-rw-r--r--src/corelib/kernel/qjniobject.h523
-rw-r--r--src/corelib/time/qdatetime.cpp12
-rw-r--r--src/corelib/tools/qflatmap_p.h4
-rw-r--r--src/gui/text/qfont.cpp2
-rw-r--r--src/plugins/platforms/windows/qwindowswindowclassdescription.cpp26
-rw-r--r--src/plugins/platforms/windows/qwindowswindowclassdescription.h1
-rw-r--r--tests/auto/corelib/kernel/qjniobject/CMakeLists.txt8
-rw-r--r--tests/auto/corelib/kernel/qjniobject/testdata/src/org/qtproject/qt/android/testdata/QtJniObjectTestClass.java4
-rw-r--r--tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp567
13 files changed, 1041 insertions, 254 deletions
diff --git a/src/corelib/kernel/qjniarray.h b/src/corelib/kernel/qjniarray.h
index 13349688d20..97d0cd37682 100644
--- a/src/corelib/kernel/qjniarray.h
+++ b/src/corelib/kernel/qjniarray.h
@@ -872,7 +872,7 @@ 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 (env->ExceptionCheck()) {
if (localArray)
env->DeleteLocalRef(localArray);
return QJniArray<ElementType>();
@@ -916,7 +916,7 @@ auto QJniArrayBase::makeObjectArray(List &&list)
elementClass = env->GetObjectClass(*std::begin(list));
}
auto localArray = env->NewObjectArray(length, elementClass, nullptr);
- if (QJniEnvironment::checkAndClearExceptions(env)) {
+ if (env->ExceptionCheck()) {
if (localArray)
env->DeleteLocalRef(localArray);
return ResultType();
diff --git a/src/corelib/kernel/qjnienvironment.cpp b/src/corelib/kernel/qjnienvironment.cpp
index b4f8497ddda..1ee658fd18d 100644
--- a/src/corelib/kernel/qjnienvironment.cpp
+++ b/src/corelib/kernel/qjnienvironment.cpp
@@ -559,4 +559,12 @@ bool QJniEnvironment::checkAndClearExceptions(JNIEnv *env, QJniEnvironment::Outp
return false;
}
+/*!
+ Returns the stack trace that resulted in \a exception being thrown.
+*/
+QStringList QJniEnvironment::stackTrace(jthrowable exception)
+{
+ return exceptionMessage(getJniEnv(), exception).split(u'\n');
+}
+
QT_END_NAMESPACE
diff --git a/src/corelib/kernel/qjnienvironment.h b/src/corelib/kernel/qjnienvironment.h
index b473f75bed1..a5f3700b1f0 100644
--- a/src/corelib/kernel/qjnienvironment.h
+++ b/src/corelib/kernel/qjnienvironment.h
@@ -89,6 +89,7 @@ public:
static bool checkAndClearExceptions(JNIEnv *env, OutputMode outputMode = OutputMode::Verbose);
static JNIEnv *getJniEnv();
+ static QStringList stackTrace(jthrowable exception);
private:
Q_DISABLE_COPY_MOVE(QJniEnvironment)
diff --git a/src/corelib/kernel/qjniobject.cpp b/src/corelib/kernel/qjniobject.cpp
index 8f3da9a8595..59117bd01d4 100644
--- a/src/corelib/kernel/qjniobject.cpp
+++ b/src/corelib/kernel/qjniobject.cpp
@@ -348,7 +348,10 @@ static inline QByteArray cacheKey(Args &&...args)
return (QByteArrayView(":") + ... + QByteArrayView(args));
}
-typedef QHash<QByteArray, jclass> JClassHash;
+struct JClassHash : QHash<QByteArray, jclass>
+{
+ jmethodID loadClassMethod = 0;
+};
Q_GLOBAL_STATIC(JClassHash, cachedClasses)
Q_GLOBAL_STATIC(QReadWriteLock, cachedClassesLock)
@@ -391,29 +394,22 @@ bool QJniObjectPrivate::isJString(JNIEnv *env) const
*/
static QJniObject getCleanJniObject(jobject object, JNIEnv *env)
{
- if (QJniEnvironment::checkAndClearExceptions(env) || !object) {
- if (object)
- env->DeleteLocalRef(object);
+ if (!object || env->ExceptionCheck())
return QJniObject();
- }
- QJniObject res(object);
- env->DeleteLocalRef(object);
- return res;
+ return QJniObject::fromLocalRef(object);
}
-/*!
- \internal
- \a className must be slash-encoded
-*/
-jclass QtAndroidPrivate::findClass(const char *className, JNIEnv *env)
+namespace {
+
+jclass loadClassHelper(const QByteArray &className, JNIEnv *env)
{
Q_ASSERT(env);
QByteArray classNameArray(className);
#ifdef QT_DEBUG
if (classNameArray.contains('.')) {
qWarning("QtAndroidPrivate::findClass: className '%s' should use slash separators!",
- className);
+ className.constData());
}
#endif
classNameArray.replace('.', '/');
@@ -442,21 +438,40 @@ jclass QtAndroidPrivate::findClass(const char *className, JNIEnv *env)
if (!clazz) {
// Wrong class loader, try our own
- QJniObject classLoader(QtAndroidPrivate::classLoader());
- if (!classLoader.isValid())
+ jobject classLoader = QtAndroidPrivate::classLoader();
+ if (!classLoader)
return nullptr;
+ if (!cachedClasses->loadClassMethod) {
+ jclass classLoaderClass = env->GetObjectClass(classLoader);
+ if (!classLoaderClass)
+ return nullptr;
+ cachedClasses->loadClassMethod =
+ env->GetMethodID(classLoaderClass,
+ "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;");
+ env->DeleteLocalRef(classLoaderClass);
+ if (!cachedClasses->loadClassMethod) {
+ qCritical("Couldn't find the 'loadClass' method in the Qt class loader");
+ return nullptr;
+ }
+ }
+
// ClassLoader::loadClass on the other hand wants the binary name of the class,
// e.g. dot-separated. In testing it works also with /, but better to stick to
// the specification.
const QString binaryClassName = QString::fromLatin1(className).replace(u'/', u'.');
- jstring classNameObject = env->NewString(reinterpret_cast<const jchar*>(binaryClassName.constData()),
- binaryClassName.length());
- QJniObject classObject = classLoader.callMethod<jclass>("loadClass", classNameObject);
+ jstring classNameObject = env->NewString(binaryClassName.utf16(), binaryClassName.length());
+ jobject classObject = env->CallObjectMethod(classLoader,
+ cachedClasses->loadClassMethod,
+ classNameObject);
env->DeleteLocalRef(classNameObject);
- if (!QJniEnvironment::checkAndClearExceptions(env) && classObject.isValid())
- clazz = static_cast<jclass>(env->NewGlobalRef(classObject.object()));
+ if (classObject && !env->ExceptionCheck()) {
+ clazz = static_cast<jclass>(env->NewGlobalRef(classObject));
+ env->DeleteLocalRef(classObject);
+ }
+ // Clearing the exception is the caller's responsibility (see
+ // QtAndroidPrivate::findClass()) and QJniObject::loadClass{KeepExceptions}
}
if (clazz)
@@ -465,11 +480,32 @@ jclass QtAndroidPrivate::findClass(const char *className, JNIEnv *env)
return clazz;
}
+} // unnamed namespace
+
+/*!
+ \internal
+ \a className must be slash-encoded
+*/
+jclass QtAndroidPrivate::findClass(const char *className, JNIEnv *env)
+{
+ jclass clazz = loadClassHelper(className, env);
+ if (!clazz)
+ QJniEnvironment::checkAndClearExceptions(env);
+ return clazz;
+}
+
jclass QJniObject::loadClass(const QByteArray &className, JNIEnv *env)
{
return QtAndroidPrivate::findClass(className, env);
}
+jclass QJniObject::loadClassKeepExceptions(const QByteArray &className, JNIEnv *env)
+{
+ return loadClassHelper(className, env);
+}
+
+
+
typedef QHash<QByteArray, jmethodID> JMethodIDHash;
Q_GLOBAL_STATIC(JMethodIDHash, cachedMethodID)
Q_GLOBAL_STATIC(QReadWriteLock, cachedMethodIDLock)
@@ -483,9 +519,6 @@ jmethodID QJniObject::getMethodID(JNIEnv *env,
jmethodID id = isStatic ? env->GetStaticMethodID(clazz, name, signature)
: env->GetMethodID(clazz, name, signature);
- if (QJniEnvironment::checkAndClearExceptions(env))
- return nullptr;
-
return id;
}
@@ -525,7 +558,8 @@ jmethodID QJniObject::getCachedMethodID(JNIEnv *env,
jmethodID id = getMethodID(env, clazz, name, signature, isStatic);
- cachedMethodID->insert(key, id);
+ if (id)
+ cachedMethodID->insert(key, id);
return id;
}
}
@@ -549,9 +583,6 @@ jfieldID QJniObject::getFieldID(JNIEnv *env,
jfieldID id = isStatic ? env->GetStaticFieldID(clazz, name, signature)
: env->GetFieldID(clazz, name, signature);
- if (QJniEnvironment::checkAndClearExceptions(env))
- return nullptr;
-
return id;
}
@@ -583,7 +614,8 @@ jfieldID QJniObject::getCachedFieldID(JNIEnv *env,
jfieldID id = getFieldID(env, clazz, name, signature, isStatic);
- cachedFieldID->insert(key, id);
+ if (id)
+ cachedFieldID->insert(key, id);
return id;
}
}
@@ -1008,15 +1040,15 @@ QJniObject QJniObject::callObjectMethod(const char *methodName, const char *sign
{
JNIEnv *env = jniEnv();
jmethodID id = getCachedMethodID(env, methodName, signature);
- if (id) {
- va_list args;
- va_start(args, signature);
- QJniObject res = getCleanJniObject(env->CallObjectMethodV(d->m_jobject, id, args), env);
- va_end(args);
- return res;
- }
+ va_list args;
+ va_start(args, signature);
+ // can't go back from variadic arguments to variadic templates
+ jobject object = id ? jniEnv()->CallObjectMethodV(javaObject(), id, args) : nullptr;
+ QJniObject res = getCleanJniObject(object, env);
+ va_end(args);
- return QJniObject();
+ QJniEnvironment::checkAndClearExceptions(env);
+ return res;
}
/*!
@@ -1150,7 +1182,7 @@ QJniObject QJniObject::callStaticObjectMethod(jclass clazz, jmethodID methodId,
*/
/*!
- \fn template <typename T> void QJniObject::setStaticField(const char *className, const char *fieldName, const char *signature, T value);
+ \fn template <typename Ret, typename Type> auto QJniObject::setStaticField(const char *className, const char *fieldName, const char *signature, Type value);
Sets the static field \a fieldName on the class \a className to \a value
using the setter with \a signature.
@@ -1158,7 +1190,7 @@ QJniObject QJniObject::callStaticObjectMethod(jclass clazz, jmethodID methodId,
*/
/*!
- \fn template <typename T> void QJniObject::setStaticField(jclass clazz, const char *fieldName, const char *signature, T value);
+ \fn template <typename Ret, typename Type> auto QJniObject::setStaticField(jclass clazz, const char *fieldName, const char *signature, Type value);
Sets the static field \a fieldName on the class \a clazz to \a value using
the setter with \a signature.
@@ -1196,19 +1228,19 @@ QJniObject QJniObject::callStaticObjectMethod(jclass clazz, jmethodID methodId,
*/
/*!
- \fn template <typename T> void QJniObject::setStaticField(const char *className, const char *fieldName, T value)
+ \fn template <typename Ret, typename Type> auto QJniObject::setStaticField(const char *className, const char *fieldName, Type value)
Sets the static field \a fieldName of the class \a className to \a value.
*/
/*!
- \fn template <typename T> void QJniObject::setStaticField(jclass clazz, const char *fieldName, T value)
+ \fn template <typename Ret, typename Type> auto QJniObject::setStaticField(jclass clazz, const char *fieldName, Type value)
Sets the static field \a fieldName of the class \a clazz to \a value.
*/
/*!
- \fn template <typename Klass, typename T> auto QJniObject::setStaticField(const char *fieldName, T value)
+ \fn template <typename Klass, typename Ret, typename Type> auto QJniObject::setStaticField(const char *fieldName, Type value)
Sets the static field \a fieldName of the class \c Klass to \a value.
@@ -1263,11 +1295,16 @@ QJniObject QJniObject::getStaticObjectField(jclass clazz, const char *fieldName,
{
JNIEnv *env = QJniEnvironment::getJniEnv();
jfieldID id = getFieldID(env, clazz, fieldName, signature, true);
- return getCleanJniObject(env->GetStaticObjectField(clazz, id), env);
+
+ const auto clearExceptions = qScopeGuard([env]{
+ QJniEnvironment::checkAndClearExceptions(env);
+ });
+
+ return getCleanJniObject(getStaticObjectFieldImpl(env, clazz, id), env);
}
/*!
- \fn template <typename T> void QJniObject::setField(const char *fieldName, const char *signature, T value)
+ \fn template <typename Ret, typename Type> void QJniObject::setField(const char *fieldName, const char *signature, Type value)
Sets the value of \a fieldName with \a signature to \a value.
@@ -1304,14 +1341,16 @@ QJniObject QJniObject::getObjectField(const char *fieldName, const char *signatu
{
JNIEnv *env = jniEnv();
jfieldID id = getCachedFieldID(env, fieldName, signature);
- if (!id)
- return QJniObject();
- return getCleanJniObject(env->GetObjectField(d->m_jobject, id), env);
+ const auto clearExceptions = qScopeGuard([env]{
+ QJniEnvironment::checkAndClearExceptions(env);
+ });
+
+ return getCleanJniObject(getObjectFieldImpl(env, id), env);
}
/*!
- \fn template <typename T> void QJniObject::setField(const char *fieldName, T value)
+ \fn template <typename Ret, typename Type> void QJniObject::setField(const char *fieldName, Type value)
Sets the value of \a fieldName to \a value.
diff --git a/src/corelib/kernel/qjniobject.h b/src/corelib/kernel/qjniobject.h
index 236a49544be..06dfc328b4b 100644
--- a/src/corelib/kernel/qjniobject.h
+++ b/src/corelib/kernel/qjniobject.h
@@ -27,10 +27,29 @@ struct StoresGlobalRefTest<T, std::void_t<decltype(std::declval<T>().object())>>
: std::is_same<decltype(std::declval<T>().object()), jobject>
{};
-template <typename ...Args>
+// detect if a type is std::expected-like
+template <typename R, typename = void>
+struct CallerHandlesException : std::false_type {
+ using value_type = R;
+};
+template <typename R>
+struct CallerHandlesException<R, std::void_t<typename R::unexpected_type,
+ typename R::value_type,
+ typename R::error_type>> : std::true_type
+{
+ using value_type = typename R::value_type;
+};
+
+template <typename ReturnType>
+static constexpr bool callerHandlesException = CallerHandlesException<ReturnType>::value;
+
+template <typename Ret, typename ...Args>
struct LocalFrame {
+ using ReturnType = Ret;
+
mutable JNIEnv *env;
bool hasFrame = false;
+
explicit LocalFrame(JNIEnv *env = nullptr) noexcept
: env(env)
{
@@ -52,9 +71,12 @@ struct LocalFrame {
env = QJniEnvironment::getJniEnv();
return env;
}
- bool checkAndClearExceptions()
+ bool checkAndClearExceptions() const
{
- return env ? QJniEnvironment::checkAndClearExceptions(env) : false;
+ if constexpr (callerHandlesException<ReturnType>)
+ return false;
+ else
+ return QJniEnvironment::checkAndClearExceptions(jniEnv());
}
template <typename T>
auto convertToJni(T &&value)
@@ -79,13 +101,46 @@ struct LocalFrame {
using Type = q20::remove_cvref_t<T>;
return QtJniTypes::Traits<Type>::convertFromJni(std::move(object));
}
+
+ template <typename T>
+ auto convertFromJni(jobject object);
+
+ auto makeResult()
+ {
+ if constexpr (callerHandlesException<ReturnType>) {
+ JNIEnv *env = jniEnv();
+ if (env->ExceptionCheck()) {
+ jthrowable exception = env->ExceptionOccurred();
+ env->ExceptionClear();
+ return ReturnType(typename ReturnType::unexpected_type(exception));
+ }
+ return ReturnType();
+ } else {
+ checkAndClearExceptions();
+ }
+ }
+
+ template <typename Value>
+ auto makeResult(Value &&value)
+ {
+ if constexpr (callerHandlesException<ReturnType>) {
+ auto maybeValue = makeResult();
+ if (maybeValue)
+ return ReturnType(std::forward<Value>(value));
+ return std::move(maybeValue);
+ } else {
+ checkAndClearExceptions();
+ return std::forward<Value>(value);
+ }
+ }
};
}
}
class Q_CORE_EXPORT QJniObject
{
- template <typename ...Args> using LocalFrame = QtJniTypes::Detail::LocalFrame<Args...>;
+ template <typename Ret, typename ...Args> using LocalFrame
+ = QtJniTypes::Detail::LocalFrame<Ret, Args...>;
public:
QJniObject();
@@ -97,12 +152,12 @@ public:
#endif
>
explicit QJniObject(const char *className, Args &&...args)
- : QJniObject(LocalFrame<Args...>{}, className, std::forward<Args>(args)...)
+ : QJniObject(LocalFrame<QJniObject, Args...>{}, className, std::forward<Args>(args)...)
{
}
private:
template<typename ...Args>
- explicit QJniObject(LocalFrame<Args...> localFrame, const char *className, Args &&...args)
+ explicit QJniObject(LocalFrame<QJniObject, Args...> localFrame, const char *className, Args &&...args)
: QJniObject(className, QtJniTypes::constructorSignature<Args...>().data(),
localFrame.convertToJni(std::forward<Args>(args))...)
{
@@ -130,13 +185,23 @@ public:
void swap(QJniObject &other) noexcept { d.swap(other.d); }
- template<typename Class, typename ...Args>
- static inline QJniObject construct(Args &&...args)
+ template<typename Class, typename ...Args
+#ifndef Q_QDOC
+ , QtJniTypes::IfValidSignatureTypes<Class, Args...> = true
+#endif
+ >
+ static inline auto construct(Args &&...args)
{
- LocalFrame<Args...> frame;
- return QJniObject(QtJniTypes::Traits<Class>::className().data(),
- QtJniTypes::constructorSignature<Args...>().data(),
- frame.convertToJni(std::forward<Args>(args))...);
+ LocalFrame<Class, Args...> frame;
+ jclass clazz = QJniObject::loadClassKeepExceptions(QtJniTypes::Traits<Class>::className().data(),
+ frame.jniEnv());
+ auto res = clazz
+ ? QJniObject(clazz, QtJniTypes::constructorSignature<Args...>().data(),
+ frame.convertToJni(std::forward<Args>(args))...)
+ : QtJniTypes::Detail::callerHandlesException<Class>
+ ? QJniObject(Qt::Initialization::Uninitialized)
+ : QJniObject();
+ return frame.makeResult(std::move(res));
}
jobject object() const;
@@ -149,47 +214,49 @@ public:
jclass objectClass() const;
QByteArray className() const;
- template <typename Ret = void, typename ...Args
+ template <typename ReturnType = void, typename ...Args
#ifndef Q_QDOC
- , QtJniTypes::IfValidFieldType<Ret> = true
+ , QtJniTypes::IfValidFieldType<ReturnType> = true
#endif
>
auto callMethod(const char *methodName, const char *signature, Args &&...args) const
{
- LocalFrame<Args...> frame(jniEnv());
+ using Ret = typename QtJniTypes::Detail::CallerHandlesException<ReturnType>::value_type;
+ LocalFrame<ReturnType, Args...> frame(jniEnv());
+ jmethodID id = getCachedMethodID(frame.jniEnv(), methodName, signature);
+
if constexpr (QtJniTypes::isObjectType<Ret>()) {
- return frame.template convertFromJni<Ret>(callObjectMethod(methodName, signature,
- frame.convertToJni(std::forward<Args>(args))...));
+ return frame.makeResult(frame.template convertFromJni<Ret>(callObjectMethodImpl(
+ id, frame.convertToJni(std::forward<Args>(args))...))
+ );
} else {
- jmethodID id = getCachedMethodID(frame.jniEnv(), methodName, signature);
if (id) {
if constexpr (std::is_same_v<Ret, void>) {
callVoidMethodV(frame.jniEnv(), id,
frame.convertToJni(std::forward<Args>(args))...);
- frame.checkAndClearExceptions();
} else {
Ret res{};
callMethodForType<Ret>(frame.jniEnv(), res, object(), id,
frame.convertToJni(std::forward<Args>(args))...);
- if (frame.checkAndClearExceptions())
- res = {};
- return res;
+ return frame.makeResult(res);
}
}
if constexpr (!std::is_same_v<Ret, void>)
- return Ret{};
+ return frame.makeResult(Ret{});
+ else
+ return frame.makeResult();
}
}
- template <typename Ret = void, typename ...Args
+ template <typename ReturnType = void, typename ...Args
#ifndef Q_QDOC
- , QtJniTypes::IfValidSignatureTypes<Ret, Args...> = true
+ , QtJniTypes::IfValidSignatureTypes<ReturnType, Args...> = true
#endif
>
auto callMethod(const char *methodName, Args &&...args) const
{
- constexpr auto signature = QtJniTypes::methodSignature<Ret, Args...>();
- return callMethod<Ret>(methodName, signature.data(), std::forward<Args>(args)...);
+ constexpr auto signature = QtJniTypes::methodSignature<ReturnType, Args...>();
+ return callMethod<ReturnType>(methodName, signature.data(), std::forward<Args>(args)...);
}
template <typename Ret, typename ...Args
@@ -201,9 +268,11 @@ public:
{
QtJniTypes::assertObjectType<Ret>();
constexpr auto signature = QtJniTypes::methodSignature<Ret, Args...>();
- LocalFrame<Args...> frame(jniEnv());
- return frame.template convertFromJni<Ret>(callObjectMethod(methodName, signature,
+ LocalFrame<Ret, Args...> frame(jniEnv());
+ auto object = frame.template convertFromJni<Ret>(callObjectMethod(methodName, signature,
frame.convertToJni(std::forward<Args>(args))...));
+ frame.checkAndClearExceptions();
+ return object;
}
QJniObject callObjectMethod(const char *methodName, const char *signature, ...) const;
@@ -211,90 +280,93 @@ public:
template <typename Ret = void, typename ...Args>
static auto callStaticMethod(const char *className, const char *methodName, const char *signature, Args &&...args)
{
- JNIEnv *env = QJniEnvironment::getJniEnv();
- jclass clazz = QJniObject::loadClass(className, env);
+ LocalFrame<Ret, Args...> frame;
+ jclass clazz = QJniObject::loadClass(className, frame.jniEnv());
return callStaticMethod<Ret>(clazz, methodName, signature, std::forward<Args>(args)...);
}
template <typename Ret = void, typename ...Args>
static auto callStaticMethod(jclass clazz, const char *methodName, const char *signature, Args &&...args)
{
- JNIEnv *env = QJniEnvironment::getJniEnv();
- jmethodID id = clazz ? getMethodID(env, clazz, methodName, signature, true)
+ LocalFrame<Ret, Args...> frame;
+ jmethodID id = clazz ? getMethodID(frame.jniEnv(), clazz, methodName, signature, true)
: 0;
return callStaticMethod<Ret>(clazz, id, std::forward<Args>(args)...);
}
- template <typename Ret = void, typename ...Args
+ template <typename ReturnType = void, typename ...Args
#ifndef Q_QDOC
- , QtJniTypes::IfValidFieldType<Ret> = true
+ , QtJniTypes::IfValidFieldType<ReturnType> = true
#endif
>
static auto callStaticMethod(jclass clazz, jmethodID methodId, Args &&...args)
{
- LocalFrame<Args...> frame;
+ using Ret = typename QtJniTypes::Detail::CallerHandlesException<ReturnType>::value_type;
+ LocalFrame<ReturnType, Args...> frame;
if constexpr (QtJniTypes::isObjectType<Ret>()) {
- return frame.template convertFromJni<Ret>(callStaticObjectMethod(clazz, methodId,
- frame.convertToJni(std::forward<Args>(args))...));
+ return frame.makeResult(frame.template convertFromJni<Ret>(callStaticObjectMethod(
+ clazz, methodId,
+ frame.convertToJni(std::forward<Args>(args))...))
+ );
} else {
if (clazz && methodId) {
if constexpr (std::is_same_v<Ret, void>) {
callStaticMethodForVoid(frame.jniEnv(), clazz, methodId,
frame.convertToJni(std::forward<Args>(args))...);
- frame.checkAndClearExceptions();
} else {
Ret res{};
callStaticMethodForType<Ret>(frame.jniEnv(), res, clazz, methodId,
frame.convertToJni(std::forward<Args>(args))...);
- if (frame.checkAndClearExceptions())
- res = {};
- return res;
+ return frame.makeResult(res);
}
}
if constexpr (!std::is_same_v<Ret, void>)
- return Ret{};
+ return frame.makeResult(Ret{});
+ else
+ return frame.makeResult();
}
}
- template <typename Ret = void, typename ...Args
+ template <typename ReturnType = void, typename ...Args
#ifndef Q_QDOC
- , QtJniTypes::IfValidSignatureTypes<Ret, Args...> = true
+ , QtJniTypes::IfValidSignatureTypes<ReturnType, Args...> = true
#endif
>
static auto callStaticMethod(const char *className, const char *methodName, Args &&...args)
{
- JNIEnv *env = QJniEnvironment::getJniEnv();
- jclass clazz = QJniObject::loadClass(className, env);
- const jmethodID id = clazz ? getMethodID(env, clazz, methodName,
+ using Ret = typename QtJniTypes::Detail::CallerHandlesException<ReturnType>::value_type;
+ LocalFrame<Ret, Args...> frame;
+ jclass clazz = QJniObject::loadClass(className, frame.jniEnv());
+ const jmethodID id = clazz ? getMethodID(frame.jniEnv(), clazz, methodName,
QtJniTypes::methodSignature<Ret, Args...>().data(), true)
: 0;
- return callStaticMethod<Ret>(clazz, id, std::forward<Args>(args)...);
+ return callStaticMethod<ReturnType>(clazz, id, std::forward<Args>(args)...);
}
- template <typename Ret = void, typename ...Args
+ template <typename ReturnType = void, typename ...Args
#ifndef Q_QDOC
- , QtJniTypes::IfValidSignatureTypes<Ret, Args...> = true
+ , QtJniTypes::IfValidSignatureTypes<ReturnType, Args...> = true
#endif
>
static auto callStaticMethod(jclass clazz, const char *methodName, Args &&...args)
{
- constexpr auto signature = QtJniTypes::methodSignature<Ret, Args...>();
- return callStaticMethod<Ret>(clazz, methodName, signature.data(), std::forward<Args>(args)...);
+ constexpr auto signature = QtJniTypes::methodSignature<ReturnType, Args...>();
+ return callStaticMethod<ReturnType>(clazz, methodName, signature.data(), std::forward<Args>(args)...);
}
- template <typename Klass, typename Ret = void, typename ...Args
+ template <typename Klass, typename ReturnType = void, typename ...Args
#ifndef Q_QDOC
- , QtJniTypes::IfValidSignatureTypes<Ret, Args...> = true
+ , QtJniTypes::IfValidSignatureTypes<ReturnType, Args...> = true
#endif
>
static auto callStaticMethod(const char *methodName, Args &&...args)
{
- JNIEnv *env = QJniEnvironment::getJniEnv();
+ LocalFrame<ReturnType, Args...> frame;
const jclass clazz = QJniObject::loadClass(QtJniTypes::Traits<Klass>::className().data(),
- env);
- const jmethodID id = clazz ? getMethodID(env, clazz, methodName,
- QtJniTypes::methodSignature<Ret, Args...>().data(), true)
+ frame.jniEnv());
+ const jmethodID id = clazz ? getMethodID(frame.jniEnv(), clazz, methodName,
+ QtJniTypes::methodSignature<ReturnType, Args...>().data(), true)
: 0;
- return callStaticMethod<Ret>(clazz, id, std::forward<Args>(args)...);
+ return callStaticMethod<ReturnType>(clazz, id, std::forward<Args>(args)...);
}
static QJniObject callStaticObjectMethod(const char *className, const char *methodName,
@@ -315,7 +387,7 @@ public:
{
QtJniTypes::assertObjectType<Ret>();
constexpr auto signature = QtJniTypes::methodSignature<Ret, Args...>();
- LocalFrame<Args...> frame;
+ LocalFrame<QJniObject, Args...> frame;
return frame.template convertFromJni<Ret>(callStaticObjectMethod(className, methodName, signature.data(),
frame.convertToJni(std::forward<Args>(args))...));
}
@@ -329,98 +401,95 @@ public:
{
QtJniTypes::assertObjectType<Ret>();
constexpr auto signature = QtJniTypes::methodSignature<Ret, Args...>();
- LocalFrame<Args...> frame;
+ LocalFrame<QJniObject, Args...> frame;
return frame.template convertFromJni<Ret>(callStaticObjectMethod(clazz, methodName, signature.data(),
- frame.convertToJni(std::forward<Args>(args))...));
+ frame.convertToJni(std::forward<Args>(args))...));
}
- template <typename T
+ template <typename Type
#ifndef Q_QDOC
- , QtJniTypes::IfValidFieldType<T> = true
+ , QtJniTypes::IfValidFieldType<Type> = true
#endif
>
auto getField(const char *fieldName) const
{
- LocalFrame<T> frame(jniEnv());
+ using T = typename QtJniTypes::Detail::CallerHandlesException<Type>::value_type;
+ LocalFrame<Type, T> frame(jniEnv());
+ constexpr auto signature = QtJniTypes::fieldSignature<T>();
+ jfieldID id = getCachedFieldID(frame.jniEnv(), fieldName, signature);
+
if constexpr (QtJniTypes::isObjectType<T>()) {
- return frame.template convertFromJni<T>(getObjectField<T>(fieldName));
+ return frame.makeResult(frame.template convertFromJni<T>(getObjectFieldImpl(
+ frame.jniEnv(), id))
+ );
} else {
T res{};
- constexpr auto signature = QtJniTypes::fieldSignature<T>();
- jfieldID id = getCachedFieldID(frame.jniEnv(), fieldName, signature);
- if (id) {
+ if (id)
getFieldForType<T>(frame.jniEnv(), res, object(), id);
- if (frame.checkAndClearExceptions())
- res = {};
- }
- return res;
+ return frame.makeResult(res);
}
}
- template <typename T
+ template <typename Klass, typename T
#ifndef Q_QDOC
, QtJniTypes::IfValidFieldType<T> = true
#endif
>
- static auto getStaticField(const char *className, const char *fieldName)
+ static auto getStaticField(const char *fieldName)
{
- LocalFrame<T> frame;
- if constexpr (QtJniTypes::isObjectType<T>()) {
- return frame.template convertFromJni<T>(getStaticObjectField<T>(className, fieldName));
- } else {
- jclass clazz = QJniObject::loadClass(className, frame.jniEnv());
- if (!clazz)
- return T{};
- return getStaticField<T>(clazz, fieldName);
- }
+ return getStaticField<T>(QtJniTypes::Traits<Klass>::className(), fieldName);
}
template <typename T
#ifndef Q_QDOC
- , QtJniTypes::IfValidFieldType<T> = true
+ , std::enable_if_t<QtJniTypes::isObjectType<T>(), bool> = true
#endif
>
- static auto getStaticField(jclass clazz, const char *fieldName)
+ QJniObject getObjectField(const char *fieldName) const
{
- LocalFrame<T> frame;
- if constexpr (QtJniTypes::isObjectType<T>()) {
- return frame.template convertFromJni<T>(getStaticObjectField<T>(clazz, fieldName));
- } else {
- T res{};
- constexpr auto signature = QtJniTypes::fieldSignature<T>();
- jfieldID id = getFieldID(frame.jniEnv(), clazz, fieldName, signature, true);
- if (id) {
- getStaticFieldForType<T>(frame.jniEnv(), res, clazz, id);
- if (frame.checkAndClearExceptions())
- res = {};
- }
- return res;
- }
+ constexpr auto signature = QtJniTypes::fieldSignature<T>();
+ return getObjectField(fieldName, signature);
}
- template <typename Klass, typename T
+ QJniObject getObjectField(const char *fieldName, const char *signature) const;
+
+ template <typename Type
#ifndef Q_QDOC
- , QtJniTypes::IfValidFieldType<T> = true
+ , QtJniTypes::IfValidFieldType<Type> = true
#endif
>
- static auto getStaticField(const char *fieldName)
+ static auto getStaticField(const char *className, const char *fieldName)
{
- return getStaticField<T>(QtJniTypes::Traits<Klass>::className(), fieldName);
+ using T = typename QtJniTypes::Detail::CallerHandlesException<Type>::value_type;
+ LocalFrame<Type, T> frame;
+ jclass clazz = QJniObject::loadClass(className, frame.jniEnv());
+ return getStaticField<Type>(clazz, fieldName);
}
- template <typename T
+ template <typename Type
#ifndef Q_QDOC
- , std::enable_if_t<QtJniTypes::isObjectType<T>(), bool> = true
+ , QtJniTypes::IfValidFieldType<Type> = true
#endif
>
- QJniObject getObjectField(const char *fieldName) const
+ static auto getStaticField(jclass clazz, const char *fieldName)
{
+ using T = typename QtJniTypes::Detail::CallerHandlesException<Type>::value_type;
+ LocalFrame<Type, T> frame;
constexpr auto signature = QtJniTypes::fieldSignature<T>();
- return getObjectField(fieldName, signature);
+ jfieldID id = clazz ? getFieldID(frame.jniEnv(), clazz, fieldName, signature, true)
+ : nullptr;
+ if constexpr (QtJniTypes::isObjectType<T>()) {
+ return frame.makeResult(frame.template convertFromJni<T>(getStaticObjectFieldImpl(
+ frame.jniEnv(), clazz, id))
+ );
+ } else {
+ T res{};
+ if (id)
+ getStaticFieldForType<T>(frame.jniEnv(), res, clazz, id);
+ return frame.makeResult(res);
+ }
}
- QJniObject getObjectField(const char *fieldName, const char *signature) const;
-
template <typename T
#ifndef Q_QDOC
, std::enable_if_t<QtJniTypes::isObjectType<T>(), bool> = true
@@ -450,114 +519,122 @@ public:
static QJniObject getStaticObjectField(jclass clazz, const char *fieldName,
const char *signature);
- template <typename T
+ template <typename Ret = void, typename Type
#ifndef Q_QDOC
- , QtJniTypes::IfValidFieldType<T> = true
+ , QtJniTypes::IfValidFieldType<Type> = true
#endif
>
- void setField(const char *fieldName, T value)
+ auto setField(const char *fieldName, Type value)
{
+ // handle old code explicitly specifying a non-return type for Ret
+ using T = std::conditional_t<!std::is_void_v<Ret> && !QtJniTypes::Detail::callerHandlesException<Ret>,
+ Ret, Type>;
+ LocalFrame<Ret, T> frame(jniEnv());
constexpr auto signature = QtJniTypes::fieldSignature<T>();
jfieldID id = getCachedFieldID(jniEnv(), fieldName, signature);
- if (id) {
+ if (id)
setFieldForType<T>(jniEnv(), object(), id, value);
- QJniEnvironment::checkAndClearExceptions(jniEnv());
- }
+ return frame.makeResult();
}
- template <typename T
+ template <typename Ret = void, typename Type
#ifndef Q_QDOC
- , QtJniTypes::IfValidFieldType<T> = true
+ , QtJniTypes::IfValidFieldType<Type> = true
#endif
>
- void setField(const char *fieldName, const char *signature, T value)
- {
- jfieldID id = getCachedFieldID(jniEnv(), fieldName, signature);
- if (id) {
+ auto setField(const char *fieldName, const char *signature, Type value)
+ {
+ // handle old code explicitly specifying a non-return type for Ret
+ using T = std::conditional_t<!std::is_void_v<Ret> && !QtJniTypes::Detail::callerHandlesException<Ret>,
+ Ret, Type>;
+ LocalFrame<Ret, T> frame(jniEnv());
+ jfieldID id = getCachedFieldID(frame.jniEnv(), fieldName, signature);
+ if (id)
setFieldForType<T>(jniEnv(), object(), id, value);
- QJniEnvironment::checkAndClearExceptions(jniEnv());
- }
+ return frame.makeResult();
}
- template <typename T
+ template <typename Ret = void, typename Type
#ifndef Q_QDOC
- , QtJniTypes::IfValidFieldType<T> = true
+ , QtJniTypes::IfValidFieldType<Type> = true
#endif
>
- static void setStaticField(const char *className, const char *fieldName, T value)
- {
- LocalFrame<T> frame;
- jclass clazz = QJniObject::loadClass(className, frame.jniEnv());
- if (!clazz)
- return;
-
- constexpr auto signature = QtJniTypes::fieldSignature<T>();
- jfieldID id = getCachedFieldID(frame.jniEnv(), clazz, className, fieldName,
- signature, true);
- if (!id)
- return;
-
- setStaticFieldForType<T>(frame.jniEnv(), clazz, id, value);
- frame.checkAndClearExceptions();
+ static auto setStaticField(const char *className, const char *fieldName, Type value)
+ {
+ // handle old code explicitly specifying a non-return type for Ret
+ using T = std::conditional_t<!std::is_void_v<Ret> && !QtJniTypes::Detail::callerHandlesException<Ret>,
+ Ret, Type>;
+ LocalFrame<Ret, T> frame;
+ if (jclass clazz = QJniObject::loadClass(className, frame.jniEnv())) {
+ constexpr auto signature = QtJniTypes::fieldSignature<q20::remove_cvref_t<T>>();
+ jfieldID id = getCachedFieldID(frame.jniEnv(), clazz, className, fieldName,
+ signature, true);
+ if (id)
+ setStaticFieldForType<T>(frame.jniEnv(), clazz, id, value);
+ }
+ return frame.makeResult();
}
- template <typename T
+ template <typename Ret = void, typename Type
#ifndef Q_QDOC
- , QtJniTypes::IfValidFieldType<T> = true
+ , QtJniTypes::IfValidFieldType<Type> = true
#endif
>
- static void setStaticField(const char *className, const char *fieldName,
- const char *signature, T value)
- {
- JNIEnv *env = QJniEnvironment::getJniEnv();
- jclass clazz = QJniObject::loadClass(className, env);
-
- if (!clazz)
- return;
-
- jfieldID id = getCachedFieldID(env, clazz, className, fieldName,
- signature, true);
- if (id) {
- setStaticFieldForType<T>(env, clazz, id, value);
- QJniEnvironment::checkAndClearExceptions(env);
+ static auto setStaticField(const char *className, const char *fieldName,
+ const char *signature, Type value)
+ {
+ // handle old code explicitly specifying a non-return type for Ret
+ using T = std::conditional_t<!std::is_void_v<Ret> && !QtJniTypes::Detail::callerHandlesException<Ret>,
+ Ret, Type>;
+ LocalFrame<Ret, T> frame;
+ if (jclass clazz = QJniObject::loadClass(className, frame.jniEnv())) {
+ jfieldID id = getCachedFieldID(frame.jniEnv(), clazz, className, fieldName,
+ signature, true);
+ if (id)
+ setStaticFieldForType<T>(frame.jniEnv(), clazz, id, value);
}
+ return frame.makeResult();
}
- template <typename T
+ template <typename Ret = void, typename Type
#ifndef Q_QDOC
- , QtJniTypes::IfValidFieldType<T> = true
+ , QtJniTypes::IfValidFieldType<Type> = true
#endif
>
- static void setStaticField(jclass clazz, const char *fieldName,
- const char *signature, T value)
+ static auto setStaticField(jclass clazz, const char *fieldName,
+ const char *signature, Type value)
{
- JNIEnv *env = QJniEnvironment::getJniEnv();
- jfieldID id = getFieldID(env, clazz, fieldName, signature, true);
+ // handle old code explicitly specifying a non-return type for Ret
+ using T = std::conditional_t<!std::is_void_v<Ret> && !QtJniTypes::Detail::callerHandlesException<Ret>,
+ Ret, Type>;
+ LocalFrame<Ret, T> frame;
+ jfieldID id = getFieldID(frame.jniEnv(), clazz, fieldName, signature, true);
- if (id) {
- setStaticFieldForType<T>(env, clazz, id, value);
- QJniEnvironment::checkAndClearExceptions(env);
- }
+ if (id)
+ setStaticFieldForType<T>(frame.jniEnv(), clazz, id, value);
+ return frame.makeResult();
}
- template <typename T
+ template <typename Ret = void, typename Type
#ifndef Q_QDOC
- , QtJniTypes::IfValidFieldType<T> = true
+ , QtJniTypes::IfValidFieldType<Type> = true
#endif
>
- static void setStaticField(jclass clazz, const char *fieldName, T value)
+ static auto setStaticField(jclass clazz, const char *fieldName, Type value)
{
- setStaticField(clazz, fieldName, QtJniTypes::fieldSignature<T>(), value);
+ return setStaticField<Ret, Type>(clazz, fieldName,
+ QtJniTypes::fieldSignature<q20::remove_cvref_t<Type>>(),
+ value);
}
- template <typename Klass, typename T
+ template <typename Klass, typename Ret = void, typename Type
#ifndef Q_QDOC
- , QtJniTypes::IfValidFieldType<T> = true
+ , QtJniTypes::IfValidFieldType<Type> = true
#endif
>
- static void setStaticField(const char *fieldName, T value)
+ static auto setStaticField(const char *fieldName, Type value)
{
- setStaticField(QtJniTypes::Traits<Klass>::className(), fieldName, value);
+ return setStaticField<Ret, Type>(QtJniTypes::Traits<Klass>::className(), fieldName, value);
}
static QJniObject fromString(const QString &string);
@@ -583,6 +660,7 @@ protected:
private:
static jclass loadClass(const QByteArray &className, JNIEnv *env);
+ static jclass loadClassKeepExceptions(const QByteArray &className, JNIEnv *env);
#if QT_CORE_REMOVED_SINCE(6, 7)
// these need to stay in the ABI as they were used in inline methods before 6.7
@@ -620,12 +698,25 @@ private:
template<typename T>
static constexpr void callMethodForType(JNIEnv *env, T &res, jobject obj, jmethodID id, ...)
{
+ if (!id)
+ return;
+
va_list args = {};
va_start(args, id);
QtJniTypes::Caller<T>::callMethodForType(env, res, obj, id, args);
va_end(args);
}
+ jobject callObjectMethodImpl(jmethodID method, ...) const
+ {
+ va_list args;
+ va_start(args, method);
+ jobject res = method ? jniEnv()->CallObjectMethodV(javaObject(), method, args)
+ : nullptr;
+ va_end(args);
+ return res;
+ }
+
template<typename T>
static constexpr void callStaticMethodForType(JNIEnv *env, T &res, jclass clazz,
jmethodID id, ...)
@@ -652,6 +743,9 @@ private:
template<typename T>
static constexpr void getFieldForType(JNIEnv *env, T &res, jobject obj, jfieldID id)
{
+ if (!id)
+ return;
+
QtJniTypes::Caller<T>::getFieldForType(env, res, obj, id);
}
@@ -661,22 +755,42 @@ private:
QtJniTypes::Caller<T>::getStaticFieldForType(env, res, clazz, id);
}
- template<typename T>
- static constexpr void setFieldForType(JNIEnv *env, jobject obj, jfieldID id, T value)
+ template<typename Type>
+ static constexpr void setFieldForType(JNIEnv *env, jobject obj, jfieldID id, Type value)
{
+ if (!id)
+ return;
+
+ using T = q20::remove_cvref_t<Type>;
if constexpr (QtJniTypes::isObjectType<T>()) {
- LocalFrame<T> frame(env);
+ LocalFrame<T, T> frame(env);
env->SetObjectField(obj, id, static_cast<jobject>(frame.convertToJni(value)));
} else {
- QtJniTypes::Caller<T>::setFieldForType(env, obj, id, value);
+ using ValueType = typename QtJniTypes::Detail::CallerHandlesException<T>::value_type;
+ QtJniTypes::Caller<ValueType>::setFieldForType(env, obj, id, value);
}
}
- template<typename T>
- static constexpr void setStaticFieldForType(JNIEnv *env, jclass clazz, jfieldID id, T value)
+ jobject getObjectFieldImpl(JNIEnv *env, jfieldID field) const
+ {
+ return field ? env->GetObjectField(javaObject(), field) : nullptr;
+ }
+
+ static jobject getStaticObjectFieldImpl(JNIEnv *env, jclass clazz, jfieldID field)
+ {
+ return clazz && field ? env->GetStaticObjectField(clazz, field)
+ : nullptr;
+ }
+
+ template<typename Type>
+ static constexpr void setStaticFieldForType(JNIEnv *env, jclass clazz, jfieldID id, Type value)
{
+ if (!clazz || !id)
+ return;
+
+ using T = q20::remove_cvref_t<Type>;
if constexpr (QtJniTypes::isObjectType<T>()) {
- LocalFrame<T> frame(env);
+ LocalFrame<T, T> frame(env);
env->SetStaticObjectField(clazz, id, static_cast<jobject>(frame.convertToJni(value)));
} else {
QtJniTypes::Caller<T>::setStaticFieldForType(env, clazz, id, value);
@@ -797,14 +911,14 @@ public:
{
return QJniObject::getStaticField<Class, T>(field);
}
- template <typename T
+ template <typename Ret = void, typename T
#ifndef Q_QDOC
, QtJniTypes::IfValidFieldType<T> = true
#endif
>
- static void setStaticField(const char *field, T &&value)
+ static auto setStaticField(const char *field, T &&value)
{
- QJniObject::setStaticField<Class, T>(field, std::forward<T>(value));
+ return QJniObject::setStaticField<Class, Ret, T>(field, std::forward<T>(value));
}
// keep only these overloads, the rest is made private
@@ -827,14 +941,14 @@ public:
return m_object.getField<T>(fieldName);
}
- template <typename T
+ template <typename Ret = void, typename T
#ifndef Q_QDOC
, QtJniTypes::IfValidFieldType<T> = true
#endif
>
- void setField(const char *fieldName, T &&value)
+ auto setField(const char *fieldName, T &&value)
{
- m_object.setField(fieldName, std::forward<T>(value));
+ return m_object.setField<Ret>(fieldName, std::forward<T>(value));
}
QByteArray className() const {
@@ -911,6 +1025,37 @@ struct Traits<QString>
}
};
+template <typename T>
+struct Traits<T, std::enable_if_t<QtJniTypes::Detail::callerHandlesException<T>>>
+{
+ static constexpr auto className()
+ {
+ return Traits<typename T::value_type>::className();
+ }
+
+ static constexpr auto signature()
+ {
+ return Traits<typename T::value_type>::signature();
+ }
+};
+
+}
+
+template <typename ReturnType, typename ...Args>
+template <typename T>
+auto QtJniTypes::Detail::LocalFrame<ReturnType, Args...>::convertFromJni(jobject object)
+{
+ // If the caller wants to handle exceptions through a std::expected-like
+ // type, then we cannot turn the jobject into a QJniObject, as a
+ // std::expected<jobject, jthrowable> cannot be constructed from a QJniObject.
+ // The caller will have to take care of this themselves, by asking for a
+ // std::expected<QJniObject, ...>, or (typically) using a declared JNI class
+ // or implicitly supported Qt type (QString or array type).
+ if constexpr (callerHandlesException<ReturnType> &&
+ std::is_base_of_v<std::remove_pointer_t<jobject>, std::remove_pointer_t<T>>)
+ return static_cast<T>(object);
+ else
+ return convertFromJni<T>(object ? QJniObject::fromLocalRef(object) : QJniObject());
}
QT_END_NAMESPACE
diff --git a/src/corelib/time/qdatetime.cpp b/src/corelib/time/qdatetime.cpp
index deac396061d..34e910fabec 100644
--- a/src/corelib/time/qdatetime.cpp
+++ b/src/corelib/time/qdatetime.cpp
@@ -3894,10 +3894,12 @@ QDateTime::Data QDateTimePrivate::create(QDate toDate, QTime toTime, const QTime
Selects a time on the standard time side of the transition.
\value PreferDaylightSaving
Selects a time on the daylight-saving-time side of the transition.
- \value LegacyBehavior
- An alias for RelativeToBefore, which is used as default for
- TransitionResolution parameters, as this most closely matches the
- behavior prior to Qt 6.7.
+ \omitvalue LegacyBehavior
+
+ An additional constant, \c LegacyBehavior, is used as a default value for
+ TransitionResolution parameters in some constructors and setter functions.
+ This is an alias for \c RelativeToBefore, which implements behavior that
+ most closely matches the behavior of QDateTime prior to Qt 6.7.
For \l addDays(), \l addMonths() or \l addYears(), the behavior is and
(mostly) was to use \c RelativeToBefore if adding a positive adjustment and \c
@@ -3909,7 +3911,7 @@ QDateTime::Data QDateTimePrivate::create(QDate toDate, QTime toTime, const QTime
where the daylight-saving mechanism is a decrease in offset from UTC in
winter (known as "negative DST"), the reverse applies, provided the
operating system reports - as it does on most platforms - whether a datetime
- is in DST or standard time. For some platforms, where transition times are
+ is in DST or standard time. For some platforms, where transition details are
unavailable even for Qt::TimeZone datetimes, QTimeZone is obliged to presume
that the side with lower offset from UTC is standard time, effectively
assuming positive DST.
diff --git a/src/corelib/tools/qflatmap_p.h b/src/corelib/tools/qflatmap_p.h
index 585eab33ae8..50b7c6bd310 100644
--- a/src/corelib/tools/qflatmap_p.h
+++ b/src/corelib/tools/qflatmap_p.h
@@ -16,6 +16,7 @@
//
#include "qlist.h"
+#include <QtCore/qtclasshelpermacros.h>
#include "private/qglobal_p.h"
#include <algorithm>
@@ -44,8 +45,7 @@ QT_BEGIN_NAMESPACE
namespace Qt {
-struct OrderedUniqueRange_t {};
-constexpr OrderedUniqueRange_t OrderedUniqueRange = {};
+QT_DEFINE_TAG(OrderedUniqueRange);
} // namespace Qt
diff --git a/src/gui/text/qfont.cpp b/src/gui/text/qfont.cpp
index b60cad34a8d..2b2f2a27fcd 100644
--- a/src/gui/text/qfont.cpp
+++ b/src/gui/text/qfont.cpp
@@ -2201,7 +2201,7 @@ QString QFont::toString() const
fontDescription += comma + QString::number(sortedFeatures.size());
for (const auto &[tag, value] : std::as_const(sortedFeatures).asKeyValueRange())
- fontDescription += comma + tag.toString() + u'=' + QString::number(value);
+ fontDescription += comma + QLatin1StringView{tag.toString()} + u'=' + QString::number(value);
return fontDescription;
}
diff --git a/src/plugins/platforms/windows/qwindowswindowclassdescription.cpp b/src/plugins/platforms/windows/qwindowswindowclassdescription.cpp
index abd6f02fb0b..55e36b4587a 100644
--- a/src/plugins/platforms/windows/qwindowswindowclassdescription.cpp
+++ b/src/plugins/platforms/windows/qwindowswindowclassdescription.cpp
@@ -41,6 +41,25 @@ QString QWindowsWindowClassDescription::classNameSuffix(Qt::WindowFlags type, un
return suffix;
}
+bool QWindowsWindowClassDescription::computeHasIcon(Qt::WindowFlags flags, Qt::WindowFlags type)
+{
+ bool hasIcon = true;
+
+ switch (type) {
+ case Qt::Tool:
+ case Qt::ToolTip:
+ case Qt::Popup:
+ hasIcon = false;
+ break;
+ case Qt::Dialog:
+ if (!(flags & Qt::WindowSystemMenuHint))
+ hasIcon = false; // QTBUG-2027, dialogs without system menu.
+ break;
+ }
+
+ return hasIcon;
+}
+
QWindowsWindowClassDescription QWindowsWindowClassDescription::fromName(QString name, WNDPROC procedure)
{
return { std::move(name), procedure };
@@ -57,7 +76,7 @@ QWindowsWindowClassDescription QWindowsWindowClassDescription::fromWindow(const
const Qt::WindowFlags type = flags & Qt::WindowType_Mask;
// Determine style and icon.
description.style = CS_DBLCLKS;
- description.hasIcon = true;
+ description.hasIcon = computeHasIcon(flags, type);
// The following will not set CS_OWNDC for any widget window, even if it contains a
// QOpenGLWidget or QQuickWidget later on. That cannot be detected at this stage.
if (window->surfaceType() == QSurface::OpenGLSurface || (flags & Qt::MSWindowsOwnDC))
@@ -71,11 +90,6 @@ QWindowsWindowClassDescription QWindowsWindowClassDescription::fromWindow(const
case Qt::ToolTip:
case Qt::Popup:
description.style |= CS_SAVEBITS; // Save/restore background
- description.hasIcon = false;
- break;
- case Qt::Dialog:
- if (!(flags & Qt::WindowSystemMenuHint))
- description.hasIcon = false; // QTBUG-2027, dialogs without system menu.
break;
}
description.name = "QWindow"_L1 + classNameSuffix(type, description.style, description.hasIcon);
diff --git a/src/plugins/platforms/windows/qwindowswindowclassdescription.h b/src/plugins/platforms/windows/qwindowswindowclassdescription.h
index 692bf18e618..78d99b4c525 100644
--- a/src/plugins/platforms/windows/qwindowswindowclassdescription.h
+++ b/src/plugins/platforms/windows/qwindowswindowclassdescription.h
@@ -26,6 +26,7 @@ struct QWindowsWindowClassDescription
private:
static QString classNameSuffix(Qt::WindowFlags type, unsigned int style, bool hasIcon);
+ static bool computeHasIcon(Qt::WindowFlags flags, Qt::WindowFlags type);
friend QDebug operator<<(QDebug dbg, const QWindowsWindowClassDescription &description);
};
diff --git a/tests/auto/corelib/kernel/qjniobject/CMakeLists.txt b/tests/auto/corelib/kernel/qjniobject/CMakeLists.txt
index 98509dc0e5f..1d320b49c86 100644
--- a/tests/auto/corelib/kernel/qjniobject/CMakeLists.txt
+++ b/tests/auto/corelib/kernel/qjniobject/CMakeLists.txt
@@ -14,8 +14,16 @@ endif()
qt_internal_add_test(tst_qjniobject
SOURCES
tst_qjniobject.cpp
+ LIBRARIES
+ Qt::CorePrivate
)
+# if we can, enable C++23 so that we can use std::expected in the test
+# otherwise the test will use our own tl::expected copy from qexpected_p.h
+if ("${CMAKE_CXX_COMPILE_FEATURES}" MATCHES "cxx_std_23")
+ set_property(TARGET tst_qjniobject PROPERTY CXX_STANDARD 23)
+endif()
+
if(ANDROID)
set_property(TARGET tst_qjniobject APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
${CMAKE_CURRENT_SOURCE_DIR}/testdata
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 e0765fcb7e0..bdec173ca97 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
@@ -137,6 +137,10 @@ public class QtJniObjectTestClass
public Throwable throwableMethod() { return staticThrowableMethod(); }
// --------------------------------------------------------------------------------------------
+ public static void staticThrowingMethod() throws Throwable { throw new Throwable(A_STRING_OBJECT); }
+ public void throwingMethod() throws Throwable { throw new Throwable(A_STRING_OBJECT); }
+
+ // --------------------------------------------------------------------------------------------
public static Object[] staticObjectArrayMethod()
{ Object[] array = { new Object(), new Object(), new Object() }; return array; }
public Object[] objectArrayMethod() { return staticObjectArrayMethod(); }
diff --git a/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp b/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp
index c06b500778b..215b3bf3b78 100644
--- a/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp
+++ b/tests/auto/corelib/kernel/qjniobject/tst_qjniobject.cpp
@@ -8,12 +8,20 @@
#include <QtCore/QJniObject>
#include <QTest>
+#if defined(__cpp_lib_expected)
+# include <expected>
+#else
+# include <QtCore/private/qexpected_p.h>
+#endif
+
QT_BEGIN_NAMESPACE
using namespace Qt::StringLiterals;
static constexpr const char testClassName[] = "org/qtproject/qt/android/testdatapackage/QtJniObjectTestClass";
Q_DECLARE_JNI_CLASS(QtJniObjectTestClass, testClassName)
+Q_DECLARE_JNI_CLASS(NoSuchClass, "no/such/Class")
+
using TestClass = QtJniTypes::QtJniObjectTestClass;
static const jbyte A_BYTE_VALUE = 127;
@@ -125,6 +133,24 @@ private slots:
void callback();
void callStaticOverloadResolution();
+ void implicitExceptionHandling_construct();
+ void implicitExceptionHandling_callMethod();
+ void implicitExceptionHandling_callStaticMethod();
+ void implicitExceptionHandling_getField();
+ void implicitExceptionHandling_setField();
+ void implicitExceptionHandling_getStaticField();
+ void implicitExceptionHandling_setStaticField();
+
+ void constructWithException();
+ void callMethodWithException();
+ void callMethodWithMonadic();
+ void callMethodWithTryCatch();
+ void callStaticMethodWithException();
+ void getFieldWithException();
+ void setFieldWithException();
+ void getStaticFieldWithException();
+ void setStaticFieldWithException();
+
void cleanupTestCase();
};
@@ -2303,8 +2329,547 @@ void tst_QJniObject::callStaticOverloadResolution()
QCOMPARE(result, value);
}
-QT_END_NAMESPACE
+void tst_QJniObject::implicitExceptionHandling_construct()
+{
+ QJniEnvironment env;
+ const QRegularExpression invalidClass("java.lang.ClassNotFoundException: .*");
+ const QRegularExpression invalidMethod("java.lang.NoSuchMethodError: .*");
+
+ // Constructor, including named constructor
+ {
+ QTest::ignoreMessage(QtWarningMsg, invalidClass);
+ QJniObject testObject("NoSuchClass");
+ QVERIFY(!env.checkAndClearExceptions());
+ }
+ {
+ QTest::ignoreMessage(QtWarningMsg, invalidMethod);
+ QJniObject testObject = QJniObject::construct<TestClass>(u"NoSuchConstructor"_s);
+ QVERIFY(!env.checkAndClearExceptions());
+ }
+}
+
+void tst_QJniObject::implicitExceptionHandling_callMethod()
+{
+ QJniEnvironment env;
+ const QRegularExpression invalidMethod("java.lang.NoSuchMethodError: .*");
+ const QRegularExpression throwingMethod("java.lang.Throwable: .*");
+
+ QJniObject testObject = QJniObject::construct<TestClass>();
+
+ QTest::ignoreMessage(QtWarningMsg, invalidMethod);
+ testObject.callMethod<void>("noSuchMethod1");
+ QVERIFY(!env.checkAndClearExceptions());
+
+ QTest::ignoreMessage(QtWarningMsg, invalidMethod);
+ testObject.callMethod<void>("noSuchMethod2", "()V");
+ QVERIFY(!env.checkAndClearExceptions());
+
+ QTest::ignoreMessage(QtWarningMsg, throwingMethod);
+ testObject.callMethod<void>("throwingMethod");
+ QVERIFY(!env.checkAndClearExceptions());
+
+ QTest::ignoreMessage(QtWarningMsg, invalidMethod);
+ (void)testObject.callObjectMethod<jstring>("noSuchMethod");
+ QVERIFY(!env.checkAndClearExceptions());
+
+ QTest::ignoreMessage(QtWarningMsg, invalidMethod);
+ testObject.callObjectMethod("noSuchMethod", "()V");
+ QVERIFY(!env.checkAndClearExceptions());
+}
+
+void tst_QJniObject::implicitExceptionHandling_callStaticMethod()
+{
+ QJniEnvironment env;
+ const QRegularExpression invalidClass("java.lang.ClassNotFoundException: .*");
+ const QRegularExpression invalidMethod("java.lang.NoSuchMethodError: .*");
+ const QRegularExpression throwingMethod("java.lang.Throwable: .*");
+
+ const jclass classId = env.findClass<TestClass>();
+ QVERIFY(classId != nullptr);
+
+ QTest::ignoreMessage(QtWarningMsg, invalidMethod);
+ TestClass::callStaticMethod<void>("noSuchStaticMethod");
+ QVERIFY(!env.checkAndClearExceptions());
+
+ QTest::ignoreMessage(QtWarningMsg, invalidMethod);
+ QJniObject::callStaticMethod<void>(QtJniTypes::Traits<TestClass>::className(),
+ "noSuchStaticMethod2", "()V");
+ QVERIFY(!env.checkAndClearExceptions());
+
+ QTest::ignoreMessage(QtWarningMsg, invalidClass);
+ QJniObject::callStaticMethod<void>("noSuchClass", "noSuchStaticMethod2", "()V");
+ QVERIFY(!env.checkAndClearExceptions());
+
+ QTest::ignoreMessage(QtWarningMsg, throwingMethod);
+ jmethodID methodId = env.findStaticMethod<void>(classId, "staticThrowingMethod");
+ QVERIFY(methodId != nullptr);
+ QJniObject::callStaticMethod<void>(classId, methodId);
+ QVERIFY(!env.checkAndClearExceptions());
+
+ QTest::ignoreMessage(QtWarningMsg, invalidClass);
+ QJniObject::callStaticObjectMethod("noSuchClass", "noSuchStaticMethod", "()V");
+ QVERIFY(!env.checkAndClearExceptions());
+
+ QTest::ignoreMessage(QtWarningMsg, invalidMethod);
+ QJniObject::callStaticObjectMethod(classId, "noSuchStaticMethod", "()V");
+ QVERIFY(!env.checkAndClearExceptions());
+}
+
+void tst_QJniObject::implicitExceptionHandling_getField()
+{
+ QJniEnvironment env;
+ const QRegularExpression invalidField("java.lang.NoSuchFieldError: .*");
+
+ QJniObject testObject = QJniObject::construct<TestClass>();
+ QTest::ignoreMessage(QtWarningMsg, invalidField);
+ (void)testObject.getField<int>("noSuchField");
+ QVERIFY(!env.checkAndClearExceptions());
+
+ QTest::ignoreMessage(QtWarningMsg, invalidField);
+ (void)testObject.getObjectField<jobject>("noSuchObjectField");
+ QVERIFY(!env.checkAndClearExceptions());
+
+ QTest::ignoreMessage(QtWarningMsg, invalidField);
+ (void)testObject.getObjectField("noSuchObjectField2", "Ljava/lang/Object;");
+ QVERIFY(!env.checkAndClearExceptions());
+}
+
+void tst_QJniObject::implicitExceptionHandling_setField()
+{
+ QJniEnvironment env;
+ const QRegularExpression invalidField("java.lang.NoSuchFieldError: .*");
+
+ QJniObject testObject = QJniObject::construct<TestClass>();
+ QTest::ignoreMessage(QtWarningMsg, invalidField);
+ testObject.setField("noSuchField", 123);
+ QVERIFY(!env.checkAndClearExceptions());
+
+ QTest::ignoreMessage(QtWarningMsg, invalidField);
+ testObject.setField("BOOLEAN_VAR", "I", 123);
+ QVERIFY(!env.checkAndClearExceptions());
+
+ // make sure that code specifying deducible type explicitly still works
+ static_assert(std::is_same_v<decltype(testObject.setField<jboolean>("BOOLEAN_VAR", true)),
+ void>);
+}
+
+void tst_QJniObject::implicitExceptionHandling_getStaticField()
+{
+ QJniEnvironment env;
+ const QRegularExpression invalidClass("java.lang.ClassNotFoundException: .*");
+ const QRegularExpression invalidField("java.lang.NoSuchFieldError: .*");
+
+ const jclass classId = env.findClass<TestClass>();
+ QVERIFY(classId != nullptr);
+
+ QTest::ignoreMessage(QtWarningMsg, invalidField);
+ (void)TestClass::getStaticField<int>("noSuchStaticField");
+ QVERIFY(!env.checkAndClearExceptions());
+
+ QTest::ignoreMessage(QtWarningMsg, invalidField);
+ (void)QJniObject::getStaticField<int>(classId, "noSuchStaticField");
+ QVERIFY(!env.checkAndClearExceptions());
+
+ QTest::ignoreMessage(QtWarningMsg, invalidClass);
+ (void)QJniObject::getStaticObjectField("noSuchClass", "noSuchStaticField", "I");
+ QVERIFY(!env.checkAndClearExceptions());
+
+ QTest::ignoreMessage(QtWarningMsg, invalidField);
+ (void)QJniObject::getStaticObjectField(classId, "S_BOOLEAN_VAR", "I");
+ QVERIFY(!env.checkAndClearExceptions());
+}
+
+void tst_QJniObject::implicitExceptionHandling_setStaticField()
+{
+ QJniEnvironment env;
+ const QRegularExpression invalidClass("java.lang.ClassNotFoundException: .*");
+ const QRegularExpression invalidField("java.lang.NoSuchFieldError: .*");
+
+ const jclass classId = env.findClass<TestClass>();
+ QVERIFY(classId != nullptr);
+
+ QTest::ignoreMessage(QtWarningMsg, invalidField);
+ TestClass::setStaticField("noSuchStaticField", 123);
+ QVERIFY(!env.checkAndClearExceptions());
+
+ QTest::ignoreMessage(QtWarningMsg, invalidField);
+ QJniObject::setStaticField(classId, "noSuchStaticField", 123);
+ QVERIFY(!env.checkAndClearExceptions());
+
+ QTest::ignoreMessage(QtWarningMsg, invalidClass);
+ QJniObject::setStaticField("noSuchClass", "noSuchStaticField", 123);
+ QVERIFY(!env.checkAndClearExceptions());
+
+ QTest::ignoreMessage(QtWarningMsg, invalidField);
+ QJniObject::setStaticField(QtJniTypes::Traits<TestClass>::className(),
+ "S_BOOLEAN_VAR", "I", 123);
+ QVERIFY(!env.checkAndClearExceptions());
+
+ // make sure that code specifying deducible type explicitly still works
+ static_assert(std::is_same_v<decltype(TestClass::setStaticField<jboolean>("S_BOOLEAN_VAR", true)),
+ void>);
+}
+
+#if __cpp_lib_expected
+template <typename T>
+using QJniReturnValue = std::expected<T, jthrowable>;
+using BadAccessException = std::bad_expected_access<jthrowable>;
+// even with __cpp_lib_expected >= 202211L, monadic functions seem to be rather
+// broken or not reliably available
+#define EXPECTED_HAS_MONADIC false
+#elif TL_EXPECTED_VERSION_MAJOR
+#define EXPECTED_HAS_MONADIC true
+template <typename T>
+using QJniReturnValue = tl::expected<T, jthrowable>;
+using BadAccessException = tl::bad_expected_access<jthrowable>;
+#endif
+
+static_assert(QtJniTypes::Traits<QJniReturnValue<int>>::signature() ==
+ QtJniTypes::Traits<int>::signature());
+static_assert(QtJniTypes::Traits<QJniReturnValue<QString>>::signature() ==
+ QtJniTypes::Traits<QString>::signature());
+
+
+void tst_QJniObject::constructWithException()
+{
+#if __cpp_lib_expected
+ qInfo() << "Testing explicit exception handling with std::expected" << __cpp_lib_expected;
+#elif defined(TL_EXPECTED_VERSION_MAJOR)
+ qInfo() << "Testing explicit exception handling with tl::expected";
+#else
+ qInfo() << "Testing explicit exception handling with QJniReturnValue";
+#endif
+
+ const QRegularExpression invalidClass("java.lang.ClassNotFoundException: .*");
+ {
+ QTest::ignoreMessage(QtWarningMsg, invalidClass);
+ QJniObject invalid = QJniObject::construct<QtJniTypes::NoSuchClass>();
+ }
+
+ QVERIFY(!QJniEnvironment().checkAndClearExceptions());
+
+ {
+ // can only handle exceptions when using the named constructor
+ auto result = QJniObject::construct<QJniReturnValue<TestClass>>();
+ QVERIFY(result);
+ result = QJniObject::construct<QJniReturnValue<TestClass>>(u"123"_s);
+ QVERIFY(!result);
+ QVERIFY(result.error());
+ }
+
+ QVERIFY(!QJniEnvironment().checkAndClearExceptions());
+
+ {
+ const auto result = QJniObject::construct<QJniReturnValue<QtJniTypes::NoSuchClass>>();
+ QVERIFY(!result);
+ QVERIFY(result.error());
+ }
+
+ QVERIFY(!QJniEnvironment().checkAndClearExceptions());
+
+ {
+ // no way to prevent implicit exception here
+ QTest::ignoreMessage(QtWarningMsg, invalidClass);
+ QtJniTypes::NoSuchClass noSuchClass;
+ QVERIFY(!noSuchClass.isValid());
+ }
+
+ QVERIFY(!QJniEnvironment().checkAndClearExceptions());
+}
+
+void tst_QJniObject::callMethodWithException()
+{
+ TestClass testObject;
+ {
+ auto result = testObject.callMethod<QJniReturnValue<void>>("voidMethod");
+ QVERIFY(result);
+ result = testObject.callMethod<QJniReturnValue<void>>("voidMethod", 123);
+ QVERIFY(!result);
+ QVERIFY(result.error());
+ }
+
+ {
+ auto result = testObject.callMethod<QJniReturnValue<jint>>("intMethod");
+ QVERIFY(result);
+ QCOMPARE(result.value(), A_INT_VALUE);
+ result = testObject.callMethod<QJniReturnValue<jint>>("intMethod", 123);
+ QVERIFY(!result);
+ QVERIFY(result.error());
+ QCOMPARE(result.value_or(456), 456);
+ }
+
+ {
+ auto result = testObject.callMethod<QJniReturnValue<jstring>>("stringMethod");
+ QVERIFY(result);
+ QVERIFY(result.value());
+ result = testObject.callMethod<QJniReturnValue<jstring>>("stringMethod", 123);
+ QVERIFY(!result);
+ QVERIFY(result.error());
+ }
+
+ {
+ auto result = testObject.callMethod<QJniReturnValue<QString>>("stringMethod");
+ QVERIFY(result);
+ QVERIFY(!result.value().isEmpty());
+ result = testObject.callMethod<QJniReturnValue<QString>>("stringMethod", 123);
+ QVERIFY(!result);
+ QVERIFY(result.error());
+ QCOMPARE(result.value_or(u"Default"_s), u"Default"_s);
+ }
+
+ {
+ QJniArray<jboolean> newArray(QList<jboolean>{true, false, false});
+ auto result = testObject.callMethod<QJniReturnValue<QJniArray<jboolean>>>("reverseBooleanArray", newArray);
+ // this shorthand cannot work with e.g. std::expected
+ // result = testObject.callMethod<QJniReturnValue<jboolean[]>>("reverseBooleanArray", newArray);
+ QVERIFY(result);
+ QCOMPARE(result.value().toContainer(), (QList<jboolean>{false, false, true}));
+ result = testObject.callMethod<QJniReturnValue<QJniArray<jboolean>>>("reverseBooleanArray", 123);
+ QVERIFY(!result);
+ QVERIFY(result.error());
+ }
+
+ // throwing method - QJniObject cleans the exception and prints qWarning
+ {
+ QTest::ignoreMessage(QtWarningMsg, QRegularExpression(u"java.lang.Throwable: "_s + A_STRING_OBJECT() + u".*"_s));
+ testObject.callMethod<void>("throwingMethod");
+
+ auto result = testObject.callMethod<QJniReturnValue<void>>("throwingMethod");
+ QVERIFY(!result);
+ const QStringList stackTrace = QJniEnvironment::stackTrace(result.error());
+ QCOMPARE_GE(stackTrace.size(), 1);
+ QCOMPARE(stackTrace.at(0), u"java.lang.Throwable: "_s + A_STRING_OBJECT());
+ }
+}
+
+void tst_QJniObject::callMethodWithMonadic()
+{
+#if !EXPECTED_HAS_MONADIC
+ QSKIP("Used version of std::expected does not have monadic functions");
+#else
+ enum Monadic {
+ AndThen,
+ OrElse,
+ Transform,
+ };
+
+ TestClass testObject;
+ {
+ QList<Monadic> flow;
+ const auto result = testObject.callMethod<QJniReturnValue<void>>("voidMethod")
+ .and_then([&flow]{
+ flow << AndThen;
+ return QJniReturnValue<void>();
+ })
+ .or_else([&flow](jthrowable error){
+ if (error)
+ flow << OrElse;
+ else
+ qWarning("Invalid call to or_else monadic");
+ return QJniReturnValue<void>();
+ })
+ .transform([&flow](){
+ flow << Transform;
+ return 42;
+ });
+ QCOMPARE(flow, (QList<Monadic>{AndThen, Transform}));
+ QVERIFY(result);
+ QCOMPARE(*result, 42);
+ }
+ {
+ QList<Monadic> flow;
+ const auto result = testObject.callMethod<QJniReturnValue<void>>("voidMethod", 123)
+ .and_then([&flow]{
+ flow << AndThen;
+ return QJniReturnValue<void>();
+ })
+ .or_else([&flow](jthrowable error){
+ if (error)
+ flow << OrElse;
+ else
+ qWarning("Invalid call to or_else monadic");
+ return QJniReturnValue<void>(typename QJniReturnValue<void>::unexpected_type(error));
+ })
+ .transform([&flow](){
+ flow << Transform;
+ return 42;
+ });
+ QCOMPARE(flow, (QList<Monadic>{OrElse}));
+ QVERIFY(!result);
+ }
+
+ {
+ QList<Monadic> flow;
+ const auto result = testObject.callMethod<QJniReturnValue<jobject>>("objectMethod")
+ .and_then([&flow](auto &&obj){
+ flow << AndThen;
+ return QJniReturnValue<jobject>(obj);
+ })
+ .or_else([&flow](jthrowable error){
+ if (error)
+ flow << OrElse;
+ else
+ qWarning("Invalid call to or_else monadic");
+ return QJniReturnValue<jobject>(typename QJniReturnValue<void>::unexpected_type(error));
+ })
+ .transform([&flow](auto &&obj){
+ flow << Transform;
+ return QJniObject(obj).template getField<int>("INT_FIELD");
+ })
+ .and_then([&flow](auto value){
+ flow << AndThen;
+ return QJniReturnValue<int>(value * 2);
+ });
+ QCOMPARE(flow, (QList<Monadic>{AndThen, Transform, AndThen}));
+ QVERIFY(result);
+ QCOMPARE(*result, 246);
+ }
+ {
+ QList<Monadic> flow;
+ const auto result = testObject.callMethod<QJniReturnValue<jobject>>("objectMethod", 123)
+ .and_then([&flow](const QJniObject &obj){
+ flow << AndThen;
+ return QJniReturnValue<jobject>(obj.object());
+ })
+ .or_else([&flow](jthrowable error){
+ if (error)
+ flow << OrElse;
+ else
+ qWarning("Invalid call to or_else monadic");
+ return QJniReturnValue<jobject>(typename QJniReturnValue<jobject>::unexpected_type(error));
+ })
+ .transform([&flow](const QJniObject &obj){
+ flow << Transform;
+ return obj.getField<int>("INT_FIELD");
+ });
+ QCOMPARE(flow, (QList<Monadic>{OrElse}));
+ QVERIFY(!result);
+ }
+#endif
+}
+
+void tst_QJniObject::callMethodWithTryCatch()
+{
+ TestClass testObject;
+
+ const QRegularExpression invalidMethod("java.lang.NoSuchMethodError: .*");
+ QTest::ignoreMessage(QtWarningMsg, invalidMethod);
+
+ try {
+ const auto result = testObject.callMethod<QJniReturnValue<QJniObject>>("objectMethod", 123);
+ result.value().getField<int>("INT_FIELD");
+ }
+ catch (BadAccessException &e) {
+ qWarning().noquote() << QJniEnvironment::stackTrace(e.error()).join('\n');
+ }
+}
+
+void tst_QJniObject::callStaticMethodWithException()
+{
+ {
+ auto result = TestClass::callStaticMethod<QJniReturnValue<int>>("staticIntMethod");
+ QVERIFY(result);
+ QCOMPARE(*result, A_INT_VALUE);
+ result = TestClass::callStaticMethod<QJniReturnValue<int>>("staticIntMethod", 123);
+ QVERIFY(!result && result.error());
+ }
+
+ {
+ auto result = TestClass::callStaticMethod<QJniReturnValue<QString>>("staticStringMethod");
+ QVERIFY(result);
+ QCOMPARE(*result, A_STRING_OBJECT());
+ result = TestClass::callStaticMethod<QJniReturnValue<QString>>("staticStringMethod", 123);
+ QVERIFY(!result && result.error());
+ }
+
+ // throwing method
+ {
+ const auto result = TestClass::callStaticMethod<QJniReturnValue<void>>("staticThrowingMethod");
+ QVERIFY(!result);
+ QStringList stackTrace = QJniEnvironment::stackTrace(result.error());
+ QCOMPARE_GE(stackTrace.size(), 1);
+ QCOMPARE(stackTrace.at(0), u"java.lang.Throwable: "_s + A_STRING_OBJECT());
+ }
+}
+
+void tst_QJniObject::getFieldWithException()
+{
+ TestClass testObject;
+ {
+ auto result = testObject.getField<QJniReturnValue<jboolean>>("BOOL_FIELD");
+ QVERIFY(result);
+ result = testObject.getField<QJniReturnValue<jboolean>>("INVALID_BOOL");
+ QVERIFY(!result && result.error());
+ }
+
+ {
+ auto result = testObject.getField<QJniReturnValue<QString>>("STRING_OBJECT_VAR");
+ QVERIFY(result);
+ result = testObject.getField<QJniReturnValue<QString>>("INVALID_STRING");
+ QVERIFY(!result && result.error());
+ }
+}
+
+void tst_QJniObject::setFieldWithException()
+{
+ TestClass testObject;
+ {
+ auto result = testObject.setField<QJniReturnValue<jboolean>>("BOOL_FIELD", true);
+ QVERIFY(result);
+ result = testObject.setField<QJniReturnValue<jboolean>>("SET_INVALID_BOOL", true);
+ QVERIFY(!result);
+ QVERIFY(result.error());
+ }
+
+ {
+ auto result = testObject.setField<QJniReturnValue<QString>>("STRING_OBJECT_VAR", u"test"_s);
+ QVERIFY(result);
+ result = testObject.setField<QJniReturnValue<QString>>("SET_INVALID_STRING", u"test"_s);
+ QVERIFY(!result);
+ QVERIFY(result.error());
+ }
+}
+
+void tst_QJniObject::getStaticFieldWithException()
+{
+ {
+ auto result = TestClass::getStaticField<QJniReturnValue<jshort>>("S_SHORT_VAR");
+ QVERIFY(result);
+ result = TestClass::getStaticField<QJniReturnValue<jshort>>("S_INVALID_SHORT");
+ QVERIFY(!result);
+ QVERIFY(result.error());
+ }
+
+ {
+ auto result = TestClass::getStaticField<QJniReturnValue<QString>>("S_STRING_OBJECT_VAR");
+ QVERIFY(result);
+ result = TestClass::getStaticField<QJniReturnValue<QString>>("S_INVALID_STRING");
+ QVERIFY(!result);
+ QVERIFY(result.error());
+ }
+}
+
+void tst_QJniObject::setStaticFieldWithException()
+{
+ {
+ auto result = TestClass::setStaticField<QJniReturnValue<jboolean>>("S_BOOLEAN_VAR", true);
+ QVERIFY(result);
+ result = TestClass::setStaticField<QJniReturnValue<jboolean>>("SET_S_INVALID_BOOL", true);
+ QVERIFY(!result);
+ QVERIFY(result.error());
+ }
+
+ {
+ auto result = TestClass::setStaticField<QJniReturnValue<QString>>("S_STRING_OBJECT_VAR", u"test"_s);
+ QVERIFY(result);
+ result = TestClass::setStaticField<QJniReturnValue<QString>>("SET_S_INVALID_STRING", u"test"_s);
+ QVERIFY(!result);
+ QVERIFY(result.error());
+ }
+}
QTEST_MAIN(tst_QJniObject)
+QT_END_NAMESPACE
+
#include "tst_qjniobject.moc"