| // 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 PureDartBindingsGenerator extends BindingsGenerator { |
| static final indent = BindingsGenerator.indent; |
| static const accessors = 'jniAccessors'; |
| static const env = 'jniEnv'; |
| |
| /// Name for reference in base class. |
| static const selfPointer = BindingsGenerator.selfPointer; |
| |
| /// Name of field holding class reference. |
| static const classRef = '_classRef'; |
| |
| static const ffi = BindingsGenerator.ffi; |
| static const jni = BindingsGenerator.jni; |
| |
| static const voidPointer = BindingsGenerator.jobjectType; |
| static const ffiVoidType = BindingsGenerator.ffiVoidType; |
| static const jniObjectType = BindingsGenerator.jniObjectType; |
| |
| static final _deleteInstruction = BindingsGenerator.deleteInstruction; |
| |
| String escapeDollarSign(String s) => s.replaceAll('\$', '\\\$'); |
| |
| PureDartBindingsGenerator(this.config); |
| final Config config; |
| |
| @override |
| String generateBindings(ClassDecl decl, SymbolResolver resolver) { |
| if (!decl.isPreprocessed) { |
| throw StateError( |
| 'Java class declaration ${decl.binaryName} must be preprocessed before' |
| 'being passed to bindings generator'); |
| } |
| if (!decl.isIncluded) { |
| return ''; |
| } |
| final bindings = _class(decl, resolver); |
| log.fine('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: '')); |
| |
| final internalName = escapeDollarSign(getInternalName(decl.binaryName)); |
| |
| s.write(dartClassDefinition(decl, resolver)); |
| s.write( |
| '${indent}static final $classRef = $accessors.getClassOf("$internalName");\n'); |
| |
| 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(); |
| } |
| |
| @override |
| String toNativeArg(String name, TypeUsage type, |
| {bool convertBooleanToInt = false}) { |
| return super |
| .toNativeArg(name, type, convertBooleanToInt: convertBooleanToInt); |
| } |
| |
| @override |
| String actualArgs(Method m, {bool addSelf = false}) { |
| return super.actualArgs(m, addSelf: addSelf); |
| } |
| |
| String _method(ClassDecl c, Method m, SymbolResolver resolver) { |
| final name = m.finalName; |
| final isStatic = isStaticMethod(m); |
| final ifStatic = isStatic ? 'Static' : ''; |
| final s = StringBuffer(); |
| final mID = '_id_$name'; |
| final jniSignature = escapeDollarSign(getJniSignatureForMethod(m)); |
| |
| s.write('static final $mID = $accessors.get${ifStatic}MethodIDOf(' |
| '$classRef, "${m.name}", "$jniSignature");\n'); |
| |
| // Different logic for constructor and method; |
| // For constructor, we want return type to be new object. |
| var returnType = getDartOuterType( |
| m.asyncReturnType ?? m.returnType, |
| resolver, |
| ); |
| if (m.asyncReturnType != null) { |
| returnType = toFuture(returnType); |
| } |
| final returnTypeClass = |
| getDartTypeClass(m.asyncReturnType ?? m.returnType, resolver); |
| s.write('$indent/// from: ${getOriginalMethodHeader(m)}\n'); |
| if (!isPrimitive(m.returnType) || isCtor(m)) { |
| s.write(_deleteInstruction); |
| } |
| s.write(breakDocComment(m.javadoc)); |
| if (isCtor(m)) { |
| final wrapperExpr = |
| '$accessors.newObjectWithArgs($classRef, $mID, [${actualArgs(m)}]).object'; |
| final className = c.finalName; |
| final ctorFnName = name == 'ctor' ? className : '$className.$name'; |
| s.write( |
| '$ctorFnName(${getFormalArgs(c, m, resolver)}) : ' |
| 'super.fromRef(${dartSuperArgs(c, resolver)}$wrapperExpr);\n', |
| ); |
| return s.toString(); |
| } |
| |
| s.write(indent); |
| if (isStatic) { |
| s.write('static '); |
| } |
| final selfArgument = isStatic ? classRef : selfPointer; |
| final callType = getCallType(m.returnType); |
| final resultGetter = getResultGetterName(m.returnType); |
| final typeParamsWithExtend = |
| dartTypeParams(m.typeParams, includeExtends: true); |
| var wrapperExpr = '$accessors.call${ifStatic}MethodWithArgs' |
| '($selfArgument, $mID, $callType, [${actualArgs(m)}])' |
| '.$resultGetter'; |
| s.write( |
| '$returnType $name$typeParamsWithExtend(${getFormalArgs(c, m, resolver)}) ', |
| ); |
| if (m.asyncReturnType == null) { |
| wrapperExpr = toDartResult(wrapperExpr, m.returnType, returnTypeClass); |
| s.write('=> $wrapperExpr;\n'); |
| } else { |
| s.write( |
| 'async {\n' |
| '${indent * 2}final \$p = ReceivePort();\n' |
| '${indent * 2}final \$c = ${jni}Jni.newPortContinuation(\$p);\n' |
| '${indent * 2}$wrapperExpr;\n' |
| '${indent * 2}final \$o = $voidPointer.fromAddress(await \$p.first);\n' |
| '${indent * 2}final \$k = $returnTypeClass.getClass().reference;\n' |
| '${indent * 2}if (${jni}Jni.env.IsInstanceOf(\$o, \$k) == 0) {\n' |
| '${indent * 3}throw "Failed";\n' |
| '${indent * 2}}\n' |
| '${indent * 2}return ${toDartResult( |
| '\$o', |
| m.returnType, |
| returnTypeClass, |
| )};\n' |
| '}\n', |
| ); |
| } |
| return s.toString(); |
| } |
| |
| String _field(ClassDecl c, Field f, SymbolResolver resolver) { |
| final name = f.finalName; |
| final isStatic = isStaticField(f); |
| final ifStatic = isStatic ? 'Static' : ''; |
| final isFinal = isFinalField(f); |
| final s = StringBuffer(); |
| final selfArgument = isStatic ? classRef : selfPointer; |
| final callType = getCallType(f.type); |
| final resultGetter = getResultGetterName(f.type); |
| final outerType = getDartOuterType(f.type, resolver); |
| |
| void writeDocs({bool writeDeleteInstruction = true}) { |
| s.write('$indent/// from: ${getOriginalFieldDecl(f)}\n'); |
| if (!isPrimitive(f.type) && writeDeleteInstruction) { |
| s.write(_deleteInstruction); |
| } |
| s.write(breakDocComment(f.javadoc)); |
| } |
| |
| if (isStatic && isFinal && f.defaultValue != null) { |
| writeDocs(writeDeleteInstruction: false); |
| s.write( |
| '${indent}static const $name = ${getDartLiteral(f.defaultValue)};\n'); |
| return s.toString(); |
| } |
| final jniSignature = escapeDollarSign(getDescriptor(f.type)); |
| final fID = '_id_$name'; |
| s.write('static final $fID = ' |
| '$accessors.get${ifStatic}FieldIDOf(_classRef, ' |
| '"${f.name}","$jniSignature");\n'); |
| void writeAccessor({bool setter = false}) { |
| writeDocs(); |
| s.write(indent); |
| if (isStatic) s.write('static '); |
| if (setter) { |
| final fieldType = getTypeNameAtCallSite(f.type); |
| s.write('set $name($outerType value) => $env.Set$ifStatic' |
| '${fieldType}Field($selfArgument, $fID, ' |
| '${toNativeArg("value", f.type, convertBooleanToInt: true)});\n'); |
| } else { |
| final outer = getDartOuterType(f.type, resolver); |
| final typeClass = getDartTypeClass(f.type, resolver); |
| final callExpr = |
| '$accessors.get${ifStatic}Field($selfArgument, $fID, $callType)' |
| '.$resultGetter'; |
| final resultExpr = toDartResult(callExpr, f.type, typeClass); |
| s.write('$outer get $name => $resultExpr;\n'); |
| } |
| } |
| |
| writeAccessor(setter: false); |
| if (!isFinalField(f)) writeAccessor(setter: true); |
| return s.toString(); |
| } |
| |
| static const autoGeneratedNotice = '// Autogenerated by jnigen. ' |
| 'DO NOT EDIT!\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_field\n' |
| '// ignore_for_file: unused_import\n' |
| '// ignore_for_file: unused_shown_name\n' |
| '\n'; |
| |
| static const dartImports = 'import "dart:isolate" show ReceivePort;\n\n'; |
| static const jniImport = 'import "package:jni/jni.dart" as jni;\n\n'; |
| static const internalHelpersImport = |
| 'import "package:jni/internal_helpers_for_jnigen.dart";\n\n'; |
| static const defaultImports = dartImports + jniImport + internalHelpersImport; |
| |
| static const initialization = 'final jniEnv = ${jni}Jni.env;\n' |
| 'final jniAccessors = ${jni}Jni.accessors;\n\n'; |
| static String initImport(String initFilePath) => |
| 'import "$initFilePath" show jniEnv, jniAccessors;\n\n'; |
| |
| @override |
| String getPostImportBoilerplate([String? initFilePath]) { |
| return (config.outputConfig.dartConfig.structure == |
| OutputStructure.singleFile) |
| ? initialization |
| : initImport(initFilePath!); |
| } |
| |
| @override |
| String getPreImportBoilerplate([String? initFilePath]) { |
| return autoGeneratedNotice + defaultLintSuppressions + defaultImports; |
| } |
| |
| @override |
| String getInitFileContents() { |
| return jniImport + initialization; |
| } |
| } |