blob: e9d58c04517a3c71e75bc14ecc907b90b37f788d [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 'symbol_resolver.dart';
import 'common.dart';
class CBasedDartBindingsGenerator extends BindingsGenerator {
static const selfPointer = BindingsGenerator.selfPointer;
/// Symbol lookup function for generated code.
static const lookup = 'jniLookup';
// import prefixes
static const ffi = BindingsGenerator.ffi;
static const jni = BindingsGenerator.jni;
static final indent = ' ' * 2;
static const voidPointer = BindingsGenerator.voidPointer;
static const ffiVoidType = BindingsGenerator.ffiVoidType;
static const jniObjectType = BindingsGenerator.jniObjectType;
CBasedDartBindingsGenerator(this.config);
final Config config;
@override
String generateBindings(ClassDecl decl, SymbolResolver resolver) {
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, resolver);
log.finest('generated bindings for class ${decl.binaryName}');
return bindings;
}
String _class(ClassDecl decl, SymbolResolver resolver) {
final s = StringBuffer();
s.write('/// from: ${decl.binaryName}\n');
s.write(breakDocComment(decl.javadoc, depth: ''));
s.write(dartClassDefinition(decl, resolver));
s.write(dartStaticTypeGetter(decl));
for (var field in decl.fields) {
if (!field.isIncluded) {
continue;
}
try {
s.write(_field(decl, field, resolver));
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, resolver));
s.writeln();
} on SkipException catch (e) {
log.fine('skip field ${decl.binaryName}#${method.name}: '
'${e.message}');
}
}
s.write("}\n");
s.write(dartTypeClass(decl));
s.write(dartArrayExtension(decl));
return s.toString();
}
String _method(ClassDecl c, Method m, SymbolResolver resolver) {
final name = m.finalName;
final cName = getMemberNameInC(c, name);
final s = StringBuffer();
final sym = '_$name';
final ffiSig = dartSigForMethod(m, isFfiSig: true);
final dartSig = dartSigForMethod(m, isFfiSig: false);
final returnType = getDartOuterType(m.returnType, resolver);
final returnTypeClass = getDartTypeClass(m.returnType, resolver);
final ifStaticMethod = isStaticMethod(m) ? 'static' : '';
// Load corresponding C method.
s.write('${indent}static final $sym = $lookup'
'<${ffi}NativeFunction<$ffiSig>>("$cName")\n'
'.asFunction<$dartSig>();\n');
// Different logic for constructor and method;
// For constructor, we want return type to be new object.
s.write('$indent/// from: ${getOriginalMethodHeader(m)}\n');
if (!isPrimitive(m.returnType)) {
s.write(BindingsGenerator.deleteInstruction);
}
s.write(breakDocComment(m.javadoc));
s.write(indent);
if (isCtor(m)) {
final className = c.finalName;
final ctorFnName = name == 'ctor' ? className : '$className.$name';
final wrapperExpr = '$sym(${actualArgs(m)})';
s.write(
'$ctorFnName(${getFormalArgs(c, m, resolver)}) : '
'super.fromRef(${dartSuperArgs(c, resolver)}$wrapperExpr.object);\n',
);
return s.toString();
}
final resultGetter = getJValueAccessor(m.returnType);
var wrapperExpr = '$sym(${actualArgs(m)}).$resultGetter';
wrapperExpr = toDartResult(wrapperExpr, m.returnType, returnTypeClass);
final typeParamsWithExtend =
dartTypeParams(m.typeParams, includeExtends: true);
final params = getFormalArgs(c, m, resolver);
s.write(
'$indent$ifStaticMethod $returnType $name$typeParamsWithExtend($params) '
'=> $wrapperExpr;\n');
return s.toString();
}
String _field(ClassDecl c, Field f, SymbolResolver resolver) {
final name = f.finalName;
final s = StringBuffer();
void writeDocs({bool writeDeleteInstruction = true}) {
s.write('$indent/// from: ${getOriginalFieldDecl(f)}\n');
if (!isPrimitive(f.type) && writeDeleteInstruction) {
s.write(BindingsGenerator.deleteInstruction);
}
s.write(breakDocComment(f.javadoc));
}
if (isStaticField(f) && isFinalField(f) && f.defaultValue != null) {
writeDocs(writeDeleteInstruction: false);
s.write('${indent}static const $name = '
'${getDartLiteral(f.defaultValue)};\n');
return s.toString();
}
final cName = getMemberNameInC(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 = $lookup'
'<${ffi}NativeFunction<$ffiSig>>("${symPrefix}_$cName")\n'
'.asFunction<$dartSig>();\n');
writeDocs();
final ifStatic = isStaticField(f) ? 'static' : '';
if (isSetter) {
final args = [
if (!isStaticField(f)) selfPointer,
toNativeArg('value', f.type),
].join(', ');
final valueOuterType = getDartOuterType(f.type, resolver);
s.write('$indent$ifStatic set $name($valueOuterType value) => '
'$sym($args);\n');
} else {
// getter
final self = isStaticField(f) ? '' : selfPointer;
final outerType = getDartOuterType(f.type, resolver);
final outerTypeClass = getDartTypeClass(f.type, resolver);
final resultGetter = getJValueAccessor(f.type);
final callExpr = '$sym($self).$resultGetter';
final resultExpr = toDartResult(callExpr, f.type, outerTypeClass);
s.write('$indent$ifStatic $outerType get $name => $resultExpr;\n');
}
}
writeAccessor(isSetter: false);
if (!isFinalField(f)) writeAccessor(isSetter: true);
s.writeln();
return s.toString();
}
static const _importsForInitCode = '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 _initCode() => '// Auto-generated initialization code.\n'
'\n'
'final ffi.Pointer<T> Function<T extends ffi.NativeType>(String sym)\n'
'jniLookup = ProtectedJniExtensions.initGeneratedLibrary'
'("${config.outputConfig.cConfig!.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/internal_helpers_for_jnigen.dart";\n'
'import "package:jni/jni.dart" as jni;\n\n';
static const defaultLintSuppressions =
'// ignore_for_file: annotate_overrides\n'
'// ignore_for_file: camel_case_extensions\n'
'// ignore_for_file: camel_case_types\n'
'// ignore_for_file: constant_identifier_names\n'
'// ignore_for_file: file_names\n'
'// ignore_for_file: no_leading_underscores_for_local_identifiers\n'
'// ignore_for_file: non_constant_identifier_names\n'
'// ignore_for_file: overridden_fields\n'
'// ignore_for_file: unnecessary_cast\n'
'// ignore_for_file: unused_element\n'
'// ignore_for_file: unused_import\n'
'\n';
static const preImportBoilerplate =
autoGeneratedNotice + defaultLintSuppressions + defaultImports;
@override
String getPostImportBoilerplate([String? initFilePath]) {
if (config.outputConfig.dartConfig.structure ==
OutputStructure.singleFile) {
return _initCode();
} else {
return 'import "${initFilePath!}" show jniLookup;\n\n';
}
}
@override
String getPreImportBoilerplate([String? initFilePath]) {
return preImportBoilerplate;
}
@override
String getInitFileContents() {
return '$_importsForInitCode\n${_initCode()}';
}
}