blob: 19fba1139be0343bf8a1eb47f07b7a67c805cc8e [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/util/rename_conflict.dart';
import 'symbol_resolver.dart';
/// Base class of both C based and Dart-only binding generators. Implements
/// Many methods used commonly by both of them.
///
/// These methods are in a superclass since they usually require access
/// to resolver, or for consistency with similar methods which require
/// the resolver.
abstract class BindingsGenerator {
// Name for reference in base class.
static const selfPointer = 'reference';
static final indent = ' ' * 2;
// import prefixes
static const ffi = 'ffi.';
static const jni = 'jni.';
static const String voidPointer = '${ffi}Pointer<${ffi}Void>';
static const String ffiVoidType = '${ffi}Void';
static const String jobjectType = '${jni}JObjectPtr';
static const String jthrowableType = '${jni}JThrowablePtr';
static const String jniStringType = '${jni}JString';
static const String jniStringTypeClass = '${jni}JString$typeClassSuffix';
static const String jniObjectType = '${jni}JObject';
static const String jniObjectTypeClass = '${jni}JObject$typeClassSuffix';
static const String jniArrayType = '${jni}JArray';
static const String jniArrayTypeClass = '${jni}JArray$typeClassSuffix';
static const String jniCallType = '${jni}JniCallType';
static const String jniTypeType = '${jni}JObjType';
static const String typeClassSuffix = 'Type';
// TODO(#143): this is a temporary fix for the name collision.
static const String typeClassPrefix = '\$';
static const String instanceTypeGetter = '\$type';
static const String typeParamPrefix = '\$';
static const String jniResultType = '${jni}JniResult';
/// Generate bindings string for given class declaration.
String generateBindings(ClassDecl decl, SymbolResolver resolver);
/// Get common initialization code for bindings.
///
/// if this method returns an empty String, no init file is created.
String getInitFileContents();
/// Get boilerplate to be pasted on a file before any imports.
///
/// [initFilePath], if provided, shall point to the _init.dart file where
/// initialization code is stored. If this is null, the method should provide
/// standalone boilerplate which doesn't need an init file.
///
/// This method should return an empty string if no such boilerplate is
/// required.
String getPreImportBoilerplate([String? initFilePath]);
/// Get boilerplate to be pasted on a file before any imports.
///
/// See [getPreImportBoilerplate] for an explanation of [initFilePath].
///
/// This method should return an empty String if no such boilerplate is
/// required.
String getPostImportBoilerplate([String? initFilePath]);
/// Returns the formal parameters list of the generated function.
///
/// This is the signature seen by the user.
String getFormalArgs(ClassDecl c, Method m, SymbolResolver resolver) {
final List<String> args = [];
// Prepending the parameters with type parameters
if (isCtor(m)) {
for (final typeParam in c.allTypeParams) {
args.add('this.$typeParamPrefix${typeParam.name}');
}
}
for (final typeParam in m.typeParams) {
args.add(
'$jniTypeType<${typeParam.name}> $typeParamPrefix${typeParam.name}');
}
for (final param in m.params) {
args.add(
'${getDartOuterType(param.type, resolver)} ${kwRename(param.name)}');
}
return args.join(', ');
}
/// Actual arguments passed to native call.
String actualArgs(Method m, {bool addSelf = true}) {
final List<String> args = [if (hasSelfParam(m) && addSelf) selfPointer];
for (var param in m.params) {
final paramName = kwRename(param.name);
args.add(toNativeArg(paramName, param.type));
}
return args.join(', ');
}
String _dartTypeClassName(String className) {
return '$typeClassPrefix$className$typeClassSuffix';
}
String dartTypeParams(List<TypeParam> typeParams,
{required bool includeExtends}) {
if (typeParams.isEmpty) return '';
// TODO(#144): resolve the actual type being extended, if any.
final ifExtendsIncluded = includeExtends ? ' extends $jniObjectType' : '';
final args =
typeParams.map((e) => '${e.name}$ifExtendsIncluded').join(' ,');
return '<$args>';
}
String dartClassDefinition(ClassDecl decl, SymbolResolver resolver) {
final name = decl.finalName;
var superName = jniObjectType;
if (decl.superclass != null) {
superName = _dartType(decl.superclass!, resolver: resolver);
}
final typeParamsWithExtend =
dartTypeParams(decl.allTypeParams, includeExtends: true);
final ifSomeArgs =
decl.allTypeParams.isNotEmpty ? '(${_typeParamArgs(decl)})' : '';
return 'class $name$typeParamsWithExtend extends $superName {\n'
'late final $jniTypeType? _$instanceTypeGetter;\n'
'@override\n'
'$jniTypeType get $instanceTypeGetter => '
'_$instanceTypeGetter ??= type$ifSomeArgs;\n\n'
'${_typeParamDefs(decl)}\n'
'$indent$name.fromRef(\n'
'${_typeParamCtorArgs(decl)}'
'$jobjectType ref,'
'): super.fromRef(${dartSuperArgs(decl, resolver)}ref);\n\n';
}
String dartSigForField(Field f,
{bool isSetter = false, required bool isFfiSig}) {
final ref = f.modifiers.contains('static') ? '' : '$jobjectType, ';
final conv = isFfiSig ? getDartFfiType : getDartInnerType;
if (isSetter) {
return '$jthrowableType Function($ref${conv(f.type)})';
}
return '$jniResultType Function($ref)';
}
String dartSuperArgs(ClassDecl decl, SymbolResolver resolver) {
if (decl.superclass == null ||
resolver.resolve((decl.superclass!.type as DeclaredType).binaryName) ==
null) {
return '';
}
return (decl.superclass!.type as DeclaredType)
.params
.map((param) => '${getDartTypeClass(param, resolver)},')
.join();
}
String dartArrayExtension(ClassDecl decl) {
final name = decl.finalName;
final typeParamsWithExtend =
dartTypeParams(decl.allTypeParams, includeExtends: true);
final typeParams =
dartTypeParams(decl.allTypeParams, includeExtends: false);
final typeClassName = _dartTypeClassName(name);
return '\nextension \$${name}Array$typeParamsWithExtend on $jniArrayType<$name$typeParams> {\n'
'$indent$name$typeParams operator [](int index) {\n'
'${indent * 2}return (elementType as $typeClassName$typeParams)'
'.fromRef(elementAt(index, ${jni}JniCallType.objectType).object);\n'
'$indent}\n\n'
'${indent}void operator []=(int index, $name$typeParams value) {\n'
'${indent * 2}(this as $jniArrayType<$jniObjectType>)[index] = value;\n'
'$indent}\n'
'}\n';
}
String _typeParamDefs(ClassDecl decl) {
return decl.allTypeParams
.map((e) =>
'${indent}final $jniTypeType<${e.name}> $typeParamPrefix${e.name};\n')
.join();
}
String _typeParamCtorArgs(ClassDecl decl) {
return decl.allTypeParams
.map((e) => '${indent * 2}this.$typeParamPrefix${e.name},\n')
.join();
}
String _typeParamArgs(ClassDecl decl) {
return decl.allTypeParams.map((e) => '$typeParamPrefix${e.name}, ').join();
}
String dartTypeClass(ClassDecl decl) {
final name = decl.finalName;
final signature = getSignature(decl.binaryName);
final typeClassName = _dartTypeClassName(name);
final typeParamsWithExtend =
dartTypeParams(decl.allTypeParams, includeExtends: true);
final typeParams =
dartTypeParams(decl.allTypeParams, includeExtends: false);
return '\nclass $typeClassName$typeParamsWithExtend extends $jniTypeType<$name$typeParams> {\n'
'${_typeParamDefs(decl)}\n'
'${indent}const $typeClassName(\n'
'${_typeParamCtorArgs(decl)}'
'$indent);\n\n'
'$indent@override\n'
'${indent}String get signature => r"$signature";\n\n'
'$indent@override\n'
'$indent$name$typeParams fromRef($jobjectType ref) => $name.fromRef(${_typeParamArgs(decl)}ref);\n'
'}\n';
}
String dartInitType(ClassDecl decl) {
final typeClassName = _dartTypeClassName(decl.finalName);
final args =
decl.allTypeParams.map((e) => '$typeParamPrefix${e.name},').join();
return '$instanceTypeGetter = $typeClassName($args)';
}
String dartStaticTypeGetter(ClassDecl decl) {
final typeClassName = _dartTypeClassName(decl.finalName);
const docs =
'/// The type which includes information such as the signature of this class.';
if (decl.allTypeParams.isEmpty) {
return '$indent$docs\n'
'${indent}static const type = $typeClassName();\n\n';
}
final typeParamsWithExtend =
dartTypeParams(decl.allTypeParams, includeExtends: true);
final typeParams =
dartTypeParams(decl.allTypeParams, includeExtends: false);
final methodArgs = decl.allTypeParams
.map((e) =>
'${indent * 2}$jniTypeType<${e.name}> $typeParamPrefix${e.name},\n')
.join();
final ctorArgs = decl.allTypeParams
.map((e) => '${indent * 3}$typeParamPrefix${e.name},\n')
.join();
return '$indent$docs\n'
'${indent}static $typeClassName$typeParams type$typeParamsWithExtend(\n'
'$methodArgs'
'$indent) {\n'
'${indent * 2}return $typeClassName(\n'
'$ctorArgs'
'${indent * 2});\n'
'$indent}\n\n';
}
String dartSigForMethod(Method m, {required bool isFfiSig}) {
final conv = isFfiSig ? getDartFfiType : getDartInnerType;
final argTypes = [if (hasSelfParam(m)) voidPointer];
for (var param in m.params) {
argTypes.add(conv(param.type));
}
return '$jniResultType Function (${argTypes.join(", ")})';
}
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',
};
const jniTypes = {
'byte': 'JByte',
'short': 'JShort',
'char': 'JChar',
'int': 'JInt',
'long': 'JLong',
'float': 'JFloat',
'double': 'JDouble',
'boolean': 'JBoolean',
};
switch (t.kind) {
case Kind.primitive:
if (t.name == 'boolean' && resolver == null) return 'int';
return primitives[(t.type as PrimitiveType).name]!;
case Kind.typeVariable:
if (resolver != null) {
return t.name;
}
return voidPointer;
case Kind.wildcard:
throw SkipException('Wildcards are not yet supported');
case Kind.array:
if (resolver != null) {
final innerType = (t.type as ArrayType).type;
final dartType = innerType.kind == Kind.primitive
? jni + jniTypes[(innerType.type as PrimitiveType).name]!
: _dartType(innerType, resolver: resolver);
return '$jniArrayType<$dartType>';
}
return voidPointer;
case Kind.declared:
if (resolver != null) {
final type = t.type as DeclaredType;
final resolved = resolver.resolve(type.binaryName);
final resolvedClass = resolver.resolveClass(type.binaryName);
if (resolved == null ||
resolvedClass == null ||
!resolvedClass.isIncluded) {
return jniObjectType;
}
// All type parameters of this type
final allTypeParams =
resolvedClass.allTypeParams.map((param) => param.name).toList();
// The ones that are declared.
final paramTypeClasses =
type.params.map((param) => _dartType(param, resolver: resolver));
// Replacing the declared ones. They come at the end.
if (allTypeParams.length >= type.params.length) {
allTypeParams.replaceRange(
allTypeParams.length - type.params.length,
allTypeParams.length,
paramTypeClasses,
);
}
final args = allTypeParams.join(',');
final ifArgs = args.isNotEmpty ? '<$args>' : '';
return '$resolved$ifArgs';
}
return voidPointer;
}
}
String getDartTypeClass(
TypeUsage t,
SymbolResolver resolver,
) {
return _getDartTypeClass(t, resolver, addConst: true).name;
}
_TypeClass _getDartTypeClass(
TypeUsage t,
SymbolResolver resolver, {
required bool addConst,
}) {
const primitives = {
'byte': 'JByteType',
'short': 'JShortType',
'char': 'JCharType',
'int': 'JIntType',
'long': 'JLongType',
'float': 'JFloatType',
'double': 'JDoubleType',
'boolean': 'JBooleanType',
'void': 'JVoidType', // This will never be in the generated code.
};
switch (t.kind) {
case Kind.primitive:
final ifConst = addConst ? 'const ' : '';
return _TypeClass(
'$ifConst$jni${primitives[(t.type as PrimitiveType).name]}()',
true,
);
case Kind.typeVariable:
return _TypeClass(
'$typeParamPrefix${(t.type as TypeVar).name}',
false,
);
case Kind.wildcard:
throw SkipException('Wildcards are not yet supported');
case Kind.array:
final innerType = (t.type as ArrayType).type;
final innerTypeClass = _getDartTypeClass(
innerType,
resolver,
addConst: false,
);
final ifConst = addConst && innerTypeClass.canBeConst ? 'const ' : '';
return _TypeClass(
'$ifConst$jniArrayTypeClass(${innerTypeClass.name})',
innerTypeClass.canBeConst,
);
case Kind.declared:
final type = (t.type as DeclaredType);
final resolved = resolver.resolve(type.binaryName);
final resolvedClass = resolver.resolveClass(type.binaryName);
if (resolved == null ||
resolvedClass == null ||
!resolvedClass.isIncluded) {
return _TypeClass(
'${addConst ? 'const ' : ''}$jniObjectTypeClass()',
true,
);
}
// All type params of this type
final allTypeParams = resolvedClass.allTypeParams
.map((param) => '$typeParamPrefix${param.name}')
.toList();
// The ones that are declared.
final paramTypeClasses = type.params.map(
(param) => _getDartTypeClass(param, resolver, addConst: false));
// Replacing the declared ones. They come at the end.
if (allTypeParams.length >= type.params.length) {
allTypeParams.replaceRange(
allTypeParams.length - type.params.length,
allTypeParams.length,
paramTypeClasses.map((param) => param.name),
);
}
final args = allTypeParams.join(',');
final canBeConst = (allTypeParams.length == paramTypeClasses.length &&
paramTypeClasses.every((e) => e.canBeConst)) ||
allTypeParams.isEmpty;
final ifConst = addConst && canBeConst ? 'const ' : '';
if (resolved == jniObjectType) {
return _TypeClass('$ifConst$jniObjectTypeClass()', true);
} else if (resolved == jniStringType) {
return _TypeClass('$ifConst$jniStringTypeClass()', true);
} else if (resolved.contains('.')) {
// It is in form of jni.SomeClass which should be converted to jni.$SomeClassType
final dotIndex = resolved.indexOf('.');
final module = resolved.substring(0, dotIndex);
final clazz = resolved.substring(dotIndex + 1);
return _TypeClass(
'$ifConst$module.${_dartTypeClassName(clazz)}($args)',
canBeConst,
);
}
return _TypeClass(
'$ifConst${_dartTypeClassName(resolved)}($args)',
canBeConst,
);
}
}
/// Get corresponding Dart FFI type of Java type.
String getDartFfiType(TypeUsage t) {
const primitives = {
'byte': 'Int8',
'short': 'Int16',
'char': 'Uint16',
'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.wildcard:
throw SkipException('Wildcards are not yet supported');
case Kind.typeVariable:
case Kind.array:
case Kind.declared:
return voidPointer;
}
}
String getDartInnerType(TypeUsage t) => _dartType(t);
String getDartOuterType(TypeUsage t, SymbolResolver resolver) =>
_dartType(t, resolver: resolver);
String getDartLiteral(dynamic value) {
if (value is String) {
// TODO(#31): escape string literal.
return '"$value"'.replaceAll('\\', '\\\\');
}
if (value is int || value is double || value is bool) {
return value.toString();
}
throw SkipException('Not a constant of a known type.');
}
String getJValueAccessor(TypeUsage type) {
const primitives = {
'boolean': 'boolean',
'byte': 'byte',
'short': 'short',
'char': 'char',
'int': 'integer',
'long': 'long',
'float': 'float',
'double': 'doubleFloat',
'void': 'check()',
};
if (isPrimitive(type)) {
return primitives[type.name]!;
}
return 'object';
}
String getOriginalFieldDecl(Field f) {
final declStmt = '${f.type.shorthand} ${f.name}';
return [...f.modifiers, declStmt].join(' ');
}
String getOriginalMethodHeader(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 toNativeArg(String name, TypeUsage type,
{bool convertBooleanToInt = true}) {
if (isPrimitive(type)) {
return (type.name == 'boolean' && convertBooleanToInt)
? '$name ? 1 : 0'
: name;
}
return '$name.$selfPointer';
}
String toDartResult(String expr, TypeUsage type, String dartTypeClass) {
if (isPrimitive(type)) {
return expr;
}
return '$dartTypeClass.fromRef($expr)';
}
static final deleteInstruction =
'$indent/// The returned object must be deleted after use, '
'by calling the `delete` method.\n';
String getCallType(TypeUsage returnType) {
if (isPrimitive(returnType)) {
return "$jniCallType.${returnType.name}Type";
}
return "$jniCallType.objectType";
}
}
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 name canonicalized for C bindings, by replacing "." with "_" and
/// "$" with "__".
String getUniqueClassName(ClassDecl decl) {
if (!decl.isPreprocessed) {
throw StateError("class not preprocessed: ${decl.binaryName}");
}
return decl.uniqueName;
}
/// Returns the name of the class member as referred to by C bindings
String getMemberNameInC(ClassDecl decl, String name) =>
"${getUniqueClassName(decl)}__$name";
String getCType(String binaryName) {
switch (binaryName) {
case "void":
return "void";
case "byte":
return "int8_t";
case "char":
return "char";
case "double":
return "double";
case "float":
return "float";
case "int":
return "int32_t";
case "long":
return "int64_t";
case "short":
return "int16_t";
case "boolean":
return "uint8_t";
default:
return "jobject";
}
}
String getInternalName(String binaryName) {
switch (binaryName) {
case "void":
return "V";
case "byte":
return "B";
case "char":
return "C";
case "double":
return "D";
case "float":
return "F";
case "int":
return "I";
case "long":
return "J";
case "short":
return "S";
case "boolean":
return "Z";
default:
return binaryName.replaceAll(".", "/");
}
}
String getSignature(String binaryName) {
switch (binaryName) {
case "void":
return "V";
case "byte":
return "B";
case "char":
return "C";
case "double":
return "D";
case "float":
return "F";
case "int":
return "I";
case "long":
return "J";
case "short":
return "S";
case "boolean":
return "Z";
default:
return 'L${binaryName.replaceAll(".", "/")};';
}
}
String getDescriptor(TypeUsage usage, {bool escapeDollarSign = false}) {
switch (usage.kind) {
case Kind.declared:
return getSignature((usage.type as DeclaredType).binaryName);
case Kind.primitive:
return getSignature((usage.type as PrimitiveType).name);
case Kind.typeVariable:
// It should be possible to compute the erasure of a type
// in parser itself.
// TODO(#23): Use erasure of the type variable here.
// This is just a (wrong) placeholder
return "Ljava/lang/Object;";
case Kind.array:
final inner = getDescriptor((usage.type as ArrayType).type);
return "[$inner";
case Kind.wildcard:
final extendsBound = (usage.type as Wildcard).extendsBound;
if (extendsBound != null) {
return getDescriptor(extendsBound);
}
return 'Ljava/lang/Object;';
}
}
bool isPrimitive(TypeUsage t) => t.kind == Kind.primitive;
bool isVoid(TypeUsage t) => isPrimitive(t) && t.name == 'void';
bool isStaticField(Field f) => f.modifiers.contains('static');
bool isStaticMethod(Method m) => m.modifiers.contains('static');
bool isFinalField(Field f) => f.modifiers.contains('final');
bool isFinalMethod(Method m) => m.modifiers.contains('final');
bool isCtor(Method m) => m.name == '<init>';
// static methods & constructors do not have self param.
bool hasSelfParam(Method m) => !isStaticMethod(m) && !isCtor(m);
bool isObjectField(Field f) => !isPrimitive(f.type);
bool isObjectMethod(Method m) => !isPrimitive(m.returnType);
/// Returns class name as useful in dart.
///
/// Eg -> a.b.X.Y -> X_Y
String getSimplifiedClassName(String binaryName) =>
binaryName.split('.').last.replaceAll('\$', '_');
// Marker exception when a method or class cannot be translated
// The inner functions may not know how much context has to be skipped in case
// of an error or unknown element. They throw SkipException.
class SkipException implements Exception {
SkipException(this.message, [this.element]);
String message;
dynamic element;
@override
String toString() {
return '$message;';
}
}
String getTypeNameAtCallSite(TypeUsage t) {
if (isPrimitive(t)) {
return t.name.substring(0, 1).toUpperCase() + t.name.substring(1);
}
return "Object";
}
String getResultGetterName(TypeUsage returnType) {
final primitives = {
'boolean': 'boolean',
'byte': 'byte',
'short': 'short',
'char': 'char',
'int': 'integer',
'long': 'long',
'float': 'float',
'double': 'doubleFloat',
'void': 'check()',
};
return primitives[returnType.name] ?? 'object';
}
/// Returns the JNI signature of the method.
String getJniSignatureForMethod(Method m) {
final s = StringBuffer();
s.write('(');
for (var param in m.params) {
final type = getDescriptor(param.type);
s.write(type);
}
s.write(')');
final returnType = getDescriptor(m.returnType);
s.write(returnType);
return s.toString();
}
class _TypeClass {
final String name;
final bool canBeConst;
_TypeClass(this.name, this.canBeConst);
}