diff --git a/gson/src/main/java/com/google/gson/JsonObject.java b/gson/src/main/java/com/google/gson/JsonObject.java index 78c7a177..83d3624c 100644 --- a/gson/src/main/java/com/google/gson/JsonObject.java +++ b/gson/src/main/java/com/google/gson/JsonObject.java @@ -16,7 +16,7 @@ package com.google.gson; -import com.google.gson.internal.LinkedTreeMap; +import com.google.gson.internal.LinkedHashTreeMap; import java.util.Map; import java.util.Set; @@ -30,8 +30,8 @@ import java.util.Set; * @author Joel Leitch */ public final class JsonObject extends JsonElement { - private final LinkedTreeMap members = - new LinkedTreeMap(); + private final LinkedHashTreeMap members = + new LinkedHashTreeMap(); @Override JsonObject deepCopy() { diff --git a/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java b/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java index 8a694af1..998a32df 100644 --- a/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java +++ b/gson/src/main/java/com/google/gson/internal/ConstructorConstructor.java @@ -188,7 +188,7 @@ public final class ConstructorConstructor { } else { return new ObjectConstructor() { public T construct() { - return (T) new LinkedTreeMap(); + return (T) new LinkedHashTreeMap(); } }; } diff --git a/gson/src/main/java/com/google/gson/internal/LinkedTreeMap.java b/gson/src/main/java/com/google/gson/internal/LinkedTreeMap.java deleted file mode 100644 index 28d1dd41..00000000 --- a/gson/src/main/java/com/google/gson/internal/LinkedTreeMap.java +++ /dev/null @@ -1,496 +0,0 @@ -/* - * Copyright (C) 2012 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.gson.internal; - -import java.io.ObjectStreamException; -import java.io.Serializable; -import java.util.AbstractMap; -import java.util.AbstractSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; - -/** - * A map with a {@code Comparable} key that is implemented as a red-black tree. - * - *

A red-black tree offers quicker insert operations than AVL trees; however, slower "find" - * operations. - * - *

This implementation was derived from the JDK's TreeMap class. - */ -public class LinkedTreeMap, V> - extends AbstractMap implements Serializable { - private static final boolean BLACK = false; - private static final boolean RED = true; - - // Size stored as a field for optimization instead of recursing tree. - private transient int size = 0; - - private transient TreeNode root; - - // Store the head and tail to preserve the ordering of nodes inserted into tree - private transient TreeNode head; - private transient TreeNode tail; - - public Set> entrySet() { - return new EntrySet(); - } - - public boolean containsKey(K key) { - return (find(key) != null); - } - - public V get(K key) { - TreeNode entry = find(key); - return (entry == null) ? null : entry.getValue(); - } - - public V put(K key, V value) { - $Gson$Preconditions.checkNotNull(key); - if (root == null) { - root = new TreeNode(null, null, key, value); - head = root; - tail = root; - size++; - return null; - } else { - return findAndUpdateOrCreateNode(key, value); - } - } - - private V findAndUpdateOrCreateNode(K key, V value) { - TreeNode parent; - int lastCompare; - - TreeNode entry = root; - do { - parent = entry; - lastCompare = key.compareTo(entry.key); - if (lastCompare < 0) { - entry = entry.left; - } else if (lastCompare > 0) { - entry = entry.right; - } else { - V rval = entry.getValue(); - entry.setValue(value); - return rval; - } - } while (entry != null); - - size++; - - // Create a new node and set up the tree edges - TreeNode newEntry = new TreeNode(parent, tail, key, value); - if (lastCompare < 0) { - parent.left = newEntry; - } else if (lastCompare > 0) { - parent.right = newEntry; - } - - tail.next = newEntry; - tail = newEntry; - rebalanceAfterInsert(newEntry); - return null; - } - - private void rebalanceAfterInsert(TreeNode x) { - x.color = RED; - - while (x != null && x != root && x.parent.color == RED) { - if (x.parent == leftOf(parentOf(parentOf(x)))) { - TreeNode y = rightOf(parentOf(parentOf(x))); - if (colorOf(y) == RED) { - setColor(parentOf(x), BLACK); - setColor(y, BLACK); - setColor(parentOf(parentOf(x)), RED); - x = parentOf(parentOf(x)); - } else { - if (x == rightOf(parentOf(x))) { - x= parentOf(x); - rotateLeft(x); - } - setColor(parentOf(x), BLACK); - setColor(parentOf(parentOf(x)), RED); - rotateRight(parentOf(parentOf(x))); - } - } else { - TreeNode y = leftOf(parentOf(parentOf(x))); - if (colorOf(y) == RED) { - setColor(parentOf(x), BLACK); - setColor(y, BLACK); - setColor(parentOf(parentOf(x)), RED); - x = parentOf(parentOf(x)); - } else { - if (x == leftOf(parentOf(x))) { - x = parentOf(x); - rotateRight(x); - } - setColor(parentOf(x), BLACK); - setColor(parentOf(parentOf(x)), RED); - rotateLeft(parentOf(parentOf(x))); - } - } - } - root.color = BLACK; - } - - private static , V> TreeNode parentOf(TreeNode e) { - return (e != null ? e.parent : null); - } - - private static , V> boolean colorOf(TreeNode e) { - return (e != null ? e.color : BLACK); - } - - private static , V> TreeNode leftOf(TreeNode e) { - return (e != null ? e.left : null); - } - - private static , V> TreeNode rightOf(TreeNode e) { - return (e != null ? e.right : null); - } - - private static , V> void setColor(TreeNode e, boolean c) { - if (e != null){ - e.color = c; - } - } - - private static , V> TreeNode successor(TreeNode t) { - if (t == null) { - return null; - } else if (t.right != null) { - TreeNode p = t.right; - while (p.left != null) { - p = p.left; - } - return p; - } else { - TreeNode p = t.parent; - TreeNode ch = t; - while (p != null && ch == p.right) { - ch = p; - p = p.parent; - } - return p; - } - } - - private void rotateLeft(TreeNode p) { - if (p != null) { - TreeNode r = p.right; - p.right = r.left; - if (r.left != null) { - r.left.parent = p; - } - r.parent = p.parent; - if (p.parent == null) { - root = r; - } else if (p.parent.left == p) { - p.parent.left = r; - } else { - p.parent.right = r; - } - r.left = p; - p.parent = r; - } - } - - private void rotateRight(TreeNode p) { - if (p != null) { - TreeNode l = p.left; - p.left = l.right; - if (l.right != null) { - l.right.parent = p; - } - l.parent = p.parent; - if (p.parent == null) { - root = l; - } else if (p.parent.right == p) { - p.parent.right = l; - } else { - p.parent.left = l; - } - l.right = p; - p.parent = l; - } - } - - public V remove(K key) { - TreeNode entry = find(key); - if (entry == null) { - return null; - } else { - size--; - V rval = entry.getValue(); - preserveOrderForRemoval(entry); - removeNode(entry); - return rval; - } - } - - private void removeNode(TreeNode p) { - if (p.left != null && p.right != null) { - TreeNode s = successor(p); - p.key = s.key; - p.value = s.value; - p = s; - } - - TreeNode replacement = (p.left != null ? p.left : p.right); - if (replacement != null) { - // Link replacement to parent - replacement.parent = p.parent; - if (p.parent == null) { - root = replacement; - } else if (p == p.parent.left) { - p.parent.left = replacement; - } else { - p.parent.right = replacement; - } - - // Null out links so they are OK to use by fixAfterDeletion. - p.left = null; - p.right = null; - p.parent = null; - - // Fix replacement - if (p.color == BLACK) { - fixAfterDeletion(replacement); - } - } else if (p.parent == null) { // return if we are the only node. - root = null; - } else { // No children. Use self as phantom replacement and unlink. - if (p.color == BLACK) { - fixAfterDeletion(p); - } - - if (p.parent != null) { - if (p == p.parent.left) { - p.parent.left = null; - } else if (p == p.parent.right) { - p.parent.right = null; - } - p.parent = null; - } - } - } - - private void preserveOrderForRemoval(TreeNode p) { - // Preserve insertion order for entry set iteration - if (p == head) { - head = p.next; - } - if (p == tail) { - tail = p.previous; - } - - TreeNode previousNode = p.previous; - TreeNode nextNode = p.next; - if (previousNode != null) { - previousNode.next = nextNode; - } - if (nextNode != null) { - nextNode.previous = previousNode; - } - } - - private void fixAfterDeletion(TreeNode x) { - while (x != root && colorOf(x) == BLACK) { - if (x == leftOf(parentOf(x))) { - TreeNode sib = rightOf(parentOf(x)); - - if (colorOf(sib) == RED) { - setColor(sib, BLACK); - setColor(parentOf(x), RED); - rotateLeft(parentOf(x)); - sib = rightOf(parentOf(x)); - } - - if (colorOf(leftOf(sib)) == BLACK && - colorOf(rightOf(sib)) == BLACK) { - setColor(sib, RED); - x = parentOf(x); - } else { - if (colorOf(rightOf(sib)) == BLACK) { - setColor(leftOf(sib), BLACK); - setColor(sib, RED); - rotateRight(sib); - sib = rightOf(parentOf(x)); - } - setColor(sib, colorOf(parentOf(x))); - setColor(parentOf(x), BLACK); - setColor(rightOf(sib), BLACK); - rotateLeft(parentOf(x)); - x = root; - } - } else { // symmetric - TreeNode sib = leftOf(parentOf(x)); - - if (colorOf(sib) == RED) { - setColor(sib, BLACK); - setColor(parentOf(x), RED); - rotateRight(parentOf(x)); - sib = leftOf(parentOf(x)); - } - - if (colorOf(rightOf(sib)) == BLACK && - colorOf(leftOf(sib)) == BLACK) { - setColor(sib, RED); - x = parentOf(x); - } else { - if (colorOf(leftOf(sib)) == BLACK) { - setColor(rightOf(sib), BLACK); - setColor(sib, RED); - rotateLeft(sib); - sib = leftOf(parentOf(x)); - } - setColor(sib, colorOf(parentOf(x))); - setColor(parentOf(x), BLACK); - setColor(leftOf(sib), BLACK); - rotateRight(parentOf(x)); - x = root; - } - } - } - - setColor(x, BLACK); - } - - public int size() { - return size; - } - - /** - * If somebody is unlucky enough to have to serialize one of these, serialize - * it as a LinkedHashMap so that they won't need Gson on the other side to - * deserialize it. Using serialization defeats our DoS defence, so most apps - * shouldn't use it. - */ - private Object writeReplace() throws ObjectStreamException { - return new LinkedHashMap(this); - } - - private TreeNode find(K key) { - if (key != null) { - for (TreeNode entry = root; entry != null; ) { - int compareVal = key.compareTo(entry.key); - if (compareVal < 0) { - entry = entry.left; - } else if (compareVal > 0) { - entry = entry.right; - } else { - return entry; - } - } - } - return null; - } - - private static class TreeNode, V> implements Map.Entry { - private K key; - private V value; - private TreeNode parent; - private TreeNode left; - private TreeNode right; - - // Used for rebalance tree - private boolean color = BLACK; - - // This is used for preserving the insertion order - private TreeNode next; - private TreeNode previous; - - TreeNode(TreeNode parent, TreeNode previous, K key, V value) { - this.parent = parent; - this.previous = previous; - this.key = key; - this.value = value; - } - - public K getKey() { - return key; - } - - public V getValue() { - return value; - } - - // I'd like to make this throw an UnsupportedOperationException; however, - public V setValue(V value) { - V rval = this.value; - this.value = value; - return rval; - } - - @Override - public final boolean equals(Object o) { - if (!(o instanceof Entry)) { - return false; - } - Entry e = (Entry) o; - Object eValue = e.getValue(); - return key.equals(e.getKey()) - && (value == null ? eValue == null : value.equals(eValue)); - } - - @Override - public final int hashCode() { - return key.hashCode() ^ (value == null ? 0 : value.hashCode()); - } - - @Override - public final String toString() { - return key + "=" + value; - } - } - - class EntrySet extends AbstractSet> { - @Override - public Iterator> iterator() { - return new LinkedTreeIterator(head); - } - - @Override - public int size() { - return LinkedTreeMap.this.size(); - } - } - - private class LinkedTreeIterator implements Iterator> { - private TreeNode current; - - private LinkedTreeIterator(TreeNode first) { - this.current = first; - } - - public boolean hasNext() { - return current != null; - } - - public Map.Entry next() { - TreeNode rval = current; - current = current.next; - return rval; - } - - public final void remove() { - LinkedTreeMap.this.remove(current.getKey()); - } - } -} diff --git a/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java b/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java index b753246e..8537216e 100644 --- a/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java +++ b/gson/src/main/java/com/google/gson/internal/bind/ObjectTypeAdapter.java @@ -19,7 +19,7 @@ package com.google.gson.internal.bind; import com.google.gson.Gson; import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapterFactory; -import com.google.gson.internal.LinkedTreeMap; +import com.google.gson.internal.LinkedHashTreeMap; import com.google.gson.reflect.TypeToken; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; @@ -64,7 +64,7 @@ public final class ObjectTypeAdapter extends TypeAdapter { return list; case BEGIN_OBJECT: - Map map = new LinkedTreeMap(); + Map map = new LinkedHashTreeMap(); in.beginObject(); while (in.hasNext()) { map.put(in.nextName(), read(in)); diff --git a/gson/src/test/java/com/google/gson/internal/LinkedHashTreeMapTest.java b/gson/src/test/java/com/google/gson/internal/LinkedHashTreeMapTest.java index 1f424472..897b7429 100644 --- a/gson/src/test/java/com/google/gson/internal/LinkedHashTreeMapTest.java +++ b/gson/src/test/java/com/google/gson/internal/LinkedHashTreeMapTest.java @@ -16,14 +16,16 @@ package com.google.gson.internal; +import com.google.gson.common.MoreAsserts; import com.google.gson.internal.LinkedHashTreeMap.AvlBuilder; import com.google.gson.internal.LinkedHashTreeMap.AvlIterator; import com.google.gson.internal.LinkedHashTreeMap.Node; +import junit.framework.TestCase; + import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.Map; -import junit.framework.TestCase; public final class LinkedHashTreeMapTest extends TestCase { public void testIterationOrder() { @@ -73,6 +75,22 @@ public final class LinkedHashTreeMapTest extends TestCase { assertEquals(0, map.size()); } + public void testEqualsAndHashCode() throws Exception { + LinkedHashTreeMap map1 = new LinkedHashTreeMap(); + map1.put("A", 1); + map1.put("B", 2); + map1.put("C", 3); + map1.put("D", 4); + + LinkedHashTreeMap map2 = new LinkedHashTreeMap(); + map2.put("C", 3); + map2.put("B", 2); + map2.put("D", 4); + map2.put("A", 1); + + MoreAsserts.assertEqualsAndHashCode(map1, map2); + } + public void testAvlWalker() { assertAvlWalker(node(node("a"), "b", node("c")), "a", "b", "c"); diff --git a/gson/src/test/java/com/google/gson/internal/LinkedTreeMapTest.java b/gson/src/test/java/com/google/gson/internal/LinkedTreeMapTest.java deleted file mode 100644 index a46d2b90..00000000 --- a/gson/src/test/java/com/google/gson/internal/LinkedTreeMapTest.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (C) 2012 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.gson.internal; - -import com.google.gson.common.MoreAsserts; - -import junit.framework.TestCase; - -import java.util.Iterator; -import java.util.Map; -import java.util.Set; - -/** - * Unit tests for {@code LinkedTreeMap} class. - * - * @author Joel Leitch - */ -public class LinkedTreeMapTest extends TestCase { - - public void testPutAndGet() throws Exception { - LinkedTreeMap map = new LinkedTreeMap(); - map.put("B", 2); - map.put("A", 1); - map.put("C", 3); - - assertTrue(map.containsKey("A")); - assertTrue(map.containsKey("B")); - assertTrue(map.containsKey("C")); - assertFalse(map.containsKey("D")); - - assertEquals(1, (int) map.get("A")); - assertEquals(2, (int) map.get("B")); - assertEquals(3, (int) map.get("C")); - assertEquals(3, map.entrySet().size()); - - assertEquals(1, (int) map.put("A", 4)); - assertTrue(map.containsKey("A")); - assertEquals(4, (int) map.get("A")); - assertEquals(3, map.entrySet().size()); - - // Ensure entry set size is same as map size - assertEquals(map.size(), map.entrySet().size()); - } - - public void testGetAndContainsNullKey() throws Exception { - LinkedTreeMap map = new LinkedTreeMap(); - assertFalse(map.containsKey(null)); - assertNull(map.get(null)); - - map.put("A", 1); - assertFalse(map.containsKey(null)); - assertNull(map.get(null)); - } - - public void testDisallowPutForNullKeys() throws Exception { - LinkedTreeMap map = new LinkedTreeMap(); - try { - map.put(null, 1); - fail(); - } catch (NullPointerException expected) {} - } - - public void testSingleElement() throws Exception { - LinkedTreeMap map = new LinkedTreeMap(); - map.put("A", 1); - assertEquals(1, map.size()); - - assertEquals(1, (int) map.get("A")); - map.remove("A"); - assertEquals(0, map.size()); - - // Ensure the map and entry set are empty - assertTrue(map.entrySet().isEmpty()); - assertTrue(map.isEmpty()); - } - - public void testAddAndRemove() throws Exception { - LinkedTreeMap map = new LinkedTreeMap(); - map.put("A", 1); - map.put("B", 2); - map.put("C", 3); - map.put("D", 4); - map.put("E", 5); - map.put("F", 6); - - assertEquals(3, (int) map.remove("C")); - assertEquals(5, map.size()); - assertIterationOrder(map.entrySet(), - new String[] { "A", "B", "D", "E", "F" }, new int[] { 1, 2, 4, 5, 6 }); - - // Remove a non-existent key - assertNull(map.remove("G")); - assertEquals(5, map.size()); - - // Remove the first element - assertEquals(1, (int) map.remove("A")); - assertIterationOrder(map.entrySet(), - new String[] { "B", "D", "E", "F" }, new int[] { 2, 4, 5, 6 }); - - // Remove the last element - assertEquals(6, (int) map.remove("F")); - assertIterationOrder(map.entrySet(), - new String[] { "B", "D", "E" }, new int[] { 2, 4, 5 }); - } - - public void testInsertionOrderPreserved() throws Exception { - LinkedTreeMap map = new LinkedTreeMap(); - String[] keys = { "B", "A", "D", "C", "Z", "W", "E", "F", "T" }; - int[] values = new int[keys.length]; - for (int i = 0; i < keys.length; ++i) { - values[i] = i; - map.put(keys[i], i); - } - - Set> entries = map.entrySet(); - assertEquals(keys.length, entries.size()); - assertIterationOrder(entries, keys, values); - } - - public void testEqualsAndHashCode() throws Exception { - LinkedTreeMap map1 = new LinkedTreeMap(); - map1.put("A", 1); - map1.put("B", 2); - map1.put("C", 3); - map1.put("D", 4); - - LinkedTreeMap map2 = new LinkedTreeMap(); - map2.put("C", 3); - map2.put("B", 2); - map2.put("D", 4); - map2.put("A", 1); - - MoreAsserts.assertEqualsAndHashCode(map1, map2); - } - - private void assertIterationOrder(Set> entries, String[] keys, int[] values) { - int i = 0; - for (Iterator> iterator = entries.iterator(); iterator.hasNext(); ++i) { - Map.Entry entry = iterator.next(); - assertEquals(keys[i], entry.getKey()); - assertEquals(values[i], (int) entry.getValue()); - } - } -}