// Copyright (c) 2020, 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. import 'dart:collection'; import 'package:collection/collection.dart'; import 'package:yaml/yaml.dart'; /// Creates a map that uses our custom [deepEquals] and [deepHashCode] functions /// to determine equality. Map deepEqualsMap() => LinkedHashMap(equals: deepEquals, hashCode: deepHashCode); /// Compares two [Object]s for deep equality. This implementation differs from /// `package:yaml`'s deep equality notation by allowing for comparison of /// non-scalar map keys. bool deepEquals(dynamic obj1, dynamic obj2) { if (obj1 is YamlNode) obj1 = obj1.value; if (obj2 is YamlNode) obj2 = obj2.value; if (obj1 is Map && obj2 is Map) { return mapDeepEquals(obj1, obj2); } if (obj1 is List && obj2 is List) { return listDeepEquals(obj1, obj2); } return obj1 == obj2; } /// Compares two [List]s for deep equality. bool listDeepEquals(List list1, List list2) { if (list1.length != list2.length) return false; if (list1 is YamlList) list1 = list1.nodes; if (list2 is YamlList) list2 = list2.nodes; for (var i = 0; i < list1.length; i++) { if (!deepEquals(list1[i], list2[i])) { return false; } } return true; } /// Compares two [Map]s for deep equality. Differs from `package:yaml`'s deep /// equality notation by allowing for comparison of non-scalar map keys. bool mapDeepEquals(Map map1, Map map2) { if (map1.length != map2.length) return false; if (map1 is YamlList) map1 = (map1 as YamlMap).nodes; if (map2 is YamlList) map2 = (map2 as YamlMap).nodes; return map1.keys.every((key) { if (!containsKey(map2, key)) return false; /// Because two keys may be equal by deep equality but using one key on the /// other map might not get a hit since they may not be both using our /// [deepEqualsMap]. final key2 = getKey(map2, key); if (!deepEquals(map1[key], map2[key2])) { return false; } return true; }); } /// Returns a hashcode for [value] such that structures that are equal by /// [deepEquals] will have the same hash code. int deepHashCode(Object? value) { if (value is Map) { const equality = UnorderedIterableEquality(); return equality.hash(value.keys.map(deepHashCode)) ^ equality.hash(value.values.map(deepHashCode)); } else if (value is Iterable) { return const IterableEquality().hash(value.map(deepHashCode)); } else if (value is YamlScalar) { return (value.value as Object?).hashCode; } return value.hashCode; } /// Returns the [YamlNode] corresponding to the provided [key]. YamlNode getKeyNode(YamlMap map, Object? key) { return map.nodes.keys.firstWhere((node) => deepEquals(node, key)) as YamlNode; } /// Returns the [YamlNode] after the [YamlNode] corresponding to the provided /// [key]. YamlNode? getNextKeyNode(YamlMap map, Object? key) { final keyIterator = map.nodes.keys.iterator; while (keyIterator.moveNext()) { if (deepEquals(keyIterator.current, key) && keyIterator.moveNext()) { return keyIterator.current as YamlNode?; } } return null; } /// Returns the key in [map] that is equal to the provided [key] by the notion /// of deep equality. Object? getKey(Map map, Object? key) { return map.keys.firstWhere((k) => deepEquals(k, key)); } /// Checks if [map] has any keys equal to the provided [key] by deep equality. bool containsKey(Map map, Object? key) { return map.keys.where((node) => deepEquals(node, key)).isNotEmpty; }