Skip to content

Commit 7087c8a

Browse files
author
kasperl@google.com
committed
Make the JSON decoder (parser) create Dart maps lazily when no reviver is provided (common case).
R=floitsch@google.com, lrn@google.com BUG=http://dartbug.com/19649 Review URL: https://codereview.chromium.org//336863008 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@37858 260f80e4-7a28-3924-810f-c04153c831b5
1 parent 200fc91 commit 7087c8a

File tree

4 files changed

+580
-9
lines changed

4 files changed

+580
-9
lines changed

runtime/lib/collection_patch.dart

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -174,9 +174,11 @@ class _HashMap<K, V> implements HashMap<K, V> {
174174
}
175175

176176
void clear() {
177-
_elementCount = 0;
178177
_buckets = new List(_INITIAL_CAPACITY);
179-
_modificationCount = (_modificationCount + 1) & _MODIFICATION_COUNT_MASK;
178+
if (_elementCount > 0) {
179+
_elementCount = 0;
180+
_modificationCount = (_modificationCount + 1) & _MODIFICATION_COUNT_MASK;
181+
}
180182
}
181183

182184
void _removeEntry(_HashMapEntry entry,
@@ -673,9 +675,11 @@ class _HashSet<E> extends _HashSetBase<E> implements HashSet<E> {
673675
}
674676

675677
void clear() {
676-
_elementCount = 0;
677678
_buckets = new List(_INITIAL_CAPACITY);
678-
_modificationCount++;
679+
if (_elementCount > 0) {
680+
_elementCount = 0;
681+
_modificationCount = (_modificationCount + 1) & _MODIFICATION_COUNT_MASK;
682+
}
679683
}
680684

681685
void _addEntry(E key, int hashCode, int index) {
@@ -963,9 +967,11 @@ abstract class _LinkedHashMapMixin<K, V> implements LinkedHashMap<K, V> {
963967

964968
void clear() {
965969
_nextEntry = _previousEntry = this;
966-
_elementCount = 0;
967970
_buckets = new List(_HashMap._INITIAL_CAPACITY);
968-
_modificationCount = (_modificationCount + 1) & _MODIFICATION_COUNT_MASK;
971+
if (_elementCount > 0) {
972+
_elementCount = 0;
973+
_modificationCount = (_modificationCount + 1) & _MODIFICATION_COUNT_MASK;
974+
}
969975
}
970976

971977
void _addEntry(List buckets, int index, int length,

sdk/lib/_internal/lib/convert_patch.dart

Lines changed: 235 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import 'dart:_js_helper' show patch;
88
import 'dart:_foreign_helper' show JS;
99
import 'dart:_interceptors' show JSExtendableArray;
10+
import 'dart:_internal' show MappedIterable;
11+
import 'dart:collection' show Maps, LinkedHashMap;
1012

1113
/**
1214
* Parses [json] and builds the corresponding parsed JSON value.
@@ -37,7 +39,11 @@ _parseJson(String source, reviver(key, value)) {
3739
throw new FormatException(JS('String', 'String(#)', e));
3840
}
3941

40-
return _convertJsonToDart(parsed, reviver);
42+
if (reviver == null) {
43+
return _convertJsonToDartLazy(parsed);
44+
} else {
45+
return _convertJsonToDart(parsed, reviver);
46+
}
4147
}
4248

4349
/**
@@ -46,7 +52,6 @@ _parseJson(String source, reviver(key, value)) {
4652
* in-place.
4753
*/
4854
_convertJsonToDart(json, reviver(key, value)) {
49-
5055
var revive = reviver == null ? (key, value) => value : reviver;
5156

5257
walk(e) {
@@ -59,7 +64,8 @@ _convertJsonToDart(json, reviver(key, value)) {
5964
// TODO(sra): Replace this test with cheaper '#.constructor === Array' when
6065
// bug 621 below is fixed.
6166
if (JS('bool', 'Object.getPrototypeOf(#) === Array.prototype', e)) {
62-
var list = JS('JSExtendableArray', '#', e); // Teach compiler the type is known.
67+
// Teach compiler the type is known by passing it through a JS-expression.
68+
var list = JS('JSExtendableArray', '#', e);
6369
// In-place update of the elements since JS Array is a Dart List.
6470
for (int i = 0; i < list.length; i++) {
6571
// Use JS indexing to avoid range checks. We know this is the only
@@ -95,6 +101,232 @@ _convertJsonToDart(json, reviver(key, value)) {
95101
return revive(null, walk(json));
96102
}
97103

104+
_convertJsonToDartLazy(object) {
105+
// JavaScript null and undefined are represented as null.
106+
if (object == null) return null;
107+
108+
// JavaScript string, number, bool already has the correct representation.
109+
if (JS('bool', 'typeof # != "object"', object)) {
110+
return object;
111+
}
112+
113+
// This test is needed to avoid identifing '{"__proto__":[]}' as an array.
114+
// TODO(sra): Replace this test with cheaper '#.constructor === Array' when
115+
// bug https://code.google.com/p/v8/issues/detail?id=621 is fixed.
116+
if (JS('bool', 'Object.getPrototypeOf(#) !== Array.prototype', object)) {
117+
return new _JsonMap(object);
118+
}
119+
120+
// Update the elements in place since JS arrays are Dart lists.
121+
for (int i = 0; i < JS('int', '#.length', object); i++) {
122+
// Use JS indexing to avoid range checks. We know this is the only
123+
// reference to the list, but the compiler will likely never be able to
124+
// tell that this instance of the list cannot have its length changed by
125+
// the reviver even though it later will be passed to the reviver at the
126+
// outer level.
127+
var item = JS('', '#[#]', object, i);
128+
JS('', '#[#]=#', object, i, _convertJsonToDartLazy(item));
129+
}
130+
return object;
131+
}
132+
133+
class _JsonMap implements LinkedHashMap {
134+
// The original JavaScript object remains unchanged until
135+
// the map is eventually upgraded, in which case we null it
136+
// out to reclaim the memory used by it.
137+
var _original;
138+
139+
// We keep track of the map entries that we have already
140+
// processed by adding them to a separate JavaScript object.
141+
var _processed = _newJavaScriptObject();
142+
143+
// If the data slot isn't null, it represents either the list
144+
// of keys (for non-upgraded JSON maps) or the upgraded map.
145+
var _data = null;
146+
147+
_JsonMap(this._original);
148+
149+
operator[](key) {
150+
if (_isUpgraded) {
151+
return _upgradedMap[key];
152+
} else if (key is !String) {
153+
return null;
154+
} else {
155+
var result = _getProperty(_processed, key);
156+
if (_isUnprocessed(result)) result = _process(key);
157+
return result;
158+
}
159+
}
160+
161+
int get length => _isUpgraded
162+
? _upgradedMap.length
163+
: _computeKeys().length;
164+
165+
bool get isEmpty => length == 0;
166+
bool get isNotEmpty => length > 0;
167+
168+
Iterable get keys {
169+
if (_isUpgraded) return _upgradedMap.keys;
170+
return _computeKeys().skip(0);
171+
}
172+
173+
Iterable get values {
174+
if (_isUpgraded) return _upgradedMap.values;
175+
return new MappedIterable(_computeKeys(), (each) => this[each]);
176+
}
177+
178+
operator[]=(key, value) {
179+
if (_isUpgraded) {
180+
_upgradedMap[key] = value;
181+
} else if (containsKey(key)) {
182+
_setProperty(_processed, key, value);
183+
_setProperty(_original, key, null); // Reclaim memory.
184+
} else {
185+
_upgrade()[key] = value;
186+
}
187+
}
188+
189+
void addAll(Map other) {
190+
other.forEach((key, value) {
191+
this[key] = value;
192+
});
193+
}
194+
195+
bool containsValue(value) {
196+
if (_isUpgraded) return _upgradedMap.containsValue(value);
197+
List<String> keys = _computeKeys();
198+
for (int i = 0; i < keys.length; i++) {
199+
String key = keys[i];
200+
if (this[key] == value) return true;
201+
}
202+
return false;
203+
}
204+
205+
bool containsKey(key) {
206+
if (_isUpgraded) return _upgradedMap.containsKey(key);
207+
if (key is !String) return false;
208+
return _hasProperty(_original, key);
209+
}
210+
211+
putIfAbsent(key, ifAbsent()) {
212+
if (containsKey(key)) return this[key];
213+
var value = ifAbsent();
214+
this[key] = value;
215+
return value;
216+
}
217+
218+
remove(Object key) {
219+
if (!_isUpgraded && !containsKey(key)) return null;
220+
return _upgrade().remove(key);
221+
}
222+
223+
void clear() {
224+
if (_isUpgraded) {
225+
_upgradedMap.clear();
226+
} else {
227+
if (_data != null) {
228+
// Clear the list of keys to make sure we force
229+
// a concurrent modification error if anyone is
230+
// currently iterating over it.
231+
_data.clear();
232+
}
233+
_original = _processed = null;
234+
_data = {};
235+
}
236+
}
237+
238+
void forEach(void f(key, value)) {
239+
if (_isUpgraded) return _upgradedMap.forEach(f);
240+
List<String> keys = _computeKeys();
241+
for (int i = 0; i < keys.length; i++) {
242+
String key = keys[i];
243+
f(key, this[key]);
244+
245+
// Check if invoking the callback function changed
246+
// the key set. If so, throw an exception.
247+
if (!identical(keys, _data)) {
248+
throw new ConcurrentModificationError(this);
249+
}
250+
}
251+
}
252+
253+
String toString() => Maps.mapToString(this);
254+
255+
256+
// ------------------------------------------
257+
// Private helper methods.
258+
// ------------------------------------------
259+
260+
bool get _isUpgraded => _processed == null;
261+
262+
Map get _upgradedMap {
263+
assert(_isUpgraded);
264+
return _data;
265+
}
266+
267+
List<String> _computeKeys() {
268+
assert(!_isUpgraded);
269+
List keys = _data;
270+
if (keys == null) {
271+
keys = _data = _getPropertyNames(_original);
272+
}
273+
return keys;
274+
}
275+
276+
Map _upgrade() {
277+
if (_isUpgraded) return _upgradedMap;
278+
279+
// Copy all the (key, value) pairs to a freshly allocated
280+
// linked hash map thus preserving the ordering.
281+
Map result = {};
282+
List<String> keys = _computeKeys();
283+
for (int i = 0; i < keys.length; i++) {
284+
String key = keys[i];
285+
result[key] = this[key];
286+
}
287+
288+
// We only upgrade when we need to extend the map, so we can
289+
// safely force a concurrent modification error in case
290+
// someone is iterating over the map here.
291+
if (keys.isEmpty) {
292+
keys.add(null);
293+
} else {
294+
keys.clear();
295+
}
296+
297+
// Clear out the associated JavaScript objects and mark the
298+
// map as having been upgraded.
299+
_original = _processed = null;
300+
_data = result;
301+
assert(_isUpgraded);
302+
return result;
303+
}
304+
305+
_process(String key) {
306+
if (!_hasProperty(_original, key)) return null;
307+
var result = _convertJsonToDartLazy(_getProperty(_original, key));
308+
return _setProperty(_processed, key, result);
309+
}
310+
311+
312+
// ------------------------------------------
313+
// Private JavaScript helper methods.
314+
// ------------------------------------------
315+
316+
static bool _hasProperty(object, String key)
317+
=> JS('bool', 'Object.prototype.hasOwnProperty.call(#,#)', object, key);
318+
static _getProperty(object, String key)
319+
=> JS('', '#[#]', object, key);
320+
static _setProperty(object, String key, value)
321+
=> JS('', '#[#]=#', object, key, value);
322+
static List _getPropertyNames(object)
323+
=> JS('JSExtendableArray', 'Object.keys(#)', object);
324+
static bool _isUnprocessed(object)
325+
=> JS('bool', 'typeof(#)=="undefined"', object);
326+
static _newJavaScriptObject()
327+
=> JS('=Object', 'Object.create(null)');
328+
}
329+
98330
@patch
99331
class _Utf8Encoder {
100332
// Use Uint8List when supported on all platforms.

0 commit comments

Comments
 (0)