diff --git a/jni/lib/internal_helpers_for_jnigen.dart b/jni/lib/internal_helpers_for_jnigen.dart index 257b0c25..014b84fa 100644 --- a/jni/lib/internal_helpers_for_jnigen.dart +++ b/jni/lib/internal_helpers_for_jnigen.dart @@ -7,6 +7,7 @@ library internal_helpers_for_jnigen; export 'src/accessors.dart'; +export 'src/jfinal_string.dart'; export 'src/jni.dart' show ProtectedJniExtensions; export 'src/jreference.dart'; export 'src/method_invocation.dart'; diff --git a/jnigen/example/in_app_java/lib/android_utils.dart b/jnigen/example/in_app_java/lib/android_utils.dart index 8a0e58a8..91bd65ca 100644 --- a/jnigen/example/in_app_java/lib/android_utils.dart +++ b/jnigen/example/in_app_java/lib/android_utils.dart @@ -139,8 +139,13 @@ class EmojiCompat extends jni.JObject { /// The type which includes information such as the signature of this class. static const type = $EmojiCompatType(); + static final _get_EDITOR_INFO_METAVERSION_KEY = + jniLookup>( + "get_EmojiCompat__EDITOR_INFO_METAVERSION_KEY") + .asFunction(); /// from: static public final java.lang.String EDITOR_INFO_METAVERSION_KEY + /// The returned object must be released after use, by calling the [release] method. /// /// Key in EditorInfo\#extras that represents the emoji metadata version used by the /// widget. The existence of the value means that the widget is using EmojiCompat. @@ -148,15 +153,22 @@ class EmojiCompat extends jni.JObject { /// If exists, the value for the key is an {@code int} and can be used to query EmojiCompat to /// see whether the widget has the ability to display a certain emoji using /// \#hasEmojiGlyph(CharSequence, int). - static const EDITOR_INFO_METAVERSION_KEY = - r"""android.support.text.emoji.emojiCompat_metadataVersion"""; + static jni.JString get EDITOR_INFO_METAVERSION_KEY => JFinalString( + () => _get_EDITOR_INFO_METAVERSION_KEY().object, + "android.support.text.emoji.emojiCompat_metadataVersion"); + static final _get_EDITOR_INFO_REPLACE_ALL_KEY = + jniLookup>( + "get_EmojiCompat__EDITOR_INFO_REPLACE_ALL_KEY") + .asFunction(); /// from: static public final java.lang.String EDITOR_INFO_REPLACE_ALL_KEY + /// The returned object must be released after use, by calling the [release] method. /// /// Key in EditorInfo\#extras that represents EmojiCompat.Config\#setReplaceAll(boolean) configuration parameter. The key is added only if /// EmojiCompat is used by the widget. If exists, the value is a boolean. - static const EDITOR_INFO_REPLACE_ALL_KEY = - r"""android.support.text.emoji.emojiCompat_replaceAll"""; + static jni.JString get EDITOR_INFO_REPLACE_ALL_KEY => JFinalString( + () => _get_EDITOR_INFO_REPLACE_ALL_KEY().object, + "android.support.text.emoji.emojiCompat_replaceAll"); /// from: static public final int LOAD_STATE_DEFAULT /// @@ -322,7 +334,6 @@ class EmojiCompat extends jni.JObject { /// androidx.core.graphics.PaintCompat\#hasGlyph(Paint, String) for each emoji /// subsequence. static const EMOJI_FALLBACK = 2; - static final _init = jniLookup< ffi .NativeFunction)>>( @@ -2397,10 +2408,15 @@ class Build_Partition extends jni.JObject { /// The type which includes information such as the signature of this class. static const type = $Build_PartitionType(); + static final _get_PARTITION_NAME_SYSTEM = + jniLookup>( + "get_Build_Partition__PARTITION_NAME_SYSTEM") + .asFunction(); /// from: static public final java.lang.String PARTITION_NAME_SYSTEM - static const PARTITION_NAME_SYSTEM = r"""system"""; - + /// The returned object must be released after use, by calling the [release] method. + static jni.JString get PARTITION_NAME_SYSTEM => + JFinalString(() => _get_PARTITION_NAME_SYSTEM().object, "system"); static final _getName = jniLookup< ffi .NativeFunction)>>( @@ -2753,7 +2769,6 @@ class Build_VERSION_CODES extends jni.JObject { /// from: static public final int TIRAMISU static const TIRAMISU = 33; - static final _new0 = jniLookup>( "Build_VERSION_CODES__new0") .asFunction(); @@ -3057,9 +3072,15 @@ class Build extends jni.JObject { static jni.JString get TYPE => const jni.JStringType().fromRef(_get_TYPE().object); - /// from: static public final java.lang.String UNKNOWN - static const UNKNOWN = r"""unknown"""; + static final _get_UNKNOWN = + jniLookup>( + "get_Build__UNKNOWN") + .asFunction(); + /// from: static public final java.lang.String UNKNOWN + /// The returned object must be released after use, by calling the [release] method. + static jni.JString get UNKNOWN => + JFinalString(() => _get_UNKNOWN().object, "unknown"); static final _get_USER = jniLookup>("get_Build__USER") .asFunction(); 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 983838bc..381224d4 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 @@ -517,6 +517,36 @@ JniResult EmojiCompat__updateEditorInfo(jobject self_, jobject outAttrs) { return (JniResult){.value = {.j = 0}, .exception = check_exception()}; } +jfieldID _f_EmojiCompat__EDITOR_INFO_METAVERSION_KEY = NULL; +FFI_PLUGIN_EXPORT +JniResult get_EmojiCompat__EDITOR_INFO_METAVERSION_KEY() { + load_env(); + load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat"); + if (_c_EmojiCompat == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_static_field(_c_EmojiCompat, + &_f_EmojiCompat__EDITOR_INFO_METAVERSION_KEY, + "EDITOR_INFO_METAVERSION_KEY", "Ljava/lang/String;"); + jobject _result = (*jniEnv)->GetStaticObjectField( + jniEnv, _c_EmojiCompat, _f_EmojiCompat__EDITOR_INFO_METAVERSION_KEY); + return to_global_ref_result(_result); +} + +jfieldID _f_EmojiCompat__EDITOR_INFO_REPLACE_ALL_KEY = NULL; +FFI_PLUGIN_EXPORT +JniResult get_EmojiCompat__EDITOR_INFO_REPLACE_ALL_KEY() { + load_env(); + load_class_global_ref(&_c_EmojiCompat, "androidx/emoji2/text/EmojiCompat"); + if (_c_EmojiCompat == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_static_field(_c_EmojiCompat, + &_f_EmojiCompat__EDITOR_INFO_REPLACE_ALL_KEY, + "EDITOR_INFO_REPLACE_ALL_KEY", "Ljava/lang/String;"); + jobject _result = (*jniEnv)->GetStaticObjectField( + jniEnv, _c_EmojiCompat, _f_EmojiCompat__EDITOR_INFO_REPLACE_ALL_KEY); + return to_global_ref_result(_result); +} + // androidx.emoji2.text.EmojiCompat$Config jclass _c_EmojiCompat_Config = NULL; @@ -1435,6 +1465,21 @@ JniResult Build_Partition__hashCode1(jobject self_) { return (JniResult){.value = {.i = _result}, .exception = check_exception()}; } +jfieldID _f_Build_Partition__PARTITION_NAME_SYSTEM = NULL; +FFI_PLUGIN_EXPORT +JniResult get_Build_Partition__PARTITION_NAME_SYSTEM() { + 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_static_field(_c_Build_Partition, + &_f_Build_Partition__PARTITION_NAME_SYSTEM, + "PARTITION_NAME_SYSTEM", "Ljava/lang/String;"); + jobject _result = (*jniEnv)->GetStaticObjectField( + jniEnv, _c_Build_Partition, _f_Build_Partition__PARTITION_NAME_SYSTEM); + return to_global_ref_result(_result); +} + // android.os.Build$VERSION jclass _c_Build_VERSION = NULL; @@ -2048,6 +2093,20 @@ JniResult get_Build__TYPE() { return to_global_ref_result(_result); } +jfieldID _f_Build__UNKNOWN = NULL; +FFI_PLUGIN_EXPORT +JniResult get_Build__UNKNOWN() { + load_env(); + load_class_global_ref(&_c_Build, "android/os/Build"); + if (_c_Build == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_static_field(_c_Build, &_f_Build__UNKNOWN, "UNKNOWN", + "Ljava/lang/String;"); + jobject _result = + (*jniEnv)->GetStaticObjectField(jniEnv, _c_Build, _f_Build__UNKNOWN); + return to_global_ref_result(_result); +} + jfieldID _f_Build__USER = NULL; FFI_PLUGIN_EXPORT JniResult get_Build__USER() { diff --git a/jnigen/lib/src/bindings/c_generator.dart b/jnigen/lib/src/bindings/c_generator.dart index 79b07c8b..ff3269ef 100644 --- a/jnigen/lib/src/bindings/c_generator.dart +++ b/jnigen/lib/src/bindings/c_generator.dart @@ -249,9 +249,14 @@ class _CFieldGenerator extends Visitor { final fieldNameInC = node.accept(const CFieldName()); final fieldVar = "$_fieldVarPrefix$fieldNameInC"; - // If the field is final and default is assigned, then no need to wrap - // this field. It should then be a constant in dart code. - if (node.isStatic && node.isFinal && node.defaultValue != null) { + // If the field is final and a numeric or boolean default is assigned, + // then no need to wrap this field. + // + // It should then be a constant in Dart code. + if (node.isStatic && + node.isFinal && + node.defaultValue != null && + (node.defaultValue is num || node.defaultValue is bool)) { return; } diff --git a/jnigen/lib/src/bindings/dart_generator.dart b/jnigen/lib/src/bindings/dart_generator.dart index 9c206929..43c643f2 100644 --- a/jnigen/lib/src/bindings/dart_generator.dart +++ b/jnigen/lib/src/bindings/dart_generator.dart @@ -672,11 +672,6 @@ class _TypeGenerator extends TypeVisitor { @override String visitDeclaredType(DeclaredType node) { - if (node.classDecl.binaryName == 'java.lang.Object' || - node.classDecl.binaryName == 'java.lang.String') { - return '$_jni.${node.classDecl.finalName}'; - } - // All type parameters of this type final allTypeParams = node.classDecl.allTypeParams .accept(const _TypeParamGenerator(withExtends: false)) @@ -1070,16 +1065,34 @@ class _FieldGenerator extends Visitor { if (node.isFinal && node.isStatic && node.defaultValue != null) { final name = node.finalName; final value = node.defaultValue!; - // TODO(#31): Should we leave String as a normal getter instead? - if (value is String || value is num || value is bool) { + if (value is num || value is bool) { writeDocs(node, writeReleaseInstructions: false); - s.write(' static const $name = '); - if (value is String) { - s.write('r"""$value"""'); - } else { - s.write(value); - } - s.writeln(';\n'); + s.writeln(' static const $name = $value;'); + return; + } else if (value is String) { + (isCBased ? writeCAccessor : writeDartOnlyAccessor)(node); + final type = node.type.accept(_TypeGenerator(resolver)); + writeDocs(node, writeReleaseInstructions: true); + const simpleEscapes = { + '\$': r'\$', + '\\': r'\\', + '"': r'\"', + '\n': r'\n', + '\r': r'\r', + '\t': r'\t', + }; + final sanitized = value.splitMapJoin( + // All ASCII printable characters except '"', '$', '\'. + RegExp(r'[ !#%-[\]-~]'), + onNonMatch: (s) => s.codeUnits.map((codeUnit) { + // Use normal espacing for common characters to improve readability. + return simpleEscapes[String.fromCharCode(codeUnit)] ?? + '\\u{${codeUnit.toRadixString(16)}}'; + }).join(), + ); + s.write(' static $type get $name => JFinalString(() => '); + s.write((isCBased ? cGetter : dartOnlyGetter)(node)); + s.writeln(', "$sanitized");'); return; } } diff --git a/jnigen/test/jackson_core_test/third_party/c_based/c_bindings/jackson_core.c b/jnigen/test/jackson_core_test/third_party/c_based/c_bindings/jackson_core.c index 3203612e..11bc5da4 100644 --- a/jnigen/test/jackson_core_test/third_party/c_based/c_bindings/jackson_core.c +++ b/jnigen/test/jackson_core_test/third_party/c_based/c_bindings/jackson_core.c @@ -1386,6 +1386,21 @@ JniResult JsonFactory__createJsonGenerator2(jobject self_, jobject out) { return to_global_ref_result(_result); } +jfieldID _f_JsonFactory__FORMAT_NAME_JSON = NULL; +FFI_PLUGIN_EXPORT +JniResult get_JsonFactory__FORMAT_NAME_JSON() { + load_env(); + load_class_global_ref(&_c_JsonFactory, + "com/fasterxml/jackson/core/JsonFactory"); + if (_c_JsonFactory == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_static_field(_c_JsonFactory, &_f_JsonFactory__FORMAT_NAME_JSON, + "FORMAT_NAME_JSON", "Ljava/lang/String;"); + jobject _result = (*jniEnv)->GetStaticObjectField( + jniEnv, _c_JsonFactory, _f_JsonFactory__FORMAT_NAME_JSON); + return to_global_ref_result(_result); +} + jfieldID _f_JsonFactory__DEFAULT_FACTORY_FEATURE_FLAGS = NULL; FFI_PLUGIN_EXPORT JniResult get_JsonFactory__DEFAULT_FACTORY_FEATURE_FLAGS() { diff --git a/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart b/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart index 95ddfd57..91f0956c 100644 --- a/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart +++ b/jnigen/test/jackson_core_test/third_party/c_based/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart @@ -70,13 +70,18 @@ class JsonFactory extends jni.JObject { /// The type which includes information such as the signature of this class. static const type = $JsonFactoryType(); + static final _get_FORMAT_NAME_JSON = + jniLookup>( + "get_JsonFactory__FORMAT_NAME_JSON") + .asFunction(); /// from: static public final java.lang.String FORMAT_NAME_JSON + /// The returned object must be released after use, by calling the [release] method. /// /// Name used to identify JSON format /// (and returned by \#getFormatName() - static const FORMAT_NAME_JSON = r"""JSON"""; - + static jni.JString get FORMAT_NAME_JSON => + JFinalString(() => _get_FORMAT_NAME_JSON().object, "JSON"); static final _get_DEFAULT_FACTORY_FEATURE_FLAGS = jniLookup>( "get_JsonFactory__DEFAULT_FACTORY_FEATURE_FLAGS") diff --git a/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart b/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart index 3cdf2ff8..d3dae8c2 100644 --- a/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart +++ b/jnigen/test/jackson_core_test/third_party/dart_only/dart_bindings/com/fasterxml/jackson/core/JsonFactory.dart @@ -72,13 +72,23 @@ class JsonFactory extends jni.JObject { /// The type which includes information such as the signature of this class. static const type = $JsonFactoryType(); + static final _id_FORMAT_NAME_JSON = jni.Jni.accessors.getStaticFieldIDOf( + _class.reference, + r"FORMAT_NAME_JSON", + r"Ljava/lang/String;", + ); /// from: static public final java.lang.String FORMAT_NAME_JSON + /// The returned object must be released after use, by calling the [release] method. /// /// Name used to identify JSON format /// (and returned by \#getFormatName() - static const FORMAT_NAME_JSON = r"""JSON"""; - + static jni.JString get FORMAT_NAME_JSON => JFinalString( + () => jni.Jni.accessors + .getStaticField(_class.reference, _id_FORMAT_NAME_JSON, + jni.JniCallType.objectType) + .object, + "JSON"); static final _id_DEFAULT_FACTORY_FEATURE_FLAGS = jni.Jni.accessors.getStaticFieldIDOf( _class.reference, 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 20c7e7e5..a83d6e44 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 @@ -793,6 +793,36 @@ JniResult Example__overloaded4(jobject self_, jobject a) { return (JniResult){.value = {.j = 0}, .exception = check_exception()}; } +jfieldID _f_Example__SEMICOLON_STRING = NULL; +FFI_PLUGIN_EXPORT +JniResult get_Example__SEMICOLON_STRING() { + load_env(); + load_class_global_ref(&_c_Example, + "com/github/dart_lang/jnigen/simple_package/Example"); + if (_c_Example == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_static_field(_c_Example, &_f_Example__SEMICOLON_STRING, + "SEMICOLON_STRING", "Ljava/lang/String;"); + jobject _result = (*jniEnv)->GetStaticObjectField( + jniEnv, _c_Example, _f_Example__SEMICOLON_STRING); + return to_global_ref_result(_result); +} + +jfieldID _f_Example__WEIRD_STRING = NULL; +FFI_PLUGIN_EXPORT +JniResult get_Example__WEIRD_STRING() { + load_env(); + load_class_global_ref(&_c_Example, + "com/github/dart_lang/jnigen/simple_package/Example"); + if (_c_Example == NULL) + return (JniResult){.value = {.j = 0}, .exception = check_exception()}; + load_static_field(_c_Example, &_f_Example__WEIRD_STRING, "WEIRD_STRING", + "Ljava/lang/String;"); + jobject _result = (*jniEnv)->GetStaticObjectField(jniEnv, _c_Example, + _f_Example__WEIRD_STRING); + return to_global_ref_result(_result); +} + jfieldID _f_Example__unusedRandom = NULL; FFI_PLUGIN_EXPORT JniResult get_Example__unusedRandom() { 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 7327fcfb..7954b579 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 @@ -113,10 +113,25 @@ class Example extends jni.JObject { /// from: static public final char SEMICOLON static const SEMICOLON = 59; + static final _get_SEMICOLON_STRING = + jniLookup>( + "get_Example__SEMICOLON_STRING") + .asFunction(); /// from: static public final java.lang.String SEMICOLON_STRING - static const SEMICOLON_STRING = r""";"""; + /// The returned object must be released after use, by calling the [release] method. + static jni.JString get SEMICOLON_STRING => + JFinalString(() => _get_SEMICOLON_STRING().object, ";"); + static final _get_WEIRD_STRING = + jniLookup>( + "get_Example__WEIRD_STRING") + .asFunction(); + /// from: static public final java.lang.String WEIRD_STRING + /// The returned object must be released after use, by calling the [release] method. + static jni.JString get WEIRD_STRING => JFinalString( + () => _get_WEIRD_STRING().object, + "Correct'\u{8}\u{8}\nOmega:\t\u{3a9}\n\u{d83d}\u{de0e}\u{d83d}\u{de06}\n\\no \$accidental \"changes\"!\n\u{7}"); static final _get_unusedRandom = jniLookup>( "get_Example__unusedRandom") 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 ba022690..f1e84c55 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 @@ -118,10 +118,34 @@ class Example extends jni.JObject { /// from: static public final char SEMICOLON static const SEMICOLON = 59; + static final _id_SEMICOLON_STRING = jni.Jni.accessors.getStaticFieldIDOf( + _class.reference, + r"SEMICOLON_STRING", + r"Ljava/lang/String;", + ); /// from: static public final java.lang.String SEMICOLON_STRING - static const SEMICOLON_STRING = r""";"""; + /// The returned object must be released after use, by calling the [release] method. + static jni.JString get SEMICOLON_STRING => JFinalString( + () => jni.Jni.accessors + .getStaticField(_class.reference, _id_SEMICOLON_STRING, + jni.JniCallType.objectType) + .object, + ";"); + static final _id_WEIRD_STRING = jni.Jni.accessors.getStaticFieldIDOf( + _class.reference, + r"WEIRD_STRING", + r"Ljava/lang/String;", + ); + /// from: static public final java.lang.String WEIRD_STRING + /// The returned object must be released after use, by calling the [release] method. + static jni.JString get WEIRD_STRING => JFinalString( + () => jni.Jni.accessors + .getStaticField( + _class.reference, _id_WEIRD_STRING, jni.JniCallType.objectType) + .object, + "Correct'\u{8}\u{8}\nOmega:\t\u{3a9}\n\u{d83d}\u{de0e}\u{d83d}\u{de06}\n\\no \$accidental \"changes\"!\n\u{7}"); static final _id_unusedRandom = jni.Jni.accessors.getStaticFieldIDOf( _class.reference, r"unusedRandom", 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 b96156eb..e948670a 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 @@ -13,6 +13,8 @@ public class Example { public static final double PI = 3.14159; public static final char SEMICOLON = ';'; public static final String SEMICOLON_STRING = ";"; + public static final String WEIRD_STRING = + "Correct'\b\b\nOmega:\t\u03A9\nšŸ˜ŽšŸ˜†\n\\no $accidental \"changes\"!\n\u0007"; public static final Random unusedRandom = new Random(); diff --git a/jnigen/test/simple_package_test/runtime_test_registrant.dart b/jnigen/test/simple_package_test/runtime_test_registrant.dart index 3791f9e4..4df92503 100644 --- a/jnigen/test/simple_package_test/runtime_test_registrant.dart +++ b/jnigen/test/simple_package_test/runtime_test_registrant.dart @@ -36,7 +36,12 @@ void registerTests(String groupName, TestRunnerCallback test) { expect(Example.OFF, equals(0)); expect(Example.PI, closeTo(pi, fpDelta)); expect(Example.SEMICOLON, equals(';'.codeUnitAt(0))); - expect(Example.SEMICOLON_STRING, equals(';')); + expect(Example.SEMICOLON_STRING.toDartString(releaseOriginal: true), + equals(';')); + expect( + Example.WEIRD_STRING.toDartString(releaseOriginal: true), + JString.fromRef(Example.WEIRD_STRING.reference) + .toDartString(releaseOriginal: true)); }); test('Static methods - primitive', () {