blob: c79a18e445a092fcfbb73037dbd65dccf08e3d00 [file] [log] [blame]
// Copyright 2020 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'ast.dart';
import 'generator_tools.dart';
const Map<String, String> _javaTypeForDartTypeMap = <String, String>{
'bool': 'Boolean',
'int': 'Long',
'String': 'String',
'double': 'Double',
'Uint8List': 'byte[]',
'Int32List': 'int[]',
'Int64List': 'long[]',
'Float64List': 'double[]',
'List': 'List<Object>',
'Map': 'Map<String, Object>',
};
/// Options that control how Java code will be generated.
class JavaOptions {
/// The name of the class that will house all the generated classes.
String className;
/// The package where the generated class will live.
String package;
}
void _writeHostApi(Indent indent, Api api) {
assert(api.location == ApiLocation.host);
if (api.methods.any((Method it) => it.isAsynchronous)) {
indent.write('public interface Result<T> ');
indent.scoped('{', '}', () {
indent.writeln('void success(T result);');
});
indent.addln('');
}
indent.writeln(
'/** Generated interface from Pigeon that represents a handler of messages from Flutter.*/');
indent.write('public interface ${api.name} ');
indent.scoped('{', '}', () {
for (Method method in api.methods) {
final String returnType =
method.isAsynchronous ? 'void' : method.returnType;
final List<String> argSignature = <String>[];
if (method.argType != 'void') {
argSignature.add('${method.argType} arg');
}
if (method.isAsynchronous) {
argSignature.add('Result<${method.returnType}> result');
}
indent.writeln('$returnType ${method.name}(${argSignature.join(', ')});');
}
indent.addln('');
indent.writeln(
'/** Sets up an instance of `${api.name}` to handle messages through the `binaryMessenger` */');
indent.write(
'static void setup(BinaryMessenger binaryMessenger, ${api.name} api) ');
indent.scoped('{', '}', () {
for (Method method in api.methods) {
final String channelName = makeChannelName(api, method);
indent.write('');
indent.scoped('{', '}', () {
indent.writeln('BasicMessageChannel<Object> channel =');
indent.inc();
indent.inc();
indent.writeln(
'new BasicMessageChannel<>(binaryMessenger, "$channelName", new StandardMessageCodec());');
indent.dec();
indent.dec();
indent.write('if (api != null) ');
indent.scoped('{', '} else {', () {
indent.write('channel.setMessageHandler((message, reply) -> ');
indent.scoped('{', '});', () {
final String argType = method.argType;
final String returnType = method.returnType;
indent.writeln('Map<String, Object> wrapped = new HashMap<>();');
indent.write('try ');
indent.scoped('{', '}', () {
final List<String> methodArgument = <String>[];
if (argType != 'void') {
indent.writeln('@SuppressWarnings("ConstantConditions")');
indent.writeln(
'$argType input = $argType.fromMap((Map<String, Object>)message);');
methodArgument.add('input');
}
if (method.isAsynchronous) {
methodArgument.add(
'result -> { '
'wrapped.put("${Keys.result}", result.toMap()); '
'reply.reply(wrapped); '
'}',
);
}
final String call =
'api.${method.name}(${methodArgument.join(', ')})';
if (method.isAsynchronous) {
indent.writeln('$call;');
} else if (method.returnType == 'void') {
indent.writeln('$call;');
indent.writeln('wrapped.put("${Keys.result}", null);');
} else {
indent.writeln('$returnType output = $call;');
indent.writeln(
'wrapped.put("${Keys.result}", output.toMap());');
}
});
indent.write('catch (Exception exception) ');
indent.scoped('{', '}', () {
indent.writeln(
'wrapped.put("${Keys.error}", wrapError(exception));');
if (method.isAsynchronous) {
indent.writeln('reply.reply(wrapped);');
}
});
if (!method.isAsynchronous) {
indent.writeln('reply.reply(wrapped);');
}
});
});
indent.scoped(null, '}', () {
indent.writeln('channel.setMessageHandler(null);');
});
});
}
});
});
}
void _writeFlutterApi(Indent indent, Api api) {
assert(api.location == ApiLocation.flutter);
indent.writeln(
'/** Generated class from Pigeon that represents Flutter messages that can be called from Java.*/');
indent.write('public static class ${api.name} ');
indent.scoped('{', '}', () {
indent.writeln('private final BinaryMessenger binaryMessenger;');
indent.write('public ${api.name}(BinaryMessenger argBinaryMessenger)');
indent.scoped('{', '}', () {
indent.writeln('this.binaryMessenger = argBinaryMessenger;');
});
indent.write('public interface Reply<T> ');
indent.scoped('{', '}', () {
indent.writeln('void reply(T reply);');
});
for (Method func in api.methods) {
final String channelName = makeChannelName(api, func);
final String returnType =
func.returnType == 'void' ? 'Void' : func.returnType;
String sendArgument;
if (func.argType == 'void') {
indent.write('public void ${func.name}(Reply<$returnType> callback) ');
sendArgument = 'null';
} else {
indent.write(
'public void ${func.name}(${func.argType} argInput, Reply<$returnType> callback) ');
sendArgument = 'inputMap';
}
indent.scoped('{', '}', () {
indent.writeln('BasicMessageChannel<Object> channel =');
indent.inc();
indent.inc();
indent.writeln(
'new BasicMessageChannel<>(binaryMessenger, "$channelName", new StandardMessageCodec());');
indent.dec();
indent.dec();
if (func.argType != 'void') {
indent.writeln('Map<String, Object> inputMap = argInput.toMap();');
}
indent.write('if (callback != null)');
indent.scoped('{', '}', () {
indent.write('channel.send($sendArgument, channelReply -> ');
indent.scoped('{', '});', () {
if (func.returnType == 'void') {
indent.writeln('callback.reply(null);');
} else {
indent.writeln('Map outputMap = (Map)channelReply;');
indent.writeln('@SuppressWarnings("ConstantConditions")');
indent.writeln(
'${func.returnType} output = ${func.returnType}.fromMap(outputMap);');
indent.writeln('callback.reply(output);');
}
});
});
indent.write(' else ');
indent.scoped('{', '}', () {
indent.writeln('channel.send($sendArgument, null);');
});
});
if (func.argType == 'void') {
indent.write('public void ${func.name}() ');
} else {
indent.write('public void ${func.name}(${func.argType} argInput) ');
}
indent.scoped('{', '}', () {
if (func.argType == 'void') {
indent.writeln('${func.name}(null);');
} else {
indent.writeln('${func.name}(argInput, null);');
}
});
}
});
}
String _makeGetter(Field field) {
final String uppercased =
field.name.substring(0, 1).toUpperCase() + field.name.substring(1);
return 'get$uppercased';
}
String _makeSetter(Field field) {
final String uppercased =
field.name.substring(0, 1).toUpperCase() + field.name.substring(1);
return 'set$uppercased';
}
String _javaTypeForDartType(String datatype) {
return _javaTypeForDartTypeMap[datatype];
}
String _castObject(Field field, List<Class> classes, String varName) {
final HostDatatype hostDatatype =
getHostDatatype(field, classes, _javaTypeForDartType);
if (field.dataType == 'int') {
return '($varName == null) ? null : (($varName instanceof Integer) ? (Integer)$varName : (${hostDatatype.datatype})$varName)';
} else if (!hostDatatype.isBuiltin &&
classes.map((Class x) => x.name).contains(field.dataType)) {
return '${hostDatatype.datatype}.fromMap((Map)$varName)';
} else {
return '(${hostDatatype.datatype})$varName';
}
}
/// Generates the ".java" file for the AST represented by [root] to [sink] with the
/// provided [options].
void generateJava(JavaOptions options, Root root, StringSink sink) {
final Set<String> rootClassNameSet =
root.classes.map((Class x) => x.name).toSet();
final Indent indent = Indent(sink);
indent.writeln('// $generatedCodeWarning');
indent.writeln('// $seeAlsoWarning');
indent.addln('');
if (options.package != null) {
indent.writeln('package ${options.package};');
}
indent.addln('');
indent.writeln('import io.flutter.plugin.common.BasicMessageChannel;');
indent.writeln('import io.flutter.plugin.common.BinaryMessenger;');
indent.writeln('import io.flutter.plugin.common.StandardMessageCodec;');
indent.writeln('import java.util.List;');
indent.writeln('import java.util.Map;');
indent.writeln('import java.util.HashMap;');
indent.addln('');
assert(options.className != null);
indent.writeln('/** Generated class from Pigeon. */');
indent.writeln(
'@SuppressWarnings({"unused", "unchecked", "CodeBlock2Expr", "RedundantSuppression"})');
indent.write('public class ${options.className} ');
indent.scoped('{', '}', () {
for (Class klass in root.classes) {
indent.addln('');
indent.writeln(
'/** Generated class from Pigeon that represents data sent in messages. */');
indent.write('public static class ${klass.name} ');
indent.scoped('{', '}', () {
for (Field field in klass.fields) {
final HostDatatype hostDatatype =
getHostDatatype(field, root.classes, _javaTypeForDartType);
indent.writeln('private ${hostDatatype.datatype} ${field.name};');
indent.writeln(
'public ${hostDatatype.datatype} ${_makeGetter(field)}() { return ${field.name}; }');
indent.writeln(
'public void ${_makeSetter(field)}(${hostDatatype.datatype} setterArg) { this.${field.name} = setterArg; }');
indent.addln('');
}
indent.write('Map<String, Object> toMap() ');
indent.scoped('{', '}', () {
indent.writeln('Map<String, Object> toMapResult = new HashMap<>();');
for (Field field in klass.fields) {
final HostDatatype hostDatatype =
getHostDatatype(field, root.classes, _javaTypeForDartType);
String toWriteValue = '';
if (!hostDatatype.isBuiltin &&
rootClassNameSet.contains(field.dataType)) {
toWriteValue = '${field.name}.toMap()';
} else {
toWriteValue = field.name;
}
indent.writeln('toMapResult.put("${field.name}", $toWriteValue);');
}
indent.writeln('return toMapResult;');
});
indent.write('static ${klass.name} fromMap(Map<String, Object> map) ');
indent.scoped('{', '}', () {
indent.writeln('${klass.name} fromMapResult = new ${klass.name}();');
for (Field field in klass.fields) {
indent.writeln('Object ${field.name} = map.get("${field.name}");');
indent.writeln(
'fromMapResult.${field.name} = ${_castObject(field, root.classes, field.name)};');
}
indent.writeln('return fromMapResult;');
});
});
}
for (Api api in root.apis) {
indent.addln('');
if (api.location == ApiLocation.host) {
_writeHostApi(indent, api);
} else if (api.location == ApiLocation.flutter) {
_writeFlutterApi(indent, api);
}
}
indent.format(
'''private static Map<String, Object> wrapError(Exception exception) {
\tMap<String, Object> errorMap = new HashMap<>();
\terrorMap.put("${Keys.errorMessage}", exception.toString());
\terrorMap.put("${Keys.errorCode}", exception.getClass().getSimpleName());
\terrorMap.put("${Keys.errorDetails}", null);
\treturn errorMap;
}''');
});
}