JNI: move signature/className template functions into a template class

Template functions don't permit partial specialization, e.g. we cannot
specialize typeSignature() to return an array signature for any
std::vector or QList type. We need to do that for better array support,
so move those functions as static members into a template class, which
then can be specialized.

Since submodules are both calling and specializing typeSignature and
className as template functions, keep and use those until the porting is
complete.

Change-Id: I74ec957fc41f78046cd9d0f803d8cc9d1e56672b
Reviewed-by: Petri Virkkunen <petri.virkkunen@qt.io>
Reviewed-by: Zoltan Gera <zoltan.gera@qt.io>
Reviewed-by: Tinja Paavoseppä <tinja.paavoseppa@qt.io>
This commit is contained in:
Volker Hilsheimer 2023-09-17 12:40:40 +02:00
parent 0022b05a9a
commit e1a349983c
9 changed files with 172 additions and 142 deletions

View File

@ -25,7 +25,7 @@ public:
JNIEnv *jniEnv() const;
jclass findClass(const char *className);
template<typename Class>
jclass findClass() { return findClass(QtJniTypes::className<Class>().data()); }
jclass findClass() { return findClass(QtJniTypes::Traits<Class>::className().data()); }
jmethodID findMethod(jclass clazz, const char *methodName, const char *signature);
template<typename ...Args>
jmethodID findMethod(jclass clazz, const char *methodName) {

View File

@ -39,31 +39,35 @@ struct Type : Object \
#define Q_DECLARE_JNI_TYPE(Type, Signature) \
Q_DECLARE_JNI_TYPE_HELPER(Type) \
template<> \
constexpr auto QtJniTypes::typeSignature<QtJniTypes::Type>() \
{ \
static_assert((Signature[0] == 'L' || Signature[0] == '[') \
&& Signature[sizeof(Signature) - 2] == ';', \
"Type signature needs to start with 'L' or '['" \
" and end with ';'"); \
return QtJniTypes::CTString(Signature); \
} \
struct QtJniTypes::Traits<QtJniTypes::Type> { \
static constexpr auto signature() \
{ \
static_assert((Signature[0] == 'L' \
|| Signature[0] == '[') \
&& Signature[sizeof(Signature) - 2] == ';', \
"Type signature needs to start with 'L' or" \
" '[' and end with ';'"); \
return QtJniTypes::CTString(Signature); \
} \
}; \
#define Q_DECLARE_JNI_CLASS(Type, Signature) \
Q_DECLARE_JNI_TYPE_HELPER(Type) \
template<> \
constexpr auto QtJniTypes::className<QtJniTypes::Type>() \
{ \
return QtJniTypes::CTString(Signature); \
} \
template<> \
constexpr auto QtJniTypes::typeSignature<QtJniTypes::Type>() \
{ \
return QtJniTypes::CTString("L") \
+ QtJniTypes::CTString(Signature) \
+ QtJniTypes::CTString(";"); \
} \
struct QtJniTypes::Traits<QtJniTypes::Type> { \
static constexpr auto className() \
{ \
return QtJniTypes::CTString(Signature); \
} \
static constexpr auto signature() \
{ \
return QtJniTypes::CTString("L") \
+ className() \
+ QtJniTypes::CTString(";"); \
} \
}; \
#define Q_DECLARE_JNI_NATIVE_METHOD(...) \
#define Q_DECLARE_JNI_NATIVE_METHOD(...) \
QT_OVERLOADED_MACRO(QT_DECLARE_JNI_NATIVE_METHOD, __VA_ARGS__) \
#define QT_DECLARE_JNI_NATIVE_METHOD_2(Method, Name) \

View File

@ -149,97 +149,113 @@ static void staticAssertTypeMismatch()
"Use a JNI based type instead.");
}
template<typename T>
constexpr auto typeSignature()
{
if constexpr (std::is_array_v<T>) {
using UnderlyingType = typename std::remove_extent_t<T>;
static_assert(!std::is_array_v<UnderlyingType>,
"typeSignature() does not handle multi-dimensional arrays");
return CTString("[") + typeSignature<UnderlyingType>();
} else if constexpr (std::is_same_v<T, jobject>) {
return CTString("Ljava/lang/Object;");
} else if constexpr (std::is_same_v<T, jclass>) {
return CTString("Ljava/lang/Class;");
} else if constexpr (std::is_same_v<T, jstring>) {
return CTString("Ljava/lang/String;");
} else if constexpr (std::is_same_v<T, jobjectArray>) {
return CTString("[Ljava/lang/Object;");
} else if constexpr (std::is_same_v<T, jthrowable>) {
return CTString("Ljava/lang/Throwable;");
} else if constexpr (std::is_same_v<T, jbooleanArray>) {
return CTString("[Z");
} else if constexpr (std::is_same_v<T, jbyteArray>) {
return CTString("[B");
} else if constexpr (std::is_same_v<T, jshortArray>) {
return CTString("[S");
} else if constexpr (std::is_same_v<T, jintArray>) {
return CTString("[I");
} else if constexpr (std::is_same_v<T, jlongArray>) {
return CTString("[J");
} else if constexpr (std::is_same_v<T, jfloatArray>) {
return CTString("[F");
} else if constexpr (std::is_same_v<T, jdoubleArray>) {
return CTString("[D");
} else if constexpr (std::is_same_v<T, jcharArray>) {
return CTString("[C");
} else if constexpr (std::is_same_v<T, jboolean>) {
return CTString("Z");
} else if constexpr (std::is_same_v<T, bool>) {
return CTString("Z");
} else if constexpr (std::is_same_v<T, jbyte>) {
return CTString("B");
} else if constexpr (std::is_same_v<T, jchar>) {
return CTString("C");
} else if constexpr (std::is_same_v<T, char>) {
return CTString("C");
} else if constexpr (std::is_same_v<T, jshort>) {
return CTString("S");
} else if constexpr (std::is_same_v<T, short>) {
return CTString("S");
} else if constexpr (std::is_same_v<T, jint>) {
return CTString("I");
} else if constexpr (std::is_same_v<T, int>) {
return CTString("I");
} else if constexpr (std::is_same_v<T, uint>) {
return CTString("I");
} else if constexpr (std::is_same_v<T, jlong>) {
return CTString("J");
} else if constexpr (std::is_same_v<T, long>) {
return CTString("J");
} else if constexpr (std::is_same_v<T, jfloat>) {
return CTString("F");
} else if constexpr (std::is_same_v<T, float>) {
return CTString("F");
} else if constexpr (std::is_same_v<T, jdouble>) {
return CTString("D");
} else if constexpr (std::is_same_v<T, double>) {
return CTString("D");
} else if constexpr (std::is_same_v<T, void>) {
return CTString("V");
}
// else: The return type becomes void, indicating that the typeSignature
// template is not implemented for the respective type. We use this to
// detect invalid types in the ValidSignatureTypes and ValidFieldType
// predicates below.
}
template<bool flag = false>
static void staticAssertClassNotRegistered()
{
static_assert(flag, "Class not registered, use Q_DECLARE_JNI_CLASS");
}
template<typename T>
constexpr auto className()
template <typename T>
struct Traits {
// The return type of className/signature becomes void for any type
// not handled here. This indicates that the Traits type is not specialized
// for the respective type, which we use to detect invalid types in the
// ValidSignatureTypes and ValidFieldType predicates below.
static constexpr auto className()
{
if constexpr (std::is_same_v<T, jstring>)
return CTString("java/lang/String");
else if constexpr (std::is_same_v<T, jobject>)
return CTString("java/lang/Object");
else if constexpr (std::is_same_v<T, jclass>)
return CTString("java/lang/Class");
else if constexpr (std::is_same_v<T, jthrowable>)
return CTString("java/lang/Throwable");
// else: return void -> not implemented
}
static constexpr auto signature()
{
if constexpr (!std::is_same_v<decltype(className()), void>) {
// the type signature of any object class is L<className>;
return CTString("L") + className() + CTString(";");
} else if constexpr (std::is_array_v<T>) {
using UnderlyingType = typename std::remove_extent_t<T>;
static_assert(!std::is_array_v<UnderlyingType>,
"Traits::signature() does not handle multi-dimensional arrays");
return CTString("[") + Traits<UnderlyingType>::signature();
} else if constexpr (std::is_same_v<T, jobjectArray>) {
return CTString("[Ljava/lang/Object;");
} else if constexpr (std::is_same_v<T, jbooleanArray>) {
return CTString("[Z");
} else if constexpr (std::is_same_v<T, jbyteArray>) {
return CTString("[B");
} else if constexpr (std::is_same_v<T, jshortArray>) {
return CTString("[S");
} else if constexpr (std::is_same_v<T, jintArray>) {
return CTString("[I");
} else if constexpr (std::is_same_v<T, jlongArray>) {
return CTString("[J");
} else if constexpr (std::is_same_v<T, jfloatArray>) {
return CTString("[F");
} else if constexpr (std::is_same_v<T, jdoubleArray>) {
return CTString("[D");
} else if constexpr (std::is_same_v<T, jcharArray>) {
return CTString("[C");
} else if constexpr (std::is_same_v<T, jboolean>) {
return CTString("Z");
} else if constexpr (std::is_same_v<T, bool>) {
return CTString("Z");
} else if constexpr (std::is_same_v<T, jbyte>) {
return CTString("B");
} else if constexpr (std::is_same_v<T, jchar>) {
return CTString("C");
} else if constexpr (std::is_same_v<T, char>) {
return CTString("C");
} else if constexpr (std::is_same_v<T, jshort>) {
return CTString("S");
} else if constexpr (std::is_same_v<T, short>) {
return CTString("S");
} else if constexpr (std::is_same_v<T, jint>) {
return CTString("I");
} else if constexpr (std::is_same_v<T, int>) {
return CTString("I");
} else if constexpr (std::is_same_v<T, uint>) {
return CTString("I");
} else if constexpr (std::is_same_v<T, jlong>) {
return CTString("J");
} else if constexpr (std::is_same_v<T, long>) {
return CTString("J");
} else if constexpr (std::is_same_v<T, jfloat>) {
return CTString("F");
} else if constexpr (std::is_same_v<T, float>) {
return CTString("F");
} else if constexpr (std::is_same_v<T, jdouble>) {
return CTString("D");
} else if constexpr (std::is_same_v<T, double>) {
return CTString("D");
} else if constexpr (std::is_same_v<T, void>) {
return CTString("V");
}
// else: return void -> not implemented
}
};
// compatibility until submodules are ported
template <typename T>
constexpr auto typeSignature()
{
if constexpr (std::is_same_v<T, jstring>)
return CTString("java/lang/String");
else
staticAssertClassNotRegistered();
return Traits<T>::signature();
}
template <typename T>
constexpr auto className()
{
return Traits<T>::className();
}
// have to use the compatibility functions here until porting is complete
template<typename T>
static constexpr bool isPrimitiveType()
{

View File

@ -31,7 +31,7 @@ QAndroidTimeZonePrivate::QAndroidTimeZonePrivate()
{
// Keep in sync with systemTimeZoneId():
androidTimeZone = QJniObject::callStaticMethod<QtJniTypes::TimeZone>(
QtJniTypes::className<QtJniTypes::TimeZone>(), "getDefault");
QtJniTypes::Traits<QtJniTypes::TimeZone>::className(), "getDefault");
const QJniObject id = androidTimeZone.callMethod<jstring>("getID");
m_id = id.toString().toUtf8();
}
@ -59,7 +59,7 @@ static QString getDisplayName(QJniObject zone, jint style, jboolean dst,
{
QJniObject jbcpTag = QJniObject::fromString(locale.bcp47Name());
QJniObject jlocale = QJniObject::callStaticMethod<QtJniTypes::Locale>(
QtJniTypes::className<QtJniTypes::Locale>(), "forLanguageTag",
QtJniTypes::Traits<QtJniTypes::Locale>::className(), "forLanguageTag",
jbcpTag.object<jstring>());
return zone.callMethod<jstring>("getDisplayName", dst, style,
@ -70,7 +70,7 @@ void QAndroidTimeZonePrivate::init(const QByteArray &ianaId)
{
const QString iana = QString::fromUtf8(ianaId);
androidTimeZone = QJniObject::callStaticMethod<QtJniTypes::TimeZone>(
QtJniTypes::className<QtJniTypes::TimeZone>(), "getTimeZone",
QtJniTypes::Traits<QtJniTypes::TimeZone>::className(), "getTimeZone",
QJniObject::fromString(iana).object<jstring>());
// The ID or display name of the zone we've got, if it looks like what we asked for:
@ -200,7 +200,7 @@ QByteArray QAndroidTimeZonePrivate::systemTimeZoneId() const
{
// Keep in sync with default constructor:
QJniObject androidSystemTimeZone = QJniObject::callStaticMethod<QtJniTypes::TimeZone>(
QtJniTypes::className<QtJniTypes::TimeZone>(), "getDefault");
QtJniTypes::Traits<QtJniTypes::TimeZone>::className(), "getDefault");
const QJniObject id = androidSystemTimeZone.callMethod<jstring>("getID");
return id.toString().toUtf8();
}
@ -209,7 +209,7 @@ QList<QByteArray> QAndroidTimeZonePrivate::availableTimeZoneIds() const
{
QList<QByteArray> availableTimeZoneIdList;
QJniObject androidAvailableIdList = QJniObject::callStaticMethod<QtJniTypes::StringArray>(
QtJniTypes::className<QtJniTypes::TimeZone>(), "getAvailableIDs");
QtJniTypes::Traits<QtJniTypes::TimeZone>::className(), "getAvailableIDs");
QJniEnvironment jniEnv;
int androidTZcount = jniEnv->GetArrayLength(androidAvailableIdList.object<jarray>());

View File

@ -465,7 +465,7 @@ const QLatin1String MIME_TYPE_DIR("vnd.android.document/directory");
QString documentId(const QJniObject &uri)
{
return QJniObject::callStaticMethod<jstring, QtJniTypes::UriType>(
QtJniTypes::className<QtJniTypes::DocumentsContract>(),
QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
"getDocumentId",
uri.object()).toString();
}
@ -473,7 +473,7 @@ QString documentId(const QJniObject &uri)
QString treeDocumentId(const QJniObject &uri)
{
return QJniObject::callStaticMethod<jstring, QtJniTypes::UriType>(
QtJniTypes::className<QtJniTypes::DocumentsContract>(),
QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
"getTreeDocumentId",
uri.object()).toString();
}
@ -481,7 +481,7 @@ QString treeDocumentId(const QJniObject &uri)
QJniObject buildChildDocumentsUriUsingTree(const QJniObject &uri, const QString &parentDocumentId)
{
return QJniObject::callStaticMethod<QtJniTypes::UriType>(
QtJniTypes::className<QtJniTypes::DocumentsContract>(),
QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
"buildChildDocumentsUriUsingTree",
uri.object<QtJniTypes::UriType>(),
QJniObject::fromString(parentDocumentId).object<jstring>());
@ -491,7 +491,7 @@ QJniObject buildChildDocumentsUriUsingTree(const QJniObject &uri, const QString
QJniObject buildDocumentUriUsingTree(const QJniObject &treeUri, const QString &documentId)
{
return QJniObject::callStaticMethod<QtJniTypes::UriType>(
QtJniTypes::className<QtJniTypes::DocumentsContract>(),
QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
"buildDocumentUriUsingTree",
treeUri.object<QtJniTypes::UriType>(),
QJniObject::fromString(documentId).object<jstring>());
@ -500,7 +500,7 @@ QJniObject buildDocumentUriUsingTree(const QJniObject &treeUri, const QString &d
bool isDocumentUri(const QJniObject &uri)
{
return QJniObject::callStaticMethod<jboolean>(
QtJniTypes::className<QtJniTypes::DocumentsContract>(),
QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
"isDocumentUri",
QNativeInterface::QAndroidApplication::context(),
uri.object<QtJniTypes::UriType>());
@ -509,7 +509,7 @@ bool isDocumentUri(const QJniObject &uri)
bool isTreeUri(const QJniObject &uri)
{
return QJniObject::callStaticMethod<jboolean>(
QtJniTypes::className<QtJniTypes::DocumentsContract>(),
QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
"isTreeUri",
uri.object<QtJniTypes::UriType>());
}
@ -518,7 +518,7 @@ QJniObject createDocument(const QJniObject &parentDocumentUri, const QString &mi
const QString &displayName)
{
return QJniObject::callStaticMethod<QtJniTypes::UriType>(
QtJniTypes::className<QtJniTypes::DocumentsContract>(),
QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
"createDocument",
contentResolverInstance().object<QtJniTypes::ContentResolverType>(),
parentDocumentUri.object<QtJniTypes::UriType>(),
@ -533,7 +533,7 @@ bool deleteDocument(const QJniObject &documentUri)
return {};
return QJniObject::callStaticMethod<jboolean>(
QtJniTypes::className<QtJniTypes::DocumentsContract>(),
QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
"deleteDocument",
contentResolverInstance().object<QtJniTypes::ContentResolverType>(),
documentUri.object<QtJniTypes::UriType>());
@ -548,7 +548,7 @@ QJniObject moveDocument(const QJniObject &sourceDocumentUri,
return {};
return QJniObject::callStaticMethod<QtJniTypes::UriType>(
QtJniTypes::className<QtJniTypes::DocumentsContract>(),
QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
"moveDocument",
contentResolverInstance().object<QtJniTypes::ContentResolverType>(),
sourceDocumentUri.object<QtJniTypes::UriType>(),
@ -563,7 +563,7 @@ QJniObject renameDocument(const QJniObject &documentUri, const QString &displayN
return {};
return QJniObject::callStaticMethod<QtJniTypes::UriType>(
QtJniTypes::className<QtJniTypes::DocumentsContract>(),
QtJniTypes::Traits<QtJniTypes::DocumentsContract>::className(),
"renameDocument",
contentResolverInstance().object<QtJniTypes::ContentResolverType>(),
documentUri.object<QtJniTypes::UriType>(),
@ -598,7 +598,7 @@ QJniObject parseUri(const QString &uri)
uriToParse.replace(' ', QUrl::toPercentEncoding(" "));
return QJniObject::callStaticMethod<QtJniTypes::UriType>(
QtJniTypes::className<QtJniTypes::Uri>(),
QtJniTypes::Traits<QtJniTypes::Uri>::className(),
"parse",
QJniObject::fromString(uriToParse).object<jstring>());
}

View File

@ -66,7 +66,7 @@ namespace {
QAndroidPlatformScreen* createScreenForDisplayId(int displayId)
{
const QJniObject display = QJniObject::callStaticObjectMethod<QtJniTypes::Display>(
QtJniTypes::className<QtJniTypes::QtNative>(),
QtJniTypes::Traits<QtJniTypes::QtNative>::className(),
"getDisplay",
displayId);
if (!display.isValid())
@ -184,10 +184,10 @@ QAndroidPlatformIntegration::QAndroidPlatformIntegration(const QStringList &para
qFatal("Could not bind GL_ES API");
m_primaryDisplayId = QJniObject::getStaticField<jint>(
QtJniTypes::className<QtJniTypes::Display>(), "DEFAULT_DISPLAY");
QtJniTypes::Traits<QtJniTypes::Display>::className(), "DEFAULT_DISPLAY");
const QJniObject nativeDisplaysList = QJniObject::callStaticObjectMethod<QtJniTypes::List>(
QtJniTypes::className<QtJniTypes::QtNative>(),
QtJniTypes::Traits<QtJniTypes::QtNative>::className(),
"getAvailableDisplays");
const int numberOfAvailableDisplays = nativeDisplaysList.callMethod<jint>("size");

View File

@ -92,7 +92,7 @@ QAndroidPlatformScreen::QAndroidPlatformScreen(const QJniObject &displayObject)
displayObject.object<QtJniTypes::Display>());
const auto sizeObj = QJniObject::callStaticMethod<QtJniTypes::Size>(
QtJniTypes::className<QtJniTypes::QtNative>(),
QtJniTypes::Traits<QtJniTypes::QtNative>::className(),
"getDisplaySize",
displayContext.object<QtJniTypes::Context>(),
displayObject.object<QtJniTypes::Display>());

View File

@ -78,11 +78,11 @@ bool QAndroidPlatformServices::openUrl(const QUrl &theUrl)
const auto providerName = QJniObject::fromString(appId + ".qtprovider"_L1);
const auto urlPath = QJniObject::fromString(url.path());
const auto urlFile = QJniObject(QtJniTypes::className<QtJniTypes::File>(),
const auto urlFile = QJniObject(QtJniTypes::Traits<QtJniTypes::File>::className(),
urlPath.object<jstring>());
const auto fileProviderUri = QJniObject::callStaticMethod<QtJniTypes::UriType>(
QtJniTypes::className<QtJniTypes::FileProvider>(), "getUriForFile",
QtJniTypes::Traits<QtJniTypes::FileProvider>::className(), "getUriForFile",
QAndroidApplication::context(), providerName.object<jstring>(),
urlFile.object<QtJniTypes::FileType>());

View File

@ -19,35 +19,45 @@ private slots:
struct QtJavaWrapper {};
template<>
constexpr auto QtJniTypes::typeSignature<QtJavaWrapper>()
struct QtJniTypes::Traits<QtJavaWrapper>
{
return QtJniTypes::CTString("Lorg/qtproject/qt/android/QtJavaWrapper;");
}
static constexpr auto signature()
{
return QtJniTypes::CTString("Lorg/qtproject/qt/android/QtJavaWrapper;");
}
};
template<>
constexpr auto QtJniTypes::typeSignature<QJniObject>()
struct QtJniTypes::Traits<QJniObject>
{
return QtJniTypes::CTString("Ljava/lang/Object;");
}
static constexpr auto signature()
{
return QtJniTypes::CTString("Ljava/lang/Object;");
}
};
struct QtCustomJniObject : QJniObject {};
template<>
constexpr auto QtJniTypes::typeSignature<QtCustomJniObject>()
{
return QtJniTypes::CTString("Lorg/qtproject/qt/android/QtCustomJniObject;");
}
static_assert(QtJniTypes::typeSignature<QtJavaWrapper>() == "Lorg/qtproject/qt/android/QtJavaWrapper;");
static_assert(QtJniTypes::typeSignature<QtJavaWrapper>() != "Ljava/lang/Object;");
static_assert(!(QtJniTypes::typeSignature<QtJavaWrapper>() == "X"));
template<>
struct QtJniTypes::Traits<QtCustomJniObject>
{
static constexpr auto signature()
{
return QtJniTypes::CTString("Lorg/qtproject/qt/android/QtCustomJniObject;");
}
};
static_assert(QtJniTypes::Traits<QtJavaWrapper>::signature() == "Lorg/qtproject/qt/android/QtJavaWrapper;");
static_assert(QtJniTypes::Traits<QtJavaWrapper>::signature() != "Ljava/lang/Object;");
static_assert(!(QtJniTypes::Traits<QtJavaWrapper>::signature() == "X"));
Q_DECLARE_JNI_CLASS(JavaType, "org/qtproject/qt/JavaType");
static_assert(QtJniTypes::typeSignature<QtJniTypes::JavaType>() == "Lorg/qtproject/qt/JavaType;");
static_assert(QtJniTypes::Traits<QtJniTypes::JavaType>::signature() == "Lorg/qtproject/qt/JavaType;");
Q_DECLARE_JNI_TYPE(ArrayType, "[Lorg/qtproject/qt/ArrayType;")
static_assert(QtJniTypes::typeSignature<QtJniTypes::ArrayType>() == "[Lorg/qtproject/qt/ArrayType;");
static_assert(QtJniTypes::Traits<QtJniTypes::ArrayType>::signature() == "[Lorg/qtproject/qt/ArrayType;");
Q_DECLARE_JNI_CLASS(QtTextToSpeech, "org/qtproject/qt/android/speech/QtTextToSpeech")
static_assert(QtJniTypes::className<QtJniTypes::QtTextToSpeech>() == "org/qtproject/qt/android/speech/QtTextToSpeech");
static_assert(QtJniTypes::Traits<QtJniTypes::QtTextToSpeech>::className() == "org/qtproject/qt/android/speech/QtTextToSpeech");
static_assert(QtJniTypes::fieldSignature<jint>() == "I");
static_assert(QtJniTypes::fieldSignature<jint[]>() == "[I");