classlib: fix various issues in TreeMap (#813)

This commit is contained in:
Ivan Hetman 2023-10-13 22:01:40 +03:00 committed by GitHub
parent 6faecc91d2
commit 82cd9d9cdf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 2136 additions and 120 deletions

View File

@ -237,14 +237,18 @@ public abstract class TAbstractMap<K, V> extends TObject implements TMap<K, V> {
if (size() != other.size()) { if (size() != other.size()) {
return false; return false;
} }
for (TIterator<? extends TMap.Entry<K, V>> iter = entrySet().iterator(); iter.hasNext();) { try {
TMap.Entry<K, V> entry = iter.next(); for (var it = entrySet().iterator(); it.hasNext();) {
if (!other.containsKey(entry.getKey())) { TMap.Entry<K, V> entry = it.next();
return false; if (!other.containsKey(entry.getKey())) {
} return false;
if (!TObjects.equals(entry.getValue(), other.get(entry.getKey()))) { }
return false; if (!TObjects.equals(entry.getValue(), other.get(entry.getKey()))) {
return false;
}
} }
} catch (ClassCastException | NullPointerException e) {
return false;
} }
return true; return true;
} }

View File

@ -38,12 +38,16 @@ public interface TNavigableMap<K, V> extends TSortedMap<K, V> {
K higherKey(K key); K higherKey(K key);
@Override
Entry<K, V> firstEntry(); Entry<K, V> firstEntry();
@Override
Entry<K, V> lastEntry(); Entry<K, V> lastEntry();
@Override
Entry<K, V> pollFirstEntry(); Entry<K, V> pollFirstEntry();
@Override
Entry<K, V> pollLastEntry(); Entry<K, V> pollLastEntry();
TNavigableMap<K, V> descendingMap(); TNavigableMap<K, V> descendingMap();

View File

@ -17,7 +17,6 @@ package org.teavm.classlib.java.util;
import org.teavm.classlib.java.io.TSerializable; import org.teavm.classlib.java.io.TSerializable;
import org.teavm.classlib.java.lang.TCloneable; import org.teavm.classlib.java.lang.TCloneable;
import org.teavm.classlib.java.lang.TIllegalArgumentException;
public class TTreeMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TSerializable, TNavigableMap<K, V> { public class TTreeMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TSerializable, TNavigableMap<K, V> {
static class TreeNode<K, V> extends SimpleEntry<K, V> { static class TreeNode<K, V> extends SimpleEntry<K, V> {
@ -89,6 +88,10 @@ public class TTreeMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
} }
} }
private static <K, V> TMap.Entry<K, V> clone(TMap.Entry<K, V> entry) {
return entry == null ? null : TMap.entry(entry.getKey(), entry.getValue());
}
TreeNode<K, V> root; TreeNode<K, V> root;
private TComparator<? super K> comparator; private TComparator<? super K> comparator;
private TComparator<? super K> originalComparator; private TComparator<? super K> originalComparator;
@ -195,9 +198,11 @@ public class TTreeMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
TreeNode<?, V> findExact(Object key) { TreeNode<?, V> findExact(Object key) {
TreeNode<K, V> node = root; TreeNode<K, V> node = root;
@SuppressWarnings("unchecked")
K k = (K) key;
comparator.compare(k, k);
while (node != null) { while (node != null) {
@SuppressWarnings("unchecked") int cmp = comparator.compare(k, node.getKey());
int cmp = comparator.compare((K) key, node.getKey());
if (cmp == 0) { if (cmp == 0) {
return node; return node;
} else if (cmp < 0) { } else if (cmp < 0) {
@ -374,20 +379,17 @@ public class TTreeMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
@Override @Override
public TSortedMap<K, V> subMap(K fromKey, K toKey) { public TSortedMap<K, V> subMap(K fromKey, K toKey) {
if (comparator.compare(fromKey, toKey) > 0) { return subMap(fromKey, true, toKey, false);
throw new TIllegalArgumentException();
}
return new MapView<>(this, fromKey, true, true, toKey, false, true, false);
} }
@Override @Override
public TNavigableMap<K, V> headMap(K toKey) { public TNavigableMap<K, V> headMap(K toKey) {
return new MapView<>(this, null, true, false, toKey, false, true, false); return headMap(toKey, false);
} }
@Override @Override
public TNavigableMap<K, V> tailMap(K fromKey) { public TNavigableMap<K, V> tailMap(K fromKey) {
return new MapView<>(this, fromKey, true, true, null, false, false, false); return tailMap(fromKey, true);
} }
@Override @Override
@ -410,7 +412,7 @@ public class TTreeMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
@Override @Override
public Entry<K, V> lowerEntry(K key) { public Entry<K, V> lowerEntry(K key) {
return findNext(key, true); return clone(findNext(key, true));
} }
@Override @Override
@ -421,7 +423,7 @@ public class TTreeMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
@Override @Override
public Entry<K, V> floorEntry(K key) { public Entry<K, V> floorEntry(K key) {
return findExactOrNext(key, true); return clone(findExactOrNext(key, true));
} }
@Override @Override
@ -432,7 +434,7 @@ public class TTreeMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
@Override @Override
public Entry<K, V> ceilingEntry(K key) { public Entry<K, V> ceilingEntry(K key) {
return findExactOrNext(key, false); return clone(findExactOrNext(key, false));
} }
@Override @Override
@ -443,7 +445,7 @@ public class TTreeMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
@Override @Override
public Entry<K, V> higherEntry(K key) { public Entry<K, V> higherEntry(K key) {
return findNext(key, false); return clone(findNext(key, false));
} }
@Override @Override
@ -454,30 +456,32 @@ public class TTreeMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
@Override @Override
public Entry<K, V> firstEntry() { public Entry<K, V> firstEntry() {
return firstNode(false); return clone(firstNode(false));
} }
@Override @Override
public Entry<K, V> lastEntry() { public Entry<K, V> lastEntry() {
return firstNode(true); return clone(firstNode(true));
} }
@Override @Override
public Entry<K, V> pollFirstEntry() { public Entry<K, V> pollFirstEntry() {
TreeNode<K, V> node = firstNode(false); TreeNode<K, V> node = firstNode(false);
if (node != null) { if (node != null) {
remove(node.getKey()); root = deleteNode(root, node.getKey());
modCount++;
} }
return node; return clone(node);
} }
@Override @Override
public Entry<K, V> pollLastEntry() { public Entry<K, V> pollLastEntry() {
TreeNode<K, V> node = firstNode(true); TreeNode<K, V> node = firstNode(true);
if (node != null) { if (node != null) {
remove(node.getKey()); root = deleteNode(root, node.getKey());
modCount++;
} }
return node; return clone(node);
} }
@Override @Override
@ -584,21 +588,26 @@ public class TTreeMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
this.reverse = reverse; this.reverse = reverse;
} }
@Override
public boolean isEmpty() {
return !iterator().hasNext();
}
@Override @Override
public int size() { public int size() {
int size = cachedSize;
if (modCount != owner.modCount) { if (modCount != owner.modCount) {
modCount = owner.modCount; modCount = owner.modCount;
size = owner.size(); int size = owner.size();
TreeNode<K, V>[] fromPath = null;
if (fromChecked) { if (fromChecked) {
TreeNode<K, V>[] path = fromIncluded ? owner.pathToNext(from, true) fromPath = fromIncluded ? owner.pathToNext(from, true)
: owner.pathToExactOrNext(from, true); : owner.pathToExactOrNext(from, true);
for (TreeNode<K, V> node : path) { for (TreeNode<K, V> node : fromPath) {
if (node.left != null) { if (node.left != null) {
size -= node.left.size; size -= node.left.size;
} }
} }
size -= path.length; size -= fromPath.length;
} }
if (toChecked) { if (toChecked) {
TreeNode<K, V>[] path = toIncluded ? owner.pathToNext(to, false) TreeNode<K, V>[] path = toIncluded ? owner.pathToNext(to, false)
@ -609,10 +618,14 @@ public class TTreeMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
} }
} }
size -= path.length; size -= path.length;
if (fromPath != null && fromPath.length > 0 && path.length > 0
&& fromPath[fromPath.length - 1] == path[path.length - 1]) {
size++;
}
} }
cachedSize = size; cachedSize = size;
} }
return size; return cachedSize;
} }
@Override @Override
@ -665,11 +678,6 @@ public class TTreeMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
return node != null && node.equals(o); return node != null && node.equals(o);
} }
@Override
public boolean isEmpty() {
return size() == 0;
}
@Override @Override
public TSequencedSet<Entry<K, V>> reversed() { public TSequencedSet<Entry<K, V>> reversed() {
return new EntrySet<>(owner, from, fromIncluded, fromChecked, to, toIncluded, toChecked, !reverse); return new EntrySet<>(owner, from, fromIncluded, fromChecked, to, toIncluded, toChecked, !reverse);
@ -740,7 +748,7 @@ public class TTreeMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
if (cmp > 0) { if (cmp > 0) {
depth = 0; depth = 0;
} }
} else { } else {
if (cmp >= 0) { if (cmp >= 0) {
depth = 0; depth = 0;
} }
@ -753,23 +761,18 @@ public class TTreeMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
throw new TConcurrentModificationException(); throw new TConcurrentModificationException();
} }
if (last == null) { if (last == null) {
throw new TNoSuchElementException(); throw new IllegalStateException();
}
var newRoot = owner.deleteNode(owner.root, last.getKey());
if (owner.root != newRoot) {
owner.root = newRoot;
var newPath = owner.pathToNext(last.getKey(), reverse);
System.arraycopy(newPath, 0, path, 0, newPath.length);
depth = newPath.length;
} }
owner.root = owner.deleteNode(owner.root, last.getKey());
var newPath = owner.pathToNext(last.getKey(), reverse);
System.arraycopy(newPath, 0, path, 0, newPath.length);
depth = newPath.length;
modCount = ++owner.modCount; modCount = ++owner.modCount;
last = null; last = null;
} }
} }
static class MapView<K, V> extends TAbstractMap<K, V> implements TNavigableMap<K, V>, TSerializable { static class MapView<K, V> extends TAbstractMap<K, V> implements TNavigableMap<K, V>, TSerializable {
private int modCount = -1;
private int cachedSize;
private TTreeMap<K, V> owner; private TTreeMap<K, V> owner;
private K from; private K from;
private boolean fromIncluded; private boolean fromIncluded;
@ -783,6 +786,7 @@ public class TTreeMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
MapView(TTreeMap<K, V> owner, K from, boolean fromIncluded, boolean fromChecked, MapView(TTreeMap<K, V> owner, K from, boolean fromIncluded, boolean fromChecked,
K to, boolean toIncluded, boolean toChecked, boolean reverse) { K to, boolean toIncluded, boolean toChecked, boolean reverse) {
check(owner, from, fromChecked, to, toChecked);
this.owner = owner; this.owner = owner;
this.from = from; this.from = from;
this.fromIncluded = fromIncluded; this.fromIncluded = fromIncluded;
@ -791,6 +795,25 @@ public class TTreeMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
this.toIncluded = toIncluded; this.toIncluded = toIncluded;
this.toChecked = toChecked; this.toChecked = toChecked;
this.reverse = reverse; this.reverse = reverse;
if (reverse) {
owner.ensureRevertedComparator();
}
}
private void check(TTreeMap<K, V> owner, K from, boolean fromChecked, K to, boolean toChecked) {
if (fromChecked) {
if (toChecked) {
if (owner.comparator.compare(from, to) > 0) {
throw new IllegalArgumentException();
}
} else {
owner.comparator.compare(from, from);
}
} else {
if (toChecked) {
owner.comparator.compare(to, to);
}
}
} }
@Override @Override
@ -800,18 +823,28 @@ public class TTreeMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
@Override @Override
public TComparator<? super K> comparator() { public TComparator<? super K> comparator() {
if (!reverse) { return !reverse ? owner.originalComparator : owner.revertedComparator;
return owner.originalComparator; }
} else {
owner.ensureRevertedComparator(); private void checkKey(K key, boolean inclusive) {
return owner.revertedComparator; boolean inRange = inclusive ? keyInRange(key) : keyInClosedRange(key);
if (!inRange) {
throw new IllegalArgumentException();
} }
} }
private void checkKey(K key) { private boolean keyInClosedRange(K key) {
if (!keyInRange(key)) { if (fromChecked) {
throw new TIllegalArgumentException(); if (owner.comparator.compare(key, from) < 0) {
return false;
}
} }
if (toChecked) {
if (owner.comparator.compare(key, to) > 0) {
return false;
}
}
return true;
} }
private boolean keyInRange(K key) { private boolean keyInRange(K key) {
@ -823,7 +856,7 @@ public class TTreeMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
} }
if (toChecked) { if (toChecked) {
int cmp = owner.comparator.compare(key, to); int cmp = owner.comparator.compare(key, to);
if (fromIncluded ? cmp > 0 : cmp >= 0) { if (toIncluded ? cmp > 0 : cmp >= 0) {
return false; return false;
} }
} }
@ -850,48 +883,29 @@ public class TTreeMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
@Override @Override
public V put(K key, V value) { public V put(K key, V value) {
checkKey(key); if (!keyInRange(key)) {
throw new IllegalArgumentException();
}
return owner.put(key, value); return owner.put(key, value);
} }
@Override @Override
public void clear() { public void clear() {
if (!fromChecked && !toChecked) { if (fromChecked || toChecked) {
owner.clear(); entrySet().clear();
} else { } else {
super.clear(); owner.clear();
} }
} }
@Override
public boolean isEmpty() {
return fromChecked || toChecked ? entrySet().isEmpty() : owner.isEmpty();
}
@Override @Override
public int size() { public int size() {
int size = cachedSize; return fromChecked || toChecked ? entrySet().size() : owner.size();
if (modCount != owner.modCount) {
modCount = owner.modCount;
size = owner.size();
if (fromChecked) {
TreeNode<K, V>[] path = fromIncluded ? owner.pathToNext(from, true)
: owner.pathToExactOrNext(from, true);
for (TreeNode<K, V> node : path) {
if (node.left != null) {
size -= node.left.size;
}
}
size -= path.length;
}
if (toChecked) {
TreeNode<K, V>[] path = toIncluded ? owner.pathToNext(to, false)
: owner.pathToExactOrNext(to, false);
for (TreeNode<K, V> node : path) {
if (node.right != null) {
size -= node.right.size;
}
}
size -= path.length;
}
cachedSize = size;
}
return size;
} }
@Override @Override
@ -906,28 +920,13 @@ public class TTreeMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
if (toChecked) { if (toChecked) {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
int cmp = owner.comparator.compare((K) key, to); int cmp = owner.comparator.compare((K) key, to);
if (fromIncluded ? cmp > 0 : cmp >= 0) { if (toIncluded ? cmp > 0 : cmp >= 0) {
return false; return false;
} }
} }
return owner.containsKey(key); return owner.containsKey(key);
} }
@Override
public TSortedMap<K, V> subMap(K fromKey, K toKey) {
return subMap(fromKey, true, toKey, false);
}
@Override
public TSortedMap<K, V> headMap(K toKey) {
return headMap(toKey, false);
}
@Override
public TSortedMap<K, V> tailMap(K fromKey) {
return tailMap(fromKey, true);
}
@Override @Override
public K firstKey() { public K firstKey() {
TreeNode<K, V> node = !reverse ? firstNode() : lastNode(); TreeNode<K, V> node = !reverse ? firstNode() : lastNode();
@ -1028,30 +1027,32 @@ public class TTreeMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
@Override @Override
public Entry<K, V> firstEntry() { public Entry<K, V> firstEntry() {
return !reverse ? firstNode() : lastNode(); return TTreeMap.clone(!reverse ? firstNode() : lastNode());
} }
@Override @Override
public Entry<K, V> lastEntry() { public Entry<K, V> lastEntry() {
return !reverse ? lastNode() : firstNode(); return TTreeMap.clone(!reverse ? lastNode() : firstNode());
} }
@Override @Override
public Entry<K, V> pollFirstEntry() { public Entry<K, V> pollFirstEntry() {
TreeNode<K, V> node = !reverse ? firstNode() : lastNode(); TreeNode<K, V> node = !reverse ? firstNode() : lastNode();
if (node != null) { if (node != null) {
owner.remove(node.getKey()); owner.root = owner.deleteNode(owner.root, node.getKey());
owner.modCount++;
} }
return node; return TTreeMap.clone(node);
} }
@Override @Override
public Entry<K, V> pollLastEntry() { public Entry<K, V> pollLastEntry() {
TreeNode<K, V> node = !reverse ? lastNode() : firstNode(); TreeNode<K, V> node = !reverse ? lastNode() : firstNode();
if (node != null) { if (node != null) {
owner.remove(node.getKey()); owner.root = owner.deleteNode(owner.root, node.getKey());
owner.modCount++;
} }
return node; return TTreeMap.clone(node);
} }
@Override @Override
@ -1094,26 +1095,31 @@ public class TTreeMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
return descendingMap().navigableKeySet(); return descendingMap().navigableKeySet();
} }
@Override
public TSortedMap<K, V> subMap(K fromKey, K toKey) {
return subMap(fromKey, true, toKey, false);
}
@Override @Override
public TNavigableMap<K, V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) { public TNavigableMap<K, V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) {
checkKey(fromKey); checkKey(fromKey, fromInclusive);
checkKey(toKey); checkKey(toKey, toInclusive);
if (!reverse) { if (!reverse) {
if (owner.comparator.compare(fromKey, toKey) > 0) {
throw new IllegalArgumentException();
}
return new MapView<>(owner, fromKey, fromInclusive, true, toKey, toInclusive, true, false); return new MapView<>(owner, fromKey, fromInclusive, true, toKey, toInclusive, true, false);
} else { } else {
if (owner.comparator.compare(fromKey, toKey) < 0) {
throw new IllegalArgumentException();
}
return new MapView<>(owner, toKey, toInclusive, true, fromKey, fromInclusive, true, true); return new MapView<>(owner, toKey, toInclusive, true, fromKey, fromInclusive, true, true);
} }
} }
@Override
public TSortedMap<K, V> headMap(K toKey) {
return headMap(toKey, false);
}
@Override @Override
public TNavigableMap<K, V> headMap(K toKey, boolean inclusive) { public TNavigableMap<K, V> headMap(K toKey, boolean inclusive) {
checkKey(toKey); checkKey(toKey, inclusive);
if (!reverse) { if (!reverse) {
return new MapView<>(owner, from, fromIncluded, fromChecked, toKey, inclusive, true, false); return new MapView<>(owner, from, fromIncluded, fromChecked, toKey, inclusive, true, false);
} else { } else {
@ -1121,9 +1127,14 @@ public class TTreeMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
} }
} }
@Override
public TSortedMap<K, V> tailMap(K fromKey) {
return tailMap(fromKey, true);
}
@Override @Override
public TNavigableMap<K, V> tailMap(K fromKey, boolean inclusive) { public TNavigableMap<K, V> tailMap(K fromKey, boolean inclusive) {
checkKey(fromKey); checkKey(fromKey, inclusive);
if (!reverse) { if (!reverse) {
return new MapView<>(owner, fromKey, inclusive, true, to, toIncluded, toChecked, false); return new MapView<>(owner, fromKey, inclusive, true, to, toIncluded, toChecked, false);
} else { } else {
@ -1169,11 +1180,33 @@ public class TTreeMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
return map.lastKey(); return map.lastKey();
} }
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override
public boolean contains(Object o) {
return map.containsKey(o);
}
@Override @Override
public int size() { public int size() {
return map.size(); return map.size();
} }
@Override
public void clear() {
map.clear();
}
@Override
public boolean remove(Object o) {
int old = map.size();
map.remove(o);
return map.size() != old;
}
@Override @Override
public TIterator<K> iterator() { public TIterator<K> iterator() {
return map.keySet().iterator(); return map.keySet().iterator();
@ -1244,6 +1277,11 @@ public class TTreeMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
this.map = map; this.map = map;
} }
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override @Override
public int size() { public int size() {
return map.size(); return map.size();
@ -1265,6 +1303,18 @@ public class TTreeMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
}; };
} }
@Override
public boolean remove(Object o) {
for (TIterator<TMap.Entry<K, V>> it = map.entrySet().iterator(); it.hasNext();) {
TMap.Entry<K, V> e = it.next();
if (TObjects.equals(e.getValue(), o)) {
it.remove();
return true;
}
}
return false;
}
@Override @Override
public TSequencedCollection<V> reversed() { public TSequencedCollection<V> reversed() {
return new NavigableMapValues<>(map.reversed()); return new NavigableMapValues<>(map.reversed());

View File

@ -46,6 +46,16 @@ public class TTreeSet<E> extends TAbstractSet<E> implements TNavigableSet<E> {
} }
} }
@Override
public boolean contains(Object o) {
return map.containsKey(o);
}
@Override
public boolean isEmpty() {
return map.isEmpty();
}
@Override @Override
public int size() { public int size() {
return map.size(); return map.size();

File diff suppressed because it is too large Load Diff