Visitar URL original
[Pigeon] Added java target for host functions. (#96) · flutter/packages@389c4fc · GitHub
Skip to content

Commit 389c4fc

Browse files
authored
[Pigeon] Added java target for host functions. (#96)
* [Pigeon] Added java target for host functions.
1 parent b1e6038 commit 389c4fc

8 files changed

Lines changed: 272 additions & 2 deletions

File tree

‎packages/pigeon/CHANGELOG.md‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 0.1.0-experimental.3
2+
3+
* Added Flutter->Host calls for Android Java.
4+
15
## 0.1.0-experimental.2
26

37
* Added Host->Flutter calls for Objective-C
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
// Copyright 2020 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'ast.dart';
6+
import 'generator_tools.dart';
7+
8+
/// Options that control how Java code will be generated.
9+
class JavaOptions {
10+
/// The name of the class that will house all the generated classes.
11+
String className;
12+
13+
/// The package where the generated class will live.
14+
String package;
15+
}
16+
17+
void _writeHostApi(Indent indent, Api api) {
18+
assert(api.location == ApiLocation.host);
19+
indent.writeln(
20+
'/** Generated interface from Pigeon that represents a handler of messages from Flutter.*/');
21+
indent.write('public interface ${api.name} ');
22+
indent.scoped('{', '}', () {
23+
for (Method method in api.methods) {
24+
indent.writeln(
25+
'${method.returnType} ${method.name}(${method.argType} arg);');
26+
}
27+
indent.addln('');
28+
indent.writeln(
29+
'/** Sets up an instance of `${api.name}` to handle messages through the `binaryMessenger` */');
30+
indent.write(
31+
'public static void setup(BinaryMessenger binaryMessenger, ${api.name} api) ');
32+
indent.scoped('{', '}', () {
33+
for (Method method in api.methods) {
34+
final String channelName = makeChannelName(api, method);
35+
indent.write('');
36+
indent.scoped('{', '}', () {
37+
indent.writeln('BasicMessageChannel<Object> channel =');
38+
indent.inc();
39+
indent.inc();
40+
indent.writeln(
41+
'new BasicMessageChannel<Object>(binaryMessenger, "$channelName", new StandardMessageCodec());');
42+
indent.dec();
43+
indent.dec();
44+
indent.write(
45+
'channel.setMessageHandler(new BasicMessageChannel.MessageHandler<Object>() ');
46+
indent.scoped('{', '});', () {
47+
indent.write(
48+
'public void onMessage(Object message, BasicMessageChannel.Reply<Object> reply) ');
49+
indent.scoped('{', '}', () {
50+
final String argType = method.argType;
51+
final String returnType = method.returnType;
52+
indent.writeln(
53+
'$argType input = $argType.fromMap((HashMap)message);');
54+
indent.writeln('$returnType output = api.${method.name}(input);');
55+
indent.writeln('reply.reply(output.toMap());');
56+
});
57+
});
58+
});
59+
}
60+
});
61+
});
62+
}
63+
64+
// void _writeFlutterApi(Indent indent, Api api) {
65+
// assert(api.location == ApiLocation.flutter);
66+
// }
67+
68+
String _makeGetter(Field field) {
69+
final String uppercased =
70+
field.name.substring(0, 1).toUpperCase() + field.name.substring(1);
71+
return 'get$uppercased';
72+
}
73+
74+
String _makeSetter(Field field) {
75+
final String uppercased =
76+
field.name.substring(0, 1).toUpperCase() + field.name.substring(1);
77+
return 'set$uppercased';
78+
}
79+
80+
/// Generates the ".java" file for the AST represented by [root] to [sink] with the
81+
/// provided [options].
82+
void generateJava(JavaOptions options, Root root, StringSink sink) {
83+
final Indent indent = Indent(sink);
84+
indent.writeln('// Autogenerated from Pigeon, do not edit directly.');
85+
indent.writeln('// See also: https://pub.dev/packages/pigeon');
86+
indent.addln('');
87+
if (options.package != null) {
88+
indent.writeln('package ${options.package};');
89+
}
90+
indent.addln('');
91+
92+
indent.writeln('import java.util.HashMap;');
93+
indent.addln('');
94+
indent.writeln('import io.flutter.plugin.common.BasicMessageChannel;');
95+
indent.writeln('import io.flutter.plugin.common.BinaryMessenger;');
96+
indent.writeln('import io.flutter.plugin.common.StandardMessageCodec;');
97+
98+
indent.addln('');
99+
assert(options.className != null);
100+
indent.writeln('/** Generated class from Pigeon. */');
101+
indent.write('public class ${options.className} ');
102+
indent.scoped('{', '}', () {
103+
for (Class klass in root.classes) {
104+
indent.addln('');
105+
indent.writeln(
106+
'/** Generated class from Pigeon that represents data sent in messages. */');
107+
indent.write('public static class ${klass.name} ');
108+
indent.scoped('{', '}', () {
109+
for (Field field in klass.fields) {
110+
indent.writeln('private ${field.dataType} ${field.name};');
111+
indent.writeln(
112+
'public ${field.dataType} ${_makeGetter(field)}() { return ${field.name}; }');
113+
indent.writeln(
114+
'public void ${_makeSetter(field)}(${field.dataType} setterArg) { this.${field.name} = setterArg; }');
115+
indent.addln('');
116+
}
117+
indent.write('HashMap toMap() ');
118+
indent.scoped('{', '}', () {
119+
indent.writeln(
120+
'HashMap<String, Object> toMapResult = new HashMap<String, Object>();');
121+
for (Field field in klass.fields) {
122+
indent.writeln('toMapResult.put("${field.name}", ${field.name});');
123+
}
124+
indent.writeln('return toMapResult;');
125+
});
126+
indent.write('static ${klass.name} fromMap(HashMap map) ');
127+
indent.scoped('{', '}', () {
128+
indent.writeln('${klass.name} fromMapResult = new ${klass.name}();');
129+
for (Field field in klass.fields) {
130+
indent.writeln(
131+
'fromMapResult.${field.name} = (${field.dataType})map.get("${field.name}");');
132+
}
133+
indent.writeln('return fromMapResult;');
134+
});
135+
});
136+
}
137+
138+
for (Api api in root.apis) {
139+
indent.addln('');
140+
if (api.location == ApiLocation.host) {
141+
_writeHostApi(indent, api);
142+
} else if (api.location == ApiLocation.flutter) {
143+
// _writeFlutterApi(indent, api);
144+
}
145+
}
146+
});
147+
}

‎packages/pigeon/lib/pigeon_lib.dart‎

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import 'dart:mirrors';
77

88
import 'package:args/args.dart';
99
import 'package:path/path.dart';
10+
import 'package:pigeon/java_generator.dart';
1011

1112
import 'ast.dart';
1213
import 'dart_generator.dart';
@@ -90,6 +91,12 @@ class PigeonOptions {
9091

9192
/// Options that control how Objective-C will be generated.
9293
ObjcOptions objcOptions = ObjcOptions();
94+
95+
/// Path to the java file that will be generated.
96+
String javaOut;
97+
98+
/// Options that control how Java will be generated.
99+
JavaOptions javaOptions = JavaOptions();
93100
}
94101

95102
/// A collection of an AST represented as a [Root] and [Error]'s.
@@ -196,6 +203,7 @@ options:
196203
help: 'REQUIRED: Path to generated dart source file (.dart).')
197204
..addOption('objc_source_out',
198205
help: 'Path to generated Objective-C source file (.m).')
206+
..addOption('java_out', help: 'Path to generated Java file (.java).')
199207
..addOption('objc_header_out',
200208
help: 'Path to generated Objective-C header file (.h).')
201209
..addOption('objc_prefix',
@@ -211,6 +219,7 @@ options:
211219
opts.objcHeaderOut = results['objc_header_out'];
212220
opts.objcSourceOut = results['objc_source_out'];
213221
opts.objcOptions.prefix = results['objc_prefix'];
222+
opts.javaOut = results['java_out'];
214223
return opts;
215224
}
216225

@@ -278,7 +287,12 @@ options:
278287

279288
final List<Error> errors = <Error>[];
280289
final List<Type> apis = <Type>[];
281-
options.objcOptions.header = basename(options.objcHeaderOut);
290+
if (options.objcHeaderOut != null) {
291+
options.objcOptions.header = basename(options.objcHeaderOut);
292+
}
293+
if (options.javaOut != null) {
294+
options.javaOptions.className = basenameWithoutExtension(options.javaOut);
295+
}
282296

283297
for (LibraryMirror library in currentMirrorSystem().libraries.values) {
284298
for (DeclarationMirror declaration in library.declarations.values) {
@@ -309,6 +323,12 @@ options:
309323
(StringSink sink) => generateObjcSource(
310324
options.objcOptions, parseResults.root, sink));
311325
}
326+
if (options.javaOut != null) {
327+
await _runGenerator(
328+
options.javaOut,
329+
(StringSink sink) =>
330+
generateJava(options.javaOptions, parseResults.root, sink));
331+
}
312332
} else {
313333
errors.add(Error(message: 'No pigeon classes found, nothing generated.'));
314334
}

‎packages/pigeon/pigeons/message.dart‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ abstract class NestedApi {
2828

2929
void setupPigeon(PigeonOptions options) {
3030
options.objcOptions.prefix = 'AC';
31+
options.javaOptions.package = 'dev.flutter.aaclarke.pigeon';
3132
}
3233

3334
@FlutterApi()

‎packages/pigeon/pubspec.yaml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: pigeon
2-
version: 0.1.0-experimental.2
2+
version: 0.1.0-experimental.3
33
description: Code generator tool to make communication between Flutter and the host platform type-safe and easier.
44
homepage: https://github.com/flutter/packages/tree/master/packages/pigeon
55
dependencies:

‎packages/pigeon/run_tests.sh‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,26 @@ test_pigeon_ios() {
2929
rm -rf $temp_dir
3030
}
3131

32+
test_pigeon_android() {
33+
temp_dir=$(mktemp -d -t pigeon)
34+
35+
pub run pigeon \
36+
--input $1 \
37+
--dart_out $temp_dir/pigeon.dart \
38+
--java_out $temp_dir/Pigeon.java \
39+
40+
if ! javac $temp_dir/Pigeon.java \
41+
-Xlint:unchecked \
42+
-classpath "$flutter_bin/cache/artifacts/engine/android-x64/flutter.jar"; then
43+
echo "javac $temp_dir/Pigeon.java failed"
44+
exit 1
45+
fi
46+
47+
rm -rf $temp_dir
48+
}
49+
3250
pub run test test/
51+
test_pigeon_android ./pigeons/message.dart
3352
test_pigeon_ios ./pigeons/message.dart
3453
test_pigeon_ios ./pigeons/host2flutter.dart
3554

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright 2020 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:test/test.dart';
6+
import 'package:pigeon/java_generator.dart';
7+
import 'package:pigeon/ast.dart';
8+
9+
void main() {
10+
test('gen one class', () {
11+
final Class klass = Class()
12+
..name = 'Foobar'
13+
..fields = <Field>[
14+
Field()
15+
..name = 'field1'
16+
..dataType = 'int'
17+
];
18+
final Root root = Root()
19+
..apis = <Api>[]
20+
..classes = <Class>[klass];
21+
final StringBuffer sink = StringBuffer();
22+
final JavaOptions javaOptions = JavaOptions();
23+
javaOptions.className = 'Messages';
24+
generateJava(javaOptions, root, sink);
25+
final String code = sink.toString();
26+
expect(code, contains('public class Messages'));
27+
expect(code, contains('public static class Foobar'));
28+
expect(code, contains('private int field1;'));
29+
});
30+
31+
test('package', () {
32+
final Class klass = Class()
33+
..name = 'Foobar'
34+
..fields = <Field>[
35+
Field()
36+
..name = 'field1'
37+
..dataType = 'int'
38+
];
39+
final Root root = Root()
40+
..apis = <Api>[]
41+
..classes = <Class>[klass];
42+
final StringBuffer sink = StringBuffer();
43+
final JavaOptions javaOptions = JavaOptions();
44+
javaOptions.className = 'Messages';
45+
javaOptions.package = 'com.google.foobar';
46+
generateJava(javaOptions, root, sink);
47+
final String code = sink.toString();
48+
expect(code, contains('package com.google.foobar;'));
49+
expect(code, contains('HashMap toMap()'));
50+
});
51+
52+
test('gen one host api', () {
53+
final Root root = Root(apis: <Api>[
54+
Api(name: 'Api', location: ApiLocation.host, methods: <Method>[
55+
Method(name: 'doSomething', argType: 'Input', returnType: 'Output')
56+
])
57+
], classes: <Class>[
58+
Class(
59+
name: 'Input',
60+
fields: <Field>[Field(name: 'input', dataType: 'String')]),
61+
Class(
62+
name: 'Output',
63+
fields: <Field>[Field(name: 'output', dataType: 'String')])
64+
]);
65+
final StringBuffer sink = StringBuffer();
66+
final JavaOptions javaOptions = JavaOptions();
67+
javaOptions.className = 'Messages';
68+
generateJava(javaOptions, root, sink);
69+
final String code = sink.toString();
70+
expect(code, contains('public interface Api'));
71+
expect(code, matches('Output.*doSomething.*Input'));
72+
});
73+
}

‎packages/pigeon/test/pigeon_lib_test.dart‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ void main() {
5151
expect(opts.dartOut, equals('foo.dart'));
5252
});
5353

54+
test('parse args - input', () {
55+
final PigeonOptions opts =
56+
Pigeon.parseArgs(<String>['--java_out', 'foo.java']);
57+
expect(opts.javaOut, equals('foo.java'));
58+
});
59+
5460
test('parse args - objc_header_out', () {
5561
final PigeonOptions opts =
5662
Pigeon.parseArgs(<String>['--objc_header_out', 'foo.h']);

0 commit comments

Comments
 (0)