-/*******************************************************************************\r
- * Copyright (c) 2007, 2010 Association for Decentralized Information Management\r
- * in Industry THTH ry.\r
- * All rights reserved. This program and the accompanying materials\r
- * are made available under the terms of the Eclipse Public License v1.0\r
- * which accompanies this distribution, and is available at\r
- * http://www.eclipse.org/legal/epl-v10.html\r
- *\r
- * Contributors:\r
- * VTT Technical Research Centre of Finland - initial API and implementation\r
- *******************************************************************************/\r
-package org.simantics.utils.datastructures;\r
-\r
-import java.util.Arrays;\r
-import java.util.Collection;\r
-import java.util.Collections;\r
-import java.util.Iterator;\r
-import java.util.Map;\r
-import java.util.NoSuchElementException;\r
-import java.util.Set;\r
-\r
-/**\r
- * A space-optimized immutable Map implementation.\r
- * \r
- * <p>\r
- * Both map keys and values are specified as separate typed arrays that must be\r
- * equally sized. This provides for space-optimization by allowing reusable key\r
- * array instances since the keys cannot be modified.\r
- * \r
- * <p>\r
- * The map should be considered immutable, but it does allow modification of a\r
- * value for an existing key as this only changes the value, the key will remain\r
- * untouched.\r
- * \r
- * @author Tuukka Lehtonen\r
- * \r
- * @param <K> key class\r
- * @param <V> value class\r
- */\r
-public class ArrayMap<K, V> implements Map<K, V> {\r
-\r
- final K[] keys;\r
- final V[] values;\r
- Set<Map.Entry<K, V>> entrySet;\r
- Set<K> keySet;\r
- Collection<V> valueSet;\r
-\r
- public static class ArrayMapBuilder<K2, V2> {\r
-\r
- final private K2[] keys;\r
-\r
- ArrayMapBuilder(K2[] keys) {\r
- this.keys = keys;\r
- }\r
-\r
- public ArrayMap<K2, V2> values(@SuppressWarnings("unchecked") V2 ... values) {\r
- return new ArrayMap<K2, V2>(keys, values);\r
- }\r
-\r
- }\r
-\r
- public static <K2, V2>ArrayMapBuilder<K2, V2> keys(@SuppressWarnings("unchecked") K2 ... keys) {\r
- return new ArrayMapBuilder<K2, V2>(keys);\r
- }\r
-\r
- public static <K2, V2> ArrayMap<K2, V2> make(K2[] keys, @SuppressWarnings("unchecked") V2... values) {\r
- return new ArrayMap<K2, V2>(keys, values);\r
- }\r
-\r
- /**\r
- * Constructs new ArrayMap based on a key and value array. Both arrays must\r
- * be of the same size.\r
- * \r
- * @param keys key array\r
- * @param values value array\r
- * @throws IllegalArgumentException if the arrays are of different size\r
- * @throws NullPointerException if either parameter is <code>null</code>\r
- */\r
- public ArrayMap(K[] keys, V[] values) {\r
- // NPE is caught by this.\r
- if (keys.length != values.length)\r
- throw new IllegalArgumentException("key array size (" + keys.length + ") != value array size (" + values.length + ")");\r
- this.keys = keys;\r
- this.values = values;\r
- }\r
-\r
- @Override\r
- public Set<Map.Entry<K, V>> entrySet() {\r
- return (entrySet != null) ? entrySet : (entrySet = new EntrySet());\r
- }\r
-\r
- @Override\r
- public void clear() {\r
- throw new UnsupportedOperationException();\r
- }\r
-\r
- @Override\r
- public boolean containsKey(Object key) {\r
- return keySet().contains(key);\r
- }\r
-\r
- @Override\r
- public boolean containsValue(Object value) {\r
- return values().contains(value);\r
- }\r
-\r
- @Override\r
- public V get(Object key) {\r
- if (key == null) {\r
- for (int i = 0; i < keys.length; i++) {\r
- if (keys[i] == null) {\r
- return values[i];\r
- }\r
- }\r
- return null;\r
- }\r
- int hash = key.hashCode();\r
- for (int i = 0; i < keys.length; i++) {\r
- K k = keys[i];\r
- if (k == key || (hash == k.hashCode() && key.equals(k))) {\r
- return values[i];\r
- }\r
- }\r
- return null;\r
- }\r
-\r
- @Override\r
- public boolean isEmpty() {\r
- return keys.length == 0;\r
- }\r
-\r
- @Override\r
- public Set<K> keySet() {\r
- return (keySet != null) ? keySet : (keySet = new KeySet());\r
- }\r
-\r
- @Override\r
- public V put(K key, V value) {\r
- if (key == null) {\r
- for (int i = 0; i < keys.length; i++) {\r
- if (keys[i] == null) {\r
- V old = values[i];\r
- values[i] = value;\r
- return old;\r
- }\r
- }\r
- throw new UnsupportedOperationException("key " + key + " not present in ArrayMap");\r
- }\r
- int hash = key.hashCode();\r
- for (int i = 0; i < keys.length; i++) {\r
- K k = keys[i];\r
- if (k == key || (hash == k.hashCode() && key.equals(k))) {\r
- V old = values[i];\r
- values[i] = value;\r
- return old;\r
- }\r
- }\r
- throw new UnsupportedOperationException("key " + key + " not present in ArrayMap");\r
- }\r
-\r
- @Override\r
- public void putAll(Map<? extends K, ? extends V> m) {\r
- for (K k : m.keySet()) {\r
- if (!containsKey(k))\r
- throw new UnsupportedOperationException("key " + k + " not present in ArrayMap");\r
- }\r
- for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) {\r
- put(entry.getKey(), entry.getValue());\r
- }\r
- }\r
-\r
- @Override\r
- public V remove(Object key) {\r
- throw new UnsupportedOperationException();\r
- }\r
-\r
- @Override\r
- public int size() {\r
- return keys.length;\r
- }\r
-\r
- @Override\r
- public Collection<V> values() {\r
- return valueSet != null ? valueSet : (valueSet = Collections.unmodifiableCollection(Arrays.asList(values)));\r
- }\r
-\r
- class KeySet extends ImmutableSet<K> implements Set<K> {\r
-\r
- @Override\r
- public boolean contains(Object o) {\r
- if (o == null) {\r
- for (K k : keys)\r
- if (k == null)\r
- return true;\r
- return false;\r
- }\r
- int hash = o.hashCode();\r
- for (K k : keys)\r
- if (o == k || (hash == k.hashCode() && o.equals(k)))\r
- return true;\r
- return false;\r
- }\r
-\r
- @Override\r
- public boolean containsAll(Collection<?> c) {\r
- for (Object o : c)\r
- if (!contains(o))\r
- return false;\r
- return true;\r
- }\r
-\r
- @Override\r
- public boolean isEmpty() {\r
- return keys.length == 0;\r
- }\r
-\r
- @Override\r
- public Iterator<K> iterator() {\r
- return new ImmutableIterator<K>() {\r
- int i = 0;\r
-\r
- @Override\r
- public boolean hasNext() {\r
- return i < keys.length;\r
- }\r
-\r
- @Override\r
- public K next() {\r
- if (i >= keys.length)\r
- throw new NoSuchElementException("no more elements (" + keys.length + " walked)");\r
- K k = keys[i];\r
- ++i;\r
- return k;\r
- }\r
- };\r
- }\r
-\r
- @Override\r
- public int size() {\r
- return keys.length;\r
- }\r
-\r
- @Override\r
- public Object[] toArray() {\r
- return keys;\r
- }\r
-\r
- @SuppressWarnings("unchecked")\r
- @Override\r
- public <T> T[] toArray(T[] a) {\r
- if (a.length < keys.length)\r
- // Make a new array of a's runtime type, but my contents:\r
- return (T[]) Arrays.copyOf(keys, keys.length, a.getClass());\r
- System.arraycopy(keys, 0, a, 0, keys.length);\r
- if (a.length > keys.length)\r
- a[keys.length] = null;\r
- return a;\r
- }\r
- }\r
-\r
- class EntrySet extends ImmutableSet<Map.Entry<K, V>> implements Set<Map.Entry<K, V>> {\r
-\r
- @Override\r
- public boolean contains(Object o) {\r
- throw new UnsupportedOperationException("TODO");\r
- }\r
-\r
- @Override\r
- public boolean containsAll(Collection<?> c) {\r
- for (Object o : c)\r
- if (!contains(o))\r
- return false;\r
- return true;\r
- }\r
-\r
- @Override\r
- public boolean isEmpty() {\r
- return keys.length == 0;\r
- }\r
-\r
- @Override\r
- public Iterator<Map.Entry<K, V>> iterator() {\r
- return new ImmutableIterator<Map.Entry<K, V>>() {\r
- int i = 0;\r
-\r
- @Override\r
- public boolean hasNext() {\r
- return i < keys.length;\r
- }\r
-\r
- @Override\r
- public Map.Entry<K, V> next() {\r
- if (i >= keys.length)\r
- throw new NoSuchElementException("no more elements (" + keys.length + " walked)");\r
- Map.Entry<K, V> entry = new ArrayMapEntry<K, V>(i, keys[i], values[i]);\r
- ++i;\r
- return entry;\r
- }\r
- };\r
- }\r
-\r
- @Override\r
- public int size() {\r
- return keys.length;\r
- }\r
-\r
- }\r
-\r
- /**\r
- * Returns the hash code value for this map. The hash code of a map is\r
- * defined to be the sum of the hash codes of each entry in the map's\r
- * <tt>entrySet()</tt> view. This ensures that <tt>m1.equals(m2)</tt>\r
- * implies that <tt>m1.hashCode()==m2.hashCode()</tt> for any two maps\r
- * <tt>m1</tt> and <tt>m2</tt>, as required by the general contract of\r
- * {@link Object#hashCode}.\r
- *\r
- * <p>This implementation iterates over <tt>entrySet()</tt>, calling\r
- * {@link Map.Entry#hashCode hashCode()} on each element (entry) in the\r
- * set, and adding up the results.\r
- *\r
- * @return the hash code value for this map\r
- * @see Map.Entry#hashCode()\r
- * @see Object#equals(Object)\r
- * @see Set#equals(Object)\r
- */\r
- @Override\r
- public int hashCode() {\r
- int h = 0;\r
- int l = keys.length;\r
- for (int i = 0; i < l; i++) {\r
- K key = keys[i];\r
- V value = values[i];\r
- int hash = (key==null ? 0 : key.hashCode()) ^ (value==null ? 0 : value.hashCode());\r
- h += hash;\r
- }\r
- return h;\r
- }\r
-\r
- /**\r
- * Compares the specified object with this map for equality. Returns\r
- * <tt>true</tt> if the given object is also a map and the two maps\r
- * represent the same mappings. More formally, two maps <tt>m1</tt> and\r
- * <tt>m2</tt> represent the same mappings if\r
- * <tt>m1.entrySet().equals(m2.entrySet())</tt>. This ensures that the\r
- * <tt>equals</tt> method works properly across different implementations\r
- * of the <tt>Map</tt> interface.\r
- *\r
- * <p>This implementation first checks if the specified object is this map;\r
- * if so it returns <tt>true</tt>. Then, it checks if the specified\r
- * object is a map whose size is identical to the size of this map; if\r
- * not, it returns <tt>false</tt>. If so, it iterates over this map's\r
- * <tt>entrySet</tt> collection, and checks that the specified map\r
- * contains each mapping that this map contains. If the specified map\r
- * fails to contain such a mapping, <tt>false</tt> is returned. If the\r
- * iteration completes, <tt>true</tt> is returned.\r
- *\r
- * @param o object to be compared for equality with this map\r
- * @return <tt>true</tt> if the specified object is equal to this map\r
- */\r
- @Override\r
- @SuppressWarnings("unchecked")\r
- public boolean equals(Object o) {\r
- if (o == this)\r
- return true;\r
-\r
- if (!(o instanceof Map))\r
- return false;\r
- Map<K, V> m = (Map<K, V>) o;\r
- if (m.size() != size())\r
- return false;\r
-\r
- try {\r
- int l = keys.length;\r
- for (int i = 0; i < l; i++) {\r
- K key = keys[i];\r
- V value = values[i];\r
- if (value == null) {\r
- if (!(m.get(key)==null && m.containsKey(key)))\r
- return false;\r
- } else {\r
- if (!value.equals(m.get(key)))\r
- return false;\r
- }\r
- }\r
- } catch (ClassCastException unused) {\r
- return false;\r
- } catch (NullPointerException unused) {\r
- return false;\r
- }\r
-\r
- return true;\r
- }\r
-\r
- @Override\r
- public String toString() {\r
- Iterator<Map.Entry<K,V>> i = entrySet().iterator();\r
- if (! i.hasNext())\r
- return "{}";\r
-\r
- StringBuilder sb = new StringBuilder();\r
- sb.append('{');\r
- for (;;) {\r
- Map.Entry<K,V> e = i.next();\r
- K key = e.getKey();\r
- V value = e.getValue();\r
- sb.append(key == this ? "(this Map)" : key);\r
- sb.append('=');\r
- sb.append(value == this ? "(this Map)" : value);\r
- if (! i.hasNext())\r
- return sb.append('}').toString();\r
- sb.append(", ");\r
- }\r
- }\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.utils.datastructures;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+/**
+ * A space-optimized immutable Map implementation.
+ *
+ * <p>
+ * Both map keys and values are specified as separate typed arrays that must be
+ * equally sized. This provides for space-optimization by allowing reusable key
+ * array instances since the keys cannot be modified.
+ *
+ * <p>
+ * The map should be considered immutable, but it does allow modification of a
+ * value for an existing key as this only changes the value, the key will remain
+ * untouched.
+ *
+ * @author Tuukka Lehtonen
+ *
+ * @param <K> key class
+ * @param <V> value class
+ */
+public class ArrayMap<K, V> implements Map<K, V> {
+
+ final K[] keys;
+ final V[] values;
+ Set<Map.Entry<K, V>> entrySet;
+ Set<K> keySet;
+ Collection<V> valueSet;
+
+ public static class ArrayMapBuilder<K2, V2> {
+
+ final private K2[] keys;
+
+ ArrayMapBuilder(K2[] keys) {
+ this.keys = keys;
+ }
+
+ public ArrayMap<K2, V2> values(@SuppressWarnings("unchecked") V2 ... values) {
+ return new ArrayMap<K2, V2>(keys, values);
+ }
+
+ }
+
+ public static <K2, V2>ArrayMapBuilder<K2, V2> keys(@SuppressWarnings("unchecked") K2 ... keys) {
+ return new ArrayMapBuilder<K2, V2>(keys);
+ }
+
+ public static <K2, V2> ArrayMap<K2, V2> make(K2[] keys, @SuppressWarnings("unchecked") V2... values) {
+ return new ArrayMap<K2, V2>(keys, values);
+ }
+
+ /**
+ * Constructs new ArrayMap based on a key and value array. Both arrays must
+ * be of the same size.
+ *
+ * @param keys key array
+ * @param values value array
+ * @throws IllegalArgumentException if the arrays are of different size
+ * @throws NullPointerException if either parameter is <code>null</code>
+ */
+ public ArrayMap(K[] keys, V[] values) {
+ // NPE is caught by this.
+ if (keys.length != values.length)
+ throw new IllegalArgumentException("key array size (" + keys.length + ") != value array size (" + values.length + ")");
+ this.keys = keys;
+ this.values = values;
+ }
+
+ @Override
+ public Set<Map.Entry<K, V>> entrySet() {
+ return (entrySet != null) ? entrySet : (entrySet = new EntrySet());
+ }
+
+ @Override
+ public void clear() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean containsKey(Object key) {
+ return keySet().contains(key);
+ }
+
+ @Override
+ public boolean containsValue(Object value) {
+ return values().contains(value);
+ }
+
+ @Override
+ public V get(Object key) {
+ if (key == null) {
+ for (int i = 0; i < keys.length; i++) {
+ if (keys[i] == null) {
+ return values[i];
+ }
+ }
+ return null;
+ }
+ int hash = key.hashCode();
+ for (int i = 0; i < keys.length; i++) {
+ K k = keys[i];
+ if (k == key || (hash == k.hashCode() && key.equals(k))) {
+ return values[i];
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return keys.length == 0;
+ }
+
+ @Override
+ public Set<K> keySet() {
+ return (keySet != null) ? keySet : (keySet = new KeySet());
+ }
+
+ @Override
+ public V put(K key, V value) {
+ if (key == null) {
+ for (int i = 0; i < keys.length; i++) {
+ if (keys[i] == null) {
+ V old = values[i];
+ values[i] = value;
+ return old;
+ }
+ }
+ throw new UnsupportedOperationException("key " + key + " not present in ArrayMap");
+ }
+ int hash = key.hashCode();
+ for (int i = 0; i < keys.length; i++) {
+ K k = keys[i];
+ if (k == key || (hash == k.hashCode() && key.equals(k))) {
+ V old = values[i];
+ values[i] = value;
+ return old;
+ }
+ }
+ throw new UnsupportedOperationException("key " + key + " not present in ArrayMap");
+ }
+
+ @Override
+ public void putAll(Map<? extends K, ? extends V> m) {
+ for (K k : m.keySet()) {
+ if (!containsKey(k))
+ throw new UnsupportedOperationException("key " + k + " not present in ArrayMap");
+ }
+ for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) {
+ put(entry.getKey(), entry.getValue());
+ }
+ }
+
+ @Override
+ public V remove(Object key) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public int size() {
+ return keys.length;
+ }
+
+ @Override
+ public Collection<V> values() {
+ return valueSet != null ? valueSet : (valueSet = Collections.unmodifiableCollection(Arrays.asList(values)));
+ }
+
+ class KeySet extends ImmutableSet<K> implements Set<K> {
+
+ @Override
+ public boolean contains(Object o) {
+ if (o == null) {
+ for (K k : keys)
+ if (k == null)
+ return true;
+ return false;
+ }
+ int hash = o.hashCode();
+ for (K k : keys)
+ if (o == k || (hash == k.hashCode() && o.equals(k)))
+ return true;
+ return false;
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> c) {
+ for (Object o : c)
+ if (!contains(o))
+ return false;
+ return true;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return keys.length == 0;
+ }
+
+ @Override
+ public Iterator<K> iterator() {
+ return new ImmutableIterator<K>() {
+ int i = 0;
+
+ @Override
+ public boolean hasNext() {
+ return i < keys.length;
+ }
+
+ @Override
+ public K next() {
+ if (i >= keys.length)
+ throw new NoSuchElementException("no more elements (" + keys.length + " walked)");
+ K k = keys[i];
+ ++i;
+ return k;
+ }
+ };
+ }
+
+ @Override
+ public int size() {
+ return keys.length;
+ }
+
+ @Override
+ public Object[] toArray() {
+ return keys;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> T[] toArray(T[] a) {
+ if (a.length < keys.length)
+ // Make a new array of a's runtime type, but my contents:
+ return (T[]) Arrays.copyOf(keys, keys.length, a.getClass());
+ System.arraycopy(keys, 0, a, 0, keys.length);
+ if (a.length > keys.length)
+ a[keys.length] = null;
+ return a;
+ }
+ }
+
+ class EntrySet extends ImmutableSet<Map.Entry<K, V>> implements Set<Map.Entry<K, V>> {
+
+ @Override
+ public boolean contains(Object o) {
+ throw new UnsupportedOperationException("TODO");
+ }
+
+ @Override
+ public boolean containsAll(Collection<?> c) {
+ for (Object o : c)
+ if (!contains(o))
+ return false;
+ return true;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return keys.length == 0;
+ }
+
+ @Override
+ public Iterator<Map.Entry<K, V>> iterator() {
+ return new ImmutableIterator<Map.Entry<K, V>>() {
+ int i = 0;
+
+ @Override
+ public boolean hasNext() {
+ return i < keys.length;
+ }
+
+ @Override
+ public Map.Entry<K, V> next() {
+ if (i >= keys.length)
+ throw new NoSuchElementException("no more elements (" + keys.length + " walked)");
+ Map.Entry<K, V> entry = new ArrayMapEntry<K, V>(i, keys[i], values[i]);
+ ++i;
+ return entry;
+ }
+ };
+ }
+
+ @Override
+ public int size() {
+ return keys.length;
+ }
+
+ }
+
+ /**
+ * Returns the hash code value for this map. The hash code of a map is
+ * defined to be the sum of the hash codes of each entry in the map's
+ * <tt>entrySet()</tt> view. This ensures that <tt>m1.equals(m2)</tt>
+ * implies that <tt>m1.hashCode()==m2.hashCode()</tt> for any two maps
+ * <tt>m1</tt> and <tt>m2</tt>, as required by the general contract of
+ * {@link Object#hashCode}.
+ *
+ * <p>This implementation iterates over <tt>entrySet()</tt>, calling
+ * {@link Map.Entry#hashCode hashCode()} on each element (entry) in the
+ * set, and adding up the results.
+ *
+ * @return the hash code value for this map
+ * @see Map.Entry#hashCode()
+ * @see Object#equals(Object)
+ * @see Set#equals(Object)
+ */
+ @Override
+ public int hashCode() {
+ int h = 0;
+ int l = keys.length;
+ for (int i = 0; i < l; i++) {
+ K key = keys[i];
+ V value = values[i];
+ int hash = (key==null ? 0 : key.hashCode()) ^ (value==null ? 0 : value.hashCode());
+ h += hash;
+ }
+ return h;
+ }
+
+ /**
+ * Compares the specified object with this map for equality. Returns
+ * <tt>true</tt> if the given object is also a map and the two maps
+ * represent the same mappings. More formally, two maps <tt>m1</tt> and
+ * <tt>m2</tt> represent the same mappings if
+ * <tt>m1.entrySet().equals(m2.entrySet())</tt>. This ensures that the
+ * <tt>equals</tt> method works properly across different implementations
+ * of the <tt>Map</tt> interface.
+ *
+ * <p>This implementation first checks if the specified object is this map;
+ * if so it returns <tt>true</tt>. Then, it checks if the specified
+ * object is a map whose size is identical to the size of this map; if
+ * not, it returns <tt>false</tt>. If so, it iterates over this map's
+ * <tt>entrySet</tt> collection, and checks that the specified map
+ * contains each mapping that this map contains. If the specified map
+ * fails to contain such a mapping, <tt>false</tt> is returned. If the
+ * iteration completes, <tt>true</tt> is returned.
+ *
+ * @param o object to be compared for equality with this map
+ * @return <tt>true</tt> if the specified object is equal to this map
+ */
+ @Override
+ @SuppressWarnings("unchecked")
+ public boolean equals(Object o) {
+ if (o == this)
+ return true;
+
+ if (!(o instanceof Map))
+ return false;
+ Map<K, V> m = (Map<K, V>) o;
+ if (m.size() != size())
+ return false;
+
+ try {
+ int l = keys.length;
+ for (int i = 0; i < l; i++) {
+ K key = keys[i];
+ V value = values[i];
+ if (value == null) {
+ if (!(m.get(key)==null && m.containsKey(key)))
+ return false;
+ } else {
+ if (!value.equals(m.get(key)))
+ return false;
+ }
+ }
+ } catch (ClassCastException unused) {
+ return false;
+ } catch (NullPointerException unused) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ Iterator<Map.Entry<K,V>> i = entrySet().iterator();
+ if (! i.hasNext())
+ return "{}";
+
+ StringBuilder sb = new StringBuilder();
+ sb.append('{');
+ for (;;) {
+ Map.Entry<K,V> e = i.next();
+ K key = e.getKey();
+ V value = e.getValue();
+ sb.append(key == this ? "(this Map)" : key);
+ sb.append('=');
+ sb.append(value == this ? "(this Map)" : value);
+ if (! i.hasNext())
+ return sb.append('}').toString();
+ sb.append(", ");
+ }
+ }
+}