| // Copyright (c) 2023, 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 'dart:io'; |
| |
| import 'package:meta/meta.dart'; |
| |
| import '../config/config.dart'; |
| import '../config/experiments.dart'; |
| import '../elements/elements.dart'; |
| import '../logging/logging.dart'; |
| import '../util/string_util.dart'; |
| import 'c_generator.dart'; |
| import 'resolver.dart'; |
| import 'visitor.dart'; |
| |
| // Import prefixes. |
| const _jni = 'jni'; |
| const _ffi = 'ffi'; |
| |
| // package:jni types. |
| const _jType = '$_jni.JObjType'; |
| const _jPointer = '$_jni.JObjectPtr'; |
| const _jReference = '$_jni.JReference'; |
| const _jGlobalReference = '$_jni.JGlobalReference'; |
| const _jArray = '$_jni.JArray'; |
| const _jObject = '$_jni.JObject'; |
| const _jResult = '$_jni.JniResult'; |
| |
| // package:ffi types. |
| const _voidPointer = '$_ffi.Pointer<$_ffi.Void>'; |
| |
| // Prefixes and suffixes. |
| const _typeParamPrefix = '\$'; |
| |
| // Misc. |
| const _protectedExtension = 'ProtectedJniExtensions'; |
| const _classRef = '_class'; |
| const _lookup = 'jniLookup'; |
| |
| /// Used for C bindings. |
| const _selfPointer = 'reference.pointer'; |
| |
| /// Used for Dart-only bindings. |
| const _self = 'this'; |
| |
| // Docs. |
| const _releaseInstruction = |
| ' /// The returned object must be released after use, ' |
| 'by calling the [release] method.'; |
| |
| extension on Iterable<String> { |
| /// Similar to [join] but adds the [separator] to the end as well. |
| String delimited([String separator = '']) { |
| return map((e) => '$e$separator').join(); |
| } |
| } |
| |
| /// Encloses [inside] in the middle of [open] and [close] |
| /// if [inside] is not empty. |
| String _encloseIfNotEmpty(String open, String inside, String close) { |
| if (inside == '') return ''; |
| return '$open$inside$close'; |
| } |
| |
| String _newLine({int depth = 0}) { |
| return '\n${' ' * depth}'; |
| } |
| |
| /// Merges two maps. For the same keys, their value lists will be concatenated. |
| /// |
| /// ** After calling this, the original maps might get modified! ** |
| Map<K, List<V>> _mergeMapValues<K, V>(Map<K, List<V>> a, Map<K, List<V>> b) { |
| final merged = <K, List<V>>{}; |
| for (final key in {...a.keys, ...b.keys}) { |
| if (!a.containsKey(key)) { |
| merged[key] = b[key]!; |
| continue; |
| } |
| if (!b.containsKey(key)) { |
| merged[key] = a[key]!; |
| continue; |
| } |
| |
| // Merging the smaller one to the bigger one |
| if (a[key]!.length > b[key]!.length) { |
| merged[key] = a[key]!; |
| merged[key]!.addAll(b[key]!); |
| } else { |
| merged[key] = b[key]!; |
| merged[key]!.addAll(a[key]!); |
| } |
| } |
| return merged; |
| } |
| |
| /// **Naming Convention** |
| /// |
| /// Let's take the following code as an example: |
| /// |
| /// ```dart |
| /// Method definition |
| /// void f<T extends num, U>(JType<T> $T, JType<U> $U, T t, U u) { |
| /// // ... |
| /// } |
| /// f<int, String>($T, $U, t, u); // Calling the Method |
| /// ``` |
| /// |
| /// Here `f` will be replaced according to the place of usage. |
| /// |
| /// * `fArgsDef` refers to `T t, U u` – the arguments in the method |
| /// definition. |
| /// * `fArgsCall` refer to `t, u` – the arguments passed to call the method. |
| /// * `fTypeParamsDef` refers to `<T extends num, U>` – the type parameters |
| /// of the method at the point of definition. |
| /// * `fTypeParamsCall` refers to `<int, String>` – the type parameters when |
| /// calling, or whenever we don't want to have the `extends` keyword. |
| /// * `fTypeClassesDef` refers to `JType<T> $T, JType<U> $U`. |
| /// * `fTypeClassesCall` refers to `$T, $U` when calling the method. |
| class DartGenerator extends Visitor<Classes, Future<void>> { |
| final Config config; |
| |
| DartGenerator(this.config); |
| |
| static const cInitImport = 'import "dart:ffi" as ffi;\n' |
| 'import "package:jni/internal_helpers_for_jnigen.dart";\n'; |
| |
| /// Initialization code for C based bindings. |
| /// |
| /// Should be called once in a package. In package-structured bindings |
| /// this is placed in _init.dart in package root. |
| String get cInitCode => ''' |
| // Auto-generated initialization code. |
| |
| final $_ffi.Pointer<T> Function<T extends $_ffi.NativeType>(String sym) $_lookup = |
| $_protectedExtension.initGeneratedLibrary("${config.outputConfig.cConfig!.libraryName}"); |
| |
| |
| '''; |
| |
| static const autoGeneratedNotice = '// Autogenerated by jnigen. ' |
| 'DO NOT EDIT!\n\n'; |
| static const defaultImports = ''' |
| import "dart:isolate" show ReceivePort; |
| import "dart:ffi" as ffi; |
| import "package:jni/internal_helpers_for_jnigen.dart"; |
| import "package:jni/jni.dart" as jni; |
| |
| '''; |
| |
| // Sort alphabetically. |
| static const defaultLintSuppressions = ''' |
| // ignore_for_file: annotate_overrides |
| // ignore_for_file: camel_case_extensions |
| // ignore_for_file: camel_case_types |
| // ignore_for_file: constant_identifier_names |
| // ignore_for_file: doc_directive_unknown |
| // ignore_for_file: file_names |
| // ignore_for_file: lines_longer_than_80_chars |
| // ignore_for_file: no_leading_underscores_for_local_identifiers |
| // ignore_for_file: non_constant_identifier_names |
| // ignore_for_file: overridden_fields |
| // ignore_for_file: unnecessary_cast |
| // ignore_for_file: unused_element |
| // ignore_for_file: unused_field |
| // ignore_for_file: unused_import |
| // ignore_for_file: unused_local_variable |
| // ignore_for_file: unused_shown_name |
| // ignore_for_file: use_super_parameters |
| |
| '''; |
| static const preImportBoilerplate = |
| autoGeneratedNotice + defaultLintSuppressions + defaultImports; |
| |
| /// Run dart format command on [path]. |
| Future<void> _runDartFormat(String path) async { |
| log.info('Running dart format...'); |
| final formatRes = await Process.run('dart', ['format', path]); |
| // if negative exit code, likely due to an interrupt. |
| if (formatRes.exitCode > 0) { |
| log.fatal('Dart format completed with exit code ${formatRes.exitCode} ' |
| 'This usually means there\'s a syntax error in bindings.\n' |
| 'Please look at the generated files and report a bug: \n' |
| 'https://github.com/dart-lang/native/issues/new?labels=package%3Ajnigen\n'); |
| } |
| } |
| |
| @override |
| Future<void> visit(Classes node) async { |
| final cBased = config.outputConfig.bindingsType == BindingsType.cBased; |
| final root = config.outputConfig.dartConfig.path; |
| final preamble = config.preamble ?? ''; |
| if (config.outputConfig.dartConfig.structure == |
| OutputStructure.singleFile) { |
| final file = File.fromUri(root); |
| await file.create(recursive: true); |
| log.info("Generating ${cBased ? "C + Dart" : "Pure Dart"} Bindings"); |
| final s = file.openWrite(); |
| s.writeln(preamble); |
| s.writeln(autoGeneratedNotice); |
| s.writeln(defaultLintSuppressions); |
| s.writeln(defaultImports); |
| if (cBased) { |
| s.writeln(cInitCode); |
| } |
| final resolver = Resolver( |
| importedClasses: config.importedClasses, |
| currentClass: null, // Single file mode. |
| inputClassNames: node.decls.keys.toSet(), |
| ); |
| final classGenerator = _ClassGenerator(config, s, resolver); |
| for (final classDecl in node.decls.values) { |
| classDecl.accept(classGenerator); |
| } |
| await s.close(); |
| await _runDartFormat(file.path); |
| return; |
| } |
| final files = <String, List<ClassDecl>>{}; |
| final packages = <String, Set<String>>{}; |
| for (final classDecl in node.decls.values) { |
| final fileClass = Resolver.getFileClassName(classDecl.binaryName); |
| |
| files.putIfAbsent(fileClass, () => <ClassDecl>[]); |
| files[fileClass]!.add(classDecl); |
| |
| packages.putIfAbsent(classDecl.packageName, () => {}); |
| packages[classDecl.packageName]!.add(fileClass.split('.').last); |
| } |
| |
| log.info("Using dart root = $root"); |
| const initFileName = '_init.dart'; |
| if (cBased) { |
| final initFileUri = root.resolve(initFileName); |
| final initFile = File.fromUri(initFileUri); |
| await initFile.create(recursive: true); |
| final initStream = initFile.openWrite(); |
| initStream.writeln(preamble); |
| initStream.writeln(cInitImport); |
| initStream.writeln(cInitCode); |
| await initStream.close(); |
| } |
| for (var fileClassName in files.keys) { |
| final relativeFileName = '${fileClassName.replaceAll('.', '/')}.dart'; |
| final dartFileUri = root.resolve(relativeFileName); |
| final dartFile = await File.fromUri(dartFileUri).create(recursive: true); |
| log.fine('$fileClassName -> ${dartFile.path}'); |
| |
| final classesInFile = files[fileClassName]!; |
| final dartFileStream = dartFile.openWrite(); |
| dartFileStream.writeln(preamble); |
| dartFileStream.writeln(autoGeneratedNotice); |
| dartFileStream.writeln(defaultLintSuppressions); |
| dartFileStream.writeln(defaultImports); |
| final s = StringBuffer(); |
| if (cBased) { |
| final initFilePath = ('../' * |
| relativeFileName.codeUnits |
| .where((cu) => cu == '/'.codeUnitAt(0)) |
| .length) + |
| initFileName; |
| s.write('import "$initFilePath";'); |
| } |
| final resolver = Resolver( |
| importedClasses: config.importedClasses, |
| currentClass: fileClassName, |
| inputClassNames: node.decls.keys.toSet(), |
| ); |
| final classGenerator = _ClassGenerator(config, s, resolver); |
| for (final classDecl in classesInFile) { |
| classDecl.accept(classGenerator); |
| } |
| dartFileStream.writeAll(resolver.getImportStrings(), '\n'); |
| dartFileStream.writeln(s.toString()); |
| await dartFileStream.close(); |
| } |
| |
| // write _package.dart export files |
| for (var package in packages.keys) { |
| final dirUri = root.resolve('${package.replaceAll('.', '/')}/'); |
| final exportFileUri = dirUri.resolve("_package.dart"); |
| final exportFile = File.fromUri(exportFileUri); |
| exportFile.createSync(recursive: true); |
| final exports = |
| packages[package]!.map((cls) => 'export "$cls.dart";').join('\n'); |
| exportFile.writeAsStringSync(exports); |
| } |
| await _runDartFormat(root.toFilePath()); |
| log.info('Completed.'); |
| } |
| } |
| |
| /// Generates the Dart class definition, type class, and the array extension. |
| class _ClassGenerator extends Visitor<ClassDecl, void> { |
| final Config config; |
| final StringSink s; |
| final Resolver resolver; |
| |
| _ClassGenerator( |
| this.config, |
| this.s, |
| this.resolver, |
| ); |
| |
| static const staticTypeGetter = 'type'; |
| static const instanceTypeGetter = '\$$staticTypeGetter'; |
| |
| @override |
| void visit(ClassDecl node) { |
| final isDartOnly = |
| config.outputConfig.bindingsType == BindingsType.dartOnly; |
| |
| // Docs. |
| s.write('/// from: ${node.binaryName}\n'); |
| node.javadoc?.accept(_DocGenerator(s, depth: 0)); |
| |
| // Class definition. |
| final name = node.finalName; |
| final superName = node.superclass!.accept(_TypeGenerator(resolver)); |
| final implClassName = '\$${name}Impl'; |
| final typeParamsDef = _encloseIfNotEmpty( |
| '<', |
| node.allTypeParams |
| .accept(const _TypeParamGenerator(withExtends: true)) |
| .join(', '), |
| '>', |
| ); |
| final typeParams = node.allTypeParams |
| .accept(const _TypeParamGenerator(withExtends: false)); |
| final typeParamsCall = _encloseIfNotEmpty( |
| '<', |
| typeParams.map((typeParam) => '$_typeParamPrefix$typeParam').join(', '), |
| '>', |
| ); |
| final staticTypeGetterCallArgs = _encloseIfNotEmpty( |
| '(', |
| typeParams.join(', '), |
| ')', |
| ); |
| final typeClassesDef = typeParams |
| .map((typeParam) => |
| 'final $_jType<$_typeParamPrefix$typeParam> $typeParam;') |
| .join(_newLine(depth: 1)); |
| final ctorTypeClassesDef = typeParams |
| .map((typeParam) => 'this.$typeParam,') |
| .join(_newLine(depth: 2)); |
| final superClass = (node.classDecl.superclass!.type as DeclaredType); |
| final superTypeClassesCall = superClass.classDecl.isObject |
| ? '' |
| : superClass.params |
| .accept(_TypeClassGenerator(resolver)) |
| .map((typeClass) => '${typeClass.name},') |
| .join(_newLine(depth: 2)); |
| s.write(''' |
| class $name$typeParamsDef extends $superName { |
| @override |
| late final $_jType<$name$typeParamsCall> $instanceTypeGetter = $staticTypeGetter$staticTypeGetterCallArgs; |
| |
| $typeClassesDef |
| |
| $name.fromReference( |
| $ctorTypeClassesDef |
| $_jReference reference, |
| ): super.fromReference( |
| $superTypeClassesCall |
| reference |
| ); |
| |
| '''); |
| |
| if (isDartOnly) { |
| final internalName = node.internalName; |
| s.write(''' |
| static final $_classRef = $_jni.JClass.forName(r"$internalName"); |
| |
| '''); |
| } |
| |
| // Static TypeClass getter. |
| s.writeln( |
| ' /// The type which includes information such as the signature of this class.'); |
| final typeClassName = node.typeClassName; |
| if (typeParams.isEmpty) { |
| s.writeln('static const $staticTypeGetter = $typeClassName();'); |
| } else { |
| final staticTypeGetterTypeClassesDef = typeParams |
| .map( |
| (typeParam) => '$_jType<$_typeParamPrefix$typeParam> $typeParam,') |
| .join(_newLine(depth: 2)); |
| final typeClassesCall = |
| typeParams.map((typeParam) => '$typeParam,').join(_newLine(depth: 3)); |
| s.write(''' |
| static $typeClassName$typeParamsCall $staticTypeGetter$typeParamsDef( |
| $staticTypeGetterTypeClassesDef |
| ) { |
| return $typeClassName( |
| $typeClassesCall |
| ); |
| } |
| |
| '''); |
| } |
| |
| // Fields and Methods |
| final fieldGenerator = _FieldGenerator(config, resolver, s); |
| for (final field in node.fields) { |
| field.accept(fieldGenerator); |
| } |
| final methodGenerator = _MethodGenerator(config, resolver, s); |
| for (final method in node.methods) { |
| method.accept(methodGenerator); |
| } |
| |
| // Experimental: Interface implementation. |
| if (node.declKind == DeclKind.interfaceKind && |
| (config.experiments?.contains(Experiment.interfaceImplementation) ?? |
| false)) { |
| s.write(''' |
| /// Maps a specific port to the implemented interface. |
| static final Map<int, $implClassName> _\$impls = {}; |
| '''); |
| s.write(r''' |
| ReceivePort? _$p; |
| |
| static jni.JObjectPtr _$invoke( |
| int port, |
| jni.JObjectPtr descriptor, |
| jni.JObjectPtr args, |
| ) { |
| return _$invokeMethod( |
| port, |
| $MethodInvocation.fromAddresses( |
| 0, |
| descriptor.address, |
| args.address, |
| ), |
| ); |
| } |
| |
| static final ffi.Pointer< |
| ffi.NativeFunction< |
| jni.JObjectPtr Function( |
| ffi.Uint64, jni.JObjectPtr, jni.JObjectPtr)>> |
| _$invokePointer = ffi.Pointer.fromFunction(_$invoke); |
| |
| static ffi.Pointer<ffi.Void> _$invokeMethod( |
| int $p, |
| $MethodInvocation $i, |
| ) { |
| try { |
| final $d = $i.methodDescriptor.toDartString(releaseOriginal: true); |
| final $a = $i.args; |
| '''); |
| final proxyMethodIf = _InterfaceMethodIf(resolver, s); |
| for (final method in node.methods) { |
| method.accept(proxyMethodIf); |
| } |
| s.write(''' |
| } catch (e) { |
| return $_protectedExtension.newDartException(e.toString()); |
| } |
| return jni.nullptr; |
| } |
| |
| factory $name.implement( |
| $implClassName$typeParamsCall \$impl, |
| ) { |
| '''); |
| final typeClassesCall = typeParams |
| .map((typeParam) => '\$impl.$typeParam,') |
| .join(_newLine(depth: 3)); |
| s.write(''' |
| final \$p = ReceivePort(); |
| final \$x = $name.fromReference( |
| $typeClassesCall |
| $_protectedExtension.newPortProxy( |
| r"${node.binaryName}", |
| \$p, |
| _\$invokePointer, |
| ), |
| ).._\$p = \$p; |
| final \$a = \$p.sendPort.nativePort; |
| _\$impls[\$a] = \$impl; |
| \$p.listen((\$m) { |
| if (\$m == null) { |
| _\$impls.remove(\$p.sendPort.nativePort); |
| \$p.close(); |
| return; |
| } |
| final \$i = \$MethodInvocation.fromMessage(\$m as List<dynamic>); |
| final \$r = _\$invokeMethod(\$p.sendPort.nativePort, \$i); |
| $_protectedExtension.returnResult(\$i.result, \$r); |
| }); |
| return \$x; |
| } |
| '''); |
| } |
| |
| // Writing any custom code provided for this class. |
| if (config.customClassBody?.containsKey(node.binaryName) ?? false) { |
| s.writeln(config.customClassBody![node.binaryName]); |
| } |
| |
| // End of Class definition. |
| s.writeln('}'); |
| |
| // Experimental: Interface implementation |
| // Abstract and concrete Impl class definition. |
| // Used for interface implementation. |
| if (node.declKind == DeclKind.interfaceKind && |
| (config.experiments?.contains(Experiment.interfaceImplementation) ?? |
| false)) { |
| // Abstract Impl class. |
| final typeClassGetters = typeParams |
| .map((typeParam) => |
| '$_jType<$_typeParamPrefix$typeParam> get $typeParam;') |
| .join(_newLine(depth: 1)); |
| final abstractFactoryArgs = _encloseIfNotEmpty( |
| '{', |
| [ |
| ...typeParams |
| .map((typeParam) => 'required $_jType<\$$typeParam> $typeParam,'), |
| ...node.methods.accept(_ConcreteImplClosureCtorArg(resolver)), |
| ].join(_newLine(depth: 2)), |
| '}', |
| ); |
| s.write(''' |
| abstract interface class $implClassName$typeParamsDef { |
| factory $implClassName( |
| $abstractFactoryArgs |
| ) = _$implClassName; |
| |
| $typeClassGetters |
| |
| '''); |
| final abstractImplMethod = _AbstractImplMethod(resolver, s); |
| for (final method in node.methods) { |
| method.accept(abstractImplMethod); |
| } |
| s.writeln('}'); |
| |
| // Concrete Impl class. |
| // This is for passing closures instead of implementing the class. |
| final concreteCtorArgs = _encloseIfNotEmpty( |
| '{', |
| [ |
| ...typeParams.map((typeParam) => 'required this.$typeParam,'), |
| ...node.methods.accept(_ConcreteImplClosureCtorArg(resolver)), |
| ].join(_newLine(depth: 2)), |
| '}', |
| ); |
| final setClosures = _encloseIfNotEmpty( |
| ' : ', |
| node.methods |
| .map((method) => '_${method.finalName} = ${method.finalName}') |
| .join(', '), |
| '', |
| ); |
| final typeClassesDef = typeParams.map((typeParam) => ''' |
| @override |
| final $_jType<\$$typeParam> $typeParam; |
| ''').join(_newLine(depth: 1)); |
| s.write(''' |
| |
| class _$implClassName$typeParamsDef implements $implClassName$typeParamsCall { |
| _$implClassName( |
| $concreteCtorArgs |
| )$setClosures; |
| |
| $typeClassesDef |
| |
| '''); |
| final concreteClosureDef = _ConcreteImplClosureDef(resolver, s); |
| for (final method in node.methods) { |
| method.accept(concreteClosureDef); |
| } |
| s.writeln(); |
| final concreteMethodDef = _ConcreteImplMethod(resolver, s); |
| for (final method in node.methods) { |
| method.accept(concreteMethodDef); |
| } |
| s.writeln('}'); |
| } |
| // TypeClass definition. |
| final typeClassesCall = |
| typeParams.map((typeParam) => '$typeParam,').join(_newLine(depth: 2)); |
| final signature = node.signature; |
| final superTypeClass = superClass.accept(_TypeClassGenerator(resolver)); |
| final hashCodeTypeClasses = typeParams.join(', '); |
| final equalityTypeClasses = typeParams |
| .map((typeParam) => ' &&\n $typeParam == other.$typeParam') |
| .join(); |
| final hashCode = typeParams.isEmpty |
| ? '($typeClassName).hashCode' |
| : 'Object.hash($typeClassName, $hashCodeTypeClasses)'; |
| s.write(''' |
| final class $typeClassName$typeParamsDef extends $_jType<$name$typeParamsCall> { |
| $typeClassesDef |
| |
| const $typeClassName( |
| $ctorTypeClassesDef |
| ); |
| |
| @override |
| String get signature => r"$signature"; |
| |
| @override |
| $name$typeParamsCall fromReference($_jReference reference) => $name.fromReference( |
| $typeClassesCall |
| reference |
| ); |
| |
| @override |
| $_jType get superType => ${superTypeClass.name}; |
| |
| @override |
| final superCount = ${node.superCount}; |
| |
| @override |
| int get hashCode => $hashCode; |
| |
| @override |
| bool operator ==(Object other) { |
| return other.runtimeType == ($typeClassName$typeParamsCall) && |
| other is $typeClassName$typeParamsCall$equalityTypeClasses; |
| } |
| } |
| |
| '''); |
| log.finest('Generated bindings for class ${node.binaryName}'); |
| } |
| } |
| |
| /// Generates the JavaDoc comments. |
| class _DocGenerator extends Visitor<JavaDocComment, void> { |
| final StringSink s; |
| final int depth; |
| |
| const _DocGenerator(this.s, {required this.depth}); |
| |
| @override |
| void visit(JavaDocComment node) { |
| final link = RegExp('{@link ([^{}]+)}'); |
| final indent = ' ' * depth; |
| final comments = node.comment |
| .replaceAllMapped(link, (match) => match.group(1) ?? '') |
| .replaceAll('#', '\\#') |
| .replaceAll('<p>', '') |
| .replaceAll('</p>', '\n') |
| .replaceAll('<b>', '__') |
| .replaceAll('</b>', '__') |
| .replaceAll('<em>', '_') |
| .replaceAll('</em>', '_') |
| .split('\n') |
| .join('\n$indent///'); |
| s.write(''' |
| $indent/// |
| $indent/// $comments |
| '''); |
| } |
| } |
| |
| /// Generates the user-facing Dart type. |
| class _TypeGenerator extends TypeVisitor<String> { |
| final Resolver? resolver; |
| |
| const _TypeGenerator(this.resolver); |
| |
| @override |
| String visitArrayType(ArrayType node) { |
| final innerType = node.type; |
| if (innerType.kind == Kind.primitive) { |
| return '$_jArray<$_jni.j${(innerType.type as PrimitiveType).name}>'; |
| } |
| return '$_jArray<${innerType.accept(this)}>'; |
| } |
| |
| @override |
| String visitDeclaredType(DeclaredType node) { |
| if (node.classDecl.binaryName == 'java.lang.Object' || |
| node.classDecl.binaryName == 'java.lang.String') { |
| return '$_jni.${node.classDecl.finalName}'; |
| } |
| |
| // All type parameters of this type |
| final allTypeParams = node.classDecl.allTypeParams |
| .accept(const _TypeParamGenerator(withExtends: false)) |
| .toList(); |
| // The ones that are declared. |
| final definedTypeParams = node.params.accept(this).toList(); |
| |
| // Replacing the declared ones. They come at the end. |
| // The rest will be JObject. |
| if (allTypeParams.length >= node.params.length) { |
| allTypeParams.replaceRange( |
| 0, |
| allTypeParams.length - node.params.length, |
| List.filled( |
| allTypeParams.length - node.params.length, |
| _jObject, |
| ), |
| ); |
| allTypeParams.replaceRange( |
| allTypeParams.length - node.params.length, |
| allTypeParams.length, |
| definedTypeParams, |
| ); |
| } |
| |
| final typeParams = _encloseIfNotEmpty('<', allTypeParams.join(', '), '>'); |
| final prefix = resolver?.resolvePrefix(node.classDecl) ?? ''; |
| return '$prefix${node.classDecl.finalName}$typeParams'; |
| } |
| |
| @override |
| String visitPrimitiveType(PrimitiveType node) { |
| return node.dartType; |
| } |
| |
| @override |
| String visitTypeVar(TypeVar node) { |
| return '$_typeParamPrefix${node.name}'; |
| } |
| |
| @override |
| String visitWildcard(Wildcard node) { |
| // TODO(#141): Support wildcards |
| return super.visitWildcard(node); |
| } |
| |
| @override |
| String visitNonPrimitiveType(ReferredType node) { |
| return _jObject; |
| } |
| } |
| |
| class _TypeClass { |
| final String name; |
| final bool canBeConst; |
| |
| const _TypeClass(this.name, this.canBeConst); |
| } |
| |
| /// Generates the type class. |
| class _TypeClassGenerator extends TypeVisitor<_TypeClass> { |
| final bool isConst; |
| |
| /// Whether or not to return the equivalent boxed type class for primitives. |
| /// Only for interface implemetation. |
| final bool boxPrimitives; |
| |
| /// Whether or not to find the correct type variable from the static map. |
| /// Only for interface implemetation. |
| final bool typeVarFromMap; |
| |
| final Resolver resolver; |
| |
| _TypeClassGenerator( |
| this.resolver, { |
| this.isConst = true, |
| this.boxPrimitives = false, |
| this.typeVarFromMap = false, |
| }); |
| |
| @override |
| _TypeClass visitArrayType(ArrayType node) { |
| final innerTypeClass = node.type.accept(_TypeClassGenerator( |
| resolver, |
| isConst: false, |
| boxPrimitives: false, |
| typeVarFromMap: typeVarFromMap, |
| )); |
| final ifConst = innerTypeClass.canBeConst && isConst ? 'const ' : ''; |
| return _TypeClass( |
| '$ifConst${_jArray}Type(${innerTypeClass.name})', |
| innerTypeClass.canBeConst, |
| ); |
| } |
| |
| @override |
| _TypeClass visitDeclaredType(DeclaredType node) { |
| final allTypeParams = node.classDecl.allTypeParams |
| .accept(const _TypeParamGenerator(withExtends: false)) |
| .toList(); |
| |
| // The ones that are declared. |
| final definedTypeClasses = node.params.accept(_TypeClassGenerator( |
| resolver, |
| isConst: false, |
| boxPrimitives: false, |
| typeVarFromMap: typeVarFromMap, |
| )); |
| |
| // Can be const if all the type parameters are defined and each of them are |
| // also const. |
| final canBeConst = |
| allTypeParams.isEmpty || definedTypeClasses.every((e) => e.canBeConst); |
| |
| // Replacing the declared ones. They come at the end. |
| // The rest will be `JObjectType`. |
| if (allTypeParams.length >= node.params.length) { |
| allTypeParams.replaceRange( |
| 0, |
| allTypeParams.length - node.params.length, |
| List.filled( |
| allTypeParams.length - node.params.length, |
| // Adding const to subexpressions if the entire expression is not const. |
| '${canBeConst ? '' : 'const '}${_jObject}Type()', |
| ), |
| ); |
| allTypeParams.replaceRange( |
| allTypeParams.length - node.params.length, |
| allTypeParams.length, |
| // Adding const to subexpressions if the entire expression is not const. |
| definedTypeClasses.map((param) => |
| '${param.canBeConst && !canBeConst ? 'const ' : ''}${param.name}'), |
| ); |
| } |
| |
| final args = allTypeParams.join(', '); |
| final ifConst = isConst && canBeConst ? 'const ' : ''; |
| final prefix = resolver.resolvePrefix(node.classDecl); |
| return _TypeClass( |
| '$ifConst$prefix${node.classDecl.typeClassName}($args)', |
| canBeConst, |
| ); |
| } |
| |
| @override |
| _TypeClass visitPrimitiveType(PrimitiveType node) { |
| final ifConst = isConst ? 'const ' : ''; |
| final name = boxPrimitives ? 'J${node.boxedName}' : 'j${node.name}'; |
| return _TypeClass('$ifConst$_jni.${name}Type()', true); |
| } |
| |
| @override |
| _TypeClass visitTypeVar(TypeVar node) { |
| if (typeVarFromMap) { |
| return _TypeClass('_\$impls[\$p]!.${node.name}', false); |
| } |
| return _TypeClass(node.name, false); |
| } |
| |
| @override |
| _TypeClass visitWildcard(Wildcard node) { |
| // TODO(#141): Support wildcards |
| return super.visitWildcard(node); |
| } |
| |
| @override |
| _TypeClass visitNonPrimitiveType(ReferredType node) { |
| final ifConst = isConst ? 'const ' : ''; |
| return _TypeClass('$ifConst${_jObject}Type()', true); |
| } |
| } |
| |
| class _TypeParamGenerator extends Visitor<TypeParam, String> { |
| final bool withExtends; |
| |
| const _TypeParamGenerator({required this.withExtends}); |
| |
| @override |
| String visit(TypeParam node) { |
| if (!withExtends) { |
| return node.name; |
| } |
| // TODO(#144): resolve the actual type being extended, if any. |
| return '$_typeParamPrefix${node.name} extends $_jObject'; |
| } |
| } |
| |
| class _JniResultGetter extends TypeVisitor<String> { |
| final Resolver resolver; |
| |
| _JniResultGetter(this.resolver); |
| |
| @override |
| String visitPrimitiveType(PrimitiveType node) { |
| if (node.name == 'void') return 'check()'; |
| if (node.name == 'double') return 'doubleFloat'; |
| if (node.name == 'int') return 'integer'; |
| return node.name; |
| } |
| |
| @override |
| String visitNonPrimitiveType(ReferredType node) { |
| final type = node.accept(_TypeClassGenerator(resolver)).name; |
| return 'object($type)'; |
| } |
| } |
| |
| /// Type signature for C-based bindings. |
| /// |
| /// When `isFFi` is `true`, it generates the ffi type signature and when it's |
| /// false, it generates the dart type signature. |
| /// |
| /// For example `ffi.Int32` is an ffi type signature while `int` is a Dart one: |
| /// ```dart |
| /// jniLookup<ffi.NativeFunction<jni.JniResult Function(ffi.Int32, ffi.Int32)>>( |
| /// "sum") |
| /// .asFunction<jni.JniResult Function(int, int)>(); |
| /// ``` |
| class _TypeSig extends TypeVisitor<String> { |
| final bool isFfi; |
| |
| const _TypeSig({required this.isFfi}); |
| |
| @override |
| String visitPrimitiveType(PrimitiveType node) { |
| if (isFfi) return '$_ffi.${node.ffiType}'; |
| if (node.name == 'boolean') return 'int'; |
| return node.dartType; |
| } |
| |
| @override |
| String visitNonPrimitiveType(ReferredType node) { |
| return _voidPointer; |
| } |
| } |
| |
| class _ToNativeSuffix extends TypeVisitor<String> { |
| const _ToNativeSuffix(); |
| |
| @override |
| String visitPrimitiveType(PrimitiveType node) { |
| if (node.name == 'boolean') { |
| return ' ? 1 : 0'; |
| } |
| return ''; |
| } |
| |
| @override |
| String visitNonPrimitiveType(ReferredType node) { |
| return '.$_selfPointer'; |
| } |
| } |
| |
| class _FieldGenerator extends Visitor<Field, void> { |
| final Config config; |
| final Resolver resolver; |
| final StringSink s; |
| |
| const _FieldGenerator(this.config, this.resolver, this.s); |
| |
| void writeCAccessor(Field node) { |
| final name = node.finalName; |
| final cName = node.accept(const CFieldName()); |
| final ifRef = node.isStatic ? '' : '$_jPointer, '; |
| |
| s.write(''' |
| static final _get_$name = |
| $_lookup<$_ffi.NativeFunction<$_jResult Function($ifRef)>>( |
| "get_$cName") |
| .asFunction<$_jResult Function($ifRef)>(); |
| |
| '''); |
| |
| if (!node.isFinal) { |
| final ffiSig = node.type.accept(const _TypeSig(isFfi: true)); |
| final dartSig = node.type.accept(const _TypeSig(isFfi: false)); |
| s.write(''' |
| static final _set_$name = |
| $_lookup<$_ffi.NativeFunction<$_jResult Function($ifRef$ffiSig)>>( |
| "set_$cName") |
| .asFunction<$_jResult Function($ifRef$dartSig)>(); |
| |
| '''); |
| } |
| } |
| |
| String cGetter(Field node) { |
| final name = node.finalName; |
| final getter = node.type.accept(_JniResultGetter(resolver)); |
| final self = node.isStatic ? '' : _selfPointer; |
| return '_get_$name($self).$getter'; |
| } |
| |
| String cSetter(Field node) { |
| final name = node.finalName; |
| final self = node.isStatic ? '' : '$_selfPointer, '; |
| final toNativeSuffix = node.type.accept(const _ToNativeSuffix()); |
| return '_set_$name(${self}value$toNativeSuffix).check()'; |
| } |
| |
| void writeDartOnlyAccessor(Field node) { |
| final name = node.finalName; |
| final staticOrInstance = node.isStatic ? 'static' : 'instance'; |
| final descriptor = node.type.descriptor; |
| s.write(''' |
| static final _id_$name = |
| $_classRef.${staticOrInstance}FieldId( |
| r"${node.name}", |
| r"$descriptor", |
| ); |
| '''); |
| } |
| |
| String dartOnlyGetter(Field node) { |
| final name = node.finalName; |
| final self = node.isStatic ? _classRef : _self; |
| final type = node.type.accept(_TypeClassGenerator(resolver)).name; |
| return '_id_$name.get($self, $type)'; |
| } |
| |
| String dartOnlySetter(Field node) { |
| final name = node.finalName; |
| final self = node.isStatic ? _classRef : _self; |
| final type = node.type.accept(_TypeClassGenerator(resolver)).name; |
| return '_id_$name.set($self, $type, value)'; |
| } |
| |
| void writeDocs(Field node, {required bool writeReleaseInstructions}) { |
| final originalDecl = '${node.type.shorthand} ${node.name}'; |
| s.writeln(' /// from: ${node.modifiers.join(' ')} $originalDecl'); |
| if (node.type.kind != Kind.primitive && writeReleaseInstructions) { |
| s.writeln(_releaseInstruction); |
| } |
| node.javadoc?.accept(_DocGenerator(s, depth: 1)); |
| } |
| |
| @override |
| void visit(Field node) { |
| final isCBased = config.outputConfig.bindingsType == BindingsType.cBased; |
| |
| // Check if it should be a `static const` getter. |
| if (node.isFinal && node.isStatic && node.defaultValue != null) { |
| final name = node.finalName; |
| final value = node.defaultValue!; |
| if (value is num || value is bool) { |
| writeDocs(node, writeReleaseInstructions: false); |
| s.writeln(' static const $name = $value;'); |
| return; |
| } |
| } |
| |
| // Accessors. |
| (isCBased ? writeCAccessor : writeDartOnlyAccessor)(node); |
| |
| // Getter docs. |
| writeDocs(node, writeReleaseInstructions: true); |
| |
| final name = node.finalName; |
| final ifStatic = node.isStatic ? 'static ' : ''; |
| final type = node.type.accept(_TypeGenerator(resolver)); |
| s.write('$ifStatic$type get $name => '); |
| s.write((isCBased ? cGetter : dartOnlyGetter)(node)); |
| s.writeln(';\n'); |
| if (!node.isFinal) { |
| // Setter docs. |
| writeDocs(node, writeReleaseInstructions: true); |
| |
| s.write('${ifStatic}set $name($type value) => '); |
| s.write((isCBased ? cSetter : dartOnlySetter)(node)); |
| s.writeln(';\n'); |
| } |
| } |
| } |
| |
| class _MethodTypeSig extends Visitor<Method, String> { |
| final bool isFfi; |
| |
| const _MethodTypeSig({required this.isFfi}); |
| |
| @override |
| String visit(Method node) { |
| final args = [ |
| if (!node.isCtor && !node.isStatic) _voidPointer, |
| ...node.params.map((param) => param.type).accept( |
| _TypeSig(isFfi: isFfi), |
| ) |
| ].join(', '); |
| return '$_jResult Function($args)'; |
| } |
| } |
| |
| /// Generates Dart bindings for Java methods. |
| class _MethodGenerator extends Visitor<Method, void> { |
| final Config config; |
| final Resolver resolver; |
| final StringSink s; |
| |
| const _MethodGenerator(this.config, this.resolver, this.s); |
| |
| void writeCAccessor(Method node) { |
| final name = node.finalName; |
| final cName = node.accept(const CMethodName()); |
| final ffiSig = node.accept(const _MethodTypeSig(isFfi: true)); |
| final dartSig = node.accept(const _MethodTypeSig(isFfi: false)); |
| s.write(''' |
| static final _$name = |
| $_lookup<$_ffi.NativeFunction<$ffiSig>>("$cName") |
| .asFunction<$dartSig>(); |
| |
| '''); |
| } |
| |
| void writeDartOnlyAccessor(Method node) { |
| final name = node.finalName; |
| final kind = node.isCtor |
| ? 'constructor' |
| : node.isStatic |
| ? 'staticMethod' |
| : 'instanceMethod'; |
| final descriptor = node.descriptor; |
| s.write(''' |
| static final _id_$name = $_classRef.${kind}Id( |
| '''); |
| if (!node.isCtor) s.writeln(' r"${node.name}",'); |
| s.write(''' |
| r"$descriptor", |
| ); |
| '''); |
| } |
| |
| bool isSuspendFun(Method node) { |
| return node.asyncReturnType != null; |
| } |
| |
| String cCtor(Method node) { |
| final name = node.finalName; |
| final params = |
| node.params.accept(const _ParamCall(isCBased: true)).join(', '); |
| return '_$name($params).reference'; |
| } |
| |
| String dartOnlyCtor(Method node) { |
| final name = node.finalName; |
| final params = |
| node.params.accept(const _ParamCall(isCBased: false)).join(', '); |
| return '_id_$name($_classRef, referenceType, [$params])'; |
| } |
| |
| String cMethodCall(Method node) { |
| final name = node.finalName; |
| final params = [ |
| if (!node.isStatic) _selfPointer, |
| ...node.params.accept(const _ParamCall(isCBased: true)), |
| ].join(', '); |
| final resultGetter = node.returnType.accept(_JniResultGetter(resolver)); |
| return '_$name($params).$resultGetter'; |
| } |
| |
| String dartOnlyMethodCall(Method node) { |
| final name = node.finalName; |
| final self = node.isStatic ? _classRef : _self; |
| final params = |
| node.params.accept(const _ParamCall(isCBased: false)).join(', '); |
| final type = node.returnType.accept(_TypeClassGenerator(resolver)).name; |
| return '_id_$name($self, $type, [$params])'; |
| } |
| |
| @override |
| void visit(Method node) { |
| final isCBased = config.outputConfig.bindingsType == BindingsType.cBased; |
| |
| // Accessors |
| (isCBased ? writeCAccessor : writeDartOnlyAccessor)(node); |
| |
| // Docs |
| s.write(' /// from: '); |
| s.writeAll(node.modifiers.map((m) => '$m ')); |
| s.write('${node.returnType.shorthand} ${node.name}('); |
| s.writeAll(node.params.map((p) => '${p.type.shorthand} ${p.name}'), ', '); |
| s.writeln(')'); |
| if (node.returnType.kind != Kind.primitive || node.isCtor) { |
| s.writeln(_releaseInstruction); |
| } |
| node.javadoc?.accept(_DocGenerator(s, depth: 1)); |
| |
| // Used for inferring the type parameter from the given parameters. |
| final typeLocators = node.params |
| .accept(_ParamTypeLocator(resolver: resolver)) |
| .fold(<String, List<String>>{}, _mergeMapValues).map( |
| (key, value) => MapEntry( |
| key, |
| _encloseIfNotEmpty( |
| '[', |
| value.delimited(', '), |
| ']', |
| ), |
| )); |
| |
| bool isRequired(TypeParam typeParam) { |
| return (typeLocators[typeParam.name] ?? '').isEmpty; |
| } |
| |
| final typeInference = |
| (node.isCtor ? node.classDecl.allTypeParams : node.typeParams) |
| .where((tp) => !isRequired(tp)) |
| .map((tp) => tp.name) |
| .map((tp) => '$tp ??= $_jni.lowestCommonSuperType' |
| '(${typeLocators[tp]}) as $_jType<$_typeParamPrefix$tp>;') |
| .join(_newLine(depth: 2)); |
| |
| if (node.isCtor) { |
| final className = node.classDecl.finalName; |
| final name = node.finalName; |
| final ctorName = name == 'new0' ? className : '$className.$name'; |
| final paramsDef = node.params.accept(_ParamDef(resolver)).delimited(', '); |
| final typeClassDef = _encloseIfNotEmpty( |
| '{', |
| node.classDecl.allTypeParams |
| .map((typeParam) => typeParam |
| .accept(_CtorTypeClassDef(isRequired: isRequired(typeParam)))) |
| .delimited(', '), |
| '}', |
| ); |
| final typeClassCall = node.classDecl.allTypeParams |
| .map((typeParam) => |
| typeParam.accept(const _TypeParamGenerator(withExtends: false))) |
| .delimited(', '); |
| |
| final ctorExpr = (isCBased ? cCtor : dartOnlyCtor)(node); |
| s.write(''' |
| factory $ctorName($paramsDef$typeClassDef) { |
| $typeInference |
| return ${node.classDecl.finalName}.fromReference( |
| $typeClassCall |
| $ctorExpr |
| ); |
| } |
| |
| '''); |
| return; |
| } |
| |
| final name = node.finalName; |
| final returnType = isSuspendFun(node) |
| ? 'Future<${node.asyncReturnType!.accept(_TypeGenerator(resolver))}>' |
| : node.returnType.accept(_TypeGenerator(resolver)); |
| final returnTypeClass = (node.asyncReturnType ?? node.returnType) |
| .accept(_TypeClassGenerator(resolver)) |
| .name; |
| final ifStatic = node.isStatic ? 'static ' : ''; |
| final defArgs = node.params.accept(_ParamDef(resolver)).toList(); |
| final typeClassDef = _encloseIfNotEmpty( |
| '{', |
| node.typeParams |
| .map((typeParam) => typeParam.accept(_MethodTypeClassDef( |
| isRequired: isRequired(typeParam), |
| ))) |
| .delimited(', '), |
| '}', |
| ); |
| final typeParamsDef = _encloseIfNotEmpty( |
| '<', |
| node.typeParams |
| .accept(const _TypeParamGenerator(withExtends: true)) |
| .join(', '), |
| '>', |
| ); |
| if (isSuspendFun(node)) { |
| defArgs.removeLast(); |
| } |
| final params = defArgs.delimited(', '); |
| s.write(' $ifStatic$returnType $name$typeParamsDef($params$typeClassDef)'); |
| final callExpr = (isCBased ? cMethodCall : dartOnlyMethodCall)(node); |
| if (isSuspendFun(node)) { |
| final returningType = |
| node.asyncReturnType!.accept(_TypeClassGenerator(resolver)).name; |
| s.write('''async { |
| $typeInference |
| final \$p = ReceivePort(); |
| final \$c = $_jObject.fromReference($_protectedExtension.newPortContinuation(\$p)); |
| $callExpr; |
| final \$o = $_jGlobalReference($_jPointer.fromAddress(await \$p.first)); |
| final \$k = $returnTypeClass.jClass.reference.pointer; |
| if (!$_jni.Jni.env.IsInstanceOf(\$o.pointer, \$k)) { |
| throw "Failed"; |
| } |
| return $returningType.fromReference(\$o); |
| } |
| |
| '''); |
| } else { |
| final returning = returnType == 'void' ? callExpr : 'return $callExpr'; |
| s.writeln('''{ |
| $typeInference |
| $returning; |
| } |
| '''); |
| } |
| } |
| } |
| |
| /// Generates the method type param definition. |
| /// |
| /// For example `required JObjType<T> $T` in: |
| /// ```dart |
| /// void bar(..., {required JObjType<T> $T}) => ... |
| /// ``` |
| class _MethodTypeClassDef extends Visitor<TypeParam, String> { |
| final bool isRequired; |
| |
| const _MethodTypeClassDef({required this.isRequired}); |
| |
| @override |
| String visit(TypeParam node) { |
| return '${isRequired ? 'required ' : ''}$_jType' |
| '<$_typeParamPrefix${node.name}>${isRequired ? '' : '?'} ${node.name}'; |
| } |
| } |
| |
| /// Generates the class type param definition. Used only in constructors. |
| /// |
| /// For example `required this.$T` in: |
| /// ```dart |
| /// class Foo { |
| /// final JObjType<T> $T; |
| /// Foo(..., {required this.$T}) => ... |
| /// } |
| /// ``` |
| class _CtorTypeClassDef extends Visitor<TypeParam, String> { |
| final bool isRequired; |
| |
| const _CtorTypeClassDef({required this.isRequired}); |
| |
| @override |
| String visit(TypeParam node) { |
| return '${isRequired ? 'required ' : ''} $_jType' |
| '<$_typeParamPrefix${node.name}>${isRequired ? '' : '?'} ${node.name}'; |
| } |
| } |
| |
| /// Method parameter's definition. |
| /// |
| /// For example `Foo foo` in: |
| /// ```dart |
| /// void bar(Foo foo) => ... |
| /// ``` |
| class _ParamDef extends Visitor<Param, String> { |
| final Resolver resolver; |
| |
| const _ParamDef(this.resolver); |
| |
| @override |
| String visit(Param node) { |
| final type = node.type.accept(_TypeGenerator(resolver)); |
| return '$type ${node.finalName}'; |
| } |
| } |
| |
| /// Method parameter used in calling the native method. |
| /// |
| /// For example `foo.reference.pointer` in: |
| /// ```dart |
| /// void bar(Foo foo) => _bar(foo.reference.pointer); |
| /// ``` |
| class _ParamCall extends Visitor<Param, String> { |
| final bool isCBased; |
| |
| const _ParamCall({required this.isCBased}); |
| |
| @override |
| String visit(Param node) { |
| final nativeSuffix = node.type.accept(const _ToNativeSuffix()); |
| final paramCall = '${node.finalName}$nativeSuffix'; |
| if (!isCBased) { |
| // We need to wrap [paramCall] in the appropriate wrapper class. |
| return node.type.accept(_JValueWrapper(paramCall)); |
| } |
| return paramCall; |
| } |
| } |
| |
| /// Wraps the parameter in the appropriate JValue wrapper class. |
| /// |
| /// For instance, `int` in Dart can be mapped to `long` or `int` or ... in Java. |
| /// The wrapper class is how we identify the type in pure dart bindings. |
| class _JValueWrapper extends TypeVisitor<String> { |
| final String param; |
| |
| _JValueWrapper(this.param); |
| |
| @override |
| String visitNonPrimitiveType(ReferredType node) { |
| return param; |
| } |
| |
| @override |
| String visitPrimitiveType(PrimitiveType node) { |
| if (node.name == 'long' || |
| node.name == 'double' || |
| node.name == 'boolean') { |
| return param; |
| } |
| return '$_jni.JValue${node.name.capitalize()}($param)'; |
| } |
| } |
| |
| /// A pair of [StringBuffer]s that can create an expression from the outer layer |
| /// inwards. |
| /// |
| /// For example: |
| /// ```dart |
| /// final buffer = OutsideInBuffer(); // asterisk (*) is used to show the middle |
| /// buffer.appendLeft('f('); // f(* |
| /// buffer.prependRight('x)'); // f(*x) |
| /// buffer.appendLeft('g('); // f(g(*x) |
| /// buffer.prependRight('y) + '); // f(g(*y) + x) |
| /// buffer.toString(); // f(g(y) + x) |
| /// ``` |
| @visibleForTesting |
| class OutsideInBuffer { |
| final StringBuffer _leftBuffer; |
| final StringBuffer _rightBuffer; |
| |
| OutsideInBuffer() |
| : _leftBuffer = StringBuffer(), |
| _rightBuffer = StringBuffer(); |
| |
| void prependRight(Object? object) { |
| final s = object.toString(); |
| for (var i = 0; i < s.length; ++i) { |
| _rightBuffer.write(s[s.length - i - 1]); |
| } |
| } |
| |
| void appendLeft(Object? object) { |
| _leftBuffer.write(object); |
| } |
| |
| @override |
| String toString() { |
| return _leftBuffer.toString() + _rightBuffer.toString().reversed; |
| } |
| } |
| |
| /// The ways to locate each type parameter. |
| /// |
| /// For example in `JArray<JMap<$T, $T>> a`, `T` can be retreived using |
| /// ```dart |
| /// ((((a.$type as jni.JArrayType).elementType) as $JMapType).K) as jni.JObjType<$T> |
| /// ``` |
| /// and |
| /// ```dart |
| /// ((((a.$type as jni.JArrayType).elementType) as $JMapType).V) as jni.JObjType<$T> |
| /// ``` |
| class _ParamTypeLocator extends Visitor<Param, Map<String, List<String>>> { |
| final Resolver resolver; |
| |
| _ParamTypeLocator({required this.resolver}); |
| |
| @override |
| Map<String, List<String>> visit(Param node) { |
| return node.type.accept(_TypeVarLocator(resolver: resolver)).map( |
| (key, value) => MapEntry( |
| key, |
| value |
| .map((e) => |
| (e..appendLeft('${node.finalName}.\$type')).toString()) |
| .toList(), |
| ), |
| ); |
| } |
| } |
| |
| class _TypeVarLocator extends TypeVisitor<Map<String, List<OutsideInBuffer>>> { |
| final Resolver resolver; |
| |
| _TypeVarLocator({required this.resolver}); |
| |
| @override |
| Map<String, List<OutsideInBuffer>> visitNonPrimitiveType(ReferredType node) { |
| return {}; |
| } |
| |
| @override |
| Map<String, List<OutsideInBuffer>> visitWildcard(Wildcard node) { |
| // TODO(#141): Support wildcards |
| return super.visitWildcard(node); |
| } |
| |
| @override |
| Map<String, List<OutsideInBuffer>> visitTypeVar(TypeVar node) { |
| return { |
| node.name: [ |
| OutsideInBuffer(), |
| ], |
| }; |
| } |
| |
| @override |
| Map<String, List<OutsideInBuffer>> visitDeclaredType(DeclaredType node) { |
| if (node.classDecl.isObject) { |
| return {}; |
| } |
| final offset = node.classDecl.allTypeParams.length - node.params.length; |
| final result = <String, List<OutsideInBuffer>>{}; |
| final prefix = resolver.resolvePrefix(node.classDecl); |
| final typeClass = '$prefix${node.classDecl.typeClassName}'; |
| for (var i = 0; i < node.params.length; ++i) { |
| final typeParam = node.classDecl.allTypeParams[i + offset].name; |
| final exprs = node.params[i].accept(this); |
| for (final expr in exprs.entries) { |
| for (final buffer in expr.value) { |
| buffer.appendLeft('('); |
| buffer.prependRight(' as $typeClass).$typeParam'); |
| result[expr.key] = (result[expr.key] ?? [])..add(buffer); |
| } |
| } |
| } |
| return result; |
| } |
| |
| @override |
| Map<String, List<OutsideInBuffer>> visitArrayType(ArrayType node) { |
| final exprs = node.type.accept(this); |
| for (final e in exprs.values.expand((i) => i)) { |
| e.appendLeft('(('); |
| e.prependRight(' as ${_jArray}Type).elementType as $_jType)'); |
| } |
| return exprs; |
| } |
| |
| @override |
| Map<String, List<OutsideInBuffer>> visitPrimitiveType(PrimitiveType node) { |
| return {}; |
| } |
| } |
| |
| /// Method defintion for Impl abstract class used for interface implementation. |
| class _AbstractImplMethod extends Visitor<Method, void> { |
| final Resolver resolver; |
| final StringSink s; |
| |
| _AbstractImplMethod(this.resolver, this.s); |
| |
| @override |
| void visit(Method node) { |
| final returnType = node.returnType.accept(_TypeGenerator(resolver)); |
| final name = node.finalName; |
| final args = node.params.accept(_ParamDef(resolver)).join(', '); |
| s.writeln(' $returnType $name($args);'); |
| } |
| } |
| |
| /// Closure defintion for concrete Impl class used for interface implementation. |
| class _ConcreteImplClosureDef extends Visitor<Method, void> { |
| final Resolver resolver; |
| final StringSink s; |
| |
| _ConcreteImplClosureDef(this.resolver, this.s); |
| |
| @override |
| void visit(Method node) { |
| final returnType = node.returnType.accept(_TypeGenerator(resolver)); |
| final name = node.finalName; |
| final args = node.params.accept(_ParamDef(resolver)).join(', '); |
| s.writeln(' final $returnType Function($args) _$name;'); |
| } |
| } |
| |
| /// Closure argument for concrete Impl class constructor. |
| /// Used for interface implementation. |
| class _ConcreteImplClosureCtorArg extends Visitor<Method, String> { |
| final Resolver resolver; |
| |
| _ConcreteImplClosureCtorArg(this.resolver); |
| |
| @override |
| String visit(Method node) { |
| final returnType = node.returnType.accept(_TypeGenerator(resolver)); |
| final name = node.finalName; |
| final args = node.params.accept(_ParamDef(resolver)).join(', '); |
| return 'required $returnType Function($args) $name,'; |
| } |
| } |
| |
| /// Method defintion for concrete Impl class used for interface implementation. |
| class _ConcreteImplMethod extends Visitor<Method, void> { |
| final Resolver resolver; |
| final StringSink s; |
| |
| _ConcreteImplMethod(this.resolver, this.s); |
| |
| @override |
| void visit(Method node) { |
| final returnType = node.returnType.accept(_TypeGenerator(resolver)); |
| final name = node.finalName; |
| final argsDef = node.params.accept(_ParamDef(resolver)).join(', '); |
| final argsCall = node.params.map((param) => param.finalName).join(', '); |
| s.write(''' |
| $returnType $name($argsDef) { |
| return _$name($argsCall); |
| }'''); |
| } |
| } |
| |
| /// The if statement to check which method has been called from the proxy class. |
| class _InterfaceMethodIf extends Visitor<Method, void> { |
| final Resolver resolver; |
| final StringSink s; |
| |
| _InterfaceMethodIf(this.resolver, this.s); |
| |
| @override |
| void visit(Method node) { |
| final isVoid = node.returnType.name == 'void'; |
| final signature = node.javaSig; |
| final saveResult = isVoid ? '' : 'final \$r = '; |
| final name = node.finalName; |
| s.write(''' |
| if (\$d == r"$signature") { |
| ${saveResult}_\$impls[\$p]!.$name( |
| '''); |
| for (var i = 0; i < node.params.length; ++i) { |
| node.params[i].accept(_InterfaceParamCast(resolver, s, paramIndex: i)); |
| } |
| const returnBox = _InterfaceReturnBox(); |
| s.write(''' |
| ); |
| return ${node.returnType.accept(returnBox)}; |
| } |
| '''); |
| } |
| } |
| |
| /// Generates casting to the correct parameter type from the list of JObject |
| /// arguments received from the call to the proxy class. |
| class _InterfaceParamCast extends Visitor<Param, void> { |
| final Resolver resolver; |
| final StringSink s; |
| final int paramIndex; |
| |
| _InterfaceParamCast( |
| this.resolver, |
| this.s, { |
| required this.paramIndex, |
| }); |
| |
| @override |
| void visit(Param node) { |
| final typeClass = node.type |
| .accept(_TypeClassGenerator( |
| resolver, |
| boxPrimitives: true, |
| typeVarFromMap: true, |
| )) |
| .name; |
| s.write('\$a[$paramIndex].castTo($typeClass, releaseOriginal: true)'); |
| if (node.type.kind == Kind.primitive) { |
| // Convert to Dart type. |
| final name = (node.type.type as PrimitiveType).name; |
| s.write('.${name}Value(releaseOriginal: true)'); |
| } |
| s.writeln(','); |
| } |
| } |
| |
| /// Boxes the returned primitive value into the correct Boxed type. |
| /// Only returns the reference for non primitive types. |
| /// Returns null for void. |
| /// |
| /// Since Dart doesn't know that this global reference is still used, it might |
| /// garbage collect it via [NativeFinalizer] thus making it invalid. |
| /// This passes the ownership to Java using [setAsReleased]. |
| /// |
| /// `toPointer` detaches the object from the [NativeFinalizer] and Java |
| /// will clean up the global reference afterwards. |
| /// |
| /// For example `$r.toJInteger().reference.toPointer()` when the return |
| /// type is `integer`. |
| class _InterfaceReturnBox extends TypeVisitor<String> { |
| const _InterfaceReturnBox(); |
| |
| @override |
| String visitNonPrimitiveType(ReferredType node) { |
| // Casting is done to create a new global reference. The user might |
| // use the original reference elsewhere and so the original object |
| // should not be [setAsReleased]. |
| return '(\$r as $_jObject).castTo(const ${_jObject}Type()).reference.toPointer()'; |
| } |
| |
| @override |
| String visitPrimitiveType(PrimitiveType node) { |
| if (node.name == 'void') { |
| return '$_jni.nullptr'; |
| } |
| return '$_jni.J${node.boxedName}(\$r).reference.toPointer()'; |
| } |
| } |