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 18224e0c7..550702dad 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 @@ -53,19 +53,19 @@ public abstract class TAbstractMap extends TObject implements TMap { @Override public boolean equals(Object obj) { - if (!(obj instanceof TMap.Entry)) { - return false; + if (this == obj) { + return true; } - TMap.Entry other = (TMap.Entry) obj; - if (getKey() == null ? other.getKey() != null : !getKey().equals(other.getKey())) { - return false; + if (obj instanceof TMap.Entry) { + TMap.Entry entry = (TMap.Entry) obj; + return TObjects.equals(key, entry.getKey()) && TObjects.equals(value, entry.getValue()); } - return getValue() == null ? other.getValue() == null : getValue().equals(other.getValue()); + return false; } @Override public int hashCode() { - return (getKey() == null ? 0 : getKey().hashCode()) ^ (getValue() == null ? 0 : getValue().hashCode()); + return TObjects.hashCode(key) ^ TObjects.hashCode(value); } @Override @@ -104,19 +104,19 @@ public abstract class TAbstractMap extends TObject implements TMap { @Override public boolean equals(Object obj) { - if (!(obj instanceof TMap.Entry)) { - return false; + if (this == obj) { + return true; } - TMap.Entry other = (TMap.Entry) obj; - if (getKey() == null ? other.getKey() != null : !getKey().equals(other.getKey())) { - return false; + if (obj instanceof TMap.Entry) { + TMap.Entry entry = (TMap.Entry) obj; + return TObjects.equals(key, entry.getKey()) && TObjects.equals(value, entry.getValue()); } - return getValue() == null ? other.getValue() == null : getValue().equals(other.getValue()); + return false; } @Override public int hashCode() { - return (getKey() == null ? 0 : getKey().hashCode()) ^ (getValue() == null ? 0 : getValue().hashCode()); + return TObjects.hashCode(key) ^ TObjects.hashCode(value); } @Override @@ -254,7 +254,7 @@ public abstract class TAbstractMap extends TObject implements TMap { int result = 0; for (TIterator> iter = entrySet().iterator(); iter.hasNext();) { TMap.Entry entry = iter.next(); - result ^= entry.hashCode(); + result += entry.hashCode(); } return result; } @@ -292,16 +292,16 @@ public abstract class TAbstractMap extends TObject implements TMap { private class KeySet extends TAbstractSet { @Override public TIterator iterator() { - final TIterator> iter = TAbstractMap.this.entrySet().iterator(); - return new TIterator() { + final TIterator> it = TAbstractMap.this.entrySet().iterator(); + return new TIterator<>() { @Override public boolean hasNext() { - return iter.hasNext(); + return it.hasNext(); } @Override public K next() { - return iter.next().getKey(); + return it.next().getKey(); } @Override public void remove() { - iter.remove(); + it.remove(); } }; } diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TCollections.java b/classlib/src/main/java/org/teavm/classlib/java/util/TCollections.java index 637eb92a5..bc86b888c 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TCollections.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TCollections.java @@ -21,7 +21,7 @@ import org.teavm.classlib.java.util.TMap.Entry; public class TCollections extends TObject { @SuppressWarnings("rawtypes") - public static final TSet EMPTY_SET = new TTemplateCollections.AbstractImmutableSet() { + public static final TSet EMPTY_SET = new TTemplateCollections.AbstractImmutableSet<>() { @Override public int size() { return 0; } @@ -40,7 +40,7 @@ public class TCollections extends TObject { }; @SuppressWarnings("rawtypes") - public static final TMap EMPTY_MAP = new TTemplateCollections.AbstractImmutableMap() { + public static final TMap EMPTY_MAP = new TTemplateCollections.AbstractImmutableMap<>() { @Override public TSet> entrySet() { return emptySet(); } @@ -63,7 +63,7 @@ public class TCollections extends TObject { }; @SuppressWarnings("rawtypes") - public static final TList EMPTY_LIST = new TTemplateCollections.AbstractImmutableList() { + public static final TList EMPTY_LIST = new TTemplateCollections.AbstractImmutableList<>() { @Override public Object get(int index) { throw new TIndexOutOfBoundsException(); } @@ -86,7 +86,7 @@ public class TCollections extends TObject { } }; - private static final TIterator EMPTY_ITERATOR = new TIterator() { + private static final TIterator EMPTY_ITERATOR = new TIterator<>() { @Override public boolean hasNext() { return false; } @@ -98,7 +98,7 @@ public class TCollections extends TObject { } }; - private static final TListIterator EMPTY_LIST_ITERATOR = new TListIterator() { + private static final TListIterator EMPTY_LIST_ITERATOR = new TListIterator<>() { @Override public boolean hasNext() { return false; } @@ -144,7 +144,7 @@ public class TCollections extends TObject { } public static TEnumeration emptyEnumeration() { - return new TEnumeration() { + return new TEnumeration<>() { @Override public boolean hasMoreElements() { return false; } @@ -174,7 +174,7 @@ public class TCollections extends TObject { public static TMap singletonMap(final K key, final V value) { final TSet> entries = singleton(new TAbstractMap.SimpleImmutableEntry<>(key, value)); - return new TAbstractMap() { + return new TAbstractMap<>() { @Override public TSet> entrySet() { return entries; } @@ -182,7 +182,7 @@ public class TCollections extends TObject { } public static TList unmodifiableList(final TList list) { - return new TAbstractList() { + return new TAbstractList<>() { @Override public T get(int index) { return list.get(index); } @@ -193,7 +193,7 @@ public class TCollections extends TObject { } public static TList nCopies(final int n, final T o) { - return new TAbstractList() { + return new TAbstractList<>() { @Override public T get(int index) { if (index < 0 || index >= n) { throw new TIndexOutOfBoundsException(); @@ -461,7 +461,7 @@ public class TCollections extends TObject { } public static TCollection unmodifiableCollection(final TCollection c) { - return new TAbstractCollection() { + return new TAbstractCollection<>() { @Override public TIterator iterator() { return unmodifiableIterator(c.iterator()); } @@ -472,7 +472,7 @@ public class TCollections extends TObject { } private static TIterator unmodifiableIterator(final TIterator c) { - return new TIterator() { + return new TIterator<>() { @Override public boolean hasNext() { return c.hasNext(); } @@ -486,7 +486,7 @@ public class TCollections extends TObject { } public static TSet unmodifiableSet(final TSet s) { - return new TAbstractSet() { + return new TAbstractSet<>() { @Override public TIterator iterator() { return unmodifiableIterator(s.iterator()); } @@ -497,7 +497,7 @@ public class TCollections extends TObject { } public static TMap unmodifiableMap(final TMap m) { - return new TAbstractMap() { + return new TAbstractMap<>() { @Override public TSet> entrySet() { return unmodifiableMapEntrySet(m.entrySet()); } @@ -506,7 +506,7 @@ public class TCollections extends TObject { private static TSet> unmodifiableMapEntrySet( final TSet> c) { - return new TAbstractSet>() { + return new TAbstractSet<>() { @Override public int size() { return c.size(); } @@ -518,7 +518,7 @@ public class TCollections extends TObject { private static TIterator> unmodifiableMapEntryIterator( final TIterator> c) { - return new TIterator>() { + return new TIterator<>() { @Override public boolean hasNext() { return c.hasNext(); } @@ -552,15 +552,19 @@ public class TCollections extends TObject { return (TComparator) reverseOrder; } + @SuppressWarnings("unchecked") private static TComparator reverseOrder = (o1, o2) -> ((TComparable) o2).compareTo(o1); public static TComparator reverseOrder(final TComparator cmp) { + if (cmp == null) { + return reverseOrder(); + } return (o1, o2) -> -cmp.compare(o1, o2); } public static TEnumeration enumeration(TCollection c) { final TIterator iter = c.iterator(); - return new TEnumeration() { + return new TEnumeration<>() { @Override public boolean hasMoreElements() { return iter.hasNext(); } diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/THashMap.java b/classlib/src/main/java/org/teavm/classlib/java/util/THashMap.java index d053ed64b..93252b789 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/THashMap.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/THashMap.java @@ -13,27 +13,9 @@ * 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 java.util.Arrays; -import java.util.Objects; import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; @@ -61,7 +43,7 @@ public class THashMap extends TAbstractMap implements TCloneable, TS HashEntry(K theKey, V theValue) { super(theKey, theValue); - origKeyHash = Objects.hashCode(theKey); + origKeyHash = TObjects.hashCode(theKey); } @Override @@ -210,9 +192,9 @@ public class THashMap extends TAbstractMap implements TCloneable, TS public boolean remove(Object object) { if (object instanceof TMap.Entry) { TMap.Entry oEntry = (TMap.Entry) object; - TMap.Entry entry = associatedMap.getEntry(oEntry.getKey()); - if (valuesEq(entry, oEntry)) { - associatedMap.removeEntry(entry); + TMap.Entry entry = associatedMap.entryByKey(oEntry.getKey()); + if (entry != null && TObjects.equals(entry.getValue(), oEntry.getValue())) { + associatedMap.removeByKey(entry.getKey()); return true; } } @@ -223,19 +205,12 @@ public class THashMap extends TAbstractMap implements TCloneable, TS public boolean contains(Object object) { if (object instanceof TMap.Entry) { TMap.Entry oEntry = (TMap.Entry) object; - TMap.Entry entry = associatedMap.getEntry(oEntry.getKey()); - return valuesEq(entry, oEntry); + TMap.Entry entry = associatedMap.entryByKey(oEntry.getKey()); + return entry != null && TObjects.equals(entry.getValue(), oEntry.getValue()); } return false; } - private static boolean valuesEq(TMap.Entry entry, TMap.Entry oEntry) { - return entry != null - && (entry.getValue() == null - ? oEntry.getValue() == null - : areEqualValues(entry.getValue(), oEntry.getValue())); - } - @Override public TIterator> iterator() { return new EntryIterator<>(associatedMap); @@ -347,7 +322,7 @@ public class THashMap extends TAbstractMap implements TCloneable, TS @Override public boolean containsKey(Object key) { - HashEntry m = getEntry(key); + HashEntry m = entryByKey(key); return m != null; } @@ -357,7 +332,7 @@ public class THashMap extends TAbstractMap implements TCloneable, TS for (int i = 0; i < elementData.length; i++) { HashEntry entry = elementData[i]; while (entry != null) { - if (areEqualValues(value, entry.value)) { + if (value.equals(entry.value)) { return true; } entry = entry.next; @@ -384,14 +359,14 @@ public class THashMap extends TAbstractMap implements TCloneable, TS @Override public V get(Object key) { - HashEntry m = getEntry(key); + HashEntry m = entryByKey(key); if (m != null) { return m.value; } return null; } - final HashEntry getEntry(Object key) { + final HashEntry entryByKey(Object key) { HashEntry m; if (key == null) { m = findNullKeyEntry(); @@ -438,7 +413,7 @@ public class THashMap extends TAbstractMap implements TCloneable, TS THashMap.this.clear(); } @Override public boolean remove(Object key) { - HashEntry entry = THashMap.this.removeEntry(key); + HashEntry entry = THashMap.this.removeByKey(key); return entry != null; } @Override public TIterator iterator() { @@ -469,7 +444,7 @@ public class THashMap extends TAbstractMap implements TCloneable, TS return putImpl(key, value); } - V putImpl(K key, V value) { + private V putImpl(K key, V value) { HashEntry entry; if (key == null) { entry = findNullKeyEntry(); @@ -498,7 +473,7 @@ public class THashMap extends TAbstractMap implements TCloneable, TS return result; } - HashEntry createHashedEntry(K key, int index, int hash) { + private HashEntry createHashedEntry(K key, int index, int hash) { HashEntry entry = new HashEntry<>(key, hash); entry.next = elementData[index]; elementData[index] = entry; @@ -512,14 +487,13 @@ public class THashMap extends TAbstractMap implements TCloneable, TS } } - private void putAllImpl(TMap map) { + void putAllImpl(TMap map) { int capacity = elementCount + map.size(); if (capacity > threshold) { rehash(capacity); } - for (TIterator> iter = map.entrySet().iterator(); - iter.hasNext();) { - TMap.Entry entry = iter.next(); + for (var it = map.entrySet().iterator(); it.hasNext();) { + TMap.Entry entry = it.next(); putImpl(entry.getKey(), entry.getValue()); } } @@ -549,30 +523,14 @@ public class THashMap extends TAbstractMap implements TCloneable, TS @Override public V remove(Object key) { - HashEntry entry = removeEntry(key); + HashEntry entry = removeByKey(key); if (entry != null) { return entry.value; } return null; } - final void removeEntry(HashEntry entry) { - int index = entry.origKeyHash & (elementData.length - 1); - HashEntry m = elementData[index]; - if (m == entry) { - elementData[index] = entry.next; - } else { - while (m.next != entry) { - m = m.next; - } - m.next = entry.next; - - } - modCount++; - elementCount--; - } - - final HashEntry removeEntry(Object key) { + final HashEntry removeByKey(Object key) { int index = 0; HashEntry entry; HashEntry last = null; @@ -682,8 +640,4 @@ public class THashMap extends TAbstractMap implements TCloneable, TS static boolean areEqualKeys(Object key1, Object key2) { return (key1 == key2) || key1.equals(key2); } - - static boolean areEqualValues(Object value1, Object value2) { - return (value1 == value2) || value1.equals(value2); - } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TLinkedHashMap.java b/classlib/src/main/java/org/teavm/classlib/java/util/TLinkedHashMap.java index 13302bb8b..cbefe33fe 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TLinkedHashMap.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TLinkedHashMap.java @@ -13,33 +13,16 @@ * 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 java.util.function.BiConsumer; -import java.util.function.Consumer; -import org.teavm.classlib.java.lang.TIllegalStateException; +import java.util.function.BiFunction; -public class TLinkedHashMap extends THashMap implements TMap { - private final boolean accessOrder; +public class TLinkedHashMap extends THashMap implements TSequencedMap { + private boolean accessOrder; - transient private LinkedHashMapEntry head; - transient private LinkedHashMapEntry tail; + transient LinkedHashMapEntry head; + transient LinkedHashMapEntry tail; public TLinkedHashMap() { accessOrder = false; @@ -73,127 +56,15 @@ public class TLinkedHashMap extends THashMap implements TMap { putAll(m); } - private static class AbstractMapIterator { - int expectedModCount; - LinkedHashMapEntry futureEntry; - LinkedHashMapEntry currentEntry; - final TLinkedHashMap associatedMap; - - AbstractMapIterator(TLinkedHashMap map) { - expectedModCount = map.modCount; - futureEntry = map.head; - associatedMap = map; + @Override + void putAllImpl(TMap map) { + int capacity = elementCount + map.size(); + if (capacity > threshold) { + rehash(capacity); } - - public boolean hasNext() { - return futureEntry != null; - } - - final void checkConcurrentMod() throws TConcurrentModificationException { - if (expectedModCount != associatedMap.modCount) { - throw new TConcurrentModificationException(); - } - } - - final void makeNext() { - checkConcurrentMod(); - if (!hasNext()) { - throw new TNoSuchElementException(); - } - currentEntry = futureEntry; - futureEntry = futureEntry.chainForward; - } - - public void remove() { - checkConcurrentMod(); - if (currentEntry == null) { - throw new TIllegalStateException(); - } - associatedMap.removeEntry(currentEntry); - LinkedHashMapEntry lhme = currentEntry; - LinkedHashMapEntry p = lhme.chainBackward; - LinkedHashMapEntry n = lhme.chainForward; - TLinkedHashMap lhm = associatedMap; - if (p != null) { - p.chainForward = n; - if (n != null) { - n.chainBackward = p; - } else { - lhm.tail = p; - } - } else { - lhm.head = n; - if (n != null) { - n.chainBackward = null; - } else { - lhm.tail = null; - } - } - currentEntry = null; - expectedModCount++; - } - } - - private static class EntryIterator extends AbstractMapIterator implements TIterator> { - EntryIterator(TLinkedHashMap map) { - super(map); - } - - @Override - public Entry next() { - makeNext(); - return currentEntry; - } - } - - private static class KeyIterator extends AbstractMapIterator implements TIterator { - KeyIterator(TLinkedHashMap map) { - super(map); - } - - @Override - public K next() { - makeNext(); - return currentEntry.key; - } - } - - private static class ValueIterator extends AbstractMapIterator implements TIterator { - ValueIterator(TLinkedHashMap map) { - super(map); - } - - @Override - public V next() { - makeNext(); - return currentEntry.value; - } - } - - static final class LinkedHashMapEntrySet extends HashMapEntrySet { - public LinkedHashMapEntrySet(TLinkedHashMap lhm) { - super(lhm); - } - - @Override - public TIterator> iterator() { - return new EntryIterator<>((TLinkedHashMap) hashMap()); - } - - @Override - public void forEach(Consumer> action) { - TLinkedHashMap map = (TLinkedHashMap) hashMap(); - if (map.elementCount > 0) { - int prevModCount = map.modCount; - LinkedHashMapEntry entry = map.head; - do { - action.accept(entry); - entry = entry.chainForward; - if (map.modCount != prevModCount) { - throw new TConcurrentModificationException(); - } - } while (entry != null); - } + for (var it = map.entrySet().iterator(); it.hasNext();) { + TMap.Entry entry = it.next(); + putImpl(entry.getKey(), entry.getValue(), false); } } @@ -255,7 +126,7 @@ public class TLinkedHashMap extends THashMap implements TMap { } @Override - public V get(Object key) { + public V getOrDefault(Object key, V defaultValue) { LinkedHashMapEntry m; if (key == null) { m = (LinkedHashMapEntry) findNullKeyEntry(); @@ -265,7 +136,7 @@ public class TLinkedHashMap extends THashMap implements TMap { m = (LinkedHashMapEntry) findNonNullKeyEntry(key, index, hash); } if (m == null) { - return null; + return defaultValue; } if (accessOrder && tail != m) { LinkedHashMapEntry p = m.chainBackward; @@ -285,27 +156,24 @@ public class TLinkedHashMap extends THashMap implements TMap { } @Override - HashEntry createHashedEntry(K key, int index, int hash) { + public V get(Object key) { + return getOrDefault(key, null); + } + + private HashEntry createHashedEntry(K key, int index, int hash, boolean first) { LinkedHashMapEntry m = new LinkedHashMapEntry<>(key, hash); m.next = elementData[index]; elementData[index] = m; - linkEntry(m); + linkEntry(m, first); return m; } @Override public V put(K key, V value) { - V result = putImpl(key, value); - - if (removeEldestEntry(head)) { - remove(head.key); - } - - return result; + return putImpl(key, value, false); } - @Override - V putImpl(K key, V value) { + V putImpl(K key, V value, boolean first) { LinkedHashMapEntry m; if (elementCount == 0) { head = null; @@ -321,36 +189,37 @@ public class TLinkedHashMap extends THashMap implements TMap { if (++elementCount > threshold) { rehash(); } - m = (LinkedHashMapEntry) createHashedEntry(null, 0, 0); + m = (LinkedHashMapEntry) createHashedEntry(null, 0, 0, first); } else { - linkEntry(m); + linkEntry(m, first); } } else { int hash = key.hashCode(); - int index = (hash & 0x7FFFFFFF) % elementData.length; + int index = (hash & Integer.MAX_VALUE) % elementData.length; m = (LinkedHashMapEntry) findNonNullKeyEntry(key, index, hash); if (m == null) { modCount++; if (++elementCount > threshold) { rehash(); - index = (hash & 0x7FFFFFFF) % elementData.length; + index = (hash & Integer.MAX_VALUE) % elementData.length; } - m = (LinkedHashMapEntry) createHashedEntry(key, index, hash); + m = (LinkedHashMapEntry) createHashedEntry(key, index, hash, first); } else { - linkEntry(m); + linkEntry(m, first); } } V result = m.value; m.value = value; + + if (removeEldestEntry(head)) { + remove(head.key); + } + return result; } - void linkEntry(LinkedHashMapEntry m) { - if (tail == m) { - return; - } - + void linkEntry(LinkedHashMapEntry m, boolean first) { if (head == null) { // Check if the map is empty head = m; @@ -364,8 +233,8 @@ public class TLinkedHashMap extends THashMap implements TMap { LinkedHashMapEntry n = m.chainForward; if (p == null) { if (n != null) { - // The entry must be the head but not the tail - if (accessOrder) { + // Existing entry must be the head but not the tail + if (!first && accessOrder) { head = n; n.chainBackward = null; m.chainBackward = tail; @@ -375,133 +244,87 @@ public class TLinkedHashMap extends THashMap implements TMap { } } else { // This is a new entry - m.chainBackward = tail; - m.chainForward = null; - tail.chainForward = m; - tail = m; + m.chainBackward = first ? null : tail; + m.chainForward = first ? head : null; + if (first) { + head.chainBackward = m; + head = m; + } else { + tail.chainForward = m; + tail = m; + } + } + } else { + if (n == null) { + // Existing entry must be the tail but not the head + if (first && accessOrder) { + tail = p; + p.chainForward = null; + m.chainBackward = null; + m.chainForward = head; + head.chainBackward = m; + head = m; + } + } else { + if (elementCount > 1 && accessOrder) { + // Existing entry is neither the head nor tail + p.chainForward = n; + n.chainBackward = p; + if (first) { + m.chainForward = head; + m.chainBackward = null; + head.chainBackward = m; + head = m; + } else { + m.chainForward = null; + m.chainBackward = tail; + tail.chainForward = m; + tail = m; + } + } } - return; - } - - if (n == null) { - // The entry must be the tail so we can't get here - return; - } - - // The entry is neither the head nor tail - if (accessOrder) { - p.chainForward = n; - n.chainBackward = p; - m.chainForward = null; - m.chainBackward = tail; - tail.chainForward = m; - tail = m; } } @Override public TSet> entrySet() { - return new LinkedHashMapEntrySet<>(this); + return new TLinkedHashMapEntrySet<>(this, false); } @Override public TSet keySet() { + return sequencedKeySet(); + } + + @Override + public TSequencedSet sequencedKeySet() { if (cachedKeySet == null) { - cachedKeySet = new TAbstractSet() { - @Override - public boolean contains(Object object) { - return containsKey(object); - } - - @Override - public int size() { - return TLinkedHashMap.this.size(); - } - - @Override - public void clear() { - TLinkedHashMap.this.clear(); - } - - @Override - public boolean remove(Object key) { - if (containsKey(key)) { - TLinkedHashMap.this.remove(key); - return true; - } - return false; - } - - @Override - public TIterator iterator() { - return new KeyIterator<>(TLinkedHashMap.this); - } - - @Override - public void forEach(Consumer action) { - if (elementCount > 0) { - int prevModCount = modCount; - LinkedHashMapEntry entry = head; - do { - action.accept(entry.key); - entry = entry.chainForward; - if (modCount != prevModCount) { - throw new TConcurrentModificationException(); - } - } while (entry != null); - } - } - }; + cachedKeySet = new TLinkedHashMapKeySet<>(this, false); } - return cachedKeySet; + return (TSequencedSet) cachedKeySet; } @Override public TCollection values() { + return sequencedValues(); + } + + @Override + public TSequencedCollection sequencedValues() { if (cachedValues == null) { - cachedValues = new TAbstractCollection() { - @Override - public boolean contains(Object object) { - return containsValue(object); - } - - @Override - public int size() { - return TLinkedHashMap.this.size(); - } - - @Override - public void clear() { - TLinkedHashMap.this.clear(); - } - - @Override - public TIterator iterator() { - return new ValueIterator<>(TLinkedHashMap.this); - } - - @Override - public void forEach(Consumer action) { - if (elementCount > 0) { - int prevModCount = modCount; - LinkedHashMapEntry entry = head; - do { - action.accept(entry.value); - entry = entry.chainForward; - if (modCount != prevModCount) { - throw new TConcurrentModificationException(); - } - } while (entry != null); - } - } - }; + cachedValues = new TLinkedHashMapValues<>(this, false); } - return cachedValues; + return (TSequencedCollection) cachedValues; + } + + @Override + public TSequencedSet> sequencedEntrySet() { + return new TLinkedHashMapEntrySet<>(this, false); } @Override public V remove(Object key) { - LinkedHashMapEntry m = (LinkedHashMapEntry) removeEntry(key); + LinkedHashMapEntry m = (LinkedHashMapEntry) removeByKey(key); if (m == null) { return null; } @@ -509,13 +332,18 @@ public class TLinkedHashMap extends THashMap implements TMap { LinkedHashMapEntry n = m.chainForward; if (p != null) { p.chainForward = n; + if (n != null) { + n.chainBackward = p; + } else { + tail = p; + } } else { head = n; - } - if (n != null) { - n.chainBackward = p; - } else { - tail = p; + if (n != null) { + n.chainBackward = null; + } else { + tail = null; + } } return m.value; } @@ -545,4 +373,41 @@ public class TLinkedHashMap extends THashMap implements TMap { head = null; tail = null; } + + @Override + public void replaceAll(BiFunction function) { + if (elementCount > 0) { + int prevModCount = modCount; + LinkedHashMapEntry entry = head; + do { + entry.value = function.apply(entry.key, entry.value); + entry = entry.chainForward; + if (modCount != prevModCount) { + throw new TConcurrentModificationException(); + } + } while (entry != null); + } + } + + @Override + public V putFirst(K k, V v) { + return putImpl(k, v, true); + } + + @Override + public V putLast(K k, V v) { + return putImpl(k, v, false); + } + + @Override + public TSequencedMap reversed() { + return new TReversedLinkedHashMap<>(this); + } + + static T checkNotNull(T node) { + if (node == null) { + throw new TNoSuchElementException(); + } + return node; + } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TLinkedHashMapEntrySet.java b/classlib/src/main/java/org/teavm/classlib/java/util/TLinkedHashMapEntrySet.java new file mode 100644 index 000000000..50725779f --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TLinkedHashMapEntrySet.java @@ -0,0 +1,121 @@ +/* + * 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 java.util.function.Consumer; + +class TLinkedHashMapEntrySet extends TAbstractSet> + implements TSequencedSet> { + private final TLinkedHashMap base; + private final boolean reversed; + + TLinkedHashMapEntrySet(TLinkedHashMap base, boolean reversed) { + this.base = base; + this.reversed = reversed; + } + + @Override + public final int size() { + return base.elementCount; + } + + @Override + public final void clear() { + base.clear(); + } + + @Override + public final TIterator> iterator() { + return new TLinkedHashMapIterator.EntryIterator<>(base, reversed); + } + + @Override + public final boolean contains(Object o) { + if (o instanceof TMap.Entry) { + TMap.Entry oEntry = (TMap.Entry) o; + TMap.Entry entry = base.entryByKey(oEntry.getKey()); + return entry != null && TObjects.equals(entry.getValue(), oEntry.getValue()); + } + return false; + } + + @Override + public boolean remove(Object object) { + if (object instanceof TMap.Entry) { + TMap.Entry oEntry = (TMap.Entry) object; + TMap.Entry entry = base.entryByKey(oEntry.getKey()); + if (entry != null && TObjects.equals(entry.getValue(), oEntry.getValue())) { + base.remove(entry.getKey()); + return true; + } + } + return false; + } + + @Override + public final void forEach(Consumer> action) { + if (base.elementCount > 0) { + int prevModCount = base.modCount; + TLinkedHashMap.LinkedHashMapEntry entry = reversed ? base.tail : base.head; + do { + action.accept(entry); + entry = reversed ? entry.chainBackward : entry.chainForward; + if (base.modCount != prevModCount) { + throw new TConcurrentModificationException(); + } + } while (entry != null); + } + } + + @Override + public final void addFirst(TMap.Entry e) { + throw new UnsupportedOperationException(); + } + + @Override + public final void addLast(TMap.Entry e) { + throw new UnsupportedOperationException(); + } + + @Override + public final TMap.Entry getFirst() { + return TLinkedHashMap.checkNotNull(reversed ? base.tail : base.head); + } + + @Override + public final TMap.Entry getLast() { + return TLinkedHashMap.checkNotNull(reversed ? base.head : base.tail); + } + + @Override + public final TMap.Entry removeFirst() { + var e = TLinkedHashMap.checkNotNull(reversed ? base.tail : base.head); + base.remove(e.key); + return e; + } + + @Override + public final TMap.Entry removeLast() { + var e = TLinkedHashMap.checkNotNull(reversed ? base.head : base.tail); + base.remove(e.key); + return e; + } + + @Override + public TSequencedSet> reversed() { + return new TLinkedHashMapEntrySet<>(base, !reversed); + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TLinkedHashMapIterator.java b/classlib/src/main/java/org/teavm/classlib/java/util/TLinkedHashMapIterator.java new file mode 100644 index 000000000..62de6626f --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TLinkedHashMapIterator.java @@ -0,0 +1,96 @@ +/* + * 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 TLinkedHashMapIterator { + private final TLinkedHashMap base; + private final boolean reversed; + private int expectedModCount; + private TLinkedHashMap.LinkedHashMapEntry futureEntry; + TLinkedHashMap.LinkedHashMapEntry currentEntry; + + TLinkedHashMapIterator(TLinkedHashMap base, boolean reversed) { + this.base = base; + this.reversed = reversed; + expectedModCount = base.modCount; + futureEntry = reversed ? base.tail : base.head; + } + + public boolean hasNext() { + return futureEntry != null; + } + + final void checkConcurrentMod() throws TConcurrentModificationException { + if (expectedModCount != base.modCount) { + throw new TConcurrentModificationException(); + } + } + + final void makeNext() { + checkConcurrentMod(); + if (!hasNext()) { + throw new TNoSuchElementException(); + } + currentEntry = futureEntry; + futureEntry = reversed ? futureEntry.chainBackward : futureEntry.chainForward; + } + + public void remove() { + if (currentEntry == null) { + throw new IllegalStateException(); + } + checkConcurrentMod(); + base.remove(currentEntry.key); + currentEntry = null; + expectedModCount++; + } + + static class EntryIterator extends TLinkedHashMapIterator implements TIterator> { + EntryIterator(TLinkedHashMap map, boolean reversed) { + super(map, reversed); + } + + @Override + public TMap.Entry next() { + makeNext(); + return currentEntry; + } + } + + static class KeyIterator extends TLinkedHashMapIterator implements TIterator { + KeyIterator(TLinkedHashMap map, boolean reversed) { + super(map, reversed); + } + + @Override + public K next() { + makeNext(); + return currentEntry.key; + } + } + + static class ValueIterator extends TLinkedHashMapIterator implements TIterator { + ValueIterator(TLinkedHashMap map, boolean reversed) { + super(map, reversed); + } + + @Override + public V next() { + makeNext(); + return currentEntry.value; + } + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TLinkedHashMapKeySet.java b/classlib/src/main/java/org/teavm/classlib/java/util/TLinkedHashMapKeySet.java new file mode 100644 index 000000000..672ac4384 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TLinkedHashMapKeySet.java @@ -0,0 +1,103 @@ +/* + * 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 java.util.function.Consumer; + +class TLinkedHashMapKeySet extends TAbstractSet implements TSequencedSet { + private final TLinkedHashMap base; + private final boolean reversed; + + TLinkedHashMapKeySet(TLinkedHashMap base, boolean reversed) { + this.base = base; + this.reversed = reversed; + } + + @Override + public final int size() { + return base.elementCount; + } + + @Override + public final void clear() { + base.clear(); + } + + @Override + public final TIterator iterator() { + return new TLinkedHashMapIterator.KeyIterator<>(base, reversed); + } + + @Override + public final boolean contains(Object o) { + return base.containsKey(o); + } + + @Override + public final boolean remove(Object key) { + int befCount = base.elementCount; + base.remove(key); + return base.elementCount != befCount; + } + + @Override + public final void forEach(Consumer action) { + if (base.elementCount > 0) { + int prevModCount = base.modCount; + TLinkedHashMap.LinkedHashMapEntry entry = reversed ? base.tail : base.head; + do { + action.accept(entry.key); + entry = reversed ? entry.chainBackward : entry.chainForward; + if (base.modCount != prevModCount) { + throw new TConcurrentModificationException(); + } + } while (entry != null); + } + } + + @Override + public final K getFirst() { + return TLinkedHashMap.checkNotNull(reversed ? base.tail : base.head).key; + } + + @Override + public final K getLast() { + return TLinkedHashMap.checkNotNull(reversed ? base.head : base.tail).key; + } + + @Override + public final K removeFirst() { + var e = TLinkedHashMap.checkNotNull(reversed ? base.tail : base.head); + base.remove(e.key); + return e.key; + } + + @Override + public final K removeLast() { + var e = TLinkedHashMap.checkNotNull(reversed ? base.head : base.tail); + base.remove(e.key); + return e.key; + } + + @Override + public TSequencedSet reversed() { + if (reversed) { + return base.sequencedKeySet(); + } else { + return new TLinkedHashMapKeySet<>(base, true); + } + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TLinkedHashMapValues.java b/classlib/src/main/java/org/teavm/classlib/java/util/TLinkedHashMapValues.java new file mode 100644 index 000000000..0f02159d9 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TLinkedHashMapValues.java @@ -0,0 +1,94 @@ +/* + * 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 java.util.function.Consumer; + +class TLinkedHashMapValues extends TAbstractCollection implements TSequencedCollection { + private final TLinkedHashMap base; + private final boolean reversed; + + TLinkedHashMapValues(TLinkedHashMap base, boolean reversed) { + this.base = base; + this.reversed = reversed; + } + + @Override + public final int size() { + return base.size(); + } + + @Override + public final void clear() { + base.clear(); + } + + @Override + public final TIterator iterator() { + return new TLinkedHashMapIterator.ValueIterator<>(base, reversed); + } + + @Override + public final boolean contains(Object o) { + return base.containsValue(o); + } + + @Override + public final void forEach(Consumer action) { + if (base.elementCount > 0) { + int prevModCount = base.modCount; + TLinkedHashMap.LinkedHashMapEntry entry = reversed ? base.tail : base.head; + do { + action.accept(entry.value); + entry = reversed ? entry.chainBackward : entry.chainForward; + if (base.modCount != prevModCount) { + throw new TConcurrentModificationException(); + } + } while (entry != null); + } + } + + @Override + public final V getFirst() { + return TLinkedHashMap.checkNotNull(reversed ? base.tail : base.head).value; + } + + @Override + public final V getLast() { + return TLinkedHashMap.checkNotNull(reversed ? base.head : base.tail).value; + } + + @Override + public final V removeFirst() { + THashMap.HashEntry e = TLinkedHashMap.checkNotNull(reversed ? base.tail : base.head); + return base.remove(e.key); + } + + @Override + public final V removeLast() { + THashMap.HashEntry e = TLinkedHashMap.checkNotNull(reversed ? base.head : base.tail); + return base.remove(e.key); + } + + @Override + public TSequencedCollection reversed() { + if (reversed) { + return base.sequencedValues(); + } else { + return new TLinkedHashMapValues<>(base, true); + } + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TLinkedHashSet.java b/classlib/src/main/java/org/teavm/classlib/java/util/TLinkedHashSet.java index d65f30e42..5de785b94 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TLinkedHashSet.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TLinkedHashSet.java @@ -13,42 +13,26 @@ * 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.TCloneable; -public class TLinkedHashSet extends THashSet implements TSet, TCloneable, TSerializable { +public class TLinkedHashSet extends THashSet implements TSequencedSet, TCloneable, TSerializable { public TLinkedHashSet() { - super(new TLinkedHashMap>()); + super(new TLinkedHashMap<>()); } public TLinkedHashSet(int capacity) { - super(new TLinkedHashMap>(capacity)); + super(new TLinkedHashMap<>(capacity)); } public TLinkedHashSet(int capacity, float loadFactor) { - super(new TLinkedHashMap>(capacity, loadFactor)); + super(new TLinkedHashMap<>(capacity, loadFactor)); } public TLinkedHashSet(TCollection collection) { - super(new TLinkedHashMap>(collection.size() < 6 ? 11 : collection.size() * 2)); + super(new TLinkedHashMap<>(collection.size() < 6 ? 11 : collection.size() * 2)); for (TIterator iter = collection.iterator(); iter.hasNext();) { add(iter.next()); } @@ -59,4 +43,101 @@ public class TLinkedHashSet extends THashSet implements TSet, TCloneabl THashMap> createBackingMap(int capacity, float loadFactor) { return new TLinkedHashMap<>(capacity, loadFactor); } + + private TLinkedHashMap> map() { + return (TLinkedHashMap>) backingMap; + } + + @Override + public void addFirst(E e) { + map().putFirst(e, this); + } + + @Override + public void addLast(E e) { + map().putLast(e, this); + } + + @Override + public E getFirst() { + return map().sequencedKeySet().getFirst(); + } + + @Override + public E getLast() { + return map().sequencedKeySet().getLast(); + } + + @Override + public E removeFirst() { + return map().sequencedKeySet().removeFirst(); + } + + @Override + public E removeLast() { + return map().sequencedKeySet().removeLast(); + } + + @Override + public TSequencedSet reversed() { + return new ReversedLinkedHashSet<>(this); + } + + static class ReversedLinkedHashSet extends TAbstractSet implements TSequencedSet { + private final TLinkedHashSet base; + + ReversedLinkedHashSet(TLinkedHashSet base) { + this.base = base; + } + + @Override + public int size() { + return base.size(); + } + + @Override + public TIterator iterator() { + return base.map().sequencedKeySet().reversed().iterator(); + } + + @Override + public boolean add(E e) { + return base.add(e); + } + + @Override + public void addFirst(E e) { + base.addLast(e); + } + + @Override + public void addLast(E e) { + base.addFirst(e); + } + + @Override + public E getFirst() { + return base.getLast(); + } + + @Override + public E getLast() { + return base.getFirst(); + } + + @Override + public E removeFirst() { + return base.removeLast(); + } + + @Override + public E removeLast() { + return base.removeFirst(); + } + + @Override + public TSequencedSet reversed() { + return base; + } + } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TMapEntry.java b/classlib/src/main/java/org/teavm/classlib/java/util/TMapEntry.java index 2e2c03dd4..c8522017c 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TMapEntry.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TMapEntry.java @@ -68,9 +68,7 @@ class TMapEntry implements TMap.Entry, Cloneable { } if (object instanceof TMap.Entry) { TMap.Entry entry = (TMap.Entry) object; - return (key == null ? entry.getKey() == null : key.equals(entry.getKey())) - && (value == null ? entry.getValue() == null : value - .equals(entry.getValue())); + return TObjects.equals(key, entry.getKey()) && TObjects.equals(value, entry.getValue()); } return false; } @@ -87,8 +85,7 @@ class TMapEntry implements TMap.Entry, Cloneable { @Override public int hashCode() { - return (key == null ? 0 : key.hashCode()) - ^ (value == null ? 0 : value.hashCode()); + return TObjects.hashCode(key) ^ TObjects.hashCode(value); } @Override diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TReversedLinkedHashMap.java b/classlib/src/main/java/org/teavm/classlib/java/util/TReversedLinkedHashMap.java new file mode 100644 index 000000000..116002b17 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TReversedLinkedHashMap.java @@ -0,0 +1,223 @@ +/* + * 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 java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; + +class TReversedLinkedHashMap extends TAbstractMap implements TSequencedMap { + private final TLinkedHashMap base; + + TReversedLinkedHashMap(TLinkedHashMap base) { + this.base = base; + } + + @Override + public boolean equals(Object o) { + return base.equals(o); + } + + @Override + public int hashCode() { + return base.hashCode(); + } + + @Override + public int size() { + return base.size(); + } + + @Override + public boolean isEmpty() { + return base.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return base.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return base.containsValue(value); + } + + @Override + public V get(Object key) { + return base.get(key); + } + + @Override + public V put(K key, V value) { + return base.put(key, value); + } + + @Override + public V remove(Object key) { + return base.remove(key); + } + + @Override + public void putAll(TMap m) { + base.putAll(m); + } + + @Override + public void clear() { + base.clear(); + } + + @Override + public TSet keySet() { + return base.sequencedKeySet().reversed(); + } + + @Override + public TCollection values() { + return base.sequencedValues().reversed(); + } + + @Override + public TSet> entrySet() { + return base.sequencedEntrySet().reversed(); + } + + @Override + public V getOrDefault(Object key, V defaultValue) { + return base.getOrDefault(key, defaultValue); + } + + @Override + public void forEach(BiConsumer action) { + if (base.elementCount > 0) { + int prevModCount = base.modCount; + TLinkedHashMap.LinkedHashMapEntry entry = base.tail; + do { + action.accept(entry.key, entry.value); + entry = entry.chainBackward; + if (base.modCount != prevModCount) { + throw new TConcurrentModificationException(); + } + } while (entry != null); + } + } + + @Override + public void replaceAll(BiFunction function) { + if (base.elementCount > 0) { + int prevModCount = base.modCount; + TLinkedHashMap.LinkedHashMapEntry entry = base.tail; + do { + entry.value = function.apply(entry.key, entry.value); + entry = entry.chainBackward; + if (base.modCount != prevModCount) { + throw new TConcurrentModificationException(); + } + } while (entry != null); + } + } + + @Override + public V putIfAbsent(K key, V value) { + return base.putIfAbsent(key, value); + } + + @Override + public boolean remove(Object key, Object value) { + return base.remove(key, value); + } + + @Override + public boolean replace(K key, V oldValue, V newValue) { + return base.replace(key, oldValue, newValue); + } + + @Override + public V replace(K key, V value) { + return base.replace(key, value); + } + + @Override + public V computeIfAbsent(K key, Function mappingFunction) { + return base.computeIfAbsent(key, mappingFunction); + } + + @Override + public V computeIfPresent(K key, BiFunction remappingFunction) { + return base.computeIfPresent(key, remappingFunction); + } + + @Override + public V compute(K key, BiFunction remappingFunction) { + return base.compute(key, remappingFunction); + } + + @Override + public V merge(K key, V value, BiFunction remappingFunction) { + return base.merge(key, value, remappingFunction); + } + + @Override + public TSequencedMap reversed() { + return base; + } + + @Override + public TMap.Entry firstEntry() { + return base.lastEntry(); + } + + @Override + public TMap.Entry lastEntry() { + return base.firstEntry(); + } + + @Override + public TMap.Entry pollFirstEntry() { + return base.pollLastEntry(); + } + + @Override + public TMap.Entry pollLastEntry() { + return base.pollFirstEntry(); + } + + @Override + public V putFirst(K k, V v) { + return base.putLast(k, v); + } + + @Override + public V putLast(K k, V v) { + return base.putFirst(k, v); + } + + @Override + public TSequencedSet sequencedKeySet() { + return new TLinkedHashMapKeySet<>(base, true); + } + + @Override + public TSequencedCollection sequencedValues() { + return new TLinkedHashMapValues<>(base, true); + } + + @Override + public TSequencedSet> sequencedEntrySet() { + return new TLinkedHashMapEntrySet<>(base, true); + } +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TSequencedMap.java b/classlib/src/main/java/org/teavm/classlib/java/util/TSequencedMap.java new file mode 100644 index 000000000..10cc80a3e --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TSequencedMap.java @@ -0,0 +1,70 @@ +/* + * 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; + +public interface TSequencedMap extends TMap { + TSequencedMap reversed(); + + private static TMap.Entry clone(TMap.Entry entry) { + return TMap.entry(entry.getKey(), entry.getValue()); + } + + default TMap.Entry firstEntry() { + var it = entrySet().iterator(); + return it.hasNext() ? clone(it.next()) : null; + } + + default TMap.Entry lastEntry() { + var it = reversed().entrySet().iterator(); + return it.hasNext() ? clone(it.next()) : null; + } + + default TMap.Entry pollFirstEntry() { + var it = entrySet().iterator(); + if (it.hasNext()) { + var entry = clone(it.next()); + it.remove(); + return entry; + } else { + return null; + } + } + + default TMap.Entry pollLastEntry() { + var it = reversed().entrySet().iterator(); + if (it.hasNext()) { + var entry = clone(it.next()); + it.remove(); + return entry; + } else { + return null; + } + } + + default V putFirst(K k, V v) { + throw new UnsupportedOperationException(); + } + + default V putLast(K k, V v) { + throw new UnsupportedOperationException(); + } + + TSequencedSet sequencedKeySet(); + + TSequencedCollection sequencedValues(); + + TSequencedSet> sequencedEntrySet(); +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TSequencedSet.java b/classlib/src/main/java/org/teavm/classlib/java/util/TSequencedSet.java new file mode 100644 index 000000000..13ecbd023 --- /dev/null +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TSequencedSet.java @@ -0,0 +1,21 @@ +/* + * 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; + +public interface TSequencedSet extends TSequencedCollection, TSet { + @Override + TSequencedSet reversed(); +} diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/TTemplateCollections.java b/classlib/src/main/java/org/teavm/classlib/java/util/TTemplateCollections.java index f04a8d32f..4b5bcc692 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/TTemplateCollections.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/TTemplateCollections.java @@ -799,9 +799,7 @@ public final class TTemplateCollections { } if (object instanceof TMap.Entry) { TMap.Entry entry = (TMap.Entry) object; - return (key == null ? entry.getKey() == null : key.equals(entry.getKey())) - && (value == null ? entry.getValue() == null : value - .equals(entry.getValue())); + return TObjects.equals(key, entry.getKey()) && TObjects.equals(value, entry.getValue()); } return false; } @@ -818,8 +816,7 @@ public final class TTemplateCollections { @Override public int hashCode() { - return (key == null ? 0 : key.hashCode()) - ^ (value == null ? 0 : value.hashCode()); + return TObjects.hashCode(key) ^ TObjects.hashCode(value); } @Override diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/TMapEntry.java b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/TMapEntry.java index b25659e23..3a7907064 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/TMapEntry.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/concurrent/TMapEntry.java @@ -70,7 +70,7 @@ class TMapEntry implements TMap.Entry, Cloneable { @Override public int hashCode() { - return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode()); + return Objects.hashCode(key) ^ Objects.hashCode(value); } @Override diff --git a/classlib/src/main/java/org/teavm/classlib/java/util/function/TPredicate.java b/classlib/src/main/java/org/teavm/classlib/java/util/function/TPredicate.java index fd555c31f..00cd1c337 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/util/function/TPredicate.java +++ b/classlib/src/main/java/org/teavm/classlib/java/util/function/TPredicate.java @@ -37,6 +37,7 @@ public interface TPredicate { return t -> TObjects.equals(t, targetRef); } + @SuppressWarnings("unchecked") static TPredicate not(TPredicate target) { return (TPredicate) target.negate(); } diff --git a/tests/src/test/java/org/teavm/classlib/java/util/HashMapTest.java b/tests/src/test/java/org/teavm/classlib/java/util/HashMapTest.java new file mode 100644 index 000000000..401bf6d05 --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/java/util/HashMapTest.java @@ -0,0 +1,984 @@ +/* + * 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.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +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.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.ConcurrentModificationException; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.TreeMap; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.teavm.classlib.support.MapTest2Support; +import org.teavm.classlib.support.UnmodifiableCollectionTestSupport; +import org.teavm.junit.TeaVMTestRunner; +import org.teavm.junit.WholeClassCompilation; + +@RunWith(TeaVMTestRunner.class) +@WholeClassCompilation +public class HashMapTest { + private HashMap ht10; + + private HashMap ht100; + + private HashMap htfull; + + private List keyList; + + private List elmList; + + public HashMapTest() { + ht10 = new HashMap<>(10); + ht100 = new HashMap<>(100); + htfull = new HashMap<>(10); + keyList = new ArrayList<>(10); + elmList = new ArrayList<>(10); + + for (int i = 0; i < 10; i++) { + ht10.put("Key " + i, "Val " + i); + keyList.add("Key " + i); + elmList.add("Val " + i); + } + + for (int i = 0; i < 7; i++) { + htfull.put("FKey " + i, "FVal " + i); + } + } + + @Test + public void test_Constructor() { + // Test for method java.util.HashMap() + new MapTest2Support(new HashMap<>()).runTest(); + + HashMap h = new HashMap<>(); + + assertEquals("Created incorrect HashMap", 0, h.size()); + } + + @Test + public void test_ConstructorI() { + // Test for method java.util.HashMap(int) + HashMap h = new HashMap<>(9); + + assertEquals("Created incorrect HashMap", 0, h.size()); + + HashMap empty = new HashMap<>(0); + assertNull("Empty HashMap access", empty.get("nothing")); + empty.put("something", "here"); + assertEquals("cannot get element", "here", empty.get("something")); + } + + @Test + public void test_ConstructorIF() { + // Test for method java.util.HashMap(int, float) + HashMap h = new HashMap<>(10, 0.5f); + assertEquals("Created incorrect HashMap", 0, h.size()); + + HashMap empty = new HashMap<>(0, 0.75f); + assertNull("Empty HashMap access", empty.get("nothing")); + empty.put("something", "here"); + assertEquals("cannot get element", "here", empty.get("something")); + } + + @Test + public void test_ConstructorLjava_util_Map() { + // Test for method java.util.HashMap(java.util.Map) + Map map = new TreeMap<>(); + Object firstVal = "Gabba"; + Object secondVal = 5; + map.put("Gah", firstVal); + map.put("Ooga", secondVal); + HashMap ht = new HashMap<>(map); + assertSame("a) Incorrect HashMap constructed", firstVal, ht.get("Gah")); + assertSame("b) Incorrect HashMap constructed", secondVal, ht.get("Ooga")); + } + + public void test_HashMap_Constructor() { + HashMap, Set>> hashMap = new HashMap<>(); + hashMap.put(hashMap, hashMap.keySet()); + new HashMap<>(hashMap); + } + + @Test + public void test_clear() { + // Test for method void java.util.HashMap.clear() + HashMap h = hashMapClone(htfull); + h.clear(); + assertEquals("HashMap was not cleared", 0, h.size()); + Iterator el = h.values().iterator(); + Iterator keys = h.keySet().iterator(); + assertTrue("HashMap improperly cleared", !el.hasNext() && !keys.hasNext()); + } + + @Test + public void test_clone() { + // Test for method java.lang.Object java.util.HashMap.clone() + + @SuppressWarnings("unchecked") + HashMap h = (HashMap) htfull.clone(); + assertEquals("Clone different size than original", h.size(), htfull.size()); + + Iterator org = htfull.keySet().iterator(); + Iterator cpy = h.keySet().iterator(); + + String okey; + String ckey; + while (org.hasNext()) { + okey = org.next(); + ckey = cpy.next(); + assertEquals("Key comparison failed", okey, ckey); + assertEquals("Value comparison failed", htfull.get(okey), h.get(ckey)); + } + assertFalse("Copy has more keys than original", cpy.hasNext()); + } + + @Test + public void test_containsLjava_lang_Object() { + // Test for method boolean + // java.util.HashMap.contains(java.lang.Object) + assertTrue("Element not found", ht10.containsValue("Val 7")); + assertFalse("Invalid element found", ht10.containsValue("ZZZZZZZZZZZZZZZZ")); + } + + @Test + public void test_containsKeyLjava_lang_Object() { + // Test for method boolean + // java.util.HashMap.containsKey(java.lang.Object) + + assertTrue("Failed to find key", htfull.containsKey("FKey 4")); + assertFalse("Failed to find key", htfull.containsKey("FKey 99")); + } + + @Test + public void test_containsValueLjava_lang_Object() { + // Test for method boolean + // java.util.HashMap.containsValue(java.lang.Object) + for (String s : elmList) { + assertTrue("Returned false for valid value", ht10.containsValue(s)); + } + assertFalse("Returned true for invalid value", ht10.containsValue(new Object())); + } + + @Test + public void test_elements() { + // Test for method java.util.Enumeration java.util.HashMap.elements() + Iterator elms = ht10.values().iterator(); + while (elms.hasNext()) { + String s = elms.next(); + assertTrue("Missing key from enumeration", elmList.contains(s)); + } + + assertEquals("All keys not retrieved", 10, ht10.size()); + + assertFalse(elms.hasNext()); + try { + elms.next(); + fail("should throw NoSuchElementException"); + } catch (NoSuchElementException e) { + // Expected + } + } + + @Test + public void test_elements_subtest0() { + // this is the reference implementation behavior + final HashMap ht = new HashMap<>(7); + ht.put("1", "a"); + // these three elements hash to the same bucket in a 7 element HashMap + ht.put("2", "b"); + ht.put("9", "c"); + ht.put("12", "d"); + // HashMap looks like: + // 0: "1" + // 1: "12" -> "9" -> "2" + Iterator en = ht.values().iterator(); + // cache the first entry + en.hasNext(); + ht.remove("12"); + ht.remove("9"); + } + + @Test + public void test_entrySet() { + // Test for method java.util.Set java.util.HashMap.entrySet() + Set> s = ht10.entrySet(); + Set s2 = new HashSet<>(); + for (Map.Entry entry : s) { + s2.add(entry.getValue()); + } + for (String string : elmList) { + assertTrue("Returned incorrect entry set", s2.contains(string)); + } + + ht10.entrySet().iterator().next().setValue(null); + } + + @Test + public void test_equalsLjava_lang_Object() { + // Test for method boolean java.util.HashMap.equals(java.lang.Object) + HashMap h = hashMapClone(ht10); + assertEquals("Returned false for equal tables", ht10, h); + assertFalse("Returned true for unequal tables", ht10.equals(htfull)); + } + + @Test + public void test_getLjava_lang_Object() { + // Test for method java.lang.Object + // java.util.HashMap.get(java.lang.Object) + HashMap h = hashMapClone(htfull); + assertEquals("Could not retrieve element", "FVal 2", h.get("FKey 2")); + + // Regression for HARMONY-262 + org.teavm.classlib.java.util.HashMapTest.ReusableKey + k = new org.teavm.classlib.java.util.HashMapTest.ReusableKey(); + HashMap h2 = new HashMap<>(); + k.setKey(1); + h2.put(k, "value1"); + + k.setKey(13); + assertNull(h2.get(k)); + + k.setKey(12); + assertNull(h2.get(k)); + } + + @Test + public void test_hashCode() { + // Test for method int java.util.HashMap.hashCode() + int expectedHash = 0; + for (Map.Entry e : ht10.entrySet()) { + expectedHash += e.hashCode(); + } + assertEquals("Incorrect hashCode returned. Wanted: " + expectedHash + " got: " + ht10.hashCode(), + expectedHash, ht10.hashCode()); + assertEquals(ht10.hashCode(), ht10.entrySet().hashCode()); + } + + @Test + public void test_isEmpty() { + // Test for method boolean java.util.HashMap.isEmpty() + + assertFalse("isEmpty returned incorrect value", ht10.isEmpty()); + assertTrue("isEmpty returned incorrect value", new HashMap().isEmpty()); + } + + @Test + public void test_keys() { + // Test for method java.util.Enumeration java.util.HashMap.keys() + + Iterator keys = ht10.keySet().iterator(); + while (keys.hasNext()) { + String s = keys.next(); + assertTrue("Missing key from enumeration", keyList.contains(s)); + } + + assertEquals("All keys not retrieved", 10, ht10.size()); + + assertFalse(keys.hasNext()); + try { + keys.next(); + fail("should throw NoSuchElementException"); + } catch (NoSuchElementException e) { + // Expected + } + } + + @Test + public void test_keys_subtest0() { + // this is the reference implementation behavior + final HashMap ht = new HashMap<>(3); + ht.put("initial", ""); + Iterator en = ht.keySet().iterator(); + en.hasNext(); + ht.remove("initial"); + try { + en.next(); + fail(); + } catch (ConcurrentModificationException e) { + // ok + } + } + + @Test + public void test_keySet() { + // Test for method java.util.Set java.util.HashMap.keySet() + Set s = ht10.keySet(); + for (String string : keyList) { + assertTrue("Returned incorrect key set", s.contains(string)); + } + + Map map = new HashMap<>(101); + map.put(1, "1"); + map.put(102, "102"); + map.put(203, "203"); + Iterator it = map.keySet().iterator(); + Integer remove1 = it.next(); + it.remove(); + Integer remove2 = it.next(); + it.remove(); + ArrayList list = new ArrayList<>(Arrays.asList(1, 102, 203)); + list.remove(remove1); + list.remove(remove2); + assertEquals("Wrong result", it.next(), list.get(0)); + assertEquals("Wrong size", 1, map.size()); + assertEquals("Wrong contents", map.keySet().iterator().next(), list.get(0)); + + Map map2 = new HashMap<>(101); + map2.put(1, "1"); + map2.put(4, "4"); + Iterator it2 = map2.keySet().iterator(); + Integer remove3 = it2.next(); + Integer next; + if (remove3 == 1) { + next = 4; + } else { + next = 1; + } + it2.hasNext(); + it2.remove(); + assertEquals("Wrong result 2", it2.next(), next); + assertEquals("Wrong size 2", 1, map2.size()); + assertEquals("Wrong contents 2", map2.keySet().iterator().next(), next); + + Iterator enumeration = s.iterator(); + assertTrue(enumeration.hasNext()); + } + + @Test + public void test_keySet_subtest0() { + Set s1 = ht10.keySet(); + assertTrue("should contain key", s1.remove("Key 0")); + assertFalse("should not contain key", s1.remove("Key 0")); + } + + @Test + public void test_keySet_subtest1() { + // this is the reference implementation behavior + final HashMap ht = new HashMap<>(7); + ht.put("1", "a"); + // these three elements hash to the same bucket in a 7 element HashMap + ht.put("2", "b"); + ht.put("9", "c"); + ht.put("12", "d"); + // HashMap looks like: + // 0: "1" + // 1: "12" -> "9" -> "2" + Iterator it = ht.keySet().iterator(); + // this is mostly a copy of the test in test_elements_subtest0() + // test removing with the iterator does not null the values + while (it.hasNext()) { + String key = it.next(); + if ("1".equals(key)) { + it.remove(); + } + } + it = ht.values().iterator(); + boolean exception = false; + try { + // cached "12" + Set iteratorElements = new HashSet<>(); + iteratorElements.add(it.next()); + iteratorElements.add(it.next()); + iteratorElements.add(it.next()); + assertTrue(iteratorElements.contains("b")); + assertTrue(iteratorElements.contains("c")); + assertTrue(iteratorElements.contains("d")); + } catch (NoSuchElementException e) { + exception = true; + } + assertFalse("unexpected NoSuchElementException", exception); + } + + @Test + public void test_putLjava_lang_ObjectLjava_lang_Object() { + // Test for method java.lang.Object + // java.util.HashMap.put(java.lang.Object, java.lang.Object) + HashMap h = hashMapClone(ht100); + Integer key = 100; + h.put("Value 100", key); + assertTrue("Key/Value not inserted", h.size() == 1 && h.containsValue(key)); + } + + @Test + public void test_putAllLjava_util_Map() { + // Test for method void java.util.HashMap.putAll(java.util.Map) + HashMap h = new HashMap<>(); + h.putAll(ht10); + for (String x : keyList) { + assertEquals("Failed to put all elements", h.get(x), ht10.get(x)); + } + } + + @Test + public void test_removeLjava_lang_Object() { + // Test for method java.lang.Object + // java.util.HashMap.remove(java.lang.Object) + HashMap h = hashMapClone(htfull); + Object k = h.remove("FKey 0"); + assertTrue("Remove failed", !h.containsKey("FKey 0") || k == null); + } + + @Test + public void test_HashMap_selfReference_scenario1() { + HashMap, Set>> hashMap = new HashMap<>(); + Set> keySet = hashMap.keySet(); + hashMap.put(hashMap, keySet); + } + + @Test + public void test_HashMap_selfReference_scenario2() { + HashMap, HashMap> hashMap = new HashMap<>(); + hashMap.keySet(); + hashMap.put(hashMap, hashMap); + } + + @Test + public void test_size() { + // Test for method int java.util.HashMap.size() + assertTrue("Returned invalid size", ht10.size() == 10 && ht100.isEmpty()); + } + + @Test + public void test_toString() { + // Test for method java.lang.String java.util.HashMap.toString() + HashMap h = new HashMap<>(); + assertEquals("Incorrect toString for Empty table", "{}", h.toString()); + + h.put("one", "1"); + h.put("two", h); + String result = h.toString(); + assertTrue("should contain self ref", result.contains("(this")); + } + + @Test + public void test_values() { + // Test for method java.util.Collection java.util.HashMap.values() + Collection c = ht10.values(); + for (String s : elmList) { + assertTrue("Returned incorrect values", c.contains(s)); + } + + HashMap myHashMap = new HashMap<>(); + for (int i = 0; i < 100; i++) { + myHashMap.put(i, i); + } + Collection values = myHashMap.values(); + new UnmodifiableCollectionTestSupport(values).runTest(); + values.remove(0); + assertFalse("Removing from the values collection should remove from the original map", + myHashMap.containsValue(0)); + } + + @Test + public void test_entrySet_remove() { + HashMap hashMap = new HashMap<>(); + hashMap.put("my.nonexistent.prop", "AAA"); + hashMap.put("parse.error", "BBB"); + Iterator> iterator = hashMap.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + final Object value = entry.getValue(); + if (value.equals("AAA")) { + iterator.remove(); + } + } + assertFalse(hashMap.containsKey("my.nonexistent.prop")); + } + + @Test + public void test_keys_elements_keySet_Exceptions() { + HashMap hashMap = new HashMap<>(); + String key = "key"; + String value = "value"; + hashMap.put(key, value); + + Iterator iterator = hashMap.keySet().iterator(); + assertTrue(iterator.hasNext()); + try { + iterator.remove(); + fail("should throw IllegalStateException"); + } catch (IllegalStateException e) { + // Expected + } + iterator.next(); + iterator.remove(); + assertFalse(iterator.hasNext()); + assertTrue(hashMap.isEmpty()); + + hashMap.put(key, value); + iterator = hashMap.values().iterator(); + assertTrue(iterator.hasNext()); + try { + iterator.remove(); + fail("should throw IllegalStateException"); + } catch (IllegalStateException e) { + // Expected + } + iterator.next(); + iterator.remove(); + assertFalse(iterator.hasNext()); + assertTrue(hashMap.isEmpty()); + + hashMap.put(key, value); + iterator = hashMap.keySet().iterator(); + assertTrue(iterator.hasNext()); + try { + iterator.remove(); + fail("should throw IllegalStateException"); + } catch (IllegalStateException e) { + // Expected + } + iterator.next(); + iterator.remove(); + assertFalse(iterator.hasNext()); + + hashMap.clear(); + for (int i = 0; i < 10; i++) { + hashMap.put(key + i, value + i); + } + + Iterator enumeration = hashMap.keySet().iterator(); + iterator = enumeration; + assertTrue(enumeration.hasNext()); + assertTrue(iterator.hasNext()); + for (int i = 0; i < 10; i++) { + if (i % 2 == 0) { + enumeration.next(); + } else { + iterator.next(); + } + } + assertFalse(enumeration.hasNext()); + assertFalse(iterator.hasNext()); + try { + enumeration.next(); + fail("should throw NoSuchElementException"); + } catch (NoSuchElementException e) { + // Expected + } + try { + iterator.next(); + fail("should throw NoSuchElementException"); + } catch (NoSuchElementException e) { + // Expected + } + + // cast Enumeration to Iterator + enumeration = hashMap.values().iterator(); + iterator = enumeration; + assertTrue(enumeration.hasNext()); + assertTrue(iterator.hasNext()); + for (int i = 0; i < 10; i++) { + if (i % 2 == 0) { + enumeration.next(); + } else { + iterator.next(); + } + } + assertFalse(enumeration.hasNext()); + assertFalse(iterator.hasNext()); + try { + enumeration.next(); + fail("should throw NoSuchElementException"); + } catch (NoSuchElementException e) { + // Expected + } + try { + iterator.next(); + fail("should throw NoSuchElementException"); + } catch (NoSuchElementException e) { + // Expected + } + + // cast Iterator to Enumeration + enumeration = hashMap.keySet().iterator(); + iterator = (Iterator) enumeration; + assertTrue(enumeration.hasNext()); + assertTrue(iterator.hasNext()); + for (int i = 0; i < 10; i++) { + if (i % 2 == 0) { + enumeration.next(); + } else { + iterator.next(); + } + } + assertFalse(enumeration.hasNext()); + assertFalse(iterator.hasNext()); + try { + enumeration.next(); + fail("should throw NoSuchElementException"); + } catch (NoSuchElementException e) { + // Expected + } + try { + iterator.next(); + fail("should throw NoSuchElementException"); + } catch (NoSuchElementException e) { + // Expected + } + } + + @Test + public void test_computeUpdatesValueIfPresent() { + HashMap ht10 = new HashMap<>(); + for (int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.compute("Key5", (k, v) -> "changed"); + assertEquals("changed", newVal); + assertEquals(10, ht10.size()); + + for (int i = 0; i < 10; i++) { + if (i == 5) { + assertEquals("Value was incorrectly changed", "changed", ht10.get("Key" + i)); + } else { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + } + } + + @Test + public void test_computePutsNewEntryIfKeyIsAbsent() { + HashMap ht10 = new HashMap<>(); + for (int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.compute("absent key", (k, v) -> "added"); + assertEquals("added", newVal); + assertEquals(11, ht10.size()); + + for (int i = 0; i < 10; i++) { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + assertEquals("New value expected", "added", ht10.get("absent key")); + } + + @Test + public void test_computeRemovesEntryWhenNullProduced() { + HashMap ht10 = new HashMap<>(); + for (int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.compute("Key5", (k, v) -> null); + assertNull(newVal); + assertEquals(9, ht10.size()); + + for (int i = 0; i < 10; i++) { + if (i == 5) { + assertNull("Value was unexpectedly present in map", ht10.get("Key" + i)); + } else { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + } + } + + @Test + public void test_computeIfAbsentNominal() { + HashMap ht10 = new HashMap<>(); + for (int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.computeIfAbsent("absent key", k -> "added"); + assertEquals("added", newVal); + assertEquals(11, ht10.size()); + + for (int i = 0; i < 10; i++) { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + assertEquals("New value expected", "added", ht10.get("absent key")); + } + + @Test + public void test_computeIfAbsentIgnoresExistingEntry() { + HashMap ht10 = new HashMap<>(); + for (int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.computeIfAbsent("Key5", v -> "changed"); + assertEquals("Val5", newVal); + assertEquals(10, ht10.size()); + + for (int i = 0; i < 10; i++) { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + } + + @Test + public void test_computeIfAbsentDoesNothingIfNullProduced() { + HashMap ht10 = new HashMap<>(); + for (int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.computeIfAbsent("absent key", v -> null); + assertNull(newVal); + assertEquals(10, ht10.size()); + + for (int i = 0; i < 10; i++) { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + } + + @Test + public void test_computeIfPresentNominal() { + HashMap ht10 = new HashMap<>(); + for (int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.computeIfPresent("Key5", (k, v) -> "changed"); + assertEquals("changed", newVal); + assertEquals(10, ht10.size()); + + for (int i = 0; i < 10; i++) { + if (i == 5) { + assertEquals("Value was incorrectly updated", "changed", ht10.get("Key" + i)); + } else { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + } + } + + @Test + public void test_computeIfPresentIgnoresAbsentKeys() { + HashMap ht10 = new HashMap<>(); + for (int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.computeIfPresent("absent key", (k, v) -> "added"); + assertNull(newVal); + assertEquals(10, ht10.size()); + + for (int i = 0; i < 10; i++) { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + } + + @Test + public void test_computeIfPresentRemovesEntryWhenNullProduced() { + HashMap ht10 = new HashMap<>(); + for (int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.computeIfPresent("Key5", (k, v) -> null); + assertNull(newVal); + assertEquals(9, ht10.size()); + + for (int i = 0; i < 10; i++) { + if (i == 5) { + assertNull("Value unexpectedly present", ht10.get("Key" + i)); + } else { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + } + } + + @Test + public void test_mergeKeyAbsentCase() { + HashMap ht10 = new HashMap<>(); + for (int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.merge("absent key", "changed", (k, v) -> "remapped"); + assertEquals("changed", newVal); + assertEquals(11, ht10.size()); + + for (int i = 0; i < 10; i++) { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + assertEquals("New value expected", "changed", ht10.get("absent key")); + } + + @Test + public void test_mergeKeyPresentCase() { + HashMap ht10 = new HashMap<>(); + for (int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.merge("Key5", "changed", (k, v) -> "remapped"); + assertEquals("remapped", newVal); + assertEquals(10, ht10.size()); + + for (int i = 0; i < 10; i++) { + if (i == 5) { + assertEquals("Value was incorrectly updated", "remapped", ht10.get("Key" + i)); + } else { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + } + } + + @Test + public void test_mergeKeyAbsentCase_remapToNull() { + HashMap ht10 = new HashMap<>(); + for (int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.merge("absent key", "changed", (k, v) -> null); + assertEquals("changed", newVal); + assertEquals(11, ht10.size()); + + for (int i = 0; i < 10; i++) { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + assertEquals("New value expected", "changed", ht10.get("absent key")); + } + + @Test + public void test_mergeKeyPresentCase_remapToNull() { + HashMap ht10 = new HashMap<>(); + for (int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.merge("Key5", "changed", (k, v) -> null); + assertNull(newVal); + assertEquals(9, ht10.size()); + + for (int i = 0; i < 10; i++) { + if (i == 5) { + assertNull("Null value expected", ht10.get("Key" + i)); + } else { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + } + } + + @Test + public void test_replaceNominal() { + HashMap ht10 = new HashMap<>(); + for (int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.replace("Key5", "changed"); + assertEquals("Val5", newVal); + assertEquals(10, ht10.size()); + + for (int i = 0; i < 10; i++) { + if (i == 5) { + assertEquals("Value was incorrectly updated", "changed", ht10.get("Key" + i)); + } else { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + } + } + + @Test + public void test_replaceAbsentKey() { + HashMap ht10 = new HashMap<>(); + for (int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + String newVal = ht10.replace("absent key", "changed"); + assertNull(newVal); + assertEquals(10, ht10.size()); + + for (int i = 0; i < 10; i++) { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + } + + @Test + public void test_replace2Nominal() { + HashMap ht10 = new HashMap<>(); + for (int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + boolean replaced = ht10.replace("Key5", "Val5", "changed"); + assertTrue(replaced); + assertEquals(10, ht10.size()); + + for (int i = 0; i < 10; i++) { + if (i == 5) { + assertEquals("Value was incorrectly updated", "changed", ht10.get("Key" + i)); + } else { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + } + } + + @Test + public void test_replace2WithIncorrectExpectation() { + HashMap ht10 = new HashMap<>(); + for (int i = 0; i < 10; i++) { + ht10.put("Key" + i, "Val" + i); + } + + boolean replaced = ht10.replace("Key5", "incorrect value", "changed"); + assertFalse(replaced); + assertEquals(10, ht10.size()); + + for (int i = 0; i < 10; i++) { + assertEquals("Value was unexpectedly changed", "Val" + i, ht10.get("Key" + i)); + } + } + + @SuppressWarnings("unchecked") + protected HashMap hashMapClone(HashMap s) { + return (HashMap) s.clone(); + } + + static class ReusableKey { + private int key; + + public void setKey(int key) { + this.key = key; + } + + @Override + public int hashCode() { + return key; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof ReusableKey)) { + return false; + } + return key == ((ReusableKey) o).key; + } + } +} diff --git a/tests/src/test/java/org/teavm/classlib/java/util/LinkedHashMapTest.java b/tests/src/test/java/org/teavm/classlib/java/util/LinkedHashMapTest.java index d1b6dab44..76b68ca22 100644 --- a/tests/src/test/java/org/teavm/classlib/java/util/LinkedHashMapTest.java +++ b/tests/src/test/java/org/teavm/classlib/java/util/LinkedHashMapTest.java @@ -32,6 +32,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.assertNotEquals; @@ -43,9 +44,16 @@ import static org.junit.Assert.fail; import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; +import java.util.Iterator; import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import java.util.NoSuchElementException; +import java.util.SequencedMap; 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.classlib.support.MapTest2Support; @@ -529,6 +537,137 @@ public class LinkedHashMapTest { lhm.put("B", "C"); assertEquals("{A=(this Map), B=C}", lhm.toString()); + assertEquals("{B=C, A={A=(this Map), B=C}}", lhm.reversed().toString()); assertEquals("{}", new LinkedHashMap<>().toString()); } + + 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, LinkedHashMap::new)); + } + + private SequencedMap generateAccessOrderMap() { + return BASE_LIST.stream().collect(Collectors.toMap(Function.identity(), i -> Integer.toString(i), + (a, b) -> a, () -> new LinkedHashMap<>(16, 0.75f, true))); + } + + @Test + public void testSequencedMap() { + SequencedMap map = generateMap(); + assertEquals(Map.entry(1, "1"), map.pollFirstEntry()); + assertArrayEquals(new Integer[] { 6, 2, 5, 3, 4 }, map.keySet().toArray(new Integer[0])); + assertEquals(Map.entry(4, "4"), map.pollLastEntry()); + assertArrayEquals(new Integer[] { 6, 2, 5, 3 }, map.keySet().toArray(new Integer[0])); + assertEquals(Map.entry(3, "3"), map.pollLastEntry()); + assertArrayEquals(new Integer[] { 6, 2, 5 }, map.keySet().toArray(new Integer[0])); + assertEquals(Map.entry(6, "6"), map.firstEntry()); + assertEquals(Map.entry(5, "5"), map.lastEntry()); + map.putFirst(1, "1"); + map.put(7, "7"); + map.putLast(3, "3"); + assertArrayEquals(new Integer[] { 1, 6, 2, 5, 7, 3 }, map.keySet().toArray(new Integer[0])); + map = generateMap().reversed(); + assertEquals(Map.entry(4, "4"), map.pollFirstEntry()); + assertArrayEquals(new Integer[] { 3, 5, 2, 6, 1 }, map.keySet().toArray(new Integer[0])); + assertEquals(Map.entry(1, "1"), map.pollLastEntry()); + assertArrayEquals(new Integer[] { 3, 5, 2, 6 }, map.keySet().toArray(new Integer[0])); + assertEquals(Map.entry(6, "6"), map.pollLastEntry()); + assertArrayEquals(new Integer[] { 3, 5, 2 }, map.keySet().toArray(new Integer[0])); + assertEquals(Map.entry(3, "3"), map.firstEntry()); + assertEquals(Map.entry(2, "2"), map.lastEntry()); + map.putFirst(1, "1"); + map.put(7, "7"); + map.putLast(6, "6"); + assertArrayEquals(new Integer[] { 7, 1, 3, 5, 2, 6 }, map.keySet().toArray(new Integer[0])); + map = generateAccessOrderMap(); + map.putFirst(3, "3"); + map.put(5, "5"); + map.putLast(2, "2"); + assertArrayEquals(new Integer[] { 3, 1, 6, 4, 5, 2 }, map.keySet().toArray(new Integer[0])); + assertArrayEquals(new Integer[] { 2, 5, 4, 6, 1, 3 }, map.reversed().keySet().toArray(new Integer[0])); + map = generateAccessOrderMap(); + map.putFirst(1, "1"); + assertArrayEquals(new Integer[] { 1, 6, 2, 5, 3, 4 }, map.keySet().toArray(new Integer[0])); + map.put(1, "1"); + assertArrayEquals(new Integer[] { 6, 2, 5, 3, 4, 1 }, map.keySet().toArray(new Integer[0])); + map.putLast(6, "6"); + assertArrayEquals(new Integer[] { 2, 5, 3, 4, 1, 6 }, map.keySet().toArray(new Integer[0])); + map.putFirst(6, "6"); + assertArrayEquals(new Integer[] { 6, 2, 5, 3, 4, 1 }, map.keySet().toArray(new Integer[0])); + assertArrayEquals(new Integer[] { 1, 4, 3, 5, 2, 6 }, map.reversed().keySet().toArray(new Integer[0])); + map = generateAccessOrderMap().reversed(); + assertArrayEquals(new Integer[] { 4, 3, 5, 2, 6, 1 }, map.keySet().toArray(new Integer[0])); + map.putFirst(1, "1"); + assertArrayEquals(new Integer[] { 1, 4, 3, 5, 2, 6 }, map.keySet().toArray(new Integer[0])); + map.put(1, "1"); + assertArrayEquals(new Integer[] { 1, 4, 3, 5, 2, 6 }, map.keySet().toArray(new Integer[0])); + map.putLast(1, "1"); + assertArrayEquals(new Integer[] { 4, 3, 5, 2, 6, 1 }, map.keySet().toArray(new Integer[0])); + assertArrayEquals(new Integer[] { 1, 6, 2, 5, 3, 4 }, map.reversed().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(6, it.next().intValue()); + it.remove(); + assertArrayEquals(new Integer[] { 1, 2, 5, 3, 4 }, map.keySet().toArray(new Integer[0])); + map = map.reversed(); + it = map.keySet().iterator(); + assertTrue(it.hasNext()); + assertEquals(4, it.next().intValue()); + assertTrue(it.hasNext()); + assertEquals(3, it.next().intValue()); + it.remove(); + assertArrayEquals(new Integer[] { 4, 5, 2, 1 }, map.keySet().toArray(new Integer[0])); + map = generateMap(); + Iterator sit = map.values().iterator(); + assertTrue(sit.hasNext()); + assertEquals("1", sit.next()); + assertTrue(sit.hasNext()); + assertEquals("6", sit.next()); + sit.remove(); + assertArrayEquals(new String[] { "1", "2", "5", "3", "4" }, map.values().toArray(new String[0])); + map = map.reversed(); + sit = map.values().iterator(); + assertTrue(sit.hasNext()); + assertEquals("4", sit.next()); + assertTrue(sit.hasNext()); + assertEquals("3", sit.next()); + sit.remove(); + assertArrayEquals(new String[] { "4", "5", "2", "1" }, map.values().toArray(new String[0])); + } + + @Test + public void testEmpty() { + var empty = new LinkedHashMap<>(); + 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 + } + } }