mirror of
https://github.com/dart-lang/sdk
synced 2024-10-04 16:44:59 +00:00
Reland "[ VM / Service ] Pulled in vm_service_drivers from its own repo."
Changes from original CL:
* Removed service_undocumented.md
* Removed generation of wrappers for undocumented RPCs
* Cleaned up generation code which was used for generating wrappers for undocumented RPCs
* Removed JARs from pkg/vm_service/java/third_party
This reverts commit 477a3c4748
.
Change-Id: I8d36733c8b2602e4935c3f23698d3f7c97a20187
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/110135
Reviewed-by: Siva Annamalai <asiva@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
This commit is contained in:
parent
5d5a0c0164
commit
368b3f0ab4
|
@ -105,6 +105,7 @@ typed_data:third_party/pkg/typed_data/lib
|
|||
unittest:third_party/pkg/unittest/lib
|
||||
usage:third_party/pkg/usage/lib
|
||||
vm:pkg/vm/lib
|
||||
vm_service:pkg/vm_service/lib
|
||||
watcher:third_party/pkg/watcher/lib
|
||||
web_components:third_party/pkg/web_components/lib
|
||||
web_socket_channel:third_party/pkg/web_socket_channel/lib
|
||||
|
|
4
pkg/vm_service/.gitignore
vendored
Normal file
4
pkg/vm_service/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
*.iml
|
||||
.dart_tool
|
||||
.packages
|
||||
pubspec.lock
|
240
pkg/vm_service/CHANGELOG.md
Normal file
240
pkg/vm_service/CHANGELOG.md
Normal file
|
@ -0,0 +1,240 @@
|
|||
# Changelog
|
||||
|
||||
## 1.0.0
|
||||
- Migrated `vm_service_lib` into the Dart SDK.
|
||||
- Renamed from `package:vm_service_lib` to `package:vm_service`.
|
||||
- Switched versioning system to follow semantic versioning standards instead of
|
||||
pinning versions to match the service protocol version.
|
||||
|
||||
## 3.22.2
|
||||
- Fix `registerService` RPC and `Service` stream not being handled correctly.
|
||||
- Fixed failing tests.
|
||||
|
||||
## 3.22.1
|
||||
- **breaking**: Changed type of `library` property in `Class` objects from
|
||||
`ObjectRef` to `LibraryRef`.
|
||||
|
||||
## 3.22.0
|
||||
- The `registerService` RPC and `Service` stream are now public.
|
||||
- `Event` has been updated to include the optional `service`, `method`, and
|
||||
`alias` properties.
|
||||
|
||||
## 3.21.1
|
||||
- **breaking**: Fixed issue where an `InstanceRef` of type `null` could be returned
|
||||
instead of null for non-`InstanceRef` properties and return values. As a
|
||||
result, some property and return types have been changed from Obj to their
|
||||
correct types.
|
||||
|
||||
## 3.21.0
|
||||
- support service protocol version 3.21
|
||||
|
||||
## 3.20.0+2
|
||||
- allow optional params in `getVMTimeline`
|
||||
|
||||
## 3.20.0+1
|
||||
- handle null isolate ids in `callServiceExtension`
|
||||
- add backwards compatibility for `InstanceSet` and `AllocationProfile`
|
||||
|
||||
## 3.20.0
|
||||
- rev to 3.20.0; expose public methods added in 3.17 - 3.20 VM Service Protocol versions
|
||||
|
||||
## 3.17.0+1
|
||||
- generate a list of available event streams
|
||||
|
||||
## 3.17.0
|
||||
- rev to 3.17.0; expose the Logging event and the getMemoryUsage call
|
||||
|
||||
## 3.15.1+2
|
||||
- fix handling of errors in registered service callbacks to return valid
|
||||
JSON-RPC errors and avoid the client getting "Service Disappeared" responses
|
||||
|
||||
## 3.15.1+1
|
||||
- rename `getVmWsUriFromObservatoryUri` to `convertToWebSocketUrl`
|
||||
- fix an assignment issue in `evaluate`
|
||||
|
||||
## 3.15.1
|
||||
- Add `getVmWsUriFromObservatoryUri`, a helper function to convert observatory URIs
|
||||
into the required WebSocket URI for connecting to the VM service.
|
||||
|
||||
## 3.15.0
|
||||
- support service protocol version 3.15
|
||||
- fix an issue decoding null `Script.tokenPosTable` values
|
||||
|
||||
## 3.14.3-dev.4
|
||||
- Add support for the `_Service` stream in the `VmServerConnection` directly.
|
||||
|
||||
## 3.14.3-dev.3
|
||||
- Add support for automatically delegating service extension requests to the
|
||||
client which registered them.
|
||||
- This is only for services that are registered via the vm service protocol,
|
||||
services registered through `dart:developer` should be handled by the
|
||||
`VmServiceInterface` implementation (which should invoke the registered
|
||||
callback directly).
|
||||
- Added a `ServiceExtensionRegistry` class, which tracks which clients have
|
||||
registered which service extensions.
|
||||
- **breaking**: Renamed `VmServer` to `VmServerConnection`.
|
||||
- One `VmServerConnection` should be created _per client_ connection to the
|
||||
server. These should typically all share the same underlying
|
||||
`VmServiceInterface` instance, as well as the same
|
||||
`ServiceExtensionRegistry` instance.
|
||||
|
||||
## 3.14.3-dev.2
|
||||
- Add `callServiceExtension` method to the `VmServiceInterface` class.
|
||||
- The `VmServer` will delegate all requests whose methods start with `ext.` to
|
||||
that implementation.
|
||||
|
||||
## 3.14.3-dev.1
|
||||
- Add `VmServiceInterface` and `VmServer` classes, which can handle routing
|
||||
jsonrpc2 requests to a `VmServiceInterface` instance, and serializing the
|
||||
responses back.
|
||||
|
||||
## 3.14.3-dev.0
|
||||
- Add `toJson` methods to all classes.
|
||||
|
||||
## 3.14.2
|
||||
- fix code generation for the `getSourceReport` call
|
||||
|
||||
## 3.14.1
|
||||
- address an encoding issue with stdout / stderr text
|
||||
|
||||
## 3.14.0
|
||||
- regenerate for `v3.14`
|
||||
- bump to a major version numbering scheme
|
||||
|
||||
## 0.3.10+2
|
||||
- work around an issue de-serializing Instance.closureContext
|
||||
|
||||
## 0.3.10+1
|
||||
- fix an issue de-serializing some object types
|
||||
|
||||
## 0.3.10
|
||||
- regenerate for `v3.12`
|
||||
- expose `isolate.getScripts()`
|
||||
- expose `isolate.getInstances()`
|
||||
|
||||
## 0.3.9+2
|
||||
- handle nulls for `Script.source`
|
||||
- fix a decoding issue for `Script.tokenPosTable`
|
||||
|
||||
## 0.3.9+1
|
||||
- rev to version `3.9` of the spec
|
||||
- expose `invoke`
|
||||
|
||||
## 0.3.9
|
||||
- Rename the `Null` type to `NullVal`
|
||||
|
||||
## 0.3.8
|
||||
- upgrades for Dart 2 dependencies
|
||||
|
||||
## 0.3.7
|
||||
- ensure the library works with Dart 2
|
||||
- regenerate the library based on the 3.8-dev spec
|
||||
- now require a minimum of a 2.0.0-dev Dart SDK
|
||||
- update to not use deprecated dart:convert constants
|
||||
|
||||
## 0.3.6
|
||||
- workaround for an issue with the type of @Library refs for VM objects
|
||||
|
||||
## 0.3.5+1
|
||||
- bug fix for deserializing `Instance` objects
|
||||
|
||||
## 0.3.5
|
||||
- improve access to the profiling APIs
|
||||
|
||||
## 0.3.4
|
||||
- more strong mode runtime fixes
|
||||
- expose some undocumented (and unsupported) service protocol methods
|
||||
|
||||
## 0.3.3
|
||||
- fix strong mode issues at runtime (with JSLists and Lists)
|
||||
- expose the ability to evaluate in the scope of another object
|
||||
- expose the async causal frame info
|
||||
- expose the `awaiterFrames` field
|
||||
- expose the `frameIndex` param for the step call
|
||||
|
||||
## 0.3.2+1
|
||||
- fix a strong mode issue in the generated Dart library
|
||||
|
||||
## 0.3.2
|
||||
- expose the `PausePostRequest` event
|
||||
|
||||
## 0.3.1
|
||||
- fix a parsing issue with ExtensionData
|
||||
|
||||
## 0.2.4
|
||||
- expose the service protocol timeline API
|
||||
- add the new `None` event type
|
||||
|
||||
## 0.2.3
|
||||
- include the name of the calling method in RPC errors
|
||||
|
||||
## 0.2.2
|
||||
- fixed several strong mode analysis issues
|
||||
|
||||
## 0.2.1
|
||||
- upgrade to service protocol version `3.3`
|
||||
|
||||
## 0.2.0
|
||||
- upgrade to service protocol version `3.2`
|
||||
|
||||
## 0.1.2
|
||||
- fixed a bug with the `ServiceExtensionAdded` event
|
||||
|
||||
## 0.1.1
|
||||
- expose the new 'Extension' event information
|
||||
|
||||
## 0.1.0
|
||||
- rev to 0.1.0; declare first stable API version
|
||||
|
||||
## 0.0.13
|
||||
- improve the toString() message for RPCError
|
||||
|
||||
## 0.0.12
|
||||
- bug fix for parsing MapAssociations
|
||||
|
||||
## 0.0.11
|
||||
- bug fix to the service extension API
|
||||
|
||||
## 0.0.10
|
||||
- expose a service extension API
|
||||
|
||||
## 0.0.9
|
||||
- update to the latest spec to capture the `Event.inspectee` field
|
||||
|
||||
## 0.0.8
|
||||
- allow listening to arbitrary event types
|
||||
- use Strings for the enum types (to allow for unknown enum values)
|
||||
|
||||
## 0.0.7
|
||||
- make the diagnostic logging synchronous
|
||||
- remove a workaround for a VM bug (fixed in 1.13.0-dev.7.3)
|
||||
- several strong mode fixes
|
||||
|
||||
## 0.0.6
|
||||
- added `exceptionPauseMode` to the Isolate class
|
||||
- added `hashCode` and `operator==` methods to classes supporting object identity
|
||||
- work around a VM bug with the `type` field of `BoundVariable` and `BoundField`
|
||||
|
||||
## 0.0.5
|
||||
- added more dartdocs
|
||||
- moved back to using Dart enums
|
||||
- changed from optional positional params to optional named params
|
||||
|
||||
## 0.0.4
|
||||
- enum redux
|
||||
|
||||
## 0.0.3
|
||||
- update to use a custom enum class
|
||||
- upgrade to the latest service protocol spec
|
||||
|
||||
## 0.0.2
|
||||
- added the `setExceptionPauseMode` method
|
||||
- fixed an issue with enum parsing
|
||||
|
||||
## 0.0.1
|
||||
- first publish
|
||||
- upgraded the library to the 3.0 version of the service protocol
|
||||
- upgraded the library to the 2.0 version of the service protocol
|
||||
- copied basic Dart API generator from Atom Dart Plugin
|
||||
https://github.com/dart-atom/dartlang/tree/master/tool
|
||||
- refactored Dart code to generate Java client as well as Dart client
|
26
pkg/vm_service/LICENSE
Normal file
26
pkg/vm_service/LICENSE
Normal file
|
@ -0,0 +1,26 @@
|
|||
Copyright 2015, the Dart project authors. All rights reserved.
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following
|
||||
disclaimer in the documentation and/or other materials provided
|
||||
with the distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived
|
||||
from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
20
pkg/vm_service/README.md
Normal file
20
pkg/vm_service/README.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
# vm_service
|
||||
|
||||
[![pub package](https://img.shields.io/pub/v/vm_service.svg)](https://pub.dartlang.org/packages/vm_service)
|
||||
|
||||
A library to access the VM Service Protocol.
|
||||
|
||||
## Usage
|
||||
|
||||
See the
|
||||
[example](https://github.com/dart-lang/sdk/blob/master/pkg/vm_service/example/vm_service_tester.dart)
|
||||
for a simple use of the library's API.
|
||||
|
||||
The VM Service Protocol spec can be found at
|
||||
[github.com/dart-lang/sdk/runtime/vm/service/service.md](https://github.com/dart-lang/sdk/blob/master/runtime/vm/service/service.md).
|
||||
|
||||
## Features and bugs
|
||||
|
||||
Please file feature requests and bugs at the [issue tracker][tracker].
|
||||
|
||||
[tracker]: https://github.com/dart-lang/sdk/issues
|
6
pkg/vm_service/analysis_options.yaml
Normal file
6
pkg/vm_service/analysis_options.yaml
Normal file
|
@ -0,0 +1,6 @@
|
|||
include: package:pedantic/analysis_options.1.7.0.yaml
|
||||
|
||||
linter:
|
||||
rules:
|
||||
- directives_ordering
|
||||
- prefer_generic_function_type_aliases
|
54
pkg/vm_service/example/sample_isolates.dart
Normal file
54
pkg/vm_service/example/sample_isolates.dart
Normal file
|
@ -0,0 +1,54 @@
|
|||
// Copyright (c) 2016, 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 'dart:async';
|
||||
import 'dart:isolate';
|
||||
|
||||
main(List<String> args) async {
|
||||
var arr = newArray(5);
|
||||
var arr2 = newArray(417);
|
||||
var hash1 = newHash(5);
|
||||
var hash2 = newHash(417);
|
||||
|
||||
// ignore unused
|
||||
arr.length;
|
||||
arr2.length;
|
||||
hash1.length;
|
||||
hash2.length;
|
||||
|
||||
startIsolate(1);
|
||||
startIsolate(2);
|
||||
startIsolate(3);
|
||||
startIsolate(4);
|
||||
|
||||
await new Future.delayed(new Duration(seconds: 5));
|
||||
|
||||
print('at end of main...');
|
||||
}
|
||||
|
||||
void startIsolate(int val) {
|
||||
Isolate.spawn(isolateEntry, val);
|
||||
}
|
||||
|
||||
isolateEntry(message) async {
|
||||
print('starting $message');
|
||||
await new Future.delayed(new Duration(seconds: message));
|
||||
print('ending $message');
|
||||
}
|
||||
|
||||
List newArray(int length) {
|
||||
List l = [];
|
||||
for (int i = 0; i < length; i++) {
|
||||
l.add('entry_$i');
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
Map newHash(int length) {
|
||||
Map m = {};
|
||||
for (int i = 0; i < length; i++) {
|
||||
m['entry_$i'] = i;
|
||||
}
|
||||
return m;
|
||||
}
|
50
pkg/vm_service/example/sample_main.dart
Normal file
50
pkg/vm_service/example/sample_main.dart
Normal file
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) 2015, 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 'dart:developer' as developer;
|
||||
|
||||
void main(List<String> args) {
|
||||
String local1 = 'abcd';
|
||||
int local2 = 2;
|
||||
var longList = [1, "hello", 3, 5, 7, 11, 13, 14, 15, 16, 17, 18, 19, 20];
|
||||
var deepList = [
|
||||
new Bar(),
|
||||
[
|
||||
[
|
||||
[
|
||||
[
|
||||
[7]
|
||||
]
|
||||
],
|
||||
"end"
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
print('hello from main');
|
||||
|
||||
foo(1);
|
||||
foo(local2);
|
||||
foo(3);
|
||||
foo(local1.length);
|
||||
|
||||
print(longList);
|
||||
print(deepList);
|
||||
|
||||
developer.debugger();
|
||||
|
||||
print('exiting...');
|
||||
}
|
||||
|
||||
void foo(int val) {
|
||||
print('val: ${val}');
|
||||
}
|
||||
|
||||
class Bar extends FooBar {
|
||||
String field1 = "my string";
|
||||
}
|
||||
|
||||
class FooBar {
|
||||
int field2 = 47;
|
||||
}
|
1004
pkg/vm_service/example/vm_service_assert.dart
Normal file
1004
pkg/vm_service/example/vm_service_assert.dart
Normal file
File diff suppressed because it is too large
Load diff
219
pkg/vm_service/example/vm_service_tester.dart
Normal file
219
pkg/vm_service/example/vm_service_tester.dart
Normal file
|
@ -0,0 +1,219 @@
|
|||
// Copyright (c) 2015, 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.
|
||||
|
||||
library service_tester;
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:pedantic/pedantic.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
import 'package:vm_service/vm_service_io.dart';
|
||||
|
||||
final String host = 'localhost';
|
||||
final int port = 7575;
|
||||
|
||||
VmService serviceClient;
|
||||
|
||||
void main() {
|
||||
Process process;
|
||||
|
||||
tearDown(() {
|
||||
process?.kill();
|
||||
});
|
||||
|
||||
test('integration', () async {
|
||||
String sdk = path.dirname(path.dirname(Platform.resolvedExecutable));
|
||||
|
||||
print('Using sdk at ${sdk}.');
|
||||
|
||||
// pause_isolates_on_start, pause_isolates_on_exit
|
||||
process = await Process.start('${sdk}/bin/dart', [
|
||||
'--pause_isolates_on_start',
|
||||
'--enable-vm-service=${port}',
|
||||
'--disable-service-auth-codes',
|
||||
'example/sample_main.dart'
|
||||
]);
|
||||
|
||||
print('dart process started');
|
||||
|
||||
// ignore: unawaited_futures
|
||||
process.exitCode.then((code) => print('vm exited: ${code}'));
|
||||
// ignore: strong_mode_down_cast_composite
|
||||
process.stdout.transform(utf8.decoder).listen(print);
|
||||
// ignore: strong_mode_down_cast_composite
|
||||
process.stderr.transform(utf8.decoder).listen(print);
|
||||
|
||||
await new Future.delayed(new Duration(milliseconds: 500));
|
||||
|
||||
serviceClient = await vmServiceConnect(host, port, log: new StdoutLog());
|
||||
|
||||
print('socket connected');
|
||||
|
||||
serviceClient.onSend.listen((str) => print('--> ${str}'));
|
||||
|
||||
// The next listener will bail out if you toggle this to false, which we need
|
||||
// to do for some things like the custom service registration tests.
|
||||
var checkResponseJsonCompatibility = true;
|
||||
serviceClient.onReceive.listen((str) {
|
||||
print('<-- ${str}');
|
||||
|
||||
if (!checkResponseJsonCompatibility) return;
|
||||
|
||||
// For each received event, check that we can deserialize it and
|
||||
// reserialize it back to the same exact representation (minus private
|
||||
// fields).
|
||||
var json = jsonDecode(str);
|
||||
var originalJson = json['result'] as Map<String, dynamic>;
|
||||
if (originalJson == null && json['method'] == 'streamNotify') {
|
||||
originalJson = json['params']['event'];
|
||||
}
|
||||
expect(originalJson, isNotNull, reason: 'Unrecognized event type! $json');
|
||||
|
||||
// ignore: invalid_use_of_visible_for_testing_member
|
||||
var instance =
|
||||
createServiceObject(originalJson, const ['Event', 'Success']);
|
||||
expect(instance, isNotNull,
|
||||
reason: 'failed to deserialize object $originalJson!');
|
||||
|
||||
var reserializedJson = (instance as dynamic).toJson();
|
||||
|
||||
forEachNestedMap(originalJson, (obj) {
|
||||
// Private fields that we don't reproduce
|
||||
obj.removeWhere((k, v) => k.startsWith('_'));
|
||||
// Extra fields that aren't specified and we don't reproduce
|
||||
obj.remove('isExport');
|
||||
});
|
||||
|
||||
forEachNestedMap(reserializedJson, (obj) {
|
||||
// We provide explicit defaults for these, need to remove them.
|
||||
obj.remove('valueAsStringIsTruncated');
|
||||
});
|
||||
|
||||
expect(reserializedJson, equals(originalJson));
|
||||
});
|
||||
|
||||
serviceClient.onIsolateEvent.listen((e) => print('onIsolateEvent: ${e}'));
|
||||
serviceClient.onDebugEvent.listen((e) => print('onDebugEvent: ${e}'));
|
||||
serviceClient.onGCEvent.listen((e) => print('onGCEvent: ${e}'));
|
||||
serviceClient.onStdoutEvent.listen((e) => print('onStdoutEvent: ${e}'));
|
||||
serviceClient.onStderrEvent.listen((e) => print('onStderrEvent: ${e}'));
|
||||
|
||||
unawaited(serviceClient.streamListen(EventStreams.kIsolate));
|
||||
unawaited(serviceClient.streamListen(EventStreams.kDebug));
|
||||
unawaited(serviceClient.streamListen(EventStreams.kStdout));
|
||||
|
||||
VM vm = await serviceClient.getVM();
|
||||
print('hostCPU=${vm.hostCPU}');
|
||||
print(await serviceClient.getVersion());
|
||||
List<IsolateRef> isolates = await vm.isolates;
|
||||
print(isolates);
|
||||
|
||||
// Disable the json reserialization checks since custom services are not
|
||||
// supported.
|
||||
checkResponseJsonCompatibility = false;
|
||||
await testServiceRegistration();
|
||||
checkResponseJsonCompatibility = true;
|
||||
|
||||
await testScriptParse(vm.isolates.first);
|
||||
await testSourceReport(vm.isolates.first);
|
||||
|
||||
IsolateRef isolateRef = isolates.first;
|
||||
print(await serviceClient.resume(isolateRef.id));
|
||||
|
||||
serviceClient.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
// Deeply traverses a map and calls [cb] with each nested map and the
|
||||
// parent map.
|
||||
void forEachNestedMap(Map input, Function(Map) cb) {
|
||||
var queue = Queue.from([input]);
|
||||
while (queue.isNotEmpty) {
|
||||
var next = queue.removeFirst();
|
||||
if (next is Map) {
|
||||
cb(next);
|
||||
queue.addAll(next.values);
|
||||
} else if (next is List) {
|
||||
queue.addAll(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future testServiceRegistration() async {
|
||||
const String serviceName = 'serviceName';
|
||||
const String serviceAlias = 'serviceAlias';
|
||||
const String movedValue = 'movedValue';
|
||||
serviceClient.registerServiceCallback(serviceName,
|
||||
(Map<String, dynamic> params) async {
|
||||
assert(params['input'] == movedValue);
|
||||
return <String, dynamic>{
|
||||
'result': {'output': params['input']}
|
||||
};
|
||||
});
|
||||
await serviceClient.registerService(serviceName, serviceAlias);
|
||||
VmService otherClient =
|
||||
await vmServiceConnect(host, port, log: new StdoutLog());
|
||||
Completer completer = new Completer();
|
||||
otherClient.onEvent('_Service').listen((e) async {
|
||||
if (e.service == serviceName && e.kind == EventKind.kServiceRegistered) {
|
||||
assert(e.alias == serviceAlias);
|
||||
Response response = await serviceClient.callMethod(
|
||||
e.method,
|
||||
args: <String, dynamic>{'input': movedValue},
|
||||
);
|
||||
assert(response.json['output'] == movedValue);
|
||||
completer.complete();
|
||||
}
|
||||
});
|
||||
await otherClient.streamListen('_Service');
|
||||
await completer.future;
|
||||
}
|
||||
|
||||
Future testScriptParse(IsolateRef isolateRef) async {
|
||||
final Isolate isolate = await serviceClient.getIsolate(isolateRef.id);
|
||||
final Library rootLibrary =
|
||||
await serviceClient.getObject(isolateRef.id, isolate.rootLib.id);
|
||||
final ScriptRef scriptRef = rootLibrary.scripts.first;
|
||||
|
||||
final Script script =
|
||||
await serviceClient.getObject(isolateRef.id, scriptRef.id);
|
||||
print(script);
|
||||
print(script.uri);
|
||||
print(script.library);
|
||||
print(script.source.length);
|
||||
print(script.tokenPosTable.length);
|
||||
}
|
||||
|
||||
Future testSourceReport(IsolateRef isolateRef) async {
|
||||
final Isolate isolate = await serviceClient.getIsolate(isolateRef.id);
|
||||
final Library rootLibrary =
|
||||
await serviceClient.getObject(isolateRef.id, isolate.rootLib.id);
|
||||
final ScriptRef scriptRef = rootLibrary.scripts.first;
|
||||
|
||||
// make sure some code has run
|
||||
await serviceClient.resume(isolateRef.id);
|
||||
await Future.delayed(const Duration(milliseconds: 25));
|
||||
|
||||
final SourceReport sourceReport = await serviceClient.getSourceReport(
|
||||
isolateRef.id, [SourceReportKind.kCoverage],
|
||||
scriptId: scriptRef.id);
|
||||
for (SourceReportRange range in sourceReport.ranges) {
|
||||
print(' $range');
|
||||
if (range.coverage != null) {
|
||||
print(' ${range.coverage}');
|
||||
}
|
||||
}
|
||||
print(sourceReport);
|
||||
}
|
||||
|
||||
class StdoutLog extends Log {
|
||||
void warning(String message) => print(message);
|
||||
|
||||
void severe(String message) => print(message);
|
||||
}
|
4
pkg/vm_service/java/.gitignore
vendored
Normal file
4
pkg/vm_service/java/.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
build/
|
||||
dist/
|
||||
out/
|
||||
src/gen/
|
54
pkg/vm_service/java/build.xml
Normal file
54
pkg/vm_service/java/build.xml
Normal file
|
@ -0,0 +1,54 @@
|
|||
<project name="vm_service_lib" default="dist">
|
||||
<target name="clean">
|
||||
<delete dir="build"/>
|
||||
</target>
|
||||
|
||||
<target name="init">
|
||||
<mkdir dir="build"/>
|
||||
<mkdir dir="dist"/>
|
||||
|
||||
<property environment="env"/>
|
||||
<property file="version.properties" prefix="service"/>
|
||||
<property name="build.id" value=""/>
|
||||
|
||||
<property
|
||||
name="path"
|
||||
value="third_party/gson/gson-2.2.4.jar;third_party/guava/guava-13.0.1.jar;third_party/weberknecht/weberknecht-0.1.5.jar"/>
|
||||
</target>
|
||||
|
||||
<target name="compile" depends="init">
|
||||
<!-- compile library source -->
|
||||
<mkdir dir="build/classes"/>
|
||||
<javac srcdir="src" destdir="build/classes" includeantruntime="false"
|
||||
source="1.7" target="1.7"
|
||||
classpath="${path}" debug="true"/>
|
||||
|
||||
<!-- compile tests -->
|
||||
<mkdir dir="build/test"/>
|
||||
<javac srcdir="test" destdir="build/test" includeantruntime="false"
|
||||
classpath="${path};build/classes" debug="true"/>
|
||||
</target>
|
||||
|
||||
<target name="jar" depends="compile">
|
||||
<copy file="version.properties" todir="build/classes"/>
|
||||
<jar destfile="build/vm_service_lib.jar" basedir="build/classes"/>
|
||||
<jar destfile="build/vm_service_lib-src.jar" basedir="src"/>
|
||||
</target>
|
||||
|
||||
<target name="test" depends="compile">
|
||||
<java classname="org.dartlang.vm.service.VmServiceTest" fork="true" failonerror="true">
|
||||
<arg value="${env.DART_SDK}"/>
|
||||
<classpath>
|
||||
<pathelement path="${path}"/>
|
||||
<pathelement location="build/classes"/>
|
||||
<pathelement location="build/test"/>
|
||||
</classpath>
|
||||
</java>
|
||||
</target>
|
||||
|
||||
<target name="dist" depends="jar,test">
|
||||
<!-- copy and rename the library -->
|
||||
<copy file="build/vm_service_lib.jar" tofile="dist/vm_service_lib-${service.version}${build.id}.jar"/>
|
||||
<copy file="build/vm_service_lib-src.jar" tofile="dist/vm_service_lib-${service.version}${build.id}-src.jar"/>
|
||||
</target>
|
||||
</project>
|
1
pkg/vm_service/java/classpath.txt
Normal file
1
pkg/vm_service/java/classpath.txt
Normal file
|
@ -0,0 +1 @@
|
|||
third_party/gson/gson-2.2.4.jar:third_party/guava/guava-13.0.1.jar:third_party/weberknecht/weberknecht-0.1.5.jar
|
53
pkg/vm_service/java/example/sample_exception.dart
Normal file
53
pkg/vm_service/java/example/sample_exception.dart
Normal file
|
@ -0,0 +1,53 @@
|
|||
// Copyright (c) 2015, 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.
|
||||
|
||||
void main(List<String> args) {
|
||||
// Hijinks to remove an analysis warning.
|
||||
dynamic local1;
|
||||
if (true) local1 = 'abcd';
|
||||
|
||||
int local2 = 2;
|
||||
var longList = [1, "hello", 3, 5, 7, 11, 13, 14, 15, 16, 17, 18, 19, 20];
|
||||
var deepList = [
|
||||
new Bar(),
|
||||
[
|
||||
[
|
||||
[
|
||||
[
|
||||
[7]
|
||||
]
|
||||
],
|
||||
"end"
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
print('hello from main');
|
||||
|
||||
// throw a caught exception
|
||||
try {
|
||||
foo(local1.baz());
|
||||
} catch (e) {
|
||||
print('-----------------');
|
||||
print('caught $e');
|
||||
print('-----------------');
|
||||
}
|
||||
foo(local2);
|
||||
|
||||
print(longList);
|
||||
print(deepList);
|
||||
print('exiting...');
|
||||
}
|
||||
|
||||
void foo(int val) {
|
||||
print('val: ${val}');
|
||||
}
|
||||
|
||||
class Bar extends FooBar {
|
||||
String field1 = "my string";
|
||||
}
|
||||
|
||||
class FooBar {
|
||||
int field2 = 47;
|
||||
}
|
46
pkg/vm_service/java/example/sample_main.dart
Normal file
46
pkg/vm_service/java/example/sample_main.dart
Normal file
|
@ -0,0 +1,46 @@
|
|||
// Copyright (c) 2015, 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.
|
||||
|
||||
void main(List<String> args) {
|
||||
String local1 = 'abcd';
|
||||
int local2 = 2;
|
||||
var longList = [1, "hello", 3, 5, 7, 11, 13, 14, 15, 16, 17, 18, 19, 20];
|
||||
var deepList = [
|
||||
new Bar(),
|
||||
[
|
||||
[
|
||||
[
|
||||
[
|
||||
[7]
|
||||
]
|
||||
],
|
||||
"end"
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
print('hello from main');
|
||||
|
||||
foo(1);
|
||||
foo(local2);
|
||||
foo(3);
|
||||
foo(local1.length);
|
||||
|
||||
print(longList);
|
||||
print(deepList);
|
||||
|
||||
print('exiting...');
|
||||
}
|
||||
|
||||
void foo(int val) {
|
||||
print('val: ${val}');
|
||||
}
|
||||
|
||||
class Bar extends FooBar {
|
||||
String field1 = "my string";
|
||||
}
|
||||
|
||||
class FooBar {
|
||||
int field2 = 47;
|
||||
}
|
5
pkg/vm_service/java/javaconfig.json
Normal file
5
pkg/vm_service/java/javaconfig.json
Normal file
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"sourcePath": ["src", "test"],
|
||||
"classPathFile": "classpath.txt",
|
||||
"outputDirectory": "build/classes"
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright (c) 2017, the Dart project authors.
|
||||
*
|
||||
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.dartlang.vm.service;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
public interface RemoteServiceCompleter {
|
||||
/**
|
||||
* Should be called when a service request completes successfully.
|
||||
*
|
||||
* @param result the result of the request
|
||||
*/
|
||||
void result(JsonObject result);
|
||||
|
||||
/**
|
||||
* Should be called when a service request completes with an error.
|
||||
*
|
||||
* @param code the error code generated by the request
|
||||
* @param message the description of the error
|
||||
* @param data [optional] the description of the error
|
||||
*/
|
||||
void error(int code, String message, JsonObject data);
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Copyright (c) 2017, the Dart project authors.
|
||||
*
|
||||
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.dartlang.vm.service;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* Interface used by {@link VmService} to register callbacks to services.
|
||||
*/
|
||||
public interface RemoteServiceRunner {
|
||||
/**
|
||||
* Called when a service request has been received.
|
||||
*
|
||||
* @param params the parameters of the request
|
||||
* @param completer the completer to invoke at the end of the execution
|
||||
*/
|
||||
void run(JsonObject params, RemoteServiceCompleter completer);
|
||||
}
|
|
@ -0,0 +1,635 @@
|
|||
/*
|
||||
* Copyright (c) 2015, the Dart project authors.
|
||||
*
|
||||
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.dartlang.vm.service;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import de.roderick.weberknecht.WebSocket;
|
||||
import de.roderick.weberknecht.WebSocketEventHandler;
|
||||
import de.roderick.weberknecht.WebSocketException;
|
||||
import de.roderick.weberknecht.WebSocketMessage;
|
||||
import org.dartlang.vm.service.consumer.*;
|
||||
import org.dartlang.vm.service.element.*;
|
||||
import org.dartlang.vm.service.internal.RequestSink;
|
||||
import org.dartlang.vm.service.internal.VmServiceConst;
|
||||
import org.dartlang.vm.service.internal.WebSocketRequestSink;
|
||||
import org.dartlang.vm.service.logging.Logging;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* Internal {@link VmService} base class containing non-generated code.
|
||||
*/
|
||||
@SuppressWarnings({"unused", "WeakerAccess"})
|
||||
abstract class VmServiceBase implements VmServiceConst {
|
||||
/**
|
||||
* Connect to the VM observatory service via the specified URI
|
||||
*
|
||||
* @return an API object for interacting with the VM service (not {@code null}).
|
||||
*/
|
||||
public static VmService connect(final String url) throws IOException {
|
||||
// Validate URL
|
||||
URI uri;
|
||||
try {
|
||||
uri = new URI(url);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new IOException("Invalid URL: " + url, e);
|
||||
}
|
||||
String wsScheme = uri.getScheme();
|
||||
if (!"ws".equals(wsScheme) && !"wss".equals(wsScheme)) {
|
||||
throw new IOException("Unsupported URL scheme: " + wsScheme);
|
||||
}
|
||||
|
||||
// Create web socket and observatory
|
||||
WebSocket webSocket;
|
||||
try {
|
||||
webSocket = new WebSocket(uri);
|
||||
} catch (WebSocketException e) {
|
||||
throw new IOException("Failed to create websocket: " + url, e);
|
||||
}
|
||||
final VmService vmService = new VmService();
|
||||
|
||||
// Setup event handler for forwarding responses
|
||||
webSocket.setEventHandler(new WebSocketEventHandler() {
|
||||
@Override
|
||||
public void onClose() {
|
||||
Logging.getLogger().logInformation("VM connection closed: " + url);
|
||||
|
||||
vmService.connectionClosed();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(WebSocketMessage message) {
|
||||
Logging.getLogger().logInformation("VM message: " + message.getText());
|
||||
try {
|
||||
vmService.processMessage(message.getText());
|
||||
} catch (Exception e) {
|
||||
Logging.getLogger().logError(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen() {
|
||||
vmService.connectionOpened();
|
||||
|
||||
Logging.getLogger().logInformation("VM connection open: " + url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPing() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPong() {
|
||||
}
|
||||
});
|
||||
|
||||
// Establish WebSocket Connection
|
||||
//noinspection TryWithIdenticalCatches
|
||||
try {
|
||||
webSocket.connect();
|
||||
} catch (WebSocketException e) {
|
||||
throw new IOException("Failed to connect: " + url, e);
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
// The weberknecht can occasionally throw an array index exception if a connect terminates on initial connect
|
||||
// (de.roderick.weberknecht.WebSocket.connect, WebSocket.java:126).
|
||||
throw new IOException("Failed to connect: " + url, e);
|
||||
}
|
||||
vmService.requestSink = new WebSocketRequestSink(webSocket);
|
||||
|
||||
// Check protocol version
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
final String[] errMsg = new String[1];
|
||||
vmService.getVersion(new VersionConsumer() {
|
||||
@Override
|
||||
public void onError(RPCError error) {
|
||||
String msg = "Failed to determine protocol version: " + error.getCode() + "\n message: "
|
||||
+ error.getMessage() + "\n details: " + error.getDetails();
|
||||
Logging.getLogger().logInformation(msg);
|
||||
errMsg[0] = msg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void received(Version response) {
|
||||
int major = response.getMajor();
|
||||
int minor = response.getMinor();
|
||||
if (major != VmService.versionMajor || minor != VmService.versionMinor) {
|
||||
if (major == 2 || major == 3) {
|
||||
Logging.getLogger().logInformation(
|
||||
"Difference in protocol version: client=" + VmService.versionMajor + "."
|
||||
+ VmService.versionMinor + " vm=" + major + "." + minor);
|
||||
} else {
|
||||
String msg = "Incompatible protocol version: client=" + VmService.versionMajor + "."
|
||||
+ VmService.versionMinor + " vm=" + major + "." + minor;
|
||||
Logging.getLogger().logError(msg);
|
||||
errMsg[0] = msg;
|
||||
}
|
||||
}
|
||||
latch.countDown();
|
||||
}
|
||||
});
|
||||
try {
|
||||
if (!latch.await(5, TimeUnit.SECONDS)) {
|
||||
throw new IOException("Failed to determine protocol version");
|
||||
}
|
||||
if (errMsg[0] != null) {
|
||||
throw new IOException(errMsg[0]);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
throw new RuntimeException("Interrupted while waiting for response", e);
|
||||
}
|
||||
|
||||
return vmService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to the VM observatory service on the given local port.
|
||||
*
|
||||
* @return an API object for interacting with the VM service (not {@code null}).
|
||||
*
|
||||
* @deprecated prefer the Url based constructor {@link VmServiceBase#connect}
|
||||
*/
|
||||
@Deprecated
|
||||
public static VmService localConnect(int port) throws IOException {
|
||||
return connect("ws://localhost:" + port + "/ws");
|
||||
}
|
||||
|
||||
/**
|
||||
* A mapping between {@link String} ids' and the associated {@link Consumer} that was passed when
|
||||
* the request was made. Synchronize against {@link #consumerMapLock} before accessing this field.
|
||||
*/
|
||||
private final Map<String, Consumer> consumerMap = Maps.newHashMap();
|
||||
|
||||
/**
|
||||
* The object used to synchronize access to {@link #consumerMap}.
|
||||
*/
|
||||
private final Object consumerMapLock = new Object();
|
||||
|
||||
/**
|
||||
* The unique ID for the next request.
|
||||
*/
|
||||
private final AtomicInteger nextId = new AtomicInteger();
|
||||
|
||||
/**
|
||||
* A list of objects to which {@link Event}s from the VM are forwarded.
|
||||
*/
|
||||
private final List<VmServiceListener> vmListeners = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* A list of objects to which {@link Event}s from the VM are forwarded.
|
||||
*/
|
||||
private final Map<String, RemoteServiceRunner> remoteServiceRunners = Maps.newHashMap();
|
||||
|
||||
/**
|
||||
* The channel through which observatory requests are made.
|
||||
*/
|
||||
RequestSink requestSink;
|
||||
|
||||
/**
|
||||
* Add a listener to receive {@link Event}s from the VM.
|
||||
*/
|
||||
public void addVmServiceListener(VmServiceListener listener) {
|
||||
vmListeners.add(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given listener from the VM.
|
||||
*/
|
||||
public void removeVmServiceListener(VmServiceListener listener) {
|
||||
vmListeners.remove(listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a VM RemoteServiceRunner.
|
||||
*/
|
||||
public void addServiceRunner(String service, RemoteServiceRunner runner) {
|
||||
remoteServiceRunners.put(service, runner);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a VM RemoteServiceRunner.
|
||||
*/
|
||||
public void removeServiceRunner(String service) {
|
||||
remoteServiceRunners.remove(service);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from the VM observatory service.
|
||||
*/
|
||||
public void disconnect() {
|
||||
requestSink.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the instance with the given identifier.
|
||||
*/
|
||||
public void getInstance(String isolateId, String instanceId, final GetInstanceConsumer consumer) {
|
||||
getObject(isolateId, instanceId, new GetObjectConsumer() {
|
||||
|
||||
@Override
|
||||
public void onError(RPCError error) {
|
||||
consumer.onError(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void received(Obj response) {
|
||||
if (response instanceof Instance) {
|
||||
consumer.received((Instance) response);
|
||||
} else {
|
||||
onError(RPCError.unexpected("Instance", response));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void received(Sentinel response) {
|
||||
onError(RPCError.unexpected("Instance", response));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the library with the given identifier.
|
||||
*/
|
||||
public void getLibrary(String isolateId, String libraryId, final GetLibraryConsumer consumer) {
|
||||
getObject(isolateId, libraryId, new GetObjectConsumer() {
|
||||
|
||||
@Override
|
||||
public void onError(RPCError error) {
|
||||
consumer.onError(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void received(Obj response) {
|
||||
if (response instanceof Library) {
|
||||
consumer.received((Library) response);
|
||||
} else {
|
||||
onError(RPCError.unexpected("Library", response));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void received(Sentinel response) {
|
||||
onError(RPCError.unexpected("Library", response));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public abstract void getObject(String isolateId, String objectId, GetObjectConsumer consumer);
|
||||
|
||||
/**
|
||||
* Invoke a specific service protocol extension method.
|
||||
* <p>
|
||||
* See https://api.dartlang.org/stable/dart-developer/dart-developer-library.html.
|
||||
*/
|
||||
public void callServiceExtension(String isolateId, String method, ServiceExtensionConsumer consumer) {
|
||||
JsonObject params = new JsonObject();
|
||||
params.addProperty("isolateId", isolateId);
|
||||
request(method, params, consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Invoke a specific service protocol extension method.
|
||||
* <p>
|
||||
* See https://api.dartlang.org/stable/dart-developer/dart-developer-library.html.
|
||||
*/
|
||||
public void callServiceExtension(String isolateId, String method, JsonObject params, ServiceExtensionConsumer consumer) {
|
||||
params.addProperty("isolateId", isolateId);
|
||||
request(method, params, consumer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the request and associates the request with the passed {@link Consumer}.
|
||||
*/
|
||||
protected void request(String method, JsonObject params, Consumer consumer) {
|
||||
|
||||
// Assemble the request
|
||||
String id = Integer.toString(nextId.incrementAndGet());
|
||||
JsonObject request = new JsonObject();
|
||||
|
||||
request.addProperty(JSONRPC, JSONRPC_VERSION);
|
||||
request.addProperty(ID, id);
|
||||
request.addProperty(METHOD, method);
|
||||
request.add(PARAMS, params);
|
||||
|
||||
// Cache the consumer to receive the response
|
||||
synchronized (consumerMapLock) {
|
||||
consumerMap.put(id, consumer);
|
||||
}
|
||||
|
||||
// Send the request
|
||||
requestSink.add(request);
|
||||
}
|
||||
|
||||
public void connectionOpened() {
|
||||
for (VmServiceListener listener : new ArrayList<>(vmListeners)) {
|
||||
try {
|
||||
listener.connectionOpened();
|
||||
} catch (Exception e) {
|
||||
Logging.getLogger().logError("Exception notifying listener", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void forwardEvent(String streamId, Event event) {
|
||||
for (VmServiceListener listener : new ArrayList<>(vmListeners)) {
|
||||
try {
|
||||
listener.received(streamId, event);
|
||||
} catch (Exception e) {
|
||||
Logging.getLogger().logError("Exception processing event: " + streamId + ", " + event.getJson(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void connectionClosed() {
|
||||
for (VmServiceListener listener : new ArrayList<>(vmListeners)) {
|
||||
try {
|
||||
listener.connectionClosed();
|
||||
} catch (Exception e) {
|
||||
Logging.getLogger().logError("Exception notifying listener", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
abstract void forwardResponse(Consumer consumer, String type, JsonObject json);
|
||||
|
||||
void logUnknownResponse(Consumer consumer, JsonObject json) {
|
||||
Class<? extends Consumer> consumerClass = consumer.getClass();
|
||||
StringBuilder msg = new StringBuilder();
|
||||
msg.append("Expected response for ").append(consumerClass).append("\n");
|
||||
for (Class<?> interf : consumerClass.getInterfaces()) {
|
||||
msg.append(" implementing ").append(interf).append("\n");
|
||||
}
|
||||
msg.append(" but received ").append(json);
|
||||
Logging.getLogger().logError(msg.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the response from the VM service and forward that response to the consumer associated
|
||||
* with the response id.
|
||||
*/
|
||||
void processMessage(String jsonText) {
|
||||
if (jsonText == null || jsonText.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Decode the JSON
|
||||
JsonObject json;
|
||||
try {
|
||||
json = (JsonObject) new JsonParser().parse(jsonText);
|
||||
} catch (Exception e) {
|
||||
Logging.getLogger().logError("Parse message failed: " + jsonText, e);
|
||||
return;
|
||||
}
|
||||
|
||||
if (json.has("method")) {
|
||||
if (!json.has(PARAMS)) {
|
||||
final String message = "Missing " + PARAMS;
|
||||
Logging.getLogger().logError(message);
|
||||
final JsonObject response = new JsonObject();
|
||||
response.addProperty(JSONRPC, JSONRPC_VERSION);
|
||||
final JsonObject error = new JsonObject();
|
||||
error.addProperty(CODE, INVALID_REQUEST);
|
||||
error.addProperty(MESSAGE, message);
|
||||
response.add(ERROR, error);
|
||||
requestSink.add(response);
|
||||
return;
|
||||
}
|
||||
if (json.has("id")) {
|
||||
processRequest(json);
|
||||
} else {
|
||||
processNotification(json);
|
||||
}
|
||||
} else if (json.has("result") || json.has("error")) {
|
||||
processResponse(json);
|
||||
} else {
|
||||
Logging.getLogger().logError("Malformed message");
|
||||
}
|
||||
}
|
||||
|
||||
void processRequest(JsonObject json) {
|
||||
final JsonObject response = new JsonObject();
|
||||
response.addProperty(JSONRPC, JSONRPC_VERSION);
|
||||
|
||||
// Get the consumer associated with this request
|
||||
String id;
|
||||
try {
|
||||
id = json.get(ID).getAsString();
|
||||
} catch (Exception e) {
|
||||
final String message = "Request malformed " + ID;
|
||||
Logging.getLogger().logError(message, e);
|
||||
final JsonObject error = new JsonObject();
|
||||
error.addProperty(CODE, INVALID_REQUEST);
|
||||
error.addProperty(MESSAGE, message);
|
||||
response.add(ERROR, error);
|
||||
requestSink.add(response);
|
||||
return;
|
||||
}
|
||||
|
||||
response.addProperty(ID, id);
|
||||
|
||||
String method;
|
||||
try {
|
||||
method = json.get(METHOD).getAsString();
|
||||
} catch (Exception e) {
|
||||
final String message = "Request malformed " + METHOD;
|
||||
Logging.getLogger().logError(message, e);
|
||||
final JsonObject error = new JsonObject();
|
||||
error.addProperty(CODE, INVALID_REQUEST);
|
||||
error.addProperty(MESSAGE, message);
|
||||
response.add(ERROR, error);
|
||||
requestSink.add(response);
|
||||
return;
|
||||
}
|
||||
|
||||
JsonObject params;
|
||||
try {
|
||||
params = json.get(PARAMS).getAsJsonObject();
|
||||
} catch (Exception e) {
|
||||
final String message = "Request malformed " + METHOD;
|
||||
Logging.getLogger().logError(message, e);
|
||||
final JsonObject error = new JsonObject();
|
||||
error.addProperty(CODE, INVALID_REQUEST);
|
||||
error.addProperty(MESSAGE, message);
|
||||
response.add(ERROR, error);
|
||||
requestSink.add(response);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!remoteServiceRunners.containsKey(method)) {
|
||||
final String message = "Unknown service " + method;
|
||||
Logging.getLogger().logError(message);
|
||||
final JsonObject error = new JsonObject();
|
||||
error.addProperty(CODE, METHOD_NOT_FOUND);
|
||||
error.addProperty(MESSAGE, message);
|
||||
response.add(ERROR, error);
|
||||
requestSink.add(response);
|
||||
return;
|
||||
}
|
||||
|
||||
final RemoteServiceRunner runner = remoteServiceRunners.get(method);
|
||||
try {
|
||||
runner.run(params, new RemoteServiceCompleter() {
|
||||
public void result(JsonObject result) {
|
||||
response.add(RESULT, result);
|
||||
requestSink.add(response);
|
||||
}
|
||||
|
||||
public void error(int code, String message, JsonObject data) {
|
||||
final JsonObject error = new JsonObject();
|
||||
error.addProperty(CODE, code);
|
||||
error.addProperty(MESSAGE, message);
|
||||
if (data != null) {
|
||||
error.add(DATA, data);
|
||||
}
|
||||
response.add(ERROR, error);
|
||||
requestSink.add(response);
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
final String message = "Internal Server Error";
|
||||
Logging.getLogger().logError(message, e);
|
||||
final JsonObject error = new JsonObject();
|
||||
error.addProperty(CODE, SERVER_ERROR);
|
||||
error.addProperty(MESSAGE, message);
|
||||
response.add(ERROR, error);
|
||||
requestSink.add(response);
|
||||
}
|
||||
}
|
||||
|
||||
private static final RemoteServiceCompleter ignoreCallback =
|
||||
new RemoteServiceCompleter() {
|
||||
public void result(JsonObject result) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
public void error(int code, String message, JsonObject data) {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
|
||||
void processNotification(JsonObject json) {
|
||||
String method;
|
||||
try {
|
||||
method = json.get(METHOD).getAsString();
|
||||
} catch (Exception e) {
|
||||
Logging.getLogger().logError("Request malformed " + METHOD, e);
|
||||
return;
|
||||
}
|
||||
JsonObject params;
|
||||
try {
|
||||
params = json.get(PARAMS).getAsJsonObject();
|
||||
} catch (Exception e) {
|
||||
Logging.getLogger().logError("Event missing " + PARAMS, e);
|
||||
return;
|
||||
}
|
||||
if ("streamNotify".equals(method)) {
|
||||
String streamId;
|
||||
try {
|
||||
streamId = params.get(STREAM_ID).getAsString();
|
||||
} catch (Exception e) {
|
||||
Logging.getLogger().logError("Event missing " + STREAM_ID, e);
|
||||
return;
|
||||
}
|
||||
Event event;
|
||||
try {
|
||||
event = new Event(params.get(EVENT).getAsJsonObject());
|
||||
} catch (Exception e) {
|
||||
Logging.getLogger().logError("Event missing " + EVENT, e);
|
||||
return;
|
||||
}
|
||||
forwardEvent(streamId, event);
|
||||
} else {
|
||||
if (!remoteServiceRunners.containsKey(method)) {
|
||||
Logging.getLogger().logError("Unknown service " + method);
|
||||
return;
|
||||
}
|
||||
|
||||
final RemoteServiceRunner runner = remoteServiceRunners.get(method);
|
||||
try {
|
||||
runner.run(params, ignoreCallback);
|
||||
} catch (Exception e) {
|
||||
Logging.getLogger().logError("Internal Server Error", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void processResponse(JsonObject json) {
|
||||
JsonElement idElem = json.get(ID);
|
||||
if (idElem == null) {
|
||||
Logging.getLogger().logError("Response missing " + ID);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the consumer associated with this response
|
||||
String id;
|
||||
try {
|
||||
id = idElem.getAsString();
|
||||
} catch (Exception e) {
|
||||
Logging.getLogger().logError("Response missing " + ID, e);
|
||||
return;
|
||||
}
|
||||
Consumer consumer = consumerMap.remove(id);
|
||||
if (consumer == null) {
|
||||
Logging.getLogger().logError("No consumer associated with " + ID + ": " + id);
|
||||
return;
|
||||
}
|
||||
|
||||
// Forward the response if the request was successfully executed
|
||||
JsonElement resultElem = json.get(RESULT);
|
||||
if (resultElem != null) {
|
||||
JsonObject result;
|
||||
try {
|
||||
result = resultElem.getAsJsonObject();
|
||||
} catch (Exception e) {
|
||||
Logging.getLogger().logError("Response has invalid " + RESULT, e);
|
||||
return;
|
||||
}
|
||||
String responseType;
|
||||
try {
|
||||
responseType = result.get(TYPE).getAsString();
|
||||
} catch (Exception e) {
|
||||
Logging.getLogger().logError("Response missing " + TYPE, e);
|
||||
return;
|
||||
}
|
||||
forwardResponse(consumer, responseType, result);
|
||||
return;
|
||||
}
|
||||
|
||||
// Forward an error if the request failed
|
||||
resultElem = json.get(ERROR);
|
||||
if (resultElem != null) {
|
||||
JsonObject error;
|
||||
try {
|
||||
error = resultElem.getAsJsonObject();
|
||||
} catch (Exception e) {
|
||||
Logging.getLogger().logError("Response has invalid " + RESULT, e);
|
||||
return;
|
||||
}
|
||||
consumer.onError(new RPCError(error));
|
||||
return;
|
||||
}
|
||||
|
||||
Logging.getLogger().logError("Response missing " + RESULT + " and " + ERROR);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2015, the Dart project authors.
|
||||
*
|
||||
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.dartlang.vm.service;
|
||||
|
||||
import org.dartlang.vm.service.element.Event;
|
||||
|
||||
/**
|
||||
* Interface used by {@link VmService} to notify others of VM events.
|
||||
*/
|
||||
public interface VmServiceListener {
|
||||
void connectionOpened();
|
||||
|
||||
/**
|
||||
* Called when a VM event has been received.
|
||||
*
|
||||
* @param streamId the stream identifier (e.g. {@link VmService#DEBUG_STREAM_ID}
|
||||
* @param event the event
|
||||
*/
|
||||
void received(String streamId, Event event);
|
||||
|
||||
void connectionClosed();
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* Copyright (c) 2015, the Dart project authors.
|
||||
*
|
||||
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.dartlang.vm.service.consumer;
|
||||
|
||||
import org.dartlang.vm.service.element.RPCError;
|
||||
|
||||
/**
|
||||
* Consumer is a common interface for all consumer interfaces.
|
||||
*/
|
||||
public interface Consumer {
|
||||
/**
|
||||
* Called if the request failed for some reason.
|
||||
*/
|
||||
void onError(RPCError error);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright (c) 2015, the Dart project authors.
|
||||
*
|
||||
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.dartlang.vm.service.consumer;
|
||||
|
||||
import org.dartlang.vm.service.element.Instance;
|
||||
|
||||
public interface GetInstanceConsumer extends Consumer {
|
||||
void received(Instance response);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright (c) 2015, the Dart project authors.
|
||||
*
|
||||
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.dartlang.vm.service.consumer;
|
||||
|
||||
import org.dartlang.vm.service.element.Library;
|
||||
|
||||
public interface GetLibraryConsumer extends Consumer {
|
||||
void received(Library response);
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright (c) 2017, the Dart project authors.
|
||||
*
|
||||
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.dartlang.vm.service.consumer;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
public interface ServiceExtensionConsumer extends Consumer {
|
||||
void received(JsonObject result);
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package org.dartlang.vm.service.element;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Superclass for all observatory elements.
|
||||
*/
|
||||
public class Element {
|
||||
protected final JsonObject json;
|
||||
|
||||
public Element(JsonObject json) {
|
||||
this.json = json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the underlying JSON backing this element.
|
||||
*/
|
||||
public JsonObject getJson() {
|
||||
return json;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a specific JSON member as a list of integers.
|
||||
*/
|
||||
List<Integer> getListInt(String memberName) {
|
||||
return jsonArrayToListInt(json.getAsJsonArray(memberName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a specific JSON member as a list of strings.
|
||||
*/
|
||||
List<String> getListString(String memberName) {
|
||||
return jsonArrayToListString(json.getAsJsonArray(memberName));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a specific JSON member as a list of list of integers.
|
||||
*/
|
||||
List<List<Integer>> getListListInt(String memberName) {
|
||||
JsonArray array = json.getAsJsonArray(memberName);
|
||||
if (array == null) {
|
||||
return null;
|
||||
}
|
||||
int size = array.size();
|
||||
List<List<Integer>> result = new ArrayList<>();
|
||||
for (int index = 0; index < size; ++index) {
|
||||
result.add(jsonArrayToListInt(array.get(index).getAsJsonArray()));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<Integer> jsonArrayToListInt(JsonArray array) {
|
||||
int size = array.size();
|
||||
List<Integer> result = new ArrayList<>();
|
||||
for (int index = 0; index < size; ++index) {
|
||||
result.add(array.get(index).getAsInt());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private List<String> jsonArrayToListString(JsonArray array) {
|
||||
int size = array.size();
|
||||
List<String> result = new ArrayList<>();
|
||||
for (int index = 0; index < size; ++index) {
|
||||
result.add(array.get(index).getAsString());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package org.dartlang.vm.service.element;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
/**
|
||||
* Simple wrapper around a {@link JsonArray} which lazily converts {@link JsonObject} elements to
|
||||
* subclasses of {@link Element}. Subclasses need only implement {@link #basicGet(JsonArray, int)}
|
||||
* to return an {@link Element} subclass for the {@link JsonObject} at a given index.
|
||||
*/
|
||||
public abstract class ElementList<T> implements Iterable<T> {
|
||||
|
||||
private final JsonArray array;
|
||||
|
||||
public ElementList(JsonArray array) {
|
||||
this.array = array;
|
||||
}
|
||||
|
||||
public T get(int index) {
|
||||
return basicGet(array, index);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return size() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return new Iterator<T>() {
|
||||
int index = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return index < size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T next() {
|
||||
return get(index++);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return array.size();
|
||||
}
|
||||
|
||||
protected abstract T basicGet(JsonArray array, int index);
|
||||
}
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* Copyright (c) 2015, the Dart project authors.
|
||||
*
|
||||
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.dartlang.vm.service.element;
|
||||
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import org.dartlang.vm.service.internal.VmServiceConst;
|
||||
|
||||
/**
|
||||
* When an RPC encounters an error, it is provided in the _error_ property of the response object.
|
||||
* JSON-RPC errors always provide _code_, _message_, and _data_ properties. <br/>
|
||||
* Here is an example error response for our [streamListen](#streamlisten) request above. This error
|
||||
* would be generated if we were attempting to subscribe to the _GC_ stream multiple times from the
|
||||
* same client.
|
||||
*
|
||||
* <pre>
|
||||
* {
|
||||
* "jsonrpc": "2.0",
|
||||
* "error": {
|
||||
* "code": 103,
|
||||
* "message": "Stream already subscribed",
|
||||
* "data": {
|
||||
* "details": "The stream 'GC' is already subscribed"
|
||||
* }
|
||||
* }
|
||||
* "id": "2"
|
||||
* }
|
||||
* </pre>
|
||||
* <p>
|
||||
* In addition the the [error codes](http://www.jsonrpc.org/specification#error_object) specified in
|
||||
* the JSON-RPC spec, we use the following application specific error codes:
|
||||
*
|
||||
* <pre>
|
||||
* code | message | meaning
|
||||
* ---- | ------- | -------
|
||||
* 100 | Feature is disabled | The operation is unable to complete because a feature is disabled
|
||||
* 101 | VM must be paused | This operation is only valid when the VM is paused
|
||||
* 102 | Cannot add breakpoint | The VM is unable to add a breakpoint at the specified line or function
|
||||
* 103 | Stream already subscribed | The client is already subscribed to the specified _streamId_
|
||||
* 104 | Stream not subscribed | The client is not subscribed to the specified _streamId_
|
||||
* </pre>
|
||||
*/
|
||||
public class RPCError extends Element implements VmServiceConst {
|
||||
|
||||
/**
|
||||
* The response code used by the client when it receives a response from the server that it did
|
||||
* not expect. For example, it requested a library element but received a list.
|
||||
*/
|
||||
public static final int UNEXPECTED_RESPONSE = 5;
|
||||
|
||||
public static RPCError unexpected(String expectedType, Response response) {
|
||||
String errMsg = "Expected type " + expectedType + " but received " + response.getType();
|
||||
if (response instanceof Sentinel) {
|
||||
errMsg += ": " + ((Sentinel) response).getKind();
|
||||
}
|
||||
JsonObject json = new JsonObject();
|
||||
json.addProperty("code", UNEXPECTED_RESPONSE);
|
||||
json.addProperty("message", errMsg);
|
||||
JsonObject data = new JsonObject();
|
||||
data.addProperty("details", errMsg);
|
||||
data.add("response", response.getJson());
|
||||
json.add("data", data);
|
||||
return new RPCError(json);
|
||||
}
|
||||
|
||||
public RPCError(JsonObject json) {
|
||||
super(json);
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return json.get("code").getAsInt();
|
||||
}
|
||||
|
||||
public String getDetails() {
|
||||
JsonElement data = json.get("data");
|
||||
if (data instanceof JsonObject) {
|
||||
JsonElement details = ((JsonObject) data).get("details");
|
||||
if (details != null) {
|
||||
return details.getAsString();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return json.get("message").getAsString();
|
||||
}
|
||||
|
||||
public JsonObject getRequest() {
|
||||
JsonElement data = json.get("data");
|
||||
if (data instanceof JsonObject) {
|
||||
JsonElement request = ((JsonObject) data).get("request");
|
||||
if (request instanceof JsonObject) {
|
||||
return (JsonObject) request;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright (c) 2015, the Dart project authors.
|
||||
*
|
||||
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.dartlang.vm.service.internal;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
/**
|
||||
* A {@link RequestSink} that enqueues all requests and can be later converted into a "passthrough"
|
||||
* or an "error" {@link RequestSink}.
|
||||
*/
|
||||
public class BlockingRequestSink implements RequestSink {
|
||||
/**
|
||||
* The base {@link RequestSink}
|
||||
*/
|
||||
private final RequestSink base;
|
||||
|
||||
/**
|
||||
* A queue of requests.
|
||||
*/
|
||||
private final LinkedList<JsonObject> queue = Lists.newLinkedList();
|
||||
|
||||
public BlockingRequestSink(RequestSink base) {
|
||||
this.base = base;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(JsonObject request) {
|
||||
synchronized (queue) {
|
||||
queue.add(request);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
base.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Responds with an error to all the currently queued requests and return a {@link RequestSink} to
|
||||
* do the same for all the future requests.
|
||||
*
|
||||
* @param errorResponseSink the sink to send error responses to, not {@code null}
|
||||
*/
|
||||
public RequestSink toErrorSink(ResponseSink errorResponseSink, String errorResponseCode,
|
||||
String errorResponseMessage) {
|
||||
ErrorRequestSink errorRequestSink = new ErrorRequestSink(errorResponseSink, errorResponseCode,
|
||||
errorResponseMessage);
|
||||
synchronized (queue) {
|
||||
for (JsonObject request : queue) {
|
||||
errorRequestSink.add(request);
|
||||
}
|
||||
}
|
||||
return errorRequestSink;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the passthrough {@link RequestSink}.
|
||||
*/
|
||||
public RequestSink toPassthroughSink() {
|
||||
synchronized (queue) {
|
||||
for (JsonObject request : queue) {
|
||||
base.add(request);
|
||||
}
|
||||
}
|
||||
return base;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright (c) 2015, the Dart project authors.
|
||||
*
|
||||
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.dartlang.vm.service.internal;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import org.dartlang.vm.service.logging.Logging;
|
||||
|
||||
/**
|
||||
* A {@link RequestSink} that reports with an error to each request.
|
||||
*/
|
||||
public class ErrorRequestSink implements RequestSink, VmServiceConst {
|
||||
/**
|
||||
* The {@link ResponseSink} to send error responses to.
|
||||
*/
|
||||
private final ResponseSink responseSink;
|
||||
|
||||
private final String code;
|
||||
private final String message;
|
||||
|
||||
public ErrorRequestSink(ResponseSink responseSink, String code, String message) {
|
||||
if (responseSink == null || code == null || message == null) {
|
||||
throw new IllegalArgumentException("Unexpected null argument: " + responseSink + " "
|
||||
+ code + " " + message);
|
||||
}
|
||||
this.responseSink = responseSink;
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(JsonObject request) {
|
||||
String id = request.getAsJsonPrimitive(ID).getAsString();
|
||||
try {
|
||||
// TODO(danrubel) is this the correct format for an error response?
|
||||
JsonObject error = new JsonObject();
|
||||
error.addProperty(CODE, code);
|
||||
error.addProperty(MESSAGE, message);
|
||||
JsonObject response = new JsonObject();
|
||||
response.addProperty(ID, id);
|
||||
response.add(ERROR, error);
|
||||
responseSink.add(response);
|
||||
} catch (Throwable e) {
|
||||
Logging.getLogger().logError(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2015, the Dart project authors.
|
||||
*
|
||||
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.dartlang.vm.service.internal;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* A destination for observatory requests.
|
||||
*/
|
||||
public interface RequestSink {
|
||||
/**
|
||||
* Put request into the sink.
|
||||
*
|
||||
* @param request the request to put, not {@code null}.
|
||||
*/
|
||||
void add(JsonObject request);
|
||||
|
||||
/**
|
||||
* Close the communication channel.
|
||||
*/
|
||||
void close();
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Copyright (c) 2015, the Dart project authors.
|
||||
*
|
||||
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.dartlang.vm.service.internal;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
/**
|
||||
* A destination for responses.
|
||||
*/
|
||||
public interface ResponseSink {
|
||||
/**
|
||||
* Put response into the sink.
|
||||
*
|
||||
* @param response the response to put, not {@code null}.
|
||||
*/
|
||||
void add(JsonObject response) throws Exception;
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) 2015, the Dart project authors.
|
||||
*
|
||||
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.dartlang.vm.service.internal;
|
||||
|
||||
/**
|
||||
* JSON constants used when communicating with the VM observatory service.
|
||||
*/
|
||||
public interface VmServiceConst {
|
||||
static final String CODE = "code";
|
||||
static final String ERROR = "error";
|
||||
static final String EVENT = "event";
|
||||
static final String ID = "id";
|
||||
static final String MESSAGE = "message";
|
||||
static final String METHOD = "method";
|
||||
static final String PARAMS = "params";
|
||||
static final String RESULT = "result";
|
||||
static final String STREAM_ID = "streamId";
|
||||
static final String TYPE = "type";
|
||||
static final String JSONRPC = "jsonrpc";
|
||||
static final String JSONRPC_VERSION = "2.0";
|
||||
static final String DATA = "data";
|
||||
|
||||
/**
|
||||
* Parse error Invalid JSON was received by the server.
|
||||
* An error occurred on the server while parsing the JSON text.
|
||||
*/
|
||||
static final int PARSE_ERROR = -32700;
|
||||
|
||||
/**
|
||||
* Invalid Request The JSON sent is not a valid Request object.
|
||||
*/
|
||||
static final int INVALID_REQUEST = -32600;
|
||||
|
||||
/**
|
||||
* Method not found The method does not exist / is not available.
|
||||
*/
|
||||
static final int METHOD_NOT_FOUND = -32601;
|
||||
|
||||
/**
|
||||
* Invalid params Invalid method parameter(s).
|
||||
*/
|
||||
static final int INVALID_PARAMS = -32602;
|
||||
|
||||
/**
|
||||
* Server error Reserved for implementation-defined server-errors.
|
||||
* -32000 to -32099
|
||||
*/
|
||||
static final int SERVER_ERROR = -32000;
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright (c) 2015, the Dart project authors.
|
||||
*
|
||||
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.dartlang.vm.service.internal;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import de.roderick.weberknecht.WebSocket;
|
||||
import de.roderick.weberknecht.WebSocketException;
|
||||
import org.dartlang.vm.service.logging.Logging;
|
||||
|
||||
/**
|
||||
* An {@link WebSocket} based implementation of {@link RequestSink}.
|
||||
*/
|
||||
public class WebSocketRequestSink implements RequestSink {
|
||||
|
||||
private WebSocket webSocket;
|
||||
|
||||
public WebSocketRequestSink(WebSocket webSocket) {
|
||||
this.webSocket = webSocket;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(JsonObject json) {
|
||||
String request = json.toString();
|
||||
if (webSocket == null) {
|
||||
Logging.getLogger().logInformation("Dropped: " + request);
|
||||
return;
|
||||
}
|
||||
Logging.getLogger().logInformation("Sent: " + request);
|
||||
try {
|
||||
webSocket.send(request);
|
||||
} catch (WebSocketException e) {
|
||||
Logging.getLogger().logError("Failed to send request: " + request, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
if (webSocket != null) {
|
||||
try {
|
||||
webSocket.close();
|
||||
} catch (WebSocketException e) {
|
||||
Logging.getLogger().logError("Failed to close websocket", e);
|
||||
}
|
||||
webSocket = null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* Copyright (c) 2012, the Dart project authors.
|
||||
*
|
||||
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.dartlang.vm.service.logging;
|
||||
|
||||
/**
|
||||
* The interface {@code Logger} defines the behavior of objects that can be used to receive
|
||||
* information about errors. Implementations usually write this information to a file, but can also
|
||||
* record the information for later use (such as during testing) or even ignore the information.
|
||||
*/
|
||||
public interface Logger {
|
||||
|
||||
/**
|
||||
* Implementation of {@link Logger} that does nothing.
|
||||
*/
|
||||
class NullLogger implements Logger {
|
||||
@Override
|
||||
public void logError(String message) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logError(String message, Throwable exception) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logInformation(String message) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logInformation(String message, Throwable exception) {
|
||||
}
|
||||
}
|
||||
|
||||
static final Logger NULL = new NullLogger();
|
||||
|
||||
/**
|
||||
* Log the given message as an error.
|
||||
*
|
||||
* @param message an explanation of why the error occurred or what it means
|
||||
*/
|
||||
void logError(String message);
|
||||
|
||||
/**
|
||||
* Log the given exception as one representing an error.
|
||||
*
|
||||
* @param message an explanation of why the error occurred or what it means
|
||||
* @param exception the exception being logged
|
||||
*/
|
||||
void logError(String message, Throwable exception);
|
||||
|
||||
/**
|
||||
* Log the given informational message.
|
||||
*
|
||||
* @param message an explanation of why the error occurred or what it means
|
||||
*/
|
||||
void logInformation(String message);
|
||||
|
||||
/**
|
||||
* Log the given exception as one representing an informational message.
|
||||
*
|
||||
* @param message an explanation of why the error occurred or what it means
|
||||
* @param exception the exception being logged
|
||||
*/
|
||||
void logInformation(String message, Throwable exception);
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright (c) 2014, the Dart project authors.
|
||||
*
|
||||
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.dartlang.vm.service.logging;
|
||||
|
||||
/**
|
||||
* {@code Logging} provides a global instance of {@link Logger}.
|
||||
*/
|
||||
public class Logging {
|
||||
|
||||
private static Logger logger = Logger.NULL;
|
||||
|
||||
public static Logger getLogger() {
|
||||
return logger;
|
||||
}
|
||||
|
||||
public static void setLogger(Logger logger) {
|
||||
Logging.logger = logger == null ? Logger.NULL : logger;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
/*
|
||||
* Copyright (c) 2015, the Dart project authors.
|
||||
*
|
||||
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.dartlang.vm.service;
|
||||
|
||||
import org.dartlang.vm.service.consumer.GetInstanceConsumer;
|
||||
import org.dartlang.vm.service.element.BoundField;
|
||||
import org.dartlang.vm.service.element.ClassRef;
|
||||
import org.dartlang.vm.service.element.Instance;
|
||||
import org.dartlang.vm.service.element.InstanceKind;
|
||||
import org.dartlang.vm.service.element.InstanceRef;
|
||||
import org.dartlang.vm.service.element.Isolate;
|
||||
import org.dartlang.vm.service.element.RPCError;
|
||||
|
||||
/**
|
||||
* Utility class for converting {@link InstanceRef} to a human readable string.
|
||||
*/
|
||||
public class InstanceRefToString {
|
||||
private Isolate isolate;
|
||||
private final VmService service;
|
||||
private final OpLatch masterLatch;
|
||||
|
||||
/**
|
||||
* Construct a new instance for converting one or more {@link InstanceRef} to human readable
|
||||
* strings. Specify an {@link OpLatch} so that this class can update the expiration time for any
|
||||
* waiting thread as it makes {@link VmService} class to obtain details about each
|
||||
* {@link InstanceRef}.
|
||||
*/
|
||||
public InstanceRefToString(Isolate isolate, VmService service, OpLatch latch) {
|
||||
this.isolate = isolate;
|
||||
this.service = service;
|
||||
this.masterLatch = latch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a human readable string for the given {@link InstanceRef}.
|
||||
*/
|
||||
public String toString(InstanceRef ref) {
|
||||
StringBuilder result = new StringBuilder();
|
||||
printInstance(result, ref, 4);
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Request the instance information from the {@link VmService}.
|
||||
*
|
||||
* @param ref the instance reference (not {@code null})
|
||||
* @return the instance or {@code null} if there was a problem.
|
||||
*/
|
||||
private Instance getInstance(InstanceRef ref) {
|
||||
|
||||
// Request master latch extend its timeout because we are making another call to VmService
|
||||
masterLatch.opWorking();
|
||||
|
||||
final ResultLatch<Instance> instLatch = new ResultLatch<Instance>();
|
||||
service.getInstance(isolate.getId(), ref.getId(), new GetInstanceConsumer() {
|
||||
@Override
|
||||
public void onError(RPCError error) {
|
||||
instLatch.setValue(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void received(Instance instance) {
|
||||
instLatch.setValue(instance);
|
||||
}
|
||||
});
|
||||
return instLatch.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given {@link InstanceRef} into a human readable string.
|
||||
*
|
||||
* @param result the buffer to which the human readable string is added
|
||||
* @param ref the instance to be converted (not {@code null})
|
||||
* @param maxDepth the maximum number of recursions this method can make on itself to determine
|
||||
* human readable strings for child objects
|
||||
*/
|
||||
private void printInstance(StringBuilder result, InstanceRef ref, int maxDepth) {
|
||||
if (ref == null) {
|
||||
result.append("-- no value --");
|
||||
return;
|
||||
}
|
||||
InstanceKind kind = ref.getKind();
|
||||
if (kind == null) {
|
||||
result.append("-- unknown instance kind --");
|
||||
return;
|
||||
}
|
||||
switch (kind) {
|
||||
case Bool:
|
||||
case Double:
|
||||
case Float32x4:
|
||||
case Float64x2:
|
||||
case Int:
|
||||
case Int32x4:
|
||||
case Null:
|
||||
case StackTrace:
|
||||
result.append(ref.getValueAsString());
|
||||
return;
|
||||
case String:
|
||||
result.append("'");
|
||||
// Should escape chars such as newline before printing
|
||||
result.append(ref.getValueAsString());
|
||||
if (ref.getValueAsStringIsTruncated()) {
|
||||
result.append("...");
|
||||
}
|
||||
result.append("'");
|
||||
return;
|
||||
case List:
|
||||
printList(result, ref, maxDepth);
|
||||
return;
|
||||
case PlainInstance:
|
||||
printPlainInstance(result, ref, maxDepth);
|
||||
return;
|
||||
case BoundedType:
|
||||
case Closure:
|
||||
case Float32List:
|
||||
case Float32x4List:
|
||||
case Float64List:
|
||||
case Float64x2List:
|
||||
case Int16List:
|
||||
case Int32List:
|
||||
case Int32x4List:
|
||||
case Int64List:
|
||||
case Int8List:
|
||||
case Map:
|
||||
case MirrorReference:
|
||||
case RegExp:
|
||||
case Type:
|
||||
case TypeParameter:
|
||||
case TypeRef:
|
||||
case Uint16List:
|
||||
case Uint32List:
|
||||
case Uint64List:
|
||||
case Uint8ClampedList:
|
||||
case Uint8List:
|
||||
case WeakProperty:
|
||||
}
|
||||
result.append("a " + kind);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given list into a human readable string.
|
||||
*
|
||||
* @param result the buffer to which the human readable string is added
|
||||
* @param ref an instance reference of type "List" (not {@code null})
|
||||
* @param maxDepth the maximum number of recursions this method can make on itself to determine
|
||||
* human readable strings for child objects
|
||||
*/
|
||||
private void printList(StringBuilder result, InstanceRef ref, int maxDepth) {
|
||||
if (maxDepth == 0) {
|
||||
result.append("a List");
|
||||
return;
|
||||
}
|
||||
result.append("[");
|
||||
Instance list = getInstance(ref);
|
||||
if (list == null) {
|
||||
result.append("?error?]");
|
||||
return;
|
||||
}
|
||||
int count = 0;
|
||||
for (InstanceRef elem : list.getElements()) {
|
||||
if (count > 10) {
|
||||
result.append(", ...");
|
||||
break;
|
||||
}
|
||||
if (count > 0) {
|
||||
result.append(", ");
|
||||
}
|
||||
++count;
|
||||
printInstance(result, elem, maxDepth - 1);
|
||||
}
|
||||
result.append("]");
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the given instance into a human readable string.
|
||||
*
|
||||
* @param result the buffer to which the human readable string is added
|
||||
* @param ref an instance reference of type "PlainInstance" (not {@code null})
|
||||
* @param maxDepth the maximum number of recursions this method can make on itself to determine
|
||||
* human readable strings for child objects
|
||||
*/
|
||||
private void printPlainInstance(StringBuilder result, InstanceRef ref, int maxDepth) {
|
||||
ClassRef classRef = ref.getClassRef();
|
||||
String className = classRef.getName();
|
||||
if (maxDepth == 0) {
|
||||
result.append("a " + className);
|
||||
return;
|
||||
}
|
||||
result.append(className);
|
||||
result.append("(");
|
||||
Instance inst = getInstance(ref);
|
||||
boolean first = true;
|
||||
for (BoundField field : inst.getFields()) {
|
||||
if (first) {
|
||||
first = false;
|
||||
} else {
|
||||
result.append(", ");
|
||||
}
|
||||
printInstance(result, field.getValue(), maxDepth - 1);
|
||||
}
|
||||
result.append(")");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* Copyright (c) 2015, the Dart project authors.
|
||||
*
|
||||
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.dartlang.vm.service;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* {@link OpLatch} is used by one thread to wait for another thread to complete (see
|
||||
* {@link OpLatch#waitOpComplete()} and {@link OpLatch#waitAndAssertOpComplete()}). If the operation
|
||||
* does not complete before the expiration time then {@link OpLatch#waitOpComplete()} returns
|
||||
* {@code false} and {@link OpLatch#waitAndAssertOpComplete()} throws an exception.
|
||||
*/
|
||||
class OpLatch {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
private long endTime;
|
||||
|
||||
/**
|
||||
* Set or increase the time after which the operation is considered failed. This is automatically
|
||||
* called by {@link #waitAndAssertOp()} and {@link #waitOpComplete()}.
|
||||
*/
|
||||
public void opWorking() {
|
||||
endTime = System.currentTimeMillis() + 5000;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call to indicate that the operation completed successfully.
|
||||
*/
|
||||
void opComplete() {
|
||||
latch.countDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the operation to complete or the time limit to expire. Periodically call
|
||||
* {@link #opWorking()} to increase the expiration time. Throw a {@link RuntimeException} if the
|
||||
* operation did not complete before the expiration time.
|
||||
*/
|
||||
void waitAndAssertOpComplete() {
|
||||
if (!waitOpComplete()) {
|
||||
System.out.println(">>> No response received");
|
||||
throw new RuntimeException("No response received");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait for the operation to complete or the time limit to expire. Periodically call
|
||||
* {@link #opWorking()} to increase the expiration time.
|
||||
*
|
||||
* @return {@code true} if the operation completed, or {@code false} otherwise
|
||||
*/
|
||||
boolean waitOpComplete() {
|
||||
opWorking();
|
||||
while (true) {
|
||||
long waitTimeMillis = endTime - System.currentTimeMillis();
|
||||
if (waitTimeMillis <= 0) {
|
||||
return latch.getCount() == 0;
|
||||
}
|
||||
try {
|
||||
if (latch.await(waitTimeMillis, TimeUnit.MILLISECONDS)) {
|
||||
return true;
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// ignore and loop to check if timeout has changed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2015, the Dart project authors.
|
||||
*
|
||||
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.dartlang.vm.service;
|
||||
|
||||
/**
|
||||
* {@link ResultLatch} is used by one thread to communicate a result to another thread.
|
||||
*/
|
||||
public class ResultLatch<T> extends OpLatch {
|
||||
private T value;
|
||||
|
||||
public T getValue() {
|
||||
waitAndAssertOpComplete();
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(T value) {
|
||||
this.value = value;
|
||||
opComplete();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright (c) 2015, the Dart project authors.
|
||||
*
|
||||
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.dartlang.vm.service;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
/**
|
||||
* Echo the content of a stream to {@link System.out} with the given prefix.
|
||||
*/
|
||||
public class SampleOutPrinter {
|
||||
private class LinesReaderThread extends Thread {
|
||||
public LinesReaderThread() {
|
||||
setName("SampleOutPrinter.LinesReaderThread - " + prefix);
|
||||
setDaemon(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
while (true) {
|
||||
String line;
|
||||
try {
|
||||
line = reader.readLine();
|
||||
} catch (IOException e) {
|
||||
System.out.println("Exception reading sample stream");
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
// check for EOF
|
||||
if (line == null) {
|
||||
return;
|
||||
}
|
||||
synchronized (currentLineLock) {
|
||||
currentLine = line;
|
||||
currentLineLock.notifyAll();
|
||||
}
|
||||
System.out.println("[" + prefix + "] " + line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String currentLine;
|
||||
|
||||
private final Object currentLineLock = new Object();
|
||||
|
||||
private final String prefix;
|
||||
private final BufferedReader reader;
|
||||
|
||||
public SampleOutPrinter(String prefix, InputStream stream) {
|
||||
this.prefix = prefix;
|
||||
this.reader = new BufferedReader(new InputStreamReader(stream, Charsets.UTF_8));
|
||||
new LinesReaderThread().start();
|
||||
}
|
||||
|
||||
public void assertEmpty() {
|
||||
synchronized (currentLineLock) {
|
||||
if (currentLine != null) {
|
||||
throw new RuntimeException("Did not expect " + prefix + ": \"" + currentLine + "\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void assertLastLine(String text) {
|
||||
synchronized (currentLineLock) {
|
||||
if (text == null) {
|
||||
if (currentLine != null) {
|
||||
throw new RuntimeException("Did not expect " + prefix + ": \"" + currentLine + "\"");
|
||||
}
|
||||
} else {
|
||||
if (currentLine == null || !currentLine.contains(text)) {
|
||||
throw new RuntimeException("Expected current line to contain text\n"
|
||||
+ "\nexpected: [" + text + "]"
|
||||
+ "\nactual: [" + currentLine + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* Copyright (c) 2015, the Dart project authors.
|
||||
*
|
||||
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.dartlang.vm.service;
|
||||
|
||||
import org.dartlang.vm.service.element.Event;
|
||||
import org.dartlang.vm.service.element.EventKind;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Sample VmListener for responding to state changes in the running application
|
||||
*/
|
||||
public class SampleVmServiceListener implements VmServiceListener {
|
||||
private final Object lock = new Object();
|
||||
private String lastStreamId;
|
||||
private Event lastEvent;
|
||||
private final Set<EventKind> ignoreAll;
|
||||
|
||||
SampleVmServiceListener(Set<EventKind> ignoreAll) {
|
||||
this.ignoreAll = ignoreAll;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionOpened() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void received(String streamId, Event event) {
|
||||
synchronized (lock) {
|
||||
if (ignoreAll.contains(event.getKind())) {
|
||||
return;
|
||||
}
|
||||
if (lastStreamId != null) {
|
||||
unexpectedEvent(lastStreamId, lastEvent);
|
||||
}
|
||||
lastStreamId = streamId;
|
||||
lastEvent = event;
|
||||
lock.notifyAll();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void connectionClosed() {
|
||||
|
||||
}
|
||||
|
||||
public Event waitFor(String expectedStreamId, EventKind expectedEventKind) {
|
||||
long end = System.currentTimeMillis() + 5000;
|
||||
synchronized (lock) {
|
||||
while (true) {
|
||||
if (expectedStreamId.equals(lastStreamId) && expectedEventKind.equals(lastEvent.getKind())) {
|
||||
Event event = lastEvent;
|
||||
lastStreamId = null;
|
||||
lastEvent = null;
|
||||
return event;
|
||||
}
|
||||
long timeout = end - System.currentTimeMillis();
|
||||
if (timeout <= 0) {
|
||||
break;
|
||||
}
|
||||
try {
|
||||
lock.wait(timeout);
|
||||
} catch (InterruptedException e) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new RuntimeException("Expected event: " + expectedStreamId + ", " + expectedEventKind);
|
||||
}
|
||||
|
||||
private void unexpectedEvent(String streamId, Event event) {
|
||||
System.out.println("****** Unexpected Event: " + streamId + ", " + event.getKind());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,661 @@
|
|||
/*
|
||||
* Copyright (c) 2015, the Dart project authors.
|
||||
*
|
||||
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
|
||||
* in compliance with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
package org.dartlang.vm.service;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import org.dartlang.vm.service.consumer.*;
|
||||
import org.dartlang.vm.service.element.*;
|
||||
import org.dartlang.vm.service.logging.Logger;
|
||||
import org.dartlang.vm.service.logging.Logging;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
||||
public class VmServiceTest {
|
||||
private static File dartVm;
|
||||
private static File sampleDart;
|
||||
private static File sampleDartWithException;
|
||||
private static int vmPort = 7575;
|
||||
private static Process process;
|
||||
private static VmService vmService;
|
||||
private static SampleOutPrinter sampleOut;
|
||||
private static SampleOutPrinter sampleErr;
|
||||
private static int actualVmServiceVersionMajor;
|
||||
|
||||
public static void main(String[] args) {
|
||||
setupLogging();
|
||||
parseArgs(args);
|
||||
|
||||
try {
|
||||
echoDartVmVersion();
|
||||
runSample();
|
||||
runSampleWithException();
|
||||
System.out.println("Test Complete");
|
||||
} finally {
|
||||
vmDisconnect();
|
||||
stopSample();
|
||||
}
|
||||
}
|
||||
|
||||
private static void echoDartVmVersion() {
|
||||
// Echo Dart VM version
|
||||
List<String> processArgs = new ArrayList<>();
|
||||
processArgs.add(dartVm.getAbsolutePath());
|
||||
processArgs.add("--version");
|
||||
ProcessBuilder processBuilder = new ProcessBuilder(processArgs);
|
||||
try {
|
||||
process = processBuilder.start();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to launch Dart VM", e);
|
||||
}
|
||||
new SampleOutPrinter("version output", process.getInputStream());
|
||||
new SampleOutPrinter("version output", process.getErrorStream());
|
||||
}
|
||||
|
||||
private static void finishExecution(SampleVmServiceListener vmListener, ElementList<IsolateRef> isolates) {
|
||||
// Finish execution
|
||||
vmResume(isolates.get(0), null);
|
||||
vmListener.waitFor(VmService.DEBUG_STREAM_ID, EventKind.Resume);
|
||||
|
||||
// VM pauses on exit and must be resumed to cleanly terminate process
|
||||
vmListener.waitFor(VmService.DEBUG_STREAM_ID, EventKind.PauseExit);
|
||||
vmResume(isolates.get(0), null);
|
||||
vmListener.waitFor(VmService.ISOLATE_STREAM_ID, EventKind.IsolateExit);
|
||||
waitForProcessExit();
|
||||
|
||||
sampleOut.assertLastLine("exiting");
|
||||
// TODO(devoncarew):
|
||||
// vm-service: isolate(544050040) 'sample_main.dart:main()' has no debugger attached and is paused at start.
|
||||
//sampleErr.assertLastLine(null);
|
||||
process = null;
|
||||
}
|
||||
|
||||
private static boolean isWindows() {
|
||||
return System.getProperty("os.name").startsWith("Win");
|
||||
}
|
||||
|
||||
private static void parseArgs(String[] args) {
|
||||
if (args.length != 1) {
|
||||
showErrorAndExit("Expected absolute path to Dart SDK");
|
||||
}
|
||||
File sdkDir = new File(args[0]);
|
||||
if (!sdkDir.isDirectory()) {
|
||||
showErrorAndExit("Specified directory does not exist: " + sdkDir);
|
||||
}
|
||||
File binDir = new File(sdkDir, "bin");
|
||||
dartVm = new File(binDir, isWindows() ? "dart.exe" : "dart");
|
||||
if (!dartVm.isFile()) {
|
||||
showErrorAndExit("Cannot find Dart VM in SDK: " + dartVm);
|
||||
}
|
||||
File currentDir = new File(".").getAbsoluteFile();
|
||||
File projDir = currentDir;
|
||||
String projName = "vm_service";
|
||||
while (!projDir.getName().equals(projName)) {
|
||||
projDir = projDir.getParentFile();
|
||||
if (projDir == null) {
|
||||
showErrorAndExit("Cannot find project " + projName + " from " + currentDir);
|
||||
return;
|
||||
}
|
||||
}
|
||||
sampleDart = new File(projDir, "java/example/sample_main.dart".replace("/", File.separator));
|
||||
if (!sampleDart.isFile()) {
|
||||
showErrorAndExit("Cannot find sample: " + sampleDart);
|
||||
}
|
||||
sampleDartWithException = new File(projDir,
|
||||
"java/example/sample_exception.dart".replace("/", File.separator));
|
||||
if (!sampleDartWithException.isFile()) {
|
||||
showErrorAndExit("Cannot find sample: " + sampleDartWithException);
|
||||
}
|
||||
System.out.println("Using Dart SDK: " + sdkDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exercise VM service with "normal" sample.
|
||||
*/
|
||||
private static void runSample() {
|
||||
SampleVmServiceListener vmListener = startSampleAndConnect(sampleDart);
|
||||
vmGetVersion();
|
||||
ElementList<IsolateRef> isolates = vmGetVmIsolates();
|
||||
Isolate sampleIsolate = vmGetIsolate(isolates.get(0));
|
||||
Library rootLib = vmGetLibrary(sampleIsolate, sampleIsolate.getRootLib());
|
||||
vmGetScript(sampleIsolate, rootLib.getScripts().get(0));
|
||||
vmCallServiceExtension(sampleIsolate);
|
||||
|
||||
// Run to breakpoint on line "foo(1);"
|
||||
vmAddBreakpoint(sampleIsolate, rootLib.getScripts().get(0), 25);
|
||||
vmListener.waitFor(VmService.DEBUG_STREAM_ID, EventKind.BreakpointAdded);
|
||||
vmResume(isolates.get(0), null);
|
||||
vmListener.waitFor(VmService.DEBUG_STREAM_ID, EventKind.Resume);
|
||||
vmListener.waitFor(VmService.DEBUG_STREAM_ID, EventKind.PauseBreakpoint);
|
||||
sampleOut.assertLastLine("hello");
|
||||
|
||||
// Get stack trace
|
||||
vmGetStack(sampleIsolate);
|
||||
|
||||
// Evaluate
|
||||
vmEvaluateInFrame(sampleIsolate, 0, "deepList[0]");
|
||||
|
||||
// Get coverage information
|
||||
vmGetSourceReport(sampleIsolate);
|
||||
|
||||
// Step over line "foo(1);"
|
||||
vmResume(isolates.get(0), StepOption.Over);
|
||||
vmListener.waitFor(VmService.DEBUG_STREAM_ID, EventKind.Resume);
|
||||
vmListener.waitFor(VmService.DEBUG_STREAM_ID, EventKind.PauseBreakpoint);
|
||||
sampleOut.assertLastLine("val: 1");
|
||||
|
||||
finishExecution(vmListener, isolates);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exercise VM service with sample that throws exceptions.
|
||||
*/
|
||||
private static void runSampleWithException() {
|
||||
SampleVmServiceListener vmListener = startSampleAndConnect(sampleDartWithException);
|
||||
ElementList<IsolateRef> isolates = vmGetVmIsolates();
|
||||
Isolate sampleIsolate = vmGetIsolate(isolates.get(0));
|
||||
|
||||
// Run until exception occurs
|
||||
vmPauseOnException(isolates.get(0), ExceptionPauseMode.All);
|
||||
vmResume(isolates.get(0), null);
|
||||
vmListener.waitFor(VmService.DEBUG_STREAM_ID, EventKind.Resume);
|
||||
Event event = vmListener.waitFor(VmService.DEBUG_STREAM_ID, EventKind.PauseException);
|
||||
InstanceRefToString convert = new InstanceRefToString(sampleIsolate, vmService, new OpLatch());
|
||||
System.out.println("Received PauseException event");
|
||||
System.out.println(" Exception: " + convert.toString(event.getException()));
|
||||
System.out.println(" Top Frame:");
|
||||
showFrame(convert, event.getTopFrame());
|
||||
sampleOut.assertLastLine("hello");
|
||||
|
||||
finishExecution(vmListener, isolates);
|
||||
}
|
||||
|
||||
private static void setupLogging() {
|
||||
Logging.setLogger(new Logger() {
|
||||
@Override
|
||||
public void logError(String message) {
|
||||
System.out.println("Log error: " + message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logError(String message, Throwable exception) {
|
||||
System.out.println("Log error: " + message);
|
||||
if (exception != null) {
|
||||
System.out.println("Log error exception: " + exception);
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logInformation(String message) {
|
||||
System.out.println("Log info: " + message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void logInformation(String message, Throwable exception) {
|
||||
System.out.println("Log info: " + message);
|
||||
if (exception != null) {
|
||||
System.out.println("Log info exception: " + exception);
|
||||
exception.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static void showErrorAndExit(String errMsg) {
|
||||
System.out.println(errMsg);
|
||||
System.out.flush();
|
||||
sleep(10);
|
||||
System.out.println("Usage: VmServiceTest /path/to/Dart/SDK");
|
||||
System.exit(1);
|
||||
}
|
||||
|
||||
private static void showFrame(InstanceRefToString convert, Frame frame) {
|
||||
System.out.println(" #" + frame.getIndex() + " " + frame.getFunction().getName() + " ("
|
||||
+ frame.getLocation().getScript().getUri() + ")");
|
||||
for (BoundVariable var : frame.getVars()) {
|
||||
InstanceRef instanceRef = (InstanceRef)var.getValue();
|
||||
System.out.println(" " + var.getName() + " = " + convert.toString(instanceRef));
|
||||
}
|
||||
}
|
||||
|
||||
private static void showRPCError(RPCError error) {
|
||||
System.out.println(">>> Received error response");
|
||||
System.out.println(" Code: " + error.getCode());
|
||||
System.out.println(" Message: " + error.getMessage());
|
||||
System.out.println(" Details: " + error.getDetails());
|
||||
System.out.println(" Request: " + error.getRequest());
|
||||
}
|
||||
|
||||
private static void showSentinel(Sentinel sentinel) {
|
||||
System.out.println(">>> Received sentinel response");
|
||||
System.out.println(" Sentinel kind: " + sentinel.getKind());
|
||||
System.out.println(" Sentinel value: " + sentinel.getValueAsString());
|
||||
}
|
||||
|
||||
private static void sleep(int milliseconds) {
|
||||
try {
|
||||
Thread.sleep(milliseconds);
|
||||
} catch (InterruptedException e) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
private static void startSample(File dartFile) {
|
||||
List<String> processArgs;
|
||||
ProcessBuilder processBuilder;
|
||||
|
||||
// Use new port to prevent race conditions
|
||||
// between one sample releasing a port
|
||||
// and the next sample using it.
|
||||
++vmPort;
|
||||
|
||||
processArgs = new ArrayList<>();
|
||||
processArgs.add(dartVm.getAbsolutePath());
|
||||
processArgs.add("--pause_isolates_on_start");
|
||||
processArgs.add("--observe");
|
||||
processArgs.add("--enable-vm-service=" + vmPort);
|
||||
processArgs.add("--disable-service-auth-codes");
|
||||
processArgs.add(dartFile.getAbsolutePath());
|
||||
processBuilder = new ProcessBuilder(processArgs);
|
||||
System.out.println("=================================================");
|
||||
System.out.println("Launching sample: " + dartFile);
|
||||
try {
|
||||
process = processBuilder.start();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to launch Dart sample", e);
|
||||
}
|
||||
// Echo sample application output to System.out
|
||||
sampleOut = new SampleOutPrinter("stdout", process.getInputStream());
|
||||
sampleErr = new SampleOutPrinter("stderr", process.getErrorStream());
|
||||
System.out.println("Dart process started - port " + vmPort);
|
||||
}
|
||||
|
||||
private static SampleVmServiceListener startSampleAndConnect(File dartFile) {
|
||||
startSample(dartFile);
|
||||
sleep(1000);
|
||||
vmConnect();
|
||||
SampleVmServiceListener vmListener = new SampleVmServiceListener(
|
||||
new HashSet<>(Collections.singletonList(EventKind.BreakpointResolved)));
|
||||
vmService.addVmServiceListener(vmListener);
|
||||
vmStreamListen(VmService.DEBUG_STREAM_ID);
|
||||
vmStreamListen(VmService.ISOLATE_STREAM_ID);
|
||||
return vmListener;
|
||||
}
|
||||
|
||||
private static void stopSample() {
|
||||
if (process == null) {
|
||||
return;
|
||||
}
|
||||
final Process processToStop = process;
|
||||
process = null;
|
||||
long endTime = System.currentTimeMillis() + 5000;
|
||||
while (System.currentTimeMillis() < endTime) {
|
||||
try {
|
||||
int exit = processToStop.exitValue();
|
||||
if (exit != 0) {
|
||||
System.out.println("Sample exit code: " + exit);
|
||||
}
|
||||
return;
|
||||
} catch (IllegalThreadStateException e) {
|
||||
//$FALL-THROUGH$
|
||||
}
|
||||
try {
|
||||
Thread.sleep(20);
|
||||
} catch (InterruptedException e) {
|
||||
//$FALL-THROUGH$
|
||||
}
|
||||
}
|
||||
processToStop.destroy();
|
||||
System.out.println("Terminated sample process");
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private static void vmAddBreakpoint(Isolate isolate, ScriptRef script, int lineNum) {
|
||||
final OpLatch latch = new OpLatch();
|
||||
vmService.addBreakpoint(isolate.getId(), script.getId(), lineNum, new BreakpointConsumer() {
|
||||
@Override
|
||||
public void onError(RPCError error) {
|
||||
showRPCError(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void received(Breakpoint response) {
|
||||
System.out.println("Received Breakpoint response");
|
||||
System.out.println(" BreakpointNumber:" + response.getBreakpointNumber());
|
||||
latch.opComplete();
|
||||
}
|
||||
});
|
||||
latch.waitAndAssertOpComplete();
|
||||
}
|
||||
|
||||
private static void vmConnect() {
|
||||
try {
|
||||
vmService = VmService.localConnect(vmPort);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to connect to the VM vmService service", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void vmDisconnect() {
|
||||
if (vmService != null) {
|
||||
vmService.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("SameParameterValue")
|
||||
private static void vmEvaluateInFrame(Isolate isolate, int frameIndex, String expression) {
|
||||
System.out.println("Evaluating: " + expression);
|
||||
final ResultLatch<InstanceRef> latch = new ResultLatch<>();
|
||||
vmService.evaluateInFrame(isolate.getId(), frameIndex, expression, new EvaluateInFrameConsumer() {
|
||||
@Override
|
||||
public void onError(RPCError error) {
|
||||
showRPCError(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void received(ErrorRef response) {
|
||||
showErrorAndExit(response.getMessage());
|
||||
}
|
||||
|
||||
public void received(Sentinel response) {
|
||||
System.out.println(response.getValueAsString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void received(InstanceRef response) {
|
||||
System.out.println("Received InstanceRef response");
|
||||
System.out.println(" Id: " + response.getId());
|
||||
System.out.println(" Kind: " + response.getKind());
|
||||
System.out.println(" Json: " + response.getJson());
|
||||
latch.setValue(response);
|
||||
}
|
||||
});
|
||||
InstanceRef instanceRef = latch.getValue();
|
||||
InstanceRefToString convert = new InstanceRefToString(isolate, vmService, latch);
|
||||
System.out.println("Result: " + convert.toString(instanceRef));
|
||||
}
|
||||
|
||||
private static SourceReport vmGetSourceReport(Isolate isolate) {
|
||||
System.out.println("Getting coverage information for " + isolate.getId());
|
||||
final long startTime = System.currentTimeMillis();
|
||||
final ResultLatch<SourceReport> latch = new ResultLatch<>();
|
||||
vmService.getSourceReport(isolate.getId(), Collections.singletonList(SourceReportKind.Coverage), new SourceReportConsumer() {
|
||||
@Override
|
||||
public void onError(RPCError error) {
|
||||
showRPCError(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void received(SourceReport response) {
|
||||
System.out.println("Received SourceReport response (" + (System.currentTimeMillis() - startTime) + "ms)");
|
||||
System.out.println(" Script count: " + response.getScripts().size());
|
||||
System.out.println(" Range count: " + response.getRanges().size());
|
||||
latch.setValue(response);
|
||||
}
|
||||
});
|
||||
return latch.getValue();
|
||||
}
|
||||
|
||||
private static Isolate vmGetIsolate(IsolateRef isolate) {
|
||||
final ResultLatch<Isolate> latch = new ResultLatch<>();
|
||||
vmService.getIsolate(isolate.getId(), new GetIsolateConsumer() {
|
||||
@Override
|
||||
public void onError(RPCError error) {
|
||||
showRPCError(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void received(Isolate response) {
|
||||
System.out.println("Received Isolate response");
|
||||
System.out.println(" Id: " + response.getId());
|
||||
System.out.println(" Name: " + response.getName());
|
||||
System.out.println(" Number: " + response.getNumber());
|
||||
System.out.println(" Start Time: " + response.getStartTime());
|
||||
System.out.println(" RootLib Id: " + response.getRootLib().getId());
|
||||
System.out.println(" RootLib Uri: " + response.getRootLib().getUri());
|
||||
System.out.println(" RootLib Name: " + response.getRootLib().getName());
|
||||
System.out.println(" RootLib Json: " + response.getRootLib().getJson());
|
||||
System.out.println(" Isolate: " + response);
|
||||
latch.setValue(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void received(Sentinel response) {
|
||||
showSentinel(response);
|
||||
}
|
||||
});
|
||||
return latch.getValue();
|
||||
}
|
||||
|
||||
private static Library vmGetLibrary(Isolate isolateId, LibraryRef library) {
|
||||
final ResultLatch<Library> latch = new ResultLatch<>();
|
||||
vmService.getLibrary(isolateId.getId(), library.getId(), new GetLibraryConsumer() {
|
||||
@Override
|
||||
public void onError(RPCError error) {
|
||||
showRPCError(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void received(Library response) {
|
||||
System.out.println("Received GetLibrary library");
|
||||
System.out.println(" uri: " + response.getUri());
|
||||
latch.setValue(response);
|
||||
}
|
||||
});
|
||||
return latch.getValue();
|
||||
}
|
||||
|
||||
private static void vmGetScript(Isolate isolate, ScriptRef scriptRef) {
|
||||
final ResultLatch<Script> latch = new ResultLatch<>();
|
||||
vmService.getObject(isolate.getId(), scriptRef.getId(), new GetObjectConsumer() {
|
||||
@Override
|
||||
public void onError(RPCError error) {
|
||||
showRPCError(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void received(Obj response) {
|
||||
if (response instanceof Script) {
|
||||
latch.setValue((Script) response);
|
||||
} else {
|
||||
RPCError.unexpected("Script", response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void received(Sentinel response) {
|
||||
RPCError.unexpected("Script", response);
|
||||
}
|
||||
});
|
||||
Script script = latch.getValue();
|
||||
System.out.println("Received Script");
|
||||
System.out.println(" Id: " + script.getId());
|
||||
System.out.println(" Uri: " + script.getUri());
|
||||
System.out.println(" Source: " + script.getSource());
|
||||
System.out.println(" TokenPosTable: " + script.getTokenPosTable());
|
||||
if (script.getTokenPosTable() == null) {
|
||||
showErrorAndExit("Expected TokenPosTable to be non-null");
|
||||
}
|
||||
}
|
||||
|
||||
private static void vmGetStack(Isolate isolate) {
|
||||
final ResultLatch<Stack> latch = new ResultLatch<>();
|
||||
vmService.getStack(isolate.getId(), new StackConsumer() {
|
||||
@Override
|
||||
public void onError(RPCError error) {
|
||||
showRPCError(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void received(Stack stack) {
|
||||
latch.setValue(stack);
|
||||
}
|
||||
});
|
||||
Stack stack = latch.getValue();
|
||||
System.out.println("Received Stack response");
|
||||
System.out.println(" Messages:");
|
||||
for (Message message : stack.getMessages()) {
|
||||
System.out.println(" " + message.getName());
|
||||
}
|
||||
System.out.println(" Frames:");
|
||||
InstanceRefToString convert = new InstanceRefToString(isolate, vmService, latch);
|
||||
for (Frame frame : stack.getFrames()) {
|
||||
showFrame(convert, frame);
|
||||
}
|
||||
}
|
||||
|
||||
private static void vmGetVersion() {
|
||||
final OpLatch latch = new OpLatch();
|
||||
vmService.getVersion(new VersionConsumer() {
|
||||
@Override
|
||||
public void onError(RPCError error) {
|
||||
showRPCError(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void received(Version response) {
|
||||
System.out.println("Received Version response");
|
||||
actualVmServiceVersionMajor = response.getMajor();
|
||||
System.out.println(" Major: " + actualVmServiceVersionMajor);
|
||||
System.out.println(" Minor: " + response.getMinor());
|
||||
System.out.println(response.getJson());
|
||||
latch.opComplete();
|
||||
}
|
||||
});
|
||||
latch.waitAndAssertOpComplete();
|
||||
}
|
||||
|
||||
private static void vmCallServiceExtension(Isolate isolateId) {
|
||||
final OpLatch latch = new OpLatch();
|
||||
vmService.callServiceExtension(isolateId.getId(), "getIsolate", new ServiceExtensionConsumer() {
|
||||
@Override
|
||||
public void onError(RPCError error) {
|
||||
showRPCError(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void received(JsonObject result) {
|
||||
System.out.println("Received response: " + result);
|
||||
latch.opComplete();
|
||||
}
|
||||
});
|
||||
latch.waitAndAssertOpComplete();
|
||||
}
|
||||
|
||||
private static ElementList<IsolateRef> vmGetVmIsolates() {
|
||||
final ResultLatch<ElementList<IsolateRef>> latch = new ResultLatch<>();
|
||||
vmService.getVM(new VMConsumer() {
|
||||
@Override
|
||||
public void onError(RPCError error) {
|
||||
showRPCError(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void received(VM response) {
|
||||
System.out.println("Received VM response");
|
||||
System.out.println(" ArchitectureBits: " + response.getArchitectureBits());
|
||||
System.out.println(" HostCPU: " + response.getHostCPU());
|
||||
System.out.println(" TargetCPU: " + response.getTargetCPU());
|
||||
System.out.println(" Pid: " + response.getPid());
|
||||
System.out.println(" StartTime: " + response.getStartTime());
|
||||
for (IsolateRef isolate : response.getIsolates()) {
|
||||
System.out.println(" Isolate " + isolate.getNumber() + ", " + isolate.getId() + ", "
|
||||
+ isolate.getName());
|
||||
}
|
||||
latch.setValue(response.getIsolates());
|
||||
}
|
||||
});
|
||||
return latch.getValue();
|
||||
}
|
||||
|
||||
private static void vmPauseOnException(IsolateRef isolate, ExceptionPauseMode mode) {
|
||||
System.out.println("Request pause on exception: " + mode);
|
||||
final OpLatch latch = new OpLatch();
|
||||
vmService.setExceptionPauseMode(isolate.getId(), mode, new SuccessConsumer() {
|
||||
@Override
|
||||
public void onError(RPCError error) {
|
||||
showRPCError(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void received(Success response) {
|
||||
System.out.println("Successfully set pause on exception");
|
||||
latch.opComplete();
|
||||
}
|
||||
});
|
||||
latch.waitAndAssertOpComplete();
|
||||
}
|
||||
|
||||
private static void vmResume(IsolateRef isolateRef, final StepOption step) {
|
||||
final String id = isolateRef.getId();
|
||||
vmService.resume(id, step, null, new SuccessConsumer() {
|
||||
@Override
|
||||
public void onError(RPCError error) {
|
||||
showRPCError(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void received(Success response) {
|
||||
if (step == null) {
|
||||
System.out.println("Resumed isolate " + id);
|
||||
} else {
|
||||
System.out.println("Step " + step + " isolate " + id);
|
||||
}
|
||||
}
|
||||
});
|
||||
// Do not wait for confirmation, but display error if it occurs
|
||||
}
|
||||
|
||||
private static void vmStreamListen(String streamId) {
|
||||
final OpLatch latch = new OpLatch();
|
||||
vmService.streamListen(streamId, new SuccessConsumer() {
|
||||
@Override
|
||||
public void onError(RPCError error) {
|
||||
showRPCError(error);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void received(Success response) {
|
||||
System.out.println("Subscribed to debug event stream");
|
||||
latch.opComplete();
|
||||
}
|
||||
});
|
||||
latch.waitAndAssertOpComplete();
|
||||
}
|
||||
|
||||
private static void waitForProcessExit() {
|
||||
if (actualVmServiceVersionMajor == 2) {
|
||||
// Don't wait for VM 1.12 - protocol 2.1
|
||||
return;
|
||||
}
|
||||
long end = System.currentTimeMillis() + 5000;
|
||||
while (true) {
|
||||
try {
|
||||
System.out.println("Exit code: " + process.exitValue());
|
||||
return;
|
||||
} catch (IllegalThreadStateException e) {
|
||||
// fall through to wait for exit
|
||||
}
|
||||
if (System.currentTimeMillis() >= end) {
|
||||
throw new RuntimeException("Expected child process to finish");
|
||||
}
|
||||
sleep(10);
|
||||
}
|
||||
}
|
||||
}
|
1
pkg/vm_service/java/version.properties
Normal file
1
pkg/vm_service/java/version.properties
Normal file
|
@ -0,0 +1 @@
|
|||
version=3.23
|
32
pkg/vm_service/lib/src/helpers.dart
Normal file
32
pkg/vm_service/lib/src/helpers.dart
Normal file
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) 2017, 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 '../vm_service.dart';
|
||||
|
||||
class IsolateHelper {
|
||||
static List<TagCounter> getTagCounters(Isolate isolate) {
|
||||
Map m = isolate.json['_tagCounters'];
|
||||
List<String> names = m['names'];
|
||||
List<int> counters = m['counters'];
|
||||
|
||||
List<TagCounter> result = [];
|
||||
for (int i = 0; i < counters.length; i++) {
|
||||
result.add(new TagCounter(names[i], counters[i]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class TagCounter {
|
||||
final String name;
|
||||
final int count;
|
||||
|
||||
TagCounter(this.name, this.count);
|
||||
}
|
||||
|
||||
//class GraphEventHelper {
|
||||
// // int chunkIndex
|
||||
// // int chunkCount
|
||||
// // int nodeCount
|
||||
//}
|
67
pkg/vm_service/lib/src/service_extension_registry.dart
Normal file
67
pkg/vm_service/lib/src/service_extension_registry.dart
Normal file
|
@ -0,0 +1,67 @@
|
|||
// Copyright (c) 2019, 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 'dart:async';
|
||||
|
||||
import '../vm_service.dart' show VmServerConnection, RPCError, Event, EventKind;
|
||||
import 'stream_helpers.dart';
|
||||
|
||||
/// A registry of custom service extensions to [VmServerConnection]s in which
|
||||
/// they were registered.
|
||||
class ServiceExtensionRegistry {
|
||||
/// Maps service extensions registered through the protocol to the
|
||||
/// [VmServerConnection] in which they were registered.
|
||||
///
|
||||
/// Note: this does not track services registered through `dart:developer`,
|
||||
/// only the services registered through the `_registerService` rpc method.
|
||||
final _extensionToConnection = <String, VmServerConnection>{};
|
||||
|
||||
/// Controller for tracking registration and unregistration events.
|
||||
final _eventController = StreamController<Event>.broadcast();
|
||||
|
||||
ServiceExtensionRegistry();
|
||||
|
||||
/// Registers [extension] for [client].
|
||||
///
|
||||
/// All future requests for [extension] will be routed to [client].
|
||||
void registerExtension(String extension, VmServerConnection client) {
|
||||
if (_extensionToConnection.containsKey(extension)) {
|
||||
throw RPCError('registerExtension', 111, 'Service already registered');
|
||||
}
|
||||
_eventController.sink.add(_toRegistrationEvent(extension));
|
||||
_extensionToConnection[extension] = client;
|
||||
// Remove the mapping if the client disconnects.
|
||||
client.done.whenComplete(() {
|
||||
_extensionToConnection.remove(extension);
|
||||
_eventController.sink.add(_toRegistrationEvent(extension,
|
||||
kind: EventKind.kServiceUnregistered));
|
||||
});
|
||||
}
|
||||
|
||||
/// Returns the [VmServerConnection] for a given [extension], or `null` if
|
||||
/// none is registered.
|
||||
///
|
||||
/// The result of this function should not be stored, because clients may
|
||||
/// shut down at any time.
|
||||
VmServerConnection clientFor(String extension) =>
|
||||
_extensionToConnection[extension];
|
||||
|
||||
/// All of the currently registered extensions
|
||||
Iterable<String> get registeredExtensions => _extensionToConnection.keys;
|
||||
|
||||
/// Emits an [Event] of type `ServiceRegistered` for all current and future
|
||||
/// extensions that are registered, and `ServiceUnregistered` when those
|
||||
/// clients disconnect.
|
||||
Stream<Event> get onExtensionEvent => _eventController.stream
|
||||
.transform(startWithMany(registeredExtensions.map(_toRegistrationEvent)));
|
||||
|
||||
/// Creates a `_Service` stream event, with a default kind of
|
||||
/// [EventKind.kServiceRegistered].
|
||||
Event _toRegistrationEvent(String method,
|
||||
{String kind = EventKind.kServiceRegistered}) =>
|
||||
Event()
|
||||
..kind = kind
|
||||
..service = method
|
||||
..method = method;
|
||||
}
|
99
pkg/vm_service/lib/src/stream_helpers.dart
Normal file
99
pkg/vm_service/lib/src/stream_helpers.dart
Normal file
|
@ -0,0 +1,99 @@
|
|||
// Copyright (c) 2019, 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 'dart:async';
|
||||
|
||||
/// Copied from package:stream_transform.
|
||||
|
||||
/// Starts emitting values from [next] after the original stream is complete.
|
||||
///
|
||||
/// If the initial stream never finishes, the [next] stream will never be
|
||||
/// listened to.
|
||||
///
|
||||
/// If a single-subscription follows the a broadcast stream it may be listened
|
||||
/// to and never canceled.
|
||||
///
|
||||
/// If a broadcast stream follows any other stream it will miss any events which
|
||||
/// occur before the first stream is done. If a broadcast stream follows a
|
||||
/// single-subscription stream, pausing the stream while it is listening to the
|
||||
/// second stream will cause events to be dropped rather than buffered.
|
||||
StreamTransformer<T, T> followedBy<T>(Stream<T> next) => _FollowedBy<T>(next);
|
||||
|
||||
class _FollowedBy<T> extends StreamTransformerBase<T, T> {
|
||||
final Stream<T> _next;
|
||||
|
||||
_FollowedBy(this._next);
|
||||
|
||||
@override
|
||||
Stream<T> bind(Stream<T> first) {
|
||||
var controller = first.isBroadcast
|
||||
? StreamController<T>.broadcast(sync: true)
|
||||
: StreamController<T>(sync: true);
|
||||
|
||||
var next = first.isBroadcast && !_next.isBroadcast
|
||||
? _next.asBroadcastStream()
|
||||
: _next;
|
||||
|
||||
StreamSubscription<T> subscription;
|
||||
var currentStream = first;
|
||||
var firstDone = false;
|
||||
var secondDone = false;
|
||||
|
||||
Function currentDoneHandler;
|
||||
|
||||
listen() {
|
||||
subscription = currentStream.listen(controller.add,
|
||||
onError: controller.addError, onDone: () => currentDoneHandler());
|
||||
}
|
||||
|
||||
onSecondDone() {
|
||||
secondDone = true;
|
||||
controller.close();
|
||||
}
|
||||
|
||||
onFirstDone() {
|
||||
firstDone = true;
|
||||
currentStream = next;
|
||||
currentDoneHandler = onSecondDone;
|
||||
listen();
|
||||
}
|
||||
|
||||
currentDoneHandler = onFirstDone;
|
||||
|
||||
controller.onListen = () {
|
||||
assert(subscription == null);
|
||||
listen();
|
||||
if (!first.isBroadcast) {
|
||||
controller
|
||||
..onPause = () {
|
||||
if (!firstDone || !next.isBroadcast) return subscription.pause();
|
||||
subscription.cancel();
|
||||
subscription = null;
|
||||
}
|
||||
..onResume = () {
|
||||
if (!firstDone || !next.isBroadcast) return subscription.resume();
|
||||
listen();
|
||||
};
|
||||
}
|
||||
controller.onCancel = () {
|
||||
if (secondDone) return null;
|
||||
var toCancel = subscription;
|
||||
subscription = null;
|
||||
return toCancel.cancel();
|
||||
};
|
||||
};
|
||||
return controller.stream;
|
||||
}
|
||||
}
|
||||
|
||||
StreamTransformer<T, T> startWithMany<T>(Iterable<T> initial) =>
|
||||
startWithStream<T>(Stream.fromIterable(initial));
|
||||
|
||||
StreamTransformer<T, T> startWithStream<T>(Stream<T> initial) =>
|
||||
StreamTransformer.fromBind((values) {
|
||||
if (values.isBroadcast && !initial.isBroadcast) {
|
||||
initial = initial.asBroadcastStream();
|
||||
}
|
||||
return initial.transform(followedBy(values));
|
||||
});
|
22
pkg/vm_service/lib/utils.dart
Normal file
22
pkg/vm_service/lib/utils.dart
Normal file
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) 2019, 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:meta/meta.dart';
|
||||
|
||||
/// Map the URI to a WebSocket URI for the VM service protocol.
|
||||
///
|
||||
/// If the URI is already a VM Service WebSocket URI it will not be modified.
|
||||
Uri convertToWebSocketUrl({@required Uri serviceProtocolUrl}) {
|
||||
final isSecure = serviceProtocolUrl.isScheme('wss') ||
|
||||
serviceProtocolUrl.isScheme('https');
|
||||
final scheme = isSecure ? 'wss' : 'ws';
|
||||
|
||||
final path = serviceProtocolUrl.path.endsWith('/ws')
|
||||
? serviceProtocolUrl.path
|
||||
: (serviceProtocolUrl.path.endsWith('/')
|
||||
? '${serviceProtocolUrl.path}ws'
|
||||
: '${serviceProtocolUrl.path}/ws');
|
||||
|
||||
return serviceProtocolUrl.replace(scheme: scheme, path: path);
|
||||
}
|
5365
pkg/vm_service/lib/vm_service.dart
Normal file
5365
pkg/vm_service/lib/vm_service.dart
Normal file
File diff suppressed because it is too large
Load diff
26
pkg/vm_service/lib/vm_service_io.dart
Normal file
26
pkg/vm_service/lib/vm_service_io.dart
Normal file
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) 2016, 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 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'vm_service.dart';
|
||||
|
||||
Future<VmService> vmServiceConnect(String host, int port, {Log log}) async {
|
||||
WebSocket socket = await WebSocket.connect('ws://$host:$port/ws');
|
||||
StreamController<String> controller = new StreamController();
|
||||
socket.listen((data) => controller.add(data));
|
||||
return new VmService(
|
||||
controller.stream, (String message) => socket.add(message),
|
||||
log: log, disposeHandler: () => socket.close());
|
||||
}
|
||||
|
||||
Future<VmService> vmServiceConnectUri(String wsUri, {Log log}) async {
|
||||
WebSocket socket = await WebSocket.connect(wsUri);
|
||||
StreamController<String> controller = new StreamController();
|
||||
socket.listen((data) => controller.add(data));
|
||||
return new VmService(
|
||||
controller.stream, (String message) => socket.add(message),
|
||||
log: log, disposeHandler: () => socket.close());
|
||||
}
|
21
pkg/vm_service/pubspec.yaml
Normal file
21
pkg/vm_service/pubspec.yaml
Normal file
|
@ -0,0 +1,21 @@
|
|||
name: vm_service
|
||||
description: A library to access the VM Service API.
|
||||
version: 1.0.0
|
||||
|
||||
author: Dart Team <misc@dartlang.org>
|
||||
homepage: https://github.com/dart-lang/sdk/pkg/vm_service/
|
||||
|
||||
environment:
|
||||
sdk: '>=2.0.0 <3.0.0'
|
||||
|
||||
dependencies:
|
||||
meta: ^1.0.2
|
||||
|
||||
dev_dependencies:
|
||||
async: ^2.0.0
|
||||
markdown: ^2.0.0
|
||||
mockito: ^4.0.0
|
||||
path: ^1.0.0
|
||||
pedantic: ^1.7.0
|
||||
pub_semver: ^1.0.0
|
||||
test: ^1.0.0
|
443
pkg/vm_service/test/server_test.dart
Normal file
443
pkg/vm_service/test/server_test.dart
Normal file
|
@ -0,0 +1,443 @@
|
|||
// Copyright (c) 2019, 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.
|
||||
|
||||
@TestOn('vm')
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:async/async.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:pedantic/pedantic.dart';
|
||||
import 'package:test/test.dart';
|
||||
import 'package:vm_service/vm_service.dart';
|
||||
|
||||
void main() {
|
||||
MockVmService serviceMock;
|
||||
StreamController<Map<String, Object>> requestsController;
|
||||
StreamController<Map<String, Object>> responsesController;
|
||||
ServiceExtensionRegistry serviceRegistry;
|
||||
|
||||
setUp(() {
|
||||
serviceMock = MockVmService();
|
||||
requestsController = StreamController<Map<String, Object>>();
|
||||
responsesController = StreamController<Map<String, Object>>();
|
||||
serviceRegistry = ServiceExtensionRegistry();
|
||||
VmServerConnection(requestsController.stream, responsesController.sink,
|
||||
serviceRegistry, serviceMock);
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
requestsController.close();
|
||||
responsesController.close();
|
||||
});
|
||||
|
||||
group('method delegation', () {
|
||||
test('works for simple methods', () {
|
||||
var request = rpcRequest("getVersion");
|
||||
var version = Version()
|
||||
..major = 1
|
||||
..minor = 0;
|
||||
when(serviceMock.getVersion()).thenAnswer((_) => Future.value(version));
|
||||
expect(responsesController.stream, emits(rpcResponse(version)));
|
||||
requestsController.add(request);
|
||||
});
|
||||
|
||||
test('works for methods with parameters', () {
|
||||
var isolate = Isolate()
|
||||
..id = '123'
|
||||
..number = '0'
|
||||
..startTime = 1
|
||||
..runnable = true
|
||||
..livePorts = 2
|
||||
..pauseOnExit = false
|
||||
..pauseEvent = (Event()
|
||||
..kind = EventKind.kResume
|
||||
..timestamp = 3)
|
||||
..libraries = []
|
||||
..breakpoints = [];
|
||||
var request = rpcRequest("getIsolate", params: {'isolateId': isolate.id});
|
||||
when(serviceMock.getIsolate(isolate.id))
|
||||
.thenAnswer((Invocation invocation) {
|
||||
expect(invocation.positionalArguments, equals([isolate.id]));
|
||||
return Future.value(isolate);
|
||||
});
|
||||
expect(responsesController.stream, emits(rpcResponse(isolate)));
|
||||
requestsController.add(request);
|
||||
});
|
||||
|
||||
group('custom service extensions', () {
|
||||
test('with no params or isolateId', () {
|
||||
var extension = 'ext.cool';
|
||||
var request = rpcRequest(extension, params: null);
|
||||
var response = Response()..json = {"hello": "world"};
|
||||
when(serviceMock.callServiceExtension(
|
||||
extension,
|
||||
isolateId: argThat(isNull, named: 'isolateId'),
|
||||
args: argThat(isNull, named: 'args'),
|
||||
)).thenAnswer((Invocation invocation) {
|
||||
expect(invocation.namedArguments,
|
||||
equals({Symbol('isolateId'): null, Symbol('args'): null}));
|
||||
return Future.value(response);
|
||||
});
|
||||
expect(responsesController.stream, emits(rpcResponse(response)));
|
||||
requestsController.add(request);
|
||||
});
|
||||
|
||||
test('with isolateId and no other params', () {
|
||||
var extension = 'ext.cool';
|
||||
var request = rpcRequest(extension, params: {'isolateId': '1'});
|
||||
var response = Response()..json = {"hello": "world"};
|
||||
when(serviceMock.callServiceExtension(
|
||||
extension,
|
||||
isolateId: argThat(equals('1'), named: 'isolateId'),
|
||||
args: argThat(equals({}), named: 'args'),
|
||||
)).thenAnswer((Invocation invocation) {
|
||||
expect(invocation.namedArguments,
|
||||
equals({Symbol('isolateId'): '1', Symbol('args'): {}}));
|
||||
return Future.value(response);
|
||||
});
|
||||
expect(responsesController.stream, emits(rpcResponse(response)));
|
||||
requestsController.add(request);
|
||||
});
|
||||
|
||||
test('with params and no isolateId', () {
|
||||
var extension = 'ext.cool';
|
||||
var params = {'cool': 'option'};
|
||||
var request = rpcRequest(extension, params: params);
|
||||
var response = Response()..json = {"hello": "world"};
|
||||
when(serviceMock.callServiceExtension(
|
||||
extension,
|
||||
isolateId: argThat(isNull, named: 'isolateId'),
|
||||
args: argThat(equals(params), named: 'args'),
|
||||
)).thenAnswer((Invocation invocation) {
|
||||
expect(invocation.namedArguments,
|
||||
equals({Symbol('isolateId'): null, Symbol('args'): params}));
|
||||
return Future.value(response);
|
||||
});
|
||||
expect(responsesController.stream, emits(rpcResponse(response)));
|
||||
requestsController.add(request);
|
||||
});
|
||||
|
||||
test('with params and isolateId', () {
|
||||
var extension = 'ext.cool';
|
||||
var params = {'cool': 'option'};
|
||||
var request =
|
||||
rpcRequest(extension, params: Map.of(params)..['isolateId'] = '1');
|
||||
var response = Response()..json = {"hello": "world"};
|
||||
when(serviceMock.callServiceExtension(
|
||||
extension,
|
||||
isolateId: argThat(equals("1"), named: 'isolateId'),
|
||||
args: argThat(equals(params), named: 'args'),
|
||||
)).thenAnswer((Invocation invocation) {
|
||||
expect(invocation.namedArguments,
|
||||
equals({Symbol('isolateId'): '1', Symbol('args'): params}));
|
||||
return Future.value(response);
|
||||
});
|
||||
expect(responsesController.stream, emits(rpcResponse(response)));
|
||||
requestsController.add(request);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('error handling', () {
|
||||
test('special cases RPCError instances', () {
|
||||
var request = rpcRequest("getVersion");
|
||||
var error =
|
||||
RPCError('getVersion', 1234, 'custom message', {'custom': 'data'});
|
||||
when(serviceMock.getVersion()).thenAnswer((_) => Future.error(error));
|
||||
expect(responsesController.stream, emits(rpcErrorResponse(error)));
|
||||
requestsController.add(request);
|
||||
});
|
||||
|
||||
test('has a fallback for generic exceptions', () {
|
||||
var request = rpcRequest("getVersion");
|
||||
var error = UnimplementedError();
|
||||
when(serviceMock.getVersion()).thenAnswer((_) => Future.error(error));
|
||||
expect(
|
||||
responsesController.stream.map((response) => '$response'),
|
||||
emits(startsWith(
|
||||
'{jsonrpc: 2.0, error: {code: -32603, message: UnimplementedError')));
|
||||
requestsController.add(request);
|
||||
});
|
||||
});
|
||||
|
||||
group('streams', () {
|
||||
test('can be listened to and canceled', () async {
|
||||
var streamId = 'Isolate';
|
||||
var responseQueue = StreamQueue(responsesController.stream);
|
||||
StreamController<Event> eventController;
|
||||
{
|
||||
var request =
|
||||
rpcRequest('streamListen', params: {'streamId': streamId});
|
||||
var response = Success();
|
||||
when(serviceMock.streamListen(streamId))
|
||||
.thenAnswer((_) => Future.value(response));
|
||||
requestsController.add(request);
|
||||
await expect(responseQueue, emitsThrough(rpcResponse(response)));
|
||||
|
||||
eventController = serviceMock.streamControllers[streamId];
|
||||
|
||||
var events = [
|
||||
Event()
|
||||
..kind = EventKind.kIsolateStart
|
||||
..timestamp = 0,
|
||||
Event()
|
||||
..kind = EventKind.kIsolateExit
|
||||
..timestamp = 1,
|
||||
];
|
||||
events.forEach(eventController.add);
|
||||
await expect(
|
||||
responseQueue,
|
||||
emitsInOrder(
|
||||
events.map((event) => streamNotifyResponse(streamId, event))));
|
||||
}
|
||||
{
|
||||
var request =
|
||||
rpcRequest('streamCancel', params: {'streamId': streamId});
|
||||
var response = Success();
|
||||
when(serviceMock.streamListen(streamId))
|
||||
.thenAnswer((_) => Future.value(response));
|
||||
requestsController.add(request);
|
||||
await expect(responseQueue, emitsThrough(rpcResponse(response)));
|
||||
|
||||
var nextEvent = Event()
|
||||
..kind = EventKind.kIsolateReload
|
||||
..timestamp = 2;
|
||||
eventController.add(nextEvent);
|
||||
expect(responseQueue,
|
||||
neverEmits(streamNotifyResponse(streamId, nextEvent)));
|
||||
|
||||
await pumpEventQueue();
|
||||
await eventController.close();
|
||||
await responsesController.close();
|
||||
}
|
||||
});
|
||||
test("can't be listened to twice", () {
|
||||
var streamId = 'Isolate';
|
||||
var responseQueue = StreamQueue(responsesController.stream);
|
||||
{
|
||||
var request =
|
||||
rpcRequest('streamListen', params: {'streamId': streamId});
|
||||
var response = Success();
|
||||
when(serviceMock.streamListen(streamId))
|
||||
.thenAnswer((_) => Future.value(response));
|
||||
requestsController.add(request);
|
||||
expect(responseQueue, emitsThrough(rpcResponse(response)));
|
||||
}
|
||||
{
|
||||
var request =
|
||||
rpcRequest('streamListen', params: {'streamId': streamId});
|
||||
var response = Success();
|
||||
when(serviceMock.streamListen(streamId))
|
||||
.thenAnswer((_) => Future.value(response));
|
||||
requestsController.add(request);
|
||||
expect(
|
||||
responseQueue,
|
||||
emitsThrough(rpcErrorResponse(
|
||||
RPCError('streamSubcribe', 103, 'Stream already subscribed', {
|
||||
'details': "The stream '$streamId' is already subscribed",
|
||||
}))));
|
||||
}
|
||||
});
|
||||
|
||||
test("can't cancel a stream that isn't being listened to", () {
|
||||
var streamId = 'Isolate';
|
||||
var responseQueue = StreamQueue(responsesController.stream);
|
||||
|
||||
var request = rpcRequest('streamCancel', params: {'streamId': streamId});
|
||||
var response = Success();
|
||||
when(serviceMock.streamListen(streamId))
|
||||
.thenAnswer((_) => Future.value(response));
|
||||
requestsController.add(request);
|
||||
expect(
|
||||
responseQueue,
|
||||
emitsThrough(rpcErrorResponse(
|
||||
RPCError('streamCancel', 104, 'Stream not subscribed', {
|
||||
'details': "The stream '$streamId' is not subscribed",
|
||||
}))));
|
||||
});
|
||||
|
||||
group('Service', () {
|
||||
final serviceStream = 'Service';
|
||||
|
||||
test('gives register and unregister events', () async {
|
||||
var serviceId = 'ext.test.service';
|
||||
var serviceRegisteredEvent = streamNotifyResponse(
|
||||
serviceStream,
|
||||
Event()
|
||||
..kind = EventKind.kServiceRegistered
|
||||
..method = serviceId
|
||||
..service = serviceId);
|
||||
var serviceUnRegisteredEvent = streamNotifyResponse(
|
||||
serviceStream,
|
||||
Event()
|
||||
..kind = EventKind.kServiceUnregistered
|
||||
..method = serviceId
|
||||
..service = serviceId);
|
||||
|
||||
requestsController.add(
|
||||
rpcRequest('streamListen', params: {'streamId': serviceStream}));
|
||||
requestsController
|
||||
.add(rpcRequest('registerService', params: {'service': serviceId}));
|
||||
await expect(
|
||||
responsesController.stream, emitsThrough(serviceRegisteredEvent));
|
||||
|
||||
// Connect another client to get the previous register events and the
|
||||
// unregister event.
|
||||
var requestsController2 = StreamController<Map<String, Object>>();
|
||||
var responsesController2 = StreamController<Map<String, Object>>();
|
||||
addTearDown(() {
|
||||
requestsController2.close();
|
||||
responsesController2.close();
|
||||
});
|
||||
|
||||
VmServerConnection(requestsController2.stream,
|
||||
responsesController2.sink, serviceRegistry, null);
|
||||
|
||||
expect(
|
||||
responsesController2.stream,
|
||||
emitsThrough(emitsInOrder(
|
||||
[serviceRegisteredEvent, serviceUnRegisteredEvent])));
|
||||
|
||||
// Should get the previously registered extension event, as well as
|
||||
// the unregister event when the client disconnects.
|
||||
requestsController2.add(
|
||||
rpcRequest('streamListen', params: {'streamId': serviceStream}));
|
||||
// Need to give the client a chance to subscribe.
|
||||
await pumpEventQueue();
|
||||
unawaited(requestsController.close());
|
||||
// Give the old client a chance to shut down
|
||||
await pumpEventQueue();
|
||||
|
||||
// Connect yet another client, it should get zero registration or
|
||||
// unregistration events.
|
||||
var requestsController3 = StreamController<Map<String, Object>>();
|
||||
var responsesController3 = StreamController<Map<String, Object>>();
|
||||
|
||||
VmServerConnection(requestsController3.stream,
|
||||
responsesController3.sink, serviceRegistry, null);
|
||||
expect(
|
||||
responsesController3.stream,
|
||||
neverEmits(
|
||||
anyOf(serviceRegisteredEvent, serviceUnRegisteredEvent)));
|
||||
// Give it a chance to deliver events.
|
||||
await pumpEventQueue();
|
||||
// Disconnect the client so the test can shut down.
|
||||
unawaited(requestsController3.close());
|
||||
unawaited(responsesController3.close());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
group('registerService', () {
|
||||
test('can delegate requests between clients', () async {
|
||||
var serviceId = 'ext.test.service';
|
||||
var responseQueue = StreamQueue(responsesController.stream);
|
||||
|
||||
var clientInputController =
|
||||
StreamController<Map<String, Object>>.broadcast();
|
||||
var clientOutputController =
|
||||
StreamController<Map<String, Object>>.broadcast();
|
||||
var client = VmService(clientInputController.stream.map(jsonEncode),
|
||||
(String message) => clientOutputController.add(jsonDecode(message)),
|
||||
disposeHandler: () async {
|
||||
await clientInputController.close();
|
||||
await clientOutputController.close();
|
||||
});
|
||||
|
||||
var clientConnection = VmServerConnection(clientOutputController.stream,
|
||||
clientInputController.sink, serviceRegistry, serviceMock);
|
||||
|
||||
var requestParams = {'foo': 'bar'};
|
||||
var expectedResponse = Response()..json = {'zap': 'zip'};
|
||||
await client.registerService(serviceId, null);
|
||||
// Duplicate registrations should fail.
|
||||
expect(client.registerService(serviceId, null),
|
||||
throwsA(const TypeMatcher<RPCError>()));
|
||||
|
||||
await client.registerServiceCallback(serviceId, (request) async {
|
||||
expect(request, equals(requestParams));
|
||||
return {'result': expectedResponse.toJson()};
|
||||
});
|
||||
|
||||
var serviceRequest = rpcRequest(serviceId, params: requestParams);
|
||||
|
||||
requestsController.add(serviceRequest);
|
||||
expect(await responseQueue.next, rpcResponse(expectedResponse));
|
||||
|
||||
// Kill the client that registered the handler, it should now fall back
|
||||
// on `callServiceExtension`.
|
||||
client.dispose();
|
||||
// This should complete as well.
|
||||
await clientConnection.done;
|
||||
|
||||
var mockResponse = Response()..json = {'mock': 'response'};
|
||||
when(serviceMock.callServiceExtension(serviceId,
|
||||
args: argThat(equals(requestParams), named: 'args'),
|
||||
isolateId: argThat(isNull, named: 'isolateId')))
|
||||
.thenAnswer((_) async => mockResponse);
|
||||
|
||||
requestsController.add(serviceRequest);
|
||||
expect(await responseQueue.next, rpcResponse(mockResponse));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Map<String, Object> rpcRequest(String method,
|
||||
{Map<String, Object> params = const {}, String id = "1"}) =>
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": method,
|
||||
"params": params,
|
||||
"id": id,
|
||||
};
|
||||
|
||||
Map<String, Object> rpcResponse(Response response, {String id = "1"}) => {
|
||||
'jsonrpc': '2.0',
|
||||
'id': id,
|
||||
'result': response.toJson(),
|
||||
};
|
||||
|
||||
Map<String, Object> rpcErrorResponse(Object error, {String id = "1"}) {
|
||||
Map<String, Object> errorJson;
|
||||
if (error is RPCError) {
|
||||
errorJson = {
|
||||
'code': error.code,
|
||||
'message': error.message,
|
||||
};
|
||||
if (error.data != null) {
|
||||
errorJson['data'] = error.data;
|
||||
}
|
||||
} else {
|
||||
errorJson = {
|
||||
'code': -32603,
|
||||
'message': error.toString(),
|
||||
};
|
||||
}
|
||||
return {
|
||||
'jsonrpc': '2.0',
|
||||
'error': errorJson,
|
||||
'id': id,
|
||||
};
|
||||
}
|
||||
|
||||
Map<String, Object> streamNotifyResponse(String streamId, Event event) {
|
||||
return {
|
||||
'jsonrpc': '2.0',
|
||||
'method': 'streamNotify',
|
||||
'params': {
|
||||
'streamId': '$streamId',
|
||||
'event': event.toJson(),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
class MockVmService extends Mock implements VmServiceInterface {
|
||||
final streamControllers = <String, StreamController<Event>>{};
|
||||
|
||||
@override
|
||||
Stream<Event> onEvent(String streamId) => streamControllers
|
||||
.putIfAbsent(streamId, () => StreamController<Event>())
|
||||
.stream;
|
||||
}
|
35
pkg/vm_service/test/utils_test.dart
Normal file
35
pkg/vm_service/test/utils_test.dart
Normal file
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) 2019, 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:test/test.dart';
|
||||
import 'package:vm_service/utils.dart';
|
||||
|
||||
void main() {
|
||||
test('convertToWebSocketUrl maps URIs correctly', () {
|
||||
final testCases = {
|
||||
'http://localhost:123/': 'ws://localhost:123/ws',
|
||||
'https://localhost:123/': 'wss://localhost:123/ws',
|
||||
'ws://localhost:123/': 'ws://localhost:123/ws',
|
||||
'wss://localhost:123/': 'wss://localhost:123/ws',
|
||||
'http://localhost:123/ABCDEF=/': 'ws://localhost:123/ABCDEF=/ws',
|
||||
'https://localhost:123/ABCDEF=/': 'wss://localhost:123/ABCDEF=/ws',
|
||||
'ws://localhost:123/ABCDEF=/': 'ws://localhost:123/ABCDEF=/ws',
|
||||
'wss://localhost:123/ABCDEF=/': 'wss://localhost:123/ABCDEF=/ws',
|
||||
'http://localhost:123': 'ws://localhost:123/ws',
|
||||
'https://localhost:123': 'wss://localhost:123/ws',
|
||||
'ws://localhost:123': 'ws://localhost:123/ws',
|
||||
'wss://localhost:123': 'wss://localhost:123/ws',
|
||||
'http://localhost:123/ABCDEF=': 'ws://localhost:123/ABCDEF=/ws',
|
||||
'https://localhost:123/ABCDEF=': 'wss://localhost:123/ABCDEF=/ws',
|
||||
'ws://localhost:123/ABCDEF=': 'ws://localhost:123/ABCDEF=/ws',
|
||||
'wss://localhost:123/ABCDEF=': 'wss://localhost:123/ABCDEF=/ws',
|
||||
};
|
||||
|
||||
testCases.forEach((String input, String expected) {
|
||||
final inputUri = Uri.parse(input);
|
||||
final actualUri = convertToWebSocketUrl(serviceProtocolUrl: inputUri);
|
||||
expect(actualUri.toString(), equals(expected));
|
||||
});
|
||||
});
|
||||
}
|
30
pkg/vm_service/tool/common/generate_common.dart
Normal file
30
pkg/vm_service/tool/common/generate_common.dart
Normal file
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) 2015, 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.
|
||||
|
||||
library generate_vm_service_common;
|
||||
|
||||
import 'package:markdown/markdown.dart';
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
|
||||
import 'src_gen_common.dart';
|
||||
|
||||
/// [ApiParseUtil] contains top level parsing utilities.
|
||||
class ApiParseUtil {
|
||||
/// Extract the current VM Service version number as a String.
|
||||
static String parseVersionString(List<Node> nodes) =>
|
||||
parseVersionSemVer(nodes).toString();
|
||||
|
||||
static Version parseVersionSemVer(List<Node> nodes) {
|
||||
final RegExp regex = new RegExp(r'[\d.]+');
|
||||
|
||||
// Extract version from header: `# Dart VM Service Protocol 2.0`.
|
||||
Element node = nodes.firstWhere((n) => isH1(n));
|
||||
Text text = node.children[0];
|
||||
Match match = regex.firstMatch(text.text);
|
||||
if (match == null) throw 'Unable to locate service protocol version';
|
||||
|
||||
// Append a `.0`.
|
||||
return new Version.parse('${match.group(0)}.0');
|
||||
}
|
||||
}
|
172
pkg/vm_service/tool/common/parser.dart
Normal file
172
pkg/vm_service/tool/common/parser.dart
Normal file
|
@ -0,0 +1,172 @@
|
|||
// Copyright (c) 2015, 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.
|
||||
|
||||
library parser;
|
||||
|
||||
class Token {
|
||||
static final RegExp _alpha = new RegExp(r'^[0-9a-zA-Z_\-@]+$');
|
||||
|
||||
final String text;
|
||||
Token next;
|
||||
|
||||
Token(this.text);
|
||||
|
||||
bool get eof => text == null;
|
||||
|
||||
bool get isName {
|
||||
if (text == null || text.isEmpty) return false;
|
||||
return _alpha.hasMatch(text);
|
||||
}
|
||||
|
||||
bool get isComment => text != null && text.startsWith('//');
|
||||
|
||||
String toString() => text == null ? 'EOF' : text;
|
||||
}
|
||||
|
||||
class Tokenizer {
|
||||
static final alphaNum =
|
||||
'@abcdefghijklmnopqrstuvwxyz-_0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
static final whitespace = ' \n\t\r';
|
||||
|
||||
String text;
|
||||
Token _head;
|
||||
Token _last;
|
||||
|
||||
Tokenizer(this.text);
|
||||
|
||||
Token tokenize() {
|
||||
_emit(null);
|
||||
|
||||
for (int i = 0; i < text.length; i++) {
|
||||
String c = text[i];
|
||||
|
||||
if (whitespace.contains(c)) {
|
||||
// skip
|
||||
} else if (c == '/' && _peek(i) == '/') {
|
||||
int index = text.indexOf('\n', i);
|
||||
if (index == -1) index = text.length;
|
||||
_emit(text.substring(i, index));
|
||||
i = index;
|
||||
} else if (alphaNum.contains(c)) {
|
||||
int start = i;
|
||||
|
||||
while (alphaNum.contains(_peek(i))) {
|
||||
i++;
|
||||
}
|
||||
|
||||
_emit(text.substring(start, i + 1));
|
||||
} else {
|
||||
_emit(c);
|
||||
}
|
||||
}
|
||||
|
||||
_emit(null);
|
||||
|
||||
_head = _head.next;
|
||||
|
||||
return _head;
|
||||
}
|
||||
|
||||
void _emit(String value) {
|
||||
Token token = new Token(value);
|
||||
if (_head == null) _head = token;
|
||||
if (_last != null) _last.next = token;
|
||||
_last = token;
|
||||
}
|
||||
|
||||
String _peek(int i) {
|
||||
i += 1;
|
||||
return i < text.length ? text[i] : new String.fromCharCodes([0]);
|
||||
}
|
||||
|
||||
String toString() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
|
||||
Token t = _head;
|
||||
|
||||
buf.write('[${t}]\n');
|
||||
|
||||
while (!t.eof) {
|
||||
t = t.next;
|
||||
buf.write('[${t}]\n');
|
||||
}
|
||||
|
||||
return buf.toString().trim();
|
||||
}
|
||||
}
|
||||
|
||||
abstract class Parser {
|
||||
final Token startToken;
|
||||
|
||||
Token current;
|
||||
|
||||
Parser(this.startToken);
|
||||
|
||||
Token expect(String text) {
|
||||
Token t = advance();
|
||||
if (text != t.text) fail('expected ${text}, got ${t}');
|
||||
return t;
|
||||
}
|
||||
|
||||
bool consume(String text) {
|
||||
if (peek().text == text) {
|
||||
advance();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Token peek() =>
|
||||
current == null ? startToken : current.eof ? current : current.next;
|
||||
|
||||
Token expectName() {
|
||||
Token t = advance();
|
||||
if (!t.isName) fail('expected name token, got ${t}');
|
||||
return t;
|
||||
}
|
||||
|
||||
Token advance() {
|
||||
if (current == null) {
|
||||
current = startToken;
|
||||
} else if (!current.eof) {
|
||||
current = current.next;
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
String collectComments() {
|
||||
StringBuffer buf = new StringBuffer();
|
||||
|
||||
while (peek().isComment) {
|
||||
Token t = advance();
|
||||
String str = t.text.substring(2);
|
||||
|
||||
if (str.startsWith(' ')) str = str.substring(1);
|
||||
|
||||
if (str.startsWith(' ')) {
|
||||
buf.write('\n - ${str.substring(2)}');
|
||||
} else if (str.isEmpty) {
|
||||
buf.write('\n\n');
|
||||
} else {
|
||||
buf.write('${str} ');
|
||||
}
|
||||
}
|
||||
|
||||
if (buf.isEmpty) return null;
|
||||
return buf
|
||||
.toString()
|
||||
.split('\n')
|
||||
.map((line) => line.trimRight())
|
||||
.join('\n')
|
||||
.trim();
|
||||
}
|
||||
|
||||
void validate(bool result, String message) {
|
||||
if (!result) throw 'expected ${message}';
|
||||
}
|
||||
|
||||
void fail(String message) => throw message;
|
||||
}
|
93
pkg/vm_service/tool/common/src_gen_common.dart
Normal file
93
pkg/vm_service/tool/common/src_gen_common.dart
Normal file
|
@ -0,0 +1,93 @@
|
|||
// Copyright (c) 2015, 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.
|
||||
|
||||
library src_gen_common;
|
||||
|
||||
import 'package:markdown/markdown.dart';
|
||||
|
||||
const int RUNE_SPACE = 32;
|
||||
const int RUNE_EOL = 10;
|
||||
const int RUNE_LEFT_CURLY = 123;
|
||||
const int RUNE_RIGHT_CURLY = 125;
|
||||
|
||||
final RegExp _wsRegexp = new RegExp(r'\s+');
|
||||
|
||||
String collapseWhitespace(String str) => str.replaceAll(_wsRegexp, ' ');
|
||||
|
||||
bool isEmphasis(Node node) => node is Element && node.tag == 'em';
|
||||
bool isPara(Node node) => node is Element && node.tag == 'p';
|
||||
bool isBlockquote(Node node) => node is Element && node.tag == 'blockquote';
|
||||
bool isPre(Node node) => node is Element && node.tag == 'pre';
|
||||
bool isH1(Node node) => node is Element && node.tag == 'h1';
|
||||
bool isH3(Node node) => node is Element && node.tag == 'h3';
|
||||
bool isHeader(Node node) => node is Element && node.tag.startsWith('h');
|
||||
String textForElement(Node node) =>
|
||||
(((node as Element).children.first) as Text).text;
|
||||
String textForCode(Node node) =>
|
||||
textForElement((node as Element).children.first);
|
||||
|
||||
/// foo ==> Foo
|
||||
String titleCase(String str) =>
|
||||
str.substring(0, 1).toUpperCase() + str.substring(1);
|
||||
|
||||
/// FooBar ==> fooBar
|
||||
String lowerTitleCase(String str) =>
|
||||
str.substring(0, 1).toLowerCase() + str.substring(1);
|
||||
|
||||
String joinLast(Iterable<String> strs, String join, [String last]) {
|
||||
if (strs.isEmpty) return '';
|
||||
List list = strs.toList();
|
||||
if (list.length == 1) return list.first;
|
||||
StringBuffer buf = new StringBuffer();
|
||||
for (int i = 0; i < list.length; i++) {
|
||||
if (i > 0) {
|
||||
if (i + 1 == list.length && last != null) {
|
||||
buf.write(last);
|
||||
} else {
|
||||
buf.write(join);
|
||||
}
|
||||
}
|
||||
buf.write(list[i]);
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
/// Wrap a string on column boundaries.
|
||||
String wrap(String str, [int col = 80]) {
|
||||
// The given string could contain newlines.
|
||||
List<String> lines = str.split('\n');
|
||||
return lines.map((l) => _simpleWrap(l, col)).join('\n');
|
||||
}
|
||||
|
||||
/// Wrap a string ignoring newlines.
|
||||
String _simpleWrap(String str, [int col = 80]) {
|
||||
List<String> lines = [];
|
||||
|
||||
while (str.length > col) {
|
||||
int index = col;
|
||||
|
||||
while (index > 0 && str.codeUnitAt(index) != RUNE_SPACE) {
|
||||
index--;
|
||||
}
|
||||
|
||||
if (index == 0) {
|
||||
index = str.indexOf(' ');
|
||||
|
||||
if (index == -1) {
|
||||
lines.add(str);
|
||||
str = '';
|
||||
} else {
|
||||
lines.add(str.substring(0, index).trim());
|
||||
str = str.substring(index).trim();
|
||||
}
|
||||
} else {
|
||||
lines.add(str.substring(0, index).trim());
|
||||
str = str.substring(index).trim();
|
||||
}
|
||||
}
|
||||
|
||||
if (str.isNotEmpty) lines.add(str);
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
1903
pkg/vm_service/tool/dart/generate_dart.dart
Normal file
1903
pkg/vm_service/tool/dart/generate_dart.dart
Normal file
File diff suppressed because it is too large
Load diff
96
pkg/vm_service/tool/dart/src_gen_dart.dart
Normal file
96
pkg/vm_service/tool/dart/src_gen_dart.dart
Normal file
|
@ -0,0 +1,96 @@
|
|||
// Copyright (c) 2015, 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.
|
||||
|
||||
/// A library to generate Dart source code.
|
||||
library src_gen_dart;
|
||||
|
||||
import '../common/src_gen_common.dart';
|
||||
|
||||
/// A class used to generate Dart source code. This class facilitates writing out
|
||||
/// dartdoc comments, automatically manages indent by counting curly braces, and
|
||||
/// automatically wraps doc comments on 80 char column boundaries.
|
||||
class DartGenerator {
|
||||
static const DEFAULT_COLUMN_BOUNDARY = 80;
|
||||
|
||||
final int colBoundary;
|
||||
|
||||
String _indent = "";
|
||||
final StringBuffer _buf = new StringBuffer();
|
||||
|
||||
bool _previousWasEol = false;
|
||||
|
||||
DartGenerator({this.colBoundary = DEFAULT_COLUMN_BOUNDARY});
|
||||
|
||||
/// Write out the given dartdoc text, wrapping lines as necessary to flow
|
||||
/// along the column boundary. If [preferSingle] is true, and the docs would
|
||||
/// fit on a single line, use `///` dartdoc style.
|
||||
void writeDocs(String docs) {
|
||||
if (docs == null) return;
|
||||
|
||||
docs = wrap(docs.trim(), colBoundary - _indent.length - 4);
|
||||
// docs = docs.replaceAll('*/', '/');
|
||||
// docs = docs.replaceAll('/*', r'/\*');
|
||||
|
||||
docs.split('\n').forEach((line) => _writeln('/// ${line}'.trimRight()));
|
||||
|
||||
// if (!docs.contains('\n') && preferSingle) {
|
||||
// _writeln("/// ${docs}", true);
|
||||
// } else {
|
||||
// _writeln("/**", true);
|
||||
// _writeln(" * ${docs.replaceAll("\n", "\n * ")}", true);
|
||||
// _writeln(" */", true);
|
||||
// }
|
||||
}
|
||||
|
||||
/// Write out the given Dart statement and terminate it with an eol. If the
|
||||
/// statement will overflow the column boundary, attempt to wrap it at
|
||||
/// reasonable places.
|
||||
void writeStatement(String str) {
|
||||
if (_indent.length + str.length > colBoundary) {
|
||||
// Split the line on the first '('. Currently, we don't do anything
|
||||
// fancier then that. This takes the edge off the long lines.
|
||||
int index = str.indexOf('(');
|
||||
|
||||
if (index == -1) {
|
||||
writeln(str);
|
||||
} else {
|
||||
writeln(str.substring(0, index + 1));
|
||||
writeln(" ${str.substring(index + 1)}");
|
||||
}
|
||||
} else {
|
||||
writeln(str);
|
||||
}
|
||||
}
|
||||
|
||||
void writeln([String str = ""]) => _write("${str}\n");
|
||||
|
||||
void write(String str) => _write(str);
|
||||
|
||||
void out(String str) => _buf.write(str);
|
||||
|
||||
void _writeln([String str = "", bool ignoreCurlies = false]) =>
|
||||
_write("${str}\n", ignoreCurlies);
|
||||
|
||||
void _write(String str, [bool ignoreCurlies = false]) {
|
||||
for (final int rune in str.runes) {
|
||||
if (!ignoreCurlies) {
|
||||
if (rune == RUNE_LEFT_CURLY) {
|
||||
_indent = "${_indent} ";
|
||||
} else if (rune == RUNE_RIGHT_CURLY && _indent.length >= 2) {
|
||||
_indent = _indent.substring(2);
|
||||
}
|
||||
}
|
||||
|
||||
if (_previousWasEol && rune != RUNE_EOL) {
|
||||
_buf.write(_indent);
|
||||
}
|
||||
|
||||
_buf.write(new String.fromCharCode(rune));
|
||||
|
||||
_previousWasEol = rune == RUNE_EOL;
|
||||
}
|
||||
}
|
||||
|
||||
String toString() => _buf.toString();
|
||||
}
|
152
pkg/vm_service/tool/generate.dart
Normal file
152
pkg/vm_service/tool/generate.dart
Normal file
|
@ -0,0 +1,152 @@
|
|||
// Copyright (c) 2015, 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 'dart:io';
|
||||
|
||||
import 'package:markdown/markdown.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:pub_semver/pub_semver.dart';
|
||||
|
||||
import 'common/generate_common.dart';
|
||||
import 'dart/generate_dart.dart' as dart show Api, api, DartGenerator;
|
||||
import 'java/generate_java.dart' as java show Api, api, JavaGenerator;
|
||||
|
||||
final bool _stampPubspecVersion = false;
|
||||
|
||||
/// Parse the 'service.md' into a model and generate both Dart and Java
|
||||
/// libraries.
|
||||
main(List<String> args) async {
|
||||
bool generateJava = false;
|
||||
if (args.length != 0) {
|
||||
if ((args.length == 1) && (args.first == '--generate-java')) {
|
||||
generateJava = true;
|
||||
} else {
|
||||
print('Invalid options: $args. Usage: dart generate [--generate-java].');
|
||||
return;
|
||||
}
|
||||
}
|
||||
String appDirPath = dirname(Platform.script.toFilePath());
|
||||
|
||||
// Parse service.md into a model.
|
||||
var file =
|
||||
new File(join(appDirPath, '../../../runtime/vm/service/service.md'));
|
||||
var document = new Document();
|
||||
StringBuffer buf = new StringBuffer(file.readAsStringSync());
|
||||
var nodes = document.parseLines(buf.toString().split('\n'));
|
||||
print('Parsed ${file.path}.');
|
||||
print('Service protocol version ${ApiParseUtil.parseVersionString(nodes)}.');
|
||||
|
||||
// Generate code from the model.
|
||||
await _generateDart(appDirPath, nodes);
|
||||
if (generateJava) {
|
||||
await _generateJava(appDirPath, nodes);
|
||||
}
|
||||
await _generateAsserts(appDirPath, nodes);
|
||||
}
|
||||
|
||||
_generateDart(String appDirPath, List<Node> nodes) async {
|
||||
print('');
|
||||
var outDirPath = normalize(join(appDirPath, '..', 'lib'));
|
||||
var outDir = new Directory(outDirPath);
|
||||
if (!outDir.existsSync()) outDir.createSync(recursive: true);
|
||||
var outputFile = new File(join(outDirPath, 'vm_service.dart'));
|
||||
var generator = new dart.DartGenerator();
|
||||
dart.api = new dart.Api();
|
||||
dart.api.parse(nodes);
|
||||
dart.api.generate(generator);
|
||||
outputFile.writeAsStringSync(generator.toString());
|
||||
Process.runSync('dartfmt', ['-w', outDirPath]);
|
||||
|
||||
if (_stampPubspecVersion) {
|
||||
// Update the pubspec file.
|
||||
Version version = ApiParseUtil.parseVersionSemVer(nodes);
|
||||
_stampPubspec(version);
|
||||
|
||||
// Validate that the changelog contains an entry for the current version.
|
||||
_checkUpdateChangelog(version);
|
||||
}
|
||||
|
||||
print('Wrote Dart to ${outputFile.path}.');
|
||||
}
|
||||
|
||||
_generateJava(String appDirPath, List<Node> nodes) async {
|
||||
print('');
|
||||
var srcDirPath = normalize(join(appDirPath, '..', 'java', 'src', 'gen'));
|
||||
assert(new Directory(srcDirPath).existsSync());
|
||||
var generator = new java.JavaGenerator(srcDirPath);
|
||||
java.api = new java.Api();
|
||||
java.api.parse(nodes);
|
||||
java.api.generate(generator);
|
||||
|
||||
// Generate a version file.
|
||||
Version version = ApiParseUtil.parseVersionSemVer(nodes);
|
||||
File file = new File(join('java', 'version.properties'));
|
||||
file.writeAsStringSync('version=${version.major}.${version.minor}\n');
|
||||
|
||||
print('Wrote Java to $srcDirPath.');
|
||||
}
|
||||
|
||||
_generateAsserts(String appDirPath, List<Node> nodes) async {
|
||||
print('');
|
||||
var outDirPath = normalize(join(appDirPath, '..', 'example'));
|
||||
var outDir = new Directory(outDirPath);
|
||||
if (!outDir.existsSync()) outDir.createSync(recursive: true);
|
||||
var outputFile = new File(join(outDirPath, 'vm_service_assert.dart'));
|
||||
var generator = new dart.DartGenerator();
|
||||
dart.api = new dart.Api();
|
||||
dart.api.parse(nodes);
|
||||
dart.api.generateAsserts(generator);
|
||||
outputFile.writeAsStringSync(generator.toString());
|
||||
Process.runSync('dartfmt', ['-w', outDirPath]);
|
||||
|
||||
if (_stampPubspecVersion) {
|
||||
// Update the pubspec file.
|
||||
Version version = ApiParseUtil.parseVersionSemVer(nodes);
|
||||
_stampPubspec(version);
|
||||
|
||||
// Validate that the changelog contains an entry for the current version.
|
||||
_checkUpdateChangelog(version);
|
||||
}
|
||||
|
||||
print('Wrote Dart to ${outputFile.path}.');
|
||||
}
|
||||
|
||||
// Push the major and minor versions into the pubspec.
|
||||
void _stampPubspec(Version version) {
|
||||
final String pattern = 'version: ';
|
||||
File file = new File('pubspec.yaml');
|
||||
String text = file.readAsStringSync();
|
||||
bool found = false;
|
||||
|
||||
text = text.split('\n').map((line) {
|
||||
if (line.startsWith(pattern)) {
|
||||
found = true;
|
||||
Version v = new Version.parse(line.substring(pattern.length));
|
||||
String pre = v.preRelease.isEmpty ? null : v.preRelease.join('-');
|
||||
String build = v.build.isEmpty ? null : v.build.join('+');
|
||||
v = new Version(version.major, version.minor, v.patch,
|
||||
pre: pre, build: build);
|
||||
return '${pattern}${v.toString()}';
|
||||
} else {
|
||||
return line;
|
||||
}
|
||||
}).join('\n');
|
||||
|
||||
if (!found) throw '`${pattern}` not found';
|
||||
|
||||
file.writeAsStringSync(text);
|
||||
}
|
||||
|
||||
void _checkUpdateChangelog(Version version) {
|
||||
// Look for `## major.minor`.
|
||||
String check = '## ${version.major}.${version.minor}';
|
||||
|
||||
File file = new File('CHANGELOG.md');
|
||||
String text = file.readAsStringSync();
|
||||
bool containsReleaseNotes =
|
||||
text.split('\n').any((line) => line.startsWith(check));
|
||||
if (!containsReleaseNotes) {
|
||||
throw '`${check}` not found in the CHANGELOG.md file';
|
||||
}
|
||||
}
|
1160
pkg/vm_service/tool/java/generate_java.dart
Normal file
1160
pkg/vm_service/tool/java/generate_java.dart
Normal file
File diff suppressed because it is too large
Load diff
307
pkg/vm_service/tool/java/src_gen_java.dart
Normal file
307
pkg/vm_service/tool/java/src_gen_java.dart
Normal file
|
@ -0,0 +1,307 @@
|
|||
// Copyright (c) 2015, 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.
|
||||
|
||||
/// A library to generate Java source code. See [JavaGenerator].
|
||||
library src_gen_java;
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:path/path.dart';
|
||||
|
||||
import '../common/src_gen_common.dart';
|
||||
|
||||
/// The maximum length for javadoc comments.
|
||||
int colBoundary = 100;
|
||||
|
||||
/// The header for every generated file.
|
||||
String fileHeader;
|
||||
|
||||
String classNameFor(String typeName) {
|
||||
// Convert ElementList<Foo> param declarations to List<Foo> declarations.
|
||||
if (typeName.startsWith('ElementList<')) {
|
||||
return typeName.substring('Element'.length);
|
||||
}
|
||||
|
||||
var index = typeName.lastIndexOf('.');
|
||||
typeName = index > 0 ? typeName.substring(index + 1) : typeName;
|
||||
if (typeName.startsWith('_')) typeName = typeName.substring(1);
|
||||
return typeName;
|
||||
}
|
||||
|
||||
String pkgNameFor(String typeName) {
|
||||
var index = typeName.lastIndexOf('.');
|
||||
return index > 0 ? typeName.substring(0, index) : '';
|
||||
}
|
||||
|
||||
typedef WriteStatements = void Function(StatementWriter writer);
|
||||
typedef WriteType = void Function(TypeWriter writer);
|
||||
|
||||
/// [JavaGenerator] generates java source files, one per Java type.
|
||||
/// Typical usage:
|
||||
///
|
||||
/// var generator = new JavaGenerator('/path/to/java/src');
|
||||
/// generator.writeType('some.package.Foo', (TypeWriter) writer) {
|
||||
/// ...
|
||||
/// });
|
||||
/// ...
|
||||
///
|
||||
class JavaGenerator {
|
||||
/// The java source directory into which files are generated.
|
||||
final String srcDirPath;
|
||||
|
||||
JavaGenerator(this.srcDirPath);
|
||||
|
||||
/// Generate a Java class/interface in the given package
|
||||
void writeType(String typeName, WriteType write) {
|
||||
var classWriter = new TypeWriter(typeName);
|
||||
write(classWriter);
|
||||
var pkgDirPath = join(srcDirPath, joinAll(pkgNameFor(typeName).split('.')));
|
||||
var pkgDir = new Directory(pkgDirPath);
|
||||
if (!pkgDir.existsSync()) pkgDir.createSync(recursive: true);
|
||||
var classFilePath = join(pkgDirPath, '${classNameFor(typeName)}.java');
|
||||
var classFile = new File(classFilePath);
|
||||
classFile.writeAsStringSync(classWriter.toSource());
|
||||
}
|
||||
}
|
||||
|
||||
class JavaMethodArg {
|
||||
final String name;
|
||||
final String typeName;
|
||||
|
||||
JavaMethodArg(this.name, this.typeName);
|
||||
}
|
||||
|
||||
class StatementWriter {
|
||||
final TypeWriter typeWriter;
|
||||
final StringBuffer _content = new StringBuffer();
|
||||
|
||||
StatementWriter(this.typeWriter);
|
||||
|
||||
void addImport(String typeName) {
|
||||
typeWriter.addImport(typeName);
|
||||
}
|
||||
|
||||
void addLine(String line) {
|
||||
_content.writeln(' $line');
|
||||
}
|
||||
|
||||
String toSource() => _content.toString();
|
||||
}
|
||||
|
||||
/// [TypeWriter] describes a Java type to be generated.
|
||||
/// Typical usage:
|
||||
///
|
||||
/// writer.addImport('package.one.Bar');
|
||||
/// writer.addImport('package.two.*');
|
||||
/// writer.superclassName = 'package.three.Blat';
|
||||
/// writer.addMethod('foo', [
|
||||
/// new JavaMethodArg('arg1', 'LocalType'),
|
||||
/// new JavaMethodArg('arg2', 'java.util.List'),
|
||||
/// ], (StatementWriter writer) {
|
||||
/// ...
|
||||
/// });
|
||||
///
|
||||
/// The [toSource()] method generates the source,
|
||||
/// but need not be called if used in conjunction with
|
||||
/// [JavaGenerator].
|
||||
class TypeWriter {
|
||||
final String pkgName;
|
||||
final String className;
|
||||
bool isInterface = false;
|
||||
bool isEnum = false;
|
||||
String javadoc;
|
||||
String modifiers = 'public';
|
||||
final Set<String> _imports = new Set<String>();
|
||||
String superclassName;
|
||||
List<String> interfaceNames = <String>[];
|
||||
final StringBuffer _content = new StringBuffer();
|
||||
final List<String> _fields = <String>[];
|
||||
final Map<String, String> _methods = new Map<String, String>();
|
||||
|
||||
TypeWriter(String typeName)
|
||||
: this.pkgName = pkgNameFor(typeName),
|
||||
this.className = classNameFor(typeName);
|
||||
|
||||
String get kind {
|
||||
if (isInterface) return 'interface';
|
||||
if (isEnum) return 'enum';
|
||||
return 'class';
|
||||
}
|
||||
|
||||
void addConstructor(Iterable<JavaMethodArg> args, WriteStatements write,
|
||||
{String javadoc, String modifiers = 'public'}) {
|
||||
_content.writeln();
|
||||
if (javadoc != null && javadoc.isNotEmpty) {
|
||||
_content.writeln(' /**');
|
||||
wrap(javadoc.trim(), colBoundary - 6)
|
||||
.split('\n')
|
||||
.forEach((line) => _content.writeln(' * $line'));
|
||||
_content.writeln(' */');
|
||||
}
|
||||
_content.write(' $modifiers $className(');
|
||||
_content.write(
|
||||
args.map((a) => '${classNameFor(a.typeName)} ${a.name}').join(', '));
|
||||
_content.write(')');
|
||||
if (write != null) {
|
||||
_content.writeln(' {');
|
||||
StatementWriter writer = new StatementWriter(this);
|
||||
write(writer);
|
||||
_content.write(writer.toSource());
|
||||
_content.writeln(' }');
|
||||
} else {
|
||||
_content.writeln(';');
|
||||
}
|
||||
}
|
||||
|
||||
void addEnumValue(
|
||||
String name, {
|
||||
String javadoc,
|
||||
bool isLast = false,
|
||||
}) {
|
||||
_content.writeln();
|
||||
if (javadoc != null && javadoc.isNotEmpty) {
|
||||
_content.writeln(' /**');
|
||||
wrap(javadoc.trim(), colBoundary - 6)
|
||||
.split('\n')
|
||||
.forEach((line) => _content.writeln(' * $line'));
|
||||
_content.writeln(' */');
|
||||
}
|
||||
_content.write(' $name');
|
||||
if (!isLast) {
|
||||
_content.writeln(',');
|
||||
} else {
|
||||
_content.writeln();
|
||||
}
|
||||
}
|
||||
|
||||
void addField(String name, String typeName,
|
||||
{String modifiers = 'public', String value, String javadoc}) {
|
||||
var fieldDecl = new StringBuffer();
|
||||
if (javadoc != null && javadoc.isNotEmpty) {
|
||||
fieldDecl.writeln(' /**');
|
||||
wrap(javadoc.trim(), colBoundary - 6)
|
||||
.split('\n')
|
||||
.forEach((line) => fieldDecl.writeln(' * $line'));
|
||||
fieldDecl.writeln(' */');
|
||||
}
|
||||
fieldDecl.write(' ');
|
||||
if (modifiers != null && modifiers.isNotEmpty) {
|
||||
fieldDecl.write('$modifiers ');
|
||||
}
|
||||
fieldDecl.write('$typeName $name');
|
||||
if (value != null && value.isNotEmpty) {
|
||||
fieldDecl.write(' = $value');
|
||||
}
|
||||
fieldDecl.writeln(';');
|
||||
_fields.add(fieldDecl.toString());
|
||||
}
|
||||
|
||||
void addImport(String typeName) {
|
||||
if (typeName == null || typeName.isEmpty) return;
|
||||
var pkgName = pkgNameFor(typeName);
|
||||
if (pkgName.isNotEmpty && pkgName != this.pkgName) {
|
||||
_imports.add(typeName);
|
||||
}
|
||||
}
|
||||
|
||||
void addMethod(
|
||||
String name,
|
||||
Iterable<JavaMethodArg> args,
|
||||
WriteStatements write, {
|
||||
String javadoc,
|
||||
String modifiers = 'public',
|
||||
String returnType = 'void',
|
||||
bool isOverride = false,
|
||||
}) {
|
||||
var methodDecl = new StringBuffer();
|
||||
if (javadoc != null && javadoc.isNotEmpty) {
|
||||
methodDecl.writeln(' /**');
|
||||
wrap(javadoc.trim(), colBoundary - 6)
|
||||
.split('\n')
|
||||
.forEach((line) => methodDecl.writeln(' * $line'.trimRight()));
|
||||
methodDecl.writeln(' */');
|
||||
}
|
||||
if (isOverride) {
|
||||
methodDecl.writeln(' @Override');
|
||||
}
|
||||
methodDecl.write(' ');
|
||||
if (modifiers != null && modifiers.isNotEmpty) {
|
||||
if (!isInterface || modifiers != 'public') {
|
||||
methodDecl.write('$modifiers ');
|
||||
}
|
||||
}
|
||||
methodDecl.write('$returnType $name(');
|
||||
methodDecl.write(args
|
||||
.map((JavaMethodArg arg) => '${classNameFor(arg.typeName)} ${arg.name}')
|
||||
.join(', '));
|
||||
methodDecl.write(')');
|
||||
if (write != null) {
|
||||
methodDecl.writeln(' {');
|
||||
StatementWriter writer = new StatementWriter(this);
|
||||
write(writer);
|
||||
methodDecl.write(writer.toSource());
|
||||
methodDecl.writeln(' }');
|
||||
} else {
|
||||
methodDecl.writeln(';');
|
||||
}
|
||||
String key = (modifiers != null && modifiers.contains('public'))
|
||||
? '1 $name('
|
||||
: '2 $name(';
|
||||
key = args.fold(key, (String k, JavaMethodArg a) => '$k${a.typeName},');
|
||||
_methods[key] = methodDecl.toString();
|
||||
}
|
||||
|
||||
String toSource() {
|
||||
var buffer = new StringBuffer();
|
||||
if (fileHeader != null) buffer.write(fileHeader);
|
||||
if (pkgName != null) {
|
||||
buffer.writeln('package $pkgName;');
|
||||
buffer.writeln();
|
||||
}
|
||||
buffer.writeln('// This is a generated file.');
|
||||
buffer.writeln();
|
||||
addImport(superclassName);
|
||||
interfaceNames.forEach((t) => addImport(t));
|
||||
if (_imports.isNotEmpty) {
|
||||
var sorted = _imports.toList()..sort();
|
||||
for (String typeName in sorted) {
|
||||
buffer.writeln('import $typeName;');
|
||||
}
|
||||
buffer.writeln();
|
||||
}
|
||||
if (javadoc != null && javadoc.isNotEmpty) {
|
||||
buffer.writeln('/**');
|
||||
wrap(javadoc.trim(), colBoundary - 4)
|
||||
.split('\n')
|
||||
.forEach((line) => buffer.writeln(' * $line'));
|
||||
buffer.writeln(' */');
|
||||
}
|
||||
|
||||
buffer.writeln('@SuppressWarnings({"WeakerAccess", "unused"})');
|
||||
|
||||
buffer.write('$modifiers $kind $className');
|
||||
if (superclassName != null) {
|
||||
buffer.write(' extends ${classNameFor(superclassName)}');
|
||||
}
|
||||
if (interfaceNames.isNotEmpty) {
|
||||
var classNames = interfaceNames.map((t) => classNameFor(t));
|
||||
buffer.write(
|
||||
' ${isInterface ? 'extends' : 'implements'} ${classNames.join(', ')}');
|
||||
}
|
||||
buffer.writeln(' {');
|
||||
buffer.write(_content.toString());
|
||||
_fields.forEach((f) {
|
||||
buffer.writeln();
|
||||
buffer.write(f);
|
||||
});
|
||||
_methods.keys.toList()
|
||||
..sort()
|
||||
..forEach((String methodName) {
|
||||
buffer.writeln();
|
||||
buffer.write(_methods[methodName]);
|
||||
});
|
||||
buffer.writeln('}');
|
||||
return buffer.toString();
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue