From c7fbb15144cd664e4d869a5c2c036aeacb0bc4f8 Mon Sep 17 00:00:00 2001 From: Mahesh Hegde <46179734+mahesh-hegde@users.noreply.github.com> Date: Thu, 6 Jul 2023 01:48:25 +0530 Subject: [PATCH] Fix summarizer nested class bug (#312) * Fix inner classes * Fix nested class finding * Remove -r and -v options * Add checks for nested classes in config * Remove problematic steps from GH action * Add anonymous inner class example * Use sorted() for deterministic order * Refactor ClassFinder logic and add Unit test * Filter anonymous classes in summarizer itself --- .github/workflows/test-package.yml | 7 - jni/example/pubspec.lock | 2 +- jni/pubspec.yaml | 2 +- jnigen/CHANGELOG.md | 4 + jnigen/bin/jnigen.dart | 1 + .../in_app_java/lib/android_utils.dart | 417 +++++++++++++++++- .../src/android_utils/android_utils.c | 278 ++++++++++++ jnigen/java/.gitignore | 2 + .../dart_lang/jnigen/apisummarizer/Main.java | 10 +- .../apisummarizer/SummarizerOptions.java | 26 +- .../apisummarizer/disasm/AsmClassVisitor.java | 7 - .../doclet/SummarizerDoclet.java | 12 +- .../apisummarizer/util/ClassFinder.java | 187 ++++---- .../apisummarizer/util/ExceptionUtil.java | 17 +- .../jnigen/apisummarizer/util/JsonWriter.java | 4 +- .../jnigen/apisummarizer/util/Log.java | 30 +- .../jnigen/apisummarizer/ClassFinderTest.java | 95 ++++ .../apisummarizer/JSONComparisonTest.java | 12 + .../test/resources/exampleClassSummary.json | 4 - jnigen/lib/src/config/config_types.dart | 29 +- jnigen/lib/src/logging/logging.dart | 77 +++- jnigen/lib/src/summary/summary.dart | 2 + jnigen/lib/src/tools/android_sdk_tools.dart | 3 + jnigen/pubspec.yaml | 2 +- jnigen/test/config_test.dart | 4 + .../c_based/c_bindings/simple_package.c | 71 +++ .../c_based/dart_bindings/simple_package.dart | 77 ++++ .../dart_bindings/simple_package.dart | 77 ++++ .../jnigen/simple_package/Example.java | 15 + jnigen/test/summary_generation_test.dart | 12 + 30 files changed, 1326 insertions(+), 160 deletions(-) create mode 100644 jnigen/java/src/test/java/com/github/dart_lang/jnigen/apisummarizer/ClassFinderTest.java diff --git a/.github/workflows/test-package.yml b/.github/workflows/test-package.yml index ee0ecbf2..8ec3cae1 100644 --- a/.github/workflows/test-package.yml +++ b/.github/workflows/test-package.yml @@ -392,13 +392,6 @@ jobs: sudo apt-get install -y ninja-build libgtk-3-dev clang-format - run: flutter config --enable-linux-desktop - run: dart pub get - - name: Generate bindings - run: | - dart run jnigen -Doutput.c.path=_c/ -Doutput.dart.path=_dart/ --config jnigen.yaml - - name: Compare generated bindings - run: | - diff -r _c src/ - diff -r _dart lib/src/third_party - name: Generate full bindings run: dart run jnigen --config jnigen.yaml --override classes="org.apache.pdfbox" - name: Analyze generated bindings diff --git a/jni/example/pubspec.lock b/jni/example/pubspec.lock index 3080aea5..34dff04b 100644 --- a/jni/example/pubspec.lock +++ b/jni/example/pubspec.lock @@ -200,7 +200,7 @@ packages: path: ".." relative: true source: path - version: "0.5.0" + version: "0.6.0-dev.1" js: dependency: transitive description: diff --git a/jni/pubspec.yaml b/jni/pubspec.yaml index 4de344fa..0c1068a3 100644 --- a/jni/pubspec.yaml +++ b/jni/pubspec.yaml @@ -1,6 +1,6 @@ name: jni description: Library to access JNI from dart and flutter -version: 0.5.0 +version: 0.6.0-dev.1 repository: https://github.com/dart-lang/jnigen/tree/main/jni environment: diff --git a/jnigen/CHANGELOG.md b/jnigen/CHANGELOG.md index 6ed1e844..48b1b212 100644 --- a/jnigen/CHANGELOG.md +++ b/jnigen/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.6.0-dev.1 +* **Breaking Change** Specifying a class always pulls in nested classes by default. If a nested class is specified in config, it will be an error. +* Save all `jnigen` logs to a file in `.dart_tool/jnigen/logs/`. This is useful for debugging. + ## 0.6.0-dev.0 * **Breaking Change** Removed `suspend_fun_to_async` flag from the config. It's now happening by default since we read the Kotlin's metadata and reliably identify the `suspend fun`s. diff --git a/jnigen/bin/jnigen.dart b/jnigen/bin/jnigen.dart index be04b584..8dc69e37 100644 --- a/jnigen/bin/jnigen.dart +++ b/jnigen/bin/jnigen.dart @@ -6,6 +6,7 @@ import 'package:jnigen/jnigen.dart'; import 'package:jnigen/src/logging/logging.dart'; void main(List args) async { + enableLoggingToFile(); Config config; try { config = Config.parseArgs(args); diff --git a/jnigen/example/in_app_java/lib/android_utils.dart b/jnigen/example/in_app_java/lib/android_utils.dart index 77ffac78..028fa429 100644 --- a/jnigen/example/in_app_java/lib/android_utils.dart +++ b/jnigen/example/in_app_java/lib/android_utils.dart @@ -2382,6 +2382,419 @@ class $DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactoryType extends jni } } +/// from: android.os.Build$Partition +class Build_Partition extends jni.JObject { + @override + late final jni.JObjType $type = type; + + Build_Partition.fromRef( + jni.JObjectPtr ref, + ) : super.fromRef(ref); + + /// The type which includes information such as the signature of this class. + static const type = $Build_PartitionType(); + + /// from: static public final java.lang.String PARTITION_NAME_SYSTEM + static const PARTITION_NAME_SYSTEM = r"""system"""; + + static final _getName = jniLookup< + ffi.NativeFunction< + jni.JniResult Function( + ffi.Pointer)>>("Build_Partition__getName") + .asFunction)>(); + + /// from: public java.lang.String getName() + /// The returned object must be deleted after use, by calling the `delete` method. + jni.JString getName() { + return const jni.JStringType().fromRef(_getName(reference).object); + } + + static final _getFingerprint = jniLookup< + ffi.NativeFunction< + jni.JniResult Function( + ffi.Pointer)>>("Build_Partition__getFingerprint") + .asFunction)>(); + + /// from: public java.lang.String getFingerprint() + /// The returned object must be deleted after use, by calling the `delete` method. + jni.JString getFingerprint() { + return const jni.JStringType().fromRef(_getFingerprint(reference).object); + } + + static final _getBuildTimeMillis = jniLookup< + ffi.NativeFunction< + jni.JniResult Function(ffi.Pointer)>>( + "Build_Partition__getBuildTimeMillis") + .asFunction)>(); + + /// from: public long getBuildTimeMillis() + int getBuildTimeMillis() { + return _getBuildTimeMillis(reference).long; + } + + static final _equals1 = jniLookup< + ffi.NativeFunction< + jni.JniResult Function(ffi.Pointer, + ffi.Pointer)>>("Build_Partition__equals1") + .asFunction< + jni.JniResult Function( + ffi.Pointer, ffi.Pointer)>(); + + /// from: public boolean equals(java.lang.Object object) + bool equals1( + jni.JObject object, + ) { + return _equals1(reference, object.reference).boolean; + } + + static final _hashCode1 = jniLookup< + ffi.NativeFunction< + jni.JniResult Function( + ffi.Pointer)>>("Build_Partition__hashCode1") + .asFunction)>(); + + /// from: public int hashCode() + int hashCode1() { + return _hashCode1(reference).integer; + } +} + +class $Build_PartitionType extends jni.JObjType { + const $Build_PartitionType(); + + @override + String get signature => r"Landroid/os/Build$Partition;"; + + @override + Build_Partition fromRef(jni.JObjectPtr ref) => Build_Partition.fromRef(ref); + + @override + jni.JObjType get superType => const jni.JObjectType(); + + @override + final superCount = 1; + + @override + int get hashCode => ($Build_PartitionType).hashCode; + + @override + bool operator ==(Object other) { + return other.runtimeType == ($Build_PartitionType) && + other is $Build_PartitionType; + } +} + +/// from: android.os.Build$VERSION +class Build_VERSION extends jni.JObject { + @override + late final jni.JObjType $type = type; + + Build_VERSION.fromRef( + jni.JObjectPtr ref, + ) : super.fromRef(ref); + + /// The type which includes information such as the signature of this class. + static const type = $Build_VERSIONType(); + static final _get_BASE_OS = + jniLookup>( + "get_Build_VERSION__BASE_OS") + .asFunction(); + + /// from: static public final java.lang.String BASE_OS + /// The returned object must be deleted after use, by calling the `delete` method. + static jni.JString get BASE_OS => + const jni.JStringType().fromRef(_get_BASE_OS().object); + + static final _get_CODENAME = + jniLookup>( + "get_Build_VERSION__CODENAME") + .asFunction(); + + /// from: static public final java.lang.String CODENAME + /// The returned object must be deleted after use, by calling the `delete` method. + static jni.JString get CODENAME => + const jni.JStringType().fromRef(_get_CODENAME().object); + + static final _get_INCREMENTAL = + jniLookup>( + "get_Build_VERSION__INCREMENTAL") + .asFunction(); + + /// from: static public final java.lang.String INCREMENTAL + /// The returned object must be deleted after use, by calling the `delete` method. + static jni.JString get INCREMENTAL => + const jni.JStringType().fromRef(_get_INCREMENTAL().object); + + static final _get_MEDIA_PERFORMANCE_CLASS = + jniLookup>( + "get_Build_VERSION__MEDIA_PERFORMANCE_CLASS") + .asFunction(); + + /// from: static public final int MEDIA_PERFORMANCE_CLASS + static int get MEDIA_PERFORMANCE_CLASS => + _get_MEDIA_PERFORMANCE_CLASS().integer; + + static final _get_PREVIEW_SDK_INT = + jniLookup>( + "get_Build_VERSION__PREVIEW_SDK_INT") + .asFunction(); + + /// from: static public final int PREVIEW_SDK_INT + static int get PREVIEW_SDK_INT => _get_PREVIEW_SDK_INT().integer; + + static final _get_RELEASE = + jniLookup>( + "get_Build_VERSION__RELEASE") + .asFunction(); + + /// from: static public final java.lang.String RELEASE + /// The returned object must be deleted after use, by calling the `delete` method. + static jni.JString get RELEASE => + const jni.JStringType().fromRef(_get_RELEASE().object); + + static final _get_RELEASE_OR_CODENAME = + jniLookup>( + "get_Build_VERSION__RELEASE_OR_CODENAME") + .asFunction(); + + /// from: static public final java.lang.String RELEASE_OR_CODENAME + /// The returned object must be deleted after use, by calling the `delete` method. + static jni.JString get RELEASE_OR_CODENAME => + const jni.JStringType().fromRef(_get_RELEASE_OR_CODENAME().object); + + static final _get_RELEASE_OR_PREVIEW_DISPLAY = + jniLookup>( + "get_Build_VERSION__RELEASE_OR_PREVIEW_DISPLAY") + .asFunction(); + + /// from: static public final java.lang.String RELEASE_OR_PREVIEW_DISPLAY + /// The returned object must be deleted after use, by calling the `delete` method. + static jni.JString get RELEASE_OR_PREVIEW_DISPLAY => + const jni.JStringType().fromRef(_get_RELEASE_OR_PREVIEW_DISPLAY().object); + + static final _get_SDK = + jniLookup>( + "get_Build_VERSION__SDK") + .asFunction(); + + /// from: static public final java.lang.String SDK + /// The returned object must be deleted after use, by calling the `delete` method. + static jni.JString get SDK => + const jni.JStringType().fromRef(_get_SDK().object); + + static final _get_SDK_INT = + jniLookup>( + "get_Build_VERSION__SDK_INT") + .asFunction(); + + /// from: static public final int SDK_INT + static int get SDK_INT => _get_SDK_INT().integer; + + static final _get_SECURITY_PATCH = + jniLookup>( + "get_Build_VERSION__SECURITY_PATCH") + .asFunction(); + + /// from: static public final java.lang.String SECURITY_PATCH + /// The returned object must be deleted after use, by calling the `delete` method. + static jni.JString get SECURITY_PATCH => + const jni.JStringType().fromRef(_get_SECURITY_PATCH().object); + + static final _ctor = jniLookup< + ffi.NativeFunction< + jni.JniResult Function( + ffi.Pointer)>>("Build_VERSION__ctor") + .asFunction)>(); + + /// from: public void (android.os.Build $parent) + /// The returned object must be deleted after use, by calling the `delete` method. + factory Build_VERSION( + Build $parent, + ) { + return Build_VERSION.fromRef(_ctor($parent.reference).object); + } +} + +class $Build_VERSIONType extends jni.JObjType { + const $Build_VERSIONType(); + + @override + String get signature => r"Landroid/os/Build$VERSION;"; + + @override + Build_VERSION fromRef(jni.JObjectPtr ref) => Build_VERSION.fromRef(ref); + + @override + jni.JObjType get superType => const jni.JObjectType(); + + @override + final superCount = 1; + + @override + int get hashCode => ($Build_VERSIONType).hashCode; + + @override + bool operator ==(Object other) { + return other.runtimeType == ($Build_VERSIONType) && + other is $Build_VERSIONType; + } +} + +/// from: android.os.Build$VERSION_CODES +class Build_VERSION_CODES extends jni.JObject { + @override + late final jni.JObjType $type = type; + + Build_VERSION_CODES.fromRef( + jni.JObjectPtr ref, + ) : super.fromRef(ref); + + /// The type which includes information such as the signature of this class. + static const type = $Build_VERSION_CODESType(); + + /// from: static public final int BASE + static const BASE = 1; + + /// from: static public final int BASE_1_1 + static const BASE_1_1 = 2; + + /// from: static public final int CUPCAKE + static const CUPCAKE = 3; + + /// from: static public final int CUR_DEVELOPMENT + static const CUR_DEVELOPMENT = 10000; + + /// from: static public final int DONUT + static const DONUT = 4; + + /// from: static public final int ECLAIR + static const ECLAIR = 5; + + /// from: static public final int ECLAIR_0_1 + static const ECLAIR_0_1 = 6; + + /// from: static public final int ECLAIR_MR1 + static const ECLAIR_MR1 = 7; + + /// from: static public final int FROYO + static const FROYO = 8; + + /// from: static public final int GINGERBREAD + static const GINGERBREAD = 9; + + /// from: static public final int GINGERBREAD_MR1 + static const GINGERBREAD_MR1 = 10; + + /// from: static public final int HONEYCOMB + static const HONEYCOMB = 11; + + /// from: static public final int HONEYCOMB_MR1 + static const HONEYCOMB_MR1 = 12; + + /// from: static public final int HONEYCOMB_MR2 + static const HONEYCOMB_MR2 = 13; + + /// from: static public final int ICE_CREAM_SANDWICH + static const ICE_CREAM_SANDWICH = 14; + + /// from: static public final int ICE_CREAM_SANDWICH_MR1 + static const ICE_CREAM_SANDWICH_MR1 = 15; + + /// from: static public final int JELLY_BEAN + static const JELLY_BEAN = 16; + + /// from: static public final int JELLY_BEAN_MR1 + static const JELLY_BEAN_MR1 = 17; + + /// from: static public final int JELLY_BEAN_MR2 + static const JELLY_BEAN_MR2 = 18; + + /// from: static public final int KITKAT + static const KITKAT = 19; + + /// from: static public final int KITKAT_WATCH + static const KITKAT_WATCH = 20; + + /// from: static public final int LOLLIPOP + static const LOLLIPOP = 21; + + /// from: static public final int LOLLIPOP_MR1 + static const LOLLIPOP_MR1 = 22; + + /// from: static public final int M + static const M = 23; + + /// from: static public final int N + static const N = 24; + + /// from: static public final int N_MR1 + static const N_MR1 = 25; + + /// from: static public final int O + static const O = 26; + + /// from: static public final int O_MR1 + static const O_MR1 = 27; + + /// from: static public final int P + static const P = 28; + + /// from: static public final int Q + static const Q = 29; + + /// from: static public final int R + static const R = 30; + + /// from: static public final int S + static const S = 31; + + /// from: static public final int S_V2 + static const S_V2 = 32; + + /// from: static public final int TIRAMISU + static const TIRAMISU = 33; + + static final _ctor = jniLookup< + ffi.NativeFunction< + jni.JniResult Function( + ffi.Pointer)>>("Build_VERSION_CODES__ctor") + .asFunction)>(); + + /// from: public void (android.os.Build $parent) + /// The returned object must be deleted after use, by calling the `delete` method. + factory Build_VERSION_CODES( + Build $parent, + ) { + return Build_VERSION_CODES.fromRef(_ctor($parent.reference).object); + } +} + +class $Build_VERSION_CODESType extends jni.JObjType { + const $Build_VERSION_CODESType(); + + @override + String get signature => r"Landroid/os/Build$VERSION_CODES;"; + + @override + Build_VERSION_CODES fromRef(jni.JObjectPtr ref) => + Build_VERSION_CODES.fromRef(ref); + + @override + jni.JObjType get superType => const jni.JObjectType(); + + @override + final superCount = 1; + + @override + int get hashCode => ($Build_VERSION_CODESType).hashCode; + + @override + bool operator ==(Object other) { + return other.runtimeType == ($Build_VERSION_CODESType) && + other is $Build_VERSION_CODESType; + } +} + /// from: android.os.Build class Build extends jni.JObject { @override @@ -2688,8 +3101,8 @@ class Build extends jni.JObject { /// from: static public java.util.List getFingerprintedPartitions() /// The returned object must be deleted after use, by calling the `delete` method. - static jni.JList getFingerprintedPartitions() { - return const jni.JListType(jni.JObjectType()) + static jni.JList getFingerprintedPartitions() { + return const jni.JListType($Build_PartitionType()) .fromRef(_getFingerprintedPartitions().object); } diff --git a/jnigen/example/in_app_java/src/android_utils/android_utils.c b/jnigen/example/in_app_java/src/android_utils/android_utils.c index 6cf0cf28..c41ff6e9 100644 --- a/jnigen/example/in_app_java/src/android_utils/android_utils.c +++ b/jnigen/example/in_app_java/src/android_utils/android_utils.c @@ -1352,6 +1352,284 @@ JniResult DefaultEmojiCompatConfig_DefaultEmojiCompatConfigFactory__create( return to_global_ref_result(_result); } +// android.os.Build$Partition +jclass _c_Build_Partition = NULL; + +jmethodID _m_Build_Partition__getName = NULL; +FFI_PLUGIN_EXPORT +JniResult Build_Partition__getName(jobject self_) { + load_env(); + load_class_global_ref(&_c_Build_Partition, "android/os/Build$Partition"); + if (_c_Build_Partition == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_method(_c_Build_Partition, &_m_Build_Partition__getName, "getName", + "()Ljava/lang/String;"); + if (_m_Build_Partition__getName == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + jobject _result = + (*jniEnv)->CallObjectMethod(jniEnv, self_, _m_Build_Partition__getName); + return to_global_ref_result(_result); +} + +jmethodID _m_Build_Partition__getFingerprint = NULL; +FFI_PLUGIN_EXPORT +JniResult Build_Partition__getFingerprint(jobject self_) { + load_env(); + load_class_global_ref(&_c_Build_Partition, "android/os/Build$Partition"); + if (_c_Build_Partition == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_method(_c_Build_Partition, &_m_Build_Partition__getFingerprint, + "getFingerprint", "()Ljava/lang/String;"); + if (_m_Build_Partition__getFingerprint == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + jobject _result = (*jniEnv)->CallObjectMethod( + jniEnv, self_, _m_Build_Partition__getFingerprint); + return to_global_ref_result(_result); +} + +jmethodID _m_Build_Partition__getBuildTimeMillis = NULL; +FFI_PLUGIN_EXPORT +JniResult Build_Partition__getBuildTimeMillis(jobject self_) { + load_env(); + load_class_global_ref(&_c_Build_Partition, "android/os/Build$Partition"); + if (_c_Build_Partition == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_method(_c_Build_Partition, &_m_Build_Partition__getBuildTimeMillis, + "getBuildTimeMillis", "()J"); + if (_m_Build_Partition__getBuildTimeMillis == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + int64_t _result = (*jniEnv)->CallLongMethod( + jniEnv, self_, _m_Build_Partition__getBuildTimeMillis); + return (JniResult){.value = {.j = _result}, .exception = check_exception()}; +} + +jmethodID _m_Build_Partition__equals1 = NULL; +FFI_PLUGIN_EXPORT +JniResult Build_Partition__equals1(jobject self_, jobject object) { + load_env(); + load_class_global_ref(&_c_Build_Partition, "android/os/Build$Partition"); + if (_c_Build_Partition == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_method(_c_Build_Partition, &_m_Build_Partition__equals1, "equals", + "(Ljava/lang/Object;)Z"); + if (_m_Build_Partition__equals1 == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + uint8_t _result = (*jniEnv)->CallBooleanMethod( + jniEnv, self_, _m_Build_Partition__equals1, object); + return (JniResult){.value = {.z = _result}, .exception = check_exception()}; +} + +jmethodID _m_Build_Partition__hashCode1 = NULL; +FFI_PLUGIN_EXPORT +JniResult Build_Partition__hashCode1(jobject self_) { + load_env(); + load_class_global_ref(&_c_Build_Partition, "android/os/Build$Partition"); + if (_c_Build_Partition == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_method(_c_Build_Partition, &_m_Build_Partition__hashCode1, "hashCode", + "()I"); + if (_m_Build_Partition__hashCode1 == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + int32_t _result = + (*jniEnv)->CallIntMethod(jniEnv, self_, _m_Build_Partition__hashCode1); + return (JniResult){.value = {.i = _result}, .exception = check_exception()}; +} + +// android.os.Build$VERSION +jclass _c_Build_VERSION = NULL; + +jmethodID _m_Build_VERSION__ctor = NULL; +FFI_PLUGIN_EXPORT +JniResult Build_VERSION__ctor(jobject _parent) { + load_env(); + load_class_global_ref(&_c_Build_VERSION, "android/os/Build$VERSION"); + if (_c_Build_VERSION == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_method(_c_Build_VERSION, &_m_Build_VERSION__ctor, "", + "(Landroid/os/Build;)V"); + if (_m_Build_VERSION__ctor == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + jobject _result = (*jniEnv)->NewObject(jniEnv, _c_Build_VERSION, + _m_Build_VERSION__ctor, _parent); + return to_global_ref_result(_result); +} + +jfieldID _f_Build_VERSION__BASE_OS = NULL; +FFI_PLUGIN_EXPORT +JniResult get_Build_VERSION__BASE_OS() { + load_env(); + load_class_global_ref(&_c_Build_VERSION, "android/os/Build$VERSION"); + if (_c_Build_VERSION == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_static_field(_c_Build_VERSION, &_f_Build_VERSION__BASE_OS, "BASE_OS", + "Ljava/lang/String;"); + jobject _result = (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build_VERSION, + _f_Build_VERSION__BASE_OS); + return to_global_ref_result(_result); +} + +jfieldID _f_Build_VERSION__CODENAME = NULL; +FFI_PLUGIN_EXPORT +JniResult get_Build_VERSION__CODENAME() { + load_env(); + load_class_global_ref(&_c_Build_VERSION, "android/os/Build$VERSION"); + if (_c_Build_VERSION == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_static_field(_c_Build_VERSION, &_f_Build_VERSION__CODENAME, "CODENAME", + "Ljava/lang/String;"); + jobject _result = (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build_VERSION, + _f_Build_VERSION__CODENAME); + return to_global_ref_result(_result); +} + +jfieldID _f_Build_VERSION__INCREMENTAL = NULL; +FFI_PLUGIN_EXPORT +JniResult get_Build_VERSION__INCREMENTAL() { + load_env(); + load_class_global_ref(&_c_Build_VERSION, "android/os/Build$VERSION"); + if (_c_Build_VERSION == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_static_field(_c_Build_VERSION, &_f_Build_VERSION__INCREMENTAL, + "INCREMENTAL", "Ljava/lang/String;"); + jobject _result = (*jniEnv)->GetStaticObjectField( + jniEnv, _c_Build_VERSION, _f_Build_VERSION__INCREMENTAL); + return to_global_ref_result(_result); +} + +jfieldID _f_Build_VERSION__MEDIA_PERFORMANCE_CLASS = NULL; +FFI_PLUGIN_EXPORT +JniResult get_Build_VERSION__MEDIA_PERFORMANCE_CLASS() { + load_env(); + load_class_global_ref(&_c_Build_VERSION, "android/os/Build$VERSION"); + if (_c_Build_VERSION == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_static_field(_c_Build_VERSION, + &_f_Build_VERSION__MEDIA_PERFORMANCE_CLASS, + "MEDIA_PERFORMANCE_CLASS", "I"); + int32_t _result = (*jniEnv)->GetStaticIntField( + jniEnv, _c_Build_VERSION, _f_Build_VERSION__MEDIA_PERFORMANCE_CLASS); + return (JniResult){.value = {.i = _result}, .exception = check_exception()}; +} + +jfieldID _f_Build_VERSION__PREVIEW_SDK_INT = NULL; +FFI_PLUGIN_EXPORT +JniResult get_Build_VERSION__PREVIEW_SDK_INT() { + load_env(); + load_class_global_ref(&_c_Build_VERSION, "android/os/Build$VERSION"); + if (_c_Build_VERSION == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_static_field(_c_Build_VERSION, &_f_Build_VERSION__PREVIEW_SDK_INT, + "PREVIEW_SDK_INT", "I"); + int32_t _result = (*jniEnv)->GetStaticIntField( + jniEnv, _c_Build_VERSION, _f_Build_VERSION__PREVIEW_SDK_INT); + return (JniResult){.value = {.i = _result}, .exception = check_exception()}; +} + +jfieldID _f_Build_VERSION__RELEASE = NULL; +FFI_PLUGIN_EXPORT +JniResult get_Build_VERSION__RELEASE() { + load_env(); + load_class_global_ref(&_c_Build_VERSION, "android/os/Build$VERSION"); + if (_c_Build_VERSION == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_static_field(_c_Build_VERSION, &_f_Build_VERSION__RELEASE, "RELEASE", + "Ljava/lang/String;"); + jobject _result = (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build_VERSION, + _f_Build_VERSION__RELEASE); + return to_global_ref_result(_result); +} + +jfieldID _f_Build_VERSION__RELEASE_OR_CODENAME = NULL; +FFI_PLUGIN_EXPORT +JniResult get_Build_VERSION__RELEASE_OR_CODENAME() { + load_env(); + load_class_global_ref(&_c_Build_VERSION, "android/os/Build$VERSION"); + if (_c_Build_VERSION == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_static_field(_c_Build_VERSION, &_f_Build_VERSION__RELEASE_OR_CODENAME, + "RELEASE_OR_CODENAME", "Ljava/lang/String;"); + jobject _result = (*jniEnv)->GetStaticObjectField( + jniEnv, _c_Build_VERSION, _f_Build_VERSION__RELEASE_OR_CODENAME); + return to_global_ref_result(_result); +} + +jfieldID _f_Build_VERSION__RELEASE_OR_PREVIEW_DISPLAY = NULL; +FFI_PLUGIN_EXPORT +JniResult get_Build_VERSION__RELEASE_OR_PREVIEW_DISPLAY() { + load_env(); + load_class_global_ref(&_c_Build_VERSION, "android/os/Build$VERSION"); + if (_c_Build_VERSION == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_static_field(_c_Build_VERSION, + &_f_Build_VERSION__RELEASE_OR_PREVIEW_DISPLAY, + "RELEASE_OR_PREVIEW_DISPLAY", "Ljava/lang/String;"); + jobject _result = (*jniEnv)->GetStaticObjectField( + jniEnv, _c_Build_VERSION, _f_Build_VERSION__RELEASE_OR_PREVIEW_DISPLAY); + return to_global_ref_result(_result); +} + +jfieldID _f_Build_VERSION__SDK = NULL; +FFI_PLUGIN_EXPORT +JniResult get_Build_VERSION__SDK() { + load_env(); + load_class_global_ref(&_c_Build_VERSION, "android/os/Build$VERSION"); + if (_c_Build_VERSION == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_static_field(_c_Build_VERSION, &_f_Build_VERSION__SDK, "SDK", + "Ljava/lang/String;"); + jobject _result = (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build_VERSION, + _f_Build_VERSION__SDK); + return to_global_ref_result(_result); +} + +jfieldID _f_Build_VERSION__SDK_INT = NULL; +FFI_PLUGIN_EXPORT +JniResult get_Build_VERSION__SDK_INT() { + load_env(); + load_class_global_ref(&_c_Build_VERSION, "android/os/Build$VERSION"); + if (_c_Build_VERSION == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_static_field(_c_Build_VERSION, &_f_Build_VERSION__SDK_INT, "SDK_INT", + "I"); + int32_t _result = (*jniEnv)->GetStaticIntField(jniEnv, _c_Build_VERSION, + _f_Build_VERSION__SDK_INT); + return (JniResult){.value = {.i = _result}, .exception = check_exception()}; +} + +jfieldID _f_Build_VERSION__SECURITY_PATCH = NULL; +FFI_PLUGIN_EXPORT +JniResult get_Build_VERSION__SECURITY_PATCH() { + load_env(); + load_class_global_ref(&_c_Build_VERSION, "android/os/Build$VERSION"); + if (_c_Build_VERSION == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_static_field(_c_Build_VERSION, &_f_Build_VERSION__SECURITY_PATCH, + "SECURITY_PATCH", "Ljava/lang/String;"); + jobject _result = (*jniEnv)->GetStaticObjectField( + jniEnv, _c_Build_VERSION, _f_Build_VERSION__SECURITY_PATCH); + return to_global_ref_result(_result); +} + +// android.os.Build$VERSION_CODES +jclass _c_Build_VERSION_CODES = NULL; + +jmethodID _m_Build_VERSION_CODES__ctor = NULL; +FFI_PLUGIN_EXPORT +JniResult Build_VERSION_CODES__ctor(jobject _parent) { + load_env(); + load_class_global_ref(&_c_Build_VERSION_CODES, + "android/os/Build$VERSION_CODES"); + if (_c_Build_VERSION_CODES == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_method(_c_Build_VERSION_CODES, &_m_Build_VERSION_CODES__ctor, "", + "(Landroid/os/Build;)V"); + if (_m_Build_VERSION_CODES__ctor == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + jobject _result = (*jniEnv)->NewObject(jniEnv, _c_Build_VERSION_CODES, + _m_Build_VERSION_CODES__ctor, _parent); + return to_global_ref_result(_result); +} + // android.os.Build jclass _c_Build = NULL; diff --git a/jnigen/java/.gitignore b/jnigen/java/.gitignore index fe41dfd1..5ccba179 100644 --- a/jnigen/java/.gitignore +++ b/jnigen/java/.gitignore @@ -8,3 +8,5 @@ target/* .idea/jarRepositories.xml .idea/misc.xml .idea/runConfigurations.xml + +.classpath ## Used for experimenting with JShell REPL diff --git a/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/Main.java b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/Main.java index 1eda4600..0077fd71 100644 --- a/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/Main.java +++ b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/Main.java @@ -7,7 +7,10 @@ import com.github.dart_lang.jnigen.apisummarizer.disasm.AsmSummarizer; import com.github.dart_lang.jnigen.apisummarizer.doclet.SummarizerDoclet; import com.github.dart_lang.jnigen.apisummarizer.elements.ClassDecl; -import com.github.dart_lang.jnigen.apisummarizer.util.*; +import com.github.dart_lang.jnigen.apisummarizer.util.ClassFinder; +import com.github.dart_lang.jnigen.apisummarizer.util.InputStreamProvider; +import com.github.dart_lang.jnigen.apisummarizer.util.JsonWriter; +import com.github.dart_lang.jnigen.apisummarizer.util.StreamUtil; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; @@ -38,16 +41,12 @@ public static List runDocletWithClass( Class docletClass, List fileObjects, SummarizerOptions options) { - Log.setVerbose(options.verbose); var fileManager = javaDoc.getStandardFileManager(null, null, null); var cli = new ArrayList(); cli.add((options.useModules ? "--module-" : "--") + "source-path=" + options.sourcePath); if (options.classPath != null) { cli.add("--class-path=" + options.classPath); } - if (options.addDependencies) { - cli.add("--expand-requires=all"); - } cli.addAll(List.of("-encoding", "utf8")); if (options.toolArgs != null) { @@ -73,7 +72,6 @@ public static void main(String[] args) throws FileNotFoundException { } else { output = new FileOutputStream(options.outputFile); } - List sourcePaths = options.sourcePath != null ? Arrays.asList(options.sourcePath.split(File.pathSeparator)) diff --git a/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/SummarizerOptions.java b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/SummarizerOptions.java index ca63a453..8a3e5450 100644 --- a/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/SummarizerOptions.java +++ b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/SummarizerOptions.java @@ -1,5 +1,6 @@ package com.github.dart_lang.jnigen.apisummarizer; +import java.util.Arrays; import org.apache.commons.cli.*; public class SummarizerOptions { @@ -9,7 +10,6 @@ public class SummarizerOptions { boolean useModules; Main.Backend backend; String modulesList; - boolean addDependencies; String toolArgs; boolean verbose; String outputFile; @@ -19,15 +19,13 @@ public class SummarizerOptions { public static SummarizerOptions fromCommandLine(CommandLine cmd) { var opts = new SummarizerOptions(); - opts.sourcePath = cmd.getOptionValue("sources", "."); + opts.sourcePath = cmd.getOptionValue("sources", null); var backendString = cmd.getOptionValue("backend", "auto"); opts.backend = Main.Backend.valueOf(backendString.toUpperCase()); opts.classPath = cmd.getOptionValue("classes", null); opts.useModules = cmd.hasOption("use-modules"); opts.modulesList = cmd.getOptionValue("module-names", null); - opts.addDependencies = cmd.hasOption("recursive"); opts.toolArgs = cmd.getOptionValue("doctool-args", null); - opts.verbose = cmd.hasOption("verbose"); opts.outputFile = cmd.getOptionValue("output-file", null); opts.args = cmd.getArgs(); if (opts.args.length == 0) { @@ -47,28 +45,16 @@ public static SummarizerOptions parseArgs(String[] args) { true, "backend to use for summary generation ('doclet', 'asm' or 'auto' (default))."); Option useModules = new Option("M", "use-modules", false, "use Java modules"); - Option recursive = new Option("r", "recursive", false, "include dependencies of classes"); Option moduleNames = new Option("m", "module-names", true, "comma separated list of module names"); Option doctoolArgs = new Option("D", "doctool-args", true, "arguments to pass to the documentation tool"); - Option verbose = new Option("v", "verbose", false, "enable verbose output"); Option outputFile = new Option("o", "output-file", true, "write JSON to file instead of stdout"); - for (Option opt : - new Option[] { - sources, - classes, - backend, - useModules, - recursive, - moduleNames, - doctoolArgs, - verbose, - outputFile, - }) { - options.addOption(opt); - } + Option[] allOptions = { + sources, classes, backend, useModules, moduleNames, doctoolArgs, outputFile + }; + Arrays.stream(allOptions).forEach(options::addOption); HelpFormatter help = new HelpFormatter(); diff --git a/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmClassVisitor.java b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmClassVisitor.java index 50bf2f0d..6a536d32 100644 --- a/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmClassVisitor.java +++ b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/disasm/AsmClassVisitor.java @@ -4,9 +4,6 @@ package com.github.dart_lang.jnigen.apisummarizer.disasm; -import static org.objectweb.asm.Opcodes.ACC_PROTECTED; -import static org.objectweb.asm.Opcodes.ACC_PUBLIC; - import com.github.dart_lang.jnigen.apisummarizer.elements.*; import com.github.dart_lang.jnigen.apisummarizer.util.SkipException; import com.github.dart_lang.jnigen.apisummarizer.util.StreamUtil; @@ -59,10 +56,6 @@ public void visit( super.visit(version, access, name, signature, superName, interfaces); } - private static boolean isPrivate(int access) { - return ((access & ACC_PUBLIC) == 0) && ((access & ACC_PROTECTED) == 0); - } - @Override public FieldVisitor visitField( int access, String name, String descriptor, String signature, Object value) { diff --git a/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/doclet/SummarizerDoclet.java b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/doclet/SummarizerDoclet.java index b1664e5e..abbc7cc6 100644 --- a/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/doclet/SummarizerDoclet.java +++ b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/doclet/SummarizerDoclet.java @@ -46,7 +46,7 @@ public static List getClasses() { @Override public boolean run(DocletEnvironment docletEnvironment) { - Log.timed("Initializing doclet"); + Log.info("Initializing doclet"); utils = AstEnv.fromEnvironment(docletEnvironment); SummarizingScanner scanner = new SummarizingScanner(); docletEnvironment.getSpecifiedElements().forEach(e -> scanner.scan(e, new SummaryCollector())); @@ -77,7 +77,7 @@ public Void scan(Element e, SummaryCollector collector) { @Override public Void visitPackage(PackageElement e, SummaryCollector collector) { - Log.verbose("Visiting package: %s", e.getQualifiedName()); + Log.info("Visiting package: %s", e.getQualifiedName()); collector.packages.push(new Package()); System.out.println("package: " + e.getQualifiedName()); var result = super.visitPackage(e, collector); @@ -91,7 +91,7 @@ public Void visitType(TypeElement e, SummaryCollector collector) { if (!collector.types.isEmpty()) { return null; } - Log.verbose("Visiting class: %s, %s", e.getQualifiedName(), collector.types); + Log.info("Visiting class: %s, %s", e.getQualifiedName(), collector.types); switch (e.getKind()) { case CLASS: case INTERFACE: @@ -102,11 +102,11 @@ public Void visitType(TypeElement e, SummaryCollector collector) { super.visitType(e, collector); types.add(collector.types.pop()); } catch (SkipException skip) { - Log.always("Skip type: %s", e.getQualifiedName()); + Log.info("Skip type: %s", e.getQualifiedName()); } break; case ANNOTATION_TYPE: - Log.always("Skip annotation type: %s", e.getQualifiedName()); + Log.info("Skip annotation type: %s", e.getQualifiedName()); break; } return null; @@ -149,7 +149,7 @@ public Void visitExecutable(ExecutableElement element, SummaryCollector collecto collector.method = null; cls.methods.add(method); } catch (SkipException skip) { - Log.always("Skip method: %s", element.getSimpleName()); + Log.info("Skip method: %s", element.getSimpleName()); } break; case STATIC_INIT: diff --git a/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/ClassFinder.java b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/ClassFinder.java index 29380224..23c1ca53 100644 --- a/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/ClassFinder.java +++ b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/ClassFinder.java @@ -1,7 +1,11 @@ package com.github.dart_lang.jnigen.apisummarizer.util; +import static com.github.dart_lang.jnigen.apisummarizer.util.ExceptionUtil.wrapCheckedException; + import java.io.File; -import java.io.FileFilter; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.*; import java.util.function.BiFunction; import java.util.function.Function; @@ -13,84 +17,146 @@ import javax.tools.StandardJavaFileManager; public class ClassFinder { + // If class is A$B$C, simpleName can be B$C or A$B$C. Doesn't matter much, because + // A can't be anonymous class. + private static boolean isNonAnonymousNestedClassName(String simpleName) { + String[] nestedParts = simpleName.split("\\$"); + return Arrays.stream(nestedParts).allMatch(part -> part.matches("[a-zA-Z_][a-zA-Z0-9_]*")); + } + + public static boolean isNestedClassOf(String pathString, String fqnWithSlashes, String suffix) { + var fqnWithSlashesDollarSign = fqnWithSlashes + "$"; + if (!pathString.startsWith(fqnWithSlashesDollarSign) || !pathString.endsWith(suffix)) { + return false; + } + String nested = + pathString.substring( + fqnWithSlashesDollarSign.length(), pathString.length() - suffix.length()); + return isNonAnonymousNestedClassName(nested); + } + + private static boolean isNonAnonymousClassFullPath(String path) { + String[] pathParts = path.split("[/\\\\]"); + String simpleNameWithExt = pathParts[pathParts.length - 1]; + int extIndex = simpleNameWithExt.indexOf('.'); + assert extIndex != -1 : "Should've passed full path with extension to this method"; + String simpleName = simpleNameWithExt.substring(0, extIndex); + return isNonAnonymousNestedClassName(simpleName); + } + + // Finds [fqn] and its children with [suffix] in [entries]. + public static Optional> findClassAndChildren( + TreeSet entries, String fqn, String sep, String suffix) { + String fqnWithSlashes = fqn.replace(".", sep); + String fqnWithSlashesSuffix = fqnWithSlashes + suffix; + String fqnWithSlashesSlash = fqnWithSlashes + sep; + String fqnWithSlashesDollarSign = fqnWithSlashes + "$"; + if (entries.contains(fqnWithSlashesSuffix)) { + List classes = new ArrayList<>(); + // Add nested classes first, because they're alphabetically first + entries.tailSet(fqnWithSlashesDollarSign).stream() + .takeWhile(entry -> entry.startsWith(fqnWithSlashesDollarSign)) + // Note: filter comes after takeWhile, because it can filter out additional elements + // eg: Class$1 - but there may be valid nested classes after Class$1. + .filter(entry -> isNestedClassOf(entry, fqnWithSlashes, suffix)) + .forEach(classes::add); + classes.add(fqnWithSlashesSuffix); + return Optional.of(classes); + } + + // consider fqnWithSlashes as a directory + List children = + entries.tailSet(fqnWithSlashesSlash).stream() + // takeWhile instead of filter - the difference is O(log n + k) instead of O(n) + // so always use takeWhile when doing a treeSet subset stream. + .takeWhile(entry -> entry.startsWith(fqnWithSlashesSlash)) + .filter(entry -> entry.endsWith(suffix)) + .filter(ClassFinder::isNonAnonymousClassFullPath) + .collect(Collectors.toList()); + return children.isEmpty() ? Optional.empty() : Optional.of(children); + } + public static void findFilesInPath( - String searchPath, + String searchLocation, String suffix, Map> classes, - Function, List> mapper) { + Function, List> mapper) { + Path searchPath = Path.of(searchLocation); + + TreeSet filePaths; + try (var walk = Files.walk(searchPath)) { + filePaths = + walk.map(searchPath::relativize) + .map(Path::toString) + .collect(Collectors.toCollection(TreeSet::new)); + } catch (IOException e) { + throw new RuntimeException(e); + } - for (var binaryName : classes.keySet()) { - if (classes.get(binaryName) != null) { + for (var className : classes.keySet()) { + if (classes.get(className) != null) { // Already found by other method of searching continue; } - var s = binaryName.replace(".", File.separator); - var f = new File(searchPath, s + suffix); - if (f.exists() && f.isFile()) { - classes.put(binaryName, mapper.apply(List.of(f))); - } - - var d = new File(searchPath, s); - if (d.exists() && d.isDirectory()) { - var files = recursiveListFiles(d, file -> file.getName().endsWith(suffix)); - classes.put(binaryName, mapper.apply(files)); + var resultPaths = findClassAndChildren(filePaths, className, File.separator, suffix); + if (resultPaths.isPresent()) { + // [filePaths] and [resultPaths] are relativized to searchPath. + // perform opposite operation (resolve) to get full paths. + var fullPaths = + resultPaths.get().stream().map(searchPath::resolve).collect(Collectors.toList()); + classes.put(className, mapper.apply(fullPaths)); } } } - public static void findFilesInJar( + public static boolean findFilesInJar( Map> classes, JarFile jar, String suffix, BiFunction, List> mapper) { - var entries = + + // It appears JAR file entries are always separated by "/" + var jarSeparator = "/"; + var entryNames = jar.stream().map(JarEntry::getName).collect(Collectors.toCollection(TreeSet::new)); - for (var binaryName : classes.keySet()) { - if (classes.get(binaryName) != null) { + boolean foundClassesInThisJar = false; + for (var fqn : classes.keySet()) { + if (classes.get(fqn) != null) { // already found continue; } - var relativePath = binaryName.replace('.', '/'); - - var filePath = relativePath + suffix; - if (entries.contains(filePath)) { - var found = List.of(jar.getEntry(filePath)); - classes.put(binaryName, mapper.apply(jar, found)); - } - - // Obtain set of all strings prefixed with relativePath + '/' - var dirPath = relativePath + '/'; - var children = - entries.tailSet(dirPath).stream() - .takeWhile(e -> e.startsWith(dirPath)) - .filter(e -> e.endsWith(suffix)) - .map(jar::getEntry) - .collect(Collectors.toList()); - if (!children.isEmpty()) { - var mapped = mapper.apply(jar, children); - classes.put(binaryName, mapped); + var resultPaths = findClassAndChildren(entryNames, fqn, jarSeparator, suffix); + if (resultPaths.isPresent()) { + var jarEntries = resultPaths.get().stream().map(jar::getEntry).collect(Collectors.toList()); + classes.put(fqn, mapper.apply(jar, jarEntries)); + foundClassesInThisJar = true; } } + return foundClassesInThisJar; } public static void find( Map> classes, List searchPaths, String suffix, - Function, List> fileMapper, + Function, List> fileMapper, BiFunction, List> entryMapper) { for (var searchPath : searchPaths) { File searchFile = new File(searchPath); if (searchFile.isDirectory()) { findFilesInPath(searchPath, suffix, classes, fileMapper); } else if (searchFile.isFile() && searchPath.endsWith(".jar")) { - var jarFile = ExceptionUtil.wrapCheckedException(JarFile::new, searchPath); - findFilesInJar(classes, jarFile, suffix, entryMapper); + var jarFile = wrapCheckedException(JarFile::new, searchPath); + var useful = findFilesInJar(classes, jarFile, suffix, entryMapper); + if (!useful) { + wrapCheckedException(jarFile::close); + } } } } private static List getJavaFileObjectsFromFiles( - List files, StandardJavaFileManager fm) { + List paths, StandardJavaFileManager fm) { var result = new ArrayList(); + var files = StreamUtil.map(paths, Path::toFile); fm.getJavaFileObjectsFromFiles(files).forEach(result::add); return result; } @@ -100,8 +166,8 @@ private static List getJavaFileObjectsFromJar( return StreamUtil.map(entries, (entry) -> new JarEntryFileObject(jarFile, entry)); } - private static List getInputStreamProvidersFromFiles(List files) { - return StreamUtil.map(files, FileInputStreamProvider::new); + private static List getInputStreamProvidersFromFiles(List files) { + return StreamUtil.map(files, (path) -> new FileInputStreamProvider(path.toFile())); } private static List getInputStreamProvidersFromJar( @@ -130,37 +196,4 @@ public static void findJavaClasses( ClassFinder::getInputStreamProvidersFromFiles, ClassFinder::getInputStreamProvidersFromJar); } - - /** - * Lists all files under given directory, which satisfy the condition of filter.
- * The order of listing will be deterministic. - */ - public static List recursiveListFiles(File file, FileFilter filter) { - if (!file.exists()) { - throw new RuntimeException("File not found: " + file.getPath()); - } - - if (!file.isDirectory()) { - return List.of(file); - } - - // List files using a breadth-first traversal. - var files = new ArrayList(); - var queue = new ArrayDeque(); - queue.add(file); - while (!queue.isEmpty()) { - var dir = queue.poll(); - var list = dir.listFiles(entry -> entry.isDirectory() || filter.accept(entry)); - if (list == null) throw new IllegalArgumentException("File.listFiles returned null!"); - Arrays.sort(list); - for (var path : list) { - if (path.isDirectory()) { - queue.add(path); - } else { - files.add(path); - } - } - } - return files; - } } diff --git a/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/ExceptionUtil.java b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/ExceptionUtil.java index 3ed92a52..6b4c8bd1 100644 --- a/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/ExceptionUtil.java +++ b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/ExceptionUtil.java @@ -2,19 +2,32 @@ public class ExceptionUtil { @FunctionalInterface - public interface FunctionThrowingException { + public interface CheckedFunction { R function(T value) throws Exception; } + @FunctionalInterface + public interface CheckedRunnable { + void run() throws Exception; + } + /** * Wraps a function throwing a checked exception and throws all checked exceptions as runtime * exceptions. */ - public static R wrapCheckedException(FunctionThrowingException function, T value) { + public static R wrapCheckedException(CheckedFunction function, T value) { try { return function.function(value); } catch (Exception e) { throw new RuntimeException(e); } } + + public static void wrapCheckedException(CheckedRunnable runnable) { + try { + runnable.run(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/JsonWriter.java b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/JsonWriter.java index 873a6be9..112e241f 100644 --- a/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/JsonWriter.java +++ b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/JsonWriter.java @@ -11,7 +11,7 @@ public class JsonWriter { public static void writeJSON(List classes, OutputStream output) { var mapper = new ObjectMapper(); - Log.timed("Writing JSON"); + Log.info("Writing JSON for %d classes", classes.size()); mapper.enable(SerializationFeature.INDENT_OUTPUT); mapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY); try { @@ -19,6 +19,6 @@ public static void writeJSON(List classes, OutputStream output) { } catch (IOException e) { e.printStackTrace(); } - Log.timed("Finished"); + Log.info("Finished"); } } diff --git a/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/Log.java b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/Log.java index b2b2d78b..dfb48e18 100644 --- a/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/Log.java +++ b/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/Log.java @@ -4,30 +4,22 @@ package com.github.dart_lang.jnigen.apisummarizer.util; -public class Log { - private static long lastPrinted = System.currentTimeMillis(); - - public static void setVerbose(boolean verbose) { - Log.verboseLogs = verbose; - } +import java.util.logging.Level; +import java.util.logging.Logger; - private static boolean verboseLogs = false; +public class Log { + private static final Logger logger = Logger.getLogger("ApiSummarizer"); - public static void verbose(String format, Object... args) { - if (!verboseLogs) { - return; - } - System.err.printf(format + "\n", args); + private static void log(Level level, String format, Object... args) { + String formatted = String.format(format, args); + logger.log(level, formatted); } - public static void timed(String format, Object... args) { - long now = System.currentTimeMillis(); - System.err.printf("[%6d ms] ", now - lastPrinted); - lastPrinted = now; - System.err.printf(format + "\n", args); + public static void info(String format, Object... args) { + log(Level.INFO, format, args); } - public static void always(String format, Object... args) { - System.err.printf(format + "\n", args); + public static void warning(String format, Object... args) { + log(Level.WARNING, format, args); } } diff --git a/jnigen/java/src/test/java/com/github/dart_lang/jnigen/apisummarizer/ClassFinderTest.java b/jnigen/java/src/test/java/com/github/dart_lang/jnigen/apisummarizer/ClassFinderTest.java new file mode 100644 index 00000000..4209e3a2 --- /dev/null +++ b/jnigen/java/src/test/java/com/github/dart_lang/jnigen/apisummarizer/ClassFinderTest.java @@ -0,0 +1,95 @@ +package com.github.dart_lang.jnigen.apisummarizer; + +import static com.github.dart_lang.jnigen.apisummarizer.util.ClassFinder.findClassAndChildren; +import static com.github.dart_lang.jnigen.apisummarizer.util.ClassFinder.isNestedClassOf; +import static org.junit.Assert.*; + +import java.util.*; +import java.util.stream.Collectors; +import org.junit.Test; + +public class ClassFinderTest { + @Test + public void testNestedClassCheck() { + assertTrue( + "Single nested class", + isNestedClassOf("com/abc/Class$Nested.class", "com/abc/Class", ".class")); + assertTrue( + "Nested twice", + isNestedClassOf("com/abc/Class$Nested$Twice.class", "com/abc/Class", ".class")); + assertTrue( + "Single nested class - backslash separator", + isNestedClassOf("com\\abc\\Class$Nested.class", "com\\abc\\Class", ".class")); + assertFalse( + "Anon inner class", isNestedClassOf("com/abc/Class$1.class", "com/abc/Class", ".class")); + assertFalse( + "Anon inner class inside nested class", + isNestedClassOf("com/abc/Class$Nested$1.class", "com/abc/Class", ".class")); + assertFalse( + "Different class name", + isNestedClassOf("com/abc/AClass$Nested.class", "com/abc/Class", ".class")); + } + + private Optional> pathListOf(String sep, String... paths) { + List pathList = + Arrays.stream(paths).map(path -> path.replace("/", sep)).collect(Collectors.toList()); + return Optional.of(pathList); + } + + @Test + public void testFindChildren() { + TreeSet entriesWithSlash = + new TreeSet<>( + List.of( + "random/os/App.class", + "random/os/App$1.class", + "random/os/App$1$3.class", + "random/os/Process.class", + "random/os/Process$Fork.class", + "random/os/Process$Fork$1.class", + "random/os/Process$Fork$A$B$C$2.class", + "random/widget/Dialog.class", + "random/widget/Dialog$Button.class", + "random/widget/Dialog$Button$2.class", + "random/widget/Dialog$Button$Color.class", + "random/widget/Dialogue$Button.class", + "random/time/Clock.class", + "random/time/Clock$1.class", + "random/time/Calendar.class", + "random/time/Calendar$Month$1.class", + "random/time/Calendar$Month.class")); + TreeSet entriesWithBackslash = + entriesWithSlash.stream() + .map(x -> x.replace('/', '\\')) + .collect(Collectors.toCollection(TreeSet::new)); + Map> bySeparater = + Map.of("/", entriesWithSlash, "\\", entriesWithBackslash); + + for (var sep : bySeparater.keySet()) { + var entries = bySeparater.get(sep); + assertEquals( + pathListOf(sep, "random/os/Process$Fork.class", "random/os/Process.class"), + findClassAndChildren(entries, "random.os.Process", sep, ".class")); + assertEquals( + pathListOf( + sep, + "random/time/Calendar$Month.class", + "random/time/Calendar.class", + "random/time/Clock.class"), + findClassAndChildren(entries, "random.time", sep, ".class")); + assertEquals( + pathListOf( + sep, + "random/widget/Dialog$Button$Color.class", + "random/widget/Dialog$Button.class", + "random/widget/Dialog.class"), + findClassAndChildren(entries, "random.widget.Dialog", sep, ".class")); + assertEquals( + pathListOf(sep, "random/os/App.class"), + findClassAndChildren(entries, "random.os.App", sep, ".class")); + assertEquals( + pathListOf(sep, "random/os/App.class"), + findClassAndChildren(entries, "random.os.App", sep, ".class")); + } + } +} diff --git a/jnigen/java/src/test/java/com/github/dart_lang/jnigen/apisummarizer/JSONComparisonTest.java b/jnigen/java/src/test/java/com/github/dart_lang/jnigen/apisummarizer/JSONComparisonTest.java index 4d15ad2e..b02ee15e 100644 --- a/jnigen/java/src/test/java/com/github/dart_lang/jnigen/apisummarizer/JSONComparisonTest.java +++ b/jnigen/java/src/test/java/com/github/dart_lang/jnigen/apisummarizer/JSONComparisonTest.java @@ -1,5 +1,6 @@ package com.github.dart_lang.jnigen.apisummarizer; +import com.github.dart_lang.jnigen.apisummarizer.util.Log; import java.io.File; import java.io.IOException; import org.junit.Assert; @@ -31,11 +32,22 @@ private int gitDiff(String a, String b) throws IOException, InterruptedException @Test public void testExampleSummary() throws IOException, InterruptedException { var tempFile = File.createTempFile("summarizer_test", ".json"); + Log.info("Temporary file: %s", tempFile.getPath()); Main.main( new String[] { "-s", "src/test/resources", "com.example.Example", "-o", tempFile.getPath(), }); int comparison = gitDiff(exampleClassJsonOutput, tempFile); + if (comparison != 0) { + Log.warning("New output (%s) is different than reference output.", tempFile.getPath()); + } + + // Fail test if git diff exited with 1 Assert.assertEquals(0, comparison); + + var deleted = tempFile.delete(); + if (!deleted) { + Log.warning("Cannot delete temp file %s", tempFile.getPath()); + } } } diff --git a/jnigen/java/src/test/resources/exampleClassSummary.json b/jnigen/java/src/test/resources/exampleClassSummary.json index 90a08e41..208e7ae1 100644 --- a/jnigen/java/src/test/resources/exampleClassSummary.json +++ b/jnigen/java/src/test/resources/exampleClassSummary.json @@ -1,9 +1,7 @@ [ { "declKind" : "CLASS", "modifiers" : [ "public" ], - "simpleName" : "Example", "binaryName" : "com.example.Example", - "packageName" : "com.example", "methods" : [ { "modifiers" : [ "public" ], "name" : "", @@ -140,10 +138,8 @@ }, { "declKind" : "CLASS", "modifiers" : [ "static", "public" ], - "simpleName" : "Aux", "binaryName" : "com.example.Example$Aux", "parentName" : "com.example.Example", - "packageName" : "com.example", "methods" : [ { "modifiers" : [ "public" ], "name" : "", diff --git a/jnigen/lib/src/config/config_types.dart b/jnigen/lib/src/config/config_types.dart index f737b811..c1d166bf 100644 --- a/jnigen/lib/src/config/config_types.dart +++ b/jnigen/lib/src/config/config_types.dart @@ -268,6 +268,29 @@ class BindingExclusions { ClassFilter? classes; } +bool _isCapitalized(String s) { + final firstLetter = s.substring(0, 1); + return firstLetter == firstLetter.toUpperCase(); +} + +void _validateClassName(String className) { + final parts = className.split('.'); + assert(parts.isNotEmpty); + const nestedClassesInfo = + "Nested classes cannot be specified separately. Specifying the " + "parent class will pull the nested classes."; + if (parts.length > 1 && _isCapitalized(parts[parts.length - 2])) { + // Try to detect possible nested classes specified using dot notation eg: + // `com.package.Class.NestedClass` and emit a warning. + log.warning("It appears a nested class $className is specified in the " + "config. $nestedClassesInfo"); + } + if (className.contains('\$')) { + throw ConfigException( + "Nested class $className not allowed. $nestedClassesInfo"); + } +} + /// Configuration for jnigen binding generation. class Config { Config({ @@ -283,7 +306,11 @@ class Config { this.logLevel = Level.INFO, this.dumpJsonTo, this.imports, - }); + }) { + for (final className in classes) { + _validateClassName(className); + } + } /// Output configuration for generated bindings OutputConfig outputConfig; diff --git a/jnigen/lib/src/logging/logging.dart b/jnigen/lib/src/logging/logging.dart index 67c605ee..71db2179 100644 --- a/jnigen/lib/src/logging/logging.dart +++ b/jnigen/lib/src/logging/logging.dart @@ -18,10 +18,69 @@ String _colorize(String message, String colorCode) { return message; } +/// Format [DateTime] for use in filename +String _formatTime(DateTime now) { + return '${now.year}-${now.month}-${now.day}-' + '${now.hour}.${now.minute}.${now.second}'; +} + +// We need to respect logging level for console but log everything to file. +// Hierarchical logging is convoluted. I'm just keeping track of log level. +var _logLevel = Level.INFO; + +final _logDirUri = Directory.current.uri.resolve(".dart_tool/jnigen/logs/"); + +final _logDir = () { + final dir = Directory.fromUri(_logDirUri); + dir.createSync(recursive: true); + return dir; +}(); + +Uri _getDefaultLogFileUri() => + _logDir.uri.resolve("jnigen-${_formatTime(DateTime.now())}.log"); + +IOSink? _logStream; + +/// Enable saving the logs to a file. +/// +/// This is only meant to be called from an application entry point such as +/// `main`. +void enableLoggingToFile() { + _deleteOldLogFiles(); + if (_logStream != null) { + throw StateError('Log file is already set'); + } + _logStream = File.fromUri(_getDefaultLogFileUri()).openWrite(); +} + +// Maximum number of log files to keep. +const _maxLogFiles = 5; + +/// Delete log files except most recent [_maxLogFiles] files. +void _deleteOldLogFiles() { + final logFiles = _logDir.listSync().map((f) => File(f.path)).toList(); + // sort in descending order of last modified time. + logFiles + .sort((f1, f2) => f2.lastModifiedSync().compareTo(f1.lastModifiedSync())); + final toDelete = logFiles.length < _maxLogFiles + ? const [] + : logFiles.sublist(_maxLogFiles - 1); + for (final oldLogFile in toDelete) { + oldLogFile.deleteSync(); + } +} + Logger log = () { + // initialize the logger. final jnigenLogger = Logger('jnigen'); - Logger.root.level = Level.INFO; + Logger.root.level = Level.ALL; Logger.root.onRecord.listen((r) { + // Write to file regardless of level. + _logStream?.writeln('${r.level} ${r.time}: ${r.message}'); + // write to console only if level is above configured level. + if (r.level < _logLevel) { + return; + } var message = '(${r.loggerName}) ${r.level.name}: ${r.message}'; if ((r.level == Level.SHOUT || r.level == Level.SEVERE)) { message = _colorize(message, _ansiRed); @@ -35,10 +94,8 @@ Logger log = () { /// Set logging level to [level]. void setLoggingLevel(Level level) { - /// This initializes `log` as a side effect, so that level setting we apply - /// is always the last one applied. log.fine('Set log level: $level'); - Logger.root.level = level; + _logLevel = level; } /// Prints [message] without logging information. @@ -55,3 +112,15 @@ extension FatalErrors on Logger { return exit(exitCode); } } + +extension WriteToFile on Logger { + void writeToFile(Object? data) { + _logStream?.writeln(data); + } + + void writeSectionToFile(String? sectionName, Object? data) { + _logStream?.writeln("==== Begin $sectionName ===="); + _logStream?.writeln(data); + _logStream?.writeln("==== End $sectionName ===="); + } +} diff --git a/jnigen/lib/src/summary/summary.dart b/jnigen/lib/src/summary/summary.dart index 31473a63..09c13f5c 100644 --- a/jnigen/lib/src/summary/summary.dart +++ b/jnigen/lib/src/summary/summary.dart @@ -187,6 +187,8 @@ Future getSummary(Config config) async { stderrBuffer.toString(), 'Cannot generate summary: $e', ); + } finally { + log.writeSectionToFile("summarizer logs", stderrBuffer.toString()); } if (json == null) { throw SummaryParseException('Expected JSON element from summarizer.'); diff --git a/jnigen/lib/src/tools/android_sdk_tools.dart b/jnigen/lib/src/tools/android_sdk_tools.dart index 466863c3..3de5575f 100644 --- a/jnigen/lib/src/tools/android_sdk_tools.dart +++ b/jnigen/lib/src/tools/android_sdk_tools.dart @@ -209,6 +209,9 @@ task $_gradleGetSourcesTaskName(type: Copy) { 'yet cached. Please run `flutter build apk`$inAndroidProject and try ' 'again\n'); } + // Record both stdout and stderr of gradle. + log.writeSectionToFile("Gradle logs ($stubName)", procRes.stderr); + log.writeSectionToFile("Gradle output ($stubName)", procRes.stdout); final output = procRes.stdout as String; if (output.isEmpty) { printError(procRes.stderr); diff --git a/jnigen/pubspec.yaml b/jnigen/pubspec.yaml index 75be98a7..ee704308 100644 --- a/jnigen/pubspec.yaml +++ b/jnigen/pubspec.yaml @@ -3,7 +3,7 @@ # BSD-style license that can be found in the LICENSE file. name: jnigen -version: 0.6.0-dev.0 +version: 0.6.0-dev.1 description: Experimental generator for FFI+JNI bindings. repository: https://github.com/dart-lang/jnigen/tree/main/jnigen diff --git a/jnigen/test/config_test.dart b/jnigen/test/config_test.dart index 8538ac1c..4b6617b3 100644 --- a/jnigen/test/config_test.dart +++ b/jnigen/test/config_test.dart @@ -132,5 +132,9 @@ void main() async { name: 'Invalid log level', overrides: ['-Dlog_level=inf'], ); + testForErrorChecking( + name: 'Nested class specified', + overrides: ['-Dclasses=com.android.Clock\$Clock'], + ); }); } diff --git a/jnigen/test/simple_package_test/c_based/c_bindings/simple_package.c b/jnigen/test/simple_package_test/c_based/c_bindings/simple_package.c index 4a170e76..6c0f3e24 100644 --- a/jnigen/test/simple_package_test/c_based/c_bindings/simple_package.c +++ b/jnigen/test/simple_package_test/c_based/c_bindings/simple_package.c @@ -607,6 +607,24 @@ JniResult Example_Nested__ctor(uint8_t value) { return to_global_ref_result(_result); } +jmethodID _m_Example_Nested__usesAnonymousInnerClass = NULL; +FFI_PLUGIN_EXPORT +JniResult Example_Nested__usesAnonymousInnerClass(jobject self_) { + load_env(); + load_class_global_ref( + &_c_Example_Nested, + "com/github/dart_lang/jnigen/simple_package/Example$Nested"); + if (_c_Example_Nested == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_method(_c_Example_Nested, &_m_Example_Nested__usesAnonymousInnerClass, + "usesAnonymousInnerClass", "()V"); + if (_m_Example_Nested__usesAnonymousInnerClass == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + (*jniEnv)->CallVoidMethod(jniEnv, self_, + _m_Example_Nested__usesAnonymousInnerClass); + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; +} + jmethodID _m_Example_Nested__getValue = NULL; FFI_PLUGIN_EXPORT JniResult Example_Nested__getValue(jobject self_) { @@ -642,6 +660,59 @@ JniResult Example_Nested__setValue(jobject self_, uint8_t value) { return (JniResult){.value = {.j = 0}, .exception = check_exception()}; } +// com.github.dart_lang.jnigen.simple_package.Example$Nested$NestedTwice +jclass _c_Example_Nested_NestedTwice = NULL; + +jmethodID _m_Example_Nested_NestedTwice__ctor = NULL; +FFI_PLUGIN_EXPORT +JniResult Example_Nested_NestedTwice__ctor() { + load_env(); + load_class_global_ref( + &_c_Example_Nested_NestedTwice, + "com/github/dart_lang/jnigen/simple_package/Example$Nested$NestedTwice"); + if (_c_Example_Nested_NestedTwice == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_method(_c_Example_Nested_NestedTwice, + &_m_Example_Nested_NestedTwice__ctor, "", "()V"); + if (_m_Example_Nested_NestedTwice__ctor == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + jobject _result = (*jniEnv)->NewObject(jniEnv, _c_Example_Nested_NestedTwice, + _m_Example_Nested_NestedTwice__ctor); + return to_global_ref_result(_result); +} + +jfieldID _f_Example_Nested_NestedTwice__ZERO = NULL; +FFI_PLUGIN_EXPORT +JniResult get_Example_Nested_NestedTwice__ZERO() { + load_env(); + load_class_global_ref( + &_c_Example_Nested_NestedTwice, + "com/github/dart_lang/jnigen/simple_package/Example$Nested$NestedTwice"); + if (_c_Example_Nested_NestedTwice == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_static_field(_c_Example_Nested_NestedTwice, + &_f_Example_Nested_NestedTwice__ZERO, "ZERO", "I"); + int32_t _result = + (*jniEnv)->GetStaticIntField(jniEnv, _c_Example_Nested_NestedTwice, + _f_Example_Nested_NestedTwice__ZERO); + return (JniResult){.value = {.i = _result}, .exception = check_exception()}; +} + +FFI_PLUGIN_EXPORT +JniResult set_Example_Nested_NestedTwice__ZERO(int32_t value) { + load_env(); + load_class_global_ref( + &_c_Example_Nested_NestedTwice, + "com/github/dart_lang/jnigen/simple_package/Example$Nested$NestedTwice"); + if (_c_Example_Nested_NestedTwice == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_static_field(_c_Example_Nested_NestedTwice, + &_f_Example_Nested_NestedTwice__ZERO, "ZERO", "I"); + (*jniEnv)->SetStaticIntField(jniEnv, _c_Example_Nested_NestedTwice, + _f_Example_Nested_NestedTwice__ZERO, value); + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; +} + // com.github.dart_lang.jnigen.simple_package.Exceptions jclass _c_Exceptions = NULL; diff --git a/jnigen/test/simple_package_test/c_based/dart_bindings/simple_package.dart b/jnigen/test/simple_package_test/c_based/dart_bindings/simple_package.dart index 4e16f1e8..d0ad47c7 100644 --- a/jnigen/test/simple_package_test/c_based/dart_bindings/simple_package.dart +++ b/jnigen/test/simple_package_test/c_based/dart_bindings/simple_package.dart @@ -559,6 +559,17 @@ class Example_Nested extends jni.JObject { return Example_Nested.fromRef(_ctor(value ? 1 : 0).object); } + static final _usesAnonymousInnerClass = jniLookup< + ffi.NativeFunction< + jni.JniResult Function(ffi.Pointer)>>( + "Example_Nested__usesAnonymousInnerClass") + .asFunction)>(); + + /// from: public void usesAnonymousInnerClass() + void usesAnonymousInnerClass() { + return _usesAnonymousInnerClass(reference).check(); + } + static final _getValue = jniLookup< ffi.NativeFunction< jni.JniResult Function( @@ -610,6 +621,72 @@ class $Example_NestedType extends jni.JObjType { } } +/// from: com.github.dart_lang.jnigen.simple_package.Example$Nested$NestedTwice +class Example_Nested_NestedTwice extends jni.JObject { + @override + late final jni.JObjType $type = type; + + Example_Nested_NestedTwice.fromRef( + jni.JObjectPtr ref, + ) : super.fromRef(ref); + + /// The type which includes information such as the signature of this class. + static const type = $Example_Nested_NestedTwiceType(); + static final _get_ZERO = + jniLookup>( + "get_Example_Nested_NestedTwice__ZERO") + .asFunction(); + + static final _set_ZERO = + jniLookup>( + "set_Example_Nested_NestedTwice__ZERO") + .asFunction(); + + /// from: static public int ZERO + static int get ZERO => _get_ZERO().integer; + + /// from: static public int ZERO + static set ZERO(int value) => _set_ZERO(value).check(); + + static final _ctor = jniLookup>( + "Example_Nested_NestedTwice__ctor") + .asFunction(); + + /// from: public void () + /// The returned object must be deleted after use, by calling the `delete` method. + factory Example_Nested_NestedTwice() { + return Example_Nested_NestedTwice.fromRef(_ctor().object); + } +} + +class $Example_Nested_NestedTwiceType + extends jni.JObjType { + const $Example_Nested_NestedTwiceType(); + + @override + String get signature => + r"Lcom/github/dart_lang/jnigen/simple_package/Example$Nested$NestedTwice;"; + + @override + Example_Nested_NestedTwice fromRef(jni.JObjectPtr ref) => + Example_Nested_NestedTwice.fromRef(ref); + + @override + jni.JObjType get superType => const jni.JObjectType(); + + @override + final superCount = 1; + + @override + int get hashCode => ($Example_Nested_NestedTwiceType).hashCode; + + @override + bool operator ==(Object other) { + return other.runtimeType == ($Example_Nested_NestedTwiceType) && + other is $Example_Nested_NestedTwiceType; + } +} + /// from: com.github.dart_lang.jnigen.simple_package.Exceptions class Exceptions extends jni.JObject { @override diff --git a/jnigen/test/simple_package_test/dart_only/dart_bindings/simple_package.dart b/jnigen/test/simple_package_test/dart_only/dart_bindings/simple_package.dart index fb92e1dd..3394d3d9 100644 --- a/jnigen/test/simple_package_test/dart_only/dart_bindings/simple_package.dart +++ b/jnigen/test/simple_package_test/dart_only/dart_bindings/simple_package.dart @@ -527,6 +527,15 @@ class Example_Nested extends jni.JObject { .newObjectWithArgs(_class.reference, _id_ctor, [value ? 1 : 0]).object); } + static final _id_usesAnonymousInnerClass = jni.Jni.accessors + .getMethodIDOf(_class.reference, r"usesAnonymousInnerClass", r"()V"); + + /// from: public void usesAnonymousInnerClass() + void usesAnonymousInnerClass() { + return jni.Jni.accessors.callMethodWithArgs(reference, + _id_usesAnonymousInnerClass, jni.JniCallType.voidType, []).check(); + } + static final _id_getValue = jni.Jni.accessors.getMethodIDOf(_class.reference, r"getValue", r"()Z"); @@ -574,6 +583,74 @@ class $Example_NestedType extends jni.JObjType { } } +/// from: com.github.dart_lang.jnigen.simple_package.Example$Nested$NestedTwice +class Example_Nested_NestedTwice extends jni.JObject { + @override + late final jni.JObjType $type = type; + + Example_Nested_NestedTwice.fromRef( + jni.JObjectPtr ref, + ) : super.fromRef(ref); + + static final _class = jni.Jni.findJClass( + r"com/github/dart_lang/jnigen/simple_package/Example$Nested$NestedTwice"); + + /// The type which includes information such as the signature of this class. + static const type = $Example_Nested_NestedTwiceType(); + static final _id_ZERO = jni.Jni.accessors.getStaticFieldIDOf( + _class.reference, + r"ZERO", + r"I", + ); + + /// from: static public int ZERO + static int get ZERO => jni.Jni.accessors + .getStaticField(_class.reference, _id_ZERO, jni.JniCallType.intType) + .integer; + + /// from: static public int ZERO + static set ZERO(int value) => + jni.Jni.env.SetStaticIntField(_class.reference, _id_ZERO, value); + + static final _id_ctor = + jni.Jni.accessors.getMethodIDOf(_class.reference, r"", r"()V"); + + /// from: public void () + /// The returned object must be deleted after use, by calling the `delete` method. + factory Example_Nested_NestedTwice() { + return Example_Nested_NestedTwice.fromRef(jni.Jni.accessors + .newObjectWithArgs(_class.reference, _id_ctor, []).object); + } +} + +class $Example_Nested_NestedTwiceType + extends jni.JObjType { + const $Example_Nested_NestedTwiceType(); + + @override + String get signature => + r"Lcom/github/dart_lang/jnigen/simple_package/Example$Nested$NestedTwice;"; + + @override + Example_Nested_NestedTwice fromRef(jni.JObjectPtr ref) => + Example_Nested_NestedTwice.fromRef(ref); + + @override + jni.JObjType get superType => const jni.JObjectType(); + + @override + final superCount = 1; + + @override + int get hashCode => ($Example_Nested_NestedTwiceType).hashCode; + + @override + bool operator ==(Object other) { + return other.runtimeType == ($Example_Nested_NestedTwiceType) && + other is $Example_Nested_NestedTwiceType; + } +} + /// from: com.github.dart_lang.jnigen.simple_package.Exceptions class Exceptions extends jni.JObject { @override diff --git a/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/simple_package/Example.java b/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/simple_package/Example.java index edb27489..47fab9d2 100644 --- a/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/simple_package/Example.java +++ b/jnigen/test/simple_package_test/java/com/github/dart_lang/jnigen/simple_package/Example.java @@ -176,6 +176,17 @@ public Nested(boolean value) { this.value = value; } + public void usesAnonymousInnerClass() { + new Thread( + new Runnable() { + @Override + public void run() { + System.out.println("2982157093127690243"); + } + }) + .start(); + } + public boolean getValue() { return value; } @@ -183,5 +194,9 @@ public boolean getValue() { public void setValue(boolean value) { this.value = value; } + + public static class NestedTwice { + public static int ZERO = 0; + } } } diff --git a/jnigen/test/summary_generation_test.dart b/jnigen/test/summary_generation_test.dart index 0133bac3..e1657f1a 100644 --- a/jnigen/test/summary_generation_test.dart +++ b/jnigen/test/summary_generation_test.dart @@ -21,6 +21,15 @@ import 'package:test/test.dart'; import 'test_util/test_util.dart'; +const nestedClasses = [ + 'com.github.dart_lang.jnigen.simple_package.Example\$Nested', + 'com.github.dart_lang.jnigen.simple_package.Example\$Nested\$NestedTwice', + 'com.github.dart_lang.jnigen.generics.GrandParent\$StaticParent', + 'com.github.dart_lang.jnigen.generics.GrandParent\$StaticParent\$Child', + 'com.github.dart_lang.jnigen.generics.GrandParent\$Parent', + 'com.github.dart_lang.jnigen.generics.GrandParent\$Parent\$Child', +]; + void expectSummaryHasAllClasses(Classes? classes) { expect(classes, isNotNull); final decls = classes!.decls; @@ -28,6 +37,9 @@ void expectSummaryHasAllClasses(Classes? classes) { final declNames = decls.keys.toSet(); final expectedClasses = javaClasses.where((name) => !name.contains("annotations.")).toList(); + // Nested classes should be included automatically with parent class. + // change this part if you change this behavior intentionally. + expectedClasses.addAll(nestedClasses); expect(declNames, containsAll(expectedClasses)); }