Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(282)

Side by Side Diff: runtime/lib/convert_patch.dart

Issue 181543004: Optimize VM JSON parser for memory use. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Typo in type. Created 6 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | sdk/lib/core/iterable.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 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 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 import "dart:typed_data"; 5 import "dart:typed_data";
6 import "dart:collection" show HashMap, LinkedHashMap, Maps;
7 import "dart:_internal" show SubListIterable, makeListFixedLength;
6 8
7 // JSON conversion. 9 // JSON conversion.
8 10
9 patch _parseJson(String json, reviver(var key, var value)) { 11 patch _parseJson(String json, reviver(var key, var value)) {
10 _BuildJsonListener listener; 12 _BuildJsonListener listener;
11 if (reviver == null) { 13 if (reviver == null) {
12 listener = new _BuildJsonListener(); 14 listener = new _BuildJsonListener();
13 } else { 15 } else {
14 listener = new _ReviverJsonListener(reviver); 16 listener = new _ReviverJsonListener(reviver);
15 } 17 }
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after
51 * started. If the container is a [Map], there is also a current [key] 53 * started. If the container is a [Map], there is also a current [key]
52 * which is also stored on the stack. 54 * which is also stored on the stack.
53 */ 55 */
54 List stack = []; 56 List stack = [];
55 /** The current [Map] or [List] being built. */ 57 /** The current [Map] or [List] being built. */
56 var currentContainer; 58 var currentContainer;
57 /** The most recently read property key. */ 59 /** The most recently read property key. */
58 String key; 60 String key;
59 /** The most recently read value. */ 61 /** The most recently read value. */
60 var value; 62 var value;
63 /** Cache for reusable hidden classes of objects. Start out in multi-mode. */
64 static _JsonTransitionMap staticCache =
65 new _JsonMultiTransitionMap(const _JsonHiddenClass.empty(),
66 new HashMap());
67 // Counts how many transitions have been added to the cache.
68 // Cache is cleared when reaching the max size.
69 static int staticCacheSize = 0;
70 static const int MAX_STATIC_CACHE_SIZE = 512;
61 71
62 /** Pushes the currently active container (and key, if a [Map]). */ 72 /** Pushes the currently active container (and key, if a [Map]). */
63 void pushContainer() { 73 void pushContainer() {
64 if (currentContainer is Map) stack.add(key); 74 if (key != null)
75 if (currentContainer is _JsonObjectBuilder) stack.add(key);
65 stack.add(currentContainer); 76 stack.add(currentContainer);
66 } 77 }
67 78
68 /** Pops the top container from the [stack], including a key if applicable. */ 79 /** Pops the top container from the [stack], including a key if applicable. */
69 void popContainer() { 80 void popContainer() {
70 value = currentContainer; 81 value = currentContainer;
71 currentContainer = stack.removeLast(); 82 currentContainer = stack.removeLast();
72 if (currentContainer is Map) key = stack.removeLast(); 83 if (currentContainer is _JsonObjectBuilder) key = stack.removeLast();
73 } 84 }
74 85
75 void handleString(String value) { this.value = value; } 86 void handleString(String value) { this.value = value; }
76 void handleNumber(num value) { this.value = value; } 87 void handleNumber(num value) { this.value = value; }
77 void handleBool(bool value) { this.value = value; } 88 void handleBool(bool value) { this.value = value; }
78 void handleNull() { this.value = null; } 89 void handleNull() { this.value = null; }
79 90
80 void beginObject() { 91 void beginObject() {
81 pushContainer(); 92 pushContainer();
82 currentContainer = {}; 93 currentContainer = new _JsonObjectBuilder(staticCache);
83 } 94 }
84 95
85 void propertyName() { 96 void propertyName() {
86 key = value; 97 key = value;
87 value = null; 98 value = null;
88 } 99 }
89 100
90 void propertyValue() { 101 void propertyValue() {
91 Map map = currentContainer; 102 _JsonObjectBuilder builder = currentContainer;
92 map[key] = value; 103 builder.add(key, value);
93 key = value = null; 104 key = value = null;
94 } 105 }
95 106
96 void endObject() { 107 void endObject() {
108 _JsonObjectBuilder builder = currentContainer;
109 currentContainer = builder.toMap();
110 staticCacheSize += builder.transitionsAdded;
97 popContainer(); 111 popContainer();
98 } 112 }
99 113
100 void beginArray() { 114 void beginArray() {
101 pushContainer(); 115 pushContainer();
102 currentContainer = []; 116 currentContainer = [];
103 } 117 }
104 118
105 void arrayElement() { 119 void arrayElement() {
106 List list = currentContainer; 120 List list = currentContainer;
107 currentContainer.add(value); 121 currentContainer.add(value);
108 value = null; 122 value = null;
109 } 123 }
110 124
111 void endArray() { 125 void endArray() {
112 popContainer(); 126 popContainer();
113 } 127 }
114 128
115 /** Read out the final result of parsing a JSON string. */ 129 /** Read out the final result of parsing a JSON string. */
116 get result { 130 get result {
117 assert(currentContainer == null); 131 assert(currentContainer == null);
132 if (staticCacheSize > MAX_STATIC_CACHE_SIZE) {
133 _JsonMultiTransitionMap cache = staticCache;
134 cache.mapping.clear();
135 staticCacheSize = 0;
136 }
118 return value; 137 return value;
119 } 138 }
120 } 139 }
121 140
122 class _ReviverJsonListener extends _BuildJsonListener { 141 class _ReviverJsonListener extends _BuildJsonListener {
123 final _Reviver reviver; 142 final _Reviver reviver;
124 _ReviverJsonListener(reviver(key, value)) : this.reviver = reviver; 143 _ReviverJsonListener(reviver(key, value)) : this.reviver = reviver;
125 144
126 void arrayElement() { 145 void arrayElement() {
127 List list = currentContainer; 146 List list = currentContainer;
128 value = reviver(list.length, value); 147 value = reviver(list.length, value);
129 super.arrayElement(); 148 super.arrayElement();
130 } 149 }
131 150
132 void propertyValue() { 151 void propertyValue() {
133 value = reviver(key, value); 152 value = reviver(key, value);
134 super.propertyValue(); 153 super.propertyValue();
135 } 154 }
136 155
137 get result { 156 get result {
138 return reviver(null, value); 157 return reviver(null, value);
139 } 158 }
140 } 159 }
141 160
142 class _JsonParser { 161 class _JsonParser {
143 // A simple non-recursive state-based parser for JSON. 162 // A simple non-recursive state-based parser for JSON.
144 // 163 //
145 // Literal values accepted in states ARRAY_EMPTY, ARRAY_COMMA, OBJECT_COLON 164 // Literal values accepted in states ARRAY_EMPTY, ARRAY_COMMA, OBJECT_COLON
146 // and strings also in OBJECT_EMPTY, OBJECT_COMMA. 165 // and strings also in OBJECT_EMPTY, OBJECT_COMMA.
147 // VALUE STRING : , } ] Transitions to 166 // VALUE STRING : , } ] f to
148 // EMPTY X X -> END 167 // EMPTY X X -> END
149 // ARRAY_EMPTY X X @ -> ARRAY_VALUE / pop 168 // ARRAY_EMPTY X X @ -> ARRAY_VALUE / pop
150 // ARRAY_VALUE @ @ -> ARRAY_COMMA / pop 169 // ARRAY_VALUE @ @ -> ARRAY_COMMA / pop
151 // ARRAY_COMMA X X -> ARRAY_VALUE 170 // ARRAY_COMMA X X -> ARRAY_VALUE
152 // OBJECT_EMPTY X @ -> OBJECT_KEY / pop 171 // OBJECT_EMPTY X @ -> OBJECT_KEY / pop
153 // OBJECT_KEY @ -> OBJECT_COLON 172 // OBJECT_KEY @ -> OBJECT_COLON
154 // OBJECT_COLON X X -> OBJECT_VALUE 173 // OBJECT_COLON X X -> OBJECT_VALUE
155 // OBJECT_VALUE @ @ -> OBJECT_COMMA / pop 174 // OBJECT_VALUE @ @ -> OBJECT_COMMA / pop
156 // OBJECT_COMMA X -> OBJECT_KEY 175 // OBJECT_COMMA X -> OBJECT_KEY
157 // END 176 // END
(...skipping 226 matching lines...) Expand 10 before | Expand all | Expand 10 after
384 fail(position); 403 fail(position);
385 } 404 }
386 listener.handleNull(); 405 listener.handleNull();
387 return position + 4; 406 return position + 4;
388 } 407 }
389 408
390 int parseString(int position) { 409 int parseString(int position) {
391 // Format: '"'([^\x00-\x1f\\\"]|'\\'[bfnrt/\\"])*'"' 410 // Format: '"'([^\x00-\x1f\\\"]|'\\'[bfnrt/\\"])*'"'
392 // Initial position is right after first '"'. 411 // Initial position is right after first '"'.
393 int start = position; 412 int start = position;
394 int char; 413 while (position < source.length) {
395 do { 414 int char = source.codeUnitAt(position);
396 if (position == source.length) { 415 if (char <= BACKSLASH) { // BACKSLASH is larger than QUOTE.
397 fail(start - 1, "Unterminated string"); 416 if (char == BACKSLASH) {
398 } 417 return parseStringWithEscapes(start, position);
399 char = source.codeUnitAt(position); 418 }
400 if (char == QUOTE) { 419 if (char == QUOTE) {
401 listener.handleString(source.substring(start, position)); 420 listener.handleString(source.substring(start, position));
402 return position + 1; 421 return position + 1;
403 } 422 }
404 if (char < SPACE) { 423 if (char < SPACE) {
405 fail(position, "Control character in string"); 424 fail(position, "Control character in string");
425 }
406 } 426 }
407 position++; 427 position++;
408 } while (char != BACKSLASH); 428 }
429 fail(start - 1, "Unterminated string");
430 }
431
432 int parseStringWithEscapes(string, position) {
409 // Backslash escape detected. Collect character codes for rest of string. 433 // Backslash escape detected. Collect character codes for rest of string.
410 int firstEscape = position - 1; 434 int firstEscape = position - 1;
411 List<int> chars = <int>[]; 435 List<int> chars = <int>[];
412 while (true) { 436 while (true) {
413 if (position == source.length) { 437 if (position == source.length) {
414 fail(start - 1, "Unterminated string"); 438 fail(start - 1, "Unterminated string");
415 } 439 }
416 char = source.codeUnitAt(position); 440 char = source.codeUnitAt(position);
417 switch (char) { 441 switch (char) {
418 case CHAR_b: char = BACKSPACE; break; 442 case CHAR_b: char = BACKSPACE; break;
(...skipping 46 matching lines...) Expand 10 before | Expand all | Expand 10 after
465 return position + 1; 489 return position + 1;
466 } 490 }
467 if (char < SPACE) { 491 if (char < SPACE) {
468 fail(position, "Control character in string"); 492 fail(position, "Control character in string");
469 } 493 }
470 } while (char != BACKSLASH); 494 } while (char != BACKSLASH);
471 position++; 495 position++;
472 } 496 }
473 } 497 }
474 498
475 int _handleLiteral(start, position, isDouble) {
476 String literal = source.substring(start, position);
477 // This correctly creates -0 for doubles.
478 num value = (isDouble ? double.parse(literal) : int.parse(literal));
479 listener.handleNumber(value);
480 return position;
481 }
482
483 int parseNumber(int char, int position) { 499 int parseNumber(int char, int position) {
484 // Format: 500 // Format:
485 // '-'?('0'|[1-9][0-9]*)('.'[0-9]+)?([eE][+-]?[0-9]+)? 501 // '-'?('0'|[1-9][0-9]*)('.'[0-9]+)?([eE][+-]?[0-9]+)?
486 int start = position; 502 int start = position;
487 int length = source.length; 503 int length = source.length;
504 int intValue = 0; // Collect int value while parsing.
505 int intSign = 1;
488 bool isDouble = false; 506 bool isDouble = false;
489 if (char == MINUS) { 507 // Break this block when the end of the number literal is reached.
490 position++; 508 // At that time, position points to the next character, and isDouble
491 if (position == length) fail(position, "Missing expected digit"); 509 // is set if the literal contains a decimal point or an exponential.
492 char = source.codeUnitAt(position); 510 parsing: {
493 } 511 if (char == MINUS) {
494 if (char < CHAR_0 || char > CHAR_9) { 512 intSign = -1;
495 fail(position, "Missing expected digit");
496 }
497 if (char == CHAR_0) {
498 position++;
499 if (position == length) return _handleLiteral(start, position, false);
500 char = source.codeUnitAt(position);
501 if (CHAR_0 <= char && char <= CHAR_9) {
502 fail(position);
503 }
504 } else {
505 do {
506 position++;
507 if (position == length) return _handleLiteral(start, position, false);
508 char = source.codeUnitAt(position);
509 } while (CHAR_0 <= char && char <= CHAR_9);
510 }
511 if (char == DECIMALPOINT) {
512 isDouble = true;
513 position++;
514 if (position == length) fail(position, "Missing expected digit");
515 char = source.codeUnitAt(position);
516 if (char < CHAR_0 || char > CHAR_9) fail(position);
517 do {
518 position++;
519 if (position == length) return _handleLiteral(start, position, true);
520 char = source.codeUnitAt(position);
521 } while (CHAR_0 <= char && char <= CHAR_9);
522 }
523 if (char == CHAR_e || char == CHAR_E) {
524 isDouble = true;
525 position++;
526 if (position == length) fail(position, "Missing expected digit");
527 char = source.codeUnitAt(position);
528 if (char == PLUS || char == MINUS) {
529 position++; 513 position++;
530 if (position == length) fail(position, "Missing expected digit"); 514 if (position == length) fail(position, "Missing expected digit");
531 char = source.codeUnitAt(position); 515 char = source.codeUnitAt(position);
532 } 516 }
533 if (char < CHAR_0 || char > CHAR_9) { 517 if (char < CHAR_0 || char > CHAR_9) {
534 fail(position, "Missing expected digit"); 518 fail(position, "Missing expected digit");
535 } 519 }
536 do { 520 if (char == CHAR_0) {
537 position++; 521 position++;
538 if (position == length) return _handleLiteral(start, position, true); 522 if (position == length) break parsing;
539 char = source.codeUnitAt(position); 523 char = source.codeUnitAt(position);
540 } while (CHAR_0 <= char && char <= CHAR_9); 524 if (CHAR_0 <= char && char <= CHAR_9) {
525 fail(position);
526 }
527 } else {
528 do {
529 intValue = intValue * 10 + (char - CHAR_0);
530 position++;
531 if (position == length) break parsing;
532 char = source.codeUnitAt(position);
533 } while (CHAR_0 <= char && char <= CHAR_9);
534 }
535 if (char == DECIMALPOINT) {
536 isDouble = true;
537 position++;
538 if (position == length) fail(position, "Missing expected digit");
539 char = source.codeUnitAt(position);
540 if (char < CHAR_0 || char > CHAR_9) fail(position);
541 do {
542 position++;
543 if (position == length) break parsing;
544 char = source.codeUnitAt(position);
545 } while (CHAR_0 <= char && char <= CHAR_9);
546 }
547 if (char == CHAR_e || char == CHAR_E) {
548 isDouble = true;
549 position++;
550 if (position == length) fail(position, "Missing expected digit");
551 char = source.codeUnitAt(position);
552 if (char == PLUS || char == MINUS) {
553 position++;
554 if (position == length) fail(position, "Missing expected digit");
555 char = source.codeUnitAt(position);
556 }
557 if (char < CHAR_0 || char > CHAR_9) {
558 fail(position, "Missing expected digit");
559 }
560 do {
561 position++;
562 if (position == length) break parsing;
563 char = source.codeUnitAt(position);
564 } while (CHAR_0 <= char && char <= CHAR_9);
565 }
541 } 566 }
542 return _handleLiteral(start, position, isDouble); 567 if (!isDouble) {
568 listener.handleNumber(intSign * intValue);
569 return position;
570 }
571 // Consider whether we can have an int/double.parse that works on part of
572 // a string, to avoid creating the substring.
573 String literal = source.substring(start, position);
574 // This correctly creates -0.0 for doubles.
575 listener.handleNumber(double.parse(literal));
576 return position;
543 } 577 }
544 578
545 void fail(int position, [String message]) { 579 void fail(int position, [String message]) {
546 if (message == null) message = "Unexpected character"; 580 if (message == null) message = "Unexpected character";
547 listener.fail(source, position, message); 581 listener.fail(source, position, message);
548 // If the listener didn't throw, do it here. 582 // If the listener didn't throw, do it here.
549 String slice; 583 String slice;
550 int sliceEnd = position + 20; 584 int sliceEnd = position + 20;
551 if (sliceEnd > source.length) { 585 if (sliceEnd > source.length) {
552 slice = "'${source.substring(position)}'"; 586 slice = "'${source.substring(position)}'";
553 } else { 587 } else {
554 slice = "'${source.substring(position, sliceEnd)}...'"; 588 slice = "'${source.substring(position, sliceEnd)}...'";
555 } 589 }
556 throw new FormatException("Unexpected character at $position: $slice"); 590 throw new FormatException("Unexpected character at $position: $slice");
557 } 591 }
558 } 592 }
559 593
594 /*
595 * JSON Map
596 *
597 * A map with hidden class structure.
598 *
599 * When building maps, don't use a linked hashmap directly.
600 * Instead use a "hidden class" map that keeps the hash structure
601 * in a separate sharable structure representation, and only the
602 * data in the actual map.
603 * Basically, use a map of string->index, and a list of values,
604 * and share the map between all objects with the same structure.
605 *
606 * JSON maps are expected to preserve order, so the hidden classes
607 * maintain the order of the keys.
608 *
609 * The maps will be a delegating map that points to the hidden class
610 * (itself a "map") except that all modifying operations makes the
611 * hidden class replace itself with a linked hash map.
612 */
613
614 /**
615 * A transition cache that shows transitions from one hidden class
616 * to another.
617 */
618 class _JsonTransitionMap {
619 _JsonHiddenClass get hiddenClass;
620 /** See if there is a transition from this class with [key] as key. */
621 _JsonTransitionMap lookup(String key);
622 /** Add a new transition from this class to a new one. */
623 _JsonTransitionMap addAlternative(String key, _JsonTransitionMap targetMap);
624 /** Update the transition map that is linked by a given key. */
625 void update(String key, _JsonTransitionMap map);
626 }
627
628 class _JsonLeafTransitionMap implements _JsonTransitionMap {
629 final _JsonHiddenClass hiddenClass;
630 _JsonLeafTransitionMap(this.hiddenClass);
631 _JsonTransitionMap lookup(String key) => null;
632 _JsonTransitionMap addAlternative(String key, _JsonTransitionMap targetMap) {
633 return new _JsonSingletonTransitionMap(hiddenClass, key, targetMap);
634 }
635 void update(String key, _JsonTransitionMap map) {
636 assert(false); // Must not be called.
637 }
638 }
639
640 class _JsonSingletonTransitionMap implements _JsonTransitionMap {
641 final _JsonHiddenClass hiddenClass;
642 final String key;
643 _JsonTransitionMap next;
644 _JsonSingletonTransitionMap(this.hiddenClass, this.key, this.next);
645
646 _JsonTransitionMap lookup(String key) {
647 if (this.key == key) return next;
648 return null;
649 }
650
651 _JsonTransitionMap addAlternative(String key, _JsonTransitionMap targetMap) {
652 Map mapping = new HashMap();
653 mapping[this.key] = next;
654 mapping[key] = targetMap;
655 return new _JsonMultiTransitionMap(hiddenClass, mapping);
656 }
657
658 void update(String key, _JsonTransitionMap map) {
659 assert(this.key == key);
660 next = map;
661 }
662 }
663
664 class _JsonMultiTransitionMap implements _JsonTransitionMap {
665 final _JsonHiddenClass hiddenClass;
666 final Map mapping;
667 _JsonMultiTransitionMap(this.hiddenClass, this.mapping);
668 _JsonTransitionMap lookup(String key) => mapping[key];
669 _JsonTransitionMap addAlternative(String key, _JsonTransitionMap targetMap) {
670 assert(!mapping.containsKey(key));
671 mapping[key] = targetMap;
672 return this;
673 }
674 void update(String key, _JsonTransitionMap map) {
675 assert(mapping.containsKey(key));
676 mapping[key] = map;
677 }
678 }
679
680 /**
681 * A JSON Object builder that keeps a hidden class for keys and a list of
682 * values.
683 *
684 * When the object is complete, it can be extracted as a `Map` using `toMap`.
685 *
686 */
687 class _JsonObjectBuilder {
688 int transitionsAdded = 0;
689 _JsonTransitionMap parentMap;
690 String previousKey;
691 _JsonTransitionMap currentMap;
692
693 final List values = [];
694
695 _JsonObjectBuilder(this.currentMap);
696
697 Object toMap() {
698 return currentMap.hiddenClass.asMap(values);
699 }
700
701 /**
702 * Add a property to the object being built.
703 *
704 * If the key is already in the object, its value is just overwritten.
705 * Otherwise the hidden class is transitioned to one with the new key
706 * and the result is added at the end.
707 */
708 void add(String key, var value) {
709 int index = currentMap.hiddenClass.lookup(key);
710 if (index >= 0) {
711 values[index] = value;
712 } else {
713 _JsonTransitionMap nextMap = currentMap.lookup(key);
714 if (nextMap == null) {
715 _JsonHiddenClass nextClass = currentMap.hiddenClass.addKey(key);
716 nextMap = new _JsonLeafTransitionMap(nextClass);
717 currentMap = currentMap.addAlternative(key, nextMap);
718 if (parentMap != null) {
719 parentMap.update(previousKey, currentMap);
720 }
721 transitionsAdded++;
722 }
723 parentMap = currentMap;
724 previousKey = key;
725 currentMap = nextMap;
726
727 values.add(value);
728 }
729 }
730 }
731
732 /**
733 * A "hidden class" is a mapping from string key to integer index.
734 *
735 * A map using a class will have a list of values for each index in the
736 * hidden class.
737 */
738 abstract class _JsonHiddenClass {
739 const _JsonHiddenClass();
740 const factory _JsonHiddenClass.empty() = _JsonEmptyHiddenClass;
741 int lookup(String key);
742 Map toMap(List values) {
743 Map map = new LinkedHashMap<String, dynamic>();
744 forEach((k, v) { map[k] = v; });
745 }
746 Iterable<String> get keyIterable;
747 void forEach(List values, void action(String key, var value));
748 int get length;
749
750 _JsonHiddenClass addKey(String key);
751
752 Map<String, dynamic> asMap(List values) {
753 return new _JsonHiddenClassMap(this, values).wrapper;
754 //return new _JsonHiddenClassMap(this, makeListFixedLength(values)).wrapper;
sra1 2014/02/27 04:45:22 Delete comment.
Lasse Reichstein Nielsen 2014/02/27 09:10:38 Acl, yes. It was an attempt to save a little extra
755 }
756 }
757
758 class _JsonEmptyHiddenClass extends _JsonHiddenClass {
759 const _JsonEmptyHiddenClass();
760 int lookup(String key) => -1;
761 Map toMap(List values) => new LinkedHashMap<String, dynamic>();
762 Iterable<String> get keyIterable => new Iterable<String>.generate(0, null);
763 void forEach(List values, void action(String key, var value)) {}
764 int get length => 0;
765 _JsonHiddenClass addKey(String key) {
766 return new _JsonSmallHiddenClass(<String>[key], 1);
767 }
768 }
769
770 /**
771 * A hidden class for a JSON object that maps keys to value indices.
772 *
773 * This is intended for small objects. Looking up a key is done using
774 * linear search.
775 */
776 class _JsonSmallHiddenClass extends _JsonHiddenClass {
777 final List keys;
778 final int length; // `keys` may contain more elements than length.
779 _JsonSmallHiddenClass(this.keys, this.length);
780 int lookup(String key) {
781 for (int i = 0; i < length; i++) {
782 if (keys[i] == key) return i;
783 }
784 return -1;
785 }
786
787 Iterable<String> get keyIterable =>
788 new SubListIterable<String>(keys, 0, length);
789
790 void forEach(List values, void action(String key, var value)) {
791 for (int i = 0; i < length; i++) {
792 action(keys[i], values[i]);
793 }
794 }
795
796 _JsonHiddenClass addKey(String key) {
797 const int MAX_SMALL_CLASS = 4;
798 if (length == MAX_SMALL_CLASS) {
799 Map map = new LinkedHashMap<String,int>();
800 for (int i = 0; i < length; i++) map[keys[i]] = i;
801 map[key] = length;
802 return new _JsonMediumHiddenClass(map, length + 1);
803 }
804 // TODO(lrn): Add an implementation for larger key lists that doesn't use
805 // linear search. Switch to using that implementation here if length is
806 // above a threshold.
807 var newKeys;
808 if (keys.length > length) {
809 newKeys = keys.sublist(0, length);
810 } else {
811 newKeys = keys;
812 }
813 newKeys.add(key);
814 return new _JsonSmallHiddenClass(newKeys, length + 1);
815 }
816 }
817
818 /**
819 * A hidden class that uses a [LinkedHashMap] to store the key-to-index mapping.
820 *
821 * This introduces the same overhead as a normal map, so if the hidden class
822 * is only used once, it's just an overhead.
823 */
824 class _JsonMediumHiddenClass extends _JsonHiddenClass {
825 final LinkedHashMap<String, int> keys;
826 final int length; // `keys` may contain more elements than length.
827 _JsonMediumHiddenClass(this.keys, this.length);
828
829 int lookup(String key) {
830 int index = keys[key];
831 if (index == null || index >= length) return -1;
832 return index;
833 }
834
835 Iterable<String> get keyIterable => keys.keys.take(length);
836
837 void forEach(List values, void action(String key, var value)) {
838 int i = 0;
839 assert(length != 0);
840 for (String key in keys.keys) {
841 action(key, values[i]);
842 i++;
843 if (i == length) break;
844 }
845 }
846
847 _JsonHiddenClass addKey(String key) {
848 // TODO(lrn): Add an implementation for larger key lists that doesn't use
849 // linear search. Switch to using that implementation here if length is
850 // above a threshold.
851 var newKeys;
852 if (keys.length > length) {
853 newKeys = new HashMap<String,int>();
854 keys.forEach((String key, int value) {
855 if (value < length) newKeys[key] = value;
856 });
857 } else {
858 newKeys = keys;
859 }
860 newKeys[key] = length;
861 return new _JsonMediumHiddenClass(newKeys, length + 1);
862 }
863 }
864
865
866 /**
867 * A map based on a hidden class.
868 *
869 * The hidden class translates string keys to integer indices, and the
870 * values are stored at those indices in [values].
871 * The idea is that the hidden class can be shared between multiple similar
872 * objects, reducing the memory overhead of the map created by decoding a
873 * JSON Object. This only works when there are more than one object with
874 * the same structure.
875 *
876 *
877 * This object is hidden behind the [_JsonMapWrapper].
878 *
879 * Any attempt to write to the map will make it convert itself to a
880 * [LinkedHashMap] with the same values, and make the wrapper delegate to that
881 * map instead.
882 */
883 class _JsonHiddenClassMap implements Map {
884 final _JsonHiddenClass hiddenClass;
885 final List mapValues;
886 _JsonMapWrapper wrapper;
887
888 _JsonHiddenClassMap(this.hiddenClass, this.mapValues) {
889 wrapper = new _JsonMapWrapper(this);
890 }
891
892 Map convertToMap() {
893 Map map = hiddenClass.toMap(mapValues);
894 wapper._delegate = map;
895 return map;
896 }
897
898 bool containsValue(Object value) {
899 for (int i = 0; i < mapValues.length; i++) {
900 if (mapValues[i] == value) return true;
901 }
902 return false;
903 }
904
905 bool containsKey(Object key) => hiddenClass.lookup(key) >= 0;
906
907 operator [](Object key) {
908 int index = hiddenClass.lookup(key);
909 if (index < 0) return null;
910 return mapValues[index];
911 }
912
913 void operator []=(String key, var value) {
914 convertToMap()[key] = value;
915 }
916
917 putIfAbsent(String key, ifAbsent()) {
918 return convertToMap().putIfAbsent(key, ifAbsent);
919 }
920
921 void addAll(Map<String, dynamic> other) {
922 convertToMap().addAll(other);
923 }
924
925 remove(Object key) {
926 convertToMap().remove(key);
927 }
928
929 void clear() { wrapper._delegate = new LinkedHashMap<String, dynamic>(); }
930
931 void forEach(void f(String key, var value)) {
932 hiddenClass.forEach(mapValues, f);
933 }
934
935 Iterable<String> get keys => hiddenClass.keyIterable;
936
937 Iterable get valueIterator =>
938 new SubListIterable(mapValues, 0, values.length);
sra1 2014/02/27 04:45:22 If we modify the map while iterating the keys or v
Lasse Reichstein Nielsen 2014/02/27 09:10:38 Ack. Concurrent modification. Why don't we just di
939
940 int get length => mapValues.length;
941
942 bool get isEmpty => mapValues.length == 0;
943
944 bool get isNotEmpty => mapValues.length != 0;
945
946 String toString() => Maps.mapToString(this);
947 }
948
949 /**
950 * Delegating map wrapper.
951 *
952 * Used to have a "copy on write" map implementation optimized for reading,
953 * which converts itself to a [LinkedHashMap] on any write operation by
954 * creating the hash map and writing it to [_delegate].
955 *
956 * This is the only object that the JSON decoder's user sees.
sra1 2014/02/26 21:56:06 Interesting trick. Can you think of a way to make
Lasse Reichstein Nielsen 2014/02/27 09:10:38 I am considering adding a public static setter fun
957 */
958 class _JsonMapWrapper implements Map<String, dynamic> {
sra1 2014/02/26 21:56:06 The original map was created with currentContaine
Lasse Reichstein Nielsen 2014/02/27 09:10:38 It wasn't the intent, because I hadn't noticed tha
959 Map _delegate;
960
961 _JsonMapWrapper(this._delegate);
962
963 bool containsValue(Object value) => _delegate.containsValue(value);
964
965 bool containsKey(Object key) => _delegate.containsKey(key);
966
967 operator [](Object key) => _delegate[key];
968
969 void operator []=(String key, var value) { _delegate[key] = value; }
970
971 putIfAbsent(String key, ifAbsent()) => _delegate.putIfAbsent(key, ifAbsent);
972
973 void addAll(Map<String, dynamic> other) => _delegate.addAll(other);
974
975 remove(Object key) => _delegate.remove(key);
976
977 void clear() { _delegate.clear(); }
sra1 2014/02/26 21:56:06 FYI you can use => for these too. I guess in poorl
Lasse Reichstein Nielsen 2014/02/27 09:10:38 I prefre (strongly) to not use "=>" for void funct
978
979 void forEach(void f(String key, var value)) { _delegate.forEach(f); }
980
981 Iterable<String> get keys => _delegate.keys;
982
983 Iterable get values => _delegate.values;
984
985 int get length => _delegate.length;
986
987 bool get isEmpty => _delegate.isEmpty;
988
989 bool get isNotEmpty => _delegate.isNotEmpty;
990
991 String toString() => _delegate.toString();
992 }
993
560 // UTF-8 conversion. 994 // UTF-8 conversion.
561 995
562 patch class _Utf8Encoder { 996 patch class _Utf8Encoder {
563 /* patch */ static List<int> _createBuffer(int size) => new Uint8List(size); 997 /* patch */ static List<int> _createBuffer(int size) => new Uint8List(size);
564 } 998 }
999
OLDNEW
« no previous file with comments | « no previous file | sdk/lib/core/iterable.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698