diff --git a/gson/src/main/java/com/google/gson/JsonObject.java b/gson/src/main/java/com/google/gson/JsonObject.java index 7333400d..523fb755 100644 --- a/gson/src/main/java/com/google/gson/JsonObject.java +++ b/gson/src/main/java/com/google/gson/JsonObject.java @@ -34,7 +34,7 @@ public final class JsonObject extends JsonElement { // 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 members = new StringMap(); + private final StringMap members = new StringMap(); /** * Creates an empty JsonObject. diff --git a/gson/src/main/java/com/google/gson/internal/StringMap.java b/gson/src/main/java/com/google/gson/internal/StringMap.java index caa21452..c95075b6 100644 --- a/gson/src/main/java/com/google/gson/internal/StringMap.java +++ b/gson/src/main/java/com/google/gson/internal/StringMap.java @@ -34,9 +34,7 @@ import java.util.Set; * *

This implementation was derived from Android 4.0's LinkedHashMap. */ -public final class StringMap extends AbstractMap { - // TODO: defend against predictable hash collisions - +public final class StringMap extends AbstractMap { /** * Min capacity (other than zero) for a HashMap. Must be a power of two * greater than 1 (and less than 1 << 30). @@ -53,7 +51,7 @@ public final class StringMap extends AbstractMap { * 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 header; + private LinkedEntry header; /** * An empty table shared by all zero-capacity maps (typically from default @@ -67,7 +65,7 @@ public final class StringMap extends AbstractMap { * The hash table. If this hash map contains a mapping for null, it is * not represented this hash table. */ - private LinkedEntry[] table; + private LinkedEntry[] table; /** * The number of mappings in this hash map. @@ -83,15 +81,15 @@ public final class StringMap extends AbstractMap { private int threshold; // Views - lazily initialized - private Set keySet; - private Set> entrySet; + private Set keySet; + private Set> entrySet; private Collection values; @SuppressWarnings("unchecked") public StringMap() { - table = (LinkedEntry[]) EMPTY_TABLE; + table = (LinkedEntry[]) EMPTY_TABLE; threshold = -1; // Forces first put invocation to replace EMPTY_TABLE - header = new LinkedEntry(); + header = new LinkedEntry(); } @Override public int size() { @@ -99,23 +97,27 @@ public final class StringMap extends AbstractMap { } @Override public boolean containsKey(Object key) { - return getEntry(key) != null; + return key instanceof String && getEntry((String) key) != null; } @Override public V get(Object key) { - LinkedEntry entry = getEntry(key); - return entry != null ? entry.value : null; + if (key instanceof String) { + LinkedEntry entry = getEntry((String) key); + return entry != null ? entry.value : null; + } else { + return null; + } } - private LinkedEntry getEntry(Object key) { + private LinkedEntry getEntry(String key) { if (key == null) { return null; } - int hash = secondaryHash(key.hashCode()); - LinkedEntry[] tab = table; - for (LinkedEntry e = tab[hash & (tab.length - 1)]; e != null; e = e.next) { - K eKey = e.key; + int hash = hash(key); + LinkedEntry[] tab = table; + for (LinkedEntry 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; } @@ -123,15 +125,15 @@ public final class StringMap extends AbstractMap { return null; } - @Override public V put(K key, V value) { + @Override public V put(String key, V value) { if (key == null) { throw new NullPointerException("key == null"); } - int hash = secondaryHash(key.hashCode()); - LinkedEntry[] tab = table; + int hash = hash(key); + LinkedEntry[] tab = table; int index = hash & (tab.length - 1); - for (LinkedEntry e = tab[index]; e != null; e = e.next) { + for (LinkedEntry e = tab[index]; e != null; e = e.next) { if (e.hash == hash && key.equals(e.key)) { V oldValue = e.value; e.value = value; @@ -148,12 +150,12 @@ public final class StringMap extends AbstractMap { return null; } - private void addNewEntry(K key, V value, int hash, int index) { - LinkedEntry header = this.header; + private void addNewEntry(String key, V value, int hash, int index) { + LinkedEntry header = this.header; // Create new entry, link it on to list, and put it into table - LinkedEntry oldTail = header.prv; - LinkedEntry newTail = new LinkedEntry( + LinkedEntry oldTail = header.prv; + LinkedEntry newTail = new LinkedEntry( key, value, hash, table[index], header, oldTail); table[index] = oldTail.nxt = header.prv = newTail; } @@ -162,9 +164,9 @@ public final class StringMap extends AbstractMap { * Allocate a table of the given capacity and set the threshold accordingly. * @param newCapacity must be a power of two */ - private LinkedEntry[] makeTable(int newCapacity) { + private LinkedEntry[] makeTable(int newCapacity) { @SuppressWarnings("unchecked") - LinkedEntry[] newTable = (LinkedEntry[]) new LinkedEntry[newCapacity]; + LinkedEntry[] newTable = (LinkedEntry[]) new LinkedEntry[newCapacity]; table = newTable; threshold = (newCapacity >> 1) + (newCapacity >> 2); // 3/4 capacity return newTable; @@ -176,14 +178,14 @@ public final class StringMap extends AbstractMap { * MAXIMUM_CAPACITY, this method is a no-op. Returns the table, which * will be new unless we were already at MAXIMUM_CAPACITY. */ - private LinkedEntry[] doubleCapacity() { - LinkedEntry[] oldTable = table; + private LinkedEntry[] doubleCapacity() { + LinkedEntry[] oldTable = table; int oldCapacity = oldTable.length; if (oldCapacity == MAXIMUM_CAPACITY) { return oldTable; } int newCapacity = oldCapacity * 2; - LinkedEntry[] newTable = makeTable(newCapacity); + LinkedEntry[] newTable = makeTable(newCapacity); if (size == 0) { return newTable; } @@ -193,14 +195,14 @@ public final class StringMap extends AbstractMap { * Rehash the bucket using the minimum number of field writes. * This is the most subtle and delicate code in the class. */ - LinkedEntry e = oldTable[j]; + LinkedEntry e = oldTable[j]; if (e == null) { continue; } int highBit = e.hash & oldCapacity; - LinkedEntry broken = null; + LinkedEntry broken = null; newTable[j | highBit] = e; - for (LinkedEntry n = e.next; n != null; e = n, n = n.next) { + for (LinkedEntry n = e.next; n != null; e = n, n = n.next) { int nextHighBit = n.hash & oldCapacity; if (nextHighBit != highBit) { if (broken == null) { @@ -220,13 +222,13 @@ public final class StringMap extends AbstractMap { } @Override public V remove(Object key) { - if (key == null) { + if (key == null || !(key instanceof String)) { return null; } - int hash = secondaryHash(key.hashCode()); - LinkedEntry[] tab = table; + int hash = hash((String) key); + LinkedEntry[] tab = table; int index = hash & (tab.length - 1); - for (LinkedEntry e = tab[index], prev = null; + for (LinkedEntry e = tab[index], prev = null; e != null; prev = e, e = e.next) { if (e.hash == hash && key.equals(e.key)) { if (prev == null) { @@ -242,7 +244,7 @@ public final class StringMap extends AbstractMap { return null; } - private void unlink(LinkedEntry e) { + private void unlink(LinkedEntry e) { e.prv.nxt = e.nxt; e.nxt.prv = e.prv; e.nxt = e.prv = null; // Help the GC (for performance) @@ -255,9 +257,9 @@ public final class StringMap extends AbstractMap { } // Clear all links to help GC - LinkedEntry header = this.header; - for (LinkedEntry e = header.nxt; e != header; ) { - LinkedEntry nxt = e.nxt; + LinkedEntry header = this.header; + for (LinkedEntry e = header.nxt; e != header; ) { + LinkedEntry nxt = e.nxt; e.nxt = e.prv = null; e = nxt; } @@ -265,8 +267,8 @@ public final class StringMap extends AbstractMap { header.nxt = header.prv = header; } - @Override public Set keySet() { - Set ks = keySet; + @Override public Set keySet() { + Set ks = keySet; return (ks != null) ? ks : (keySet = new KeySet()); } @@ -275,18 +277,18 @@ public final class StringMap extends AbstractMap { return (vs != null) ? vs : (values = new Values()); } - public Set> entrySet() { - Set> es = entrySet; + public Set> entrySet() { + Set> es = entrySet; return (es != null) ? es : (entrySet = new EntrySet()); } - static class LinkedEntry implements Entry { - final K key; + static class LinkedEntry implements Entry { + final String key; V value; final int hash; - LinkedEntry next; - LinkedEntry nxt; - LinkedEntry prv; + LinkedEntry next; + LinkedEntry nxt; + LinkedEntry prv; /** Create the header entry */ LinkedEntry() { @@ -294,8 +296,8 @@ public final class StringMap extends AbstractMap { nxt = prv = this; } - LinkedEntry(K key, V value, int hash, LinkedEntry next, - LinkedEntry nxt, LinkedEntry prv) { + LinkedEntry(String key, V value, int hash, LinkedEntry next, + LinkedEntry nxt, LinkedEntry prv) { this.key = key; this.value = value; this.hash = hash; @@ -304,7 +306,7 @@ public final class StringMap extends AbstractMap { this.prv = prv; } - public final K getKey() { + public final String getKey() { return key; } @@ -342,14 +344,14 @@ public final class StringMap extends AbstractMap { * exists; otherwise, returns does nothing and returns false. */ private boolean removeMapping(Object key, Object value) { - if (key == null) { + if (key == null || !(key instanceof String)) { return false; } - int hash = secondaryHash(key.hashCode()); - LinkedEntry[] tab = table; + int hash = hash((String) key); + LinkedEntry[] tab = table; int index = hash & (tab.length - 1); - for (LinkedEntry e = tab[index], prev = null; e != null; prev = e, e = e.next) { + for (LinkedEntry 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 @@ -368,15 +370,15 @@ public final class StringMap extends AbstractMap { } private abstract class LinkedHashIterator implements Iterator { - LinkedEntry next = header.nxt; - LinkedEntry lastReturned = null; + LinkedEntry next = header.nxt; + LinkedEntry lastReturned = null; public final boolean hasNext() { return next != header; } - final LinkedEntry nextEntry() { - LinkedEntry e = next; + final LinkedEntry nextEntry() { + LinkedEntry e = next; if (e == header) { throw new NoSuchElementException(); } @@ -393,10 +395,10 @@ public final class StringMap extends AbstractMap { } } - private final class KeySet extends AbstractSet { - public Iterator iterator() { - return new LinkedHashIterator() { - public final K next() { + private final class KeySet extends AbstractSet { + public Iterator iterator() { + return new LinkedHashIterator() { + public final String next() { return nextEntry().key; } }; @@ -443,10 +445,10 @@ public final class StringMap extends AbstractMap { } } - private final class EntrySet extends AbstractSet> { - public Iterator> iterator() { - return new LinkedHashIterator>() { - public final Map.Entry next() { + private final class EntrySet extends AbstractSet> { + public Iterator> iterator() { + return new LinkedHashIterator>() { + public final Map.Entry next() { return nextEntry(); } }; @@ -478,14 +480,18 @@ public final class StringMap extends AbstractMap { } } - /** - * Applies a supplemental hash function to a given hashCode, which defends - * against poor quality hash functions. This is critical because HashMap - * uses power-of-two length hash tables, that otherwise encounter collisions - * for hashCodes that do not differ in lower or upper bits. - */ - private static int secondaryHash(int h) { - // Doug Lea's supplemental hash function + private static int hash(String key) { + // TODO: use an unpredictable hash function + + int h = 0; + for (int i = 0; i < key.length(); i++) { + h = 31 * h + key.charAt(i); + } + + /* + * 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); } 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 77a9a6bd..0ba5ddac 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 @@ -63,8 +63,7 @@ public final class ObjectTypeAdapter extends TypeAdapter { return list; case BEGIN_OBJECT: - // TODO: string map doesn't support null values - Map map = new StringMap(); + Map map = new StringMap(); in.beginObject(); while (in.hasNext()) { map.put(in.nextName(), read(in));