Commit 96c50444 authored by Volker Krause's avatar Volker Krause
Browse files

Add JNI method wrapper

This generates a JNI method call of the right type with automatic
conversion to/from native types where possible, and with validating
the argument types.
parent 8c9b49d9
......@@ -6,6 +6,7 @@ add_definitions(-DSOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
ecm_add_test(jnisignaturetest.cpp LINK_LIBRARIES Qt::Test KAndroidExtras)
ecm_add_test(jnipropertytest.cpp LINK_LIBRARIES Qt::Test KAndroidExtras)
ecm_add_test(jniarraytest.cpp LINK_LIBRARIES Qt::Test KAndroidExtras)
ecm_add_test(jnimethodtest.cpp LINK_LIBRARIES Qt::Test KAndroidExtras)
ecm_add_test(androidwrappertest.cpp LINK_LIBRARIES Qt::Test KAndroidExtras)
ecm_add_test(gpxwritertest.cpp LINK_LIBRARIES Qt::Test GpxIo)
......
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#include <kandroidextras/jnimethod.h>
#include <kandroidextras/jnitypes.h>
#include <kandroidextras/intent.h>
#include <kandroidextras/javatypes.h>
#include <QtTest/qtest.h>
using namespace KAndroidExtras;
class TestClass : android::content::Intent
{
JNI_OBJECT(TestClass)
public:
JNI_METHOD(java::lang::String, getName)
JNI_METHOD(void, setName, java::lang::String)
JNI_METHOD(jint, getFlags)
JNI_METHOD(void, setFlags, jint)
JNI_METHOD(void, start)
JNI_METHOD(bool, setCoordinates, jfloat, jfloat)
inline QAndroidJniObject handle() const { return m_handle; }
private:
QAndroidJniObject m_handle;
};
class JniMethodTest : public QObject
{
Q_OBJECT
private Q_SLOTS:
void testArgumentConversion()
{
static_assert(Internal::is_argument_compatible<jint, jint>::value);
static_assert(Internal::is_argument_compatible<java::lang::String, QAndroidJniObject>::value);
static_assert(Internal::is_argument_compatible<java::lang::String, QString>::value);
static_assert(!Internal::is_argument_compatible<jint, jdouble>::value);
static_assert(!Internal::is_argument_compatible<jint, QAndroidJniObject>::value);
static_assert(!Internal::is_argument_compatible<java::lang::String, QUrl>::value);
static_assert(Internal::is_call_compatible<jint>::with<jint>::value);
static_assert(Internal::is_call_compatible<java::lang::String, jfloat>::with<QAndroidJniObject, float>::value);
static_assert(!Internal::is_call_compatible<java::lang::String, jfloat>::with<float, QAndroidJniObject>::value);
static_assert(Internal::is_call_compatible<java::lang::String, java::lang::String>::with<QString, QAndroidJniObject>::value);
static_assert(Internal::is_call_compatible<java::lang::String, java::lang::String>::with<QAndroidJniObject, QString>::value);
}
void testMethodCalls()
{
#ifndef Q_OS_ANDROID
TestClass obj;
QString s = obj.getName();
Q_UNUSED(s);
obj.setName(QStringLiteral("bla"));
obj.setName(QAndroidJniObject::fromString(QStringLiteral("bla")));
int i = obj.getFlags();
Q_UNUSED(i);
obj.setFlags(42);
obj.start();
bool b = obj.setCoordinates(0.0f, 0.0f);
Q_UNUSED(b)
QCOMPARE(obj.handle().protocol().size(), 7);
QCOMPARE(obj.handle().protocol()[0], QLatin1String("callObjectMethod: getName ()Ljava/lang/String;"));
QCOMPARE(obj.handle().protocol()[1], QLatin1String("callMethod: setName (Ljava/lang/String;)V"));
QCOMPARE(obj.handle().protocol()[2], QLatin1String("callMethod: setName (Ljava/lang/String;)V"));
QCOMPARE(obj.handle().protocol()[3], QLatin1String("callMethod: getFlags ()I"));
QCOMPARE(obj.handle().protocol()[4], QLatin1String("callMethod: setFlags (I)V"));
QCOMPARE(obj.handle().protocol()[5], QLatin1String("callMethod: start ()V"));
QCOMPARE(obj.handle().protocol()[6], QLatin1String("callMethod: setCoordinates (FF)Z"));
#if 0
// stuff that must not compile
obj.setName(42);
obj.setFlags(QStringLiteral("42"));
#endif
#endif
}
};
QTEST_GUILESS_MAIN(JniMethodTest)
#include "jnimethodtest.moc"
......@@ -61,6 +61,7 @@ ecm_generate_headers(KAndroidExtras_java_FORWARDING_HEADERS
ecm_generate_headers(KAndroidExtras_jni_FORWARDING_HEADERS
HEADER_NAMES
JniArray
JniMethod
JniProperty
JniSignature
JniTypes
......
......@@ -48,14 +48,17 @@ public:
T callMethod(const char *methodName, const char *signature, ...) const
{
addToProtocol(QLatin1String("callMethod: ") + QLatin1String(methodName) + QLatin1Char(' ') + QLatin1String(signature));
return {};
if constexpr (!std::is_same_v<T, void>) {
return {};
}
}
QAndroidJniObject callObjectMethod(const char *methodName, const char *signature, ...) const
{
addToProtocol(QLatin1String("callObjectMethod: ") + QLatin1String(methodName) + QLatin1Char(' ') + QLatin1String(signature));
QAndroidJniObject obj;
obj.setProtocol(protocol());
obj.addToProtocol(QLatin1String("callObjectMethod: ") + QLatin1String(methodName) + QLatin1Char(' ') + QLatin1String(signature));
return obj;
}
......
......@@ -7,6 +7,7 @@
// list all headers here that have no .cpp file
// this only serves as a guarantee that these headers actually compile
#include "jniarray.h"
#include "jnimethod.h"
#include "jniproperty.h"
#include "jnisignature.h"
#include "jnitypes.h"
......
/*
SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#ifndef KANDROIDEXTRAS_JNIMETHOD_H
#define KANDROIDEXTRAS_JNIMETHOD_H
#include "jnitypetraits.h"
#include <QAndroidJniObject>
namespace KAndroidExtras {
///@cond internal
namespace Internal {
// argument compatibility checking
template <typename SigT, typename ArgT> struct is_argument_compatible
{
static inline constexpr bool value =
!std::is_same<SigT, void>::value && !std::is_same<ArgT, void>::value &&
(std::is_same<SigT, ArgT>::value ||
(!Jni::is_basic_type<SigT>::value && std::is_same<ArgT, QAndroidJniObject>::value) ||
(!Jni::is_basic_type<SigT>::value && std::is_same<ArgT, typename Jni::converter<SigT>::type>::value));
};
template <typename ...Sig> struct is_call_compatible
{
template <typename ...Args> struct with {
template<typename... Values>
inline static constexpr bool all(Values... values) { return (... && values); }
static inline constexpr bool value = all(is_argument_compatible<Sig, Args>::value...);
};
};
// argument type conversion
template <typename ArgT, bool is_basic, bool is_convertible> struct call_argument {};
template <typename ArgT> struct call_argument<ArgT, true, false>
{
inline constexpr ArgT operator()(ArgT value) const
{
return value;
}
};
template <typename ArgT> struct call_argument<ArgT, false, true>
{
inline constexpr jobject operator()(const ArgT &value) const
{
return Jni::converter<ArgT>::convert(value).object();
}
};
template <> struct call_argument<QAndroidJniObject, false, false>
{
inline jobject operator()(const QAndroidJniObject &value) const
{
return value.object();
}
};
template <typename ArgT> constexpr call_argument<ArgT, Jni::is_basic_type<ArgT>::value, !std::is_same_v<typename Jni::converter<ArgT>::type, void>> toCallArgument = {};
// return type conversion
template <typename RetT>
struct call_return {
static inline constexpr bool is_basic = Jni::is_basic_type<RetT>::value;
static inline constexpr bool is_convertible = !std::is_same_v<typename Jni::converter<RetT>::type, void>;
typedef std::conditional_t<is_basic || !is_convertible, RetT, typename Jni::converter<RetT>::type> CallReturnT;
typedef std::conditional_t<is_basic, RetT, QAndroidJniObject> JniReturnT;
static inline constexpr CallReturnT toReturnValue(JniReturnT value)
{
if constexpr (is_convertible) {
return Jni::converter<RetT>::convert(value);
} else {
return value;
}
}
};
template <>
struct call_return<void> {
typedef void CallReturnT;
};
// call wrapper
template <typename RetT, typename ...Sig>
struct invoker {
template <typename ...Args>
static typename Internal::call_return<RetT>::CallReturnT call(QAndroidJniObject handle, const char *name, const char *signature, Args... args)
{
static_assert(is_call_compatible<Sig...>::template with<Args...>::value, "incompatible call arguments");
if constexpr (Jni::is_basic_type<RetT>::value) {
return handle.callMethod<RetT>(name, signature, toCallArgument<Args>(args)...);
} else {
return Internal::call_return<RetT>::toReturnValue(handle.callObjectMethod(name, signature, toCallArgument<Args>(args)...));
}
return {};
}
};
template <typename ...Sig>
struct invoker<void, Sig...> {
template <typename ...Args>
static void call(QAndroidJniObject handle, const char *name, const char *signature, Args... args)
{
static_assert(is_call_compatible<Sig...>::template with<Args...>::value, "incompatible call arguments");
handle.callMethod<void>(name, signature, toCallArgument<Args>(std::forward<Args>(args))...);
}
};
template <typename RetT>
struct invoker<RetT> {
static typename Internal::call_return<RetT>::CallReturnT call(QAndroidJniObject handle, const char *name, const char *signature)
{
if constexpr (Jni::is_basic_type<RetT>::value) {
return handle.callMethod<RetT>(name, signature);
} else {
return Internal::call_return<RetT>::toReturnValue(handle.callObjectMethod(name, signature));
}
}
};
template <>
struct invoker<void> {
static void call(QAndroidJniObject handle, const char *name, const char *signature)
{
handle.callMethod<void>(name, signature);
}
};
}
///@endcond
/**
* Wrap a JNI method call.
* This will add a method named @p name to the current class. Argument types are checked at compile time,
* with the following inputs being accepted:
* - basic types have to match exactly
* - non-basic types can be either passed as @c QAndroidJniObject instance or with a type that has an
* conversion registered with @c JNI_DECLARE_CONVERTER.
* The return type is handled similarly, if a conversion to a native type is available that is returned,
* @c QAndroidJniObject otherwise.
* Can only be placed in classes with a @c JNI_OBJECT.
*
* @param RetT The return type. Must either be a basic type or a type declared with @c JNI_TYPE
* @param Name The name of the method. Must match the JNI method to be called exactly.
* @param Args A list or argument types (can be empty). Must either be basic types or types declared
* with @c JNI_TYPE.
*/
#define JNI_METHOD(RetT, Name, ...) \
template <typename ...Args> \
inline Internal::call_return<RetT>::CallReturnT Name(Args... args) const { \
return Internal::invoker<RetT, ## __VA_ARGS__>::call(handle(), "" #Name, Jni::signature<RetT(__VA_ARGS__)>(), std::forward<Args>(args)...); \
}
}
#endif
......@@ -29,6 +29,7 @@ template <> struct is_basic_type<jint> : std::true_type {};
template <> struct is_basic_type<jlong> : std::true_type {};
template <> struct is_basic_type<jfloat> : std::true_type {};
template <> struct is_basic_type<jdouble> : std::true_type {};
template <> struct is_basic_type<void> : std::true_type {};
/** Type conversion trait, see @c JNI_DECLARE_CONVERTER. */
template <typename T> struct converter {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment