Chromium Code Reviews| OLD | NEW |
|---|---|
| 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, IterableBase; | |
| 7 import "dart:_internal" show SubListIterable, EfficientLength; | |
| 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 Loading... | |
| 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) |
|
srdjan
2014/03/03 15:12:54
Curly braces missing?
floitsch
2014/03/03 15:36:44
something's not right here.
| |
| 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 |
|
srdjan
2014/03/03 15:12:54
?
floitsch
2014/03/03 15:36:44
?
| |
| 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 392 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 550 int sliceEnd = position + 20; | 569 int sliceEnd = position + 20; |
| 551 if (sliceEnd > source.length) { | 570 if (sliceEnd > source.length) { |
| 552 slice = "'${source.substring(position)}'"; | 571 slice = "'${source.substring(position)}'"; |
| 553 } else { | 572 } else { |
| 554 slice = "'${source.substring(position, sliceEnd)}...'"; | 573 slice = "'${source.substring(position, sliceEnd)}...'"; |
| 555 } | 574 } |
| 556 throw new FormatException("Unexpected character at $position: $slice"); | 575 throw new FormatException("Unexpected character at $position: $slice"); |
| 557 } | 576 } |
| 558 } | 577 } |
| 559 | 578 |
| 579 /* | |
| 580 * JSON Map | |
| 581 * | |
| 582 * A map with hidden class structure. | |
| 583 * | |
| 584 * When building maps, don't use a linked hashmap directly. | |
| 585 * Instead use a "hidden class" map that keeps the hash structure | |
| 586 * in a separate sharable structure representation, and only the | |
| 587 * data in the actual map. | |
| 588 * Basically, use a map of string->index, and a list of values, | |
| 589 * and share the map between all objects with the same structure. | |
| 590 * | |
| 591 * JSON maps are expected to preserve order, so the hidden classes | |
| 592 * maintain the order of the keys. | |
| 593 * | |
| 594 * The maps will be a delegating map that points to the hidden class | |
| 595 * (itself a "map") except that all modifying operations makes the | |
| 596 * hidden class replace itself with a linked hash map. | |
| 597 */ | |
| 598 | |
| 599 /** | |
| 600 * A transition cache that shows transitions from one hidden class | |
| 601 * to another. | |
| 602 */ | |
| 603 class _JsonTransitionMap { | |
| 604 _JsonHiddenClass get hiddenClass; | |
| 605 /** See if there is a transition from this class with [key] as key. */ | |
| 606 _JsonTransitionMap lookup(String key); | |
| 607 /** Add a new transition from this class to a new one. */ | |
| 608 _JsonTransitionMap addAlternative(String key, _JsonTransitionMap targetMap); | |
| 609 /** Update the transition map that is linked by a given key. */ | |
| 610 void update(String key, _JsonTransitionMap map); | |
| 611 } | |
| 612 | |
| 613 class _JsonLeafTransitionMap implements _JsonTransitionMap { | |
| 614 final _JsonHiddenClass hiddenClass; | |
| 615 _JsonLeafTransitionMap(this.hiddenClass); | |
| 616 _JsonTransitionMap lookup(String key) => null; | |
| 617 _JsonTransitionMap addAlternative(String key, _JsonTransitionMap targetMap) { | |
| 618 return new _JsonSingletonTransitionMap(hiddenClass, key, targetMap); | |
| 619 } | |
| 620 void update(String key, _JsonTransitionMap map) { | |
| 621 assert(false); // Must not be called. | |
| 622 } | |
| 623 } | |
| 624 | |
| 625 class _JsonSingletonTransitionMap implements _JsonTransitionMap { | |
| 626 final _JsonHiddenClass hiddenClass; | |
| 627 final String key; | |
| 628 _JsonTransitionMap next; | |
| 629 _JsonSingletonTransitionMap(this.hiddenClass, this.key, this.next); | |
| 630 | |
| 631 _JsonTransitionMap lookup(String key) { | |
| 632 if (this.key == key) return next; | |
| 633 return null; | |
| 634 } | |
| 635 | |
| 636 _JsonTransitionMap addAlternative(String key, _JsonTransitionMap targetMap) { | |
| 637 Map mapping = new HashMap(); | |
| 638 mapping[this.key] = next; | |
| 639 mapping[key] = targetMap; | |
| 640 return new _JsonMultiTransitionMap(hiddenClass, mapping); | |
| 641 } | |
| 642 | |
| 643 void update(String key, _JsonTransitionMap map) { | |
| 644 assert(this.key == key); | |
| 645 next = map; | |
| 646 } | |
| 647 } | |
| 648 | |
| 649 class _JsonMultiTransitionMap implements _JsonTransitionMap { | |
| 650 final _JsonHiddenClass hiddenClass; | |
| 651 final Map mapping; | |
| 652 _JsonMultiTransitionMap(this.hiddenClass, this.mapping); | |
| 653 _JsonTransitionMap lookup(String key) => mapping[key]; | |
| 654 _JsonTransitionMap addAlternative(String key, _JsonTransitionMap targetMap) { | |
| 655 assert(!mapping.containsKey(key)); | |
| 656 mapping[key] = targetMap; | |
| 657 return this; | |
| 658 } | |
| 659 void update(String key, _JsonTransitionMap map) { | |
| 660 assert(mapping.containsKey(key)); | |
| 661 mapping[key] = map; | |
| 662 } | |
| 663 } | |
| 664 | |
| 665 /** | |
| 666 * A JSON Object builder that keeps a hidden class for keys and a list of | |
| 667 * values. | |
| 668 * | |
| 669 * When the object is complete, it can be extracted as a `Map` using `toMap`. | |
| 670 * | |
| 671 */ | |
| 672 class _JsonObjectBuilder { | |
| 673 int transitionsAdded = 0; | |
| 674 _JsonTransitionMap parentMap; | |
| 675 String previousKey; | |
| 676 _JsonTransitionMap currentMap; | |
| 677 | |
| 678 final List values = []; | |
| 679 | |
| 680 _JsonObjectBuilder(this.currentMap); | |
| 681 | |
| 682 Object toMap() { | |
| 683 return currentMap.hiddenClass.asMap(values); | |
| 684 } | |
| 685 | |
| 686 /** | |
| 687 * Add a property to the object being built. | |
| 688 * | |
| 689 * If the key is already in the object, its value is just overwritten. | |
| 690 * Otherwise the hidden class is transitioned to one with the new key | |
| 691 * and the result is added at the end. | |
| 692 */ | |
| 693 void add(String key, var value) { | |
| 694 int index = currentMap.hiddenClass.lookup(key); | |
| 695 if (index >= 0) { | |
| 696 values[index] = value; | |
| 697 } else { | |
| 698 _JsonTransitionMap nextMap = currentMap.lookup(key); | |
| 699 if (nextMap == null) { | |
| 700 _JsonHiddenClass nextClass = currentMap.hiddenClass.addKey(key); | |
| 701 nextMap = new _JsonLeafTransitionMap(nextClass); | |
| 702 currentMap = currentMap.addAlternative(key, nextMap); | |
| 703 if (parentMap != null) { | |
| 704 parentMap.update(previousKey, currentMap); | |
| 705 } | |
| 706 transitionsAdded++; | |
| 707 } | |
| 708 parentMap = currentMap; | |
| 709 previousKey = key; | |
| 710 currentMap = nextMap; | |
| 711 | |
| 712 values.add(value); | |
| 713 } | |
| 714 } | |
| 715 } | |
| 716 | |
| 717 /** | |
| 718 * A "hidden class" is a mapping from string key to integer index. | |
| 719 * | |
| 720 * A map using a class will have a list of values for each index in the | |
| 721 * hidden class. | |
| 722 */ | |
| 723 abstract class _JsonHiddenClass { | |
| 724 const _JsonHiddenClass(); | |
| 725 const factory _JsonHiddenClass.empty() = _JsonEmptyHiddenClass; | |
| 726 int lookup(String key); | |
| 727 Map toMap(List values) { | |
| 728 Map map = new LinkedHashMap<String, dynamic>(); | |
| 729 addToMap(map, values); | |
| 730 return map; | |
| 731 } | |
| 732 Iterator<String> get keyIterator; | |
| 733 void addToMap(Map<String,dynamic> map, List values); | |
| 734 int get length; | |
| 735 | |
| 736 _JsonHiddenClass addKey(String key); | |
| 737 | |
| 738 Map<String, dynamic> asMap(List values) { | |
| 739 return new _JsonHiddenClassMap(this, values).wrapper; | |
| 740 } | |
| 741 } | |
| 742 | |
| 743 class _JsonEmptyHiddenClass extends _JsonHiddenClass { | |
| 744 const _JsonEmptyHiddenClass(); | |
| 745 int lookup(String key) => -1; | |
| 746 Map toMap(List values) => new LinkedHashMap<String, dynamic>(); | |
| 747 Iterator<String> get keyIterator => const[]; | |
| 748 void addToMap(Map map, List values) {} | |
| 749 int get length => 0; | |
| 750 _JsonHiddenClass addKey(String key) { | |
| 751 return new _JsonSmallHiddenClass(<String>[key], 1); | |
| 752 } | |
| 753 } | |
| 754 | |
| 755 /** | |
| 756 * A hidden class for a JSON object that maps keys to value indices. | |
| 757 * | |
| 758 * This is intended for small objects. Looking up a key is done using | |
| 759 * linear search. | |
| 760 */ | |
| 761 class _JsonSmallHiddenClass extends _JsonHiddenClass { | |
| 762 final List keys; | |
| 763 final int length; // `keys` may contain more elements than length. | |
|
floitsch
2014/03/03 15:36:44
Point to `addKey` where we add a new key for the n
| |
| 764 _JsonSmallHiddenClass(this.keys, this.length); | |
| 765 int lookup(String key) { | |
| 766 for (int i = 0; i < length; i++) { | |
| 767 if (keys[i] == key) return i; | |
| 768 } | |
| 769 return -1; | |
| 770 } | |
| 771 | |
| 772 Iterator<String> get keyIterator => keys.take(length).iterator; | |
| 773 | |
| 774 void addToMap(Map map, List values) { | |
| 775 for (int i = 0; i < length; i++) { | |
| 776 map[keys[i]] = values[i]; | |
| 777 } | |
| 778 } | |
| 779 | |
| 780 _JsonHiddenClass addKey(String key) { | |
| 781 const int MAX_SMALL_CLASS = 4; | |
|
floitsch
2014/03/03 15:36:44
I would go higher, but that's just my gut-reaction
| |
| 782 if (length == MAX_SMALL_CLASS) { | |
| 783 Map map = new LinkedHashMap<String,int>(); | |
| 784 for (int i = 0; i < length; i++) map[keys[i]] = i; | |
| 785 map[key] = length; | |
| 786 return new _JsonMediumHiddenClass(map, length + 1); | |
| 787 } | |
| 788 // TODO(lrn): Add an implementation for larger key lists that doesn't use | |
| 789 // linear search. Switch to using that implementation here if length is | |
| 790 // above a threshold. | |
| 791 var newKeys; | |
| 792 if (keys.length > length) { | |
| 793 newKeys = keys.sublist(0, length); | |
| 794 } else { | |
| 795 newKeys = keys; | |
|
floitsch
2014/03/03 15:36:44
Add comment that we are sharing the list here.
| |
| 796 } | |
| 797 newKeys.add(key); | |
| 798 return new _JsonSmallHiddenClass(newKeys, length + 1); | |
| 799 } | |
| 800 } | |
| 801 | |
| 802 /** | |
| 803 * A hidden class that uses a [LinkedHashMap] to store the key-to-index mapping. | |
| 804 * | |
| 805 * This introduces the same overhead as a normal map, so if the hidden class | |
| 806 * is only used once, it's just an overhead. | |
| 807 */ | |
| 808 class _JsonMediumHiddenClass extends _JsonHiddenClass { | |
| 809 final LinkedHashMap<String, int> keys; | |
| 810 final int length; // `keys` may contain more elements than length. | |
|
floitsch
2014/03/03 15:36:44
ditto. point to `addKey`.
| |
| 811 _JsonMediumHiddenClass(this.keys, this.length); | |
| 812 | |
| 813 int lookup(String key) { | |
| 814 int index = keys[key]; | |
| 815 if (index == null || index >= length) return -1; | |
| 816 return index; | |
| 817 } | |
| 818 | |
| 819 Iterator<String> get keyIterator => keys.keys.take(length).iterator; | |
| 820 | |
| 821 void addToMap(Map map, List values) { | |
| 822 int i = 0; | |
| 823 assert(length != 0); | |
| 824 for (String key in keys.keys) { | |
| 825 map[key] = values[i]; | |
| 826 i++; | |
| 827 if (i == length) break; | |
| 828 } | |
| 829 } | |
| 830 | |
| 831 _JsonHiddenClass addKey(String key) { | |
| 832 // TODO(lrn): Add an implementation for larger key lists that doesn't use | |
| 833 // linear search. Switch to using that implementation here if length is | |
| 834 // above a threshold. | |
| 835 var newKeys; | |
| 836 if (keys.length > length) { | |
| 837 newKeys = new HashMap<String,int>(); | |
| 838 keys.forEach((String key, int value) { | |
| 839 if (value < length) newKeys[key] = value; | |
| 840 }); | |
| 841 } else { | |
| 842 newKeys = keys; | |
| 843 } | |
| 844 newKeys[key] = length; | |
| 845 return new _JsonMediumHiddenClass(newKeys, length + 1); | |
| 846 } | |
| 847 } | |
| 848 | |
| 849 | |
| 850 /** | |
| 851 * A map based on a hidden class. | |
| 852 * | |
| 853 * The hidden class translates string keys to integer indices, and the | |
| 854 * values are stored at those indices in [values]. | |
| 855 * The idea is that the hidden class can be shared between multiple similar | |
| 856 * objects, reducing the memory overhead of the map created by decoding a | |
| 857 * JSON Object. This only works when there are more than one object with | |
| 858 * the same structure. | |
| 859 * | |
| 860 * This object is hidden behind the [_JsonMapWrapper]. | |
| 861 * | |
| 862 * Any attempt to write to the map will make it convert itself to a | |
| 863 * [LinkedHashMap] with the same values, and make the wrapper delegate to that | |
| 864 * map instead. | |
| 865 */ | |
| 866 class _JsonHiddenClassMap implements Map { | |
| 867 final _JsonHiddenClass hiddenClass; | |
| 868 final List mapValues; | |
| 869 bool modified = false; | |
| 870 _JsonMapWrapper wrapper; | |
| 871 | |
| 872 _JsonHiddenClassMap(this.hiddenClass, this.mapValues) { | |
| 873 wrapper = new _JsonMapWrapper(this); | |
| 874 } | |
| 875 | |
| 876 Map convertToMap() { | |
| 877 modified = true; | |
| 878 Map map = hiddenClass.toMap(mapValues); | |
| 879 wrapper._delegate = map; | |
| 880 return map; | |
| 881 } | |
| 882 | |
| 883 bool containsValue(Object value) { | |
| 884 for (int i = 0; i < mapValues.length; i++) { | |
| 885 if (mapValues[i] == value) return true; | |
| 886 } | |
| 887 return false; | |
| 888 } | |
| 889 | |
| 890 bool containsKey(Object key) => hiddenClass.lookup(key) >= 0; | |
| 891 | |
| 892 operator [](Object key) { | |
| 893 int index = hiddenClass.lookup(key); | |
| 894 if (index < 0) return null; | |
| 895 return mapValues[index]; | |
| 896 } | |
| 897 | |
| 898 void operator []=(String key, var value) { | |
| 899 int index = hiddenClass.lookup(key); | |
| 900 if (index >= 0) { | |
| 901 mapValues[index] = value; | |
| 902 } else { | |
| 903 convertToMap()[key] = value; | |
| 904 } | |
| 905 } | |
| 906 | |
| 907 putIfAbsent(String key, ifAbsent()) { | |
| 908 int index = hiddenClass.lookup(key); | |
| 909 if (index >= 0) { | |
| 910 return mapValues[index]; | |
| 911 } | |
| 912 return convertToMap().putIfAbsent(key, ifAbsent); | |
| 913 } | |
| 914 | |
| 915 void addAll(Map<String, dynamic> other) { | |
| 916 Iterator values = other.iterator; | |
|
floitsch
2014/03/03 15:36:44
Maps don't have iterators.
| |
| 917 if (!values.moveNext()) returm | |
|
floitsch
2014/03/03 15:36:44
You could also just ask, if other.length == 0, con
| |
| 918 Map map = convertToMap(); | |
| 919 do { | |
| 920 map.add(values.current); | |
|
floitsch
2014/03/03 15:36:44
map doesn't have "add".
| |
| 921 } while (values.moveNext()); | |
| 922 } | |
| 923 | |
| 924 remove(Object key) { | |
| 925 int index = hiddenClass.lookup(key); | |
| 926 if (index < 0) return null; | |
| 927 return convertToMap().remove(key); | |
| 928 } | |
| 929 | |
| 930 void clear() { | |
| 931 modified = true; | |
| 932 wrapper._delegate = new LinkedHashMap<String, dynamic>(); | |
| 933 } | |
| 934 | |
| 935 void forEach(void f(String key, var value)) { | |
| 936 Iterator keys = hiddenClass.keyIterator; | |
| 937 for (int i = 0; i < mapValues.length; i++) { | |
| 938 keys.moveNext(); | |
| 939 String key = keys.current; | |
| 940 f(key, mapValues[i]); | |
| 941 if (modified) throw new ConcurrentModificationError(wrapper); | |
| 942 } | |
| 943 } | |
| 944 | |
| 945 Iterable<String> get keys => new _JsonHiddenClassMapKeyIterable(this); | |
| 946 | |
| 947 Iterable get valueIterator => new _JsonHiddenClassMapValueIterable(this); | |
| 948 | |
| 949 int get length => mapValues.length; | |
| 950 | |
| 951 bool get isEmpty => mapValues.length == 0; | |
| 952 | |
| 953 bool get isNotEmpty => mapValues.length != 0; | |
| 954 | |
| 955 String toString() => Maps.mapToString(this); | |
| 956 } | |
| 957 | |
| 958 abstract class _JsonHiddenClassMapIterable<T> extends IterableBase<T> | |
| 959 implements EfficientLength { | |
| 960 _JsonHiddenClassMap _map; | |
| 961 _JsonHiddenClassMapIterable(this._map); | |
| 962 int get length => _map.length; | |
| 963 bool get isEmpty => _map.isEmpty; | |
| 964 bool get isNotEmpty => _map.isNotEmpty; | |
| 965 } | |
| 966 | |
| 967 class _JsonHiddenClassMapKeyIterable | |
| 968 extends _JsonHiddenClassMapIterable<String> { | |
| 969 _JsonHiddenClassMapKeyIterable(_JsonHiddenClassMap map) : super(map); | |
| 970 Iterator get iterator => new _JsonHiddenClassMapKeyIterator(_map); | |
| 971 } | |
| 972 | |
| 973 class _JsonHiddenClassMapValueIterable extends _JsonHiddenClassMapIterable { | |
| 974 _JsonHiddenClassMapValueIterable(_JsonHiddenClassMap map) : super(map); | |
| 975 Iterator get iterator => new _JsonHiddenClassMapValueIterator(_map); | |
| 976 } | |
| 977 | |
| 978 class _JsonHiddenClassMapKeyIterator implements Iterator<String> { | |
| 979 Iterator _keys; | |
| 980 _JsonHiddenClassMap _map; | |
| 981 _JsonHiddenClassMapKeyIterator(_JsonHiddenClassMap map) | |
| 982 : _map = map, _keys = map.hiddenClass.keyIterator; | |
| 983 bool moveNext() { | |
| 984 if (_map.modified) throw new ConcurrentModificationError(_map.wrapper); | |
| 985 return _keys.moveNext(); | |
| 986 } | |
| 987 String get current => _keys.current; | |
| 988 } | |
| 989 | |
| 990 class _JsonHiddenClassMapValueIterator implements Iterator { | |
| 991 int _index = 0; | |
| 992 var _current; | |
| 993 _JsonHiddenClassMap _map; | |
| 994 _JsonHiddenClassMapKeyIterator(_JsonHiddenClassMap map) | |
| 995 : _map = map; | |
| 996 bool moveNext() { | |
| 997 if (_map.modified) throw new ConcurrentModificationError(_map.wrapper); | |
| 998 if (_index == _map.mapValues.length) { | |
| 999 _current = false; | |
| 1000 return false; | |
| 1001 } | |
| 1002 _current = _map.mapValues[_index++]; | |
| 1003 return true; | |
| 1004 } | |
| 1005 get current => _current; | |
| 1006 } | |
| 1007 | |
| 1008 /** | |
| 1009 * Delegating map wrapper. | |
| 1010 * | |
| 1011 * Used to have a "copy on write" map implementation optimized for reading, | |
|
floitsch
2014/03/03 15:36:44
s/Used to have/Has/
| |
| 1012 * which converts itself to a [LinkedHashMap] on any write operation by | |
| 1013 * creating the hash map and writing it to [_delegate]. | |
| 1014 * | |
| 1015 * This is the only object that the JSON decoder's user sees. | |
| 1016 */ | |
| 1017 class _JsonMapWrapper implements Map<String, dynamic> { | |
| 1018 Map _delegate; | |
| 1019 | |
| 1020 _JsonMapWrapper(this._delegate); | |
| 1021 | |
| 1022 bool containsValue(Object value) => _delegate.containsValue(value); | |
| 1023 | |
| 1024 bool containsKey(Object key) => _delegate.containsKey(key); | |
| 1025 | |
| 1026 operator [](Object key) => _delegate[key]; | |
| 1027 | |
| 1028 void operator []=(String key, var value) { _delegate[key] = value; } | |
| 1029 | |
| 1030 putIfAbsent(String key, ifAbsent()) => _delegate.putIfAbsent(key, ifAbsent); | |
| 1031 | |
| 1032 void addAll(Map<String, dynamic> other) => _delegate.addAll(other); | |
| 1033 | |
| 1034 remove(Object key) => _delegate.remove(key); | |
| 1035 | |
| 1036 void clear() { _delegate.clear(); } | |
| 1037 | |
| 1038 void forEach(void f(String key, var value)) { _delegate.forEach(f); } | |
| 1039 | |
| 1040 Iterable<String> get keys => _delegate.keys; | |
| 1041 | |
| 1042 Iterable get values => _delegate.values; | |
| 1043 | |
| 1044 int get length => _delegate.length; | |
| 1045 | |
| 1046 bool get isEmpty => _delegate.isEmpty; | |
| 1047 | |
| 1048 bool get isNotEmpty => _delegate.isNotEmpty; | |
| 1049 | |
| 1050 String toString() => _delegate.toString(); | |
| 1051 } | |
| 1052 | |
| 560 // UTF-8 conversion. | 1053 // UTF-8 conversion. |
| 561 | 1054 |
| 562 patch class _Utf8Encoder { | 1055 patch class _Utf8Encoder { |
| 563 /* patch */ static List<int> _createBuffer(int size) => new Uint8List(size); | 1056 /* patch */ static List<int> _createBuffer(int size) => new Uint8List(size); |
| 564 } | 1057 } |
| OLD | NEW |