blob: 989f9d3a925804d4378bfb8a5e876727902664c1 [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';
void _writeHostApi(Indent indent, Api api) {
assert(api.location == ApiLocation.host);
indent.write('class ${api.name} ');
indent.scoped('{', '}', () {
for (Method func in api.methods) {
String argSignature = '';
String sendArgument = 'null';
String requestMapDeclaration;
if (func.argType != 'void') {
argSignature = '${func.argType} arg';
sendArgument = 'requestMap';
requestMapDeclaration =
'final Map<dynamic, dynamic> requestMap = arg._toMap();';
}
indent.write(
'Future<${func.returnType}> ${func.name}($argSignature) async ');
indent.scoped('{', '}', () {
if (requestMapDeclaration != null) {
indent.writeln(requestMapDeclaration);
}
final String channelName = makeChannelName(api, func);
indent.writeln('const BasicMessageChannel<dynamic> channel =');
indent.inc();
indent.inc();
indent.writeln(
'BasicMessageChannel<dynamic>(\'$channelName\', StandardMessageCodec());');
indent.dec();
indent.dec();
indent.writeln('');
final String returnStatement = func.returnType == 'void'
? '// noop'
: 'return ${func.returnType}._fromMap(replyMap[\'${Keys.result}\']);';
indent.format(
'''final Map<dynamic, dynamic> replyMap = await channel.send($sendArgument);
if (replyMap == null) {
\tthrow PlatformException(
\t\tcode: 'channel-error',
\t\tmessage: 'Unable to establish connection on channel.',
\t\tdetails: null);
} else if (replyMap['error'] != null) {
\tfinal Map<dynamic, dynamic> error = replyMap['${Keys.error}'];
\tthrow PlatformException(
\t\t\tcode: error['${Keys.errorCode}'],
\t\t\tmessage: error['${Keys.errorMessage}'],
\t\t\tdetails: error['${Keys.errorDetails}']);
} else {
\t$returnStatement
}
''');
});
}
});
indent.writeln('');
}
void _writeFlutterApi(Indent indent, Api api,
{String Function(Method) channelNameFunc, bool isMockHandler = false}) {
assert(api.location == ApiLocation.flutter);
indent.write('abstract class ${api.name} ');
indent.scoped('{', '}', () {
for (Method func in api.methods) {
final String argSignature =
func.argType == 'void' ? '' : '${func.argType} arg';
indent.writeln('${func.returnType} ${func.name}($argSignature);');
}
indent.write('static void setup(${api.name} api) ');
indent.scoped('{', '}', () {
for (Method func in api.methods) {
indent.write('');
indent.scoped('{', '}', () {
indent.writeln('const BasicMessageChannel<dynamic> channel =');
indent.inc();
indent.inc();
final String channelName = channelNameFunc == null
? makeChannelName(api, func)
: channelNameFunc(func);
indent.writeln(
'BasicMessageChannel<dynamic>(\'$channelName\', StandardMessageCodec());');
indent.dec();
indent.dec();
final String messageHandlerSetter =
isMockHandler ? 'setMockMessageHandler' : 'setMessageHandler';
indent
.write('channel.$messageHandlerSetter((dynamic message) async ');
indent.scoped('{', '});', () {
final String argType = func.argType;
final String returnType = func.returnType;
indent.writeln(
'final Map<dynamic, dynamic> mapMessage = message as Map<dynamic, dynamic>;');
String call;
if (argType == 'void') {
call = 'api.${func.name}()';
} else {
indent.writeln(
'final $argType input = $argType._fromMap(mapMessage);');
call = 'api.${func.name}(input)';
}
if (returnType == 'void') {
indent.writeln('$call;');
if (isMockHandler) {
indent.writeln('return <dynamic, dynamic>{};');
}
} else {
indent.writeln('final $returnType output = $call;');
const String returnExpresion = 'output._toMap()';
final String returnStatement = isMockHandler
? 'return <dynamic, dynamic>{\'${Keys.result}\': $returnExpresion};'
: 'return $returnExpresion;';
indent.writeln(returnStatement);
}
});
});
}
});
});
indent.addln('');
}
/// Generates Dart source code for the given AST represented by [root],
/// outputting the code to [sink].
void generateDart(Root root, StringSink sink) {
final List<String> customClassNames =
root.classes.map((Class x) => x.name).toList();
final Indent indent = Indent(sink);
indent.writeln('// $generatedCodeWarning');
indent.writeln('// $seeAlsoWarning');
indent.writeln(
'// ignore_for_file: public_member_api_docs, non_constant_identifier_names, avoid_as, unused_import');
indent.writeln('import \'dart:async\';');
indent.writeln('import \'package:flutter/services.dart\';');
indent.writeln('');
for (Class klass in root.classes) {
sink.write('class ${klass.name} ');
indent.scoped('{', '}', () {
for (Field field in klass.fields) {
indent.writeln('${field.dataType} ${field.name};');
}
indent.writeln('// ignore: unused_element');
indent.write('Map<dynamic, dynamic> _toMap() ');
indent.scoped('{', '}', () {
indent.writeln(
'final Map<dynamic, dynamic> pigeonMap = <dynamic, dynamic>{};');
for (Field field in klass.fields) {
indent.write('pigeonMap[\'${field.name}\'] = ');
if (customClassNames.contains(field.dataType)) {
indent.addln('${field.name}._toMap();');
} else {
indent.addln('${field.name};');
}
}
indent.writeln('return pigeonMap;');
});
indent.writeln('// ignore: unused_element');
indent.write(
'static ${klass.name} _fromMap(Map<dynamic, dynamic> pigeonMap) ');
indent.scoped('{', '}', () {
indent.writeln('final ${klass.name} result = ${klass.name}();');
for (Field field in klass.fields) {
indent.write('result.${field.name} = ');
if (customClassNames.contains(field.dataType)) {
indent.addln(
'${field.dataType}._fromMap(pigeonMap[\'${field.name}\']);');
} else {
indent.addln('pigeonMap[\'${field.name}\'];');
}
}
indent.writeln('return result;');
});
});
indent.writeln('');
}
for (Api api in root.apis) {
if (api.location == ApiLocation.host) {
_writeHostApi(indent, api);
if (api.dartHostTestHandler != null) {
final Api mockApi = Api(
name: api.dartHostTestHandler,
methods: api.methods,
location: ApiLocation.flutter);
_writeFlutterApi(indent, mockApi,
channelNameFunc: (Method func) => makeChannelName(api, func),
isMockHandler: true);
}
} else if (api.location == ApiLocation.flutter) {
_writeFlutterApi(indent, api);
}
}
}