Adding Red-Black Tree implementation and tying it into the Gson bindings.
This commit is contained in:
parent
c950d28460
commit
14f16e2d0c
@ -16,8 +16,8 @@
|
||||
|
||||
package com.google.gson;
|
||||
|
||||
import com.google.gson.internal.$Gson$Preconditions;
|
||||
import com.google.gson.internal.StringMap;
|
||||
import com.google.gson.internal.LinkedTreeMap;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@ -30,17 +30,8 @@ import java.util.Set;
|
||||
* @author Joel Leitch
|
||||
*/
|
||||
public final class JsonObject extends JsonElement {
|
||||
// We are using a linked hash map because it is important to preserve
|
||||
// the order in which elements are inserted. This is needed to ensure
|
||||
// that the fields of an object are inserted in the order they were
|
||||
// defined in the class.
|
||||
private final StringMap<JsonElement> members = new StringMap<JsonElement>();
|
||||
|
||||
/**
|
||||
* Creates an empty JsonObject.
|
||||
*/
|
||||
public JsonObject() {
|
||||
}
|
||||
private final LinkedTreeMap<String, JsonElement> members =
|
||||
new LinkedTreeMap<String, JsonElement>();
|
||||
|
||||
@Override
|
||||
JsonObject deepCopy() {
|
||||
@ -63,7 +54,7 @@ public final class JsonObject extends JsonElement {
|
||||
if (value == null) {
|
||||
value = JsonNull.INSTANCE;
|
||||
}
|
||||
members.put($Gson$Preconditions.checkNotNull(property), value);
|
||||
members.put(property, value);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -158,11 +149,7 @@ public final class JsonObject extends JsonElement {
|
||||
* @return the member matching the name. Null if no such member exists.
|
||||
*/
|
||||
public JsonElement get(String memberName) {
|
||||
if (members.containsKey(memberName)) {
|
||||
JsonElement member = members.get(memberName);
|
||||
return member == null ? JsonNull.INSTANCE : member;
|
||||
}
|
||||
return null;
|
||||
return members.get(memberName);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -18,6 +18,7 @@ package com.google.gson.internal;
|
||||
|
||||
import com.google.gson.InstanceCreator;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
@ -31,7 +32,9 @@ import java.util.LinkedList;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
import java.util.SortedMap;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
/**
|
||||
@ -144,26 +147,32 @@ public final class ConstructorConstructor {
|
||||
}
|
||||
|
||||
if (Map.class.isAssignableFrom(rawType)) {
|
||||
if (type instanceof ParameterizedType
|
||||
&& ((ParameterizedType) type).getActualTypeArguments()[0] == String.class) {
|
||||
if (SortedMap.class.isAssignableFrom(rawType)) {
|
||||
return new ObjectConstructor<T>() {
|
||||
public T construct() {
|
||||
return (T) new StringMap<Object>();
|
||||
return (T) new TreeMap<Object, Object>();
|
||||
}
|
||||
};
|
||||
} else {
|
||||
} else if (type instanceof ParameterizedType && !(String.class.isAssignableFrom(
|
||||
TypeToken.get(((ParameterizedType) type).getActualTypeArguments()[0]).getRawType()))) {
|
||||
return new ObjectConstructor<T>() {
|
||||
public T construct() {
|
||||
return (T) new LinkedHashMap<Object, Object>();
|
||||
}
|
||||
};
|
||||
} else {
|
||||
return new ObjectConstructor<T>() {
|
||||
public T construct() {
|
||||
return (T) new LinkedTreeMap<String, Object>();
|
||||
}
|
||||
};
|
||||
}
|
||||
// TODO: SortedMap ?
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
private <T> ObjectConstructor<T> newUnsafeAllocator(
|
||||
final Type type, final Class<? super T> rawType) {
|
||||
return new ObjectConstructor<T>() {
|
||||
|
494
gson/src/main/java/com/google/gson/internal/LinkedTreeMap.java
Normal file
494
gson/src/main/java/com/google/gson/internal/LinkedTreeMap.java
Normal file
@ -0,0 +1,494 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* <p>A red-black tree offers quicker insert operations than AVL trees; however, slower "find"
|
||||
* operations.
|
||||
*
|
||||
* <p>This implementation was derived from the JDK's TreeMap class.
|
||||
*/
|
||||
public class LinkedTreeMap<K extends Comparable<K>, V>
|
||||
extends AbstractMap<K, V> 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 int size = 0;
|
||||
|
||||
private TreeNode<K, V> root;
|
||||
|
||||
// Store the head and tail to preserve the ordering of nodes inserted into tree
|
||||
private TreeNode<K, V> head;
|
||||
private TreeNode<K, V> tail;
|
||||
|
||||
public Set<Map.Entry<K, V>> entrySet() {
|
||||
return new EntrySet();
|
||||
}
|
||||
|
||||
public boolean containsKey(K key) {
|
||||
return (find(key) != null);
|
||||
}
|
||||
|
||||
public V get(K key) {
|
||||
TreeNode<K, V> 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<K, V>(null, null, key, value);
|
||||
head = root;
|
||||
tail = root;
|
||||
size++;
|
||||
return null;
|
||||
} else {
|
||||
return findAndUpdateOrCreateNode(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
private V findAndUpdateOrCreateNode(K key, V value) {
|
||||
TreeNode<K, V> parent;
|
||||
int lastCompare;
|
||||
|
||||
TreeNode<K, V> 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<K, V> newEntry = new TreeNode<K, V>(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<K, V> x) {
|
||||
x.color = RED;
|
||||
|
||||
while (x != null && x != root && x.parent.color == RED) {
|
||||
if (x.parent == leftOf(parentOf(parentOf(x)))) {
|
||||
TreeNode<K, V> 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<K, V> 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 <K extends Comparable<K>, V> TreeNode<K, V> parentOf(TreeNode<K, V> e) {
|
||||
return (e != null ? e.parent : null);
|
||||
}
|
||||
|
||||
private static <K extends Comparable<K>, V> boolean colorOf(TreeNode<K, V> e) {
|
||||
return (e != null ? e.color : BLACK);
|
||||
}
|
||||
|
||||
private static <K extends Comparable<K>, V> TreeNode<K, V> leftOf(TreeNode<K, V> e) {
|
||||
return (e != null ? e.left : null);
|
||||
}
|
||||
|
||||
private static <K extends Comparable<K>, V> TreeNode<K, V> rightOf(TreeNode<K, V> e) {
|
||||
return (e != null ? e.right : null);
|
||||
}
|
||||
|
||||
private static <K extends Comparable<K>, V> void setColor(TreeNode<K, V> e, boolean c) {
|
||||
if (e != null){
|
||||
e.color = c;
|
||||
}
|
||||
}
|
||||
|
||||
private static <K extends Comparable<K>, V> TreeNode<K, V> successor(TreeNode<K, V> t) {
|
||||
if (t == null) {
|
||||
return null;
|
||||
} else if (t.right != null) {
|
||||
TreeNode<K, V> p = t.right;
|
||||
while (p.left != null) {
|
||||
p = p.left;
|
||||
}
|
||||
return p;
|
||||
} else {
|
||||
TreeNode<K, V> p = t.parent;
|
||||
TreeNode<K, V> ch = t;
|
||||
while (p != null && ch == p.right) {
|
||||
ch = p;
|
||||
p = p.parent;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
}
|
||||
|
||||
private void rotateLeft(TreeNode<K, V> p) {
|
||||
if (p != null) {
|
||||
TreeNode<K, V> 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<K, V> p) {
|
||||
if (p != null) {
|
||||
TreeNode<K, V> 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<K, V> entry = find(key);
|
||||
if (entry == null) {
|
||||
return null;
|
||||
} else {
|
||||
size--;
|
||||
V rval = entry.getValue();
|
||||
preserveOrderForRemoval(entry);
|
||||
removeNode(entry);
|
||||
return rval;
|
||||
}
|
||||
}
|
||||
|
||||
private void removeNode(TreeNode<K, V> p) {
|
||||
if (p.left != null && p.right != null) {
|
||||
TreeNode<K, V> s = successor(p);
|
||||
p.key = s.key;
|
||||
p.value = s.value;
|
||||
p = s;
|
||||
}
|
||||
|
||||
TreeNode<K, V> 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<K, V> p) {
|
||||
// Preserve insertion order for entry set iteration
|
||||
if (p == head) {
|
||||
head = p.next;
|
||||
}
|
||||
if (p == tail) {
|
||||
tail = p.previous;
|
||||
}
|
||||
|
||||
TreeNode<K, V> previousNode = p.previous;
|
||||
TreeNode<K, V> nextNode = p.next;
|
||||
if (previousNode != null) {
|
||||
previousNode.next = nextNode;
|
||||
}
|
||||
if (nextNode != null) {
|
||||
nextNode.previous = previousNode;
|
||||
}
|
||||
}
|
||||
|
||||
private void fixAfterDeletion(TreeNode<K, V> x) {
|
||||
while (x != root && colorOf(x) == BLACK) {
|
||||
if (x == leftOf(parentOf(x))) {
|
||||
TreeNode<K, V> 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<K, V> 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<K, V>(this);
|
||||
}
|
||||
|
||||
private TreeNode<K, V> find(K key) {
|
||||
for (TreeNode<K, V> 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<K extends Comparable<K>, V> implements Map.Entry<K, V> {
|
||||
private K key;
|
||||
private V value;
|
||||
private TreeNode<K, V> parent;
|
||||
private TreeNode<K, V> left;
|
||||
private TreeNode<K, V> right;
|
||||
|
||||
// Used for rebalance tree
|
||||
private boolean color = BLACK;
|
||||
|
||||
// This is used for preserving the insertion order
|
||||
private TreeNode<K, V> next;
|
||||
private TreeNode<K, V> previous;
|
||||
|
||||
TreeNode(TreeNode<K, V> parent, TreeNode<K, V> 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<Entry<K, V>> {
|
||||
@Override
|
||||
public Iterator<Map.Entry<K, V>> iterator() {
|
||||
return new LinkedTreeIterator(head);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return LinkedTreeMap.this.size();
|
||||
}
|
||||
}
|
||||
|
||||
private class LinkedTreeIterator implements Iterator<Map.Entry<K, V>> {
|
||||
private TreeNode<K, V> current;
|
||||
|
||||
private LinkedTreeIterator(TreeNode<K, V> first) {
|
||||
this.current = first;
|
||||
}
|
||||
|
||||
public boolean hasNext() {
|
||||
return current != null;
|
||||
}
|
||||
|
||||
public Map.Entry<K, V> next() {
|
||||
TreeNode<K, V> rval = current;
|
||||
current = current.next;
|
||||
return rval;
|
||||
}
|
||||
|
||||
public final void remove() {
|
||||
LinkedTreeMap.this.remove(current.getKey());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,565 +0,0 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You 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.AbstractCollection;
|
||||
import java.util.AbstractMap;
|
||||
import java.util.AbstractSet;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* A map of strings to values. Like LinkedHashMap, this map's iteration order is
|
||||
* well defined: it is the order that elements were inserted into the map. This
|
||||
* map does not support null keys.
|
||||
*
|
||||
* <p>This implementation was derived from Android 4.0's LinkedHashMap.
|
||||
*/
|
||||
public final class StringMap<V> extends AbstractMap<String, V> implements Serializable {
|
||||
/**
|
||||
* Min capacity (other than zero) for a HashMap. Must be a power of two
|
||||
* greater than 1 (and less than 1 << 30).
|
||||
*/
|
||||
private static final int MINIMUM_CAPACITY = 4;
|
||||
|
||||
/**
|
||||
* Max capacity for a HashMap. Must be a power of two >= MINIMUM_CAPACITY.
|
||||
*/
|
||||
private static final int MAXIMUM_CAPACITY = 1 << 30;
|
||||
|
||||
/**
|
||||
* Max number of collisions in a single bucket before falling back to
|
||||
* an unpredictable hash code.
|
||||
*/
|
||||
private static final int MAX_COLLISIONS = 512;
|
||||
|
||||
/**
|
||||
* A dummy entry in the circular linked list of entries in the map.
|
||||
* The first real entry is header.nxt, and the last is header.prv.
|
||||
* If the map is empty, header.nxt == header && header.prv == header.
|
||||
*/
|
||||
private LinkedEntry<V> header;
|
||||
|
||||
/**
|
||||
* An empty table shared by all zero-capacity maps (typically from default
|
||||
* constructor). It is never written to, and replaced on first put. Its size
|
||||
* is set to half the minimum, so that the first resize will create a
|
||||
* minimum-sized table.
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static final Entry[] EMPTY_TABLE = new LinkedEntry[MINIMUM_CAPACITY >>> 1];
|
||||
|
||||
/**
|
||||
* The hash table. If this hash map contains a mapping for null, it is
|
||||
* not represented this hash table.
|
||||
*/
|
||||
private LinkedEntry<V>[] table;
|
||||
|
||||
/**
|
||||
* The number of mappings in this hash map.
|
||||
*/
|
||||
private int size;
|
||||
|
||||
/**
|
||||
* The table is rehashed when its size exceeds this threshold.
|
||||
* The value of this field is generally .75 * capacity, except when
|
||||
* the capacity is zero, as described in the EMPTY_TABLE declaration
|
||||
* above.
|
||||
*/
|
||||
private int threshold;
|
||||
|
||||
/**
|
||||
* True to use String.hashCode(), which is cached per-string. False to use
|
||||
* less predictable (but uncached) hash algorithm.
|
||||
*/
|
||||
private boolean useFastHash = true;
|
||||
|
||||
// Views - lazily initialized
|
||||
private Set<String> keySet;
|
||||
private Set<Entry<String, V>> entrySet;
|
||||
private Collection<V> values;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public StringMap() {
|
||||
table = (LinkedEntry<V>[]) EMPTY_TABLE;
|
||||
threshold = -1; // Forces first put invocation to replace EMPTY_TABLE
|
||||
header = new LinkedEntry<V>();
|
||||
}
|
||||
|
||||
@Override public int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
@Override public boolean containsKey(Object key) {
|
||||
return key instanceof String && getEntry((String) key) != null;
|
||||
}
|
||||
|
||||
@Override public V get(Object key) {
|
||||
if (key instanceof String) {
|
||||
LinkedEntry<V> entry = getEntry((String) key);
|
||||
return entry != null ? entry.value : null;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private LinkedEntry<V> getEntry(String key) {
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int hash = useFastHash ? fastHash(key) : unpredictableHash(key);
|
||||
LinkedEntry<V>[] tab = table;
|
||||
for (LinkedEntry<V> e = tab[hash & (tab.length - 1)]; e != null; e = e.next) {
|
||||
String eKey = e.key;
|
||||
if (eKey == key || (e.hash == hash && key.equals(eKey))) {
|
||||
return e;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override public V put(String key, V value) {
|
||||
if (key == null) {
|
||||
throw new NullPointerException("key == null");
|
||||
}
|
||||
|
||||
int collisionCount = 0;
|
||||
int hash = useFastHash ? fastHash(key) : unpredictableHash(key);
|
||||
LinkedEntry<V>[] tab = table;
|
||||
int index = hash & (tab.length - 1);
|
||||
for (LinkedEntry<V> e = tab[index]; e != null; e = e.next) {
|
||||
collisionCount++;
|
||||
if (e.hash == hash && key.equals(e.key)) {
|
||||
V oldValue = e.value;
|
||||
e.value = value;
|
||||
return oldValue;
|
||||
}
|
||||
}
|
||||
|
||||
// No entry for (non-null) key is present; create one
|
||||
if (size++ > threshold) {
|
||||
tab = doubleCapacity();
|
||||
index = hash & (tab.length - 1);
|
||||
}
|
||||
addNewEntry(key, value, hash, index);
|
||||
|
||||
/*
|
||||
* If we suffer a very large number of collisions, fall back from the cached
|
||||
* String.hashCode() to an (uncached) hash code that isn't predictable.
|
||||
*/
|
||||
if (useFastHash && collisionCount >= MAX_COLLISIONS) {
|
||||
LinkedEntry<V> entry = header.nxt;
|
||||
|
||||
// clear the table
|
||||
Arrays.fill(table, null);
|
||||
size = 0;
|
||||
header.nxt = header.prv = header;
|
||||
useFastHash = false;
|
||||
|
||||
// fill it up in iteration order
|
||||
for (; entry != header; entry = entry.nxt) {
|
||||
put(entry.key, entry.value);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void addNewEntry(String key, V value, int hash, int index) {
|
||||
LinkedEntry<V> header = this.header;
|
||||
|
||||
// Create new entry, link it on to list, and put it into table
|
||||
LinkedEntry<V> oldTail = header.prv;
|
||||
LinkedEntry<V> newTail = new LinkedEntry<V>(
|
||||
key, value, hash, table[index], header, oldTail);
|
||||
table[index] = oldTail.nxt = header.prv = newTail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate a table of the given capacity and set the threshold accordingly.
|
||||
* @param newCapacity must be a power of two
|
||||
*/
|
||||
private LinkedEntry<V>[] makeTable(int newCapacity) {
|
||||
@SuppressWarnings("unchecked")
|
||||
LinkedEntry<V>[] newTable = (LinkedEntry<V>[]) new LinkedEntry[newCapacity];
|
||||
table = newTable;
|
||||
threshold = (newCapacity >> 1) + (newCapacity >> 2); // 3/4 capacity
|
||||
return newTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Doubles the capacity of the hash table. Existing entries are placed in
|
||||
* the correct bucket on the enlarged table. If the current capacity is,
|
||||
* MAXIMUM_CAPACITY, this method is a no-op. Returns the table, which
|
||||
* will be new unless we were already at MAXIMUM_CAPACITY.
|
||||
*/
|
||||
private LinkedEntry<V>[] doubleCapacity() {
|
||||
LinkedEntry<V>[] oldTable = table;
|
||||
int oldCapacity = oldTable.length;
|
||||
if (oldCapacity == MAXIMUM_CAPACITY) {
|
||||
return oldTable;
|
||||
}
|
||||
int newCapacity = oldCapacity * 2;
|
||||
LinkedEntry<V>[] newTable = makeTable(newCapacity);
|
||||
if (size == 0) {
|
||||
return newTable;
|
||||
}
|
||||
|
||||
for (int j = 0; j < oldCapacity; j++) {
|
||||
/*
|
||||
* Rehash the bucket using the minimum number of field writes.
|
||||
* This is the most subtle and delicate code in the class.
|
||||
*/
|
||||
LinkedEntry<V> e = oldTable[j];
|
||||
if (e == null) {
|
||||
continue;
|
||||
}
|
||||
int highBit = e.hash & oldCapacity;
|
||||
LinkedEntry<V> broken = null;
|
||||
newTable[j | highBit] = e;
|
||||
for (LinkedEntry<V> n = e.next; n != null; e = n, n = n.next) {
|
||||
int nextHighBit = n.hash & oldCapacity;
|
||||
if (nextHighBit != highBit) {
|
||||
if (broken == null) {
|
||||
newTable[j | nextHighBit] = n;
|
||||
} else {
|
||||
broken.next = n;
|
||||
}
|
||||
broken = e;
|
||||
highBit = nextHighBit;
|
||||
}
|
||||
}
|
||||
if (broken != null) {
|
||||
broken.next = null;
|
||||
}
|
||||
}
|
||||
return newTable;
|
||||
}
|
||||
|
||||
@Override public V remove(Object key) {
|
||||
if (key == null || !(key instanceof String)) {
|
||||
return null;
|
||||
}
|
||||
int hash = useFastHash ? fastHash(key) : unpredictableHash((String) key);
|
||||
LinkedEntry<V>[] tab = table;
|
||||
int index = hash & (tab.length - 1);
|
||||
for (LinkedEntry<V> e = tab[index], prev = null;
|
||||
e != null; prev = e, e = e.next) {
|
||||
if (e.hash == hash && key.equals(e.key)) {
|
||||
if (prev == null) {
|
||||
tab[index] = e.next;
|
||||
} else {
|
||||
prev.next = e.next;
|
||||
}
|
||||
size--;
|
||||
unlink(e);
|
||||
return e.value;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void unlink(LinkedEntry<V> e) {
|
||||
e.prv.nxt = e.nxt;
|
||||
e.nxt.prv = e.prv;
|
||||
e.nxt = e.prv = null; // Help the GC (for performance)
|
||||
}
|
||||
|
||||
@Override public void clear() {
|
||||
if (size != 0) {
|
||||
Arrays.fill(table, null);
|
||||
size = 0;
|
||||
}
|
||||
|
||||
// Clear all links to help GC
|
||||
LinkedEntry<V> header = this.header;
|
||||
for (LinkedEntry<V> e = header.nxt; e != header; ) {
|
||||
LinkedEntry<V> nxt = e.nxt;
|
||||
e.nxt = e.prv = null;
|
||||
e = nxt;
|
||||
}
|
||||
|
||||
header.nxt = header.prv = header;
|
||||
}
|
||||
|
||||
@Override public Set<String> keySet() {
|
||||
Set<String> ks = keySet;
|
||||
return (ks != null) ? ks : (keySet = new KeySet());
|
||||
}
|
||||
|
||||
@Override public Collection<V> values() {
|
||||
Collection<V> vs = values;
|
||||
return (vs != null) ? vs : (values = new Values());
|
||||
}
|
||||
|
||||
public Set<Entry<String, V>> entrySet() {
|
||||
Set<Entry<String, V>> es = entrySet;
|
||||
return (es != null) ? es : (entrySet = new EntrySet());
|
||||
}
|
||||
|
||||
static class LinkedEntry<V> implements Entry<String, V> {
|
||||
final String key;
|
||||
V value;
|
||||
final int hash;
|
||||
LinkedEntry<V> next;
|
||||
LinkedEntry<V> nxt;
|
||||
LinkedEntry<V> prv;
|
||||
|
||||
/** Create the header entry */
|
||||
LinkedEntry() {
|
||||
this(null, null, 0, null, null, null);
|
||||
nxt = prv = this;
|
||||
}
|
||||
|
||||
LinkedEntry(String key, V value, int hash, LinkedEntry<V> next,
|
||||
LinkedEntry<V> nxt, LinkedEntry<V> prv) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
this.hash = hash;
|
||||
this.next = next;
|
||||
this.nxt = nxt;
|
||||
this.prv = prv;
|
||||
}
|
||||
|
||||
public final String getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public final V getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public final V setValue(V value) {
|
||||
V oldValue = this.value;
|
||||
this.value = value;
|
||||
return oldValue;
|
||||
}
|
||||
|
||||
@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 == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode());
|
||||
}
|
||||
|
||||
@Override public final String toString() {
|
||||
return key + "=" + value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the mapping from key to value and returns true if this mapping
|
||||
* exists; otherwise, returns does nothing and returns false.
|
||||
*/
|
||||
private boolean removeMapping(Object key, Object value) {
|
||||
if (key == null || !(key instanceof String)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int hash = useFastHash ? fastHash(key) : unpredictableHash((String) key);
|
||||
LinkedEntry<V>[] tab = table;
|
||||
int index = hash & (tab.length - 1);
|
||||
for (LinkedEntry<V> e = tab[index], prev = null; e != null; prev = e, e = e.next) {
|
||||
if (e.hash == hash && key.equals(e.key)) {
|
||||
if (value == null ? e.value != null : !value.equals(e.value)) {
|
||||
return false; // Map has wrong value for key
|
||||
}
|
||||
if (prev == null) {
|
||||
tab[index] = e.next;
|
||||
} else {
|
||||
prev.next = e.next;
|
||||
}
|
||||
size--;
|
||||
unlink(e);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false; // No entry for key
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<String, V>(this);
|
||||
}
|
||||
|
||||
private abstract class LinkedHashIterator<T> implements Iterator<T> {
|
||||
LinkedEntry<V> next = header.nxt;
|
||||
LinkedEntry<V> lastReturned = null;
|
||||
|
||||
public final boolean hasNext() {
|
||||
return next != header;
|
||||
}
|
||||
|
||||
final LinkedEntry<V> nextEntry() {
|
||||
LinkedEntry<V> e = next;
|
||||
if (e == header) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
next = e.nxt;
|
||||
return lastReturned = e;
|
||||
}
|
||||
|
||||
public final void remove() {
|
||||
if (lastReturned == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
StringMap.this.remove(lastReturned.key);
|
||||
lastReturned = null;
|
||||
}
|
||||
}
|
||||
|
||||
private final class KeySet extends AbstractSet<String> {
|
||||
public Iterator<String> iterator() {
|
||||
return new LinkedHashIterator<String>() {
|
||||
public final String next() {
|
||||
return nextEntry().key;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public boolean contains(Object o) {
|
||||
return containsKey(o);
|
||||
}
|
||||
|
||||
public boolean remove(Object o) {
|
||||
int oldSize = size;
|
||||
StringMap.this.remove(o);
|
||||
return size != oldSize;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
StringMap.this.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private final class Values extends AbstractCollection<V> {
|
||||
public Iterator<V> iterator() {
|
||||
return new LinkedHashIterator<V>() {
|
||||
public final V next() {
|
||||
return nextEntry().value;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public boolean contains(Object o) {
|
||||
return containsValue(o);
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
StringMap.this.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private final class EntrySet extends AbstractSet<Entry<String, V>> {
|
||||
public Iterator<Entry<String, V>> iterator() {
|
||||
return new LinkedHashIterator<Map.Entry<String, V>>() {
|
||||
public final Map.Entry<String, V> next() {
|
||||
return nextEntry();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public boolean contains(Object o) {
|
||||
if (!(o instanceof Entry)) {
|
||||
return false;
|
||||
}
|
||||
Entry<?, ?> e = (Entry<?, ?>) o;
|
||||
V mappedValue = get(e.getKey());
|
||||
return mappedValue != null && mappedValue.equals(e.getValue());
|
||||
}
|
||||
|
||||
public boolean remove(Object o) {
|
||||
if (!(o instanceof Entry)) {
|
||||
return false;
|
||||
}
|
||||
Entry<?, ?> e = (Entry<?, ?>) o;
|
||||
return removeMapping(e.getKey(), e.getValue());
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
public void clear() {
|
||||
StringMap.this.clear();
|
||||
}
|
||||
}
|
||||
|
||||
private static int fastHash(Object key) {
|
||||
int h = key.hashCode();
|
||||
// Apply Doug Lea's supplemental hash function to avoid collisions for
|
||||
// hashes that do not differ in lower or upper bits.
|
||||
h ^= (h >>> 20) ^ (h >>> 12);
|
||||
return h ^ (h >>> 7) ^ (h >>> 4);
|
||||
}
|
||||
|
||||
private static final int seed = new Random().nextInt();
|
||||
private static int unpredictableHash(String key) {
|
||||
// Ensuring that the hash is unpredictable and well distributed.
|
||||
//
|
||||
// Finding unpredictable hash functions is a bit of a dark art as we need to balance
|
||||
// good unpredictability (to avoid DoS) and good distribution (for performance).
|
||||
//
|
||||
// We achieve this by using the same algorithm as the Perl version, but this implementation
|
||||
// is being written from scratch by inder who has never seen the
|
||||
// Perl version (for license compliance).
|
||||
//
|
||||
// TODO: investigate http://code.google.com/p/cityhash/ and http://code.google.com/p/smhasher/
|
||||
// both of which may have better distribution and/or unpredictability.
|
||||
int h = seed;
|
||||
for (int i = 0; i < key.length(); ++i) {
|
||||
int h2 = h + key.charAt(i);
|
||||
int h3 = h2 + h2 << 10; // h2 * 1024
|
||||
h = h3 ^ (h3 >>> 6); // h3 / 64
|
||||
}
|
||||
|
||||
// Apply Doug Lea's supplemental hash function to avoid collisions for
|
||||
// hashes that do not differ in lower or upper bits.
|
||||
h ^= (h >>> 20) ^ (h >>> 12);
|
||||
return h ^ (h >>> 7) ^ (h >>> 4);
|
||||
}
|
||||
}
|
@ -19,11 +19,12 @@ 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.StringMap;
|
||||
import com.google.gson.internal.LinkedTreeMap;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonToken;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@ -63,7 +64,7 @@ public final class ObjectTypeAdapter extends TypeAdapter<Object> {
|
||||
return list;
|
||||
|
||||
case BEGIN_OBJECT:
|
||||
Map<String, Object> map = new StringMap<Object>();
|
||||
Map<String, Object> map = new LinkedTreeMap<String, Object>();
|
||||
in.beginObject();
|
||||
while (in.hasNext()) {
|
||||
map.put(in.nextName(), read(in));
|
||||
|
@ -40,6 +40,7 @@ public class JsonObjectTest extends TestCase {
|
||||
JsonElement removedElement = jsonObj.remove(propertyName);
|
||||
assertEquals(value, removedElement);
|
||||
assertFalse(jsonObj.has(propertyName));
|
||||
assertNull(jsonObj.get(propertyName));
|
||||
}
|
||||
|
||||
public void testAddingNullPropertyValue() throws Exception {
|
||||
|
@ -590,11 +590,13 @@ public class DefaultTypeAdaptersTest extends TestCase {
|
||||
assertEquals("{\"foo\":1,\"bar\":2}", gson.toJson(object, JsonElement.class));
|
||||
}
|
||||
|
||||
public void testJsonObjectDeerialization() {
|
||||
public void testJsonObjectDeserialization() {
|
||||
JsonObject object = new JsonObject();
|
||||
object.add("foo", new JsonPrimitive(1));
|
||||
object.add("bar", new JsonPrimitive(2));
|
||||
assertEquals(object, gson.fromJson("{\"foo\":1,\"bar\":2}", JsonElement.class));
|
||||
|
||||
JsonElement actual = gson.fromJson("{\"foo\":1,\"bar\":2}", JsonElement.class);
|
||||
assertEquals(object, actual);
|
||||
}
|
||||
|
||||
public void testJsonNullDeerialization() {
|
||||
|
@ -16,15 +16,6 @@
|
||||
|
||||
package com.google.gson.functional;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.InstanceCreator;
|
||||
@ -40,6 +31,16 @@ import com.google.gson.common.TestTypes;
|
||||
import com.google.gson.internal.$Gson$Types;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.SortedMap;
|
||||
import java.util.TreeMap;
|
||||
|
||||
/**
|
||||
* Functional test for Json serialization and deserialization for Maps
|
||||
*
|
||||
@ -156,6 +157,22 @@ public class MapTest extends TestCase {
|
||||
assertEquals("456", map.get(123));
|
||||
}
|
||||
|
||||
public void testHashMapDeserialization() throws Exception {
|
||||
Type typeOfMap = new TypeToken<HashMap<Integer, String>>() {}.getType();
|
||||
HashMap<Integer, String> map = gson.fromJson("{\"123\":\"456\"}", typeOfMap);
|
||||
assertEquals(1, map.size());
|
||||
assertTrue(map.containsKey(123));
|
||||
assertEquals("456", map.get(123));
|
||||
}
|
||||
|
||||
public void testSortedMap() throws Exception {
|
||||
Type typeOfMap = new TypeToken<SortedMap<Integer, String>>() {}.getType();
|
||||
SortedMap<Integer, String> map = gson.fromJson("{\"123\":\"456\"}", typeOfMap);
|
||||
assertEquals(1, map.size());
|
||||
assertTrue(map.containsKey(123));
|
||||
assertEquals("456", map.get(123));
|
||||
}
|
||||
|
||||
public void testParameterizedMapSubclassSerialization() {
|
||||
MyParameterizedMap<String, String> map = new MyParameterizedMap<String, String>(10);
|
||||
map.put("a", "b");
|
||||
|
@ -0,0 +1,140 @@
|
||||
/*
|
||||
* 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<String, Integer> map = new LinkedTreeMap<String, Integer>();
|
||||
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 testSingleElement() throws Exception {
|
||||
LinkedTreeMap<String, Integer> map = new LinkedTreeMap<String, Integer>();
|
||||
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<String, Integer> map = new LinkedTreeMap<String, Integer>();
|
||||
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<String, Integer> map = new LinkedTreeMap<String, Integer>();
|
||||
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<Map.Entry<String,Integer>> entries = map.entrySet();
|
||||
assertEquals(keys.length, entries.size());
|
||||
assertIterationOrder(entries, keys, values);
|
||||
}
|
||||
|
||||
public void testEqualsAndHashCode() throws Exception {
|
||||
LinkedTreeMap<String, Integer> map1 = new LinkedTreeMap<String, Integer>();
|
||||
map1.put("A", 1);
|
||||
map1.put("B", 2);
|
||||
map1.put("C", 3);
|
||||
map1.put("D", 4);
|
||||
|
||||
LinkedTreeMap<String, Integer> map2 = new LinkedTreeMap<String, Integer>();
|
||||
map2.put("C", 3);
|
||||
map2.put("B", 2);
|
||||
map2.put("D", 4);
|
||||
map2.put("A", 1);
|
||||
|
||||
MoreAsserts.assertEqualsAndHashCode(map1, map2);
|
||||
}
|
||||
|
||||
private void assertIterationOrder(Set<Map.Entry<String, Integer>> entries, String[] keys, int[] values) {
|
||||
int i = 0;
|
||||
for (Iterator<Map.Entry<String, Integer>> iterator = entries.iterator(); iterator.hasNext(); ++i) {
|
||||
Map.Entry<String, Integer> entry = iterator.next();
|
||||
assertEquals(keys[i], entry.getKey());
|
||||
assertEquals(values[i], (int) entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2010 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.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public final class StringMapTest extends TestCase {
|
||||
public void testFallbackFromTooManyCollisions() {
|
||||
int count = 10000;
|
||||
StringMap<Integer> map = new StringMap<Integer>();
|
||||
int index = 0;
|
||||
List<String> collidingStrings = collidingStrings(1 << 20, count);
|
||||
for (String string : collidingStrings) {
|
||||
map.put(string, index++);
|
||||
}
|
||||
assertEquals(collidingStrings.size(), map.size());
|
||||
Iterator<Map.Entry<String, Integer>> iterator = map.entrySet().iterator();
|
||||
for (int i = 0; i < count; i++) {
|
||||
Map.Entry<String, Integer> entry = iterator.next();
|
||||
assertEquals(collidingStrings.get(i), entry.getKey());
|
||||
assertEquals(Integer.valueOf(i), entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param h0 the hash code of the generated strings
|
||||
*/
|
||||
private List<String> collidingStrings(int h0, int count) {
|
||||
List<String> result = new ArrayList<String>(count);
|
||||
int p1 = 31;
|
||||
int p0 = 31 * 31;
|
||||
int maxChar = Character.MAX_VALUE;
|
||||
for (char c0 = 0; c0 <= maxChar && c0 <= h0 / p0; c0++) {
|
||||
int h1 = h0 - c0 * p0;
|
||||
for (char c1 = 0; c1 <= maxChar && c1 <= h1 / p1; c1++) {
|
||||
int h2 = h1 - c1 * p1;
|
||||
char c2 = (char) h2;
|
||||
if (h2 != c2) {
|
||||
continue;
|
||||
}
|
||||
result.add(new String(new char[] { c0, c1, c2 } ));
|
||||
if (result.size() == count) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("Couldn't find " + count + " strings with hashCode " + h0);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user