Remove usage of Maps.mapToString() (#71)

diff --git a/lib/src/canonicalized_map.dart b/lib/src/canonicalized_map.dart
index 122bb35..117cd63 100644
--- a/lib/src/canonicalized_map.dart
+++ b/lib/src/canonicalized_map.dart
@@ -153,9 +153,40 @@
 
   Iterable<V> get values => _base.values.map((pair) => pair.last);
 
-  String toString() => Maps.mapToString(this);
+  String toString() {
+    // Detect toString() cycles.
+    if (_isToStringVisiting(this)) {
+      return '{...}';
+    }
+
+    var result = new StringBuffer();
+    try {
+      _toStringVisiting.add(this);
+      result.write('{');
+      bool first = true;
+      forEach((k, v) {
+        if (!first) {
+          result.write(', ');
+        }
+        first = false;
+        result.write('$k: $v');
+      });
+      result.write('}');
+    } finally {
+      assert(identical(_toStringVisiting.last, this));
+      _toStringVisiting.removeLast();
+    }
+
+    return result.toString();
+  }
 
   bool _isValidKey(Object key) =>
       (key == null || key is K) &&
       (_isValidKeyFn == null || _isValidKeyFn(key));
 }
+
+/// A collection used to identify cyclic maps during toString() calls.
+final List _toStringVisiting = [];
+
+/// Check if we are currently visiting `o` in a toString() call.
+bool _isToStringVisiting(o) => _toStringVisiting.any((e) => identical(o, e));
diff --git a/test/canonicalized_map_test.dart b/test/canonicalized_map_test.dart
index 716e738..f48778d 100644
--- a/test/canonicalized_map_test.dart
+++ b/test/canonicalized_map_test.dart
@@ -132,6 +132,35 @@
     });
   });
 
+  group("CanonicalizedMap builds an informative string representation", () {
+    var map;
+    setUp(() {
+      map = new CanonicalizedMap<int, String, dynamic>(int.parse,
+          isValidKey: (s) => new RegExp(r"^\d+$").hasMatch(s as String));
+    });
+
+    test("for an empty map", () {
+      expect(map.toString(), equals('{}'));
+    });
+
+    test("for a map with one value", () {
+      map.addAll({"1": "value 1"});
+      expect(map.toString(), equals('{1: value 1}'));
+    });
+
+    test("for a map with multiple values", () {
+      map.addAll(
+          {"1": "value 1", "01": "value 01", "2": "value 2", "03": "value 03"});
+      expect(
+          map.toString(), equals('{01: value 01, 2: value 2, 03: value 03}'));
+    });
+
+    test("for a map with a loop", () {
+      map.addAll({"1": "value 1", "2": map});
+      expect(map.toString(), equals('{1: value 1, 2: {...}}'));
+    });
+  });
+
   group("CanonicalizedMap.from", () {
     test("canonicalizes its keys", () {
       var map = new CanonicalizedMap.from(