classlib: add the copyOf method to List, Set and Map interfaces (#708)

The copyOf static method was added in Java 10 to the List, Set and Map interfaces. Since no additions were made since Java 10 this commit brings the List, Set and Map interfaces to 100% completion for the latest LTS (Java 17) at the time of writing.
This commit is contained in:
Jasper Siepkes 2023-06-07 10:13:20 +02:00 committed by GitHub
parent d209a5f02e
commit a409763f76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 151 additions and 5 deletions

View File

@ -155,4 +155,8 @@ public interface TList<E> extends TCollection<E> {
} }
return new TTemplateCollections.ImmutableArrayList<>(elements.clone()); return new TTemplateCollections.ImmutableArrayList<>(elements.clone());
} }
static <E> TList<E> copyOf(TCollection<? extends E> collection) {
return new TTemplateCollections.ImmutableArrayList<>(collection);
}
} }

View File

@ -294,4 +294,13 @@ public interface TMap<K, V> {
static <K, V> TMap.Entry<K, V> entry(K k, V v) { static <K, V> TMap.Entry<K, V> entry(K k, V v) {
return new TTemplateCollections.ImmutableEntry<>(requireNonNull(k), requireNonNull(v)); return new TTemplateCollections.ImmutableEntry<>(requireNonNull(k), requireNonNull(v));
} }
@SuppressWarnings("unchecked")
static <K, V> TMap<K, V> copyOf(TMap<? extends K, ? extends V> map) {
if (map instanceof TTemplateCollections.NEtriesMap) {
return (TTemplateCollections.NEtriesMap<K, V>) map;
} else {
return new TTemplateCollections.NEtriesMap<>(map);
}
}
} }

View File

@ -73,4 +73,8 @@ public interface TSet<E> extends TCollection<E> {
static <E> TSet<E> of(E... elements) { static <E> TSet<E> of(E... elements) {
return new TTemplateCollections.NElementSet<>(elements); return new TTemplateCollections.NElementSet<>(elements);
} }
static <E> TSet<E> copyOf(TCollection<E> collection) {
return new TTemplateCollections.NElementSet<>(collection);
}
} }

View File

@ -36,6 +36,23 @@ public final class TTemplateCollections {
this.list = list; this.list = list;
} }
@SuppressWarnings("unchecked")
public ImmutableArrayList(TCollection<? extends T> collection) {
T[] list = (T[]) new Object[collection.size()];
TIterator<? extends T> iter = collection.iterator();
int index = 0;
while (iter.hasNext()) {
T element = iter.next();
if (element == null) {
throw new NullPointerException();
}
list[index++] = element;
}
this.list = list;
}
@Override @Override
public T get(int index) { public T get(int index) {
return list[index]; return list[index];
@ -193,6 +210,9 @@ public final class TTemplateCollections {
static class NElementSet<T> extends AbstractImmutableSet<T> { static class NElementSet<T> extends AbstractImmutableSet<T> {
private T[] data; private T[] data;
/**
* Throws an exception on duplicate elements.
*/
@SafeVarargs @SafeVarargs
NElementSet(T... data) { NElementSet(T... data) {
T[] table = data.clone(); T[] table = data.clone();
@ -232,6 +252,42 @@ public final class TTemplateCollections {
this.data = table; this.data = table;
} }
/**
* Duplicate elements will be ignored (does NOT throw an exception).
*/
@SuppressWarnings("unchecked")
NElementSet(TCollection<T> collection) {
T[] temp = (T[]) new Object[collection.size()];
int index = 0;
outerLoop:
for (T element : (T[]) collection.toArray()) {
if (element == null) {
throw new NullPointerException(String.format("Element at index %s is null", index));
}
int indexTemp = Math.abs(element.hashCode()) % temp.length;
while (temp[indexTemp] != null) {
if (temp[indexTemp].equals(element)) {
continue outerLoop;
}
indexTemp = (indexTemp + 1) % temp.length;
}
temp[indexTemp] = element;
index++;
}
T[] result = (T[]) new Object[index];
index = 0;
for (T element : temp) {
if (element != null) {
result[index++] = element;
}
}
this.data = result;
}
@Override @Override
public TIterator<T> iterator() { public TIterator<T> iterator() {
return new TIterator<T>() { return new TIterator<T>() {
@ -416,17 +472,31 @@ public final class TTemplateCollections {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@SafeVarargs @SafeVarargs
NEtriesMap(Entry<K, V>... data) { NEtriesMap(Entry<K, V>... data) {
Entry<K, V>[] table = new Entry[data.length]; this.data = toEntryArray(data);
}
@SuppressWarnings("unchecked")
NEtriesMap(TMap<? extends K, ? extends V> map) {
this.data = toEntryArray(map.entrySet().toArray(new TMap.Entry[0]));
}
/**
* Creates an array where the {@code Entry} elements are positioned in such a way they
* are compatible with the contract of {@link java.util.Map}.
*/
@SuppressWarnings("unchecked")
private Entry<K, V>[] toEntryArray(Entry<K, V>[] entries) {
Entry<K, V>[] table = new Entry[entries.length];
Arrays.fill(table, null); Arrays.fill(table, null);
for (Entry<K, V> entry : data) { for (Entry<K, V> entry : entries) {
Objects.requireNonNull(entry.getKey()); Objects.requireNonNull(entry.getKey());
Objects.requireNonNull(entry.getValue()); Objects.requireNonNull(entry.getValue());
int suggestedIndex = Math.abs(entry.getKey().hashCode()) % data.length; int suggestedIndex = Math.abs(entry.getKey().hashCode()) % entries.length;
int index = suggestedIndex; int index = suggestedIndex;
boolean found = false; boolean found = false;
while (index < data.length) { while (index < entries.length) {
Entry<K, V> existingEntry = table[index]; Entry<K, V> existingEntry = table[index];
if (existingEntry == null) { if (existingEntry == null) {
found = true; found = true;
@ -451,7 +521,7 @@ public final class TTemplateCollections {
table[index] = new ImmutableEntry<>(entry.getKey(), entry.getValue()); table[index] = new ImmutableEntry<>(entry.getKey(), entry.getValue());
} }
this.data = table; return table;
} }
@Override @Override

View File

@ -19,6 +19,8 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
@ -48,6 +50,24 @@ public class ListTest {
List.of("q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "a")); List.of("q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "a"));
} }
@Test
public void copyOfWorks() {
testOf(new String[0], List.copyOf(new ArrayList<>()));
testOf(new String[] { "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "a" },
List.copyOf(Arrays.asList("q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "a")));
try {
// copyOf() must throw a NullPointerException on any 'null' element.
List<String> listWithNull = new ArrayList<>(1);
listWithNull.add(null);
List.copyOf(listWithNull);
fail("Expected NullPointerException");
} catch (NullPointerException e) {
// ok
}
}
private void testOf(String[] expected, List<String> actual) { private void testOf(String[] expected, List<String> actual) {
if (actual.size() != expected.length) { if (actual.size() != expected.length) {
fail("Expected size is " + expected.length + ", actual size is " + actual.size()); fail("Expected size is " + expected.length + ", actual size is " + actual.size());

View File

@ -18,6 +18,7 @@ package org.teavm.classlib.java.util;
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.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import java.util.HashMap; import java.util.HashMap;
@ -54,6 +55,24 @@ public class MapTest {
Map.entry("p", 9), Map.entry("a", 10))); Map.entry("p", 9), Map.entry("a", 10)));
} }
@Test
public void copyOfWorks() {
testOf(new String[0], Map.copyOf(new HashMap<>()));
testOf(new String[] { "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "a" },
Map.copyOf(
Map.ofEntries(Map.entry("q", 0), Map.entry("w", 1), Map.entry("e", 2), Map.entry("r", 3),
Map.entry("t", 4), Map.entry("y", 5), Map.entry("u", 6), Map.entry("i", 7), Map.entry("o", 8),
Map.entry("p", 9), Map.entry("a", 10))));
}
@Test
public void copyOfOptimized() {
Map<String, Integer> mapCopy1 = Map.copyOf(Map.of("q", 0, "w", 1, "e", 2));
Map<String, Integer> mapCopy2 = Map.copyOf(mapCopy1);
assertSame("Must not create copies of immutable collections", mapCopy1, mapCopy2);
}
private void testOf(String[] expected, Map<String, Integer> actual) { private void testOf(String[] expected, Map<String, Integer> actual) {
if (actual.size() != expected.length) { if (actual.size() != expected.length) {
fail("Expected size is " + expected.length + ", actual size is " + actual.size()); fail("Expected size is " + expected.length + ", actual size is " + actual.size());

View File

@ -20,6 +20,8 @@ import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.Set; import java.util.Set;
import org.junit.Test; import org.junit.Test;
@ -61,6 +63,24 @@ public class SetTest {
expectIAE(() -> Set.of("q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "q")); expectIAE(() -> Set.of("q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "q"));
} }
@Test
public void copyOfWorks() {
testOf(new String[0], Set.copyOf(new HashSet<>()));
testOf(new String[] { "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "a" },
Set.copyOf(Arrays.asList("q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "a")));
// Duplicates must be silently removed by copyOf(). Unlike of() where they throw an exception.
testOf(new String[] { "q", "e", "r", "u", "i", "o", "p" },
Set.copyOf(Arrays.asList("q", "q", "e", "r", "q", "q", "u", "i", "o", "p", "q")));
try {
// copyOf() must throw a NullPointerException on any 'null' element.
Set.copyOf(Arrays.asList("q", "q", "e", "r", "q", "q", "u", "i", "o", "p", "q", null));
fail("Expected NullPointerException");
} catch (NullPointerException e) {
// ok
}
}
private void expectIAE(Runnable r) { private void expectIAE(Runnable r) {
try { try {
r.run(); r.run();