Chromium Code Reviews| Index: lib/yaml/yaml_map.dart |
| diff --git a/lib/yaml/yaml_map.dart b/lib/yaml/yaml_map.dart |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..becae905545e8a05cc101db4e47a2dcc4dc5b5c9 |
| --- /dev/null |
| +++ b/lib/yaml/yaml_map.dart |
| @@ -0,0 +1,137 @@ |
| +// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| +// for details. All rights reserved. Use of this source code is governed by a |
| +// BSD-style license that can be found in the LICENSE file. |
| + |
| +/** |
| + * This class wraps behaves almost identically to the normal Dart Map |
|
Bob Nystrom
2012/04/20 20:28:55
"wraps behaves"?
nweiz
2012/04/23 23:06:33
Fixed.
|
| + * implementation, with the following differences: |
| + * |
| + * * It allows null, NaN, boolean, list, and map keys. |
| + * * It is itself Hashable. |
| + * * It defines `==` structurally. That is, `yamlMap1 == yamlMap2` if they have |
| + * the same contents. |
| + */ |
| +class YamlMap implements Map, Hashable { |
| + Map _map; |
| + |
| + YamlMap() : _map = new Map(); |
| + |
| + YamlMap.from(Map map) : _map = new Map.from(map); |
| + |
| + bool containsValue(value) => _map.containsValue(value); |
| + bool containsKey(key) => _map.containsKey(_wrapKey(key)); |
| + operator [](key) => _map[_wrapKey(key)]; |
| + operator []=(key, value) { _map[_wrapKey(key)] = value; } |
| + putIfAbsent(key, ifAbsent()) => _map.putIfAbsent(_wrapKey(key), ifAbsent); |
| + remove(key) => _map.remove(_wrapKey(key)); |
| + void clear() => _map.clear(); |
| + void forEach(void f(key, value)) => |
| + _map.forEach((k, v) => f(_unwrapKey(k), v)); |
| + Collection getKeys() => _map.getKeys().map(_unwrapKey); |
| + Collection getValues() => _map.getValues(); |
| + int get length() => _map.length; |
| + bool isEmpty() => _map.isEmpty(); |
| + String toString() => _map.toString(); |
| + |
| + int hashCode() => _hashCode(_map); |
| + |
| + bool operator ==(other) { |
| + if (other is! YamlMap) return false; |
| + return _mapEquals(this, other); |
| + } |
| + |
| + /** Wraps an object for use as a key in the map. */ |
| + _wrapKey(obj) { |
| + if (obj != null && obj is! bool && obj is! List && |
| + (obj is! double || !obj.isNan()) && |
| + (obj is! Map || obj is YamlMap)) { |
| + return obj; |
| + } |
| + return new _WrappedHashKey._(obj); |
| + } |
| + |
| + /** Unwraps an object that was used as a key in the map. */ |
| + _unwrapKey(obj) => obj is _WrappedHashKey ? obj.value : obj; |
| +} |
| + |
| +/** |
| + * A class for wrapping normally-unhashable objects that are being used as keys |
| + * in a YamlMap. |
| + */ |
| +class _WrappedHashKey implements Hashable { |
| + var value; |
| + |
| + _WrappedHashKey._(this.value); |
| + |
| + int hashCode() => _hashCode(value); |
| + |
| + String toString() => value.toString(); |
| + |
| + /** This is defined as both values being structurally equal. */ |
| + bool operator ==(other) { |
| + if (other is! _WrappedHashKey) return false; |
| + _WrappedHashKey otherWrappedKey = other; |
| + return _deepEquals(this.value, other.value); |
| + } |
| +} |
| + |
| +/** |
| + * Returns the hash code for [obj]. This includes null, true, false, maps, and |
| + * lists. |
| + */ |
| +int _hashCode(obj) { |
|
Bob Nystrom
2012/04/20 20:28:55
This will do Bad Things with cyclic structures. Do
nweiz
2012/04/23 23:06:33
I've fixed hashCode and deepEquals to work properl
|
| + if (obj == null) return 0; |
| + if (obj == true) return 1; |
| + if (obj == false) return 2; |
| + if (obj is Map) return _hashCode(obj.getKeys()) ^ _hashCode(obj.getValues()); |
| + if (obj is List) { |
| + // This is probably a really bad hash function, but presumably we'll get this |
| + // in the standard library before it actually matters. |
| + int hash = 0; |
| + for (var e in obj) { |
| + hash ^= _hashCode(e); |
| + } |
| + return hash; |
| + } |
| + return obj.hashCode(); |
| +} |
| + |
| +/** Returns whether [list1] and [list2] are structurally equal. */ |
| +bool _listEquals(List list1, List list2) { |
| + if (list1.length != list2.length) return false; |
| + |
| + for (var i = 0; i < list1.length; i++) { |
| + if (!_deepEquals(list1[i], list2[i])) return false; |
| + } |
| + |
| + return true; |
| +} |
| + |
| +/** Returns whether [map1] and [map2] are structurally equal. */ |
| +bool _mapEquals(Map map1, Map map2) { |
| + if (map1.length != map2.length) return false; |
| + |
| + for (var key in map1.getKeys()) { |
| + if (!map2.containsKey(key)) return false; |
| + if (!_deepEquals(map1[key], map2[key])) return false; |
| + } |
| + |
| + return true; |
| +} |
| + |
| +/** |
| + * Returns whether two doubles are equivalent. This differs from `d1 == d2` in |
| + * that it considers NaN to be equal to itself. |
| + */ |
| +bool _doubleEquals(double d1, double d2) { |
| + if (d1.isNaN() && d2.isNaN()) return true; |
| + return d1 == d2; |
| +} |
| + |
| +/** Returns whether two objects are structurally equivalent. */ |
| +bool _deepEquals(obj1, obj2) { |
| + if (obj1 is List && obj2 is List) return _listEquals(obj1, obj2); |
| + if (obj1 is Map && obj2 is Map) return _mapEquals(obj1, obj2); |
| + if (obj1 is double && obj2 is double) return _doubleEquals(obj1, obj2); |
| + return obj1 == obj2; |
| +} |