blob: 20c058baadb7027b948d186aef35d270fbde0106 [file]
// Copyright (c) 2022, 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 'package:jnigen/src/elements/elements.dart';
import 'package:jnigen/src/config/config.dart';
import 'package:jnigen/src/logging/logging.dart';
import 'package:jnigen/src/util/rename_conflict.dart';
import 'symbol_resolver.dart';
import 'common.dart';
final _indent = ' ' * 2;
class DartBindingsGenerator {
// Name for reference in base class.
static const _self = 'reference';
// symbol lookup function for generated code.
static const _jniLookup = 'jniLookup';
// import prefixes
static const ffi = 'ffi.';
static const jni = 'jni.';
static const String _voidPtr = '${ffi}Pointer<${ffi}Void>';
static const String _void = '${ffi}Void';
static const String _jniObject = '${jni}JniObject';
DartBindingsGenerator(this.config, this.resolver);
Config config;
SymbolResolver resolver;
String generateBinding(ClassDecl decl) {
if (!decl.isPreprocessed) {
throw StateError('Java class declaration must be preprocessed before'
'being passed to bindings generator');
}
if (!decl.isIncluded) {
return '';
}
final bindings = _class(decl);
log.finest('generated bindings for class ${decl.binaryName}');
return bindings;
}
String _class(ClassDecl decl) {
final s = StringBuffer();
s.write('/// from: ${decl.binaryName}\n');
s.write(_breakDocComment(decl.javadoc, depth: ''));
final name = _getSimpleName(decl.binaryName);
var superName = _jniObject;
if (decl.superclass != null) {
superName = resolver
.resolve((decl.superclass!.type as DeclaredType).binaryName) ??
_jniObject;
}
s.write('class $name extends $superName {\n');
s.write('$_indent$name.fromRef($_voidPtr ref) : '
'super.fromRef(ref);\n');
s.writeln();
for (var field in decl.fields) {
if (!field.isIncluded) {
continue;
}
try {
s.write(_field(decl, field));
s.writeln();
} on SkipException catch (e) {
log.fine('skip field ${decl.binaryName}#${field.name}: '
'${e.message}');
}
}
for (var method in decl.methods) {
if (!method.isIncluded) {
continue;
}
try {
s.write(_method(decl, method));
s.writeln();
} on SkipException catch (e) {
log.fine('skip field ${decl.binaryName}#${method.name}: '
'${e.message}');
}
}
s.write("}\n");
return s.toString();
}
static final _deleteInstruction =
'$_indent/// The returned object must be deleted after use, '
'by calling the `delete` method.\n';
String _method(ClassDecl c, Method m) {
final name = m.finalName;
final cName = memberNameInC(c, name);
final s = StringBuffer();
final sym = '_$name';
final ffiSig = dartSigForMethod(m, isFfiSig: true);
final dartSig = dartSigForMethod(m, isFfiSig: false);
s.write('${_indent}static final $sym = $_jniLookup'
'<${ffi}NativeFunction<$ffiSig>>("$cName")\n'
'.asFunction<$dartSig>();\n');
// Different logic for constructor and method;
// For constructor, we want return type to be new object.
final returnType = dartOuterType(m.returnType);
s.write('$_indent/// from: ${_originalMethodHeader(m)}\n');
if (!isPrimitive(m.returnType)) {
s.write(_deleteInstruction);
}
s.write(_breakDocComment(m.javadoc));
s.write(_indent);
if (isStaticMethod(m)) {
s.write('static ');
}
if (isCtor(m)) {
final wrapperExpr = '$sym(${_actualArgs(m)})';
final className = _getSimpleName(c.binaryName);
final ctorFnName = name == 'ctor' ? className : '$className.$name';
s.write('$ctorFnName(${_formalArgs(m)}) : '
'super.fromRef($wrapperExpr) { jni.Jni.env.checkException(); }\n');
return s.toString();
}
var wrapperExpr = '$sym(${_actualArgs(m)})';
wrapperExpr = _toDartResult(wrapperExpr, m.returnType, returnType);
final depth = '$_indent$_indent';
s.write('$returnType $name(${_formalArgs(m)}) {');
s.write('${depth}final result__ = $wrapperExpr;');
s.write('${depth}jni.Jni.env.checkException();');
s.write('${depth}return result__;\n$_indent}');
return s.toString();
}
String _formalArgs(Method m) {
final List<String> args = [];
for (var param in m.params) {
args.add('${dartOuterType(param.type)} ${kwRename(param.name)}');
}
return args.join(', ');
}
String _actualArgs(Method m) {
final List<String> args = [if (hasSelfParam(m)) _self];
for (var param in m.params) {
final paramName = kwRename(param.name);
args.add(_toCArg(paramName, param.type));
}
return args.join(', ');
}
String _field(ClassDecl c, Field f) {
final name = f.finalName;
final s = StringBuffer();
void writeDocs({bool writeDeleteInstruction = true}) {
s.write('$_indent/// from: ${_originalFieldDecl(f)}\n');
if (!isPrimitive(f.type) && writeDeleteInstruction) {
s.write(_deleteInstruction);
}
s.write(_breakDocComment(f.javadoc));
}
if (isStaticField(f) && isFinalField(f) && f.defaultValue != null) {
writeDocs(writeDeleteInstruction: false);
s.write('${_indent}static const $name = ${_literal(f.defaultValue)};\n');
return s.toString();
}
final cName = memberNameInC(c, name);
void writeAccessor({bool isSetter = false}) {
final symPrefix = isSetter ? 'set' : 'get';
final sym = '_${symPrefix}_$name';
final ffiSig = dartSigForField(f, isSetter: isSetter, isFfiSig: true);
final dartSig = dartSigForField(f, isSetter: isSetter, isFfiSig: false);
s.write('${_indent}static final $sym = $_jniLookup'
'<${ffi}NativeFunction<$ffiSig>>("${symPrefix}_$cName")\n'
'.asFunction<$dartSig>();\n');
// write original type
writeDocs();
s.write(_indent);
if (isStaticField(f)) s.write('static ');
if (isSetter) {
s.write('set $name(${dartOuterType(f.type)} value) => $sym(');
if (!isStaticField(f)) {
s.write('$_self, ');
}
s.write(_toCArg('value', f.type));
s.write(');\n');
} else {
// getter
final self = isStaticField(f) ? '' : _self;
final outer = dartOuterType(f.type);
final callExpr = '$sym($self)';
final resultExpr = _toDartResult(callExpr, f.type, outer);
s.write('$outer get $name => $resultExpr;\n');
}
}
writeAccessor(isSetter: false);
if (!isFinalField(f)) writeAccessor(isSetter: true);
return s.toString();
}
String _getSimpleName(String binaryName) {
final components = binaryName.split(".");
return components.last.replaceAll("\$", "_");
}
String dartSigForField(Field f,
{bool isSetter = false, required bool isFfiSig}) {
final conv = isFfiSig ? dartFfiType : dartInnerType;
final voidType = isFfiSig ? _void : 'void';
final ref = f.modifiers.contains('static') ? '' : '$_voidPtr, ';
if (isSetter) {
return '$voidType Function($ref${conv(f.type)})';
}
return '${conv(f.type)} Function($ref)';
}
String dartSigForMethod(Method m, {required bool isFfiSig}) {
final conv = isFfiSig ? dartFfiType : dartInnerType;
final argTypes = [if (hasSelfParam(m)) _voidPtr];
for (var param in m.params) {
argTypes.add(conv(param.type));
}
final retType = isCtor(m) ? _voidPtr : conv(m.returnType);
return '$retType Function (${argTypes.join(", ")})';
}
// Type for FFI Function signature
String dartFfiType(TypeUsage t) {
const primitives = {
'byte': 'Int8',
'short': 'Int16',
'char': 'Int16',
'int': 'Int32',
'long': 'Int64',
'float': 'Float',
'double': 'Double',
'void': 'Void',
'boolean': 'Uint8',
};
switch (t.kind) {
case Kind.primitive:
return ffi + primitives[(t.type as PrimitiveType).name]!;
case Kind.typeVariable:
case Kind.wildcard:
throw SkipException(
'Generic type parameters are not supported', t.toJson());
case Kind.array:
case Kind.declared:
return _voidPtr;
}
}
String _dartType(TypeUsage t, {SymbolResolver? resolver}) {
// if resolver == null, looking for inner fn type, type of fn reference
// else looking for outer fn type, that's what user of the library sees.
const primitives = {
'byte': 'int',
'short': 'int',
'char': 'int',
'int': 'int',
'long': 'int',
'float': 'double',
'double': 'double',
'void': 'void',
'boolean': 'bool',
};
switch (t.kind) {
case Kind.primitive:
if (t.name == 'boolean' && resolver == null) return 'int';
return primitives[(t.type as PrimitiveType).name]!;
case Kind.typeVariable:
case Kind.wildcard:
throw SkipException('Not supported: generics');
case Kind.array:
if (resolver != null) {
return _jniObject;
}
return _voidPtr;
case Kind.declared:
if (resolver != null) {
return resolver.resolve((t.type as DeclaredType).binaryName) ??
_jniObject;
}
return _voidPtr;
}
}
String dartInnerType(TypeUsage t) => _dartType(t);
String dartOuterType(TypeUsage t) => _dartType(t, resolver: resolver);
String _literal(dynamic value) {
if (value is String) {
// TODO(#31): escape string literal.
return '"$value"';
}
if (value is int || value is double || value is bool) {
return value.toString();
}
throw SkipException('Not a constant of a known type.');
}
String _originalFieldDecl(Field f) {
final declStmt = '${f.type.shorthand} ${f.name}';
return [...f.modifiers, declStmt].join(' ');
}
String _originalMethodHeader(Method m) {
final args = <String>[];
for (var p in m.params) {
args.add('${p.type.shorthand} ${p.name}');
}
final declStmt = '${m.returnType.shorthand} ${m.name}'
'(${args.join(', ')})';
return [...m.modifiers, declStmt].join(' ');
}
String _toCArg(String name, TypeUsage type) {
if (isPrimitive(type)) {
return type.name == 'boolean' ? '$name ? 1 : 0' : name;
}
return '$name.$_self';
}
String _toDartResult(String expr, TypeUsage type, String dartType) {
if (isPrimitive(type)) {
return type.name == 'boolean' ? '$expr != 0' : expr;
}
return '$dartType.fromRef($expr)';
}
static String _breakDocComment(JavaDocComment? javadoc,
{String depth = ' '}) {
final link = RegExp('{@link ([^{}]+)}');
if (javadoc == null) return '';
final comment = javadoc.comment
.replaceAllMapped(link, (match) => match.group(1) ?? '')
.replaceAll('#', '\\#')
.replaceAll('<p>', '')
.replaceAll('</p>', '\n')
.replaceAll('<b>', '__')
.replaceAll('</b>', '__')
.replaceAll('<em>', '_')
.replaceAll('</em>', '_');
return '$depth///\n'
'$depth/// ${comment.replaceAll('\n', '\n$depth///')}\n';
}
}
class DartPreludes {
static String initFile(String libraryName) => 'import "dart:ffi";\n'
'import "package:jni/internal_helpers_for_jnigen.dart";\n'
'\n'
'final Pointer<T> Function<T extends NativeType>(String sym) '
'jniLookup = ProtectedJniExtensions.initGeneratedLibrary("$libraryName");\n'
'\n';
static const autoGeneratedNotice = '// Autogenerated by jnigen. '
'DO NOT EDIT!\n\n';
static const defaultImports = 'import "dart:ffi" as ffi;\n'
'import "package:jni/jni.dart" as jni;\n\n';
static const defaultLintSuppressions =
'// ignore_for_file: camel_case_types\n'
'// ignore_for_file: non_constant_identifier_names\n'
'// ignore_for_file: constant_identifier_names\n'
'// ignore_for_file: annotate_overrides\n'
'// ignore_for_file: no_leading_underscores_for_local_identifiers\n'
'// ignore_for_file: unused_element\n'
'\n';
static const bindingFileHeaders =
autoGeneratedNotice + defaultLintSuppressions + defaultImports;
}