diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TAbstractMap.java b/classlib/src/main/java/org/teavm/classlib/java/util/TAbstractMap.java index 550702dad..2246f76b8 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TAbstractMap.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TAbstractMap.java @@ -292,7 +292,7 @@ public abstract class TAbstractMap extends TObject implements TMap { private class KeySet extends TAbstractSet { @Override public TIterator iterator() { - final TIterator> it = TAbstractMap.this.entrySet().iterator(); + final TIterator> it = entrySet().iterator(); return new TIterator<>() { @Override public boolean hasNext() { return it.hasNext(); @@ -320,16 +320,16 @@ public abstract class TAbstractMap extends TObject implements TMap { @Override public TIterator iterator() { - final TIterator> iter = TAbstractMap.this.entrySet().iterator(); - return new TIterator() { + final TIterator> it = entrySet().iterator(); + return new TIterator<>() { @Override public boolean hasNext() { - return iter.hasNext(); + return it.hasNext(); } @Override public V next() { - return iter.next().getValue(); + return it.next().getValue(); } @Override public void remove() { - iter.remove(); + it.remove(); } }; } diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TCollection.java b/classlib/src/main/java/org/teavm/classlib/java/util/TCollection.java index 0b6979c88..3168da37a 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TCollection.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TCollection.java @@ -15,7 +15,6 @@ */ package org.teavm.classlib.java.util; -import java.util.Spliterator; import java.util.function.IntFunction; import java.util.function.Predicate; import org.teavm.classlib.java.lang.TIterable; @@ -59,7 +58,7 @@ public interface TCollection extends TIterable { @SuppressWarnings("unchecked") default TStream stream() { - return new TStreamOverSpliterator<>((Spliterator) spliterator()); + return new TStreamOverSpliterator<>(spliterator()); } default boolean removeIf(Predicate filter) { diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TNavigableMap.java b/classlib/src/main/java/org/teavm/classlib/java/util/TNavigableMap.java index 99fa032bc..ace028567 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TNavigableMap.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TNavigableMap.java @@ -57,4 +57,14 @@ public interface TNavigableMap extends TSortedMap { TNavigableMap headMap(K toKey, boolean inclusive); TNavigableMap tailMap(K fromKey, boolean inclusive); + + @Override + default TSequencedSet sequencedKeySet() { + return navigableKeySet(); + } + + @Override + default TNavigableMap reversed() { + return descendingMap(); + } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TNavigableSet.java b/classlib/src/main/java/org/teavm/classlib/java/util/TNavigableSet.java index 0e2715d64..03073252f 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TNavigableSet.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TNavigableSet.java @@ -42,4 +42,27 @@ public interface TNavigableSet extends TSortedSet { TNavigableSet headSet(E toElement, boolean inclusive); TNavigableSet tailSet(E fromElement, boolean inclusive); + + @Override + default E removeFirst() { + if (isEmpty()) { + throw new TNoSuchElementException(); + } else { + return pollFirst(); + } + } + + @Override + default E removeLast() { + if (isEmpty()) { + throw new TNoSuchElementException(); + } else { + return pollLast(); + } + } + + @Override + default TNavigableSet reversed() { + return descendingSet(); + } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TReversedSortedSet.java b/classlib/src/main/java/org/teavm/classlib/java/util/TReversedSortedSet.java new file mode 100644 index 000000000..a24b2cab8 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TReversedSortedSet.java @@ -0,0 +1,320 @@ +/* + * Copyright 2023 ihromant. + * + * 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; + +class TReversedSortedSet extends TAbstractSet implements TSortedSet { + private final TSortedSet base; + + TReversedSortedSet(TSortedSet base) { + this.base = base; + } + + @Override + public TIterator iterator() { + return new DescendingSortedSetIterator<>(base); + } + + @Override + public boolean add(E e) { + base.add(e); + return true; + } + + @Override + public boolean addAll(TCollection c) { + return base.addAll(c); + } + + @Override + public void clear() { + base.clear(); + } + + @Override + public boolean contains(Object o) { + return base.contains(o); + } + + @Override + public boolean containsAll(TCollection c) { + return base.containsAll(c); + } + + @Override + public boolean isEmpty() { + return base.isEmpty(); + } + + @Override + public boolean remove(Object o) { + return base.remove(o); + } + + @Override + public E removeFirst() { + return base.removeLast(); + } + + @Override + public E removeLast() { + return base.removeFirst(); + } + + @Override + public boolean removeAll(TCollection c) { + return base.removeAll(c); + } + + @Override + public boolean retainAll(TCollection c) { + return base.retainAll(c); + } + + @Override + public int size() { + return base.size(); + } + + @Override + public TComparator comparator() { + TComparator comp = base.comparator(); + return comp != null ? comp.reversed() : TCollections.reverseOrder(); + } + + @Override + public E first() { + return base.last(); + } + + @Override + public E last() { + return base.first(); + } + + @Override + public E getFirst() { + return base.last(); + } + + @Override + public E getLast() { + return base.first(); + } + + @Override + public TSortedSet headSet(E to) { + return new DescSubset<>(base, null, to); + } + + @Override + public TSortedSet subSet(E from, E to) { + return new DescSubset<>(base, from, to); + } + + @Override + public TSortedSet tailSet(E from) { + return new DescSubset<>(base, from, null); + } + + static class DescSubset extends TAbstractSet implements TSortedSet { + final TSortedSet base; + final E head; + final E tail; + private final TComparator reversed; + + @SuppressWarnings("unchecked") + DescSubset(TSortedSet base, E head, E tail) { + this.base = base; + this.head = head; + this.tail = tail; + this.reversed = (TComparator) TCollections.reverseOrder(base.comparator()); + } + + private boolean aboveHeadInc(E e) { + return head == null || reversed.compare(e, head) >= 0; + } + + private boolean belowTailExc(E e) { + return tail == null || reversed.compare(e, tail) < 0; + } + + @Override + public TIterator iterator() { + return new TIterator<>() { + private final TIterator it = new DescendingSortedSetIterator<>(base); + private E current; + private boolean finished; + + @Override + public boolean hasNext() { + if (finished) { + return false; + } + if (current != null) { + return true; + } + while (it.hasNext()) { + E e = it.next(); + if (!aboveHeadInc(e)) { + continue; + } + if (!belowTailExc(e)) { + finished = true; + return false; + } + current = e; + return true; + } + return false; + } + + @Override + public E next() { + if (hasNext()) { + E e = current; + current = null; + return e; + } else { + throw new TNoSuchElementException(); + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + public boolean add(E e) { + if (aboveHeadInc(e) && belowTailExc(e)) { + return base.add(e); + } else { + throw new IllegalArgumentException(); + } + } + + @SuppressWarnings("unchecked") + @Override + public boolean remove(Object o) { + E e = (E) o; + if (aboveHeadInc(e) && belowTailExc(e)) { + return base.remove(o); + } else { + return false; + } + } + + @Override + public int size() { + int size = 0; + for (var it = iterator(); it.hasNext(); it.next()) { + size++; + } + return size; + } + + @Override + public TComparator comparator() { + TComparator comp = base.comparator(); + return comp != null ? comp.reversed() : TCollections.reverseOrder(); + } + + @Override + public E first() { + return iterator().next(); + } + + @Override + public E last() { + TIterator it = iterator(); + if (!it.hasNext()) { + throw new TNoSuchElementException(); + } + E last = it.next(); + while (it.hasNext()) { + last = it.next(); + } + return last; + } + + @Override + public TSortedSet subSet(E from, E to) { + if (aboveHeadInc(from) && belowTailExc(from) + && aboveHeadInc(to) && belowTailExc(to) + && reversed.compare(from, to) <= 0) { + return new DescSubset<>(base, from, to); + } else { + throw new IllegalArgumentException(); + } + } + + @Override + public TSortedSet headSet(E to) { + if (aboveHeadInc(to) && belowTailExc(to)) { + return new DescSubset<>(base, head, to); + } else { + throw new IllegalArgumentException(); + } + } + + @Override + public TSortedSet tailSet(E from) { + if (aboveHeadInc(from) && belowTailExc(from)) { + return new DescSubset<>(base, null, tail); + } else { + throw new IllegalArgumentException(); + } + } + } + + private static class DescendingSortedSetIterator implements TIterator { + private final TSortedSet base; + private TSortedSet remaining; + private E current; + + private DescendingSortedSetIterator(TSortedSet base) { + this.base = base; + this.remaining = base; + } + + @Override + public boolean hasNext() { + return !remaining.isEmpty(); + } + + @Override + public E next() { + if (remaining.isEmpty()) { + throw new TNoSuchElementException(); + } + current = remaining.last(); + remaining = base.headSet(current); + return current; + } + + @Override + public void remove() { + if (current == null) { + throw new IllegalStateException(); + } else { + base.remove(current); + current = null; + } + } + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TSortedMap.java b/classlib/src/main/java/org/teavm/classlib/java/util/TSortedMap.java index 4815accc3..0d5e715c6 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TSortedMap.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TSortedMap.java @@ -21,7 +21,7 @@ package org.teavm.classlib.java.util; * @param * @param */ -public interface TSortedMap extends TMap { +public interface TSortedMap extends TSequencedMap { TComparator comparator(); TSortedMap subMap(K fromKey, K toKey); diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TSortedSet.java b/classlib/src/main/java/org/teavm/classlib/java/util/TSortedSet.java index d9d92be93..43368149c 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TSortedSet.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TSortedSet.java @@ -20,7 +20,7 @@ package org.teavm.classlib.java.util; * @author Alexey Andreev * @param */ -public interface TSortedSet extends TSet { +public interface TSortedSet extends TSet, TSequencedSet { TComparator comparator(); TSortedSet subSet(E fromElement, E toElement); @@ -32,4 +32,33 @@ public interface TSortedSet extends TSet { E first(); E last(); + + @Override + default E getFirst() { + return first(); + } + + @Override + default E getLast() { + return last(); + } + + @Override + default E removeFirst() { + E e = this.first(); + this.remove(e); + return e; + } + + @Override + default E removeLast() { + E e = this.last(); + this.remove(e); + return e; + } + + @Override + default TSortedSet reversed() { + return new TReversedSortedSet<>(this); + } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TTreeMap.java b/classlib/src/main/java/org/teavm/classlib/java/util/TTreeMap.java index 30775eff7..fad62b590 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TTreeMap.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TTreeMap.java @@ -364,10 +364,7 @@ public class TTreeMap extends TAbstractMap implements TCloneable, TS @Override public TSet> entrySet() { - if (cachedEntrySet == null) { - cachedEntrySet = new EntrySet<>(this, null, true, false, null, true, false, false); - } - return cachedEntrySet; + return sequencedEntrySet(); } @Override @@ -469,7 +466,7 @@ public class TTreeMap extends TAbstractMap implements TCloneable, TS public Entry pollFirstEntry() { TreeNode node = firstNode(false); if (node != null) { - root = deleteNode(root, node.getKey()); + remove(node.getKey()); } return node; } @@ -478,11 +475,32 @@ public class TTreeMap extends TAbstractMap implements TCloneable, TS public Entry pollLastEntry() { TreeNode node = firstNode(true); if (node != null) { - root = deleteNode(root, node.getKey()); + remove(node.getKey()); } return node; } + @Override + public TCollection values() { + return sequencedValues(); + } + + @Override + public TSequencedCollection sequencedValues() { + if (cachedValues == null) { + cachedValues = new NavigableMapValues<>(this); + } + return (TSequencedCollection) cachedValues; + } + + @Override + public TSequencedSet> sequencedEntrySet() { + if (cachedEntrySet == null) { + cachedEntrySet = new EntrySet<>(this, null, true, false, null, true, false, false); + } + return cachedEntrySet; + } + @Override public TNavigableMap descendingMap() { return new MapView<>(this, null, false, false, null, false, false, true); @@ -542,7 +560,7 @@ public class TTreeMap extends TAbstractMap implements TCloneable, TS return copy; } - static class EntrySet extends TAbstractSet> { + static class EntrySet extends TAbstractSet> implements TSequencedSet> { private int modCount = -1; private TTreeMap owner; private K from; @@ -619,7 +637,7 @@ public class TTreeMap extends TAbstractMap implements TCloneable, TS } else { toPath = owner.pathToFirst(true); } - return new EntryIterator<>(owner, toPath, from, fromIncluded, fromChecked, true); + return new EntryIterator<>(owner, toPath, from, fromChecked, fromIncluded, true); } @Override @@ -651,6 +669,11 @@ public class TTreeMap extends TAbstractMap implements TCloneable, TS public boolean isEmpty() { return size() == 0; } + + @Override + public TSequencedSet> reversed() { + return new EntrySet<>(owner, from, fromIncluded, fromChecked, to, toIncluded, toChecked, !reverse); + } } static class EntryIterator implements TIterator> { @@ -772,11 +795,7 @@ public class TTreeMap extends TAbstractMap implements TCloneable, TS @Override public TSet> entrySet() { - if (entrySetCache == null) { - entrySetCache = new EntrySet<>(owner, from, fromIncluded, - fromChecked, to, toIncluded, toChecked, reverse); - } - return entrySetCache; + return sequencedEntrySet(); } @Override @@ -1021,7 +1040,7 @@ public class TTreeMap extends TAbstractMap implements TCloneable, TS public Entry pollFirstEntry() { TreeNode node = !reverse ? firstNode() : lastNode(); if (node != null) { - owner.deleteNode(owner.root, node.getKey()); + owner.remove(node.getKey()); } return node; } @@ -1030,11 +1049,33 @@ public class TTreeMap extends TAbstractMap implements TCloneable, TS public Entry pollLastEntry() { TreeNode node = !reverse ? lastNode() : firstNode(); if (node != null) { - owner.deleteNode(owner.root, node.getKey()); + owner.remove(node.getKey()); } return node; } + @Override + public TCollection values() { + return sequencedValues(); + } + + @Override + public TSequencedCollection sequencedValues() { + if (cachedValues == null) { + cachedValues = new NavigableMapValues<>(this); + } + return (TSequencedCollection) cachedValues; + } + + @Override + public TSequencedSet> sequencedEntrySet() { + if (entrySetCache == null) { + entrySetCache = new EntrySet<>(owner, from, fromIncluded, + fromChecked, to, toIncluded, toChecked, reverse); + } + return entrySetCache; + } + @Override public TNavigableMap descendingMap() { return new MapView<>(owner, from, fromIncluded, fromChecked, to, toIncluded, toChecked, !reverse); @@ -1086,13 +1127,13 @@ public class TTreeMap extends TAbstractMap implements TCloneable, TS if (!reverse) { return new MapView<>(owner, fromKey, inclusive, true, to, toIncluded, toChecked, false); } else { - return new MapView<>(owner, from, fromIncluded, toChecked, fromKey, inclusive, true, true); + return new MapView<>(owner, from, fromIncluded, fromChecked, fromKey, inclusive, true, true); } } } static class NavigableKeySet extends TAbstractSet implements TNavigableSet { - private TNavigableMap map; + private final TNavigableMap map; NavigableKeySet(TNavigableMap map) { this.map = map; @@ -1115,7 +1156,7 @@ public class TTreeMap extends TAbstractMap implements TCloneable, TS @Override public TSortedSet tailSet(K fromElement) { - return map.headMap(fromElement, true).navigableKeySet(); + return map.tailMap(fromElement, true).navigableKeySet(); } @Override @@ -1192,7 +1233,41 @@ public class TTreeMap extends TAbstractMap implements TCloneable, TS @Override public TNavigableSet tailSet(K fromElement, boolean inclusive) { - return map.headMap(fromElement, inclusive).navigableKeySet(); + return map.tailMap(fromElement, inclusive).navigableKeySet(); + } + } + + static class NavigableMapValues extends TAbstractCollection implements TSequencedCollection { + private final TNavigableMap map; + + NavigableMapValues(TNavigableMap map) { + this.map = map; + } + + @Override + public int size() { + return map.size(); + } + + @Override + public TIterator iterator() { + final TIterator> it = map.entrySet().iterator(); + return new TIterator<>() { + @Override public boolean hasNext() { + return it.hasNext(); + } + @Override public V next() { + return it.next().getValue(); + } + @Override public void remove() { + it.remove(); + } + }; + } + + @Override + public TSequencedCollection reversed() { + return new NavigableMapValues<>(map.reversed()); } } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/stream/TStreamSupport.java b/classlib/src/main/java/org/teavm/classlib/java/util/stream/TStreamSupport.java index 66828478d..8a33baef3 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/stream/TStreamSupport.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/stream/TStreamSupport.java @@ -17,6 +17,7 @@ package org.teavm.classlib.java.util.stream; import java.util.Spliterator; import java.util.function.Supplier; +import org.teavm.classlib.java.util.TSpliterator; import org.teavm.classlib.java.util.stream.impl.TStreamOverSpliterator; import org.teavm.classlib.java.util.stream.impl.TStreamOverSpliteratorSupplier; @@ -24,7 +25,7 @@ public final class TStreamSupport { private TStreamSupport() { } - public static TStream stream(Spliterator spliterator, boolean parallel) { + public static TStream stream(TSpliterator spliterator, boolean parallel) { return new TStreamOverSpliterator<>(spliterator); } diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/stream/impl/TStreamOverSpliterator.java b/classlib/src/main/java/org/teavm/classlib/java/util/stream/impl/TStreamOverSpliterator.java index 92006c5fa..407a749de 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/stream/impl/TStreamOverSpliterator.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/stream/impl/TStreamOverSpliterator.java @@ -18,11 +18,12 @@ package org.teavm.classlib.java.util.stream.impl; import java.util.Spliterator; import java.util.function.Consumer; import java.util.function.Predicate; +import org.teavm.classlib.java.util.TSpliterator; public class TStreamOverSpliterator extends TSimpleStreamImpl { - private Spliterator spliterator; + private TSpliterator spliterator; - public TStreamOverSpliterator(Spliterator spliterator) { + public TStreamOverSpliterator(TSpliterator spliterator) { this.spliterator = spliterator; } diff --git a/tests/src/test/java/org/teavm/classlib/java/util/TreeMapTest.java b/tests/src/test/java/org/teavm/classlib/java/util/TreeMapTest.java index 6b13f2ae8..06e05e08a 100644 --- a/tests/src/test/java/org/teavm/classlib/java/util/TreeMapTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/util/TreeMapTest.java @@ -47,6 +47,7 @@ */ package org.teavm.classlib.java.util; +import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotSame; @@ -55,15 +56,21 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.Serializable; +import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; import java.util.NavigableMap; +import java.util.NoSuchElementException; +import java.util.SequencedMap; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; +import java.util.function.Function; +import java.util.stream.Collectors; import org.junit.Test; import org.junit.runner.RunWith; import org.teavm.junit.TeaVMTestRunner; @@ -728,4 +735,124 @@ public class TreeMapTest { } } } + + private static final List BASE_LIST = Arrays.asList(1, 6, 2, 5, 3, 4); + + private SequencedMap generateMap() { + return BASE_LIST.stream().collect(Collectors.toMap(Function.identity(), i -> Integer.toString(i), + (a, b) -> a, TreeMap::new)); + } + + @Test + public void testSequencedMap() { + SequencedMap map = generateMap(); + assertEquals(Map.entry(1, "1"), map.pollFirstEntry()); + assertArrayEquals(new Integer[] { 2, 3, 4, 5, 6 }, map.keySet().toArray(new Integer[0])); + assertEquals(Map.entry(6, "6"), map.pollLastEntry()); + assertArrayEquals(new Integer[] { 2, 3, 4, 5 }, map.keySet().toArray(new Integer[0])); + assertEquals(Map.entry(5, "5"), map.pollLastEntry()); + assertArrayEquals(new Integer[] { 2, 3, 4 }, map.keySet().toArray(new Integer[0])); + assertEquals(Map.entry(2, "2"), map.firstEntry()); + assertEquals(Map.entry(4, "4"), map.lastEntry()); + try { + map.putFirst(1, "1"); + fail(); + } catch (UnsupportedOperationException e) { + // ok + } + map.put(7, "7"); + try { + map.putLast(3, "3"); + fail(); + } catch (UnsupportedOperationException e) { + // ok + } + assertArrayEquals(new Integer[] { 2, 3, 4, 7 }, map.keySet().toArray(new Integer[0])); + map = generateMap().reversed(); + assertEquals(Map.entry(6, "6"), map.pollFirstEntry()); + assertArrayEquals(new Integer[] { 5, 4, 3, 2, 1 }, map.keySet().toArray(new Integer[0])); + assertEquals(Map.entry(1, "1"), map.pollLastEntry()); + assertArrayEquals(new Integer[] { 5, 4, 3, 2 }, map.keySet().toArray(new Integer[0])); + assertEquals(Map.entry(2, "2"), map.pollLastEntry()); + assertArrayEquals(new Integer[] { 5, 4, 3 }, map.keySet().toArray(new Integer[0])); + assertEquals(Map.entry(5, "5"), map.firstEntry()); + assertEquals(Map.entry(3, "3"), map.lastEntry()); + try { + map.putFirst(1, "1"); + fail(); + } catch (UnsupportedOperationException e) { + // ok + } + map.put(7, "7"); + try { + map.putLast(6, "6"); + fail(); + } catch (UnsupportedOperationException e) { + // ok + } + assertArrayEquals(new Integer[] { 7, 5, 4, 3 }, map.keySet().toArray(new Integer[0])); + } + + @Test + public void testSequencedIterators() { + SequencedMap map = generateMap(); + Iterator it = map.keySet().iterator(); + assertTrue(it.hasNext()); + assertEquals(1, it.next().intValue()); + assertTrue(it.hasNext()); + assertEquals(2, it.next().intValue()); + it.remove(); + assertArrayEquals(new Integer[] { 1, 3, 4, 5, 6 }, map.keySet().toArray(new Integer[0])); + map = map.reversed(); + it = map.keySet().iterator(); + assertTrue(it.hasNext()); + assertEquals(6, it.next().intValue()); + assertTrue(it.hasNext()); + assertEquals(5, it.next().intValue()); + it.remove(); + assertArrayEquals(new Integer[] { 6, 4, 3, 1 }, map.keySet().toArray(new Integer[0])); + map = generateMap(); + Iterator sit = map.sequencedValues().iterator(); + assertTrue(sit.hasNext()); + assertEquals("1", sit.next()); + assertTrue(sit.hasNext()); + assertEquals("2", sit.next()); + sit.remove(); + assertArrayEquals(new String[] { "1", "3", "4", "5", "6" }, map.values().toArray(new String[0])); + map = map.reversed(); + sit = map.sequencedValues().iterator(); + assertTrue(sit.hasNext()); + assertEquals("6", sit.next()); + assertTrue(sit.hasNext()); + assertEquals("5", sit.next()); + sit.remove(); + assertArrayEquals(new String[] { "6", "4", "3", "1" }, map.values().toArray(new String[0])); + } + + @Test + public void testEmpty() { + var empty = new TreeMap<>(); + assertNull(empty.pollFirstEntry()); + assertNull(empty.pollLastEntry()); + assertNull(empty.firstEntry()); + assertNull(empty.lastEntry()); + try { + empty.entrySet().iterator().next(); + fail(); + } catch (NoSuchElementException e) { + // ok + } + try { + empty.keySet().iterator().next(); + fail(); + } catch (NoSuchElementException e) { + // ok + } + try { + empty.values().iterator().next(); + fail(); + } catch (NoSuchElementException e) { + // ok + } + } } diff --git a/tests/src/test/java/org/teavm/classlib/java/util/TreeSetTest.java b/tests/src/test/java/org/teavm/classlib/java/util/TreeSetTest.java new file mode 100644 index 000000000..365b8e1ae --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/java/util/TreeSetTest.java @@ -0,0 +1,201 @@ +/* + * Copyright 2023 ihromant. + * + * 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; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.SortedSet; +import java.util.TreeSet; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.teavm.junit.TeaVMTestRunner; + +@RunWith(TeaVMTestRunner.class) +public class TreeSetTest { + @Test + public void testSortedSetReadOnly() { + SortedSet set = new TreeSet<>(List.of("1", "2", "3", "4", "5", "6")); + SortedSet reversed = set.reversed(); + Iterator it = reversed.iterator(); + assertEquals("6", it.next()); + assertEquals("5", it.next()); + assertEquals("6", reversed.getFirst()); + assertEquals("1", reversed.getLast()); + SortedSet subset = reversed.subSet("3", "1"); + it = subset.iterator(); + assertTrue(it.hasNext()); + assertEquals("3", it.next()); + assertTrue(it.hasNext()); + assertEquals("2", it.next()); + try { + assertFalse(it.hasNext()); + it.next(); + fail(); + } catch (NoSuchElementException e) { + // ok + } + subset = reversed.headSet("4"); + it = subset.iterator(); + assertTrue(it.hasNext()); + assertEquals("6", it.next()); + assertTrue(it.hasNext()); + assertEquals("5", it.next()); + try { + assertFalse(it.hasNext()); + it.next(); + fail(); + } catch (NoSuchElementException e) { + // ok + } + subset = reversed.tailSet("2"); + it = subset.iterator(); + assertTrue(it.hasNext()); + assertEquals("2", it.next()); + assertTrue(it.hasNext()); + assertEquals("1", it.next()); + try { + assertFalse(it.hasNext()); + it.next(); + fail(); + } catch (NoSuchElementException e) { + // ok + } + } + + @Test + public void testSequencedCollectionIterator() { + SortedSet set = new TreeSet<>(List.of("a", "b", "c", "d")).reversed(); + Iterator it = set.iterator(); + assertEquals("d", it.next()); + assertEquals("c", it.next()); + it.remove(); + assertArrayEquals(new String[] { "d", "b", "a" }, set.toArray()); + set = new TreeSet<>(List.of("a", "b", "c", "d")).reversed(); + it = set.iterator(); + assertEquals("d", it.next()); + assertEquals("c", it.next()); + it.remove(); + assertEquals("b", it.next()); + it.remove(); + assertEquals("a", it.next()); + try { + it.next(); + fail(); + } catch (NoSuchElementException e) { + // ok + } + assertArrayEquals(new String[] { "d", "a" }, set.toArray()); + } + + @Test + public void sequenceCollectionMethodsOnEmpty() { + var empty = new TreeSet<>(); + + try { + empty.getFirst(); + fail(); + } catch (NoSuchElementException e) { + // ok + } + try { + empty.getLast(); + fail(); + } catch (NoSuchElementException e) { + // ok + } + + try { + empty.removeFirst(); + fail(); + } catch (NoSuchElementException e) { + // ok + } + try { + empty.removeLast(); + fail(); + } catch (NoSuchElementException e) { + // ok + } + try { + empty.addFirst("r"); + fail(); + } catch (UnsupportedOperationException e) { + // ok + } + try { + empty.addLast("r"); + fail(); + } catch (UnsupportedOperationException e) { + // ok + } + } + + @Test + public void testMutationsIterator() { + SortedSet set = new TreeSet<>(List.of("a", "b", "c", "d")); + set.add("e"); + set.add("f"); + Iterator it = set.iterator(); + assertEquals("a", it.next()); + it.remove(); + assertEquals("b", it.next()); + it.remove(); + assertEquals("c", it.next()); + it.remove(); + assertEquals("d", it.next()); + it.remove(); + assertEquals("e", it.next()); + it.remove(); + assertEquals("f", it.next()); + it.remove(); + assertTrue(set.isEmpty()); + try { + it.next(); + fail(); + } catch (NoSuchElementException e) { + // ok + } + set = new TreeSet<>(List.of("a", "b", "c", "d")); + set.add("e"); + set.add("f"); + it = set.reversed().iterator(); + assertEquals("f", it.next()); + it.remove(); + assertEquals("e", it.next()); + it.remove(); + assertEquals("d", it.next()); + it.remove(); + assertEquals("c", it.next()); + it.remove(); + assertEquals("b", it.next()); + it.remove(); + assertEquals("a", it.next()); + it.remove(); + assertTrue(set.isEmpty()); + try { + it.next(); + fail(); + } catch (NoSuchElementException e) { + // ok + } + } +}