From 43bb172e82ccd4267a611c864bce458334fe7b30 Mon Sep 17 00:00:00 2001 From: Chalard Jean Date: Mon, 14 Nov 2022 18:18:20 +0900 Subject: [PATCH] Add useful utils to CollectionUtils Test: new tests in this patch Change-Id: I9cca999f9235d4d6508533230afcd8ee0c1c54c1 --- .../net/module/util/CollectionUtils.java | 99 +++++++++++++++++++ .../net/module/util/CollectionUtilsTest.kt | 49 +++++++++ 2 files changed, 148 insertions(+) diff --git a/staticlibs/framework/com/android/net/module/util/CollectionUtils.java b/staticlibs/framework/com/android/net/module/util/CollectionUtils.java index 7cac90d598..f08880cbec 100644 --- a/staticlibs/framework/com/android/net/module/util/CollectionUtils.java +++ b/staticlibs/framework/com/android/net/module/util/CollectionUtils.java @@ -18,12 +18,15 @@ package com.android.net.module.util; import android.annotation.NonNull; import android.annotation.Nullable; +import android.util.ArrayMap; +import android.util.Pair; import android.util.SparseArray; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.function.Function; import java.util.function.Predicate; /** @@ -291,4 +294,100 @@ public final class CollectionUtils { @NonNull final Predicate condition) { return -1 != indexOf(haystack, condition); } + + /** + * Standard map function, but returns a new modifiable ArrayList + * + * This returns a new list that contains, for each element of the source collection, its + * image through the passed transform. + * Elements in the source can be null if the transform accepts null inputs. + * Elements in the output can be null if the transform ever returns null. + * This function never returns null. If the source collection is empty, it returns the + * empty list. + * Contract : this method calls the transform function exactly once for each element in the + * list, in iteration order. + * + * @param source the source collection + * @param transform the function to transform the elements + * @param type of source elements + * @param type of destination elements + * @return an unmodifiable list of transformed elements + */ + @NonNull + public static ArrayList map(@NonNull final Collection source, + @NonNull final Function transform) { + final ArrayList dest = new ArrayList<>(source.size()); + for (final T e : source) { + dest.add(transform.apply(e)); + } + return dest; + } + + /** + * Standard zip function, but returns a new modifiable ArrayList + * + * This returns a list of pairs containing, at each position, a pair of the element from the + * first list at that index and the element from the second list at that index. + * Both lists must be the same size. They may contain null. + * + * The easiest way to visualize what's happening is to think of two lists being laid out next + * to each other and stitched together with a zipper. + * + * Contract : this method will read each element of each list exactly once, in some unspecified + * order. If it throws, it will not read any element. + * + * @param first the first list of elements + * @param second the second list of elements + * @param the type of first elements + * @param the type of second elements + * @return the zipped list + */ + @NonNull + public static ArrayList> zip(@NonNull final List first, + @NonNull final List second) { + final int size = first.size(); + if (size != second.size()) { + throw new IllegalArgumentException("zip : collections must be the same size"); + } + final ArrayList> dest = new ArrayList<>(size); + for (int i = 0; i < size; ++i) { + dest.add(new Pair<>(first.get(i), second.get(i))); + } + return dest; + } + + /** + * Returns a new ArrayMap that associates each key with the value at the same index. + * + * Both lists must be the same size. + * Both keys and values may contain null. + * Keys may not contain the same value twice. + * + * Contract : this method will read each element of each list exactly once, but does not + * specify the order, except if it throws in which case the number of reads is undefined. + * + * @param keys The list of keys + * @param values The list of values + * @param The type of keys + * @param The type of values + * @return The associated map + */ + @NonNull + public static ArrayMap assoc( + @NonNull final List keys, @NonNull final List values) { + final int size = keys.size(); + if (size != values.size()) { + throw new IllegalArgumentException("assoc : collections must be the same size"); + } + final ArrayMap dest = new ArrayMap<>(size); + for (int i = 0; i < size; ++i) { + final T key = keys.get(i); + if (dest.containsKey(key)) { + throw new IllegalArgumentException( + "assoc : keys may not contain the same value twice"); + } + dest.put(key, values.get(i)); + } + return dest; + } } diff --git a/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt b/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt index 0f00d0b57b..9fb025b353 100644 --- a/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt +++ b/staticlibs/tests/unit/src/com/android/net/module/util/CollectionUtilsTest.kt @@ -22,6 +22,7 @@ import com.android.testutils.assertThrows import org.junit.Test import org.junit.runner.RunWith import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.assertFalse import kotlin.test.assertNull import kotlin.test.assertSame @@ -133,4 +134,52 @@ class CollectionUtilsTest { assertSame(CollectionUtils.findLast(listMulti) { it == "E" }, listMulti[7]) assertNull(CollectionUtils.findLast(listMulti) { it == "F" }) } + + @Test + fun testMap() { + val listAE = listOf("A", "B", "C", "D", "E", null) + assertEquals(listAE.map { "-$it-" }, CollectionUtils.map(listAE) { "-$it-" }) + } + + @Test + fun testZip() { + val listAE = listOf("A", "B", "C", "D", "E") + val list15 = listOf(1, 2, 3, 4, 5) + // Normal #zip returns kotlin.Pair, not android.util.Pair + assertEquals(list15.zip(listAE).map { android.util.Pair(it.first, it.second) }, + CollectionUtils.zip(list15, listAE)) + val listNull = listOf("A", null, "B", "C", "D") + assertEquals(list15.zip(listNull).map { android.util.Pair(it.first, it.second) }, + CollectionUtils.zip(list15, listNull)) + assertEquals(emptyList>(), + CollectionUtils.zip(emptyList(), emptyList())) + assertFailsWith { + // Different size + CollectionUtils.zip(listOf(1, 2), list15) + } + } + + @Test + fun testAssoc() { + val listADA = listOf("A", "B", "C", "D", "A") + val list15 = listOf(1, 2, 3, 4, 5) + assertEquals(list15.zip(listADA).toMap(), CollectionUtils.assoc(list15, listADA)) + + // Null key is fine + val assoc = CollectionUtils.assoc(listOf(1, 2, null), listOf("A", "B", "C")) + assertEquals("C", assoc[null]) + + assertFailsWith { + // Same key multiple times + CollectionUtils.assoc(listOf("A", "B", "A"), listOf(1, 2, 3)) + } + assertFailsWith { + // Same key multiple times, but it's null + CollectionUtils.assoc(listOf(null, "B", null), listOf(1, 2, 3)) + } + assertFailsWith { + // Different size + CollectionUtils.assoc(listOf(1, 2), list15) + } + } }