diff --git a/staticlibs/src_frameworkcommon/android/net/util/LinkPropertiesUtils.java b/staticlibs/src_frameworkcommon/android/net/util/LinkPropertiesUtils.java index 59d88accc8..c50ca0b650 100644 --- a/staticlibs/src_frameworkcommon/android/net/util/LinkPropertiesUtils.java +++ b/staticlibs/src_frameworkcommon/android/net/util/LinkPropertiesUtils.java @@ -26,8 +26,10 @@ import android.text.TextUtils; import java.net.InetAddress; import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Objects; +import java.util.function.Function; /** * Collection of link properties utilities. @@ -64,6 +66,60 @@ public final class LinkPropertiesUtils { } } + /** + * Generic class to compare two lists of items of type {@code T} whose properties can change. + * The items to be compared must provide a way to calculate a corresponding key of type + * {@code K} such that if (and only if) an old and a new item have the same key, then the new + * item is an update of the old item. Both the old list and the new list may not contain more + * than one item with the same key, and may not contain any null items. + * + * @param A class that represents the key of the items to be compared. + * @param The class that represents the object to be compared. + */ + public static class CompareOrUpdateResult { + public final List added = new ArrayList<>(); + public final List removed = new ArrayList<>(); + public final List updated = new ArrayList<>(); + + /** + * Compares two lists of items. + * @param oldItems the old list of items. + * @param newItems the new list of items. + * @param keyCalculator a {@link Function} that calculates an item's key. + */ + public CompareOrUpdateResult(Collection oldItems, Collection newItems, + Function keyCalculator) { + HashMap updateTracker = new HashMap<>(); + + for (T oldItem : oldItems) { + updateTracker.put(keyCalculator.apply(oldItem), oldItem); + } + + for (T newItem : newItems) { + T oldItem = updateTracker.remove(keyCalculator.apply(newItem)); + if (oldItem != null) { + if (!oldItem.equals(newItem)) { + // Update of existing item. + updated.add(newItem); + } + } else { + // New item. + added.add(newItem); + } + } + + removed.addAll(updateTracker.values()); + } + + @Override + public String toString() { + return "removed=[" + TextUtils.join(",", removed) + + "] added=[" + TextUtils.join(",", added) + + "] updated=[" + TextUtils.join(",", updated) + + "]"; + } + } + /** * Compares the addresses in {@code left} LinkProperties with {@code right} * LinkProperties, examining only addresses on the base link. diff --git a/staticlibs/tests/unit/src/android/net/util/LinkPropertiesUtilsTest.java b/staticlibs/tests/unit/src/android/net/util/LinkPropertiesUtilsTest.java index 6e06ee877b..e2cde34a98 100644 --- a/staticlibs/tests/unit/src/android/net/util/LinkPropertiesUtilsTest.java +++ b/staticlibs/tests/unit/src/android/net/util/LinkPropertiesUtilsTest.java @@ -26,6 +26,7 @@ import android.net.LinkAddress; import android.net.LinkProperties; import android.net.ProxyInfo; import android.net.RouteInfo; +import android.net.util.LinkPropertiesUtils.CompareOrUpdateResult; import android.net.util.LinkPropertiesUtils.CompareResult; import androidx.test.runner.AndroidJUnit4; @@ -34,6 +35,11 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.net.InetAddress; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.function.Function; @RunWith(AndroidJUnit4.class) public final class LinkPropertiesUtilsTest { @@ -185,4 +191,58 @@ public final class LinkPropertiesUtilsTest { assertEquals(linkAddr1, results.removed.get(0)); assertEquals(linkAddr2, results.added.get(0)); } -} + + private void assertSameElements(List expected, List actual) { + HashSet expectedSet = new HashSet(expected); + assertEquals("expected list contains duplicates", expectedSet.size(), expected.size()); + HashSet actualSet = new HashSet(actual); + assertEquals("actual list contains duplicates", actualSet.size(), actual.size()); + assertEquals(expectedSet, actualSet); + } + + private void assertCompareOrUpdateResult(CompareOrUpdateResult result, + List expectedAdded, List expectedRemoved, + List expectedUpdated) { + assertSameElements(expectedAdded, result.added); + assertSameElements(expectedRemoved, result.removed); + assertSameElements(expectedUpdated, result.updated); + } + + private List strArray(String... strs) { + return Arrays.asList(strs); + } + + @Test + public void testCompareOrUpdateResult() { + // As the item type, use a simple string. An item is defined to be an update of another item + // if the string starts with the same alphabetical characters. + // Extracting the key from the object is just a regexp. + Function extractPrefix = (s) -> s.replaceFirst("^([a-z]+).*", "$1"); + assertEquals("goodbye", extractPrefix.apply("goodbye1234")); + + List oldItems = strArray("hello123", "goodbye5678", "howareyou669"); + List newItems = strArray("hello123", "goodbye000", "verywell"); + + final List emptyList = new ArrayList<>(); + + // Items -> empty: everything removed. + CompareOrUpdateResult result = + new CompareOrUpdateResult(oldItems, emptyList, extractPrefix); + assertCompareOrUpdateResult(result, + emptyList, strArray("hello123", "howareyou669", "goodbye5678"), emptyList); + + // Empty -> items: everything added. + result = new CompareOrUpdateResult(emptyList, newItems, extractPrefix); + assertCompareOrUpdateResult(result, + strArray("hello123", "goodbye000", "verywell"), emptyList, emptyList); + + // Empty -> empty: no change. + result = new CompareOrUpdateResult(newItems, newItems, extractPrefix); + assertCompareOrUpdateResult(result, emptyList, emptyList, emptyList); + + // Added, removed, updated at the same time. + result = new CompareOrUpdateResult<>(oldItems, newItems, extractPrefix); + assertCompareOrUpdateResult(result, + strArray("verywell"), strArray("howareyou669"), strArray("goodbye000")); + } +} \ No newline at end of file