blob: 36711169f6249ff45867f771199db528ff6c7cd2 [file] [log] [blame]
// Copyright (c) 2014, 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.
part of service;
/// A [ServiceObject] is an object known to the VM service and is tied
/// to an owning [Isolate].
abstract class ServiceObject extends Observable {
Isolate _isolate;
/// Owning isolate.
@reflectable Isolate get isolate => _isolate;
/// Owning vm.
@reflectable VM get vm => _isolate.vm;
/// The complete service url of this object.
@reflectable String get link => isolate.relativeLink(_id);
/// The complete service url of this object with a '#/' prefix.
@reflectable String get hashLink => isolate.relativeHashLink(_id);
set hashLink(var o) { /* silence polymer */ }
String _id;
/// The id of this object.
@reflectable String get id => _id;
String _serviceType;
/// The service type of this object.
@reflectable String get serviceType => _serviceType;
bool _ref;
@observable String name;
@observable String vmName;
ServiceObject(this._isolate, this._id, this._serviceType) {
_ref = isRefType(_serviceType);
_serviceType = stripRef(_serviceType);
_created();
}
ServiceObject.fromMap(this._isolate, ObservableMap m) {
assert(isServiceMap(m));
_id = m['id'];
_ref = isRefType(m['type']);
_serviceType = stripRef(m['type']);
_created();
update(m);
}
/// If [this] was created from a reference, load the full object
/// from the service by calling [reload]. Else, return [this].
Future<ServiceObject> load() {
if (!_ref) {
// Not a reference.
return new Future.value(this);
}
// Call refresh which will fill in the entire object.
return reload();
}
/// Reload [this]. Returns a future which completes to [this] or
/// a [ServiceError].
Future<ServiceObject> reload() {
assert(isolate != null);
if (id == '') {
// Errors don't have ids.
assert(serviceType == 'Error');
return new Future.value(this);
}
return isolate.vm.getAsMap(link).then(update);
}
/// Update [this] using [m] as a source. [m] can be a reference.
ServiceObject update(ObservableMap m) {
// Assert that m is a service map.
assert(ServiceObject.isServiceMap(m));
if ((m['type'] == 'Error') && (_serviceType != 'Error')) {
// Got an unexpected error. Don't update the object.
return _upgradeToServiceObject(vm, isolate, m);
}
// TODO(johnmccutchan): Should we allow for a ServiceObject's id
// or type to change?
_id = m['id'];
_serviceType = stripRef(m['type']);
_update(m);
return this;
}
// update internal state from [map]. [map] can be a reference.
void _update(ObservableMap map);
/// Returns true if [this] has only been partially initialized via
/// a reference. See [load].
bool isRef() => _ref;
void _created() {
var refNotice = _ref ? ' Created from reference.' : '';
Logger.root.info('Created ServiceObject for \'${_id}\' with type '
'\'${_serviceType}\'.' + refNotice);
}
/// Returns true if [map] is a service map. i.e. it has the following keys:
/// 'id' and a 'type'.
static bool isServiceMap(ObservableMap m) {
return (m != null) && (m['id'] != null) && (m['type'] != null);
}
/// Returns true if [type] is a reference type. i.e. it begins with an
/// '@' character.
static bool isRefType(String type) {
return type.startsWith('@');
}
/// Returns the unreffed version of [type].
static String stripRef(String type) {
if (!isRefType(type)) {
return type;
}
// Strip off the '@' character.
return type.substring(1);
}
}
/// Recursively upgrades all [ServiceObject]s inside [collection] which must
/// be an [ObservableMap] or an [ObservableList]. Upgraded elements will be
/// associated with [vm] and [isolate].
void upgradeCollection(collection, VM vm, Isolate isolate) {
if (collection is ObservableMap) {
_upgradeObservableMap(collection, vm, isolate);
} else if (collection is ObservableList) {
_upgradeObservableList(collection, vm, isolate);
}
}
void _upgradeObservableMap(ObservableMap map, VM vm, Isolate isolate) {
map.forEach((k, v) {
if ((v is ObservableMap) && ServiceObject.isServiceMap(v)) {
map[k] = v = _upgradeToServiceObject(vm, isolate, v);
} else if (v is ObservableList) {
_upgradeObservableList(v, vm, isolate);
} else if (v is ObservableMap) {
_upgradeObservableMap(v, vm, isolate);
}
});
}
void _upgradeObservableList(ObservableList list, VM vm, Isolate isolate) {
for (var i = 0; i < list.length; i++) {
var v = list[i];
if ((v is ObservableMap) && ServiceObject.isServiceMap(v)) {
list[i] = _upgradeToServiceObject(vm, isolate, v);
} else if (v is ObservableList) {
_upgradeObservableList(v, vm, isolate);
} else if (v is ObservableMap) {
_upgradeObservableMap(v, vm, isolate);
}
}
}
/// Upgrades response ([m]) from [vm] and [isolate] to a [ServiceObject].
/// This acts like a factory which consumes an ObservableMap and returns
/// a fully upgraded ServiceObject.
ServiceObject _upgradeToServiceObject(VM vm, Isolate isolate, ObservableMap m) {
if (!ServiceObject.isServiceMap(m)) {
Logger.root.severe("Malformed service object: $m");
}
assert(ServiceObject.isServiceMap(m));
var type = ServiceObject.stripRef(m['type']);
switch (type) {
case 'Error':
return new ServiceError.fromMap(isolate, m);
case 'IsolateList':
vm.isolates.update(m);
return vm.isolates;
case 'Script':
return isolate.scripts.putIfAbsent(m);
case 'Code':
return isolate.codes.putIfAbsent(m);
case 'Isolate':
return vm.isolates.getIsolateFromMap(m);
case 'Class':
return isolate.classes.putIfAbsent(m);
case 'Function':
return isolate.functions.putIfAbsent(m);
}
return new ServiceMap.fromMap(isolate, m);
}