classlib: implement third part of JEP-431 (#771)

SequencedSet -> LinkedHashSet, SequencedMap -> LinkedHashMap
This commit is contained in:
Ivan Hetman 2023-10-08 18:05:11 +03:00 committed by GitHub
parent 2d91f539c7
commit d47fea0b55
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 2160 additions and 410 deletions

View File

@ -53,19 +53,19 @@ public abstract class TAbstractMap<K, V> extends TObject implements TMap<K, V> {
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (!(obj instanceof TMap.Entry)) { if (this == obj) {
return false; return true;
} }
TMap.Entry<?, ?> other = (TMap.Entry<?, ?>) obj; if (obj instanceof TMap.Entry) {
if (getKey() == null ? other.getKey() != null : !getKey().equals(other.getKey())) { TMap.Entry<?, ?> entry = (TMap.Entry<?, ?>) obj;
return false; return TObjects.equals(key, entry.getKey()) && TObjects.equals(value, entry.getValue());
} }
return getValue() == null ? other.getValue() == null : getValue().equals(other.getValue()); return false;
} }
@Override @Override
public int hashCode() { public int hashCode() {
return (getKey() == null ? 0 : getKey().hashCode()) ^ (getValue() == null ? 0 : getValue().hashCode()); return TObjects.hashCode(key) ^ TObjects.hashCode(value);
} }
@Override @Override
@ -104,19 +104,19 @@ public abstract class TAbstractMap<K, V> extends TObject implements TMap<K, V> {
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (!(obj instanceof TMap.Entry)) { if (this == obj) {
return false; return true;
} }
TMap.Entry<?, ?> other = (TMap.Entry<?, ?>) obj; if (obj instanceof TMap.Entry) {
if (getKey() == null ? other.getKey() != null : !getKey().equals(other.getKey())) { TMap.Entry<?, ?> entry = (TMap.Entry<?, ?>) obj;
return false; return TObjects.equals(key, entry.getKey()) && TObjects.equals(value, entry.getValue());
} }
return getValue() == null ? other.getValue() == null : getValue().equals(other.getValue()); return false;
} }
@Override @Override
public int hashCode() { public int hashCode() {
return (getKey() == null ? 0 : getKey().hashCode()) ^ (getValue() == null ? 0 : getValue().hashCode()); return TObjects.hashCode(key) ^ TObjects.hashCode(value);
} }
@Override @Override
@ -254,7 +254,7 @@ public abstract class TAbstractMap<K, V> extends TObject implements TMap<K, V> {
int result = 0; int result = 0;
for (TIterator<? extends TMap.Entry<K, V>> iter = entrySet().iterator(); iter.hasNext();) { for (TIterator<? extends TMap.Entry<K, V>> iter = entrySet().iterator(); iter.hasNext();) {
TMap.Entry<K, V> entry = iter.next(); TMap.Entry<K, V> entry = iter.next();
result ^= entry.hashCode(); result += entry.hashCode();
} }
return result; return result;
} }
@ -292,16 +292,16 @@ public abstract class TAbstractMap<K, V> extends TObject implements TMap<K, V> {
private class KeySet extends TAbstractSet<K> { private class KeySet extends TAbstractSet<K> {
@Override @Override
public TIterator<K> iterator() { public TIterator<K> iterator() {
final TIterator<TMap.Entry<K, V>> iter = TAbstractMap.this.entrySet().iterator(); final TIterator<TMap.Entry<K, V>> it = TAbstractMap.this.entrySet().iterator();
return new TIterator<K>() { return new TIterator<>() {
@Override public boolean hasNext() { @Override public boolean hasNext() {
return iter.hasNext(); return it.hasNext();
} }
@Override public K next() { @Override public K next() {
return iter.next().getKey(); return it.next().getKey();
} }
@Override public void remove() { @Override public void remove() {
iter.remove(); it.remove();
} }
}; };
} }

View File

@ -21,7 +21,7 @@ import org.teavm.classlib.java.util.TMap.Entry;
public class TCollections extends TObject { public class TCollections extends TObject {
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public static final TSet EMPTY_SET = new TTemplateCollections.AbstractImmutableSet<Object>() { public static final TSet EMPTY_SET = new TTemplateCollections.AbstractImmutableSet<>() {
@Override public int size() { @Override public int size() {
return 0; return 0;
} }
@ -40,7 +40,7 @@ public class TCollections extends TObject {
}; };
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public static final TMap EMPTY_MAP = new TTemplateCollections.AbstractImmutableMap<Object, Object>() { public static final TMap EMPTY_MAP = new TTemplateCollections.AbstractImmutableMap<>() {
@Override public TSet<Entry<Object, Object>> entrySet() { @Override public TSet<Entry<Object, Object>> entrySet() {
return emptySet(); return emptySet();
} }
@ -63,7 +63,7 @@ public class TCollections extends TObject {
}; };
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public static final TList EMPTY_LIST = new TTemplateCollections.AbstractImmutableList<Object>() { public static final TList EMPTY_LIST = new TTemplateCollections.AbstractImmutableList<>() {
@Override public Object get(int index) { @Override public Object get(int index) {
throw new TIndexOutOfBoundsException(); throw new TIndexOutOfBoundsException();
} }
@ -86,7 +86,7 @@ public class TCollections extends TObject {
} }
}; };
private static final TIterator<?> EMPTY_ITERATOR = new TIterator<Object>() { private static final TIterator<?> EMPTY_ITERATOR = new TIterator<>() {
@Override public boolean hasNext() { @Override public boolean hasNext() {
return false; return false;
} }
@ -98,7 +98,7 @@ public class TCollections extends TObject {
} }
}; };
private static final TListIterator<?> EMPTY_LIST_ITERATOR = new TListIterator<Object>() { private static final TListIterator<?> EMPTY_LIST_ITERATOR = new TListIterator<>() {
@Override public boolean hasNext() { @Override public boolean hasNext() {
return false; return false;
} }
@ -144,7 +144,7 @@ public class TCollections extends TObject {
} }
public static <T> TEnumeration<T> emptyEnumeration() { public static <T> TEnumeration<T> emptyEnumeration() {
return new TEnumeration<T>() { return new TEnumeration<>() {
@Override public boolean hasMoreElements() { @Override public boolean hasMoreElements() {
return false; return false;
} }
@ -174,7 +174,7 @@ public class TCollections extends TObject {
public static <K, V> TMap<K, V> singletonMap(final K key, final V value) { public static <K, V> TMap<K, V> singletonMap(final K key, final V value) {
final TSet<Entry<K, V>> entries = singleton(new TAbstractMap.SimpleImmutableEntry<>(key, value)); final TSet<Entry<K, V>> entries = singleton(new TAbstractMap.SimpleImmutableEntry<>(key, value));
return new TAbstractMap<K, V>() { return new TAbstractMap<>() {
@Override public TSet<Entry<K, V>> entrySet() { @Override public TSet<Entry<K, V>> entrySet() {
return entries; return entries;
} }
@ -182,7 +182,7 @@ public class TCollections extends TObject {
} }
public static <T> TList<T> unmodifiableList(final TList<? extends T> list) { public static <T> TList<T> unmodifiableList(final TList<? extends T> list) {
return new TAbstractList<T>() { return new TAbstractList<>() {
@Override public T get(int index) { @Override public T get(int index) {
return list.get(index); return list.get(index);
} }
@ -193,7 +193,7 @@ public class TCollections extends TObject {
} }
public static <T> TList<T> nCopies(final int n, final T o) { public static <T> TList<T> nCopies(final int n, final T o) {
return new TAbstractList<T>() { return new TAbstractList<>() {
@Override public T get(int index) { @Override public T get(int index) {
if (index < 0 || index >= n) { if (index < 0 || index >= n) {
throw new TIndexOutOfBoundsException(); throw new TIndexOutOfBoundsException();
@ -461,7 +461,7 @@ public class TCollections extends TObject {
} }
public static <T> TCollection<T> unmodifiableCollection(final TCollection<? extends T> c) { public static <T> TCollection<T> unmodifiableCollection(final TCollection<? extends T> c) {
return new TAbstractCollection<T>() { return new TAbstractCollection<>() {
@Override public TIterator<T> iterator() { @Override public TIterator<T> iterator() {
return unmodifiableIterator(c.iterator()); return unmodifiableIterator(c.iterator());
} }
@ -472,7 +472,7 @@ public class TCollections extends TObject {
} }
private static <T> TIterator<T> unmodifiableIterator(final TIterator<? extends T> c) { private static <T> TIterator<T> unmodifiableIterator(final TIterator<? extends T> c) {
return new TIterator<T>() { return new TIterator<>() {
@Override public boolean hasNext() { @Override public boolean hasNext() {
return c.hasNext(); return c.hasNext();
} }
@ -486,7 +486,7 @@ public class TCollections extends TObject {
} }
public static <T> TSet<T> unmodifiableSet(final TSet<? extends T> s) { public static <T> TSet<T> unmodifiableSet(final TSet<? extends T> s) {
return new TAbstractSet<T>() { return new TAbstractSet<>() {
@Override public TIterator<T> iterator() { @Override public TIterator<T> iterator() {
return unmodifiableIterator(s.iterator()); return unmodifiableIterator(s.iterator());
} }
@ -497,7 +497,7 @@ public class TCollections extends TObject {
} }
public static <K, V> TMap<K, V> unmodifiableMap(final TMap<? extends K, ? extends V> m) { public static <K, V> TMap<K, V> unmodifiableMap(final TMap<? extends K, ? extends V> m) {
return new TAbstractMap<K, V>() { return new TAbstractMap<>() {
@Override public TSet<Entry<K, V>> entrySet() { @Override public TSet<Entry<K, V>> entrySet() {
return unmodifiableMapEntrySet(m.entrySet()); return unmodifiableMapEntrySet(m.entrySet());
} }
@ -506,7 +506,7 @@ public class TCollections extends TObject {
private static <K, V> TSet<Entry<K, V>> unmodifiableMapEntrySet( private static <K, V> TSet<Entry<K, V>> unmodifiableMapEntrySet(
final TSet<? extends Entry<? extends K, ? extends V>> c) { final TSet<? extends Entry<? extends K, ? extends V>> c) {
return new TAbstractSet<Entry<K, V>>() { return new TAbstractSet<>() {
@Override public int size() { @Override public int size() {
return c.size(); return c.size();
} }
@ -518,7 +518,7 @@ public class TCollections extends TObject {
private static <K, V> TIterator<Entry<K, V>> unmodifiableMapEntryIterator( private static <K, V> TIterator<Entry<K, V>> unmodifiableMapEntryIterator(
final TIterator<? extends Entry<? extends K, ? extends V>> c) { final TIterator<? extends Entry<? extends K, ? extends V>> c) {
return new TIterator<Entry<K, V>>() { return new TIterator<>() {
@Override public boolean hasNext() { @Override public boolean hasNext() {
return c.hasNext(); return c.hasNext();
} }
@ -552,15 +552,19 @@ public class TCollections extends TObject {
return (TComparator<T>) reverseOrder; return (TComparator<T>) reverseOrder;
} }
@SuppressWarnings("unchecked")
private static TComparator<Object> reverseOrder = (o1, o2) -> ((TComparable<Object>) o2).compareTo(o1); private static TComparator<Object> reverseOrder = (o1, o2) -> ((TComparable<Object>) o2).compareTo(o1);
public static <T> TComparator<T> reverseOrder(final TComparator<T> cmp) { public static <T> TComparator<T> reverseOrder(final TComparator<T> cmp) {
if (cmp == null) {
return reverseOrder();
}
return (o1, o2) -> -cmp.compare(o1, o2); return (o1, o2) -> -cmp.compare(o1, o2);
} }
public static <T> TEnumeration<T> enumeration(TCollection<T> c) { public static <T> TEnumeration<T> enumeration(TCollection<T> c) {
final TIterator<T> iter = c.iterator(); final TIterator<T> iter = c.iterator();
return new TEnumeration<T>() { return new TEnumeration<>() {
@Override public boolean hasMoreElements() { @Override public boolean hasMoreElements() {
return iter.hasNext(); return iter.hasNext();
} }

View File

@ -13,27 +13,9 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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; package org.teavm.classlib.java.util;
import java.util.Arrays; import java.util.Arrays;
import java.util.Objects;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.BiFunction; import java.util.function.BiFunction;
import java.util.function.Consumer; import java.util.function.Consumer;
@ -61,7 +43,7 @@ public class THashMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
HashEntry(K theKey, V theValue) { HashEntry(K theKey, V theValue) {
super(theKey, theValue); super(theKey, theValue);
origKeyHash = Objects.hashCode(theKey); origKeyHash = TObjects.hashCode(theKey);
} }
@Override @Override
@ -210,9 +192,9 @@ public class THashMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
public boolean remove(Object object) { public boolean remove(Object object) {
if (object instanceof TMap.Entry) { if (object instanceof TMap.Entry) {
TMap.Entry<?, ?> oEntry = (TMap.Entry<?, ?>) object; TMap.Entry<?, ?> oEntry = (TMap.Entry<?, ?>) object;
TMap.Entry<K, V> entry = associatedMap.getEntry(oEntry.getKey()); TMap.Entry<K, V> entry = associatedMap.entryByKey(oEntry.getKey());
if (valuesEq(entry, oEntry)) { if (entry != null && TObjects.equals(entry.getValue(), oEntry.getValue())) {
associatedMap.removeEntry(entry); associatedMap.removeByKey(entry.getKey());
return true; return true;
} }
} }
@ -223,19 +205,12 @@ public class THashMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
public boolean contains(Object object) { public boolean contains(Object object) {
if (object instanceof TMap.Entry) { if (object instanceof TMap.Entry) {
TMap.Entry<?, ?> oEntry = (TMap.Entry<?, ?>) object; TMap.Entry<?, ?> oEntry = (TMap.Entry<?, ?>) object;
TMap.Entry<K, V> entry = associatedMap.getEntry(oEntry.getKey()); TMap.Entry<K, V> entry = associatedMap.entryByKey(oEntry.getKey());
return valuesEq(entry, oEntry); return entry != null && TObjects.equals(entry.getValue(), oEntry.getValue());
} }
return false; 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 @Override
public TIterator<TMap.Entry<K, V>> iterator() { public TIterator<TMap.Entry<K, V>> iterator() {
return new EntryIterator<>(associatedMap); return new EntryIterator<>(associatedMap);
@ -347,7 +322,7 @@ public class THashMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
@Override @Override
public boolean containsKey(Object key) { public boolean containsKey(Object key) {
HashEntry<K, V> m = getEntry(key); HashEntry<K, V> m = entryByKey(key);
return m != null; return m != null;
} }
@ -357,7 +332,7 @@ public class THashMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
for (int i = 0; i < elementData.length; i++) { for (int i = 0; i < elementData.length; i++) {
HashEntry<K, V> entry = elementData[i]; HashEntry<K, V> entry = elementData[i];
while (entry != null) { while (entry != null) {
if (areEqualValues(value, entry.value)) { if (value.equals(entry.value)) {
return true; return true;
} }
entry = entry.next; entry = entry.next;
@ -384,14 +359,14 @@ public class THashMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
@Override @Override
public V get(Object key) { public V get(Object key) {
HashEntry<K, V> m = getEntry(key); HashEntry<K, V> m = entryByKey(key);
if (m != null) { if (m != null) {
return m.value; return m.value;
} }
return null; return null;
} }
final HashEntry<K, V> getEntry(Object key) { final HashEntry<K, V> entryByKey(Object key) {
HashEntry<K, V> m; HashEntry<K, V> m;
if (key == null) { if (key == null) {
m = findNullKeyEntry(); m = findNullKeyEntry();
@ -438,7 +413,7 @@ public class THashMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
THashMap.this.clear(); THashMap.this.clear();
} }
@Override public boolean remove(Object key) { @Override public boolean remove(Object key) {
HashEntry<K, V> entry = THashMap.this.removeEntry(key); HashEntry<K, V> entry = THashMap.this.removeByKey(key);
return entry != null; return entry != null;
} }
@Override public TIterator<K> iterator() { @Override public TIterator<K> iterator() {
@ -469,7 +444,7 @@ public class THashMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
return putImpl(key, value); return putImpl(key, value);
} }
V putImpl(K key, V value) { private V putImpl(K key, V value) {
HashEntry<K, V> entry; HashEntry<K, V> entry;
if (key == null) { if (key == null) {
entry = findNullKeyEntry(); entry = findNullKeyEntry();
@ -498,7 +473,7 @@ public class THashMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
return result; return result;
} }
HashEntry<K, V> createHashedEntry(K key, int index, int hash) { private HashEntry<K, V> createHashedEntry(K key, int index, int hash) {
HashEntry<K, V> entry = new HashEntry<>(key, hash); HashEntry<K, V> entry = new HashEntry<>(key, hash);
entry.next = elementData[index]; entry.next = elementData[index];
elementData[index] = entry; elementData[index] = entry;
@ -512,14 +487,13 @@ public class THashMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
} }
} }
private void putAllImpl(TMap<? extends K, ? extends V> map) { void putAllImpl(TMap<? extends K, ? extends V> map) {
int capacity = elementCount + map.size(); int capacity = elementCount + map.size();
if (capacity > threshold) { if (capacity > threshold) {
rehash(capacity); rehash(capacity);
} }
for (TIterator<? extends TMap.Entry<? extends K, ? extends V>> iter = map.entrySet().iterator(); for (var it = map.entrySet().iterator(); it.hasNext();) {
iter.hasNext();) { TMap.Entry<? extends K, ? extends V> entry = it.next();
TMap.Entry<? extends K, ? extends V> entry = iter.next();
putImpl(entry.getKey(), entry.getValue()); putImpl(entry.getKey(), entry.getValue());
} }
} }
@ -549,30 +523,14 @@ public class THashMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
@Override @Override
public V remove(Object key) { public V remove(Object key) {
HashEntry<K, V> entry = removeEntry(key); HashEntry<K, V> entry = removeByKey(key);
if (entry != null) { if (entry != null) {
return entry.value; return entry.value;
} }
return null; return null;
} }
final void removeEntry(HashEntry<K, V> entry) { final HashEntry<K, V> removeByKey(Object key) {
int index = entry.origKeyHash & (elementData.length - 1);
HashEntry<K, V> 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<K, V> removeEntry(Object key) {
int index = 0; int index = 0;
HashEntry<K, V> entry; HashEntry<K, V> entry;
HashEntry<K, V> last = null; HashEntry<K, V> last = null;
@ -682,8 +640,4 @@ public class THashMap<K, V> extends TAbstractMap<K, V> implements TCloneable, TS
static boolean areEqualKeys(Object key1, Object key2) { static boolean areEqualKeys(Object key1, Object key2) {
return (key1 == key2) || key1.equals(key2); return (key1 == key2) || key1.equals(key2);
} }
static boolean areEqualValues(Object value1, Object value2) {
return (value1 == value2) || value1.equals(value2);
}
} }

View File

@ -13,33 +13,16 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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; package org.teavm.classlib.java.util;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer; import java.util.function.BiFunction;
import org.teavm.classlib.java.lang.TIllegalStateException;
public class TLinkedHashMap<K, V> extends THashMap<K, V> implements TMap<K, V> { public class TLinkedHashMap<K, V> extends THashMap<K, V> implements TSequencedMap<K, V> {
private final boolean accessOrder; private boolean accessOrder;
transient private LinkedHashMapEntry<K, V> head; transient LinkedHashMapEntry<K, V> head;
transient private LinkedHashMapEntry<K, V> tail; transient LinkedHashMapEntry<K, V> tail;
public TLinkedHashMap() { public TLinkedHashMap() {
accessOrder = false; accessOrder = false;
@ -73,127 +56,15 @@ public class TLinkedHashMap<K, V> extends THashMap<K, V> implements TMap<K, V> {
putAll(m); putAll(m);
} }
private static class AbstractMapIterator<K, V> { @Override
int expectedModCount; void putAllImpl(TMap<? extends K, ? extends V> map) {
LinkedHashMapEntry<K, V> futureEntry; int capacity = elementCount + map.size();
LinkedHashMapEntry<K, V> currentEntry; if (capacity > threshold) {
final TLinkedHashMap<K, V> associatedMap; rehash(capacity);
AbstractMapIterator(TLinkedHashMap<K, V> map) {
expectedModCount = map.modCount;
futureEntry = map.head;
associatedMap = map;
} }
for (var it = map.entrySet().iterator(); it.hasNext();) {
public boolean hasNext() { TMap.Entry<? extends K, ? extends V> entry = it.next();
return futureEntry != null; putImpl(entry.getKey(), entry.getValue(), false);
}
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<K, V> lhme = currentEntry;
LinkedHashMapEntry<K, V> p = lhme.chainBackward;
LinkedHashMapEntry<K, V> n = lhme.chainForward;
TLinkedHashMap<K, V> 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<K, V> extends AbstractMapIterator<K, V> implements TIterator<Entry<K, V>> {
EntryIterator(TLinkedHashMap<K, V> map) {
super(map);
}
@Override
public Entry<K, V> next() {
makeNext();
return currentEntry;
}
}
private static class KeyIterator<K, V> extends AbstractMapIterator<K, V> implements TIterator<K> {
KeyIterator(TLinkedHashMap<K, V> map) {
super(map);
}
@Override
public K next() {
makeNext();
return currentEntry.key;
}
}
private static class ValueIterator<K, V> extends AbstractMapIterator<K, V> implements TIterator<V> {
ValueIterator(TLinkedHashMap<K, V> map) {
super(map);
}
@Override
public V next() {
makeNext();
return currentEntry.value;
}
}
static final class LinkedHashMapEntrySet<K, V> extends HashMapEntrySet<K, V> {
public LinkedHashMapEntrySet(TLinkedHashMap<K, V> lhm) {
super(lhm);
}
@Override
public TIterator<Entry<K, V>> iterator() {
return new EntryIterator<>((TLinkedHashMap<K, V>) hashMap());
}
@Override
public void forEach(Consumer<? super Entry<K, V>> action) {
TLinkedHashMap<K, V> map = (TLinkedHashMap<K, V>) hashMap();
if (map.elementCount > 0) {
int prevModCount = map.modCount;
LinkedHashMapEntry<K, V> entry = map.head;
do {
action.accept(entry);
entry = entry.chainForward;
if (map.modCount != prevModCount) {
throw new TConcurrentModificationException();
}
} while (entry != null);
}
} }
} }
@ -255,7 +126,7 @@ public class TLinkedHashMap<K, V> extends THashMap<K, V> implements TMap<K, V> {
} }
@Override @Override
public V get(Object key) { public V getOrDefault(Object key, V defaultValue) {
LinkedHashMapEntry<K, V> m; LinkedHashMapEntry<K, V> m;
if (key == null) { if (key == null) {
m = (LinkedHashMapEntry<K, V>) findNullKeyEntry(); m = (LinkedHashMapEntry<K, V>) findNullKeyEntry();
@ -265,7 +136,7 @@ public class TLinkedHashMap<K, V> extends THashMap<K, V> implements TMap<K, V> {
m = (LinkedHashMapEntry<K, V>) findNonNullKeyEntry(key, index, hash); m = (LinkedHashMapEntry<K, V>) findNonNullKeyEntry(key, index, hash);
} }
if (m == null) { if (m == null) {
return null; return defaultValue;
} }
if (accessOrder && tail != m) { if (accessOrder && tail != m) {
LinkedHashMapEntry<K, V> p = m.chainBackward; LinkedHashMapEntry<K, V> p = m.chainBackward;
@ -285,27 +156,24 @@ public class TLinkedHashMap<K, V> extends THashMap<K, V> implements TMap<K, V> {
} }
@Override @Override
HashEntry<K, V> createHashedEntry(K key, int index, int hash) { public V get(Object key) {
return getOrDefault(key, null);
}
private HashEntry<K, V> createHashedEntry(K key, int index, int hash, boolean first) {
LinkedHashMapEntry<K, V> m = new LinkedHashMapEntry<>(key, hash); LinkedHashMapEntry<K, V> m = new LinkedHashMapEntry<>(key, hash);
m.next = elementData[index]; m.next = elementData[index];
elementData[index] = m; elementData[index] = m;
linkEntry(m); linkEntry(m, first);
return m; return m;
} }
@Override @Override
public V put(K key, V value) { public V put(K key, V value) {
V result = putImpl(key, value); return putImpl(key, value, false);
if (removeEldestEntry(head)) {
remove(head.key);
}
return result;
} }
@Override V putImpl(K key, V value, boolean first) {
V putImpl(K key, V value) {
LinkedHashMapEntry<K, V> m; LinkedHashMapEntry<K, V> m;
if (elementCount == 0) { if (elementCount == 0) {
head = null; head = null;
@ -321,36 +189,37 @@ public class TLinkedHashMap<K, V> extends THashMap<K, V> implements TMap<K, V> {
if (++elementCount > threshold) { if (++elementCount > threshold) {
rehash(); rehash();
} }
m = (LinkedHashMapEntry<K, V>) createHashedEntry(null, 0, 0); m = (LinkedHashMapEntry<K, V>) createHashedEntry(null, 0, 0, first);
} else { } else {
linkEntry(m); linkEntry(m, first);
} }
} else { } else {
int hash = key.hashCode(); int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % elementData.length; int index = (hash & Integer.MAX_VALUE) % elementData.length;
m = (LinkedHashMapEntry<K, V>) findNonNullKeyEntry(key, index, hash); m = (LinkedHashMapEntry<K, V>) findNonNullKeyEntry(key, index, hash);
if (m == null) { if (m == null) {
modCount++; modCount++;
if (++elementCount > threshold) { if (++elementCount > threshold) {
rehash(); rehash();
index = (hash & 0x7FFFFFFF) % elementData.length; index = (hash & Integer.MAX_VALUE) % elementData.length;
} }
m = (LinkedHashMapEntry<K, V>) createHashedEntry(key, index, hash); m = (LinkedHashMapEntry<K, V>) createHashedEntry(key, index, hash, first);
} else { } else {
linkEntry(m); linkEntry(m, first);
} }
} }
V result = m.value; V result = m.value;
m.value = value; m.value = value;
if (removeEldestEntry(head)) {
remove(head.key);
}
return result; return result;
} }
void linkEntry(LinkedHashMapEntry<K, V> m) { void linkEntry(LinkedHashMapEntry<K, V> m, boolean first) {
if (tail == m) {
return;
}
if (head == null) { if (head == null) {
// Check if the map is empty // Check if the map is empty
head = m; head = m;
@ -364,8 +233,8 @@ public class TLinkedHashMap<K, V> extends THashMap<K, V> implements TMap<K, V> {
LinkedHashMapEntry<K, V> n = m.chainForward; LinkedHashMapEntry<K, V> n = m.chainForward;
if (p == null) { if (p == null) {
if (n != null) { if (n != null) {
// The entry must be the head but not the tail // Existing entry must be the head but not the tail
if (accessOrder) { if (!first && accessOrder) {
head = n; head = n;
n.chainBackward = null; n.chainBackward = null;
m.chainBackward = tail; m.chainBackward = tail;
@ -375,133 +244,87 @@ public class TLinkedHashMap<K, V> extends THashMap<K, V> implements TMap<K, V> {
} }
} else { } else {
// This is a new entry // This is a new entry
m.chainBackward = tail; m.chainBackward = first ? null : tail;
m.chainForward = null; m.chainForward = first ? head : null;
tail.chainForward = m; if (first) {
tail = m; 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 @Override
public TSet<Entry<K, V>> entrySet() { public TSet<Entry<K, V>> entrySet() {
return new LinkedHashMapEntrySet<>(this); return new TLinkedHashMapEntrySet<>(this, false);
} }
@Override @Override
public TSet<K> keySet() { public TSet<K> keySet() {
return sequencedKeySet();
}
@Override
public TSequencedSet<K> sequencedKeySet() {
if (cachedKeySet == null) { if (cachedKeySet == null) {
cachedKeySet = new TAbstractSet<K>() { cachedKeySet = new TLinkedHashMapKeySet<>(this, false);
@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<K> iterator() {
return new KeyIterator<>(TLinkedHashMap.this);
}
@Override
public void forEach(Consumer<? super K> action) {
if (elementCount > 0) {
int prevModCount = modCount;
LinkedHashMapEntry<K, V> entry = head;
do {
action.accept(entry.key);
entry = entry.chainForward;
if (modCount != prevModCount) {
throw new TConcurrentModificationException();
}
} while (entry != null);
}
}
};
} }
return cachedKeySet; return (TSequencedSet<K>) cachedKeySet;
} }
@Override @Override
public TCollection<V> values() { public TCollection<V> values() {
return sequencedValues();
}
@Override
public TSequencedCollection<V> sequencedValues() {
if (cachedValues == null) { if (cachedValues == null) {
cachedValues = new TAbstractCollection<V>() { cachedValues = new TLinkedHashMapValues<>(this, false);
@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<V> iterator() {
return new ValueIterator<>(TLinkedHashMap.this);
}
@Override
public void forEach(Consumer<? super V> action) {
if (elementCount > 0) {
int prevModCount = modCount;
LinkedHashMapEntry<K, V> entry = head;
do {
action.accept(entry.value);
entry = entry.chainForward;
if (modCount != prevModCount) {
throw new TConcurrentModificationException();
}
} while (entry != null);
}
}
};
} }
return cachedValues; return (TSequencedCollection<V>) cachedValues;
}
@Override
public TSequencedSet<Entry<K, V>> sequencedEntrySet() {
return new TLinkedHashMapEntrySet<>(this, false);
} }
@Override @Override
public V remove(Object key) { public V remove(Object key) {
LinkedHashMapEntry<K, V> m = (LinkedHashMapEntry<K, V>) removeEntry(key); LinkedHashMapEntry<K, V> m = (LinkedHashMapEntry<K, V>) removeByKey(key);
if (m == null) { if (m == null) {
return null; return null;
} }
@ -509,13 +332,18 @@ public class TLinkedHashMap<K, V> extends THashMap<K, V> implements TMap<K, V> {
LinkedHashMapEntry<K, V> n = m.chainForward; LinkedHashMapEntry<K, V> n = m.chainForward;
if (p != null) { if (p != null) {
p.chainForward = n; p.chainForward = n;
if (n != null) {
n.chainBackward = p;
} else {
tail = p;
}
} else { } else {
head = n; head = n;
} if (n != null) {
if (n != null) { n.chainBackward = null;
n.chainBackward = p; } else {
} else { tail = null;
tail = p; }
} }
return m.value; return m.value;
} }
@ -545,4 +373,41 @@ public class TLinkedHashMap<K, V> extends THashMap<K, V> implements TMap<K, V> {
head = null; head = null;
tail = null; tail = null;
} }
@Override
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
if (elementCount > 0) {
int prevModCount = modCount;
LinkedHashMapEntry<K, V> 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<K, V> reversed() {
return new TReversedLinkedHashMap<>(this);
}
static <T> T checkNotNull(T node) {
if (node == null) {
throw new TNoSuchElementException();
}
return node;
}
} }

View File

@ -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<K, V> extends TAbstractSet<TMap.Entry<K, V>>
implements TSequencedSet<TMap.Entry<K, V>> {
private final TLinkedHashMap<K, V> base;
private final boolean reversed;
TLinkedHashMapEntrySet(TLinkedHashMap<K, V> 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<TMap.Entry<K, V>> 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<K, V> 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<K, V> 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<? super TMap.Entry<K, V>> action) {
if (base.elementCount > 0) {
int prevModCount = base.modCount;
TLinkedHashMap.LinkedHashMapEntry<K, V> 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<K, V> e) {
throw new UnsupportedOperationException();
}
@Override
public final void addLast(TMap.Entry<K, V> e) {
throw new UnsupportedOperationException();
}
@Override
public final TMap.Entry<K, V> getFirst() {
return TLinkedHashMap.checkNotNull(reversed ? base.tail : base.head);
}
@Override
public final TMap.Entry<K, V> getLast() {
return TLinkedHashMap.checkNotNull(reversed ? base.head : base.tail);
}
@Override
public final TMap.Entry<K, V> removeFirst() {
var e = TLinkedHashMap.checkNotNull(reversed ? base.tail : base.head);
base.remove(e.key);
return e;
}
@Override
public final TMap.Entry<K, V> removeLast() {
var e = TLinkedHashMap.checkNotNull(reversed ? base.head : base.tail);
base.remove(e.key);
return e;
}
@Override
public TSequencedSet<TMap.Entry<K, V>> reversed() {
return new TLinkedHashMapEntrySet<>(base, !reversed);
}
}

View File

@ -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<K, V> {
private final TLinkedHashMap<K, V> base;
private final boolean reversed;
private int expectedModCount;
private TLinkedHashMap.LinkedHashMapEntry<K, V> futureEntry;
TLinkedHashMap.LinkedHashMapEntry<K, V> currentEntry;
TLinkedHashMapIterator(TLinkedHashMap<K, V> 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<K, V> extends TLinkedHashMapIterator<K, V> implements TIterator<TMap.Entry<K, V>> {
EntryIterator(TLinkedHashMap<K, V> map, boolean reversed) {
super(map, reversed);
}
@Override
public TMap.Entry<K, V> next() {
makeNext();
return currentEntry;
}
}
static class KeyIterator<K, V> extends TLinkedHashMapIterator<K, V> implements TIterator<K> {
KeyIterator(TLinkedHashMap<K, V> map, boolean reversed) {
super(map, reversed);
}
@Override
public K next() {
makeNext();
return currentEntry.key;
}
}
static class ValueIterator<K, V> extends TLinkedHashMapIterator<K, V> implements TIterator<V> {
ValueIterator(TLinkedHashMap<K, V> map, boolean reversed) {
super(map, reversed);
}
@Override
public V next() {
makeNext();
return currentEntry.value;
}
}
}

View File

@ -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<K> extends TAbstractSet<K> implements TSequencedSet<K> {
private final TLinkedHashMap<K, ?> base;
private final boolean reversed;
TLinkedHashMapKeySet(TLinkedHashMap<K, ?> 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<K> 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<? super K> action) {
if (base.elementCount > 0) {
int prevModCount = base.modCount;
TLinkedHashMap.LinkedHashMapEntry<K, ?> 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<K> reversed() {
if (reversed) {
return base.sequencedKeySet();
} else {
return new TLinkedHashMapKeySet<>(base, true);
}
}
}

View File

@ -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<V> extends TAbstractCollection<V> implements TSequencedCollection<V> {
private final TLinkedHashMap<?, V> base;
private final boolean reversed;
TLinkedHashMapValues(TLinkedHashMap<?, V> 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<V> iterator() {
return new TLinkedHashMapIterator.ValueIterator<>(base, reversed);
}
@Override
public final boolean contains(Object o) {
return base.containsValue(o);
}
@Override
public final void forEach(Consumer<? super V> action) {
if (base.elementCount > 0) {
int prevModCount = base.modCount;
TLinkedHashMap.LinkedHashMapEntry<?, V> 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<?, V> e = TLinkedHashMap.checkNotNull(reversed ? base.tail : base.head);
return base.remove(e.key);
}
@Override
public final V removeLast() {
THashMap.HashEntry<?, V> e = TLinkedHashMap.checkNotNull(reversed ? base.head : base.tail);
return base.remove(e.key);
}
@Override
public TSequencedCollection<V> reversed() {
if (reversed) {
return base.sequencedValues();
} else {
return new TLinkedHashMapValues<>(base, true);
}
}
}

View File

@ -13,42 +13,26 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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; package org.teavm.classlib.java.util;
import org.teavm.classlib.java.io.TSerializable; import org.teavm.classlib.java.io.TSerializable;
import org.teavm.classlib.java.lang.TCloneable; import org.teavm.classlib.java.lang.TCloneable;
public class TLinkedHashSet<E> extends THashSet<E> implements TSet<E>, TCloneable, TSerializable { public class TLinkedHashSet<E> extends THashSet<E> implements TSequencedSet<E>, TCloneable, TSerializable {
public TLinkedHashSet() { public TLinkedHashSet() {
super(new TLinkedHashMap<E, THashSet<E>>()); super(new TLinkedHashMap<>());
} }
public TLinkedHashSet(int capacity) { public TLinkedHashSet(int capacity) {
super(new TLinkedHashMap<E, THashSet<E>>(capacity)); super(new TLinkedHashMap<>(capacity));
} }
public TLinkedHashSet(int capacity, float loadFactor) { public TLinkedHashSet(int capacity, float loadFactor) {
super(new TLinkedHashMap<E, THashSet<E>>(capacity, loadFactor)); super(new TLinkedHashMap<>(capacity, loadFactor));
} }
public TLinkedHashSet(TCollection<? extends E> collection) { public TLinkedHashSet(TCollection<? extends E> collection) {
super(new TLinkedHashMap<E, THashSet<E>>(collection.size() < 6 ? 11 : collection.size() * 2)); super(new TLinkedHashMap<>(collection.size() < 6 ? 11 : collection.size() * 2));
for (TIterator<? extends E> iter = collection.iterator(); iter.hasNext();) { for (TIterator<? extends E> iter = collection.iterator(); iter.hasNext();) {
add(iter.next()); add(iter.next());
} }
@ -59,4 +43,101 @@ public class TLinkedHashSet<E> extends THashSet<E> implements TSet<E>, TCloneabl
THashMap<E, THashSet<E>> createBackingMap(int capacity, float loadFactor) { THashMap<E, THashSet<E>> createBackingMap(int capacity, float loadFactor) {
return new TLinkedHashMap<>(capacity, loadFactor); return new TLinkedHashMap<>(capacity, loadFactor);
} }
private TLinkedHashMap<E, THashSet<E>> map() {
return (TLinkedHashMap<E, THashSet<E>>) 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<E> reversed() {
return new ReversedLinkedHashSet<>(this);
}
static class ReversedLinkedHashSet<E> extends TAbstractSet<E> implements TSequencedSet<E> {
private final TLinkedHashSet<E> base;
ReversedLinkedHashSet(TLinkedHashSet<E> base) {
this.base = base;
}
@Override
public int size() {
return base.size();
}
@Override
public TIterator<E> 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<E> reversed() {
return base;
}
}
} }

View File

@ -68,9 +68,7 @@ class TMapEntry<K, V> implements TMap.Entry<K, V>, Cloneable {
} }
if (object instanceof TMap.Entry) { if (object instanceof TMap.Entry) {
TMap.Entry<?, ?> entry = (TMap.Entry<?, ?>) object; TMap.Entry<?, ?> entry = (TMap.Entry<?, ?>) object;
return (key == null ? entry.getKey() == null : key.equals(entry.getKey())) return TObjects.equals(key, entry.getKey()) && TObjects.equals(value, entry.getValue());
&& (value == null ? entry.getValue() == null : value
.equals(entry.getValue()));
} }
return false; return false;
} }
@ -87,8 +85,7 @@ class TMapEntry<K, V> implements TMap.Entry<K, V>, Cloneable {
@Override @Override
public int hashCode() { public int hashCode() {
return (key == null ? 0 : key.hashCode()) return TObjects.hashCode(key) ^ TObjects.hashCode(value);
^ (value == null ? 0 : value.hashCode());
} }
@Override @Override

View File

@ -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<K, V> extends TAbstractMap<K, V> implements TSequencedMap<K, V> {
private final TLinkedHashMap<K, V> base;
TReversedLinkedHashMap(TLinkedHashMap<K, V> 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<? extends K, ? extends V> m) {
base.putAll(m);
}
@Override
public void clear() {
base.clear();
}
@Override
public TSet<K> keySet() {
return base.sequencedKeySet().reversed();
}
@Override
public TCollection<V> values() {
return base.sequencedValues().reversed();
}
@Override
public TSet<TMap.Entry<K, V>> entrySet() {
return base.sequencedEntrySet().reversed();
}
@Override
public V getOrDefault(Object key, V defaultValue) {
return base.getOrDefault(key, defaultValue);
}
@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
if (base.elementCount > 0) {
int prevModCount = base.modCount;
TLinkedHashMap.LinkedHashMapEntry<K, V> 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<? super K, ? super V, ? extends V> function) {
if (base.elementCount > 0) {
int prevModCount = base.modCount;
TLinkedHashMap.LinkedHashMapEntry<K, V> 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<? super K, ? extends V> mappingFunction) {
return base.computeIfAbsent(key, mappingFunction);
}
@Override
public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
return base.computeIfPresent(key, remappingFunction);
}
@Override
public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
return base.compute(key, remappingFunction);
}
@Override
public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
return base.merge(key, value, remappingFunction);
}
@Override
public TSequencedMap<K, V> reversed() {
return base;
}
@Override
public TMap.Entry<K, V> firstEntry() {
return base.lastEntry();
}
@Override
public TMap.Entry<K, V> lastEntry() {
return base.firstEntry();
}
@Override
public TMap.Entry<K, V> pollFirstEntry() {
return base.pollLastEntry();
}
@Override
public TMap.Entry<K, V> 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<K> sequencedKeySet() {
return new TLinkedHashMapKeySet<>(base, true);
}
@Override
public TSequencedCollection<V> sequencedValues() {
return new TLinkedHashMapValues<>(base, true);
}
@Override
public TSequencedSet<Entry<K, V>> sequencedEntrySet() {
return new TLinkedHashMapEntrySet<>(base, true);
}
}

View File

@ -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<K, V> extends TMap<K, V> {
TSequencedMap<K, V> reversed();
private static <K, V> TMap.Entry<K, V> clone(TMap.Entry<K, V> entry) {
return TMap.entry(entry.getKey(), entry.getValue());
}
default TMap.Entry<K, V> firstEntry() {
var it = entrySet().iterator();
return it.hasNext() ? clone(it.next()) : null;
}
default TMap.Entry<K, V> lastEntry() {
var it = reversed().entrySet().iterator();
return it.hasNext() ? clone(it.next()) : null;
}
default TMap.Entry<K, V> pollFirstEntry() {
var it = entrySet().iterator();
if (it.hasNext()) {
var entry = clone(it.next());
it.remove();
return entry;
} else {
return null;
}
}
default TMap.Entry<K, V> 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<K> sequencedKeySet();
TSequencedCollection<V> sequencedValues();
TSequencedSet<TMap.Entry<K, V>> sequencedEntrySet();
}

View File

@ -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<E> extends TSequencedCollection<E>, TSet<E> {
@Override
TSequencedSet<E> reversed();
}

View File

@ -799,9 +799,7 @@ public final class TTemplateCollections {
} }
if (object instanceof TMap.Entry) { if (object instanceof TMap.Entry) {
TMap.Entry<?, ?> entry = (TMap.Entry<?, ?>) object; TMap.Entry<?, ?> entry = (TMap.Entry<?, ?>) object;
return (key == null ? entry.getKey() == null : key.equals(entry.getKey())) return TObjects.equals(key, entry.getKey()) && TObjects.equals(value, entry.getValue());
&& (value == null ? entry.getValue() == null : value
.equals(entry.getValue()));
} }
return false; return false;
} }
@ -818,8 +816,7 @@ public final class TTemplateCollections {
@Override @Override
public int hashCode() { public int hashCode() {
return (key == null ? 0 : key.hashCode()) return TObjects.hashCode(key) ^ TObjects.hashCode(value);
^ (value == null ? 0 : value.hashCode());
} }
@Override @Override

View File

@ -70,7 +70,7 @@ class TMapEntry<K, V> implements TMap.Entry<K, V>, Cloneable {
@Override @Override
public int hashCode() { public int hashCode() {
return (key == null ? 0 : key.hashCode()) ^ (value == null ? 0 : value.hashCode()); return Objects.hashCode(key) ^ Objects.hashCode(value);
} }
@Override @Override

View File

@ -37,6 +37,7 @@ public interface TPredicate<T> {
return t -> TObjects.equals(t, targetRef); return t -> TObjects.equals(t, targetRef);
} }
@SuppressWarnings("unchecked")
static <T> TPredicate<T> not(TPredicate<? super T> target) { static <T> TPredicate<T> not(TPredicate<? super T> target) {
return (TPredicate<T>) target.negate(); return (TPredicate<T>) target.negate();
} }

View File

@ -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<String, String> ht10;
private HashMap<String, Integer> ht100;
private HashMap<String, String> htfull;
private List<String> keyList;
private List<String> 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<String, String> h = new HashMap<>();
assertEquals("Created incorrect HashMap", 0, h.size());
}
@Test
public void test_ConstructorI() {
// Test for method java.util.HashMap(int)
HashMap<String, String> h = new HashMap<>(9);
assertEquals("Created incorrect HashMap", 0, h.size());
HashMap<String, String> 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<String, String> h = new HashMap<>(10, 0.5f);
assertEquals("Created incorrect HashMap", 0, h.size());
HashMap<String, String> 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<String, Object> map = new TreeMap<>();
Object firstVal = "Gabba";
Object secondVal = 5;
map.put("Gah", firstVal);
map.put("Ooga", secondVal);
HashMap<String, Object> 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<HashMap<?, ?>, Set<HashMap<?, ?>>> 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<String, String> h = hashMapClone(htfull);
h.clear();
assertEquals("HashMap was not cleared", 0, h.size());
Iterator<String> el = h.values().iterator();
Iterator<String> 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<String, String> h = (HashMap<String, String>) htfull.clone();
assertEquals("Clone different size than original", h.size(), htfull.size());
Iterator<String> org = htfull.keySet().iterator();
Iterator<String> 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<String> 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<String, String> 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<String> 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<Map.Entry<String, String>> s = ht10.entrySet();
Set<String> s2 = new HashSet<>();
for (Map.Entry<String, String> 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<String, String> 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<String, String> 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<org.teavm.classlib.java.util.HashMapTest.ReusableKey, String> 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<String, String> 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<String, String>().isEmpty());
}
@Test
public void test_keys() {
// Test for method java.util.Enumeration java.util.HashMap.keys()
Iterator<String> 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<String, String> ht = new HashMap<>(3);
ht.put("initial", "");
Iterator<String> 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<String> s = ht10.keySet();
for (String string : keyList) {
assertTrue("Returned incorrect key set", s.contains(string));
}
Map<Integer, String> map = new HashMap<>(101);
map.put(1, "1");
map.put(102, "102");
map.put(203, "203");
Iterator<Integer> it = map.keySet().iterator();
Integer remove1 = it.next();
it.remove();
Integer remove2 = it.next();
it.remove();
ArrayList<Integer> 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<Integer, String> map2 = new HashMap<>(101);
map2.put(1, "1");
map2.put(4, "4");
Iterator<Integer> 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<String> enumeration = s.iterator();
assertTrue(enumeration.hasNext());
}
@Test
public void test_keySet_subtest0() {
Set<String> 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<String, String> 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<String> 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<String> 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<String, Integer> 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<String, String> 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<String, String> 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<HashMap<?, ?>, Set<HashMap<?, ?>>> hashMap = new HashMap<>();
Set<HashMap<?, ?>> keySet = hashMap.keySet();
hashMap.put(hashMap, keySet);
}
@Test
public void test_HashMap_selfReference_scenario2() {
HashMap<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<Serializable, Serializable> 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<String> c = ht10.values();
for (String s : elmList) {
assertTrue("Returned incorrect values", c.contains(s));
}
HashMap<Integer, Integer> myHashMap = new HashMap<>();
for (int i = 0; i < 100; i++) {
myHashMap.put(i, i);
}
Collection<Integer> 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<String, String> hashMap = new HashMap<>();
hashMap.put("my.nonexistent.prop", "AAA");
hashMap.put("parse.error", "BBB");
Iterator<Map.Entry<String, String>> iterator = hashMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, String> 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<String, String> hashMap = new HashMap<>();
String key = "key";
String value = "value";
hashMap.put(key, value);
Iterator<String> 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<String> 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<String>) 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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<String, String> 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 <K, V> HashMap<K, V> hashMapClone(HashMap<K, V> s) {
return (HashMap<K, V>) 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;
}
}
}

View File

@ -32,6 +32,7 @@
*/ */
package org.teavm.classlib.java.util; package org.teavm.classlib.java.util;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotEquals;
@ -43,9 +44,16 @@ import static org.junit.Assert.fail;
import java.util.AbstractMap; import java.util.AbstractMap;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.SequencedMap;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.teavm.classlib.support.MapTest2Support; import org.teavm.classlib.support.MapTest2Support;
@ -529,6 +537,137 @@ public class LinkedHashMapTest {
lhm.put("B", "C"); lhm.put("B", "C");
assertEquals("{A=(this Map), B=C}", lhm.toString()); assertEquals("{A=(this Map), B=C}", lhm.toString());
assertEquals("{B=C, A={A=(this Map), B=C}}", lhm.reversed().toString());
assertEquals("{}", new LinkedHashMap<>().toString()); assertEquals("{}", new LinkedHashMap<>().toString());
} }
private static final List<Integer> BASE_LIST = Arrays.asList(1, 6, 2, 5, 3, 4);
private SequencedMap<Integer, String> generateMap() {
return BASE_LIST.stream().collect(Collectors.toMap(Function.identity(), i -> Integer.toString(i),
(a, b) -> a, LinkedHashMap::new));
}
private SequencedMap<Integer, String> 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<Integer, String> 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<Integer, String> map = generateMap();
Iterator<Integer> 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<String> 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
}
}
} }