Index: sdk/lib/_internal/lib/convert_patch.dart |
diff --git a/sdk/lib/_internal/lib/convert_patch.dart b/sdk/lib/_internal/lib/convert_patch.dart |
index 3fe44df3310836447752685827915eda098f7aae..923ef2cec1e57d7356da75bc14a22bb532af1885 100644 |
--- a/sdk/lib/_internal/lib/convert_patch.dart |
+++ b/sdk/lib/_internal/lib/convert_patch.dart |
@@ -7,6 +7,8 @@ |
import 'dart:_js_helper' show patch; |
import 'dart:_foreign_helper' show JS; |
import 'dart:_interceptors' show JSExtendableArray; |
+import 'dart:_internal' show MappedIterable; |
+import 'dart:collection' show Maps, LinkedHashMap; |
/** |
* Parses [json] and builds the corresponding parsed JSON value. |
@@ -37,7 +39,11 @@ _parseJson(String source, reviver(key, value)) { |
throw new FormatException(JS('String', 'String(#)', e)); |
} |
- return _convertJsonToDart(parsed, reviver); |
+ if (reviver == null) { |
+ return _convertJsonToDartLazy(parsed); |
+ } else { |
+ return _convertJsonToDart(parsed, reviver); |
+ } |
} |
/** |
@@ -46,7 +52,6 @@ _parseJson(String source, reviver(key, value)) { |
* in-place. |
*/ |
_convertJsonToDart(json, reviver(key, value)) { |
- |
var revive = reviver == null ? (key, value) => value : reviver; |
walk(e) { |
@@ -59,7 +64,8 @@ _convertJsonToDart(json, reviver(key, value)) { |
// TODO(sra): Replace this test with cheaper '#.constructor === Array' when |
// bug 621 below is fixed. |
if (JS('bool', 'Object.getPrototypeOf(#) === Array.prototype', e)) { |
- var list = JS('JSExtendableArray', '#', e); // Teach compiler the type is known. |
+ // Teach compiler the type is known by passing it through a JS-expression. |
+ var list = JS('JSExtendableArray', '#', e); |
// In-place update of the elements since JS Array is a Dart List. |
for (int i = 0; i < list.length; i++) { |
// Use JS indexing to avoid range checks. We know this is the only |
@@ -95,6 +101,232 @@ _convertJsonToDart(json, reviver(key, value)) { |
return revive(null, walk(json)); |
} |
+_convertJsonToDartLazy(object) { |
+ // JavaScript null and undefined are represented as null. |
+ if (object == null) return null; |
+ |
+ // JavaScript string, number, bool already has the correct representation. |
+ if (JS('bool', 'typeof # != "object"', object)) { |
+ return object; |
+ } |
+ |
+ // This test is needed to avoid identifing '{"__proto__":[]}' as an array. |
+ // TODO(sra): Replace this test with cheaper '#.constructor === Array' when |
+ // bug https://code.google.com/p/v8/issues/detail?id=621 is fixed. |
+ if (JS('bool', 'Object.getPrototypeOf(#) !== Array.prototype', object)) { |
+ return new _JsonMap(object); |
+ } |
+ |
+ // Update the elements in place since JS arrays are Dart lists. |
+ for (int i = 0; i < JS('int', '#.length', object); i++) { |
+ // Use JS indexing to avoid range checks. We know this is the only |
+ // reference to the list, but the compiler will likely never be able to |
+ // tell that this instance of the list cannot have its length changed by |
+ // the reviver even though it later will be passed to the reviver at the |
+ // outer level. |
+ var item = JS('', '#[#]', object, i); |
+ JS('', '#[#]=#', object, i, _convertJsonToDartLazy(item)); |
+ } |
+ return object; |
+} |
+ |
+class _JsonMap implements LinkedHashMap { |
+ // The original JavaScript object remains unchanged until |
+ // the map is eventually upgraded, in which case we null it |
+ // out to reclaim the memory used by it. |
+ var _original; |
+ |
+ // We keep track of the map entries that we have already |
+ // processed by adding them to a separate JavaScript object. |
+ var _processed = _newJavaScriptObject(); |
+ |
+ // If the data slot isn't null, it represents either the list |
+ // of keys (for non-upgraded JSON maps) or the upgraded map. |
+ var _data = null; |
+ |
+ _JsonMap(this._original); |
+ |
+ operator[](key) { |
+ if (_isUpgraded) { |
+ return _upgradedMap[key]; |
+ } else if (key is !String) { |
+ return null; |
+ } else { |
+ var result = _getProperty(_processed, key); |
+ if (_isUnprocessed(result)) result = _process(key); |
+ return result; |
+ } |
+ } |
+ |
+ int get length => _isUpgraded |
+ ? _upgradedMap.length |
+ : _computeKeys().length; |
+ |
+ bool get isEmpty => length == 0; |
+ bool get isNotEmpty => length > 0; |
+ |
+ Iterable get keys { |
+ if (_isUpgraded) return _upgradedMap.keys; |
+ return _computeKeys().skip(0); |
+ } |
+ |
+ Iterable get values { |
+ if (_isUpgraded) return _upgradedMap.values; |
+ return new MappedIterable(_computeKeys(), (each) => this[each]); |
+ } |
+ |
+ operator[]=(key, value) { |
+ if (_isUpgraded) { |
+ _upgradedMap[key] = value; |
+ } else if (containsKey(key)) { |
+ _setProperty(_processed, key, value); |
+ _setProperty(_original, key, null); // Reclaim memory. |
+ } else { |
+ _upgrade()[key] = value; |
+ } |
+ } |
+ |
+ void addAll(Map other) { |
+ other.forEach((key, value) { |
+ this[key] = value; |
+ }); |
+ } |
+ |
+ bool containsValue(value) { |
+ if (_isUpgraded) return _upgradedMap.containsValue(value); |
+ List<String> keys = _computeKeys(); |
+ for (int i = 0; i < keys.length; i++) { |
+ String key = keys[i]; |
+ if (this[key] == value) return true; |
+ } |
+ return false; |
+ } |
+ |
+ bool containsKey(key) { |
+ if (_isUpgraded) return _upgradedMap.containsKey(key); |
+ if (key is !String) return false; |
+ return _hasProperty(_original, key); |
+ } |
+ |
+ putIfAbsent(key, ifAbsent()) { |
+ if (containsKey(key)) return this[key]; |
+ var value = ifAbsent(); |
+ this[key] = value; |
+ return value; |
+ } |
+ |
+ remove(Object key) { |
+ if (!_isUpgraded && !containsKey(key)) return null; |
+ return _upgrade().remove(key); |
+ } |
+ |
+ void clear() { |
+ if (_isUpgraded) { |
+ _upgradedMap.clear(); |
+ } else { |
+ if (_data != null) { |
+ // Clear the list of keys to make sure we force |
+ // a concurrent modification error if anyone is |
+ // currently iterating over it. |
+ _data.clear(); |
+ } |
+ _original = _processed = null; |
+ _data = {}; |
+ } |
+ } |
+ |
+ void forEach(void f(key, value)) { |
+ if (_isUpgraded) return _upgradedMap.forEach(f); |
+ List<String> keys = _computeKeys(); |
+ for (int i = 0; i < keys.length; i++) { |
+ String key = keys[i]; |
+ f(key, this[key]); |
+ |
+ // Check if invoking the callback function changed |
+ // the key set. If so, throw an exception. |
+ if (!identical(keys, _data)) { |
+ throw new ConcurrentModificationError(this); |
+ } |
+ } |
+ } |
+ |
+ String toString() => Maps.mapToString(this); |
+ |
+ |
+ // ------------------------------------------ |
+ // Private helper methods. |
+ // ------------------------------------------ |
+ |
+ bool get _isUpgraded => _processed == null; |
+ |
+ Map get _upgradedMap { |
+ assert(_isUpgraded); |
+ return _data; |
+ } |
+ |
+ List<String> _computeKeys() { |
+ assert(!_isUpgraded); |
+ List keys = _data; |
+ if (keys == null) { |
+ keys = _data = _getPropertyNames(_original); |
+ } |
+ return keys; |
+ } |
+ |
+ Map _upgrade() { |
+ if (_isUpgraded) return _upgradedMap; |
+ |
+ // Copy all the (key, value) pairs to a freshly allocated |
+ // linked hash map thus preserving the ordering. |
+ Map result = {}; |
+ List<String> keys = _computeKeys(); |
+ for (int i = 0; i < keys.length; i++) { |
+ String key = keys[i]; |
+ result[key] = this[key]; |
+ } |
+ |
+ // We only upgrade when we need to extend the map, so we can |
+ // safely force a concurrent modification error in case |
+ // someone is iterating over the map here. |
+ if (keys.isEmpty) { |
+ keys.add(null); |
+ } else { |
+ keys.clear(); |
+ } |
+ |
+ // Clear out the associated JavaScript objects and mark the |
+ // map as having been upgraded. |
+ _original = _processed = null; |
+ _data = result; |
+ assert(_isUpgraded); |
+ return result; |
+ } |
+ |
+ _process(String key) { |
+ if (!_hasProperty(_original, key)) return null; |
+ var result = _convertJsonToDartLazy(_getProperty(_original, key)); |
+ return _setProperty(_processed, key, result); |
+ } |
+ |
+ |
+ // ------------------------------------------ |
+ // Private JavaScript helper methods. |
+ // ------------------------------------------ |
+ |
+ static bool _hasProperty(object, String key) |
+ => JS('bool', 'Object.prototype.hasOwnProperty.call(#,#)', object, key); |
+ static _getProperty(object, String key) |
+ => JS('', '#[#]', object, key); |
+ static _setProperty(object, String key, value) |
+ => JS('', '#[#]=#', object, key, value); |
+ static List _getPropertyNames(object) |
+ => JS('JSExtendableArray', 'Object.keys(#)', object); |
+ static bool _isUnprocessed(object) |
+ => JS('bool', 'typeof(#)=="undefined"', object); |
+ static _newJavaScriptObject() |
+ => JS('=Object', 'Object.create(null)'); |
+} |
+ |
@patch |
class _Utf8Encoder { |
// Use Uint8List when supported on all platforms. |