blob: eeac075a8e22c4f09072a89a4459a349eb8c6e86 [file] [log] [blame]
// 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:ffi';
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';
const _jThrowable = '$_jni.JThrowablePtr';
// 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;
final bool isCBased;
const _TypeSig({required this.isFfi, required this.isCBased});
@override
String visitPrimitiveType(PrimitiveType node) {
if (!isCBased && isFfi) {
// TODO(https://github.com/dart-lang/sdk/issues/55471): Once this lands in
// the stable, use the actual types instead.
if (node.name == 'float' || node.name == 'double') {
return '$_ffi.Double';
} else {
return '$_ffi.Int64';
}
}
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, isCBased: true));
final dartSig =
node.type.accept(const _TypeSig(isFfi: false, isCBased: true));
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;
final bool isCBased;
const _MethodTypeSig({required this.isFfi, required this.isCBased});
@override
String visit(Method node) {
final callParams = node.params
.map((param) => param.type)
.accept(_TypeSig(isFfi: isFfi, isCBased: isCBased))
.join(', ');
final args = [
if ((!node.isCtor && !node.isStatic) || !isCBased) _voidPointer,
if (!isCBased) '$_jni.JMethodIDPtr',
!isCBased && isFfi && callParams.isNotEmpty
? '$_ffi.VarArgs<($callParams${node.params.length == 1 ? ',' : ''})>'
: callParams
].join(', ');
final isCtor = node.isCtor;
final isVoid = node.returnType.name == 'void';
final returnType = !isCBased && !isCtor && isVoid ? _jThrowable : _jResult;
return '$returnType 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, isCBased: true));
final dartSig =
node.accept(const _MethodTypeSig(isFfi: false, isCBased: true));
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",
);
''');
final ffiSig =
node.accept(const _MethodTypeSig(isFfi: true, isCBased: false));
final dartSig =
node.accept(const _MethodTypeSig(isFfi: false, isCBased: false));
final methodName = node.accept(const _CallMethodName());
s.write('''
static final _$name = $_protectedExtension
.lookup<$_ffi.NativeFunction<$ffiSig>>("$methodName")
.asFunction<$dartSig>();
''');
}
bool isSuspendFun(Method node) {
return node.asyncReturnType != null;
}
String cCtor(Method node) {
final name = node.finalName;
final params = node.params.accept(const _ParamCall()).join(', ');
return '_$name($params).reference';
}
String dartOnlyCtor(Method node) {
final name = node.finalName;
final params = [
'$_classRef.reference.pointer',
'_id_$name as $_jni.JMethodIDPtr',
...node.params.accept(const _ParamCall()),
].join(', ');
return '_$name($params).reference';
}
String cMethodCall(Method node) {
final name = node.finalName;
final params = [
if (!node.isStatic) _selfPointer,
...node.params.accept(const _ParamCall()),
].join(', ');
final resultGetter = node.returnType.accept(_JniResultGetter(resolver));
return '_$name($params).$resultGetter';
}
String dartOnlyMethodCall(Method node) {
final name = node.finalName;
final params = [
node.isStatic ? '$_classRef.reference.pointer' : _selfPointer,
'_id_$name as $_jni.JMethodIDPtr',
...node.params.accept(const _ParamCall()),
].join(', ');
final resultGetter = node.returnType.accept(_JniResultGetter(resolver));
return '_$name($params).$resultGetter';
}
@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> {
const _ParamCall();
@override
String visit(Param node) {
final nativeSuffix = node.type.accept(const _ToNativeSuffix());
final paramCall = '${node.finalName}$nativeSuffix';
return paramCall;
}
}
/// 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()';
}
}
class _CallMethodName extends Visitor<Method, String> {
const _CallMethodName();
@override
String visit(Method node) {
if (node.isCtor) {
return 'globalEnv_NewObject';
}
final String type;
if (node.returnType.kind == Kind.primitive) {
type = (node.returnType.type as PrimitiveType).name.capitalize();
} else {
type = 'Object';
}
return 'globalEnv_Call${node.isStatic ? 'Static' : ''}${type}Method';
}
}