| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 // Patch file for dart:convert library. | |
| 6 | |
| 7 import 'dart:_js_helper' show argumentErrorValue, patch; | |
| 8 import 'dart:_foreign_helper' show JS; | |
| 9 import 'dart:_interceptors' show JSExtendableArray; | |
| 10 import 'dart:_internal' show MappedIterable, ListIterable; | |
| 11 import 'dart:collection' show Maps, LinkedHashMap; | |
| 12 | |
| 13 /** | |
| 14 * Parses [json] and builds the corresponding parsed JSON value. | |
| 15 * | |
| 16 * Parsed JSON values Nare of the types [num], [String], [bool], [Null], | |
| 17 * [List]s of parsed JSON values or [Map]s from [String] to parsed | |
| 18 * JSON values. | |
| 19 * | |
| 20 * The optional [reviver] function, if provided, is called once for each object | |
| 21 * or list property parsed. The arguments are the property name ([String]) or | |
| 22 * list index ([int]), and the value is the parsed value. The return value of | |
| 23 * the reviver will be used as the value of that property instead of the parsed | |
| 24 * value. The top level value is passed to the reviver with the empty string as | |
| 25 * a key. | |
| 26 * | |
| 27 * Throws [FormatException] if the input is not valid JSON text. | |
| 28 */ | |
| 29 @patch | |
| 30 _parseJson(String source, reviver(key, value)) { | |
| 31 if (source is! String) throw argumentErrorValue(source); | |
| 32 | |
| 33 var parsed; | |
| 34 try { | |
| 35 parsed = JS('=Object|JSExtendableArray|Null|bool|num|String', | |
| 36 'JSON.parse(#)', | |
| 37 source); | |
| 38 } catch (e) { | |
| 39 throw new FormatException(JS('String', 'String(#)', e)); | |
| 40 } | |
| 41 | |
| 42 if (reviver == null) { | |
| 43 return _convertJsonToDartLazy(parsed); | |
| 44 } else { | |
| 45 return _convertJsonToDart(parsed, reviver); | |
| 46 } | |
| 47 } | |
| 48 | |
| 49 /** | |
| 50 * Walks the raw JavaScript value [json], replacing JavaScript Objects with | |
| 51 * Maps. [json] is expected to be freshly allocated so elements can be replaced | |
| 52 * in-place. | |
| 53 */ | |
| 54 _convertJsonToDart(json, reviver(key, value)) { | |
| 55 assert(reviver != null); | |
| 56 walk(e) { | |
| 57 // JavaScript null, string, number, bool are in the correct representation. | |
| 58 if (JS('bool', '# == null', e) || JS('bool', 'typeof # != "object"', e)) { | |
| 59 return e; | |
| 60 } | |
| 61 | |
| 62 // This test is needed to avoid identifing '{"__proto__":[]}' as an Array. | |
| 63 // TODO(sra): Replace this test with cheaper '#.constructor === Array' when | |
| 64 // bug 621 below is fixed. | |
| 65 if (JS('bool', 'Object.getPrototypeOf(#) === Array.prototype', e)) { | |
| 66 // In-place update of the elements since JS Array is a Dart List. | |
| 67 for (int i = 0; i < JS('int', '#.length', e); i++) { | |
| 68 // Use JS indexing to avoid range checks. We know this is the only | |
| 69 // reference to the list, but the compiler will likely never be able to | |
| 70 // tell that this instance of the list cannot have its length changed by | |
| 71 // the reviver even though it later will be passed to the reviver at the | |
| 72 // outer level. | |
| 73 var item = JS('', '#[#]', e, i); | |
| 74 JS('', '#[#]=#', e, i, reviver(i, walk(item))); | |
| 75 } | |
| 76 return e; | |
| 77 } | |
| 78 | |
| 79 // Otherwise it is a plain object, so copy to a JSON map, so we process | |
| 80 // and revive all entries recursively. | |
| 81 _JsonMap map = new _JsonMap(e); | |
| 82 var processed = map._processed; | |
| 83 List<String> keys = map._computeKeys(); | |
| 84 for (int i = 0; i < keys.length; i++) { | |
| 85 String key = keys[i]; | |
| 86 var revived = reviver(key, walk(JS('', '#[#]', e, key))); | |
| 87 JS('', '#[#]=#', processed, key, revived); | |
| 88 } | |
| 89 | |
| 90 // Update the JSON map structure so future access is cheaper. | |
| 91 map._original = processed; // Don't keep two objects around. | |
| 92 return map; | |
| 93 } | |
| 94 | |
| 95 return reviver(null, walk(json)); | |
| 96 } | |
| 97 | |
| 98 _convertJsonToDartLazy(object) { | |
| 99 // JavaScript null and undefined are represented as null. | |
| 100 if (object == null) return null; | |
| 101 | |
| 102 // JavaScript string, number, bool already has the correct representation. | |
| 103 if (JS('bool', 'typeof # != "object"', object)) { | |
| 104 return object; | |
| 105 } | |
| 106 | |
| 107 // This test is needed to avoid identifing '{"__proto__":[]}' as an array. | |
| 108 // TODO(sra): Replace this test with cheaper '#.constructor === Array' when | |
| 109 // bug https://code.google.com/p/v8/issues/detail?id=621 is fixed. | |
| 110 if (JS('bool', 'Object.getPrototypeOf(#) !== Array.prototype', object)) { | |
| 111 return new _JsonMap(object); | |
| 112 } | |
| 113 | |
| 114 // Update the elements in place since JS arrays are Dart lists. | |
| 115 for (int i = 0; i < JS('int', '#.length', object); i++) { | |
| 116 // Use JS indexing to avoid range checks. We know this is the only | |
| 117 // reference to the list, but the compiler will likely never be able to | |
| 118 // tell that this instance of the list cannot have its length changed by | |
| 119 // the reviver even though it later will be passed to the reviver at the | |
| 120 // outer level. | |
| 121 var item = JS('', '#[#]', object, i); | |
| 122 JS('', '#[#]=#', object, i, _convertJsonToDartLazy(item)); | |
| 123 } | |
| 124 return object; | |
| 125 } | |
| 126 | |
| 127 class _JsonMap implements LinkedHashMap { | |
| 128 // The original JavaScript object remains unchanged until | |
| 129 // the map is eventually upgraded, in which case we null it | |
| 130 // out to reclaim the memory used by it. | |
| 131 var _original; | |
| 132 | |
| 133 // We keep track of the map entries that we have already | |
| 134 // processed by adding them to a separate JavaScript object. | |
| 135 var _processed = _newJavaScriptObject(); | |
| 136 | |
| 137 // If the data slot isn't null, it represents either the list | |
| 138 // of keys (for non-upgraded JSON maps) or the upgraded map. | |
| 139 var _data = null; | |
| 140 | |
| 141 _JsonMap(this._original); | |
| 142 | |
| 143 operator[](key) { | |
| 144 if (_isUpgraded) { | |
| 145 return _upgradedMap[key]; | |
| 146 } else if (key is !String) { | |
| 147 return null; | |
| 148 } else { | |
| 149 var result = _getProperty(_processed, key); | |
| 150 if (_isUnprocessed(result)) result = _process(key); | |
| 151 return result; | |
| 152 } | |
| 153 } | |
| 154 | |
| 155 int get length => _isUpgraded | |
| 156 ? _upgradedMap.length | |
| 157 : _computeKeys().length; | |
| 158 | |
| 159 bool get isEmpty => length == 0; | |
| 160 bool get isNotEmpty => length > 0; | |
| 161 | |
| 162 Iterable get keys { | |
| 163 if (_isUpgraded) return _upgradedMap.keys; | |
| 164 return new _JsonMapKeyIterable(this); | |
| 165 } | |
| 166 | |
| 167 Iterable get values { | |
| 168 if (_isUpgraded) return _upgradedMap.values; | |
| 169 return new MappedIterable(_computeKeys(), (each) => this[each]); | |
| 170 } | |
| 171 | |
| 172 operator[]=(key, value) { | |
| 173 if (_isUpgraded) { | |
| 174 _upgradedMap[key] = value; | |
| 175 } else if (containsKey(key)) { | |
| 176 var processed = _processed; | |
| 177 _setProperty(processed, key, value); | |
| 178 var original = _original; | |
| 179 if (!identical(original, processed)) { | |
| 180 _setProperty(original, key, null); // Reclaim memory. | |
| 181 } | |
| 182 } else { | |
| 183 _upgrade()[key] = value; | |
| 184 } | |
| 185 } | |
| 186 | |
| 187 void addAll(Map other) { | |
| 188 other.forEach((key, value) { | |
| 189 this[key] = value; | |
| 190 }); | |
| 191 } | |
| 192 | |
| 193 bool containsValue(value) { | |
| 194 if (_isUpgraded) return _upgradedMap.containsValue(value); | |
| 195 List<String> keys = _computeKeys(); | |
| 196 for (int i = 0; i < keys.length; i++) { | |
| 197 String key = keys[i]; | |
| 198 if (this[key] == value) return true; | |
| 199 } | |
| 200 return false; | |
| 201 } | |
| 202 | |
| 203 bool containsKey(key) { | |
| 204 if (_isUpgraded) return _upgradedMap.containsKey(key); | |
| 205 if (key is !String) return false; | |
| 206 return _hasProperty(_original, key); | |
| 207 } | |
| 208 | |
| 209 putIfAbsent(key, ifAbsent()) { | |
| 210 if (containsKey(key)) return this[key]; | |
| 211 var value = ifAbsent(); | |
| 212 this[key] = value; | |
| 213 return value; | |
| 214 } | |
| 215 | |
| 216 remove(Object key) { | |
| 217 if (!_isUpgraded && !containsKey(key)) return null; | |
| 218 return _upgrade().remove(key); | |
| 219 } | |
| 220 | |
| 221 void clear() { | |
| 222 if (_isUpgraded) { | |
| 223 _upgradedMap.clear(); | |
| 224 } else { | |
| 225 if (_data != null) { | |
| 226 // Clear the list of keys to make sure we force | |
| 227 // a concurrent modification error if anyone is | |
| 228 // currently iterating over it. | |
| 229 _data.clear(); | |
| 230 } | |
| 231 _original = _processed = null; | |
| 232 _data = {}; | |
| 233 } | |
| 234 } | |
| 235 | |
| 236 void forEach(void f(key, value)) { | |
| 237 if (_isUpgraded) return _upgradedMap.forEach(f); | |
| 238 List<String> keys = _computeKeys(); | |
| 239 for (int i = 0; i < keys.length; i++) { | |
| 240 String key = keys[i]; | |
| 241 | |
| 242 // Compute the value under the assumption that the property | |
| 243 // is present but potentially not processed. | |
| 244 var value = _getProperty(_processed, key); | |
| 245 if (_isUnprocessed(value)) { | |
| 246 value = _convertJsonToDartLazy(_getProperty(_original, key)); | |
| 247 _setProperty(_processed, key, value); | |
| 248 } | |
| 249 | |
| 250 // Do the callback. | |
| 251 f(key, value); | |
| 252 | |
| 253 // Check if invoking the callback function changed | |
| 254 // the key set. If so, throw an exception. | |
| 255 if (!identical(keys, _data)) { | |
| 256 throw new ConcurrentModificationError(this); | |
| 257 } | |
| 258 } | |
| 259 } | |
| 260 | |
| 261 String toString() => Maps.mapToString(this); | |
| 262 | |
| 263 | |
| 264 // ------------------------------------------ | |
| 265 // Private helper methods. | |
| 266 // ------------------------------------------ | |
| 267 | |
| 268 bool get _isUpgraded => _processed == null; | |
| 269 | |
| 270 Map get _upgradedMap { | |
| 271 assert(_isUpgraded); | |
| 272 // 'cast' the union type to LinkedHashMap. It would be even better if we | |
| 273 // could 'cast' to the implementation type, since LinkedHashMap includes | |
| 274 // _JsonMap. | |
| 275 return JS('LinkedHashMap', '#', _data); | |
| 276 } | |
| 277 | |
| 278 List<String> _computeKeys() { | |
| 279 assert(!_isUpgraded); | |
| 280 List keys = _data; | |
| 281 if (keys == null) { | |
| 282 keys = _data = _getPropertyNames(_original); | |
| 283 } | |
| 284 return JS('JSExtendableArray', '#', keys); | |
| 285 } | |
| 286 | |
| 287 Map _upgrade() { | |
| 288 if (_isUpgraded) return _upgradedMap; | |
| 289 | |
| 290 // Copy all the (key, value) pairs to a freshly allocated | |
| 291 // linked hash map thus preserving the ordering. | |
| 292 Map result = {}; | |
| 293 List<String> keys = _computeKeys(); | |
| 294 for (int i = 0; i < keys.length; i++) { | |
| 295 String key = keys[i]; | |
| 296 result[key] = this[key]; | |
| 297 } | |
| 298 | |
| 299 // We only upgrade when we need to extend the map, so we can | |
| 300 // safely force a concurrent modification error in case | |
| 301 // someone is iterating over the map here. | |
| 302 if (keys.isEmpty) { | |
| 303 keys.add(null); | |
| 304 } else { | |
| 305 keys.clear(); | |
| 306 } | |
| 307 | |
| 308 // Clear out the associated JavaScript objects and mark the | |
| 309 // map as having been upgraded. | |
| 310 _original = _processed = null; | |
| 311 _data = result; | |
| 312 assert(_isUpgraded); | |
| 313 return result; | |
| 314 } | |
| 315 | |
| 316 _process(String key) { | |
| 317 if (!_hasProperty(_original, key)) return null; | |
| 318 var result = _convertJsonToDartLazy(_getProperty(_original, key)); | |
| 319 return _setProperty(_processed, key, result); | |
| 320 } | |
| 321 | |
| 322 | |
| 323 // ------------------------------------------ | |
| 324 // Private JavaScript helper methods. | |
| 325 // ------------------------------------------ | |
| 326 | |
| 327 static bool _hasProperty(object, String key) | |
| 328 => JS('bool', 'Object.prototype.hasOwnProperty.call(#,#)', object, key); | |
| 329 static _getProperty(object, String key) | |
| 330 => JS('', '#[#]', object, key); | |
| 331 static _setProperty(object, String key, value) | |
| 332 => JS('', '#[#]=#', object, key, value); | |
| 333 static List _getPropertyNames(object) | |
| 334 => JS('JSExtendableArray', 'Object.keys(#)', object); | |
| 335 static bool _isUnprocessed(object) | |
| 336 => JS('bool', 'typeof(#)=="undefined"', object); | |
| 337 static _newJavaScriptObject() | |
| 338 => JS('=Object', 'Object.create(null)'); | |
| 339 } | |
| 340 | |
| 341 class _JsonMapKeyIterable extends ListIterable { | |
| 342 final _JsonMap _parent; | |
| 343 | |
| 344 _JsonMapKeyIterable(this._parent); | |
| 345 | |
| 346 int get length => _parent.length; | |
| 347 | |
| 348 String elementAt(int index) { | |
| 349 return _parent._isUpgraded ? _parent.keys.elementAt(index) | |
| 350 : _parent._computeKeys()[index]; | |
| 351 } | |
| 352 | |
| 353 /// Although [ListIterable] defines its own iterator, we return the iterator | |
| 354 /// of the underlying list [_keys] in order to propagate | |
| 355 /// [ConcurrentModificationError]s. | |
| 356 Iterator get iterator { | |
| 357 return _parent._isUpgraded ? _parent.keys.iterator | |
| 358 : _parent._computeKeys().iterator; | |
| 359 } | |
| 360 | |
| 361 /// Delegate to [parent.containsKey] to ensure the performance expected | |
| 362 /// from [Map.keys.containsKey]. | |
| 363 bool contains(Object key) => _parent.containsKey(key); | |
| 364 } | |
| 365 | |
| 366 @patch class JsonDecoder { | |
| 367 @patch | |
| 368 StringConversionSink startChunkedConversion(Sink<Object> sink) { | |
| 369 return new _JsonDecoderSink(_reviver, sink); | |
| 370 } | |
| 371 } | |
| 372 | |
| 373 /** | |
| 374 * Implements the chunked conversion from a JSON string to its corresponding | |
| 375 * object. | |
| 376 * | |
| 377 * The sink only creates one object, but its input can be chunked. | |
| 378 */ | |
| 379 // TODO(floitsch): don't accumulate everything before starting to decode. | |
| 380 class _JsonDecoderSink extends _StringSinkConversionSink { | |
| 381 final _Reviver _reviver; | |
| 382 final Sink<Object> _sink; | |
| 383 | |
| 384 _JsonDecoderSink(this._reviver, this._sink) | |
| 385 : super(new StringBuffer()); | |
| 386 | |
| 387 void close() { | |
| 388 super.close(); | |
| 389 StringBuffer buffer = _stringSink; | |
| 390 String accumulated = buffer.toString(); | |
| 391 buffer.clear(); | |
| 392 Object decoded = _parseJson(accumulated, _reviver); | |
| 393 _sink.add(decoded); | |
| 394 _sink.close(); | |
| 395 } | |
| 396 } | |
| 397 | |
| 398 @patch class Utf8Decoder { | |
| 399 @patch | |
| 400 Converter<List<int>,dynamic> fuse(Converter<String, dynamic> next) { | |
| 401 return super.fuse(next); | |
| 402 } | |
| 403 | |
| 404 // Currently not intercepting UTF8 decoding. | |
| 405 @patch | |
| 406 static String _convertIntercepted(bool allowMalformed, List<int> codeUnits, | |
| 407 int start, int end) { | |
| 408 return null; // This call was not intercepted. | |
| 409 } | |
| 410 } | |
| OLD | NEW |