diff --git a/moshi/src/main/java/com/squareup/moshi/LinkedHashTreeMap.java b/moshi/src/main/java/com/squareup/moshi/LinkedHashTreeMap.java deleted file mode 100644 index 8a8903b..0000000 --- a/moshi/src/main/java/com/squareup/moshi/LinkedHashTreeMap.java +++ /dev/null @@ -1,855 +0,0 @@ -/* - * Copyright (C) 2010-2012 Square, 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 - * - * https://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.squareup.moshi; - -import java.io.ObjectStreamException; -import java.io.Serializable; -import java.util.AbstractMap; -import java.util.AbstractSet; -import java.util.Arrays; -import java.util.Comparator; -import java.util.ConcurrentModificationException; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.NoSuchElementException; -import java.util.Set; -import javax.annotation.Nullable; - -/** - * A map of comparable keys to values. Unlike {@code TreeMap}, this class uses insertion order for - * iteration order. Comparison order is only used as an optimization for efficient insertion and - * removal. - * - *

This implementation was derived from Android 4.1's TreeMap and LinkedHashMap classes. - */ -final class LinkedHashTreeMap extends AbstractMap implements Serializable { - @SuppressWarnings({"unchecked", "rawtypes"}) // to avoid Comparable>> - private static final Comparator NATURAL_ORDER = - new Comparator() { - public int compare(Comparable a, Comparable b) { - return a.compareTo(b); - } - }; - - final Comparator comparator; - Node[] table; - final Node header; - int size = 0; - int modCount = 0; - int threshold; - - /** Create a natural order, empty tree map whose keys must be mutually comparable and non-null. */ - LinkedHashTreeMap() { - this(null); - } - - /** - * Create a tree map ordered by {@code comparator}. This map's keys may only be null if {@code - * comparator} permits. - * - * @param comparator the comparator to order elements with, or {@code null} to use the natural - * ordering. - */ - @SuppressWarnings({ - "unchecked", - "rawtypes" // Unsafe! if comparator is null, this assumes K is comparable. - }) - LinkedHashTreeMap(Comparator comparator) { - this.comparator = comparator != null ? comparator : (Comparator) NATURAL_ORDER; - this.header = new Node<>(); - this.table = new Node[16]; // TODO: sizing/resizing policies - this.threshold = (table.length / 2) + (table.length / 4); // 3/4 capacity - } - - @Override - public int size() { - return size; - } - - @Override - public V get(Object key) { - Node node = findByObject(key); - return node != null ? node.value : null; - } - - @Override - public boolean containsKey(Object key) { - return findByObject(key) != null; - } - - @Override - public V put(K key, @Nullable V value) { - if (key == null) { - throw new NullPointerException("key == null"); - } - Node created = find(key, true); - V result = created.value; - created.value = value; - return result; - } - - @Override - public void clear() { - Arrays.fill(table, null); - size = 0; - modCount++; - - // Clear all links to help GC - Node header = this.header; - for (Node e = header.next; e != header; ) { - Node next = e.next; - e.next = e.prev = null; - e = next; - } - - header.next = header.prev = header; - } - - @Override - public V remove(Object key) { - Node node = removeInternalByKey(key); - return node != null ? node.value : null; - } - - /** - * Returns the node at or adjacent to the given key, creating it if requested. - * - * @throws ClassCastException if {@code key} and the tree's keys aren't mutually comparable. - */ - Node find(K key, boolean create) { - Comparator comparator = this.comparator; - Node[] table = this.table; - int hash = secondaryHash(key.hashCode()); - int index = hash & (table.length - 1); - Node nearest = table[index]; - int comparison = 0; - - if (nearest != null) { - // Micro-optimization: avoid polymorphic calls to Comparator.compare(). - @SuppressWarnings("unchecked") // Throws a ClassCastException below if there's trouble. - Comparable comparableKey = - (comparator == NATURAL_ORDER) ? (Comparable) key : null; - - while (true) { - comparison = - (comparableKey != null) - ? comparableKey.compareTo(nearest.key) - : comparator.compare(key, nearest.key); - - // We found the requested key. - if (comparison == 0) { - return nearest; - } - - // If it exists, the key is in a subtree. Go deeper. - Node child = (comparison < 0) ? nearest.left : nearest.right; - if (child == null) { - break; - } - - nearest = child; - } - } - - // The key doesn't exist in this tree. - if (!create) { - return null; - } - - // Create the node and add it to the tree or the table. - Node header = this.header; - Node created; - if (nearest == null) { - // Check that the value is comparable if we didn't do any comparisons. - if (comparator == NATURAL_ORDER && !(key instanceof Comparable)) { - throw new ClassCastException(key.getClass().getName() + " is not Comparable"); - } - created = new Node<>(nearest, key, hash, header, header.prev); - table[index] = created; - } else { - created = new Node<>(nearest, key, hash, header, header.prev); - if (comparison < 0) { // nearest.key is higher - nearest.left = created; - } else { // comparison > 0, nearest.key is lower - nearest.right = created; - } - rebalance(nearest, true); - } - - if (size++ > threshold) { - doubleCapacity(); - } - modCount++; - - return created; - } - - @SuppressWarnings("unchecked") - Node findByObject(Object key) { - try { - return key != null ? find((K) key, false) : null; - } catch (ClassCastException e) { - return null; - } - } - - /** - * Returns this map's entry that has the same key and value as {@code entry}, or null if this map - * has no such entry. - * - *

This method uses the comparator for key equality rather than {@code equals}. If this map's - * comparator isn't consistent with equals (such as {@code String.CASE_INSENSITIVE_ORDER}), then - * {@code remove()} and {@code contains()} will violate the collections API. - */ - Node findByEntry(Entry entry) { - Node mine = findByObject(entry.getKey()); - boolean valuesEqual = mine != null && equal(mine.value, entry.getValue()); - return valuesEqual ? mine : null; - } - - private boolean equal(Object a, Object b) { - return a == b || (a != null && a.equals(b)); - } - - /** - * 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 - h ^= (h >>> 20) ^ (h >>> 12); - return h ^ (h >>> 7) ^ (h >>> 4); - } - - /** - * Removes {@code node} from this tree, rearranging the tree's structure as necessary. - * - * @param unlink true to also unlink this node from the iteration linked list. - */ - void removeInternal(Node node, boolean unlink) { - if (unlink) { - node.prev.next = node.next; - node.next.prev = node.prev; - node.next = node.prev = null; // Help the GC (for performance) - } - - Node left = node.left; - Node right = node.right; - Node originalParent = node.parent; - if (left != null && right != null) { - - /* - * To remove a node with both left and right subtrees, move an - * adjacent node from one of those subtrees into this node's place. - * - * Removing the adjacent node may change this node's subtrees. This - * node may no longer have two subtrees once the adjacent node is - * gone! - */ - - Node adjacent = (left.height > right.height) ? left.last() : right.first(); - removeInternal(adjacent, false); // takes care of rebalance and size-- - - int leftHeight = 0; - left = node.left; - if (left != null) { - leftHeight = left.height; - adjacent.left = left; - left.parent = adjacent; - node.left = null; - } - int rightHeight = 0; - right = node.right; - if (right != null) { - rightHeight = right.height; - adjacent.right = right; - right.parent = adjacent; - node.right = null; - } - adjacent.height = Math.max(leftHeight, rightHeight) + 1; - replaceInParent(node, adjacent); - return; - } else if (left != null) { - replaceInParent(node, left); - node.left = null; - } else if (right != null) { - replaceInParent(node, right); - node.right = null; - } else { - replaceInParent(node, null); - } - - rebalance(originalParent, false); - size--; - modCount++; - } - - Node removeInternalByKey(Object key) { - Node node = findByObject(key); - if (node != null) { - removeInternal(node, true); - } - return node; - } - - private void replaceInParent(Node node, Node replacement) { - Node parent = node.parent; - node.parent = null; - if (replacement != null) { - replacement.parent = parent; - } - - if (parent != null) { - if (parent.left == node) { - parent.left = replacement; - } else { - assert (parent.right == node); - parent.right = replacement; - } - } else { - int index = node.hash & (table.length - 1); - table[index] = replacement; - } - } - - /** - * Rebalances the tree by making any AVL rotations necessary between the newly-unbalanced node and - * the tree's root. - * - * @param insert true if the node was unbalanced by an insert; false if it was by a removal. - */ - private void rebalance(Node unbalanced, boolean insert) { - for (Node node = unbalanced; node != null; node = node.parent) { - Node left = node.left; - Node right = node.right; - int leftHeight = left != null ? left.height : 0; - int rightHeight = right != null ? right.height : 0; - - int delta = leftHeight - rightHeight; - if (delta == -2) { - Node rightLeft = right.left; - Node rightRight = right.right; - int rightRightHeight = rightRight != null ? rightRight.height : 0; - int rightLeftHeight = rightLeft != null ? rightLeft.height : 0; - - int rightDelta = rightLeftHeight - rightRightHeight; - if (rightDelta != -1 && (rightDelta != 0 || insert)) { - assert (rightDelta == 1); - rotateRight(right); // AVL right left - } - rotateLeft(node); // AVL right right - if (insert) { - break; // no further rotations will be necessary - } - - } else if (delta == 2) { - Node leftLeft = left.left; - Node leftRight = left.right; - int leftRightHeight = leftRight != null ? leftRight.height : 0; - int leftLeftHeight = leftLeft != null ? leftLeft.height : 0; - - int leftDelta = leftLeftHeight - leftRightHeight; - if (leftDelta != 1 && (leftDelta != 0 || insert)) { - assert (leftDelta == -1); - rotateLeft(left); // AVL left right - } - rotateRight(node); // AVL left left - if (insert) { - break; // no further rotations will be necessary - } - - } else if (delta == 0) { - node.height = leftHeight + 1; // leftHeight == rightHeight - if (insert) { - break; // the insert caused balance, so rebalancing is done! - } - - } else { - assert (delta == -1 || delta == 1); - node.height = Math.max(leftHeight, rightHeight) + 1; - if (!insert) { - break; // the height hasn't changed, so rebalancing is done! - } - } - } - } - - /** Rotates the subtree so that its root's right child is the new root. */ - private void rotateLeft(Node root) { - Node left = root.left; - Node pivot = root.right; - Node pivotLeft = pivot.left; - Node pivotRight = pivot.right; - - // move the pivot's left child to the root's right - root.right = pivotLeft; - if (pivotLeft != null) { - pivotLeft.parent = root; - } - - replaceInParent(root, pivot); - - // move the root to the pivot's left - pivot.left = root; - root.parent = pivot; - - // fix heights - root.height = - Math.max(left != null ? left.height : 0, pivotLeft != null ? pivotLeft.height : 0) + 1; - pivot.height = Math.max(root.height, pivotRight != null ? pivotRight.height : 0) + 1; - } - - /** Rotates the subtree so that its root's left child is the new root. */ - private void rotateRight(Node root) { - Node pivot = root.left; - Node right = root.right; - Node pivotLeft = pivot.left; - Node pivotRight = pivot.right; - - // move the pivot's right child to the root's left - root.left = pivotRight; - if (pivotRight != null) { - pivotRight.parent = root; - } - - replaceInParent(root, pivot); - - // move the root to the pivot's right - pivot.right = root; - root.parent = pivot; - - // fixup heights - root.height = - Math.max(right != null ? right.height : 0, pivotRight != null ? pivotRight.height : 0) + 1; - pivot.height = Math.max(root.height, pivotLeft != null ? pivotLeft.height : 0) + 1; - } - - private EntrySet entrySet; - private KeySet keySet; - - @Override - public Set> entrySet() { - EntrySet result = entrySet; - return result != null ? result : (entrySet = new EntrySet()); - } - - @Override - public Set keySet() { - KeySet result = keySet; - return result != null ? result : (keySet = new KeySet()); - } - - static final class Node implements Entry { - Node parent; - Node left; - Node right; - Node next; - Node prev; - final K key; - final int hash; - V value; - int height; - - /** Create the header entry. */ - Node() { - key = null; - hash = -1; - next = prev = this; - } - - /** Create a regular entry. */ - Node(Node parent, K key, int hash, Node next, Node prev) { - this.parent = parent; - this.key = key; - this.hash = hash; - this.height = 1; - this.next = next; - this.prev = prev; - prev.next = this; - next.prev = this; - } - - public K getKey() { - return key; - } - - public V getValue() { - return value; - } - - public V setValue(V value) { - V oldValue = this.value; - this.value = value; - return oldValue; - } - - @SuppressWarnings("rawtypes") - @Override - public boolean equals(Object o) { - if (o instanceof Entry) { - Entry other = (Entry) o; - return (key == null ? other.getKey() == null : key.equals(other.getKey())) - && (value == null ? other.getValue() == null : value.equals(other.getValue())); - } - return false; - } - - @Override - public int hashCode() { - return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode()); - } - - @Override - public String toString() { - return key + "=" + value; - } - - /** Returns the first node in this subtree. */ - public Node first() { - Node node = this; - Node child = node.left; - while (child != null) { - node = child; - child = node.left; - } - return node; - } - - /** Returns the last node in this subtree. */ - public Node last() { - Node node = this; - Node child = node.right; - while (child != null) { - node = child; - child = node.right; - } - return node; - } - } - - private void doubleCapacity() { - table = doubleCapacity(table); - threshold = (table.length / 2) + (table.length / 4); // 3/4 capacity - } - - /** - * Returns a new array containing the same nodes as {@code oldTable}, but with twice as many - * trees, each of (approximately) half the previous size. - */ - static Node[] doubleCapacity(Node[] oldTable) { - // TODO: don't do anything if we're already at MAX_CAPACITY - int oldCapacity = oldTable.length; - @SuppressWarnings("unchecked") // Arrays and generics don't get along. - Node[] newTable = new Node[oldCapacity * 2]; - AvlIterator iterator = new AvlIterator<>(); - AvlBuilder leftBuilder = new AvlBuilder<>(); - AvlBuilder rightBuilder = new AvlBuilder<>(); - - // Split each tree into two trees. - for (int i = 0; i < oldCapacity; i++) { - Node root = oldTable[i]; - if (root == null) { - continue; - } - - // Compute the sizes of the left and right trees. - iterator.reset(root); - int leftSize = 0; - int rightSize = 0; - for (Node node; (node = iterator.next()) != null; ) { - if ((node.hash & oldCapacity) == 0) { - leftSize++; - } else { - rightSize++; - } - } - - // Split the tree into two. - leftBuilder.reset(leftSize); - rightBuilder.reset(rightSize); - iterator.reset(root); - for (Node node; (node = iterator.next()) != null; ) { - if ((node.hash & oldCapacity) == 0) { - leftBuilder.add(node); - } else { - rightBuilder.add(node); - } - } - - // Populate the enlarged array with these new roots. - newTable[i] = leftSize > 0 ? leftBuilder.root() : null; - newTable[i + oldCapacity] = rightSize > 0 ? rightBuilder.root() : null; - } - return newTable; - } - - /** - * Walks an AVL tree in iteration order. Once a node has been returned, its left, right and parent - * links are no longer used. For this reason it is safe to transform these links - * as you walk a tree. - * - *

Warning: this iterator is destructive. It clears the parent node of all - * nodes in the tree. It is an error to make a partial iteration of a tree. - */ - static class AvlIterator { - /** This stack is a singly linked list, linked by the 'parent' field. */ - private Node stackTop; - - void reset(Node root) { - Node stackTop = null; - for (Node n = root; n != null; n = n.left) { - n.parent = stackTop; - stackTop = n; // Stack push. - } - this.stackTop = stackTop; - } - - public Node next() { - Node stackTop = this.stackTop; - if (stackTop == null) { - return null; - } - Node result = stackTop; - stackTop = result.parent; - result.parent = null; - for (Node n = result.right; n != null; n = n.left) { - n.parent = stackTop; - stackTop = n; // Stack push. - } - this.stackTop = stackTop; - return result; - } - } - - /** - * Builds AVL trees of a predetermined size by accepting nodes of increasing value. To use: - * - *

    - *
  1. Call {@link #reset} to initialize the target size size. - *
  2. Call {@link #add} size times with increasing values. - *
  3. Call {@link #root} to get the root of the balanced tree. - *
- * - *

The returned tree will satisfy the AVL constraint: for every node N, the height of - * N.left and N.right is different by at most 1. It accomplishes this by omitting - * deepest-level leaf nodes when building trees whose size isn't a power of 2 minus 1. - * - *

Unlike rebuilding a tree from scratch, this approach requires no value comparisons. Using - * this class to create a tree of size S is {@code O(S)}. - */ - static final class AvlBuilder { - /** This stack is a singly linked list, linked by the 'parent' field. */ - private Node stack; - - private int leavesToSkip; - private int leavesSkipped; - private int size; - - void reset(int targetSize) { - // compute the target tree size. This is a power of 2 minus one, like 15 or 31. - int treeCapacity = Integer.highestOneBit(targetSize) * 2 - 1; - leavesToSkip = treeCapacity - targetSize; - size = 0; - leavesSkipped = 0; - stack = null; - } - - void add(Node node) { - node.left = node.parent = node.right = null; - node.height = 1; - - // Skip a leaf if necessary. - if (leavesToSkip > 0 && (size & 1) == 0) { - size++; - leavesToSkip--; - leavesSkipped++; - } - - node.parent = stack; - stack = node; // Stack push. - size++; - - // Skip a leaf if necessary. - if (leavesToSkip > 0 && (size & 1) == 0) { - size++; - leavesToSkip--; - leavesSkipped++; - } - - /* - * Combine 3 nodes into subtrees whenever the size is one less than a - * multiple of 4. For example we combine the nodes A, B, C into a - * 3-element tree with B as the root. - * - * Combine two subtrees and a spare single value whenever the size is one - * less than a multiple of 8. For example at 8 we may combine subtrees - * (A B C) and (E F G) with D as the root to form ((A B C) D (E F G)). - * - * Just as we combine single nodes when size nears a multiple of 4, and - * 3-element trees when size nears a multiple of 8, we combine subtrees of - * size (N-1) whenever the total size is 2N-1 whenever N is a power of 2. - */ - for (int scale = 4; (size & scale - 1) == scale - 1; scale *= 2) { - if (leavesSkipped == 0) { - // Pop right, center and left, then make center the top of the stack. - Node right = stack; - Node center = right.parent; - Node left = center.parent; - center.parent = left.parent; - stack = center; - // Construct a tree. - center.left = left; - center.right = right; - center.height = right.height + 1; - left.parent = center; - right.parent = center; - } else if (leavesSkipped == 1) { - // Pop right and center, then make center the top of the stack. - Node right = stack; - Node center = right.parent; - stack = center; - // Construct a tree with no left child. - center.right = right; - center.height = right.height + 1; - right.parent = center; - leavesSkipped = 0; - } else if (leavesSkipped == 2) { - leavesSkipped = 0; - } - } - } - - Node root() { - Node stackTop = this.stack; - if (stackTop.parent != null) { - throw new IllegalStateException(); - } - return stackTop; - } - } - - abstract class LinkedTreeMapIterator implements Iterator { - Node next = header.next; - Node lastReturned = null; - int expectedModCount = modCount; - - public final boolean hasNext() { - return next != header; - } - - final Node nextNode() { - Node e = next; - if (e == header) { - throw new NoSuchElementException(); - } - if (modCount != expectedModCount) { - throw new ConcurrentModificationException(); - } - next = e.next; - return lastReturned = e; - } - - public final void remove() { - if (lastReturned == null) { - throw new IllegalStateException(); - } - removeInternal(lastReturned, true); - lastReturned = null; - expectedModCount = modCount; - } - } - - final class EntrySet extends AbstractSet> { - @Override - public int size() { - return size; - } - - @Override - public Iterator> iterator() { - return new LinkedTreeMapIterator>() { - public Entry next() { - return nextNode(); - } - }; - } - - @Override - public boolean contains(Object o) { - return o instanceof Entry && findByEntry((Entry) o) != null; - } - - @Override - public boolean remove(Object o) { - if (!(o instanceof Entry)) { - return false; - } - - Node node = findByEntry((Entry) o); - if (node == null) { - return false; - } - removeInternal(node, true); - return true; - } - - @Override - public void clear() { - LinkedHashTreeMap.this.clear(); - } - } - - final class KeySet extends AbstractSet { - @Override - public int size() { - return size; - } - - @Override - public Iterator iterator() { - return new LinkedTreeMapIterator() { - public K next() { - return nextNode().key; - } - }; - } - - @Override - public boolean contains(Object o) { - return containsKey(o); - } - - @Override - public boolean remove(Object key) { - return removeInternalByKey(key) != null; - } - - @Override - public void clear() { - LinkedHashTreeMap.this.clear(); - } - } - - /** - * 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); - } -} diff --git a/moshi/src/main/java/com/squareup/moshi/LinkedHashTreeMap.kt b/moshi/src/main/java/com/squareup/moshi/LinkedHashTreeMap.kt new file mode 100644 index 0000000..ac9c984 --- /dev/null +++ b/moshi/src/main/java/com/squareup/moshi/LinkedHashTreeMap.kt @@ -0,0 +1,775 @@ +package com.squareup.moshi + +import com.squareup.moshi.LinkedHashTreeMap.Node +import com.squareup.moshi.internal.knownNotNull +import java.io.Serializable +import kotlin.math.max + +@Suppress("UNCHECKED_CAST") +private val NATURAL_ORDER = Comparator { o1, o2 -> (o1 as Comparable).compareTo(o2) } + +/** + * A map of comparable keys to values. Unlike TreeMap, this class uses insertion order for + * iteration order. Comparison order is only used as an optimization for efficient insertion and + * removal. + * + * This implementation was derived from Android 4.1's TreeMap and LinkedHashMap classes. + */ +internal class LinkedHashTreeMap +/** + * Create a tree map ordered by [comparator]. This map's keys may only be null if [comparator] permits. + * + * @param comparator the comparator to order elements with, or null to use the natural ordering. + */ +constructor( + comparator: Comparator? = null +) : AbstractMutableMap(), Serializable { + @Suppress("UNCHECKED_CAST") + private val comparator: Comparator = (comparator ?: NATURAL_ORDER) as Comparator + private var table: Array?> = arrayOfNulls(16) // TODO: sizing/resizing policies + private val header: Node = Node() + override var size = 0 + private var modCount = 0 + private var threshold = table.size / 2 + table.size / 4 // 3/4 capacity + private var entrySet: EntrySet? = null + private var keySet: KeySet? = null + + override val keys: MutableSet + get() = keySet ?: KeySet().also { keySet = it } + + override fun put(key: K, value: V): V? { + val created = findOrCreate(key) + val result = created.value + created.mutableValue = value + return result + } + + override val entries: MutableSet> + get() = entrySet ?: EntrySet().also { entrySet = it } + + override fun get(key: K) = findByObject(key)?.value + + override fun containsKey(key: K) = findByObject(key) != null + + override fun clear() { + table.fill(null) + size = 0 + modCount++ + + // Clear all links to help GC + val header = header + var e = header.next + while (e !== header) { + val next = e!!.next + e.prev = null + e.next = null + e = next + } + header.prev = header + header.next = header.prev + } + + override fun remove(key: K) = removeInternalByKey(key)?.value + + class Node : MutableMap.MutableEntry { + @JvmField + var parent: Node? = null + + @JvmField + var left: Node? = null + + @JvmField + var right: Node? = null + + @JvmField + var next: Node? + @JvmField + var prev: Node? + + private var realKey: K? = null + + override val key: K get() = knownNotNull(realKey) + + @JvmField + val hash: Int + + @JvmField + var mutableValue: V? = null + + override val value: V? + get() = mutableValue + + @JvmField + var height = 0 + + /** Create the header entry. */ + constructor() { + realKey = null + hash = -1 + prev = this + next = prev + } + + /** Create a regular entry. */ + constructor(parent: Node?, key: K, hash: Int, next: Node, prev: Node) { + this.parent = parent + this.realKey = key + this.hash = hash + height = 1 + this.next = next + this.prev = prev + prev.next = this + next.prev = this + } + + override fun setValue(newValue: V?): V? { + val oldValue = this.value + this.mutableValue = newValue + return oldValue + } + + override fun equals(other: Any?): Boolean { + if (other is Map.Entry<*, *>) { + val (key1, value1) = other + return ( + (if (realKey == null) key1 == null else realKey == key1) && + if (value == null) value1 == null else value == value1 + ) + } + return false + } + + override fun hashCode(): Int { + return (realKey?.hashCode() ?: 0) xor if (value == null) 0 else value.hashCode() + } + + override fun toString() = "$key=$value" + + /** Returns the first node in this subtree. */ + fun first(): Node { + var node = this + var child = node.left + while (child != null) { + node = child + child = node.left + } + return node + } + + /** Returns the last node in this subtree. */ + fun last(): Node { + var node = this + var child = node.right + while (child != null) { + node = child + child = node.right + } + return node + } + } + + private fun doubleCapacity() { + table = doubleCapacity(table) + threshold = table.size / 2 + table.size / 4 // 3/4 capacity + } + + /** + * Returns the node at or adjacent to the given key, creating it if requested. + * + * @throws ClassCastException if `key` and the tree's keys aren't mutually comparable. + */ + private fun findOrCreate(key: K): Node { + return knownNotNull(find(key, create = true)) + } + + /** + * Returns the node at or adjacent to the given key, creating it if requested. + * + * @throws ClassCastException if `key` and the tree's keys aren't mutually comparable. + */ + fun find(key: K, create: Boolean): Node? { + val comparator: Comparator = comparator + val table = table + val hash = secondaryHash(key.hashCode()) + val index = hash and table.size - 1 + var nearest = table[index] + var comparison = 0 + if (nearest != null) { + // Micro-optimization: avoid polymorphic calls to Comparator.compare(). + // Throws a ClassCastException below if there's trouble. + @Suppress("UNCHECKED_CAST") + val comparableKey = + if (comparator === NATURAL_ORDER) key as Comparable else null + while (true) { + comparison = comparableKey?.compareTo(knownNotNull(nearest).key) ?: comparator.compare(key, knownNotNull(nearest).key) + + // We found the requested key. + if (comparison == 0) { + return nearest + } + + // If it exists, the key is in a subtree. Go deeper. + val child = (if (comparison < 0) knownNotNull(nearest).left else knownNotNull(nearest).right) ?: break + nearest = child + } + } + + // The key doesn't exist in this tree. + if (!create) { + return null + } + + // Create the node and add it to the tree or the table. + val header = header + val created: Node + if (nearest == null) { + // Check that the value is comparable if we didn't do any comparisons. + if (comparator === NATURAL_ORDER && key !is Comparable<*>) { + throw ClassCastException("${(key as Any).javaClass.name} is not Comparable") + } + created = Node(null, key, hash, header, knownNotNull(header.prev)) + table[index] = created + } else { + created = Node(nearest, key, hash, header, knownNotNull(header.prev)) + if (comparison < 0) { // nearest.key is higher + nearest.left = created + } else { // comparison > 0, nearest.key is lower + nearest.right = created + } + rebalance(nearest, true) + } + if (size++ > threshold) { + doubleCapacity() + } + modCount++ + return created + } + + private fun findByObject(key: Any?): Node? { + return try { + @Suppress("UNCHECKED_CAST") + if (key != null) find(key as K, false) else null + } catch (e: ClassCastException) { + null + } + } + + /** + * Returns this map's entry that has the same key and value as `entry`, or null if this map + * has no such entry. + * + * This method uses the comparator for key equality rather than `equals`. If this map's + * comparator isn't consistent with equals (such as `String.CASE_INSENSITIVE_ORDER`), then + * `remove()` and `contains()` will violate the collections API. + */ + fun findByEntry(entry: Map.Entry<*, *>): Node? { + val mine = findByObject(entry.key) + val valuesEqual = mine != null && equal(mine.value, entry.value) + return if (valuesEqual) mine else null + } + + private fun equal(a: Any?, b: Any?): Boolean { + @Suppress("SuspiciousEqualsCombination") + return a === b || a != null && a == b + } + + /** + * 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 fun secondaryHash(seed: Int): Int { + // Doug Lea's supplemental hash function + var h = seed + h = h xor (h ushr 20 xor (h ushr 12)) + return h xor (h ushr 7) xor (h ushr 4) + } + + /** + * Removes `node` from this tree, rearranging the tree's structure as necessary. + * + * @param unlink true to also unlink this node from the iteration linked list. + */ + fun removeInternal(node: Node, unlink: Boolean) { + if (unlink) { + knownNotNull(node.prev).next = node.next + knownNotNull(node.next).prev = node.prev + node.prev = null + node.next = null // Help the GC (for performance) + } + var left = node.left + var right = node.right + val originalParent = node.parent + if (left != null && right != null) { + /* + * To remove a node with both left and right subtrees, move an + * adjacent node from one of those subtrees into this node's place. + * + * Removing the adjacent node may change this node's subtrees. This + * node may no longer have two subtrees once the adjacent node is + * gone! + */ + val adjacent = if (left.height > right.height) left.last() else right.first() + removeInternal(adjacent, false) // takes care of rebalance and size-- + var leftHeight = 0 + left = node.left + if (left != null) { + leftHeight = left.height + adjacent.left = left + left.parent = adjacent + node.left = null + } + var rightHeight = 0 + right = node.right + if (right != null) { + rightHeight = right.height + adjacent.right = right + right.parent = adjacent + node.right = null + } + adjacent.height = max(leftHeight, rightHeight) + 1 + replaceInParent(node, adjacent) + return + } else if (left != null) { + replaceInParent(node, left) + node.left = null + } else if (right != null) { + replaceInParent(node, right) + node.right = null + } else { + replaceInParent(node, null) + } + rebalance(originalParent, false) + size-- + modCount++ + } + + fun removeInternalByKey(key: Any?): Node? { + val node = findByObject(key) + if (node != null) { + removeInternal(node, true) + } + return node + } + + private fun replaceInParent(node: Node, replacement: Node?) { + val parent = node.parent + node.parent = null + if (replacement != null) { + replacement.parent = parent + } + if (parent != null) { + if (parent.left === node) { + parent.left = replacement + } else { + assert(parent.right === node) + parent.right = replacement + } + } else { + val index = node.hash and table.size - 1 + table[index] = replacement + } + } + + /** + * Rebalances the tree by making any AVL rotations necessary between the newly-unbalanced node and + * the tree's root. + * + * @param insert true if the node was unbalanced by an insert; false if it was by a removal. + */ + private fun rebalance(unbalanced: Node?, insert: Boolean) { + var node = unbalanced + while (node != null) { + val left = node.left + val right = node.right + val leftHeight = left?.height ?: 0 + val rightHeight = right?.height ?: 0 + val delta = leftHeight - rightHeight + when (delta) { + -2 -> { + val rightLeft = right!!.left + val rightRight = right.right + val rightRightHeight = rightRight?.height ?: 0 + val rightLeftHeight = rightLeft?.height ?: 0 + val rightDelta = rightLeftHeight - rightRightHeight + if (rightDelta != -1 && (rightDelta != 0 || insert)) { + assert(rightDelta == 1) + rotateRight(right) // AVL right left + } + rotateLeft(node) // AVL right right + if (insert) { + break // no further rotations will be necessary + } + } + 2 -> { + val leftLeft = left!!.left + val leftRight = left.right + val leftRightHeight = leftRight?.height ?: 0 + val leftLeftHeight = leftLeft?.height ?: 0 + val leftDelta = leftLeftHeight - leftRightHeight + if (leftDelta != 1 && (leftDelta != 0 || insert)) { + assert(leftDelta == -1) + rotateLeft(left) // AVL left right + } + rotateRight(node) // AVL left left + if (insert) { + break // no further rotations will be necessary + } + } + 0 -> { + node.height = leftHeight + 1 // leftHeight == rightHeight + if (insert) { + break // the insert caused balance, so rebalancing is done! + } + } + else -> { + assert(delta == -1 || delta == 1) + node.height = max(leftHeight, rightHeight) + 1 + if (!insert) { + break // the height hasn't changed, so rebalancing is done! + } + } + } + node = node.parent + } + } + + /** Rotates the subtree so that its root's right child is the new root. */ + private fun rotateLeft(root: Node) { + val left = root.left + val pivot = root.right + val pivotLeft = pivot!!.left + val pivotRight = pivot.right + + // move the pivot's left child to the root's right + root.right = pivotLeft + if (pivotLeft != null) { + pivotLeft.parent = root + } + replaceInParent(root, pivot) + + // move the root to the pivot's left + pivot.left = root + root.parent = pivot + + // fix heights + root.height = max(left?.height ?: 0, pivotLeft?.height ?: 0) + 1 + pivot.height = max(root.height, pivotRight?.height ?: 0) + 1 + } + + /** Rotates the subtree so that its root's left child is the new root. */ + private fun rotateRight(root: Node) { + val pivot = root.left + val right = root.right + val pivotLeft = pivot!!.left + val pivotRight = pivot.right + + // move the pivot's right child to the root's left + root.left = pivotRight + if (pivotRight != null) { + pivotRight.parent = root + } + replaceInParent(root, pivot) + + // move the root to the pivot's right + pivot.right = root + root.parent = pivot + + // fixup heights + root.height = max(right?.height ?: 0, pivotRight?.height ?: 0) + 1 + pivot.height = max(root.height, pivotLeft?.height ?: 0) + 1 + } + + abstract inner class LinkedTreeMapIterator : MutableIterator { + var next = header.next + private var lastReturned: Node? = null + private var expectedModCount: Int = modCount + override fun hasNext(): Boolean = next !== header + + fun nextNode(): Node { + val e = next + if (e === header) { + throw NoSuchElementException() + } + if (modCount != expectedModCount) { + throw ConcurrentModificationException() + } + next = e!!.next + return e.also { lastReturned = it } + } + + override fun remove() { + removeInternal(checkNotNull(lastReturned), true) + lastReturned = null + expectedModCount = modCount + } + } + + inner class EntrySet : AbstractMutableSet>() { + override val size: Int + get() = this@LinkedHashTreeMap.size + + override fun iterator(): MutableIterator> { + return object : LinkedTreeMapIterator>() { + override fun next(): MutableMap.MutableEntry { + @Suppress("UNCHECKED_CAST") + return nextNode() as MutableMap.MutableEntry + } + } + } + + override fun contains(element: MutableMap.MutableEntry): Boolean { + return findByEntry(element) != null + } + + override fun remove(element: MutableMap.MutableEntry): Boolean { + if (element !is Node<*, *>) { + return false + } + val node: Node = findByEntry(element) ?: return false + removeInternal(node, true) + return true + } + + override fun clear() { + this@LinkedHashTreeMap.clear() + } + + override fun add(element: MutableMap.MutableEntry): Boolean { + throw NotImplementedError() + } + } + + inner class KeySet : AbstractMutableSet() { + override val size: Int + get() = this@LinkedHashTreeMap.size + + override fun iterator(): MutableIterator { + return object : LinkedTreeMapIterator() { + override fun next(): K { + return nextNode().key ?: throw NoSuchElementException() + } + } + } + + override fun contains(element: K): Boolean { + return containsKey(element) + } + + override fun remove(element: K): Boolean { + return removeInternalByKey(element) != null + } + + override fun clear() { + this@LinkedHashTreeMap.clear() + } + + override fun add(element: K): Boolean { + throw NotImplementedError() + } + } + + /** + * 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 fun writeReplace(): Any = LinkedHashMap(this) +} + +/** + * Returns a new array containing the same nodes as `oldTable`, but with twice as many + * trees, each of (approximately) half the previous size. + */ +internal fun doubleCapacity(oldTable: Array?>): Array?> { + // TODO: don't do anything if we're already at MAX_CAPACITY + val oldCapacity = oldTable.size + // Arrays and generics don't get along. + val newTable: Array?> = arrayOfNulls?>(oldCapacity * 2) + val iterator = AvlIterator() + val leftBuilder = AvlBuilder() + val rightBuilder = AvlBuilder() + + // Split each tree into two trees. + for (i in 0 until oldCapacity) { + val root = oldTable[i] ?: continue + + // Compute the sizes of the left and right trees. + iterator.reset(root) + var leftSize = 0 + var rightSize = 0 + run { + var node: Node? + while (iterator.next().also { node = it } != null) { + if (knownNotNull(node).hash and oldCapacity == 0) { + leftSize++ + } else { + rightSize++ + } + } + } + + // Split the tree into two. + leftBuilder.reset(leftSize) + rightBuilder.reset(rightSize) + iterator.reset(root) + var node: Node? + while (iterator.next().also { node = it } != null) { + if (knownNotNull(node).hash and oldCapacity == 0) { + leftBuilder.add(knownNotNull(node)) + } else { + rightBuilder.add(knownNotNull(node)) + } + } + + // Populate the enlarged array with these new roots. + newTable[i] = if (leftSize > 0) leftBuilder.root() else null + newTable[i + oldCapacity] = if (rightSize > 0) rightBuilder.root() else null + } + return newTable +} + +/** + * Walks an AVL tree in iteration order. Once a node has been returned, its left, right and parent + * links are **no longer used**. For this reason it is safe to transform these links + * as you walk a tree. + * + * **Warning:** this iterator is destructive. It clears the parent node of all + * nodes in the tree. It is an error to make a partial iteration of a tree. + */ +internal class AvlIterator { + /** This stack is a singly linked list, linked by the 'parent' field. */ + private var stackTop: Node? = null + fun reset(root: Node?) { + var stackTop: Node? = null + var n = root + while (n != null) { + n.parent = stackTop + stackTop = n // Stack push. + n = n.left + } + this.stackTop = stackTop + } + + operator fun next(): Node? { + var stackTop: Node? = stackTop ?: return null + val result = stackTop + stackTop = result!!.parent + result.parent = null + var n = result.right + while (n != null) { + n.parent = stackTop + stackTop = n // Stack push. + n = n.left + } + this.stackTop = stackTop + return result + } +} + +/** + * Builds AVL trees of a predetermined size by accepting nodes of increasing value. To use: + * 1. Call [reset] to initialize the target size *size*. + * 2. Call [add] *size* times with increasing values. + * 3. Call [root] to get the root of the balanced tree. + * + * The returned tree will satisfy the AVL constraint: for every node *N*, the height of + * *N.left* and *N.right* is different by at most 1. It accomplishes this by omitting + * deepest-level leaf nodes when building trees whose size isn't a power of 2 minus 1. + * + * Unlike rebuilding a tree from scratch, this approach requires no value comparisons. Using + * this class to create a tree of size *S* is `O(S)`. + */ +internal class AvlBuilder { + /** This stack is a singly linked list, linked by the 'parent' field. */ + private var stack: Node? = null + private var leavesToSkip = 0 + private var leavesSkipped = 0 + private var size = 0 + fun reset(targetSize: Int) { + // compute the target tree size. This is a power of 2 minus one, like 15 or 31. + val treeCapacity = Integer.highestOneBit(targetSize) * 2 - 1 + leavesToSkip = treeCapacity - targetSize + size = 0 + leavesSkipped = 0 + stack = null + } + + fun add(node: Node) { + node.right = null + node.parent = null + node.left = null + node.height = 1 + + // Skip a leaf if necessary. + if (leavesToSkip > 0 && size and 1 == 0) { + size++ + leavesToSkip-- + leavesSkipped++ + } + node.parent = stack + stack = node // Stack push. + size++ + + // Skip a leaf if necessary. + if (leavesToSkip > 0 && size and 1 == 0) { + size++ + leavesToSkip-- + leavesSkipped++ + } + + /* + * Combine 3 nodes into subtrees whenever the size is one less than a + * multiple of 4. For example, we combine the nodes A, B, C into a + * 3-element tree with B as the root. + * + * Combine two subtrees and a spare single value whenever the size is one + * less than a multiple of 8. For example at 8 we may combine subtrees + * (A B C) and (E F G) with D as the root to form ((A B C) D (E F G)). + * + * Just as we combine single nodes when size nears a multiple of 4, and + * 3-element trees when size nears a multiple of 8, we combine subtrees of + * size (N-1) whenever the total size is 2N-1 whenever N is a power of 2. + */ + var scale = 4 + while (size and scale - 1 == scale - 1) { + when (leavesSkipped) { + 0 -> { + // Pop right, center and left, then make center the top of the stack. + val right = stack + val center = right!!.parent + val left = center!!.parent + center.parent = left!!.parent + stack = center + // Construct a tree. + center.left = left + center.right = right + center.height = right.height + 1 + left.parent = center + right.parent = center + } + 1 -> { + // Pop right and center, then make center the top of the stack. + val right = stack + val center = right!!.parent + stack = center!! + // Construct a tree with no left child. + center.right = right + center.height = right.height + 1 + right.parent = center + leavesSkipped = 0 + } + 2 -> { + leavesSkipped = 0 + } + } + scale *= 2 + } + } + + fun root(): Node { + val stackTop = stack + check(stackTop!!.parent == null) + return stackTop + } +} diff --git a/moshi/src/test/java/com/squareup/moshi/LinkedHashTreeMapTest.java b/moshi/src/test/java/com/squareup/moshi/LinkedHashTreeMapTest.java index 602db69..0cd6010 100644 --- a/moshi/src/test/java/com/squareup/moshi/LinkedHashTreeMapTest.java +++ b/moshi/src/test/java/com/squareup/moshi/LinkedHashTreeMapTest.java @@ -18,14 +18,13 @@ package com.squareup.moshi; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; -import com.squareup.moshi.LinkedHashTreeMap.AvlBuilder; -import com.squareup.moshi.LinkedHashTreeMap.AvlIterator; import com.squareup.moshi.LinkedHashTreeMap.Node; import java.util.Iterator; import java.util.Map; import java.util.Random; import org.junit.Test; +@SuppressWarnings("KotlinInternalInJava") public final class LinkedHashTreeMapTest { @Test public void iterationOrder() { @@ -228,7 +227,7 @@ public final class LinkedHashTreeMapTest { Node[] oldTable = new Node[1]; oldTable[0] = node(node(node("a"), "b", node("c")), "d", node(node("e"), "f", node("g"))); - Node[] newTable = LinkedHashTreeMap.doubleCapacity(oldTable); + Node[] newTable = LinkedHashTreeMapKt.doubleCapacity(oldTable); assertTree("(b d f)", newTable[0]); // Even hash codes! assertTree("(a c (. e g))", newTable[1]); // Odd hash codes! } @@ -239,7 +238,7 @@ public final class LinkedHashTreeMapTest { Node[] oldTable = new Node[1]; oldTable[0] = node(node("b"), "d", node("f")); - Node[] newTable = LinkedHashTreeMap.doubleCapacity(oldTable); + Node[] newTable = LinkedHashTreeMapKt.doubleCapacity(oldTable); assertTree("(b d f)", newTable[0]); // Even hash codes! assertThat(newTable[1]).isNull(); @@ -300,9 +299,9 @@ public final class LinkedHashTreeMapTest { if (root == null) { return "."; } else if (root.left == null && root.right == null) { - return String.valueOf(root.key); + return String.valueOf(root.getKey()); } else { - return String.format("(%s %s %s)", toString(root.left), root.key, toString(root.right)); + return String.format("(%s %s %s)", toString(root.left), root.getKey(), toString(root.right)); } } }