diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TNavigableMap.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TNavigableMap.java new file mode 100644 index 000000000..49dc61f46 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TNavigableMap.java @@ -0,0 +1,58 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * 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 org.teavm.classlib.java.util; + +/** + * + * @author Alexey Andreev + */ +public interface TNavigableMap extends TSortedMap { + Entry lowerEntry(K key); + + K lowerKey(K key); + + Entry floorEntry(K key); + + K floorKey(K key); + + Entry ceilingEntry(K key); + + K ceilingKey(K key); + + Entry higherEntry(K key); + + K higherKey(K key); + + Entry firstEntry(); + + Entry lastEntry(); + + Entry pollFirstEntry(); + + Entry pollLastEntry(); + + TNavigableMap descendingMap(); + + TNavigableSet navigableKeySet(); + + TNavigableSet descendingKeySet(); + + TNavigableMap subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive); + + TNavigableMap headMap(K toKey, boolean inclusive); + + TNavigableMap tailMap(K fromKey, boolean inclusive); +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TNavigableSet.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TNavigableSet.java new file mode 100644 index 000000000..fb03512da --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TNavigableSet.java @@ -0,0 +1,44 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * 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 org.teavm.classlib.java.util; + +/** + * + * @author Alexey Andreev + */ +public interface TNavigableSet extends TSortedSet { + E lower(E e); + + E floor(E e); + + E ceiling(E e); + + E higher(E e); + + E pollFirst(); + + E pollLast(); + + TNavigableSet descendingSet(); + + TIterator descendingIterator(); + + TNavigableSet subSet(E fromElement, boolean fromInclusive, E toElement, boolean toInclusive); + + TNavigableSet headSet(E toElement, boolean inclusive); + + TNavigableSet tailSet(E fromElement, boolean inclusive); +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TSortedMap.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TSortedMap.java new file mode 100644 index 000000000..864e32245 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TSortedMap.java @@ -0,0 +1,34 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * 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 org.teavm.classlib.java.util; + +/** + * + * @author Alexey Andreev + */ +public interface TSortedMap extends TMap { + TComparator comparator(); + + TSortedMap subMap(K fromKey, K toKey); + + TSortedMap headMap(K toKey); + + TSortedMap tailMap(K fromKey); + + K firstKey(); + + K lastKey(); +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TSortedSet.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TSortedSet.java index 333d5edd5..499709161 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TSortedSet.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TSortedSet.java @@ -15,13 +15,11 @@ */ package org.teavm.classlib.java.util; -import org.teavm.classlib.java.lang.TObject; - /** * * @author Alexey Andreev */ -public interface TSortedSet extends TSet { +public interface TSortedSet extends TSet { TComparator comparator(); TSortedSet subSet(E fromElement, E toElement); diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TTreeMap.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TTreeMap.java new file mode 100644 index 000000000..5acb6deeb --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TTreeMap.java @@ -0,0 +1,2408 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * 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. + */ +/* + * 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 org.teavm.classlib.java.util; + +import org.teavm.classlib.java.io.TSerializable; +import org.teavm.classlib.java.lang.*; + + +/** + * TreeMap is an implementation of SortedMap. All optional operations (adding + * and removing) are supported. The values can be any objects. The keys can be + * any objects which are comparable to each other either using their natural + * order or a specified Comparator. + * + * @since 1.2 + */ +public class TTreeMap extends TAbstractMap implements TSortedMap, TCloneable, TSerializable { + transient int size; + private TComparator comparator; + transient int modCount; + transient TSet> entrySet; + transient Node root; + + class MapEntry extends TObject implements Entry, TCloneable { + final int offset; + final Node node; + final K key; + + MapEntry(Node node, int offset) { + this.node = node; + this.offset = offset; + key = node.keys[offset]; + } + + @Override + public TObject clone() { + try { + return super.clone(); + } catch (TCloneNotSupportedException e) { + return null; + } + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object instanceof Entry) { + Entry entry = (Entry) object; + V value = getValue(); + return (key == null ? entry.getKey() == null : key.equals(entry.getKey())) + && (value == null ? entry.getValue() == null : value + .equals(entry.getValue())); + } + return false; + } + + @Override + public K getKey() { + return key; + } + + @Override + public V getValue() { + if (node.keys[offset] == key) { + return node.values[offset]; + } + if (containsKey(key)) { + return get(key); + } + throw new TIllegalStateException(); + } + + @Override + public int hashCode() { + V value = getValue(); + return (key == null ? 0 : key.hashCode()) + ^ (value == null ? 0 : value.hashCode()); + } + + @Override + public V setValue(V object) { + if (node.keys[offset] == key) { + V res = node.values[offset]; + node.values[offset] = object; + return res; + } + if (containsKey(key)) { + return put(key, object); + } + throw new TIllegalStateException(); + } + + @Override + public String toString() { + return key + "=" + getValue(); + } + } + + static class Node extends TObject implements TCloneable { + static final int NODE_SIZE = 64; + Node prev, next; + Node parent, left, right; + V[] values; + K[] keys; + int left_idx = 0; + int right_idx = -1; + int size = 0; + boolean color; + + @SuppressWarnings("unchecked") + public Node() { + keys = (K[]) new Object[NODE_SIZE]; + values = (V[]) new Object[NODE_SIZE]; + } + + @SuppressWarnings("unchecked") + Node clone(Node parent) throws TCloneNotSupportedException { + Node clone = (Node) super.clone(); + clone.keys = TArrays.copyOf(keys, keys.length); + clone.values = TArrays.copyOf(values, values.length); + clone.left_idx = left_idx; + clone.right_idx = right_idx; + clone.parent = parent; + if (left != null) { + clone.left = left.clone(clone); + } + if (right != null) { + clone.right = right.clone(clone); + } + clone.prev = null; + clone.next = null; + return clone; + } + } + + @SuppressWarnings("unchecked") + private static TComparable toComparable(T obj) { + if (obj == null) { + throw new TNullPointerException(); + } + return (TComparable) obj; + } + + static class AbstractMapIterator { + TTreeMap backingMap; + int expectedModCount; + Node node; + Node lastNode; + int offset; + int lastOffset; + + AbstractMapIterator(TTreeMap map, Node startNode, int startOffset) { + backingMap = map; + expectedModCount = map.modCount; + node = startNode; + offset = startOffset; + } + + AbstractMapIterator(TTreeMap map, Node startNode) { + this(map, startNode, startNode != null ? startNode.right_idx - startNode.left_idx : 0); + } + + AbstractMapIterator(TTreeMap map) { + this(map, minimum(map.root)); + } + + public boolean hasNext() { + return node != null; + } + + final void makeNext() { + if (expectedModCount != backingMap.modCount) { + throw new TConcurrentModificationException(); + } else if (node == null) { + throw new TNoSuchElementException(); + } + lastNode = node; + lastOffset = offset; + if (offset != 0) { + offset--; + } else { + node = node.next; + if (node != null) { + offset = node.right_idx - node.left_idx; + } + } + } + + final public void remove() { + if (expectedModCount == backingMap.modCount) { + if (lastNode != null) { + int idx = lastNode.right_idx - lastOffset; + backingMap.removeFromIterator(lastNode, idx); + lastNode = null; + expectedModCount++; + } else { + throw new TIllegalStateException(); + } + } else { + throw new TConcurrentModificationException(); + } + } + } + + static class UnboundedEntryIterator extends AbstractMapIterator + implements TIterator> { + + UnboundedEntryIterator(TTreeMap map, Node startNode, int startOffset) { + super(map, startNode, startOffset); + } + + UnboundedEntryIterator(TTreeMap map) { + super(map); + } + + @Override + public Entry next() { + makeNext(); + int idx = lastNode.right_idx - lastOffset; + return backingMap.new MapEntry(lastNode, idx); + } + } + + static class UnboundedKeyIterator extends AbstractMapIterator + implements TIterator { + + UnboundedKeyIterator(TTreeMap map, Node startNode, int startOffset) { + super(map, startNode, startOffset); + } + + UnboundedKeyIterator(TTreeMap map) { + super(map); + } + + @Override + public K next() { + makeNext(); + return lastNode.keys[lastNode.right_idx - lastOffset]; + } + } + + static class UnboundedValueIterator extends AbstractMapIterator + implements TIterator { + + UnboundedValueIterator(TTreeMap map, Node startNode, int startOffset) { + super(map, startNode, startOffset); + } + + UnboundedValueIterator(TTreeMap map) { + super(map); + } + + @Override + public V next() { + makeNext(); + return lastNode.values[lastNode.right_idx - lastOffset]; + } + } + + static class BoundedMapIterator extends AbstractMapIterator { + + Node finalNode; + int finalOffset; + + BoundedMapIterator(Node startNode, int startOffset, TTreeMap map, + Node finalNode, int finalOffset) { + super(map, finalNode == null ? null : startNode, startOffset); + this.finalNode = finalNode; + this.finalOffset = finalOffset; + } + + BoundedMapIterator(Node startNode, TTreeMap map, + Node finalNode, int finalOffset) { + this(startNode, startNode != null ? + startNode.right_idx - startNode.left_idx : 0, + map, finalNode, finalOffset); + } + + BoundedMapIterator(Node startNode, int startOffset, + TTreeMap map, Node finalNode) { + this(startNode, startOffset, map, finalNode, + finalNode.right_idx - finalNode.left_idx); + } + + void makeBoundedNext() { + makeNext(); + if (lastNode == finalNode && lastOffset == finalOffset) { + node = null; + } + } + } + + static class BoundedEntryIterator extends BoundedMapIterator + implements TIterator> { + + public BoundedEntryIterator(Node startNode, int startOffset, TTreeMap map, + Node finalNode, int finalOffset) { + super(startNode, startOffset, map, finalNode, finalOffset); + } + + @Override + public Entry next() { + makeBoundedNext(); + int idx = lastNode.right_idx - lastOffset; + return backingMap.new MapEntry(lastNode, idx); + } + } + + static class BoundedKeyIterator extends BoundedMapIterator + implements TIterator { + + public BoundedKeyIterator(Node startNode, int startOffset, TTreeMap map, + Node finalNode, int finalOffset) { + super(startNode, startOffset, map, finalNode, finalOffset); + } + + @Override + public K next() { + makeBoundedNext(); + return lastNode.keys[lastNode.right_idx - lastOffset]; + } + } + + static class BoundedValueIterator extends BoundedMapIterator + implements TIterator { + + public BoundedValueIterator(Node startNode, int startOffset, TTreeMap map, + Node finalNode, int finalOffset) { + super(startNode, startOffset, map, finalNode, finalOffset); + } + + @Override + public V next() { + makeBoundedNext(); + return lastNode.values[lastNode.right_idx - lastOffset]; + } + } + + static final class SubMap extends TAbstractMap + implements TSortedMap, TSerializable { + private TTreeMap backingMap; + boolean hasStart, hasEnd; + K startKey, endKey; + transient TSet> entrySet = null; + transient int firstKeyModCount = -1; + transient int lastKeyModCount = -1; + transient Node firstKeyNode; + transient int firstKeyIndex; + transient Node lastKeyNode; + transient int lastKeyIndex; + + SubMap(K start, TTreeMap map) { + backingMap = map; + hasStart = true; + startKey = start; + } + + SubMap(K start, TTreeMap map, K end) { + backingMap = map; + hasStart = hasEnd = true; + startKey = start; + endKey = end; + } + + SubMap(TTreeMap map, K end) { + backingMap = map; + hasEnd = true; + endKey = end; + } + + private void checkRange(K key) { + TComparator cmp = backingMap.comparator; + if (cmp == null) { + TComparable object = toComparable(key); + if (hasStart && object.compareTo(startKey) < 0) { + throw new TIllegalArgumentException(); + } + if (hasEnd && object.compareTo(endKey) > 0) { + throw new TIllegalArgumentException(); + } + } else { + if (hasStart + && backingMap.comparator().compare(key, startKey) < 0) { + throw new TIllegalArgumentException(); + } + if (hasEnd && backingMap.comparator().compare(key, endKey) > 0) { + throw new TIllegalArgumentException(); + } + } + } + + private boolean isInRange(K key) { + TComparator cmp = backingMap.comparator; + if (cmp == null) { + TComparable object = toComparable(key); + if (hasStart && object.compareTo(startKey) < 0) { + return false; + } + if (hasEnd && object.compareTo(endKey) >= 0) { + return false; + } + } else { + if (hasStart && cmp.compare(key, startKey) < 0) { + return false; + } + if (hasEnd && cmp.compare(key, endKey) >= 0) { + return false; + } + } + return true; + } + + private boolean checkUpperBound(K key) { + if (hasEnd) { + TComparator cmp = backingMap.comparator; + if (cmp == null) { + return (toComparable(key).compareTo(endKey) < 0); + } + return (cmp.compare(key, endKey) < 0); + } + return true; + } + + private boolean checkLowerBound(K key) { + if (hasStart) { + TComparator cmp = backingMap.comparator; + if (cmp == null) { + return (toComparable(key).compareTo(startKey) >= 0); + } + return (cmp.compare(key, startKey) >= 0); + } + return true; + } + + @Override + public TComparator comparator() { + return backingMap.comparator(); + } + + @SuppressWarnings("unchecked") + @Override + public boolean containsKey(Object key) { + if (isInRange((K) key)) { + return backingMap.containsKey(key); + } + return false; + } + + @Override + public void clear() { + keySet().clear(); + } + + @Override + public boolean containsValue(Object value) { + TIterator it = values().iterator(); + if (value != null) { + while (it.hasNext()) { + if (value.equals(it.next())) { + return true; + } + } + } else { + while (it.hasNext()) { + if (it.next() == null) { + return true; + } + } + } + return false; + } + + @Override + public TSet> entrySet() { + if (entrySet == null) { + entrySet = new SubMapEntrySet<>(this); + } + return entrySet; + } + + private void setFirstKey() { + if (firstKeyModCount == backingMap.modCount) { + return; + } + TComparable object = backingMap.comparator == null ? + toComparable(startKey) : null; + K key = startKey; + Node node = backingMap.root; + Node foundNode = null; + int foundIndex = -1; + TOP_LOOP: + while (node != null) { + K[] keys = node.keys; + int left_idx = node.left_idx; + int result = backingMap.cmp(object, key, keys[left_idx]); + if (result < 0) { + foundNode = node; + foundIndex = left_idx; + node = node.left; + } else if (result == 0) { + foundNode = node; + foundIndex = left_idx; + break; + } else { + int right_idx = node.right_idx; + if (left_idx != right_idx) { + result = backingMap.cmp(object, key, keys[right_idx]); + } + if (result > 0) { + node = node.right; + } else if (result == 0) { + foundNode = node; + foundIndex = right_idx; + break; + } else { /*search in node*/ + foundNode = node; + foundIndex = right_idx; + int low = left_idx + 1, mid = 0, high = right_idx - 1; + while (low <= high) { + mid = (low + high) >>> 1; + result = backingMap.cmp(object, key, keys[mid]); + if (result > 0) { + low = mid + 1; + } else if (result == 0) { + foundNode = node; + foundIndex = mid; + break TOP_LOOP; + } else { + foundNode = node; + foundIndex = mid; + high = mid - 1; + } + } + break TOP_LOOP; + } + } + } + if (foundNode != null && !checkUpperBound(foundNode.keys[foundIndex])) { + foundNode = null; + } + firstKeyNode = foundNode; + firstKeyIndex = foundIndex; + firstKeyModCount = backingMap.modCount; + } + + @Override + public K firstKey() { + if (backingMap.size > 0) { + if (!hasStart) { + Node node = minimum(backingMap.root); + if (node != null && checkUpperBound(node.keys[node.left_idx])) { + return node.keys[node.left_idx]; + } + } else { + setFirstKey(); + if (firstKeyNode != null) { + return firstKeyNode.keys[firstKeyIndex]; + } + } + } + throw new TNoSuchElementException(); + } + + + @SuppressWarnings("unchecked") + @Override + public V get(Object key) { + if (isInRange((K) key)) { + return backingMap.get(key); + } + return null; + } + + @Override + public TSortedMap headMap(K endKey) { + checkRange(endKey); + if (hasStart) { + return new SubMap<>(startKey, backingMap, endKey); + } + return new SubMap<>(backingMap, endKey); + } + + @Override + public boolean isEmpty() { + if (hasStart) { + setFirstKey(); + return firstKeyNode == null; + } else { + setLastKey(); + return lastKeyNode == null; + } + } + + @Override + public TSet keySet() { + if (cachedKeySet == null) { + cachedKeySet = new SubMapKeySet<>(this); + } + return cachedKeySet; + } + + private void setLastKey() { + if (lastKeyModCount == backingMap.modCount) { + return; + } + TComparable object = backingMap.comparator == null ? toComparable(endKey) : null; + K key = endKey; + Node node = backingMap.root; + Node foundNode = null; + int foundIndex = -1; + TOP_LOOP: + while (node != null) { + K[] keys = node.keys; + int left_idx = node.left_idx; + int result = backingMap.cmp(object, key, keys[left_idx]); + if (result <= 0) { + node = node.left; + } else { + int right_idx = node.right_idx; + if (left_idx != right_idx) { + result = backingMap.cmp(object, key, keys[right_idx]); + } + if (result > 0) { + foundNode = node; + foundIndex = right_idx; + node = node.right; + } else if (result == 0) { + if (node.left_idx == node.right_idx) { + foundNode = node.prev; + if (foundNode != null) { + foundIndex = foundNode.right_idx - 1; + } + } else { + foundNode = node; + foundIndex = right_idx - 1; + } + break; + } else { /*search in node*/ + foundNode = node; + foundIndex = left_idx; + int low = left_idx + 1, mid = 0, high = right_idx - 1; + while (low <= high) { + mid = (low + high) >>> 1; + result = backingMap.cmp(object, key, keys[mid]); + if (result > 0) { + foundNode = node; + foundIndex = mid; + low = mid + 1; + } else if (result == 0) { + foundNode = node; + foundIndex = mid - 1; + break TOP_LOOP; + } else { + high = mid - 1; + } + } + break TOP_LOOP; + } + } + } + if (foundNode != null && !checkLowerBound(foundNode.keys[foundIndex])) { + foundNode = null; + } + lastKeyNode = foundNode; + lastKeyIndex = foundIndex; + lastKeyModCount = backingMap.modCount; + } + + @Override + public K lastKey() { + if (backingMap.size > 0) { + if (!hasEnd) { + Node node = maximum(backingMap.root); + if (node != null && checkLowerBound(node.keys[node.right_idx])) { + return node.keys[node.right_idx]; + } + } else { + setLastKey(); + if (lastKeyNode != null) { + return lastKeyNode.keys[lastKeyIndex]; + } + } + } + throw new TNoSuchElementException(); + } + + + @Override + public V put(K key, V value) { + if (isInRange(key)) { + return backingMap.put(key, value); + } + throw new TIllegalArgumentException(); + } + + @SuppressWarnings("unchecked") + @Override + public V remove(Object key) { + if (isInRange((K) key)) { + return backingMap.remove(key); + } + return null; + } + + @Override + public TSortedMap subMap(K startKey, K endKey) { + checkRange(startKey); + checkRange(endKey); + TComparator c = backingMap.comparator(); + if (c == null) { + if (toComparable(startKey).compareTo(endKey) <= 0) { + return new SubMap<>(startKey, backingMap, endKey); + } + } else { + if (c.compare(startKey, endKey) <= 0) { + return new SubMap<>(startKey, backingMap, endKey); + } + } + throw new IllegalArgumentException(); + } + + @Override + public TSortedMap tailMap(K startKey) { + checkRange(startKey); + if (hasEnd) { + return new SubMap<>(startKey, backingMap, endKey); + } + return new SubMap<>(startKey, backingMap); + } + + @Override + public TCollection values() { + if (cachedValues == null) { + cachedValues = new SubMapValuesCollection<>(this); + } + return cachedValues; + } + + @Override + public int size() { + Node from, to; + int fromIndex, toIndex; + if (hasStart) { + setFirstKey(); + from = firstKeyNode; + fromIndex = firstKeyIndex; + } else { + from = minimum(backingMap.root); + fromIndex = from == null ? 0 : from.left_idx; + } + if (from == null) { + return 0; + } + if (hasEnd) { + setLastKey(); + to = lastKeyNode; + toIndex = lastKeyIndex; + } else { + to = maximum(backingMap.root); + toIndex = to == null ? 0 : to.right_idx; + } + if (to == null) { + return 0; + } + if (from == to) { + return toIndex - fromIndex + 1; + } + int sum = 0; + while (from != to) { + sum += (from.right_idx - fromIndex + 1); + from = from.next; + fromIndex = from.left_idx; + } + return sum + toIndex - fromIndex + 1; + } + + } + + static class SubMapEntrySet extends TAbstractSet> { + SubMap subMap; + + SubMapEntrySet(SubMap map) { + subMap = map; + } + + @Override + public boolean isEmpty() { + return subMap.isEmpty(); + } + + @Override + public TIterator> iterator() { + Node from; + int fromIndex; + if (subMap.hasStart) { + subMap.setFirstKey(); + from = subMap.firstKeyNode; + fromIndex = subMap.firstKeyIndex; + } else { + from = minimum(subMap.backingMap.root); + fromIndex = from != null ? from.left_idx : 0; + } + if (!subMap.hasEnd) { + return new UnboundedEntryIterator<>(subMap.backingMap, from, + from == null ? 0 : from.right_idx - fromIndex); + } + subMap.setLastKey(); + Node to = subMap.lastKeyNode; + int toIndex = subMap.lastKeyIndex; + return new BoundedEntryIterator<>(from, from == null ? 0 : from.right_idx - fromIndex, + subMap.backingMap, to, to == null ? 0 : to.right_idx - toIndex); + } + + @Override + public int size() { + return subMap.size(); + } + + @SuppressWarnings("unchecked") + @Override + public boolean contains(Object object) { + if (object instanceof Entry) { + Entry entry = (Entry) object; + K key = entry.getKey(); + if (subMap.isInRange(key)) { + V v1 = subMap.get(key), v2 = entry.getValue(); + return v1 == null ? ( v2 == null && subMap.containsKey(key) ) : v1.equals(v2); + } + } + return false; + } + + @Override + public boolean remove(Object object) { + if (contains(object)) { + @SuppressWarnings("unchecked") + Entry entry = (Entry) object; + K key = entry.getKey(); + subMap.remove(key); + return true; + } + return false; + } + } + + static class SubMapKeySet extends TAbstractSet { + SubMap subMap; + + SubMapKeySet(SubMap map) { + subMap = map; + } + + @Override + public boolean contains(Object object) { + return subMap.containsKey(object); + } + + @Override + public boolean isEmpty() { + return subMap.isEmpty(); + } + + @Override + public int size() { + return subMap.size(); + } + + @Override + public boolean remove(Object object) { + if (subMap.containsKey(object)) { + subMap.remove(object); + return true; + } + return false; + } + + @Override + public TIterator iterator() { + Node from; + int fromIndex; + if (subMap.hasStart) { + subMap.setFirstKey(); + from = subMap.firstKeyNode; + fromIndex = subMap.firstKeyIndex; + } else { + from = minimum(subMap.backingMap.root); + fromIndex = from != null ? from.left_idx : 0; + } + if (!subMap.hasEnd) { + return new UnboundedKeyIterator<>(subMap.backingMap, from, + from == null ? 0 : from.right_idx - fromIndex); + } + subMap.setLastKey(); + Node to = subMap.lastKeyNode; + int toIndex = subMap.lastKeyIndex; + return new BoundedKeyIterator<>(from, + from == null ? 0 : from.right_idx - fromIndex, subMap.backingMap, to, + to == null ? 0 : to.right_idx - toIndex); + } + } + + static class SubMapValuesCollection extends TAbstractCollection { + SubMap subMap; + + public SubMapValuesCollection(SubMap subMap) { + this.subMap = subMap; + } + + @Override + public boolean isEmpty() { + return subMap.isEmpty(); + } + + @Override + public TIterator iterator() { + Node from; + int fromIndex; + if (subMap.hasStart) { + subMap.setFirstKey(); + from = subMap.firstKeyNode; + fromIndex = subMap.firstKeyIndex; + } else { + from = minimum(subMap.backingMap.root); + fromIndex = from != null ? from.left_idx : 0; + } + if (!subMap.hasEnd) { + return new UnboundedValueIterator<>(subMap.backingMap, from, + from == null ? 0 : from.right_idx - fromIndex); + } + subMap.setLastKey(); + Node to = subMap.lastKeyNode; + int toIndex = subMap.lastKeyIndex; + return new BoundedValueIterator<>(from, + from == null ? 0 : from.right_idx - fromIndex, subMap.backingMap, to, + to == null ? 0 : to.right_idx - toIndex); + } + + @Override + public int size() { + return subMap.size(); + } + } + + /** + * Constructs a new empty {@code TreeMap} instance. + */ + public TTreeMap() { + } + + /** + * Constructs a new empty {@code TreeMap} instance with the specified + * comparator. + * + * @param comparator + * the comparator to compare keys with. + */ + public TTreeMap(TComparator comparator) { + this.comparator = comparator; + } + + /** + * Constructs a new {@code TreeMap} instance containing the mappings from + * the specified map and using natural ordering. + * + * @param map + * the mappings to add. + * @throws ClassCastException + * if a key in the specified map does not implement the + * Comparable interface, or if the keys in the map cannot be + * compared. + */ + public TTreeMap(TMap map) { + putAll(map); + } + + /** + * Constructs a new {@code TreeMap} instance containing the mappings from + * the specified SortedMap and using the same comparator. + * + * @param map + * the mappings to add. + */ + public TTreeMap(TSortedMap map) { + this(map.comparator()); + Node lastNode = null; + TIterator> it = map.entrySet().iterator(); + while (it.hasNext()) { + Entry entry = it.next(); + lastNode = addToLast(lastNode, entry.getKey(), entry.getValue()); + } + } + + Node addToLast(Node last, K key, V value) { + if (last == null) { + root = last = createNode(key, value); + size = 1; + } else if (last.size == Node.NODE_SIZE) { + Node newNode = createNode(key, value); + attachToRight(last, newNode); + balance(newNode); + size++; + last = newNode; + } else { + appendFromRight(last, key, value); + size++; + } + return last; + } + + /** + * Removes all mappings from this TreeMap, leaving it empty. + * + * @see Map#isEmpty() + * @see #size() + */ + @Override + public void clear() { + root = null; + size = 0; + modCount++; + } + + /** + * Returns a new {@code TreeMap} with the same mappings, size and comparator + * as this instance. + * + * @return a shallow copy of this instance. + * @see java.lang.Cloneable + */ + @SuppressWarnings("unchecked") + @Override + public TObject clone() { + try { + TTreeMap clone = (TTreeMap) super.clone(); + if (root != null) { + clone.root = root.clone(null); + // restore prev/next chain + Node node = minimum(clone.root); + while (true) { + Node nxt = successor(node); + if (nxt == null) { + break; + } + nxt.prev = node; + node.next = nxt; + node = nxt; + } + } + return clone; + } catch (TCloneNotSupportedException e) { + return null; + } + } + + static private Node successor(Node x) { + if (x.right != null) { + return minimum(x.right); + } + Node y = x.parent; + while (y != null && x == y.right) { + x = y; + y = y.parent; + } + return y; + } + + /** + * Returns the comparator used to compare elements in this map. + * + * @return the comparator or {@code null} if the natural ordering is used. + */ + @Override + public TComparator comparator() { + return comparator; + } + + /** + * Returns whether this map contains the specified key. + * + * @param key + * the key to search for. + * @return {@code true} if this map contains the specified key, + * {@code false} otherwise. + * @throws ClassCastException + * if the specified key cannot be compared with the keys in this + * map. + * @throws NullPointerException + * if the specified key is {@code null} and the comparator + * cannot handle {@code null} keys. + */ + @Override + public boolean containsKey(Object key) { + @SuppressWarnings("unchecked") + TComparable object = comparator == null ? toComparable((K) key) : null; + @SuppressWarnings("unchecked") + K keyK = (K)key; + Node node = root; + while (node != null) { + K[] keys = node.keys; + int left_idx = node.left_idx; + int result = cmp(object, keyK, keys[left_idx]); + if (result < 0) { + node = node.left; + } else if (result == 0) { + return true; + } else { + int right_idx = node.right_idx; + if (left_idx != right_idx) { + result = cmp(object, keyK, keys[right_idx]); + } + if (result > 0) { + node = node.right; + } else if (result == 0) { + return true; + } else { /*search in node*/ + int low = left_idx + 1, mid = 0, high = right_idx - 1; + while (low <= high) { + mid = (low + high) >>> 1; + result = cmp(object, keyK, keys[mid]); + if (result > 0) { + low = mid + 1; + } else if (result == 0) { + return true; + } else { + high = mid - 1; + } + } + return false; + } + } + } + return false; + } + + /** + * Returns whether this map contains the specified value. + * + * @param value + * the value to search for. + * @return {@code true} if this map contains the specified value, + * {@code false} otherwise. + */ + @Override + public boolean containsValue(Object value) { + if (root == null) { + return false; + } + Node node = minimum(root); + if (value != null) { + while (node != null) { + int to = node.right_idx; + V[] values = node.values; + for (int i = node.left_idx; i <= to; i++) { + if (value.equals(values[i])) { + return true; + } + } + node = node.next; + } + } else { + while (node != null) { + int to = node.right_idx; + V[] values = node.values; + for (int i = node.left_idx; i <= to; i++) { + if (values[i] == null) { + return true; + } + } + node = node.next; + } + } + return false; + } + + /** + * Returns a set containing all of the mappings in this map. Each mapping is + * an instance of {@link Map.Entry}. As the set is backed by this map, + * changes in one will be reflected in the other. It does not support adding + * operations. + * + * @return a set of the mappings. + */ + @Override + public TSet> entrySet() { + if (entrySet == null) { + entrySet = new TAbstractSet>() { + @Override + public int size() { + return size; + } + + @Override + public void clear() { + TTreeMap.this.clear(); + } + + @SuppressWarnings("unchecked") + @Override + public boolean contains(Object object) { + if (object instanceof Entry) { + Entry entry = (Entry) object; + K key = entry.getKey(); + Object v1 = TTreeMap.this.get(key), v2 = entry.getValue(); + return v1 == null ? ( v2 == null && TTreeMap.this.containsKey(key) ) : v1.equals(v2); + } + return false; + } + + @Override + public boolean remove(Object object) { + if (contains(object)) { + @SuppressWarnings("unchecked") + Entry entry = (Entry) object; + K key = entry.getKey(); + TTreeMap.this.remove(key); + return true; + } + return false; + } + + @Override + public TIterator> iterator() { + return new UnboundedEntryIterator<>(TTreeMap.this); + } + }; + } + return entrySet; + } + + /** + * Returns the first key in this map. + * + * @return the first key in this map. + * @throws NoSuchElementException + * if this map is empty. + */ + @Override + public K firstKey() { + if (root != null) { + Node node = minimum(root); + return node.keys[node.left_idx]; + } + throw new TNoSuchElementException(); + } + + + /** + * Returns the value of the mapping with the specified key. + * + * @param key + * the key. + * @return the value of the mapping with the specified key. + * @throws ClassCastException + * if the key cannot be compared with the keys in this map. + * @throws NullPointerException + * if the key is {@code null} and the comparator cannot handle + * {@code null}. + */ + @Override + public V get(Object key) { + @SuppressWarnings("unchecked") + TComparable object = comparator == null ? toComparable((K) key) : null; + @SuppressWarnings("unchecked") + K keyK = (K) key; + Node node = root; + while (node != null) { + K[] keys = node.keys; + int left_idx = node.left_idx; + int result = cmp(object, keyK, keys[left_idx]); + if (result < 0) { + node = node.left; + } else if (result == 0) { + return node.values[left_idx]; + } else { + int right_idx = node.right_idx; + if (left_idx != right_idx) { + result = cmp(object, keyK, keys[right_idx]); + } + if (result > 0) { + node = node.right; + } else if (result == 0) { + return node.values[right_idx]; + } else { /*search in node*/ + int low = left_idx + 1, mid = 0, high = right_idx - 1; + while (low <= high) { + mid = (low + high) >>> 1; + result = cmp(object, keyK, keys[mid]); + if (result > 0) { + low = mid + 1; + } else if (result == 0) { + return node.values[mid]; + } else { + high = mid - 1; + } + } + return null; + } + } + } + return null; + } + + private int cmp(TComparable object, K key1, K key2) { + return object != null ? + object.compareTo(key2) : comparator.compare(key1, key2); + } + + /** + * Returns a sorted map over a range of this sorted map with all keys that + * are less than the specified {@code endKey}. Changes to the returned + * sorted map are reflected in this sorted map and vice versa. + *

+ * Note: The returned map will not allow an insertion of a key outside the + * specified range. + * + * @param endKey + * the high boundary of the range specified. + * @return a sorted map where the keys are less than {@code endKey}. + * @throws ClassCastException + * if the specified key cannot be compared with the keys in this + * map. + * @throws NullPointerException + * if the specified key is {@code null} and the comparator + * cannot handle {@code null} keys. + * @throws IllegalArgumentException + * if this map is itself a sorted map over a range of another + * map and the specified key is outside of its range. + */ + @Override + public TSortedMap headMap(K endKey) { + // Check for errors + if (comparator == null) { + toComparable(endKey).compareTo(endKey); + } else { + comparator.compare(endKey, endKey); + } + return new SubMap<>(this, endKey); + } + + /** + * Returns a set of the keys contained in this map. The set is backed by + * this map so changes to one are reflected by the other. The set does not + * support adding. + * + * @return a set of the keys. + */ + @Override + public TSet keySet() { + if (cachedKeySet == null) { + cachedKeySet = new TAbstractSet() { + @Override + public boolean contains(Object object) { + return TTreeMap.this.containsKey(object); + } + + @Override + public int size() { + return TTreeMap.this.size; + } + + @Override + public void clear() { + TTreeMap.this.clear(); + } + + @Override + public boolean remove(Object object) { + if (contains(object)) { + TTreeMap.this.remove(object); + return true; + } + return false; + } + + @Override + public TIterator iterator() { + return new UnboundedKeyIterator<>(TTreeMap.this); + } + }; + } + return cachedKeySet; + } + + /** + * Returns the last key in this map. + * + * @return the last key in this map. + * @throws NoSuchElementException + * if this map is empty. + */ + @Override + public K lastKey() { + if (root != null) { + Node node = maximum(root); + return node.keys[node.right_idx]; + } + throw new TNoSuchElementException(); + } + + static Node minimum(Node x) { + if (x == null) { + return null; + } + while (x.left != null) { + x = x.left; + } + return x; + } + + static Node maximum(Node x) { + if (x == null) { + return null; + } + while (x.right != null) { + x = x.right; + } + return x; + } + + /** + * Maps the specified key to the specified value. + * + * @param key + * the key. + * @param value + * the value. + * @return the value of any previous mapping with the specified key or + * {@code null} if there was no mapping. + * @throws ClassCastException + * if the specified key cannot be compared with the keys in this + * map. + * @throws NullPointerException + * if the specified key is {@code null} and the comparator + * cannot handle {@code null} keys. + */ + @Override + public V put(K key, V value) { + if (root == null) { + root = createNode(key, value); + size = 1; + modCount++; + return null; + } + TComparable object = comparator == null ? toComparable(key) : null; + K keyK = key; + Node node = root; + Node prevNode = null; + int result = 0; + while (node != null) { + prevNode = node; + K[] keys = node.keys; + int left_idx = node.left_idx; + result = cmp(object, keyK, keys[left_idx]); + if (result < 0) { + node = node.left; + } else if (result == 0) { + V res = node.values[left_idx]; + node.values[left_idx] = value; + return res; + } else { + int right_idx = node.right_idx; + if (left_idx != right_idx) { + result = cmp(object, keyK, keys[right_idx]); + } + if (result > 0) { + node = node.right; + } else if (result == 0) { + V res = node.values[right_idx]; + node.values[right_idx] = value; + return res; + } else { /*search in node*/ + int low = left_idx + 1, mid = 0, high = right_idx - 1; + while (low <= high) { + mid = (low + high) >>> 1; + result = cmp(object, keyK, keys[mid]); + if (result > 0) { + low = mid + 1; + } else if (result == 0) { + V res = node.values[mid]; + node.values[mid] = value; + return res; + } else { + high = mid - 1; + } + } + result = low; + break; + } + } + } /* while */ +/* + if(node == null) { + if(prevNode==null) { + - case of empty Tree + } else { + result < 0 - prevNode.left==null - attach here + result > 0 - prevNode.right==null - attach here + } + } else { + insert into node. + result - index where it should be inserted. + } + */ + size++; + modCount++; + if (node == null) { + if (prevNode == null) { + // case of empty Tree + root = createNode(key, value); + } else if (prevNode.size < Node.NODE_SIZE) { + // there is a place for insert + if (result < 0) { + appendFromLeft(prevNode, key, value); + } else { + appendFromRight(prevNode, key, value); + } + } else { + // create and link + Node newNode = createNode(key, value); + if (result < 0) { + attachToLeft(prevNode, newNode); + } else { + attachToRight(prevNode, newNode); + } + balance(newNode); + } + } else { + // insert into node. + // result - index where it should be inserted. + if (node.size < Node.NODE_SIZE) { // insert and ok + int left_idx = node.left_idx; + int right_idx = node.right_idx; + if (left_idx == 0 || ((right_idx != Node.NODE_SIZE - 1) && (right_idx - result <= result - left_idx))) { + int right_idxPlus1 = right_idx + 1; + System.arraycopy(node.keys, result, node.keys, result + 1, right_idxPlus1 - result); + System.arraycopy(node.values, result, node.values, result + 1, right_idxPlus1 - result); + node.right_idx = right_idxPlus1; + node.keys[result] = key; + node.values[result] = value; + } else { + int left_idxMinus1 = left_idx - 1; + System.arraycopy(node.keys, left_idx, node.keys, left_idxMinus1, result - left_idx); + System.arraycopy(node.values, left_idx, node.values, left_idxMinus1, result - left_idx); + node.left_idx = left_idxMinus1; + node.keys[result - 1] = key; + node.values[result - 1] = value; + } + node.size++; + } else { + // there are no place here + // insert and push old pair + Node previous = node.prev; + Node nextNode = node.next; + boolean removeFromStart; + boolean attachFromLeft = false; + Node attachHere = null; + if (previous == null) { + if (nextNode != null && nextNode.size < Node.NODE_SIZE) { + // move last pair to next + removeFromStart = false; + } else { + // next node doesn't exist or full + // left==null + // drop first pair to new node from left + removeFromStart = true; + attachFromLeft = true; + attachHere = node; + } + } else if (nextNode == null) { + if (previous.size < Node.NODE_SIZE) { + // move first pair to prev + removeFromStart = true; + } else { + // right == null; + // drop last pair to new node from right + removeFromStart = false; + attachFromLeft = false; + attachHere = node; + } + } else { + if (previous.size < Node.NODE_SIZE) { + if (nextNode.size < Node.NODE_SIZE) { + // choose prev or next for moving + removeFromStart = previous.size < nextNode.size; + } else { + // move first pair to prev + removeFromStart = true; + } + } else { + if (nextNode.size < Node.NODE_SIZE) { + // move last pair to next + removeFromStart = false; + } else { + // prev & next are full + // if node.right!=null then node.next.left==null + // if node.left!=null then node.prev.right==null + if (node.right == null) { + attachHere = node; + attachFromLeft = false; + removeFromStart = false; + } else { + attachHere = nextNode; + attachFromLeft = true; + removeFromStart = false; + } + } + } + } + K movedKey; + V movedValue; + if (removeFromStart) { + // node.left_idx == 0 + movedKey = node.keys[0]; + movedValue = node.values[0]; + int resMunus1 = result - 1; + System.arraycopy(node.keys, 1, node.keys, 0, resMunus1); + System.arraycopy(node.values, 1, node.values, 0, resMunus1); + node.keys [resMunus1] = key; + node.values[resMunus1] = value; + } else { + // node.right_idx == Node.NODE_SIZE - 1 + movedKey = node.keys[Node.NODE_SIZE - 1]; + movedValue = node.values[Node.NODE_SIZE - 1]; + System.arraycopy(node.keys, result, node.keys, result + 1, Node.NODE_SIZE - 1 - result); + System.arraycopy(node.values, result, node.values, result + 1, Node.NODE_SIZE - 1 - result); + node.keys[result] = key; + node.values[result] = value; + } + if (attachHere == null) { + if (removeFromStart) { + appendFromRight(previous, movedKey, movedValue); + } else { + appendFromLeft(nextNode, movedKey, movedValue); + } + } else { + Node newNode = createNode(movedKey, movedValue); + if (attachFromLeft) { + attachToLeft(attachHere, newNode); + } else { + attachToRight(attachHere, newNode); + } + balance(newNode); + } + } + } + return null; + } + + private void appendFromLeft(Node node, K keyObj, V value) { + if (node.left_idx == 0) { + int new_right = node.right_idx + 1; + System.arraycopy(node.keys, 0, node.keys, 1, new_right); + System.arraycopy(node.values, 0, node.values, 1, new_right); + node.right_idx = new_right; + } else { + node.left_idx--; + } + node.size++; + node.keys[node.left_idx] = keyObj; + node.values[node.left_idx] = value; + } + + private void attachToLeft(Node node, Node newNode) { + newNode.parent = node; + // node.left==null - attach here + node.left = newNode; + Node predecessor = node.prev; + newNode.prev = predecessor; + newNode.next = node; + if (predecessor != null) { + predecessor.next = newNode; + } + node.prev = newNode; + } + + /* add pair into node; existence free room in the node should be checked + * before call + */ + private void appendFromRight(Node node, K keyObj, V value) { + if (node.right_idx == Node.NODE_SIZE - 1) { + int left_idx = node.left_idx; + int left_idxMinus1 = left_idx - 1; + System.arraycopy(node.keys, left_idx, node.keys, left_idxMinus1, Node.NODE_SIZE - left_idx); + System.arraycopy(node.values, left_idx, node.values, left_idxMinus1, Node.NODE_SIZE - left_idx); + node.left_idx = left_idxMinus1; + } else { + node.right_idx++; + } + node.size++; + node.keys[node.right_idx] = keyObj; + node.values[node.right_idx] = value; + } + + private void attachToRight(Node node, Node newNode) { + newNode.parent = node; + // - node.right==null - attach here + node.right = newNode; + newNode.prev = node; + Node successor = node.next; + newNode.next = successor; + if (successor != null) { + successor.prev = newNode; + } + node.next = newNode; + } + + private Node createNode(K keyObj, V value) { + Node node = new Node<>(); + node.keys[0] = keyObj; + node.values[0] = value; + node.left_idx = 0; + node.right_idx = 0; + node.size = 1; + return node; + } + + void balance(Node x) { + Node y; + x.color = true; + while (x != root && x.parent.color) { + if (x.parent == x.parent.parent.left) { + y = x.parent.parent.right; + if (y != null && y.color) { + x.parent.color = false; + y.color = false; + x.parent.parent.color = true; + x = x.parent.parent; + } else { + if (x == x.parent.right) { + x = x.parent; + leftRotate(x); + } + x.parent.color = false; + x.parent.parent.color = true; + rightRotate(x.parent.parent); + } + } else { + y = x.parent.parent.left; + if (y != null && y.color) { + x.parent.color = false; + y.color = false; + x.parent.parent.color = true; + x = x.parent.parent; + } else { + if (x == x.parent.left) { + x = x.parent; + rightRotate(x); + } + x.parent.color = false; + x.parent.parent.color = true; + leftRotate(x.parent.parent); + } + } + } + root.color = false; + } + + private void rightRotate(Node x) { + Node y = x.left; + x.left = y.right; + if (y.right != null) { + y.right.parent = x; + } + y.parent = x.parent; + if (x.parent == null) { + root = y; + } else { + if (x == x.parent.right) { + x.parent.right = y; + } else { + x.parent.left = y; + } + } + y.right = x; + x.parent = y; + } + + + private void leftRotate(Node x) { + Node y = x.right; + x.right = y.left; + if (y.left != null) { + y.left.parent = x; + } + y.parent = x.parent; + if (x.parent == null) { + root = y; + } else { + if (x == x.parent.left) { + x.parent.left = y; + } else { + x.parent.right = y; + } + } + y.left = x; + x.parent = y; + } + + + /** + * Copies all the mappings in the given map to this map. These mappings will + * replace all mappings that this map had for any of the keys currently in + * the given map. + * + * @param map + * the map to copy mappings from. + * @throws ClassCastException + * if a key in the specified map cannot be compared with the + * keys in this map. + * @throws NullPointerException + * if a key in the specified map is {@code null} and the + * comparator cannot handle {@code null} keys. + */ + @Override + public void putAll(TMap map) { + super.putAll(map); + } + + /** + * Removes the mapping with the specified key from this map. + * + * @param key + * the key of the mapping to remove. + * @return the value of the removed mapping or {@code null} if no mapping + * for the specified key was found. + * @throws ClassCastException + * if the specified key cannot be compared with the keys in this + * map. + * @throws NullPointerException + * if the specified key is {@code null} and the comparator + * cannot handle {@code null} keys. + */ + @Override + public V remove(Object key) { + if (size == 0) { + return null; + } + @SuppressWarnings("unchecked") + TComparable object = comparator == null ? toComparable((K) key) : null; + @SuppressWarnings("unchecked") + K keyK = (K) key; + Node node = root; + while (node != null) { + K[] keys = node.keys; + int left_idx = node.left_idx; + int result = cmp(object, keyK, keys[left_idx]); + if (result < 0) { + node = node.left; + } else if (result == 0) { + V value = node.values[left_idx]; + removeLeftmost(node); + return value; + } else { + int right_idx = node.right_idx; + if (left_idx != right_idx) { + result = cmp(object, keyK, keys[right_idx]); + } + if (result > 0) { + node = node.right; + } else if (result == 0) { + V value = node.values[right_idx]; + removeRightmost(node); + return value; + } else { /*search in node*/ + int low = left_idx + 1, mid = 0, high = right_idx - 1; + while (low <= high) { + mid = (low + high) >>> 1; + result = cmp(object, keyK, keys[mid]); + if (result > 0) { + low = mid + 1; + } else if (result == 0) { + V value = node.values[mid]; + removeMiddleElement(node, mid); + return value; + } else { + high = mid - 1; + } + } + return null; + } + } + } + return null; + } + + void removeLeftmost(Node node) { + int index = node.left_idx; + if (node.size == 1) { + deleteNode(node); + } else if (node.prev != null && (Node.NODE_SIZE - 1 - node.prev.right_idx) > node.size) { + // move all to prev node and kill it + Node prev = node.prev; + int size = node.right_idx - index; + System.arraycopy(node.keys, index + 1, prev.keys, prev.right_idx + 1, size); + System.arraycopy(node.values, index + 1, prev.values, prev.right_idx + 1, size); + prev.right_idx += size; + prev.size += size; + deleteNode(node); + } else if (node.next != null && (node.next.left_idx) > node.size) { + // move all to next node and kill it + Node next = node.next; + int size = node.right_idx - index; + int next_new_left = next.left_idx - size; + next.left_idx = next_new_left; + System.arraycopy(node.keys, index + 1, next.keys, next_new_left, size); + System.arraycopy(node.values, index + 1, next.values, next_new_left, size); + next.size += size; + deleteNode(node); + } else { + node.keys[index] = null; + node.values[index] = null; + node.left_idx++; + node.size--; + Node prev = node.prev; + if (prev != null && prev.size == 1) { + node.size++; + node.left_idx--; + node.keys [node.left_idx] = prev.keys [prev.left_idx]; + node.values[node.left_idx] = prev.values[prev.left_idx]; + deleteNode(prev); + } + } + modCount++; + size--; + } + + void removeRightmost(Node node) { + int index = node.right_idx; + if (node.size == 1) { + deleteNode(node); + } else if (node.prev != null && (Node.NODE_SIZE - 1 - node.prev.right_idx) > node.size) { + // move all to prev node and kill it + Node prev = node.prev; + int left_idx = node.left_idx; + int size = index - left_idx; + System.arraycopy(node.keys, left_idx, prev.keys, prev.right_idx + 1, size); + System.arraycopy(node.values, left_idx, prev.values, prev.right_idx + 1, size); + prev.right_idx += size; + prev.size += size; + deleteNode(node); + } else if (node.next != null && (node.next.left_idx) > node.size) { + // move all to next node and kill it + Node next = node.next; + int left_idx = node.left_idx; + int size = index - left_idx; + int next_new_left = next.left_idx - size; + next.left_idx = next_new_left; + System.arraycopy(node.keys, left_idx, next.keys, next_new_left, size); + System.arraycopy(node.values, left_idx, next.values, next_new_left, size); + next.size += size; + deleteNode(node); + } else { + node.keys[index] = null; + node.values[index] = null; + node.right_idx--; + node.size--; + Node next = node.next; + if (next != null && next.size == 1) { + node.size++; + node.right_idx++; + node.keys[node.right_idx] = next.keys[next.left_idx]; + node.values[node.right_idx] = next.values[next.left_idx]; + deleteNode(next); + } + } + modCount++; + size--; + } + + void removeMiddleElement(Node node, int index) { + // this function is called iff index if some middle element; + // so node.left_idx < index < node.right_idx + // condition above assume that node.size > 1 + if (node.prev != null && (Node.NODE_SIZE - 1 - node.prev.right_idx) > node.size) { + // move all to prev node and kill it + Node prev = node.prev; + int left_idx = node.left_idx; + int size = index - left_idx; + System.arraycopy(node.keys, left_idx, prev.keys, prev.right_idx + 1, size); + System.arraycopy(node.values, left_idx, prev.values, prev.right_idx + 1, size); + prev.right_idx += size; + size = node.right_idx - index; + System.arraycopy(node.keys, index + 1, prev.keys, prev.right_idx + 1, size); + System.arraycopy(node.values, index + 1, prev.values, prev.right_idx + 1, size); + prev.right_idx += size; + prev.size += (node.size - 1); + deleteNode(node); + } else if (node.next != null && (node.next.left_idx) > node.size) { + // move all to next node and kill it + Node next = node.next; + int left_idx = node.left_idx; + int next_new_left = next.left_idx - node.size + 1; + next.left_idx = next_new_left; + int size = index - left_idx; + System.arraycopy(node.keys, left_idx, next.keys, next_new_left, size); + System.arraycopy(node.values, left_idx, next.values, next_new_left, size); + next_new_left += size; + size = node.right_idx - index; + System.arraycopy(node.keys, index + 1, next.keys, next_new_left, size); + System.arraycopy(node.values, index + 1, next.values, next_new_left, size); + next.size += (node.size - 1); + deleteNode(node); + } else { + int moveFromRight = node.right_idx - index; + int left_idx = node.left_idx; + int moveFromLeft = index - left_idx ; + if (moveFromRight <= moveFromLeft) { + System.arraycopy(node.keys, index + 1, node.keys, index, moveFromRight); + System.arraycopy(node.values, index + 1, node.values, index, moveFromRight); + Node next = node.next; + if (next != null && next.size == 1) { + node.keys [node.right_idx] = next.keys [next.left_idx]; + node.values[node.right_idx] = next.values[next.left_idx]; + deleteNode(next); + } else { + node.keys [node.right_idx] = null; + node.values[node.right_idx] = null; + node.right_idx--; + node.size--; + } + } else { + System.arraycopy(node.keys, left_idx , node.keys, left_idx + 1, moveFromLeft); + System.arraycopy(node.values, left_idx , node.values, left_idx + 1, moveFromLeft); + Node prev = node.prev; + if (prev != null && prev.size == 1) { + node.keys [left_idx ] = prev.keys [prev.left_idx]; + node.values[left_idx ] = prev.values[prev.left_idx]; + deleteNode(prev); + } else { + node.keys [left_idx ] = null; + node.values[left_idx ] = null; + node.left_idx++; + node.size--; + } + } + } + modCount++; + size--; + } + + void removeFromIterator(Node node, int index) { + if (node.size == 1) { + // it is safe to delete the whole node here. + // iterator already moved to the next node; + deleteNode(node); + } else { + int left_idx = node.left_idx; + if (index == left_idx) { + Node prev = node.prev; + if (prev != null && prev.size == 1) { + node.keys [left_idx] = prev.keys [prev.left_idx]; + node.values[left_idx] = prev.values[prev.left_idx]; + deleteNode(prev); + } else { + node.keys [left_idx] = null; + node.values[left_idx] = null; + node.left_idx++; + node.size--; + } + } else if (index == node.right_idx) { + node.keys [index] = null; + node.values[index] = null; + node.right_idx--; + node.size--; + } else { + int moveFromRight = node.right_idx - index; + int moveFromLeft = index - left_idx; + if (moveFromRight <= moveFromLeft) { + System.arraycopy(node.keys, index + 1, node.keys, index, moveFromRight ); + System.arraycopy(node.values, index + 1, node.values, index, moveFromRight ); + node.keys [node.right_idx] = null; + node.values[node.right_idx] = null; + node.right_idx--; + node.size--; + } else { + System.arraycopy(node.keys, left_idx, node.keys, left_idx+ 1, moveFromLeft); + System.arraycopy(node.values, left_idx, node.values, left_idx+ 1, moveFromLeft); + node.keys [left_idx] = null; + node.values[left_idx] = null; + node.left_idx++; + node.size--; + } + } + } + modCount++; + size--; + } + + private void deleteNode(Node node) { + if (node.right == null) { + if (node.left != null) { + attachToParent(node, node.left); + } else { + attachNullToParent(node); + } + fixNextChain(node); + } else if(node.left == null) { // node.right != null + attachToParent(node, node.right); + fixNextChain(node); + } else { + // Here node.left!=nul && node.right!=null + // node.next should replace node in tree + // node.next!=null by tree logic. + // node.next.left==null by tree logic. + // node.next.right may be null or non-null + Node toMoveUp = node.next; + fixNextChain(node); + if(toMoveUp.right==null){ + attachNullToParent(toMoveUp); + } else { + attachToParent(toMoveUp, toMoveUp.right); + } + // Here toMoveUp is ready to replace node + toMoveUp.left = node.left; + if (node.left != null) { + node.left.parent = toMoveUp; + } + toMoveUp.right = node.right; + if (node.right != null) { + node.right.parent = toMoveUp; + } + attachToParentNoFixup(node,toMoveUp); + toMoveUp.color = node.color; + } + } + + private void attachToParentNoFixup(Node toDelete, Node toConnect) { + // assert toConnect!=null + Node parent = toDelete.parent; + toConnect.parent = parent; + if (parent == null) { + root = toConnect; + } else if (toDelete == parent.left) { + parent.left = toConnect; + } else { + parent.right = toConnect; + } + } + + private void attachToParent(Node toDelete, Node toConnect) { + // assert toConnect!=null + attachToParentNoFixup(toDelete,toConnect); + if (!toDelete.color) { + fixup(toConnect); + } + } + + private void attachNullToParent(Node toDelete) { + Node parent = toDelete.parent; + if (parent == null) { + root = null; + } else { + if (toDelete == parent.left) { + parent.left = null; + } else { + parent.right = null; + } + if (!toDelete.color) { + fixup(parent); + } + } + } + + private void fixNextChain(Node node) { + if (node.prev != null) { + node.prev.next = node.next; + } + if (node.next != null) { + node.next.prev = node.prev; + } + } + + private void fixup(Node x) { + Node w; + while (x != root && !x.color) { + if (x == x.parent.left) { + w = x.parent.right; + if (w == null) { + x = x.parent; + continue; + } + if (w.color) { + w.color = false; + x.parent.color = true; + leftRotate(x.parent); + w = x.parent.right; + if (w == null) { + x = x.parent; + continue; + } + } + if ((w.left == null || !w.left.color) + && (w.right == null || !w.right.color)) { + w.color = true; + x = x.parent; + } else { + if (w.right == null || !w.right.color) { + w.left.color = false; + w.color = true; + rightRotate(w); + w = x.parent.right; + } + w.color = x.parent.color; + x.parent.color = false; + w.right.color = false; + leftRotate(x.parent); + x = root; + } + } else { + w = x.parent.left; + if (w == null) { + x = x.parent; + continue; + } + if (w.color) { + w.color = false; + x.parent.color = true; + rightRotate(x.parent); + w = x.parent.left; + if (w == null) { + x = x.parent; + continue; + } + } + if ((w.left == null || !w.left.color) + && (w.right == null || !w.right.color)) { + w.color = true; + x = x.parent; + } else { + if (w.left == null || !w.left.color) { + w.right.color = false; + w.color = true; + leftRotate(w); + w = x.parent.left; + } + w.color = x.parent.color; + x.parent.color = false; + w.left.color = false; + rightRotate(x.parent); + x = root; + } + } + } + x.color = false; + } + + + /** + * Returns the number of mappings in this map. + * + * @return the number of mappings in this map. + */ + @Override + public int size() { + return size; + } + + /** + * Returns a sorted map over a range of this sorted map with all keys + * greater than or equal to the specified {@code startKey} and less than the + * specified {@code endKey}. Changes to the returned sorted map are + * reflected in this sorted map and vice versa. + *

+ * Note: The returned map will not allow an insertion of a key outside the + * specified range. + * + * @param startKey + * the low boundary of the range (inclusive). + * @param endKey + * the high boundary of the range (exclusive), + * @return a sorted map with the key from the specified range. + * @throws ClassCastException + * if the start or end key cannot be compared with the keys in + * this map. + * @throws NullPointerException + * if the start or end key is {@code null} and the comparator + * cannot handle {@code null} keys. + * @throws IllegalArgumentException + * if the start key is greater than the end key, or if this map + * is itself a sorted map over a range of another sorted map and + * the specified range is outside of its range. + */ + @Override + public TSortedMap subMap(K startKey, K endKey) { + if (comparator == null) { + if (toComparable(startKey).compareTo(endKey) <= 0) { + return new SubMap<>(startKey, this, endKey); + } + } else { + if (comparator.compare(startKey, endKey) <= 0) { + return new SubMap<>(startKey, this, endKey); + } + } + throw new TIllegalArgumentException(); + } + + /** + * Returns a sorted map over a range of this sorted map with all keys that + * are greater than or equal to the specified {@code startKey}. Changes to + * the returned sorted map are reflected in this sorted map and vice versa. + *

+ * Note: The returned map will not allow an insertion of a key outside the + * specified range. + * + * @param startKey + * the low boundary of the range specified. + * @return a sorted map where the keys are greater or equal to + * {@code startKey}. + * @throws ClassCastException + * if the specified key cannot be compared with the keys in this + * map. + * @throws NullPointerException + * if the specified key is {@code null} and the comparator + * cannot handle {@code null} keys. + * @throws IllegalArgumentException + * if this map itself a sorted map over a range of another map + * and the specified key is outside of its range. + */ + @Override + public TSortedMap tailMap(K startKey) { + // Check for errors + if (comparator == null) { + toComparable(startKey).compareTo(startKey); + } else { + comparator.compare(startKey, startKey); + } + return new SubMap<>(startKey, this); + } + + /** + * Returns a collection of the values contained in this map. The collection + * is backed by this map so changes to one are reflected by the other. The + * collection supports remove, removeAll, retainAll and clear operations, + * and it does not support add or addAll operations. + *

+ * This method returns a collection which is the subclass of + * AbstractCollection. The iterator method of this subclass returns a + * "wrapper object" over the iterator of map's entrySet(). The {@code size} + * method wraps the map's size method and the {@code contains} method wraps + * the map's containsValue method. + *

+ * The collection is created when this method is called for the first time + * and returned in response to all subsequent calls. This method may return + * different collections when multiple concurrent calls occur, since no + * synchronization is performed. + * + * @return a collection of the values contained in this map. + */ + @Override + public TCollection values() { + if (cachedValues == null) { + cachedValues = new TAbstractCollection() { + @Override + public boolean contains(Object object) { + return containsValue(object); + } + + @Override + public int size() { + return size; + } + + @Override + public void clear() { + TTreeMap.this.clear(); + } + + @Override + public TIterator iterator() { + return new UnboundedValueIterator<>(TTreeMap.this); + } + }; + } + return cachedValues; + } +} + diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/util/BitSetTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/util/BitSetTest.java index 53778433c..7089e35b4 100644 --- a/teavm-classlib/src/test/java/org/teavm/classlib/java/util/BitSetTest.java +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/util/BitSetTest.java @@ -29,7 +29,6 @@ * License for the specific language governing permissions and limitations under * the License. */ - package org.teavm.classlib.java.util; import static org.junit.Assert.*; diff --git a/teavm-classlib/src/test/java/org/teavm/classlib/java/util/TreeMapTest.java b/teavm-classlib/src/test/java/org/teavm/classlib/java/util/TreeMapTest.java new file mode 100644 index 000000000..5fdad00ef --- /dev/null +++ b/teavm-classlib/src/test/java/org/teavm/classlib/java/util/TreeMapTest.java @@ -0,0 +1,639 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * 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. + */ +/* + * 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 org.teavm.classlib.java.util; + +import static org.junit.Assert.*; +import java.io.Serializable; +import java.text.CollationKey; +import java.text.Collator; +import java.util.AbstractMap; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import org.junit.Test; + +public class TreeMapTest { + + public static class ReversedComparator implements Comparator { + public int compare(Object o1, Object o2) { + return -(((Comparable) o1).compareTo(o2)); + } + + public boolean equals(Object o1, Object o2) { + return (((Comparable) o1).compareTo(o2)) == 0; + } + } + + // Regression for Harmony-1026 + public static class MockComparator> implements + Comparator, Serializable { + + public int compare(T o1, T o2) { + if (o1 == o2) { + return 0; + } + if (null == o1 || null == o2) { + return -1; + } + T c1 = o1; + T c2 = o2; + return c1.compareTo(c2); + } + } + + // Regression for Harmony-1161 + class MockComparatorNullTolerable implements Comparator { + + public int compare(String o1, String o2) { + if (o1 == o2) { + return 0; + } + if (null == o1) { + return -1; + } + if (null == o2) { // comparator should be symmetric + return 1; + } + return o1.compareTo(o2); + } + } + + TreeMap tm; + + Object objArray[] = new Object[1000]; + + public TreeMapTest() { + tm = new TreeMap(); + for (int i = 0; i < objArray.length; i++) { + Object x = objArray[i] = new Integer(i); + tm.put(x.toString(), x); + } + } + + + @Test + public void test_ConstructorLjava_util_Comparator() { + // Test for method java.util.TreeMap(java.util.Comparator) + Comparator comp = new ReversedComparator(); + TreeMap reversedTreeMap = new TreeMap(comp); + assertTrue("TreeMap answered incorrect comparator", reversedTreeMap + .comparator() == comp); + reversedTreeMap.put(new Integer(1).toString(), new Integer(1)); + reversedTreeMap.put(new Integer(2).toString(), new Integer(2)); + assertTrue("TreeMap does not use comparator (firstKey was incorrect)", + reversedTreeMap.firstKey().equals(new Integer(2).toString())); + assertTrue("TreeMap does not use comparator (lastKey was incorrect)", + reversedTreeMap.lastKey().equals(new Integer(1).toString())); + + } + + @Test + public void test_ConstructorLjava_util_Map() { + // Test for method java.util.TreeMap(java.util.Map) + TreeMap myTreeMap = new TreeMap(new HashMap(tm)); + assertTrue("Map is incorrect size", myTreeMap.size() == objArray.length); + for (Object element : objArray) { + assertTrue("Map has incorrect mappings", myTreeMap.get( + element.toString()).equals(element)); + } + } + + @Test + public void test_ConstructorLjava_util_SortedMap() { + // Test for method java.util.TreeMap(java.util.SortedMap) + Comparator comp = new ReversedComparator(); + TreeMap reversedTreeMap = new TreeMap(comp); + reversedTreeMap.put(new Integer(1).toString(), new Integer(1)); + reversedTreeMap.put(new Integer(2).toString(), new Integer(2)); + TreeMap anotherTreeMap = new TreeMap(reversedTreeMap); + assertTrue("New tree map does not answer correct comparator", + anotherTreeMap.comparator() == comp); + assertTrue("TreeMap does not use comparator (firstKey was incorrect)", + anotherTreeMap.firstKey().equals(new Integer(2).toString())); + assertTrue("TreeMap does not use comparator (lastKey was incorrect)", + anotherTreeMap.lastKey().equals(new Integer(1).toString())); + + } + + @Test + public void test_clear() { + // Test for method void java.util.TreeMap.clear() + tm.clear(); + assertEquals("Cleared map returned non-zero size", 0, tm.size()); + } + + @Test + public void test_clone() { + // Test for method java.lang.Object java.util.TreeMap.clone() + TreeMap clonedMap = (TreeMap) tm.clone(); + assertTrue("Cloned map does not equal the original map", clonedMap + .equals(tm)); + assertTrue("Cloned map is the same reference as the original map", + clonedMap != tm); + for (Object element : objArray) { + assertTrue("Cloned map contains incorrect elements", clonedMap + .get(element.toString()) == tm.get(element.toString())); + } + + TreeMap map = new TreeMap(); + map.put("key", "value"); + // get the keySet() and values() on the original Map + Set keys = map.keySet(); + Collection values = map.values(); + assertEquals("values() does not work", "value", values.iterator() + .next()); + assertEquals("keySet() does not work", "key", keys.iterator().next()); + AbstractMap map2 = (AbstractMap) map.clone(); + map2.put("key", "value2"); + Collection values2 = map2.values(); + assertTrue("values() is identical", values2 != values); + // values() and keySet() on the cloned() map should be different + assertEquals("values() was not cloned", "value2", values2.iterator() + .next()); + map2.clear(); + map2.put("key2", "value3"); + Set key2 = map2.keySet(); + assertTrue("keySet() is identical", key2 != keys); + assertEquals("keySet() was not cloned", "key2", key2.iterator().next()); + } + + @Test + public void test_comparator() { + // Test for method java.util.Comparator java.util.TreeMap.comparator()\ + Comparator comp = new ReversedComparator(); + TreeMap reversedTreeMap = new TreeMap(comp); + assertTrue("TreeMap answered incorrect comparator", reversedTreeMap + .comparator() == comp); + reversedTreeMap.put(new Integer(1).toString(), new Integer(1)); + reversedTreeMap.put(new Integer(2).toString(), new Integer(2)); + assertTrue("TreeMap does not use comparator (firstKey was incorrect)", + reversedTreeMap.firstKey().equals(new Integer(2).toString())); + assertTrue("TreeMap does not use comparator (lastKey was incorrect)", + reversedTreeMap.lastKey().equals(new Integer(1).toString())); + } + + @Test + public void test_containsKeyLjava_lang_Object() { + // Test for method boolean + // java.util.TreeMap.containsKey(java.lang.Object) + assertTrue("Returned false for valid key", tm.containsKey("95")); + assertTrue("Returned true for invalid key", !tm.containsKey("XXXXX")); + } + + @Test + public void test_containsValueLjava_lang_Object() { + // Test for method boolean + // java.util.TreeMap.containsValue(java.lang.Object) + assertTrue("Returned false for valid value", tm + .containsValue(objArray[986])); + assertTrue("Returned true for invalid value", !tm + .containsValue(new Object())); + } + + @Test + public void test_entrySet() { + // Test for method java.util.Set java.util.TreeMap.entrySet() + Set anEntrySet = tm.entrySet(); + Iterator entrySetIterator = anEntrySet.iterator(); + assertTrue("EntrySet is incorrect size", + anEntrySet.size() == objArray.length); + Map.Entry entry; + while (entrySetIterator.hasNext()) { + entry = (Map.Entry) entrySetIterator.next(); + assertTrue("EntrySet does not contain correct mappings", tm + .get(entry.getKey()) == entry.getValue()); + } + } + + @Test + public void test_firstKey() { + // Test for method java.lang.Object java.util.TreeMap.firstKey() + assertEquals("Returned incorrect first key", "0", tm.firstKey()); + } + + @Test + public void test_getLjava_lang_Object() { + // Test for method java.lang.Object + // java.util.TreeMap.get(java.lang.Object) + Object o = new Object(); + tm.put("Hello", o); + assertTrue("Failed to get mapping", tm.get("Hello") == o); + + } + + @Test + public void test_headMapLjava_lang_Object() { + // Test for method java.util.SortedMap + // java.util.TreeMap.headMap(java.lang.Object) + Map head = tm.headMap("100"); + assertEquals("Returned map of incorrect size", 3, head.size()); + assertTrue("Returned incorrect elements", head.containsKey("0") + && head.containsValue(new Integer("1")) + && head.containsKey("10")); + + // Regression for Harmony-1026 + TreeMap map = new TreeMap( + new MockComparator()); + map.put(1, 2.1); + map.put(2, 3.1); + map.put(3, 4.5); + map.put(7, 21.3); + map.put(null, null); + + SortedMap smap = map.headMap(null); + assertEquals(0, smap.size()); + + Set keySet = smap.keySet(); + assertEquals(0, keySet.size()); + + Set> entrySet = smap.entrySet(); + assertEquals(0, entrySet.size()); + + Collection valueCollection = smap.values(); + assertEquals(0, valueCollection.size()); + + // Regression for Harmony-1066 + assertTrue(head instanceof Serializable); + + // Regression for ill-behaved collator + Collator c = new Collator() { + @Override + public int compare(String o1, String o2) { + if (o1 == null) { + return 0; + } + return o1.compareTo(o2); + } + + @Override + public CollationKey getCollationKey(String string) { + return null; + } + + @Override + public int hashCode() { + return 0; + } + }; + + TreeMap treemap = new TreeMap(c); + assertEquals(0, treemap.headMap(null).size()); + + treemap = new TreeMap(); + SortedMap headMap = treemap.headMap("100"); + headMap.headMap("100"); + + SortedMap intMap,sub; + int size = 16; + intMap = new TreeMap(); + for(int i=0; i(); + for(int i=0; i treeMapWithNull = new TreeMap( + new MockComparatorNullTolerable()); + treeMapWithNull.put("key1", "value1"); //$NON-NLS-1$ //$NON-NLS-2$ + treeMapWithNull.put(null, "value2"); //$NON-NLS-1$ + SortedMap subMapWithNull = treeMapWithNull.subMap(null, + "key1"); //$NON-NLS-1$ + assertEquals("Size of subMap should be 1:", 1, subMapWithNull.size()); //$NON-NLS-1$ + + // Regression test for typo in lastKey method + SortedMap map = new TreeMap(); + map.put("1", "one"); //$NON-NLS-1$ //$NON-NLS-2$ + map.put("2", "two"); //$NON-NLS-1$ //$NON-NLS-2$ + map.put("3", "three"); //$NON-NLS-1$ //$NON-NLS-2$ + assertEquals("3", map.lastKey()); + SortedMap sub = map.subMap("1", "3"); //$NON-NLS-1$ //$NON-NLS-2$ + assertEquals("2", sub.lastKey()); //$NON-NLS-1$ + } + + + @Test + public void test_subMap_Iterator() { + TreeMap map = new TreeMap(); + + String[] keys = { "1", "2", "3" }; + String[] values = { "one", "two", "three" }; + for (int i = 0; i < keys.length; i++) { + map.put(keys[i], values[i]); + } + + assertEquals(3, map.size()); + + Map subMap = map.subMap("", "test"); + assertEquals(3, subMap.size()); + + Set entrySet = subMap.entrySet(); + Iterator iter = entrySet.iterator(); + int size = 0; + while (iter.hasNext()) { + Map.Entry entry = (Map.Entry) iter + .next(); + assertTrue(map.containsKey(entry.getKey())); + assertTrue(map.containsValue(entry.getValue())); + size++; + } + assertEquals(map.size(), size); + + Set keySet = subMap.keySet(); + iter = keySet.iterator(); + size = 0; + while (iter.hasNext()) { + String key = (String) iter.next(); + assertTrue(map.containsKey(key)); + size++; + } + assertEquals(map.size(), size); + } + + + @Test + public void test_tailMapLjava_lang_Object() { + // Test for method java.util.SortedMap + // java.util.TreeMap.tailMap(java.lang.Object) + Map tail = tm.tailMap(objArray[900].toString()); + assertTrue("Returned map of incorrect size : " + tail.size(), tail + .size() == (objArray.length - 900) + 9); + for (int i = 900; i < objArray.length; i++) { + assertTrue("Map contains incorrect entries", tail + .containsValue(objArray[i])); + } + + // Regression for Harmony-1066 + assertTrue(tail instanceof Serializable); + + SortedMap intMap,sub; + int size = 16; + intMap = new TreeMap(); + for(int i=0; i(); + for(int i=0; i(); + TreeMap test_map = new TreeMap(); + + master.put("null", null); + Object[] entry = master.entrySet().toArray(); + assertFalse("Empty map should not contain the null-valued entry", + test_map.entrySet().contains(entry[0])); + + Map submap = test_map.subMap("a","z"); + entry = master.entrySet().toArray(); + assertFalse("Empty submap should not contain the null-valued entry", + submap.entrySet().contains(entry[0])); + + test_map.put("null", null); + assertTrue("entrySet().containsAll(...) should work with null values", + test_map.entrySet().containsAll(master.entrySet())); + + master.clear(); + master.put("null", '0'); + entry = master.entrySet().toArray(); + assertFalse("Null-valued entry should not equal non-null-valued entry", + test_map.entrySet().contains(entry[0])); + } + +}