diff --git a/pkgs/ffigen/CHANGELOG.md b/pkgs/ffigen/CHANGELOG.md index 4f3aa0f78..d1bfb412f 100644 --- a/pkgs/ffigen/CHANGELOG.md +++ b/pkgs/ffigen/CHANGELOG.md @@ -2,6 +2,7 @@ - Bump minimum Dart version to 3.4. - Dedupe `ObjCBlock` trampolines to reduce generated ObjC code. +- Update to latest `package:objective_c`. - ObjC objects now include the methods from the protocols they implement. Both required and optional methods are included. Optional methods will throw an exception if the method isn't implemented. diff --git a/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart b/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart index 2af132765..cffc1877b 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_built_in_functions.dart @@ -45,8 +45,10 @@ class ObjCBuiltInFunctions { ObjCImport('UnimplementedOptionalMethodException'); // Keep in sync with pkgs/objective_c/ffigen_objc.yaml. + @visibleForTesting static const builtInInterfaces = { + 'DartInputStreamAdapter', 'DartProxy', 'DartProxyBuilder', 'NSArray', @@ -58,6 +60,7 @@ class ObjCBuiltInFunctions { 'NSEnumerator', 'NSError', 'NSIndexSet', + 'NSInputStream', 'NSInvocation', 'NSItemProvider', 'NSLocale', @@ -73,8 +76,11 @@ class ObjCBuiltInFunctions { 'NSNumber', 'NSObject', 'NSOrderedSet', + 'NSOutputStream', 'NSProxy', + 'NSRunLoop', 'NSSet', + 'NSStream', 'NSString', 'NSURL', 'NSURLHandle', @@ -104,6 +110,8 @@ class ObjCBuiltInFunctions { 'NSKeyValueSetMutationKind', 'NSOrderedCollectionDifferenceCalculationOptions', 'NSSortOptions', + 'NSStreamEvent', + 'NSStreamStatus', 'NSStringCompareOptions', 'NSStringEncodingConversionOptions', 'NSStringEnumerationOptions', @@ -111,6 +119,10 @@ class ObjCBuiltInFunctions { 'NSURLBookmarkResolutionOptions', 'NSURLHandleStatus', }; + @visibleForTesting + static const builtInProtocols = { + 'NSStreamDelegate', + }; // TODO(https://github.com/dart-lang/native/issues/1173): Ideally this check // would be based on more than just the name. @@ -120,6 +132,8 @@ class ObjCBuiltInFunctions { generateForPackageObjectiveC ? null : builtInCompounds[name]; bool isBuiltInEnum(String name) => !generateForPackageObjectiveC && builtInEnums.contains(name); + bool isBuiltInProtocol(String name) => + !generateForPackageObjectiveC && builtInProtocols.contains(name); static bool isNSObject(String name) => name == 'NSObject'; // We need to load a separate instance of objc_msgSend for each signature. If diff --git a/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart b/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart index f526fd162..c44f0739a 100644 --- a/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart +++ b/pkgs/ffigen/lib/src/code_generator/objc_protocol.dart @@ -43,10 +43,11 @@ class ObjCProtocol extends NoLookUpBinding with ObjCMethods { @override void sort() => sortMethods(); + bool get _isBuiltIn => builtInFunctions.isBuiltInProtocol(originalName); @override BindingString toBindingString(Writer w) { - if (!generateBindings) { + if (!generateBindings || _isBuiltIn) { return const BindingString( type: BindingStringType.objcProtocol, string: ''); } diff --git a/pkgs/ffigen/pubspec.yaml b/pkgs/ffigen/pubspec.yaml index b230bddb7..cfe73df7a 100644 --- a/pkgs/ffigen/pubspec.yaml +++ b/pkgs/ffigen/pubspec.yaml @@ -38,7 +38,7 @@ dev_dependencies: dart_flutter_team_lints: ^2.0.0 json_schema: ^5.1.1 leak_tracker: ^10.0.7 - objective_c: ^2.1.0 + objective_c: ^3.0.0 test: ^1.16.2 dependency_overrides: diff --git a/pkgs/jni/CHANGELOG.md b/pkgs/jni/CHANGELOG.md index ab49de1ea..29c7fb34f 100644 --- a/pkgs/jni/CHANGELOG.md +++ b/pkgs/jni/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.12.1-wip + +- Add `JniUtils.fromReferenceAddress` which helps with sending `JObject`s + through method channels. You can send the address of the pointer as `long` and + reconstruct the class using the helper method. + ## 0.12.0 - **Breaking Change**: Renamed `castTo` to `as`. diff --git a/pkgs/jni/java/src/main/java/com/github/dart_lang/jni/JniUtils.java b/pkgs/jni/java/src/main/java/com/github/dart_lang/jni/JniUtils.java new file mode 100644 index 000000000..0758f5a18 --- /dev/null +++ b/pkgs/jni/java/src/main/java/com/github/dart_lang/jni/JniUtils.java @@ -0,0 +1,13 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +package com.github.dart_lang.jni; + +public class JniUtils { + static { + System.loadLibrary("dartjni"); + } + + public static native Object fromReferenceAddress(long address); +} diff --git a/pkgs/jni/pubspec.yaml b/pkgs/jni/pubspec.yaml index 4dea4acfe..e75140057 100644 --- a/pkgs/jni/pubspec.yaml +++ b/pkgs/jni/pubspec.yaml @@ -4,7 +4,7 @@ name: jni description: A library to access JNI from Dart and Flutter that acts as a support library for package:jnigen. -version: 0.12.0 +version: 0.12.1-wip repository: https://github.com/dart-lang/native/tree/main/pkgs/jni topics: diff --git a/pkgs/jnigen/CHANGELOG.md b/pkgs/jnigen/CHANGELOG.md index c180922bf..1aa41d301 100644 --- a/pkgs/jnigen/CHANGELOG.md +++ b/pkgs/jnigen/CHANGELOG.md @@ -1,3 +1,9 @@ +## 0.12.2-wip + +- Now excludes invalid identifiers by default. +- Fixed a bug where if multiple jars have classes within the same package, only + one of them gets generated. + ## 0.12.1 - Support implementing generic functions in interfaces. diff --git a/pkgs/jnigen/README.md b/pkgs/jnigen/README.md index e84d86602..87debcbb3 100644 --- a/pkgs/jnigen/README.md +++ b/pkgs/jnigen/README.md @@ -18,7 +18,7 @@ Three configuration details are needed to generate the bindings. Everything else * _Classes_: Specify which classes or packages you need bindings for. Specifying a package includes all classes inside it recursively. -Check out the [examples](jnigen/example/) to see some sample configurations. +Check out the [examples](example/) to see some sample configurations. ## Example It's possible to generate bindings for JAR libraries, or Java source files. @@ -113,7 +113,7 @@ classes: - 'com.example.in_app_java.AndroidUtils' ``` -The complete example can be found in [jnigen/example/in_app_java](jnigen/example/in_app_java), which adds few more classes to demonstrate using classes from gradle JAR and source dependencies. +The complete example can be found in [jnigen/example/in_app_java](example/in_app_java), which adds few more classes to demonstrate using classes from gradle JAR and source dependencies. ## Supported platforms | Platform | Dart Standalone | Flutter | @@ -167,7 +167,7 @@ CMake and a standard C toolchain are required to build `package:jni`. #### I am getting ClassNotFoundError at runtime. `jnigen` does not handle getting the classes into application. It has to be done by target-specific mechanism. Such as adding a gradle dependency on Android, or manually providing classpath to `Jni.spawn` on desktop / standalone targets. -On Android, `proguard` prunes classes which it deems inaccessible. Since JNI class lookup happens in runtime, this leads to ClassNotFound errors in release mode even if the dependency is included in gradle. [in_app_java example](jnigen/example/in_app_java/) discusses two mechanisms to prevent this: using `Keep` annotation (`androidx.annotation.Keep`) for the code written in the application itself, and [proguard-rules file](jnigen/example/in_app_java/android/app/proguard-rules.pro) for external libraries. +On Android, `proguard` prunes classes which it deems inaccessible. Since JNI class lookup happens in runtime, this leads to ClassNotFound errors in release mode even if the dependency is included in gradle. [in_app_java example](example/in_app_java/) discusses two mechanisms to prevent this: using `Keep` annotation (`androidx.annotation.Keep`) for the code written in the application itself, and [proguard-rules file](example/in_app_java/android/app/proguard-rules.pro) for external libraries. Lastly, some libraries such as `java.awt` do not exist in android. Attempting to use libraries which depend on them can also lead to ClassNotFound errors. @@ -222,7 +222,7 @@ A `*` denotes required configuration. | `log_level` | Logging level | Configure logging level. Defaults to `info`. | | `android_sdk_config:` | (Subsection) | Configuration for autodetection of Android dependencies and SDK. Note that this is more experimental than others, and very likely subject to change. | | `android_sdk_config:` >> `add_gradle_deps` | Boolean | If true, run a gradle stub during `jnigen` invocation, and add Android compile classpath to the classpath of jnigen. This requires a release build to have happened before, so that all dependencies are cached appropriately. | -| `android_sdk_config:` >> `android_example` | Directory path | In case of an Android plugin project, the plugin itself cannot be built and `add_gradle_deps` is not directly feasible. This property can be set to relative path of package example app (usually `example/` so that gradle dependencies can be collected by running a stub in this directory. See [notification_plugin example](jnigen/example/notification_plugin/jnigen.yaml) for an example. | +| `android_sdk_config:` >> `android_example` | Directory path | In case of an Android plugin project, the plugin itself cannot be built and `add_gradle_deps` is not directly feasible. This property can be set to relative path of package example app (usually `example/` so that gradle dependencies can be collected by running a stub in this directory. See [notification_plugin example](example/notification_plugin/jnigen.yaml) for an example. | | `summarizer:` | (Subsection) | Configuration specific to summarizer component, which builds API descriptions from Java sources or JAR files. | | `summarizer:` >> `backend` | `auto`, `doclet` or `asm` | Specifies the backend to use in API summary generation. `doclet` uses OpenJDK Doclet API to build summary from sources. `asm` uses ASM library to build summary from classes in `class_path` JARs. `auto` attempts to find the class in sources, and falls back to using ASM. | | `summarizer:` >> `extra_args` (DEV) | List of CLI arguments | Extra arguments to pass to summarizer JAR. | diff --git a/pkgs/jnigen/dartdoc_options.yaml b/pkgs/jnigen/dartdoc_options.yaml new file mode 100644 index 000000000..1d3fb4798 --- /dev/null +++ b/pkgs/jnigen/dartdoc_options.yaml @@ -0,0 +1,8 @@ +dartdoc: + categories: + "Implementing Java interfaces from Dart": + markdown: doc/interface_implementation.md + name: "Interface Implementation" + "Syntactic and semantic differences between Java and the generated Dart bindings": + markdown: doc/java_differences.md + name: "Java Differences" diff --git a/pkgs/jnigen/docs/interface_implementation.md b/pkgs/jnigen/doc/interface_implementation.md similarity index 93% rename from pkgs/jnigen/docs/interface_implementation.md rename to pkgs/jnigen/doc/interface_implementation.md index 566824e78..0d23a5f2c 100644 --- a/pkgs/jnigen/docs/interface_implementation.md +++ b/pkgs/jnigen/doc/interface_implementation.md @@ -1,17 +1,5 @@ ## Implementing Java interfaces from Dart -> [!NOTE] -> This feature is experimental, and in -> [active development](https://github.com/dart-lang/native/issues/1569). -> -> To opt in to use this feature, add the following to your JNIgen configuration -> yaml: -> -> ```yaml -> enable_experiment: -> - interface_implementation -> ``` - Let's take a simple Java interface like `Runnable` that has a single `void` method called `run`: diff --git a/pkgs/jnigen/docs/java_differences.md b/pkgs/jnigen/doc/java_differences.md similarity index 100% rename from pkgs/jnigen/docs/java_differences.md rename to pkgs/jnigen/doc/java_differences.md diff --git a/pkgs/jnigen/example/in_app_java/jnigen.yaml b/pkgs/jnigen/example/in_app_java/jnigen.yaml index 3fa8640b9..bdcdbf3cd 100644 --- a/pkgs/jnigen/example/in_app_java/jnigen.yaml +++ b/pkgs/jnigen/example/in_app_java/jnigen.yaml @@ -9,7 +9,7 @@ output: source_path: - 'android/app/src/main/java' classes: - - 'com.example.in_app_java.AndroidUtils' # from source_path + - 'com.example.in_app_java' # Generate the entire package - 'androidx.emoji2.text.EmojiCompat' # From gradle's compile classpath - 'androidx.emoji2.text.DefaultEmojiCompatConfig' # From gradle's compile classpath - 'android.os.Build' # from gradle's compile classpath diff --git a/pkgs/jnigen/example/in_app_java/lib/android_utils.dart b/pkgs/jnigen/example/in_app_java/lib/android_utils.dart index 2b7a33e06..b074990c6 100644 --- a/pkgs/jnigen/example/in_app_java/lib/android_utils.dart +++ b/pkgs/jnigen/example/in_app_java/lib/android_utils.dart @@ -34,6 +34,249 @@ import 'dart:core' as _$core; import 'package:jni/_internal.dart' as _$jni; import 'package:jni/jni.dart' as _$jni; +/// from: `com.example.in_app_java.R$drawable` +class R_drawable extends _$jni.JObject { + @_$jni.internal + @_$core.override + final _$jni.JObjType $type; + + @_$jni.internal + R_drawable.fromReference( + _$jni.JReference reference, + ) : $type = type, + super.fromReference(reference); + + static final _class = + _$jni.JClass.forName(r'com/example/in_app_java/R$drawable'); + + /// The type which includes information such as the signature of this class. + static const type = $R_drawable$Type(); + static final _id_launch_background = _class.staticFieldId( + r'launch_background', + r'I', + ); + + /// from: `static public int launch_background` + static int get launch_background => + _id_launch_background.get(_class, const _$jni.jintType()); + + /// from: `static public int launch_background` + static set launch_background(int value) => + _id_launch_background.set(_class, const _$jni.jintType(), value); +} + +final class $R_drawable$Type extends _$jni.JObjType { + @_$jni.internal + const $R_drawable$Type(); + + @_$jni.internal + @_$core.override + String get signature => r'Lcom/example/in_app_java/R$drawable;'; + + @_$jni.internal + @_$core.override + R_drawable fromReference(_$jni.JReference reference) => + R_drawable.fromReference(reference); + + @_$jni.internal + @_$core.override + _$jni.JObjType get superType => const _$jni.JObjectType(); + + @_$jni.internal + @_$core.override + final superCount = 1; + + @_$core.override + int get hashCode => ($R_drawable$Type).hashCode; + + @_$core.override + bool operator ==(Object other) { + return other.runtimeType == ($R_drawable$Type) && other is $R_drawable$Type; + } +} + +/// from: `com.example.in_app_java.R$mipmap` +class R_mipmap extends _$jni.JObject { + @_$jni.internal + @_$core.override + final _$jni.JObjType $type; + + @_$jni.internal + R_mipmap.fromReference( + _$jni.JReference reference, + ) : $type = type, + super.fromReference(reference); + + static final _class = + _$jni.JClass.forName(r'com/example/in_app_java/R$mipmap'); + + /// The type which includes information such as the signature of this class. + static const type = $R_mipmap$Type(); + static final _id_ic_launcher = _class.staticFieldId( + r'ic_launcher', + r'I', + ); + + /// from: `static public int ic_launcher` + static int get ic_launcher => + _id_ic_launcher.get(_class, const _$jni.jintType()); + + /// from: `static public int ic_launcher` + static set ic_launcher(int value) => + _id_ic_launcher.set(_class, const _$jni.jintType(), value); +} + +final class $R_mipmap$Type extends _$jni.JObjType { + @_$jni.internal + const $R_mipmap$Type(); + + @_$jni.internal + @_$core.override + String get signature => r'Lcom/example/in_app_java/R$mipmap;'; + + @_$jni.internal + @_$core.override + R_mipmap fromReference(_$jni.JReference reference) => + R_mipmap.fromReference(reference); + + @_$jni.internal + @_$core.override + _$jni.JObjType get superType => const _$jni.JObjectType(); + + @_$jni.internal + @_$core.override + final superCount = 1; + + @_$core.override + int get hashCode => ($R_mipmap$Type).hashCode; + + @_$core.override + bool operator ==(Object other) { + return other.runtimeType == ($R_mipmap$Type) && other is $R_mipmap$Type; + } +} + +/// from: `com.example.in_app_java.R$style` +class R_style extends _$jni.JObject { + @_$jni.internal + @_$core.override + final _$jni.JObjType $type; + + @_$jni.internal + R_style.fromReference( + _$jni.JReference reference, + ) : $type = type, + super.fromReference(reference); + + static final _class = + _$jni.JClass.forName(r'com/example/in_app_java/R$style'); + + /// The type which includes information such as the signature of this class. + static const type = $R_style$Type(); + static final _id_LaunchTheme = _class.staticFieldId( + r'LaunchTheme', + r'I', + ); + + /// from: `static public int LaunchTheme` + static int get LaunchTheme => + _id_LaunchTheme.get(_class, const _$jni.jintType()); + + /// from: `static public int LaunchTheme` + static set LaunchTheme(int value) => + _id_LaunchTheme.set(_class, const _$jni.jintType(), value); + + static final _id_NormalTheme = _class.staticFieldId( + r'NormalTheme', + r'I', + ); + + /// from: `static public int NormalTheme` + static int get NormalTheme => + _id_NormalTheme.get(_class, const _$jni.jintType()); + + /// from: `static public int NormalTheme` + static set NormalTheme(int value) => + _id_NormalTheme.set(_class, const _$jni.jintType(), value); +} + +final class $R_style$Type extends _$jni.JObjType { + @_$jni.internal + const $R_style$Type(); + + @_$jni.internal + @_$core.override + String get signature => r'Lcom/example/in_app_java/R$style;'; + + @_$jni.internal + @_$core.override + R_style fromReference(_$jni.JReference reference) => + R_style.fromReference(reference); + + @_$jni.internal + @_$core.override + _$jni.JObjType get superType => const _$jni.JObjectType(); + + @_$jni.internal + @_$core.override + final superCount = 1; + + @_$core.override + int get hashCode => ($R_style$Type).hashCode; + + @_$core.override + bool operator ==(Object other) { + return other.runtimeType == ($R_style$Type) && other is $R_style$Type; + } +} + +/// from: `com.example.in_app_java.R` +class R extends _$jni.JObject { + @_$jni.internal + @_$core.override + final _$jni.JObjType $type; + + @_$jni.internal + R.fromReference( + _$jni.JReference reference, + ) : $type = type, + super.fromReference(reference); + + static final _class = _$jni.JClass.forName(r'com/example/in_app_java/R'); + + /// The type which includes information such as the signature of this class. + static const type = $R$Type(); +} + +final class $R$Type extends _$jni.JObjType { + @_$jni.internal + const $R$Type(); + + @_$jni.internal + @_$core.override + String get signature => r'Lcom/example/in_app_java/R;'; + + @_$jni.internal + @_$core.override + R fromReference(_$jni.JReference reference) => R.fromReference(reference); + + @_$jni.internal + @_$core.override + _$jni.JObjType get superType => const _$jni.JObjectType(); + + @_$jni.internal + @_$core.override + final superCount = 1; + + @_$core.override + int get hashCode => ($R$Type).hashCode; + + @_$core.override + bool operator ==(Object other) { + return other.runtimeType == ($R$Type) && other is $R$Type; + } +} + /// from: `androidx.emoji2.text.EmojiCompat$CodepointSequenceMatchResult` class EmojiCompat_CodepointSequenceMatchResult extends _$jni.JObject { @_$jni.internal diff --git a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/ClassFinder.java b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/ClassFinder.java index 89d7f307a..e959ce9e0 100644 --- a/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/ClassFinder.java +++ b/pkgs/jnigen/java/src/main/java/com/github/dart_lang/jnigen/apisummarizer/util/ClassFinder.java @@ -119,13 +119,11 @@ public static boolean findFilesInJar( jar.stream().map(JarEntry::getName).collect(Collectors.toCollection(TreeSet::new)); boolean foundClassesInThisJar = false; for (var fqn : classes.keySet()) { - if (classes.get(fqn) != null) { // already found - continue; - } 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)); + classes.putIfAbsent(fqn, new ArrayList<>()); + classes.get(fqn).addAll(mapper.apply(jar, jarEntries)); foundClassesInThisJar = true; } } diff --git a/pkgs/jnigen/lib/src/bindings/excluder.dart b/pkgs/jnigen/lib/src/bindings/excluder.dart index 6169875ed..c9fba9ab5 100644 --- a/pkgs/jnigen/lib/src/bindings/excluder.dart +++ b/pkgs/jnigen/lib/src/bindings/excluder.dart @@ -11,6 +11,21 @@ extension on ClassMember { bool get isPrivate => !isPublic; } +// TODO(https://github.com/dart-lang/native/issues/1164): Kotlin compiler +// appends the method name with a dash and a hash code when arguments contain +// inline classes. This is because inline classes do not have any runtime type +// and the typical operator overloading supported by JVM cannot work for them. +// +// Once we support inline classes, we can relax the following constraints. +final _validDartIdentifier = RegExp(r'^[a-zA-Z_$][a-zA-Z0-9_$]*$'); + +extension on String { + bool get isInvalidDartIdentifier => + !_validDartIdentifier.hasMatch(this) && + this != '' && + this != ''; +} + class Excluder extends Visitor { final Config config; @@ -24,6 +39,11 @@ class Excluder extends Visitor { if (excluded) { log.fine('Excluded class ${classDecl.binaryName}'); } + if (classDecl.name.isInvalidDartIdentifier) { + log.warning('Excluded class ${classDecl.binaryName}: the name is not a' + ' valid Dart identifer'); + return true; + } return excluded; }); final classExcluder = _ClassExcluder(config); @@ -51,6 +71,12 @@ class _ClassExcluder extends Visitor { if (excluded) { log.fine('Excluded method ${node.binaryName}#${method.name}'); } + if (method.name.isInvalidDartIdentifier) { + log.warning( + 'Excluded method ${node.binaryName}#${method.name}: the name is not' + ' a valid Dart identifer'); + return false; + } return !excluded; }).toList(); node.fields = node.fields.where((field) { @@ -59,6 +85,12 @@ class _ClassExcluder extends Visitor { if (excluded) { log.fine('Excluded field ${node.binaryName}#${field.name}'); } + if (field.name.isInvalidDartIdentifier) { + log.warning( + 'Excluded field ${node.binaryName}#${field.name}: the name is not' + ' a valid Dart identifer'); + return false; + } return !excluded; }).toList(); } diff --git a/pkgs/jnigen/pubspec.yaml b/pkgs/jnigen/pubspec.yaml index 5aa30b5cb..ff495d62e 100644 --- a/pkgs/jnigen/pubspec.yaml +++ b/pkgs/jnigen/pubspec.yaml @@ -4,7 +4,7 @@ name: jnigen description: A Dart bindings generator for Java and Kotlin that uses JNI under the hood to interop with Java virtual machine. -version: 0.12.1 +version: 0.12.2-wip repository: https://github.com/dart-lang/native/tree/main/pkgs/jnigen environment: diff --git a/pkgs/objective_c/CHANGELOG.md b/pkgs/objective_c/CHANGELOG.md index f5115082b..b5007f54e 100644 --- a/pkgs/objective_c/CHANGELOG.md +++ b/pkgs/objective_c/CHANGELOG.md @@ -1,5 +1,13 @@ -## 2.1.0-wip - +## 3.0.0-wip + +- Add the following stream-related types to the core package: + - `NSInputStream` + - `NSOutputStream` + - `NSRunLoop` + - `NSStream` + - `NSStreamDelegate` + - `NSStreamEvent` + - `NSStreamStatus` - Add `UnimplementedOptionalMethodException`, which is thrown by the ObjC bindings if an optional method is invoked, and the instance doesn't implement the method. diff --git a/pkgs/objective_c/ffigen_objc.yaml b/pkgs/objective_c/ffigen_objc.yaml index 992c39708..1b1f7ebe9 100644 --- a/pkgs/objective_c/ffigen_objc.yaml +++ b/pkgs/objective_c/ffigen_objc.yaml @@ -8,6 +8,7 @@ output: headers: entry-points: - 'src/foundation.h' + - 'src/input_stream_adapter.h' - 'src/proxy.h' ffi-native: exclude-all-by-default: true @@ -22,6 +23,7 @@ external-versions: objc-interfaces: # Keep in sync with ffigen's ObjCBuiltInFunctions.builtInInterfaces. include: + - DartInputStreamAdapter - DartProxy - DartProxyBuilder - NSArray @@ -33,6 +35,7 @@ objc-interfaces: - NSEnumerator - NSError - NSIndexSet + - NSInputStream - NSInvocation - NSItemProvider - NSLocale @@ -47,14 +50,20 @@ objc-interfaces: - NSNotification - NSNumber - NSObject + - NSOutputStream - NSOrderedSet - NSProxy + - NSRunLoop - NSSet + - NSStream - NSString - NSURL - NSURLHandle - NSValue - Protocol +objc-protocols: + include: + - NSStreamDelegate structs: include: - _NSRange @@ -79,12 +88,17 @@ enums: - NSKeyValueSetMutationKind - NSOrderedCollectionDifferenceCalculationOptions - NSSortOptions + - NSStreamEvent + - NSStreamStatus - NSStringCompareOptions - NSStringEncodingConversionOptions - NSStringEnumerationOptions - NSURLBookmarkCreationOptions - NSURLBookmarkResolutionOptions - NSURLHandleStatus +globals: + include: + - NSLocalizedDescriptionKey preamble: | // Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a diff --git a/pkgs/objective_c/ios/Classes/objective_c.m b/pkgs/objective_c/ios/Classes/objective_c.m index 25d76dca3..1d3862c48 100644 --- a/pkgs/objective_c/ios/Classes/objective_c.m +++ b/pkgs/objective_c/ios/Classes/objective_c.m @@ -4,6 +4,7 @@ // Relative import to be able to reuse the ObjC sources. // See the comment in ../objective_c.podspec for more information. +#include "../../src/input_stream_adapter.m" #include "../../src/objective_c.m" #include "../../src/objective_c_bindings_generated.m" #include "../../src/proxy.m" diff --git a/pkgs/objective_c/lib/objective_c.dart b/pkgs/objective_c/lib/objective_c.dart index a19f76d19..b4a63bb53 100644 --- a/pkgs/objective_c/lib/objective_c.dart +++ b/pkgs/objective_c/lib/objective_c.dart @@ -19,6 +19,7 @@ export 'src/internal.dart' isValidClass, isValidObject; export 'src/ns_data.dart'; +export 'src/ns_input_stream.dart'; export 'src/ns_mutable_data.dart'; export 'src/ns_string.dart'; // Keep in sync with pkgs/objective_c/ffigen_objc.yaml. @@ -45,6 +46,7 @@ export 'src/objective_c_bindings_generated.dart' NSError, NSFastEnumerationState, NSIndexSet, + NSInputStream, NSInvocation, NSItemProvider, NSItemProviderFileOptions, @@ -66,10 +68,16 @@ export 'src/objective_c_bindings_generated.dart' NSObject, NSOrderedCollectionDifferenceCalculationOptions, NSOrderedSet, + NSOutputStream, NSProxy, NSRange, + NSRunLoop, NSSet, NSSortOptions, + NSStream, + NSStreamDelegate, + NSStreamEvent, + NSStreamStatus, NSString, NSStringCompareOptions, NSStringEncodingConversionOptions, diff --git a/pkgs/objective_c/lib/src/ns_input_stream.dart b/pkgs/objective_c/lib/src/ns_input_stream.dart new file mode 100644 index 000000000..cc0e3ad5c --- /dev/null +++ b/pkgs/objective_c/lib/src/ns_input_stream.dart @@ -0,0 +1,57 @@ +import 'dart:async'; +import 'dart:ffi'; +import 'dart:isolate'; + +import 'ns_data.dart'; +import 'ns_string.dart'; +import 'objective_c_bindings_generated.dart'; + +extension NSInputStreamStreamExtension on Stream> { + /// Return a [NSInputStream] that, when read, will contain the contents of + /// the [Stream]. + /// + /// > [!IMPORTANT] + /// > [NSInputStream.read_maxLength_] must be called from a different thread + /// > or [Isolate] than the one that calls [toNSInputStream]. Otherwise, + /// > [NSInputStream.read_maxLength_] will deadlock waiting for data to be + /// > added from the [Stream]. + NSInputStream toNSInputStream() { + // Eagerly add data until `maxReadAheadSize` is buffered. + const maxReadAheadSize = 4096; + + final port = ReceivePort(); + final inputStream = + DartInputStreamAdapter.inputStreamWithPort_(port.sendPort.nativePort); + late final StreamSubscription dataSubscription; + + dataSubscription = listen((data) { + if (inputStream.addData_(data.toNSData()) > maxReadAheadSize) { + dataSubscription.pause(); + } + }, onError: (Object e) { + final d = NSMutableDictionary.new1(); + d.setObject_forKey_(e.toString().toNSString(), NSLocalizedDescriptionKey); + inputStream.setError_(NSError.errorWithDomain_code_userInfo_( + 'DartError'.toNSString(), 0, d)); + port.close(); + }, onDone: () { + inputStream.setDone(); + port.close(); + }, cancelOnError: true); + + dataSubscription.pause(); + port.listen((count) { + // -1 indicates that the `NSInputStream` is closed. All other values + // indicate that the `NSInputStream` needs more data. + if (count == -1) { + dataSubscription.cancel(); + } else { + dataSubscription.resume(); + } + }, onDone: () { + dataSubscription.cancel(); + }); + + return inputStream; + } +} diff --git a/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart b/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart index ef9a3bd76..6cdeb6bad 100644 --- a/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart +++ b/pkgs/objective_c/lib/src/objective_c_bindings_generated.dart @@ -21,6 +21,21 @@ import 'dart:ffi' as ffi; import '../objective_c.dart' as objc; import 'package:ffi/ffi.dart' as pkg_ffi; +@ffi.Native>(symbol: "NSLocalizedDescriptionKey") +external ffi.Pointer _NSLocalizedDescriptionKey; + +NSString get NSLocalizedDescriptionKey => + NSString.castFromPointer(_NSLocalizedDescriptionKey, + retain: true, release: true); + +set NSLocalizedDescriptionKey(NSString value) { + NSString.castFromPointer(_NSLocalizedDescriptionKey, + retain: false, release: true) + .ref + .release(); + _NSLocalizedDescriptionKey = value.ref.retainAndReturnPointer(); +} + @ffi.Native< ffi.Pointer Function( ffi.Pointer)>(isLeaf: true) @@ -28,6 +43,13 @@ external ffi.Pointer _wrapListenerBlock_hepzs( ffi.Pointer block, ); +@ffi.Native< + ffi.Pointer Function( + ffi.Pointer)>(isLeaf: true) +external ffi.Pointer _wrapListenerBlock_m1viep( + ffi.Pointer block, +); + @ffi.Native< ffi.Pointer Function( ffi.Pointer)>(isLeaf: true) @@ -35,6 +57,151 @@ external ffi.Pointer _wrapListenerBlock_sjfpmz( ffi.Pointer block, ); +/// Helper class to adapt a Dart stream into a `NSInputStream`. +class DartInputStreamAdapter extends NSInputStream { + DartInputStreamAdapter._(ffi.Pointer pointer, + {bool retain = false, bool release = false}) + : super.castFromPointer(pointer, retain: retain, release: release); + + /// Constructs a [DartInputStreamAdapter] that points to the same underlying object as [other]. + DartInputStreamAdapter.castFrom(objc.ObjCObjectBase other) + : this._(other.ref.pointer, retain: true, release: true); + + /// Constructs a [DartInputStreamAdapter] that wraps the given raw object pointer. + DartInputStreamAdapter.castFromPointer(ffi.Pointer other, + {bool retain = false, bool release = false}) + : this._(other, retain: retain, release: release); + + /// Returns whether [obj] is an instance of [DartInputStreamAdapter]. + static bool isInstance(objc.ObjCObjectBase obj) { + return _objc_msgSend_l8lotg( + obj.ref.pointer, _sel_isKindOfClass_, _class_DartInputStreamAdapter); + } + + /// addData: + int addData_(NSData data) { + return _objc_msgSend_eymsul( + this.ref.pointer, _sel_addData_, data.ref.pointer); + } + + /// getBoundStreamsWithBufferSize:inputStream:outputStream: + static void getBoundStreamsWithBufferSize_inputStream_outputStream_( + int bufferSize, + ffi.Pointer> inputStream, + ffi.Pointer> outputStream) { + _objc_msgSend_5r8xlx( + _class_DartInputStreamAdapter, + _sel_getBoundStreamsWithBufferSize_inputStream_outputStream_, + bufferSize, + inputStream, + outputStream); + } + + /// getStreamsToHostWithName:port:inputStream:outputStream: + static void getStreamsToHostWithName_port_inputStream_outputStream_( + NSString hostname, + int port, + ffi.Pointer> inputStream, + ffi.Pointer> outputStream) { + _objc_msgSend_imc4v7( + _class_DartInputStreamAdapter, + _sel_getStreamsToHostWithName_port_inputStream_outputStream_, + hostname.ref.pointer, + port, + inputStream, + outputStream); + } + + /// initWithData: + DartInputStreamAdapter initWithData_(NSData data) { + final _ret = _objc_msgSend_juohf7(this.ref.retainAndReturnPointer(), + _sel_initWithData_, data.ref.pointer); + return DartInputStreamAdapter.castFromPointer(_ret, + retain: false, release: true); + } + + /// initWithFileAtPath: + DartInputStreamAdapter? initWithFileAtPath_(NSString path) { + final _ret = _objc_msgSend_juohf7(this.ref.retainAndReturnPointer(), + _sel_initWithFileAtPath_, path.ref.pointer); + return _ret.address == 0 + ? null + : DartInputStreamAdapter.castFromPointer(_ret, + retain: false, release: true); + } + + /// initWithURL: + DartInputStreamAdapter? initWithURL_(NSURL url) { + final _ret = _objc_msgSend_juohf7( + this.ref.retainAndReturnPointer(), _sel_initWithURL_, url.ref.pointer); + return _ret.address == 0 + ? null + : DartInputStreamAdapter.castFromPointer(_ret, + retain: false, release: true); + } + + /// inputStreamWithData: + static DartInputStreamAdapter? inputStreamWithData_(NSData data) { + final _ret = _objc_msgSend_juohf7(_class_DartInputStreamAdapter, + _sel_inputStreamWithData_, data.ref.pointer); + return _ret.address == 0 + ? null + : DartInputStreamAdapter.castFromPointer(_ret, + retain: true, release: true); + } + + /// inputStreamWithFileAtPath: + static DartInputStreamAdapter? inputStreamWithFileAtPath_(NSString path) { + final _ret = _objc_msgSend_juohf7(_class_DartInputStreamAdapter, + _sel_inputStreamWithFileAtPath_, path.ref.pointer); + return _ret.address == 0 + ? null + : DartInputStreamAdapter.castFromPointer(_ret, + retain: true, release: true); + } + + /// Creates the adapter. + /// @param sendPort A port to that is will receive two types of messages: + /// -1 => The `NSInputStream` has been closed and the port can be closed. + /// _ => The number of types being required in a `read:maxLength` call. + static DartInputStreamAdapter inputStreamWithPort_(int sendPort) { + final _ret = _objc_msgSend_n9eq1n( + _class_DartInputStreamAdapter, _sel_inputStreamWithPort_, sendPort); + return DartInputStreamAdapter.castFromPointer(_ret, + retain: true, release: true); + } + + /// inputStreamWithURL: + static DartInputStreamAdapter? inputStreamWithURL_(NSURL url) { + final _ret = _objc_msgSend_juohf7(_class_DartInputStreamAdapter, + _sel_inputStreamWithURL_, url.ref.pointer); + return _ret.address == 0 + ? null + : DartInputStreamAdapter.castFromPointer(_ret, + retain: true, release: true); + } + + /// setDone + void setDone() { + _objc_msgSend_ksby9f(this.ref.pointer, _sel_setDone); + } + + /// setError: + void setError_(NSError error) { + _objc_msgSend_ukcdfq(this.ref.pointer, _sel_setError_, error.ref.pointer); + } + + /// stream:handleEvent: + void stream_handleEvent_(NSStream aStream, NSStreamEvent eventCode) { + if (!objc.respondsToSelector(ref.pointer, _sel_stream_handleEvent_)) { + throw objc.UnimplementedOptionalMethodException( + 'DartInputStreamAdapter', 'stream:handleEvent:'); + } + _objc_msgSend_7zmbk4(this.ref.pointer, _sel_stream_handleEvent_, + aStream.ref.pointer, eventCode.value); + } +} + /// DartProxy class DartProxy extends NSProxy { DartProxy._(ffi.Pointer pointer, @@ -2791,6 +2958,126 @@ class NSIndexSet extends NSObject { } } +/// NSInputStream +class NSInputStream extends NSStream { + NSInputStream._(ffi.Pointer pointer, + {bool retain = false, bool release = false}) + : super.castFromPointer(pointer, retain: retain, release: release); + + /// Constructs a [NSInputStream] that points to the same underlying object as [other]. + NSInputStream.castFrom(objc.ObjCObjectBase other) + : this._(other.ref.pointer, retain: true, release: true); + + /// Constructs a [NSInputStream] that wraps the given raw object pointer. + NSInputStream.castFromPointer(ffi.Pointer other, + {bool retain = false, bool release = false}) + : this._(other, retain: retain, release: release); + + /// Returns whether [obj] is an instance of [NSInputStream]. + static bool isInstance(objc.ObjCObjectBase obj) { + return _objc_msgSend_l8lotg( + obj.ref.pointer, _sel_isKindOfClass_, _class_NSInputStream); + } + + /// getBoundStreamsWithBufferSize:inputStream:outputStream: + static void getBoundStreamsWithBufferSize_inputStream_outputStream_( + int bufferSize, + ffi.Pointer> inputStream, + ffi.Pointer> outputStream) { + _objc_msgSend_5r8xlx( + _class_NSInputStream, + _sel_getBoundStreamsWithBufferSize_inputStream_outputStream_, + bufferSize, + inputStream, + outputStream); + } + + /// getBuffer:length: + bool getBuffer_length_(ffi.Pointer> buffer, + ffi.Pointer len) { + return _objc_msgSend_1vnalux( + this.ref.pointer, _sel_getBuffer_length_, buffer, len); + } + + /// getStreamsToHostWithName:port:inputStream:outputStream: + static void getStreamsToHostWithName_port_inputStream_outputStream_( + NSString hostname, + int port, + ffi.Pointer> inputStream, + ffi.Pointer> outputStream) { + _objc_msgSend_imc4v7( + _class_NSInputStream, + _sel_getStreamsToHostWithName_port_inputStream_outputStream_, + hostname.ref.pointer, + port, + inputStream, + outputStream); + } + + /// hasBytesAvailable + bool get hasBytesAvailable { + return _objc_msgSend_olxnu1(this.ref.pointer, _sel_hasBytesAvailable); + } + + /// initWithData: + NSInputStream initWithData_(NSData data) { + final _ret = _objc_msgSend_juohf7(this.ref.retainAndReturnPointer(), + _sel_initWithData_, data.ref.pointer); + return NSInputStream.castFromPointer(_ret, retain: false, release: true); + } + + /// initWithFileAtPath: + NSInputStream? initWithFileAtPath_(NSString path) { + final _ret = _objc_msgSend_juohf7(this.ref.retainAndReturnPointer(), + _sel_initWithFileAtPath_, path.ref.pointer); + return _ret.address == 0 + ? null + : NSInputStream.castFromPointer(_ret, retain: false, release: true); + } + + /// initWithURL: + NSInputStream? initWithURL_(NSURL url) { + final _ret = _objc_msgSend_juohf7( + this.ref.retainAndReturnPointer(), _sel_initWithURL_, url.ref.pointer); + return _ret.address == 0 + ? null + : NSInputStream.castFromPointer(_ret, retain: false, release: true); + } + + /// inputStreamWithData: + static NSInputStream? inputStreamWithData_(NSData data) { + final _ret = _objc_msgSend_juohf7( + _class_NSInputStream, _sel_inputStreamWithData_, data.ref.pointer); + return _ret.address == 0 + ? null + : NSInputStream.castFromPointer(_ret, retain: true, release: true); + } + + /// inputStreamWithFileAtPath: + static NSInputStream? inputStreamWithFileAtPath_(NSString path) { + final _ret = _objc_msgSend_juohf7(_class_NSInputStream, + _sel_inputStreamWithFileAtPath_, path.ref.pointer); + return _ret.address == 0 + ? null + : NSInputStream.castFromPointer(_ret, retain: true, release: true); + } + + /// inputStreamWithURL: + static NSInputStream? inputStreamWithURL_(NSURL url) { + final _ret = _objc_msgSend_juohf7( + _class_NSInputStream, _sel_inputStreamWithURL_, url.ref.pointer); + return _ret.address == 0 + ? null + : NSInputStream.castFromPointer(_ret, retain: true, release: true); + } + + /// read:maxLength: + int read_maxLength_(ffi.Pointer buffer, int len) { + return _objc_msgSend_1wopcqf( + this.ref.pointer, _sel_read_maxLength_, buffer, len); + } +} + /// NSInvocation class NSInvocation extends objc.ObjCObjectBase { NSInvocation._(ffi.Pointer pointer, @@ -7216,6 +7503,133 @@ class NSOrderedSet extends NSObject { } } +/// NSOutputStream +class NSOutputStream extends NSStream { + NSOutputStream._(ffi.Pointer pointer, + {bool retain = false, bool release = false}) + : super.castFromPointer(pointer, retain: retain, release: release); + + /// Constructs a [NSOutputStream] that points to the same underlying object as [other]. + NSOutputStream.castFrom(objc.ObjCObjectBase other) + : this._(other.ref.pointer, retain: true, release: true); + + /// Constructs a [NSOutputStream] that wraps the given raw object pointer. + NSOutputStream.castFromPointer(ffi.Pointer other, + {bool retain = false, bool release = false}) + : this._(other, retain: retain, release: release); + + /// Returns whether [obj] is an instance of [NSOutputStream]. + static bool isInstance(objc.ObjCObjectBase obj) { + return _objc_msgSend_l8lotg( + obj.ref.pointer, _sel_isKindOfClass_, _class_NSOutputStream); + } + + /// getBoundStreamsWithBufferSize:inputStream:outputStream: + static void getBoundStreamsWithBufferSize_inputStream_outputStream_( + int bufferSize, + ffi.Pointer> inputStream, + ffi.Pointer> outputStream) { + _objc_msgSend_5r8xlx( + _class_NSOutputStream, + _sel_getBoundStreamsWithBufferSize_inputStream_outputStream_, + bufferSize, + inputStream, + outputStream); + } + + /// getStreamsToHostWithName:port:inputStream:outputStream: + static void getStreamsToHostWithName_port_inputStream_outputStream_( + NSString hostname, + int port, + ffi.Pointer> inputStream, + ffi.Pointer> outputStream) { + _objc_msgSend_imc4v7( + _class_NSOutputStream, + _sel_getStreamsToHostWithName_port_inputStream_outputStream_, + hostname.ref.pointer, + port, + inputStream, + outputStream); + } + + /// hasSpaceAvailable + bool get hasSpaceAvailable { + return _objc_msgSend_olxnu1(this.ref.pointer, _sel_hasSpaceAvailable); + } + + /// initToBuffer:capacity: + NSOutputStream initToBuffer_capacity_( + ffi.Pointer buffer, int capacity) { + final _ret = _objc_msgSend_pblopu(this.ref.retainAndReturnPointer(), + _sel_initToBuffer_capacity_, buffer, capacity); + return NSOutputStream.castFromPointer(_ret, retain: false, release: true); + } + + /// initToFileAtPath:append: + NSOutputStream? initToFileAtPath_append_(NSString path, bool shouldAppend) { + final _ret = _objc_msgSend_qqbb5y(this.ref.retainAndReturnPointer(), + _sel_initToFileAtPath_append_, path.ref.pointer, shouldAppend); + return _ret.address == 0 + ? null + : NSOutputStream.castFromPointer(_ret, retain: false, release: true); + } + + /// initToMemory + NSOutputStream initToMemory() { + final _ret = _objc_msgSend_1unuoxw( + this.ref.retainAndReturnPointer(), _sel_initToMemory); + return NSOutputStream.castFromPointer(_ret, retain: false, release: true); + } + + /// initWithURL:append: + NSOutputStream? initWithURL_append_(NSURL url, bool shouldAppend) { + final _ret = _objc_msgSend_qqbb5y(this.ref.retainAndReturnPointer(), + _sel_initWithURL_append_, url.ref.pointer, shouldAppend); + return _ret.address == 0 + ? null + : NSOutputStream.castFromPointer(_ret, retain: false, release: true); + } + + /// outputStreamToBuffer:capacity: + static NSOutputStream outputStreamToBuffer_capacity_( + ffi.Pointer buffer, int capacity) { + final _ret = _objc_msgSend_pblopu(_class_NSOutputStream, + _sel_outputStreamToBuffer_capacity_, buffer, capacity); + return NSOutputStream.castFromPointer(_ret, retain: true, release: true); + } + + /// outputStreamToFileAtPath:append: + static NSOutputStream outputStreamToFileAtPath_append_( + NSString path, bool shouldAppend) { + final _ret = _objc_msgSend_qqbb5y(_class_NSOutputStream, + _sel_outputStreamToFileAtPath_append_, path.ref.pointer, shouldAppend); + return NSOutputStream.castFromPointer(_ret, retain: true, release: true); + } + + /// outputStreamToMemory + static NSOutputStream outputStreamToMemory() { + final _ret = + _objc_msgSend_1unuoxw(_class_NSOutputStream, _sel_outputStreamToMemory); + return NSOutputStream.castFromPointer(_ret, retain: true, release: true); + } + + /// outputStreamWithURL:append: + static NSOutputStream? outputStreamWithURL_append_( + NSURL url, bool shouldAppend) { + final _ret = _objc_msgSend_qqbb5y(_class_NSOutputStream, + _sel_outputStreamWithURL_append_, url.ref.pointer, shouldAppend); + return _ret.address == 0 + ? null + : NSOutputStream.castFromPointer(_ret, retain: true, release: true); + } + + /// write:maxLength: + int write_maxLength_(ffi.Pointer buffer, int len) { + return _objc_msgSend_1wopcqf( + this.ref.pointer, _sel_write_maxLength_, buffer, len); + } +} + /// NSProxy class NSProxy extends objc.ObjCObjectBase { NSProxy._(ffi.Pointer pointer, @@ -7395,6 +7809,28 @@ final class NSRange extends ffi.Struct { external int length; } +/// NSRunLoop +class NSRunLoop extends objc.ObjCObjectBase { + NSRunLoop._(ffi.Pointer pointer, + {bool retain = false, bool release = false}) + : super(pointer, retain: retain, release: release); + + /// Constructs a [NSRunLoop] that points to the same underlying object as [other]. + NSRunLoop.castFrom(objc.ObjCObjectBase other) + : this._(other.ref.pointer, retain: true, release: true); + + /// Constructs a [NSRunLoop] that wraps the given raw object pointer. + NSRunLoop.castFromPointer(ffi.Pointer other, + {bool retain = false, bool release = false}) + : this._(other, retain: retain, release: release); + + /// Returns whether [obj] is an instance of [NSRunLoop]. + static bool isInstance(objc.ObjCObjectBase obj) { + return _objc_msgSend_l8lotg( + obj.ref.pointer, _sel_isKindOfClass_, _class_NSRunLoop); + } +} + /// NSSet class NSSet extends NSObject { NSSet._(ffi.Pointer pointer, @@ -7716,6 +8152,287 @@ enum NSSortOptions { }; } +/// NSStream +class NSStream extends NSObject { + NSStream._(ffi.Pointer pointer, + {bool retain = false, bool release = false}) + : super.castFromPointer(pointer, retain: retain, release: release); + + /// Constructs a [NSStream] that points to the same underlying object as [other]. + NSStream.castFrom(objc.ObjCObjectBase other) + : this._(other.ref.pointer, retain: true, release: true); + + /// Constructs a [NSStream] that wraps the given raw object pointer. + NSStream.castFromPointer(ffi.Pointer other, + {bool retain = false, bool release = false}) + : this._(other, retain: retain, release: release); + + /// Returns whether [obj] is an instance of [NSStream]. + static bool isInstance(objc.ObjCObjectBase obj) { + return _objc_msgSend_l8lotg( + obj.ref.pointer, _sel_isKindOfClass_, _class_NSStream); + } + + /// alloc + static NSStream alloc() { + final _ret = _objc_msgSend_1unuoxw(_class_NSStream, _sel_alloc); + return NSStream.castFromPointer(_ret, retain: false, release: true); + } + + /// allocWithZone: + static NSStream allocWithZone_(ffi.Pointer<_NSZone> zone) { + final _ret = + _objc_msgSend_1b3ihd0(_class_NSStream, _sel_allocWithZone_, zone); + return NSStream.castFromPointer(_ret, retain: false, release: true); + } + + /// automaticallyNotifiesObserversForKey: + static bool automaticallyNotifiesObserversForKey_(NSString key) { + return _objc_msgSend_l8lotg(_class_NSStream, + _sel_automaticallyNotifiesObserversForKey_, key.ref.pointer); + } + + /// autorelease + NSStream autorelease() { + final _ret = _objc_msgSend_1unuoxw(this.ref.pointer, _sel_autorelease); + return NSStream.castFromPointer(_ret, retain: true, release: true); + } + + /// close + void close() { + _objc_msgSend_ksby9f(this.ref.pointer, _sel_close); + } + + /// delegate + objc.ObjCObjectBase? get delegate { + final _ret = _objc_msgSend_1unuoxw(this.ref.pointer, _sel_delegate); + return _ret.address == 0 + ? null + : objc.ObjCObjectBase(_ret, retain: true, release: true); + } + + /// getBoundStreamsWithBufferSize:inputStream:outputStream: + static void getBoundStreamsWithBufferSize_inputStream_outputStream_( + int bufferSize, + ffi.Pointer> inputStream, + ffi.Pointer> outputStream) { + _objc_msgSend_5r8xlx( + _class_NSStream, + _sel_getBoundStreamsWithBufferSize_inputStream_outputStream_, + bufferSize, + inputStream, + outputStream); + } + + /// getStreamsToHostWithName:port:inputStream:outputStream: + static void getStreamsToHostWithName_port_inputStream_outputStream_( + NSString hostname, + int port, + ffi.Pointer> inputStream, + ffi.Pointer> outputStream) { + _objc_msgSend_imc4v7( + _class_NSStream, + _sel_getStreamsToHostWithName_port_inputStream_outputStream_, + hostname.ref.pointer, + port, + inputStream, + outputStream); + } + + /// init + NSStream init() { + final _ret = + _objc_msgSend_1unuoxw(this.ref.retainAndReturnPointer(), _sel_init); + return NSStream.castFromPointer(_ret, retain: false, release: true); + } + + /// keyPathsForValuesAffectingValueForKey: + static NSSet keyPathsForValuesAffectingValueForKey_(NSString key) { + final _ret = _objc_msgSend_juohf7(_class_NSStream, + _sel_keyPathsForValuesAffectingValueForKey_, key.ref.pointer); + return NSSet.castFromPointer(_ret, retain: true, release: true); + } + + /// new + static NSStream new1() { + final _ret = _objc_msgSend_1unuoxw(_class_NSStream, _sel_new); + return NSStream.castFromPointer(_ret, retain: false, release: true); + } + + /// open + void open() { + _objc_msgSend_ksby9f(this.ref.pointer, _sel_open); + } + + /// propertyForKey: + objc.ObjCObjectBase? propertyForKey_(NSString key) { + final _ret = _objc_msgSend_juohf7( + this.ref.pointer, _sel_propertyForKey_, key.ref.pointer); + return _ret.address == 0 + ? null + : objc.ObjCObjectBase(_ret, retain: true, release: true); + } + + /// removeFromRunLoop:forMode: + void removeFromRunLoop_forMode_(NSRunLoop aRunLoop, NSString mode) { + _objc_msgSend_1tjlcwl(this.ref.pointer, _sel_removeFromRunLoop_forMode_, + aRunLoop.ref.pointer, mode.ref.pointer); + } + + /// retain + NSStream retain() { + final _ret = _objc_msgSend_1unuoxw(this.ref.pointer, _sel_retain); + return NSStream.castFromPointer(_ret, retain: true, release: true); + } + + /// scheduleInRunLoop:forMode: + void scheduleInRunLoop_forMode_(NSRunLoop aRunLoop, NSString mode) { + _objc_msgSend_1tjlcwl(this.ref.pointer, _sel_scheduleInRunLoop_forMode_, + aRunLoop.ref.pointer, mode.ref.pointer); + } + + /// self + NSStream self() { + final _ret = _objc_msgSend_1unuoxw(this.ref.pointer, _sel_self); + return NSStream.castFromPointer(_ret, retain: true, release: true); + } + + /// setDelegate: + set delegate(objc.ObjCObjectBase? value) { + return _objc_msgSend_ukcdfq( + this.ref.pointer, _sel_setDelegate_, value?.ref.pointer ?? ffi.nullptr); + } + + /// setProperty:forKey: + bool setProperty_forKey_(objc.ObjCObjectBase? property, NSString key) { + return _objc_msgSend_1ywe6ev(this.ref.pointer, _sel_setProperty_forKey_, + property?.ref.pointer ?? ffi.nullptr, key.ref.pointer); + } + + /// streamError + NSError? get streamError { + final _ret = _objc_msgSend_1unuoxw(this.ref.pointer, _sel_streamError); + return _ret.address == 0 + ? null + : NSError.castFromPointer(_ret, retain: true, release: true); + } + + /// streamStatus + NSStreamStatus get streamStatus { + final _ret = _objc_msgSend_1pu7ifu(this.ref.pointer, _sel_streamStatus); + return NSStreamStatus.fromValue(_ret); + } +} + +/// NSStreamDelegate +abstract final class NSStreamDelegate { + /// Builds an object that implements the NSStreamDelegate protocol. To implement + /// multiple protocols, use [addToBuilder] or [objc.ObjCProtocolBuilder] directly. + static objc.ObjCObjectBase implement( + {void Function(NSStream, NSStreamEvent)? stream_handleEvent_}) { + final builder = objc.ObjCProtocolBuilder(); + NSStreamDelegate.stream_handleEvent_ + .implement(builder, stream_handleEvent_); + return builder.build(); + } + + /// Adds the implementation of the NSStreamDelegate protocol to an existing + /// [objc.ObjCProtocolBuilder]. + static void addToBuilder(objc.ObjCProtocolBuilder builder, + {void Function(NSStream, NSStreamEvent)? stream_handleEvent_}) { + NSStreamDelegate.stream_handleEvent_ + .implement(builder, stream_handleEvent_); + } + + /// Builds an object that implements the NSStreamDelegate protocol. To implement + /// multiple protocols, use [addToBuilder] or [objc.ObjCProtocolBuilder] directly. All + /// methods that can be implemented as listeners will be. + static objc.ObjCObjectBase implementAsListener( + {void Function(NSStream, NSStreamEvent)? stream_handleEvent_}) { + final builder = objc.ObjCProtocolBuilder(); + NSStreamDelegate.stream_handleEvent_ + .implementAsListener(builder, stream_handleEvent_); + return builder.build(); + } + + /// Adds the implementation of the NSStreamDelegate protocol to an existing + /// [objc.ObjCProtocolBuilder]. All methods that can be implemented as listeners will + /// be. + static void addToBuilderAsListener(objc.ObjCProtocolBuilder builder, + {void Function(NSStream, NSStreamEvent)? stream_handleEvent_}) { + NSStreamDelegate.stream_handleEvent_ + .implementAsListener(builder, stream_handleEvent_); + } + + /// stream:handleEvent: + static final stream_handleEvent_ = + objc.ObjCProtocolListenableMethod( + _sel_stream_handleEvent_, + objc.getProtocolMethodSignature( + _protocol_NSStreamDelegate, + _sel_stream_handleEvent_, + isRequired: false, + isInstanceMethod: true, + ), + (void Function(NSStream, NSStreamEvent) func) => + ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent.fromFunction( + (ffi.Pointer _, NSStream arg1, NSStreamEvent arg2) => + func(arg1, arg2)), + (void Function(NSStream, NSStreamEvent) func) => + ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent.listener( + (ffi.Pointer _, NSStream arg1, NSStreamEvent arg2) => + func(arg1, arg2)), + ); +} + +enum NSStreamEvent { + NSStreamEventNone(0), + NSStreamEventOpenCompleted(1), + NSStreamEventHasBytesAvailable(2), + NSStreamEventHasSpaceAvailable(4), + NSStreamEventErrorOccurred(8), + NSStreamEventEndEncountered(16); + + final int value; + const NSStreamEvent(this.value); + + static NSStreamEvent fromValue(int value) => switch (value) { + 0 => NSStreamEventNone, + 1 => NSStreamEventOpenCompleted, + 2 => NSStreamEventHasBytesAvailable, + 4 => NSStreamEventHasSpaceAvailable, + 8 => NSStreamEventErrorOccurred, + 16 => NSStreamEventEndEncountered, + _ => throw ArgumentError("Unknown value for NSStreamEvent: $value"), + }; +} + +enum NSStreamStatus { + NSStreamStatusNotOpen(0), + NSStreamStatusOpening(1), + NSStreamStatusOpen(2), + NSStreamStatusReading(3), + NSStreamStatusWriting(4), + NSStreamStatusAtEnd(5), + NSStreamStatusClosed(6), + NSStreamStatusError(7); + + final int value; + const NSStreamStatus(this.value); + + static NSStreamStatus fromValue(int value) => switch (value) { + 0 => NSStreamStatusNotOpen, + 1 => NSStreamStatusOpening, + 2 => NSStreamStatusOpen, + 3 => NSStreamStatusReading, + 4 => NSStreamStatusWriting, + 5 => NSStreamStatusAtEnd, + 6 => NSStreamStatusClosed, + 7 => NSStreamStatusError, + _ => throw ArgumentError("Unknown value for NSStreamStatus: $value"), + }; +} + /// NSString class NSString extends NSObject { factory NSString(String str) { @@ -11445,6 +12162,173 @@ extension ObjCBlock_ffiVoid_ffiVoid_NSCoder_CallExtension ref.pointer, arg0, arg1.ref.pointer); } +void _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_fnPtrTrampoline( + ffi.Pointer block, + ffi.Pointer arg0, + ffi.Pointer arg1, + int arg2) => + block.ref.target + .cast< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer arg0, + ffi.Pointer arg1, + ffi.UnsignedLong arg2)>>() + .asFunction< + void Function(ffi.Pointer, ffi.Pointer, + int)>()(arg0, arg1, arg2); +ffi.Pointer + _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_fnPtrCallable = + ffi.Pointer.fromFunction< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.UnsignedLong)>( + _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_fnPtrTrampoline) + .cast(); +void _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_closureTrampoline( + ffi.Pointer block, + ffi.Pointer arg0, + ffi.Pointer arg1, + int arg2) => + (objc.getBlockClosure(block) as void Function(ffi.Pointer, + ffi.Pointer, int))(arg0, arg1, arg2); +ffi.Pointer + _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_closureCallable = + ffi.Pointer.fromFunction< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.UnsignedLong)>( + _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_closureTrampoline) + .cast(); +void _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_listenerTrampoline( + ffi.Pointer block, + ffi.Pointer arg0, + ffi.Pointer arg1, + int arg2) { + (objc.getBlockClosure(block) as void Function(ffi.Pointer, + ffi.Pointer, int))(arg0, arg1, arg2); + objc.objectRelease(block.cast()); +} + +ffi.NativeCallable< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.UnsignedLong)> + _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_listenerCallable = ffi + .NativeCallable< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.UnsignedLong)>.listener( + _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_listenerTrampoline) + ..keepIsolateAlive = false; + +/// Construction methods for `objc.ObjCBlock, NSStream, ffi.UnsignedLong)>`. +abstract final class ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent { + /// Returns a block that wraps the given raw block pointer. + static objc.ObjCBlock< + ffi.Void Function(ffi.Pointer, NSStream, ffi.UnsignedLong)> + castFromPointer(ffi.Pointer pointer, + {bool retain = false, bool release = false}) => + objc.ObjCBlock< + ffi.Void Function(ffi.Pointer, NSStream, + ffi.UnsignedLong)>(pointer, retain: retain, release: release); + + /// Creates a block from a C function pointer. + /// + /// This block must be invoked by native code running on the same thread as + /// the isolate that registered it. Invoking the block on the wrong thread + /// will result in a crash. + static objc + .ObjCBlock, NSStream, ffi.UnsignedLong)> + fromFunctionPointer( + ffi.Pointer arg0, ffi.Pointer arg1, ffi.UnsignedLong arg2)>> + ptr) => + objc.ObjCBlock, NSStream, ffi.UnsignedLong)>( + objc.newPointerBlock( + _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_fnPtrCallable, + ptr.cast()), + retain: false, + release: true); + + /// Creates a block from a Dart function. + /// + /// This block must be invoked by native code running on the same thread as + /// the isolate that registered it. Invoking the block on the wrong thread + /// will result in a crash. + static objc.ObjCBlock, NSStream, ffi.UnsignedLong)> fromFunction( + void Function(ffi.Pointer, NSStream, NSStreamEvent) fn) => + objc.ObjCBlock, NSStream, ffi.UnsignedLong)>( + objc.newClosureBlock( + _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_closureCallable, + (ffi.Pointer arg0, ffi.Pointer arg1, + int arg2) => + fn( + arg0, + NSStream.castFromPointer(arg1, retain: true, release: true), + NSStreamEvent.fromValue(arg2))), + retain: false, + release: true); + + /// Creates a listener block from a Dart function. + /// + /// This is based on FFI's NativeCallable.listener, and has the same + /// capabilities and limitations. This block can be invoked from any thread, + /// but only supports void functions, and is not run synchronously. See + /// NativeCallable.listener for more details. + /// + /// Note that unlike the default behavior of NativeCallable.listener, listener + /// blocks do not keep the isolate alive. + static objc.ObjCBlock< + ffi.Void Function(ffi.Pointer, NSStream, ffi.UnsignedLong)> + listener( + void Function(ffi.Pointer, NSStream, NSStreamEvent) fn) { + final raw = objc.newClosureBlock( + _ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_listenerCallable + .nativeFunction + .cast(), + (ffi.Pointer arg0, ffi.Pointer arg1, + int arg2) => + fn( + arg0, + NSStream.castFromPointer(arg1, retain: false, release: true), + NSStreamEvent.fromValue(arg2))); + final wrapper = _wrapListenerBlock_m1viep(raw); + objc.objectRelease(raw.cast()); + return objc.ObjCBlock< + ffi.Void Function(ffi.Pointer, NSStream, + ffi.UnsignedLong)>(wrapper, retain: false, release: true); + } +} + +/// Call operator for `objc.ObjCBlock, NSStream, ffi.UnsignedLong)>`. +extension ObjCBlock_ffiVoid_ffiVoid_NSStream_NSStreamEvent_CallExtension + on objc.ObjCBlock< + ffi.Void Function(ffi.Pointer, NSStream, ffi.UnsignedLong)> { + void call(ffi.Pointer arg0, NSStream arg1, NSStreamEvent arg2) => + ref.pointer.ref.invoke + .cast< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer block, + ffi.Pointer arg0, + ffi.Pointer arg1, + ffi.UnsignedLong arg2)>>() + .asFunction< + void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + int)>()(ref.pointer, arg0, arg1.ref.pointer, arg2.value); +} + instancetype _ObjCBlock_instancetype_ffiVoid_NSCoder_fnPtrTrampoline( ffi.Pointer block, ffi.Pointer arg0, @@ -12320,6 +13204,8 @@ class Protocol extends objc.ObjCObjectBase { final class _NSZone extends ffi.Opaque {} +late final _class_DartInputStreamAdapter = + objc.getClass("DartInputStreamAdapter"); late final _class_DartProxy = objc.getClass("DartProxy"); late final _class_DartProxyBuilder = objc.getClass("DartProxyBuilder"); late final _class_NSArray = objc.getClass("NSArray"); @@ -12331,6 +13217,7 @@ late final _class_NSDictionary = objc.getClass("NSDictionary"); late final _class_NSEnumerator = objc.getClass("NSEnumerator"); late final _class_NSError = objc.getClass("NSError"); late final _class_NSIndexSet = objc.getClass("NSIndexSet"); +late final _class_NSInputStream = objc.getClass("NSInputStream"); late final _class_NSInvocation = objc.getClass("NSInvocation"); late final _class_NSItemProvider = objc.getClass("NSItemProvider"); late final _class_NSLocale = objc.getClass("NSLocale"); @@ -12346,8 +13233,11 @@ late final _class_NSNotification = objc.getClass("NSNotification"); late final _class_NSNumber = objc.getClass("NSNumber"); late final _class_NSObject = objc.getClass("NSObject"); late final _class_NSOrderedSet = objc.getClass("NSOrderedSet"); +late final _class_NSOutputStream = objc.getClass("NSOutputStream"); late final _class_NSProxy = objc.getClass("NSProxy"); +late final _class_NSRunLoop = objc.getClass("NSRunLoop"); late final _class_NSSet = objc.getClass("NSSet"); +late final _class_NSStream = objc.getClass("NSStream"); late final _class_NSString = objc.getClass("NSString"); late final _class_NSURL = objc.getClass("NSURL"); late final _class_NSURLHandle = objc.getClass("NSURLHandle"); @@ -13135,6 +14025,14 @@ final _objc_msgSend_1pmj399 = objc.msgSendPointer .asFunction< ffi.Pointer Function(ffi.Pointer, ffi.Pointer, double)>(); +final _objc_msgSend_1pu7ifu = objc.msgSendPointer + .cast< + ffi.NativeFunction< + ffi.UnsignedLong Function(ffi.Pointer, + ffi.Pointer)>>() + .asFunction< + int Function( + ffi.Pointer, ffi.Pointer)>(); final _objc_msgSend_1qfg2kn = objc.msgSendPointer .cast< ffi.NativeFunction< @@ -13292,6 +14190,20 @@ final _objc_msgSend_1upz917 = objc.msgSendPointer .asFunction< ffi.Pointer Function(ffi.Pointer, ffi.Pointer, bool)>(); +final _objc_msgSend_1vnalux = objc.msgSendPointer + .cast< + ffi.NativeFunction< + ffi.Bool Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer>, + ffi.Pointer)>>() + .asFunction< + bool Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer>, + ffi.Pointer)>(); final _objc_msgSend_1wjxqnx = objc.msgSendPointer .cast< ffi.NativeFunction< @@ -13326,6 +14238,17 @@ final _objc_msgSend_1wjxqnxStret = objc.msgSendStretPointer ffi.Pointer, int, NSRange)>(); +final _objc_msgSend_1wopcqf = objc.msgSendPointer + .cast< + ffi.NativeFunction< + ffi.Long Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.UnsignedLong)>>() + .asFunction< + int Function(ffi.Pointer, + ffi.Pointer, ffi.Pointer, int)>(); final _objc_msgSend_1x7hfdx = objc.msgSendPointer .cast< ffi.NativeFunction< @@ -13420,6 +14343,20 @@ final _objc_msgSend_1ypnhm3 = objc.msgSendPointer .asFunction< ffi.Pointer Function( ffi.Pointer, ffi.Pointer)>(); +final _objc_msgSend_1ywe6ev = objc.msgSendPointer + .cast< + ffi.NativeFunction< + ffi.Bool Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)>>() + .asFunction< + bool Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Pointer)>(); final _objc_msgSend_2n95es = objc.msgSendPointer .cast< ffi.NativeFunction< @@ -13468,6 +14405,22 @@ final _objc_msgSend_5ns8s6 = objc.msgSendPointer .asFunction< void Function(ffi.Pointer, ffi.Pointer, ffi.Pointer)>(); +final _objc_msgSend_5r8xlx = objc.msgSendPointer + .cast< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.UnsignedLong, + ffi.Pointer>, + ffi.Pointer>)>>() + .asFunction< + void Function( + ffi.Pointer, + ffi.Pointer, + int, + ffi.Pointer>, + ffi.Pointer>)>(); final _objc_msgSend_6ka9sp = objc.msgSendPointer .cast< ffi.NativeFunction< @@ -13518,6 +14471,20 @@ final _objc_msgSend_6toz8x = objc.msgSendPointer ffi.Pointer, ffi.Pointer, ffi.Pointer>)>(); +final _objc_msgSend_7zmbk4 = objc.msgSendPointer + .cast< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.UnsignedLong)>>() + .asFunction< + void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + int)>(); final _objc_msgSend_85e5ih = objc.msgSendPointer .cast< ffi.NativeFunction< @@ -13860,6 +14827,24 @@ final _objc_msgSend_hyhdx3 = objc.msgSendPointer ffi.Pointer, NSRange, bool)>(); +final _objc_msgSend_imc4v7 = objc.msgSendPointer + .cast< + ffi.NativeFunction< + ffi.Void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.Long, + ffi.Pointer>, + ffi.Pointer>)>>() + .asFunction< + void Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + int, + ffi.Pointer>, + ffi.Pointer>)>(); final _objc_msgSend_iq11qg = objc.msgSendPointer .cast< ffi.NativeFunction< @@ -14094,6 +15079,14 @@ final _objc_msgSend_n73nlr = objc.msgSendPointer ffi.Pointer, int, NSRange)>(); +final _objc_msgSend_n9eq1n = objc.msgSendPointer + .cast< + ffi.NativeFunction< + instancetype Function(ffi.Pointer, + ffi.Pointer, ffi.Int64)>>() + .asFunction< + instancetype Function(ffi.Pointer, + ffi.Pointer, int)>(); final _objc_msgSend_nbaahq = objc.msgSendPointer .cast< ffi.NativeFunction< @@ -14190,6 +15183,17 @@ final _objc_msgSend_p02k6o = objc.msgSendPointer ffi.Pointer, ffi.Pointer, ffi.Pointer>)>(); +final _objc_msgSend_pblopu = objc.msgSendPointer + .cast< + ffi.NativeFunction< + instancetype Function( + ffi.Pointer, + ffi.Pointer, + ffi.Pointer, + ffi.UnsignedLong)>>() + .asFunction< + instancetype Function(ffi.Pointer, + ffi.Pointer, ffi.Pointer, int)>(); final _objc_msgSend_pxgym4 = objc.msgSendPointer .cast< ffi.NativeFunction< @@ -14565,6 +15569,7 @@ final _objc_msgSend_z1lin0 = objc.msgSendPointer .asFunction< bool Function(ffi.Pointer, ffi.Pointer, ffi.Pointer, int)>(); +late final _protocol_NSStreamDelegate = objc.getProtocol("NSStreamDelegate"); late final _sel_URLByAppendingPathComponent_ = objc.registerName("URLByAppendingPathComponent:"); late final _sel_URLByAppendingPathComponent_isDirectory_ = @@ -14608,6 +15613,7 @@ late final _sel_absoluteString = objc.registerName("absoluteString"); late final _sel_absoluteURL = objc.registerName("absoluteURL"); late final _sel_absoluteURLWithDataRepresentation_relativeToURL_ = objc.registerName("absoluteURLWithDataRepresentation:relativeToURL:"); +late final _sel_addData_ = objc.registerName("addData:"); late final _sel_addEntriesFromDictionary_ = objc.registerName("addEntriesFromDictionary:"); late final _sel_addIndex_ = objc.registerName("addIndex:"); @@ -14715,6 +15721,7 @@ late final _sel_checkResourceIsReachableAndReturnError_ = objc.registerName("checkResourceIsReachableAndReturnError:"); late final _sel_class = objc.registerName("class"); late final _sel_classForCoder = objc.registerName("classForCoder"); +late final _sel_close = objc.registerName("close"); late final _sel_code = objc.registerName("code"); late final _sel_commonPrefixWithString_options_ = objc.registerName("commonPrefixWithString:options:"); @@ -14798,6 +15805,7 @@ late final _sel_decompressedDataUsingAlgorithm_error_ = objc.registerName("decompressedDataUsingAlgorithm:error:"); late final _sel_defaultCStringEncoding = objc.registerName("defaultCStringEncoding"); +late final _sel_delegate = objc.registerName("delegate"); late final _sel_deleteCharactersInRange_ = objc.registerName("deleteCharactersInRange:"); late final _sel_description = objc.registerName("description"); @@ -14884,6 +15892,9 @@ late final _sel_fragment = objc.registerName("fragment"); late final _sel_frameLength = objc.registerName("frameLength"); late final _sel_getArgumentTypeAtIndex_ = objc.registerName("getArgumentTypeAtIndex:"); +late final _sel_getBoundStreamsWithBufferSize_inputStream_outputStream_ = objc + .registerName("getBoundStreamsWithBufferSize:inputStream:outputStream:"); +late final _sel_getBuffer_length_ = objc.registerName("getBuffer:length:"); late final _sel_getBytes_length_ = objc.registerName("getBytes:length:"); late final _sel_getBytes_maxLength_usedLength_encoding_options_range_remainingRange_ = objc.registerName( @@ -14909,8 +15920,11 @@ late final _sel_getPromisedItemResourceValue_forKey_error_ = objc.registerName("getPromisedItemResourceValue:forKey:error:"); late final _sel_getResourceValue_forKey_error_ = objc.registerName("getResourceValue:forKey:error:"); +late final _sel_getStreamsToHostWithName_port_inputStream_outputStream_ = objc + .registerName("getStreamsToHostWithName:port:inputStream:outputStream:"); late final _sel_getValue_ = objc.registerName("getValue:"); late final _sel_getValue_size_ = objc.registerName("getValue:size:"); +late final _sel_hasBytesAvailable = objc.registerName("hasBytesAvailable"); late final _sel_hasDirectoryPath = objc.registerName("hasDirectoryPath"); late final _sel_hasItemConformingToTypeIdentifier_ = objc.registerName("hasItemConformingToTypeIdentifier:"); @@ -14918,6 +15932,7 @@ late final _sel_hasMemberInPlane_ = objc.registerName("hasMemberInPlane:"); late final _sel_hasPrefix_ = objc.registerName("hasPrefix:"); late final _sel_hasRepresentationConformingToTypeIdentifier_fileOptions_ = objc .registerName("hasRepresentationConformingToTypeIdentifier:fileOptions:"); +late final _sel_hasSpaceAvailable = objc.registerName("hasSpaceAvailable"); late final _sel_hasSuffix_ = objc.registerName("hasSuffix:"); late final _sel_hash = objc.registerName("hash"); late final _sel_helpAnchor = objc.registerName("helpAnchor"); @@ -14962,6 +15977,11 @@ late final _sel_initFileURLWithPath_isDirectory_relativeToURL_ = late final _sel_initFileURLWithPath_relativeToURL_ = objc.registerName("initFileURLWithPath:relativeToURL:"); late final _sel_initFromBuilder_ = objc.registerName("initFromBuilder:"); +late final _sel_initToBuffer_capacity_ = + objc.registerName("initToBuffer:capacity:"); +late final _sel_initToFileAtPath_append_ = + objc.registerName("initToFileAtPath:append:"); +late final _sel_initToMemory = objc.registerName("initToMemory"); late final _sel_initWithArray_ = objc.registerName("initWithArray:"); late final _sel_initWithArray_copyItems_ = objc.registerName("initWithArray:copyItems:"); @@ -15022,6 +16042,7 @@ late final _sel_initWithDictionary_copyItems_ = late final _sel_initWithDomain_code_userInfo_ = objc.registerName("initWithDomain:code:userInfo:"); late final _sel_initWithDouble_ = objc.registerName("initWithDouble:"); +late final _sel_initWithFileAtPath_ = objc.registerName("initWithFileAtPath:"); late final _sel_initWithFloat_ = objc.registerName("initWithFloat:"); late final _sel_initWithFormat_ = objc.registerName("initWithFormat:"); late final _sel_initWithFormat_locale_ = @@ -15071,6 +16092,8 @@ late final _sel_initWithTimeIntervalSinceReferenceDate_ = objc.registerName("initWithTimeIntervalSinceReferenceDate:"); late final _sel_initWithTimeInterval_sinceDate_ = objc.registerName("initWithTimeInterval:sinceDate:"); +late final _sel_initWithURL_ = objc.registerName("initWithURL:"); +late final _sel_initWithURL_append_ = objc.registerName("initWithURL:append:"); late final _sel_initWithUTF8String_ = objc.registerName("initWithUTF8String:"); late final _sel_initWithUnsignedChar_ = objc.registerName("initWithUnsignedChar:"); @@ -15090,6 +16113,13 @@ late final _sel_initWithValidatedFormat_validFormatSpecifiers_locale_error_ = objc.registerName( "initWithValidatedFormat:validFormatSpecifiers:locale:error:"); late final _sel_initialize = objc.registerName("initialize"); +late final _sel_inputStreamWithData_ = + objc.registerName("inputStreamWithData:"); +late final _sel_inputStreamWithFileAtPath_ = + objc.registerName("inputStreamWithFileAtPath:"); +late final _sel_inputStreamWithPort_ = + objc.registerName("inputStreamWithPort:"); +late final _sel_inputStreamWithURL_ = objc.registerName("inputStreamWithURL:"); late final _sel_insertObject_atIndex_ = objc.registerName("insertObject:atIndex:"); late final _sel_insertObjects_atIndexes_ = @@ -15268,6 +16298,7 @@ late final _sel_objectsForKeys_notFoundMarker_ = late final _sel_observationInfo = objc.registerName("observationInfo"); late final _sel_observeValueForKeyPath_ofObject_change_context_ = objc.registerName("observeValueForKeyPath:ofObject:change:context:"); +late final _sel_open = objc.registerName("open"); late final _sel_orderedSet = objc.registerName("orderedSet"); late final _sel_orderedSetByApplyingDifference_ = objc.registerName("orderedSetByApplyingDifference:"); @@ -15290,6 +16321,14 @@ late final _sel_orderedSetWithOrderedSet_range_copyItems_ = late final _sel_orderedSetWithSet_ = objc.registerName("orderedSetWithSet:"); late final _sel_orderedSetWithSet_copyItems_ = objc.registerName("orderedSetWithSet:copyItems:"); +late final _sel_outputStreamToBuffer_capacity_ = + objc.registerName("outputStreamToBuffer:capacity:"); +late final _sel_outputStreamToFileAtPath_append_ = + objc.registerName("outputStreamToFileAtPath:append:"); +late final _sel_outputStreamToMemory = + objc.registerName("outputStreamToMemory"); +late final _sel_outputStreamWithURL_append_ = + objc.registerName("outputStreamWithURL:append:"); late final _sel_paragraphRangeForRange_ = objc.registerName("paragraphRangeForRange:"); late final _sel_parameterString = objc.registerName("parameterString"); @@ -15310,6 +16349,7 @@ late final _sel_precomposedStringWithCompatibilityMapping = objc.registerName("precomposedStringWithCompatibilityMapping"); late final _sel_promisedItemResourceValuesForKeys_error_ = objc.registerName("promisedItemResourceValuesForKeys:error:"); +late final _sel_propertyForKey_ = objc.registerName("propertyForKey:"); late final _sel_propertyList = objc.registerName("propertyList"); late final _sel_propertyListFromStringsFileFormat = objc.registerName("propertyListFromStringsFileFormat"); @@ -15336,6 +16376,7 @@ late final _sel_rangeOfString_options_range_ = late final _sel_rangeOfString_options_range_locale_ = objc.registerName("rangeOfString:options:range:locale:"); late final _sel_rangeValue = objc.registerName("rangeValue"); +late final _sel_read_maxLength_ = objc.registerName("read:maxLength:"); late final _sel_readableTypeIdentifiersForItemProvider = objc.registerName("readableTypeIdentifiersForItemProvider"); late final _sel_recoveryAttempter = objc.registerName("recoveryAttempter"); @@ -15354,6 +16395,8 @@ late final _sel_removeAllIndexes = objc.registerName("removeAllIndexes"); late final _sel_removeAllObjects = objc.registerName("removeAllObjects"); late final _sel_removeCachedResourceValueForKey_ = objc.registerName("removeCachedResourceValueForKey:"); +late final _sel_removeFromRunLoop_forMode_ = + objc.registerName("removeFromRunLoop:forMode:"); late final _sel_removeIndex_ = objc.registerName("removeIndex:"); late final _sel_removeIndexesInRange_ = objc.registerName("removeIndexesInRange:"); @@ -15420,6 +16463,8 @@ late final _sel_retainCount = objc.registerName("retainCount"); late final _sel_reverseObjectEnumerator = objc.registerName("reverseObjectEnumerator"); late final _sel_reversedOrderedSet = objc.registerName("reversedOrderedSet"); +late final _sel_scheduleInRunLoop_forMode_ = + objc.registerName("scheduleInRunLoop:forMode:"); late final _sel_scheme = objc.registerName("scheme"); late final _sel_self = objc.registerName("self"); late final _sel_set = objc.registerName("set"); @@ -15430,7 +16475,10 @@ late final _sel_setByAddingObjectsFromArray_ = late final _sel_setByAddingObjectsFromSet_ = objc.registerName("setByAddingObjectsFromSet:"); late final _sel_setData_ = objc.registerName("setData:"); +late final _sel_setDelegate_ = objc.registerName("setDelegate:"); late final _sel_setDictionary_ = objc.registerName("setDictionary:"); +late final _sel_setDone = objc.registerName("setDone"); +late final _sel_setError_ = objc.registerName("setError:"); late final _sel_setLength_ = objc.registerName("setLength:"); late final _sel_setObject_atIndex_ = objc.registerName("setObject:atIndex:"); late final _sel_setObject_atIndexedSubscript_ = @@ -15439,6 +16487,7 @@ late final _sel_setObject_forKey_ = objc.registerName("setObject:forKey:"); late final _sel_setObject_forKeyedSubscript_ = objc.registerName("setObject:forKeyedSubscript:"); late final _sel_setObservationInfo_ = objc.registerName("setObservationInfo:"); +late final _sel_setProperty_forKey_ = objc.registerName("setProperty:forKey:"); late final _sel_setResourceValue_forKey_error_ = objc.registerName("setResourceValue:forKey:error:"); late final _sel_setResourceValues_error_ = @@ -15479,6 +16528,9 @@ late final _sel_startAccessingSecurityScopedResource = objc.registerName("startAccessingSecurityScopedResource"); late final _sel_stopAccessingSecurityScopedResource = objc.registerName("stopAccessingSecurityScopedResource"); +late final _sel_streamError = objc.registerName("streamError"); +late final _sel_streamStatus = objc.registerName("streamStatus"); +late final _sel_stream_handleEvent_ = objc.registerName("stream:handleEvent:"); late final _sel_string = objc.registerName("string"); late final _sel_stringByAddingPercentEncodingWithAllowedCharacters_ = objc.registerName("stringByAddingPercentEncodingWithAllowedCharacters:"); @@ -15597,6 +16649,7 @@ late final _sel_writeToURL_atomically_encoding_error_ = late final _sel_writeToURL_error_ = objc.registerName("writeToURL:error:"); late final _sel_writeToURL_options_error_ = objc.registerName("writeToURL:options:error:"); +late final _sel_write_maxLength_ = objc.registerName("write:maxLength:"); late final _sel_zone = objc.registerName("zone"); typedef instancetype = ffi.Pointer; typedef Dartinstancetype = objc.ObjCObjectBase; diff --git a/pkgs/objective_c/macos/Classes/objective_c.m b/pkgs/objective_c/macos/Classes/objective_c.m index 25d76dca3..1d3862c48 100644 --- a/pkgs/objective_c/macos/Classes/objective_c.m +++ b/pkgs/objective_c/macos/Classes/objective_c.m @@ -4,6 +4,7 @@ // Relative import to be able to reuse the ObjC sources. // See the comment in ../objective_c.podspec for more information. +#include "../../src/input_stream_adapter.m" #include "../../src/objective_c.m" #include "../../src/objective_c_bindings_generated.m" #include "../../src/proxy.m" diff --git a/pkgs/objective_c/pubspec.yaml b/pkgs/objective_c/pubspec.yaml index 545e6550e..7ad8c7369 100644 --- a/pkgs/objective_c/pubspec.yaml +++ b/pkgs/objective_c/pubspec.yaml @@ -4,7 +4,7 @@ name: objective_c description: 'A library to access Objective C from Flutter that acts as a support library for package:ffigen.' -version: 2.1.0-wip +version: 3.0.0-wip repository: https://github.com/dart-lang/native/tree/main/pkgs/objective_c topics: diff --git a/pkgs/objective_c/src/foundation.h b/pkgs/objective_c/src/foundation.h index 85e20dcc3..4dd4f0a16 100644 --- a/pkgs/objective_c/src/foundation.h +++ b/pkgs/objective_c/src/foundation.h @@ -10,6 +10,7 @@ #import #import #import +#import #import #import #import diff --git a/pkgs/objective_c/src/input_stream_adapter.h b/pkgs/objective_c/src/input_stream_adapter.h new file mode 100644 index 000000000..77f9e42ce --- /dev/null +++ b/pkgs/objective_c/src/input_stream_adapter.h @@ -0,0 +1,27 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#ifndef INPUT_STREAM_ADAPTER_H_ +#define INPUT_STREAM_ADAPTER_H_ + +#include "include/dart_api_dl.h" + +#import +#import + +/// Helper class to adapt a Dart stream into a `NSInputStream`. +@interface DartInputStreamAdapter : NSInputStream + +/// Creates the adapter. +/// @param sendPort A port to that is will receive two types of messages: +/// -1 => The `NSInputStream` has been closed and the port can be closed. +/// _ => The number of types being required in a `read:maxLength` call. ++ (instancetype)inputStreamWithPort:(Dart_Port)sendPort; + +- (NSUInteger)addData:(NSData *)data; +- (void)setDone; +- (void)setError:(NSError *)error; +@end + +#endif // INPUT_STREAM_ADAPTER_H_ diff --git a/pkgs/objective_c/src/input_stream_adapter.m b/pkgs/objective_c/src/input_stream_adapter.m new file mode 100644 index 000000000..5260ae1ba --- /dev/null +++ b/pkgs/objective_c/src/input_stream_adapter.m @@ -0,0 +1,169 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +#import "input_stream_adapter.h" + +#import +#include + +@implementation DartInputStreamAdapter { + Dart_Port _sendPort; + NSCondition *_dataCondition; + NSMutableData *_data; + NSStreamStatus _status; + BOOL _done; + NSError *_error; + id __weak _delegate; +} + ++ (instancetype)inputStreamWithPort:(Dart_Port)sendPort { + DartInputStreamAdapter *stream = [[DartInputStreamAdapter alloc] init]; + if (stream != nil) { + stream->_sendPort = sendPort; + stream->_dataCondition = [[NSCondition alloc] init]; + stream->_data = [[NSMutableData alloc] init]; + stream->_done = NO; + stream->_status = NSStreamStatusNotOpen; + stream->_error = nil; + // From https://developer.apple.com/documentation/foundation/nsstream: + // "...by a default, a stream object must be its own delegate..." + stream->_delegate = stream; + } + return stream; +} + +- (NSUInteger)addData:(NSData *)data { + [_dataCondition lock]; + [_data appendData:data]; + [_dataCondition broadcast]; + [_dataCondition unlock]; + return [_data length]; +} + +- (void)setDone { + [_dataCondition lock]; + _done = YES; + [_dataCondition broadcast]; + [_dataCondition unlock]; +} + +- (void)setError:(NSError *)error { + [_dataCondition lock]; + _error = error; + [_dataCondition broadcast]; + [_dataCondition unlock]; +} + +#pragma mark - NSStream + +- (void)scheduleInRunLoop:(NSRunLoop *)runLoop forMode:(NSString *)mode { +} + +- (void)removeFromRunLoop:(NSRunLoop *)runLoop forMode:(NSString *)mode { +} + +- (void)open { + [_dataCondition lock]; + _status = NSStreamStatusOpen; + [_dataCondition unlock]; +} + +- (void)close { + [_dataCondition lock]; + _status = NSStreamStatusClosed; + if (!_done && _error == nil) { + const bool success = Dart_PostInteger_DL(_sendPort, -1); + NSCAssert(success, @"DartInputStreamAdapter: Dart_PostCObject_DL failed."); + } + [_dataCondition unlock]; +} + +- (id)propertyForKey:(NSStreamPropertyKey)key { + return nil; +} + +- (BOOL)setProperty:(id)property forKey:(NSStreamPropertyKey)key { + return NO; +} + +- (id)delegate { + return _delegate; +} + +- (void)setDelegate:(id)delegate { + // From https://developer.apple.com/documentation/foundation/nsstream: + // "...so a delegate message with an argument of nil should restore this + // delegate..." + if (delegate == nil) { + _delegate = self; + } else { + _delegate = delegate; + } +} + +- (NSError *)streamError { + return _error; +} + +- (NSStreamStatus)streamStatus { + return _status; +} + +#pragma mark - NSInputStream + +- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len { + if (_status == NSStreamStatusNotOpen) { + os_log_with_type(OS_LOG_DEFAULT, OS_LOG_TYPE_DEBUG, + "DartInputStreamAdapter: read before open"); + return -1; + } + + [_dataCondition lock]; + + while (([_data length] == 0) && !_done && _error == nil) { + const bool success = Dart_PostInteger_DL(_sendPort, len); + NSCAssert(success, @"DartInputStreamAdapter: Dart_PostCObject_DL failed."); + + [_dataCondition wait]; + } + + NSInteger copySize; + if (_error == nil) { + copySize = MIN(len, [_data length]); + NSRange readRange = NSMakeRange(0, copySize); + [_data getBytes:(void *)buffer range:readRange]; + // Shift the remaining data over to the beginning of the buffer. + // NOTE: this makes small reads expensive! + [_data replaceBytesInRange:readRange withBytes:NULL length:0]; + + if (_done && [_data length] == 0) { + _status = NSStreamStatusAtEnd; + } + } else { + _status = NSStreamStatusError; + copySize = -1; + } + + [_dataCondition unlock]; + return copySize; +} + +- (BOOL)getBuffer:(uint8_t **)buffer length:(NSUInteger *)len { + return NO; +} + +- (BOOL)hasBytesAvailable { + return _status == NSStreamStatusOpen; +} + +#pragma mark - NSStreamDelegate + +- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent { + id delegate = _delegate; + if (delegate != self) { + [delegate stream:self handleEvent:streamEvent]; + } +} + +@end diff --git a/pkgs/objective_c/src/objective_c_bindings_generated.m b/pkgs/objective_c/src/objective_c_bindings_generated.m index 01874b672..9237ca7be 100644 --- a/pkgs/objective_c/src/objective_c_bindings_generated.m +++ b/pkgs/objective_c/src/objective_c_bindings_generated.m @@ -1,5 +1,6 @@ #include #import "foundation.h" +#import "input_stream_adapter.h" #import "proxy.h" #if !__has_feature(objc_arc) @@ -24,3 +25,11 @@ _ListenerTrampoline1 _wrapListenerBlock_sjfpmz(_ListenerTrampoline1 block) NS_RE block(arg0, objc_retain(arg1)); }; } + +typedef void (^_ListenerTrampoline2)(void * arg0, id arg1, NSStreamEvent arg2); +_ListenerTrampoline2 _wrapListenerBlock_m1viep(_ListenerTrampoline2 block) NS_RETURNS_RETAINED { + return ^void(void * arg0, id arg1, NSStreamEvent arg2) { + objc_retainBlock(block); + block(arg0, objc_retain(arg1), arg2); + }; +} diff --git a/pkgs/objective_c/test/interface_lists_test.dart b/pkgs/objective_c/test/interface_lists_test.dart index 41d3e1f96..094eabb59 100644 --- a/pkgs/objective_c/test/interface_lists_test.dart +++ b/pkgs/objective_c/test/interface_lists_test.dart @@ -12,11 +12,14 @@ import 'package:ffigen/src/code_generator/objc_built_in_functions.dart'; import 'package:test/test.dart'; import 'package:yaml/yaml.dart'; +const privateObjectiveCClasses = ['DartInputStreamAdapter']; + void main() { group('Verify interface lists', () { late List yamlInterfaces; late List yamlStructs; late List yamlEnums; + late List yamlProtocols; setUpAll(() { final yaml = @@ -36,6 +39,11 @@ void main() { .map((dynamic i) => i as String) .toList() ..sort(); + yamlProtocols = ((yaml['objc-protocols'] as YamlMap)['include'] + as YamlList) + .map((dynamic i) => i as String) + .toList() + ..sort(); }); test('ObjCBuiltInFunctions.builtInInterfaces', () { @@ -50,10 +58,16 @@ void main() { expect(ObjCBuiltInFunctions.builtInEnums, yamlEnums); }); + test('ObjCBuiltInFunctions.builtInProtocols', () { + expect(ObjCBuiltInFunctions.builtInProtocols, yamlProtocols); + }); + test('package:objective_c exports all the interfaces', () { final exportFile = File('lib/objective_c.dart').readAsStringSync(); for (final intf in yamlInterfaces) { - expect(exportFile, contains(intf)); + if (!privateObjectiveCClasses.contains(intf)) { + expect(exportFile, contains(intf)); + } } }); @@ -71,6 +85,13 @@ void main() { } }); + test('package:objective_c exports all the protocols', () { + final exportFile = File('lib/objective_c.dart').readAsStringSync(); + for (final protocol in yamlProtocols) { + expect(exportFile, contains(protocol)); + } + }); + test('All code genned interfaces are included in the list', () { final classNameRegExp = RegExp(r'^class (\w+) '); final allClassNames = []; @@ -113,8 +134,21 @@ void main() { allEnumNames.add(match[1]!); } } - allEnumNames.sort(); - expect(allEnumNames, yamlEnums); + expect(allEnumNames, unorderedEquals(yamlEnums)); + }); + + test('All code genned protocols are included in the list', () { + final protocolNameRegExp = + RegExp(r'^abstract final class (?!ObjCBlock_)(\w+) {'); + final allProtocolNames = []; + for (final line in File('lib/src/objective_c_bindings_generated.dart') + .readAsLinesSync()) { + final match = protocolNameRegExp.firstMatch(line); + if (match != null) { + allProtocolNames.add(match[1]!); + } + } + expect(allProtocolNames, unorderedEquals(yamlProtocols)); }); }); } diff --git a/pkgs/objective_c/test/ns_input_stream_test.dart b/pkgs/objective_c/test/ns_input_stream_test.dart new file mode 100644 index 000000000..c9baa02ac --- /dev/null +++ b/pkgs/objective_c/test/ns_input_stream_test.dart @@ -0,0 +1,279 @@ +// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// Objective C support is only available on mac. +@TestOn('mac-os') +library; + +import 'dart:ffi'; +import 'dart:io'; +import 'dart:isolate'; +import 'dart:math'; +import 'dart:typed_data'; + +import 'package:ffi/ffi.dart'; +import 'package:objective_c/objective_c.dart'; +import 'package:objective_c/src/objective_c_bindings_generated.dart'; +import 'package:test/test.dart'; + +Future<(int, Uint8List, bool, NSStreamStatus, NSError?)> read( + NSInputStream stream, int size) async { + // TODO(https://github.com/dart-lang/tools/issues/520): + // Use `Isolate.run`. + + final port = ReceivePort(); + await Isolate.spawn((sendPort) { + using((arena) { + final buffer = arena(size); + final readSize = stream.read_maxLength_(buffer, size); + final data = + Uint8List.fromList(buffer.asTypedList(readSize == -1 ? 0 : readSize)); + sendPort.send(( + readSize, + data, + stream.hasBytesAvailable, + stream.streamStatus, + stream.streamError, + )); + Isolate.current.kill(); + }); + }, port.sendPort); + return await port.first as (int, Uint8List, bool, NSStreamStatus, NSError?); +} + +void main() { + group('NSInputStream', () { + setUpAll(() { + // TODO(https://github.com/dart-lang/native/issues/1068): Remove this. + DynamicLibrary.open('test/objective_c.dylib'); + }); + + group('toNSInputStream', () { + group('empty', () { + late NSInputStream inputStream; + + setUp(() { + inputStream = const Stream>.empty().toNSInputStream(); + }); + + test('initial state', () { + expect( + inputStream.streamStatus, NSStreamStatus.NSStreamStatusNotOpen); + expect(inputStream.streamError, null); + }); + + test('open', () { + inputStream.open(); + expect(inputStream.streamStatus, NSStreamStatus.NSStreamStatusOpen); + expect(inputStream.streamError, null); + }); + + test('read', () async { + inputStream.open(); + final (count, data, hasBytesAvailable, status, error) = + await read(inputStream, 10); + expect(count, 0); + expect(data, isEmpty); + expect(hasBytesAvailable, false); + expect(status, NSStreamStatus.NSStreamStatusAtEnd); + expect(error, isNull); + inputStream.close(); + }); + + test('read without open', () async { + final (count, data, hasBytesAvailable, status, error) = + await read(inputStream, 10); + expect(count, -1); + expect(data, isEmpty); + expect(hasBytesAvailable, false); + expect(status, NSStreamStatus.NSStreamStatusNotOpen); + expect(error, isNull); + }); + + test('close', () { + inputStream.open(); + inputStream.close(); + expect(inputStream.streamStatus, NSStreamStatus.NSStreamStatusClosed); + expect(inputStream.streamError, null); + }); + }); + + group('small stream', () { + late NSInputStream inputStream; + + setUp(() { + inputStream = Stream.fromIterable([ + [1], + [2, 3], + [4, 5, 6] + ]).toNSInputStream(); + }); + + test('initial state', () { + expect( + inputStream.streamStatus, NSStreamStatus.NSStreamStatusNotOpen); + expect(inputStream.streamError, null); + }); + + test('open', () { + inputStream.open(); + expect(inputStream.streamStatus, NSStreamStatus.NSStreamStatusOpen); + expect(inputStream.streamError, null); + }); + + test('partial read', () async { + inputStream.open(); + final (count, data, hasBytesAvailable, status, error) = + await read(inputStream, 5); + expect(count, lessThanOrEqualTo(5)); + expect(count, greaterThanOrEqualTo(1)); + expect(data, [1, 2, 3, 4, 5].sublist(0, count)); + expect(hasBytesAvailable, true); + expect(status, NSStreamStatus.NSStreamStatusOpen); + expect(error, isNull); + }); + + test('full read', () async { + inputStream.open(); + final readData = []; + while (true) { + final (count, data, hasBytesAvailable, status, error) = + await read(inputStream, 6); + readData.addAll(data); + + expect(error, isNull); + if (count == 0) { + expect(hasBytesAvailable, false); + expect(status, NSStreamStatus.NSStreamStatusAtEnd); + expect(readData, [1, 2, 3, 4, 5, 6]); + break; + } + } + }); + + test('read without open', () async { + final (count, data, hasBytesAvailable, status, error) = + await read(inputStream, 10); + expect(count, -1); + expect(data, isEmpty); + expect(hasBytesAvailable, false); + expect(status, NSStreamStatus.NSStreamStatusNotOpen); + expect(error, isNull); + }); + + test('close', () { + inputStream.open(); + inputStream.close(); + expect(inputStream.streamStatus, NSStreamStatus.NSStreamStatusClosed); + expect(inputStream.streamError, null); + }); + }); + }); + + group('large stream', () { + late NSInputStream inputStream; + final streamData = List.generate(100, (x) => List.filled(10000, x)); + final testData = streamData.expand((x) => x).toList(); + + setUp(() { + inputStream = Stream.fromIterable(streamData).toNSInputStream(); + }); + + test('partial read', () async { + inputStream.open(); + final (count, data, hasBytesAvailable, status, error) = + await read(inputStream, 100000); + expect(count, lessThanOrEqualTo(100000)); + expect(count, greaterThanOrEqualTo(1)); + expect(data, testData.sublist(0, count)); + expect(hasBytesAvailable, true); + expect(status, NSStreamStatus.NSStreamStatusOpen); + expect(error, isNull); + }); + + test('full read', () async { + inputStream.open(); + final readData = []; + while (true) { + final (count, data, hasBytesAvailable, status, error) = + await read(inputStream, Random.secure().nextInt(100000)); + + readData.addAll(data); + + expect(error, isNull); + if (count == 0) { + expect(hasBytesAvailable, false); + expect(status, NSStreamStatus.NSStreamStatusAtEnd); + expect(readData, testData); + break; + } + } + }); + }); + + test('error in stream', () async { + late NSInputStream inputStream; + + inputStream = () async* { + yield [1, 2]; + throw const FileSystemException('some exception message'); + }() + .toNSInputStream(); + + inputStream.open(); + final (count1, data1, hasBytesAvailable1, status1, error1) = + await read(inputStream, 10); + expect(count1, 2); + expect(data1, [1, 2]); + expect(hasBytesAvailable1, true); + expect(status1, NSStreamStatus.NSStreamStatusOpen); + expect(error1, isNull); + + final (count2, _, hasBytesAvailable2, status2, error2) = + await read(inputStream, 10); + expect(count2, -1); + expect(hasBytesAvailable2, false); + expect(status2, NSStreamStatus.NSStreamStatusError); + expect( + error2, + isA() + .having((e) => e.localizedDescription.toString(), + 'localizedDescription', contains('some exception message')) + .having((e) => e.domain.toString(), 'domain', 'DartError')); + }); + }); + + group('delegate', () { + late DartInputStreamAdapter inputStream; + + setUp(() { + inputStream = Stream.fromIterable([ + [1, 2, 3], + ]).toNSInputStream() as DartInputStreamAdapter; + }); + + test('default delegate', () async { + expect(inputStream.delegate, inputStream); + inputStream.stream_handleEvent_( + inputStream, NSStreamEvent.NSStreamEventOpenCompleted); + }); + + test('non-self delegate', () async { + final protoBuilder = ObjCProtocolBuilder(); + final events = []; + + NSStreamDelegate.addToBuilder(protoBuilder, + stream_handleEvent_: (stream, event) => events.add(event)); + inputStream.delegate = protoBuilder.build(); + inputStream.stream_handleEvent_( + inputStream, NSStreamEvent.NSStreamEventOpenCompleted); + expect(events, [NSStreamEvent.NSStreamEventOpenCompleted]); + }); + + test('assign to null', () async { + inputStream.delegate = null; + expect(inputStream.delegate, inputStream); + }); + }); +} diff --git a/pkgs/objective_c/test/setup.dart b/pkgs/objective_c/test/setup.dart index 667af5aa6..8a215744f 100644 --- a/pkgs/objective_c/test/setup.dart +++ b/pkgs/objective_c/test/setup.dart @@ -16,6 +16,7 @@ import 'package:args/args.dart'; const cFiles = ['src/objective_c.c', 'src/include/dart_api_dl.c']; const objCFiles = [ + 'src/input_stream_adapter.m', 'src/objective_c.m', 'src/objective_c_bindings_generated.m', 'src/proxy.m',