From: Markus Pfeiffer Date: Tue, 21 Nov 2023 14:37:24 +0000 (+0100) Subject: android: Add utility class to determine differences in two lists of objects X-Git-Tag: android-2.5.0^2~5 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9cb23f650a6587f5632971e96bde9b448f8fc709;p=thirdparty%2Fstrongswan.git android: Add utility class to determine differences in two lists of objects This allows determining the difference between two lists in the form of inserts, updates and deletes (and unchanged elements). --- diff --git a/src/frontends/android/app/build.gradle b/src/frontends/android/app/build.gradle index 2371347716..43135e444b 100644 --- a/src/frontends/android/app/build.gradle +++ b/src/frontends/android/app/build.gradle @@ -49,5 +49,6 @@ dependencies { implementation 'androidx.preference:preference:1.2.1' implementation 'com.google.android.material:material:1.10.0' testImplementation 'junit:junit:4.13.2' + testImplementation 'org.assertj:assertj-core:3.24.2' testImplementation 'org.mockito:mockito-core:5.8.0' } diff --git a/src/frontends/android/app/src/main/java/org/strongswan/android/utils/Difference.java b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/Difference.java new file mode 100644 index 0000000000..e3d2fe9908 --- /dev/null +++ b/src/frontends/android/app/src/main/java/org/strongswan/android/utils/Difference.java @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2023 Relution GmbH + * + * Copyright (C) secunet Security Networks AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +package org.strongswan.android.utils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import androidx.annotation.NonNull; +import androidx.arch.core.util.Function; +import androidx.core.util.Pair; + +public class Difference +{ + @NonNull + private final List inserts; + @NonNull + private final List> updates; + @NonNull + private final List unchanged; + @NonNull + private final List deletes; + + @NonNull + public static Difference between( + @NonNull final List existing, + @NonNull final List modified, + @NonNull final Function getKey) + { + final Map existingMap = mapOf(existing, getKey); + final Map modifiedMap = mapOf(modified, getKey); + + final List inserts = notIn(existingMap, getKey, modified); + final List deletes = notIn(modifiedMap, getKey, existing); + final List> updates = new ArrayList<>(modifiedMap.size()); + final List unchanged = new ArrayList<>(existingMap.size()); + changeBetween(existingMap, modifiedMap, updates, unchanged); + + return new Difference<>(inserts, updates, unchanged, deletes); + } + + @NonNull + private static Map mapOf( + @NonNull final List list, + @NonNull final Function getKey) + { + final Map map = new HashMap<>(list.size()); + + for (final V entry : list) + { + final K key = getKey.apply(entry); + map.put(key, entry); + } + + return map; + } + + @NonNull + private static List notIn( + @NonNull final Map map, + @NonNull final Function getKey, + @NonNull final List list) + { + final List filtered = new ArrayList<>(list.size()); + + for (final V value : list) + { + final K key = getKey.apply(value); + if (!map.containsKey(key)) + { + filtered.add(value); + } + } + + return filtered; + } + + @NonNull + private static void changeBetween( + @NonNull final Map existingMap, + @NonNull final Map modifiedMap, + @NonNull List> updates, + @NonNull List unchanged) + { + for (final Map.Entry entry : modifiedMap.entrySet()) + { + final V existingValue = existingMap.get(entry.getKey()); + final V modifiedValue = entry.getValue(); + + if (existingValue != null && !Objects.equals(existingValue, modifiedValue)) + { + final Pair change = Pair.create(existingValue, modifiedValue); + updates.add(change); + } + else if (existingValue != null) + { + unchanged.add(existingValue); + } + } + + } + + public Difference( + @NonNull List inserts, + @NonNull List> updates, + @NonNull List unchanged, + @NonNull List deletes) + { + this.inserts = inserts; + this.updates = updates; + this.unchanged = unchanged; + this.deletes = deletes; + } + + @NonNull + public List getInserts() + { + return inserts; + } + + @NonNull + public List> getUpdates() + { + return updates; + } + + @NonNull + public List getUnchanged() + { + return unchanged; + } + + @NonNull + public List getDeletes() + { + return deletes; + } + + public boolean isEmpty() + { + return inserts.isEmpty() && updates.isEmpty() && deletes.isEmpty(); + } + + @NonNull + @Override + public String toString() + { + return "Difference {" + inserts + ", " + updates + ", " + deletes + "}"; + } +} diff --git a/src/frontends/android/app/src/test/java/org/strongswan/android/utils/DifferenceTest.java b/src/frontends/android/app/src/test/java/org/strongswan/android/utils/DifferenceTest.java new file mode 100644 index 0000000000..01fcd719eb --- /dev/null +++ b/src/frontends/android/app/src/test/java/org/strongswan/android/utils/DifferenceTest.java @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2023 Relution GmbH + * + * Copyright (C) secunet Security Networks AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +package org.strongswan.android.utils; + + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; + +import java.util.List; +import java.util.Objects; + +import androidx.core.util.Pair; + +public class DifferenceTest +{ + @Test + public void testElementAdded() + { + final Element element = new Element("a", 0); + final List existing = List.of(); + final List modified = List.of(element); + + final Difference diff = Difference.between(existing, modified, Element::getKey); + + assertThat(diff.getInserts()).containsExactly(element); + assertThat(diff.getUpdates()).isEmpty(); + assertThat(diff.getUnchanged()).isEmpty(); + assertThat(diff.getDeletes()).isEmpty(); + } + + @Test + public void testElementRemoved() + { + final Element element = new Element("a", 0); + final List existing = List.of(element); + final List modified = List.of(); + + final Difference diff = Difference.between(existing, modified, Element::getKey); + + assertThat(diff.getInserts()).isEmpty(); + assertThat(diff.getUpdates()).isEmpty(); + assertThat(diff.getUnchanged()).isEmpty(); + assertThat(diff.getDeletes()).containsExactly(element); + } + + @Test + public void testElementIdentical() + { + final Element element0 = new Element("a", 0); + final Element element1 = new Element("a", 0); + final List existing = List.of(element0); + final List modified = List.of(element1); + + final Difference diff = Difference.between(existing, modified, Element::getKey); + + assertThat(diff.getInserts()).isEmpty(); + assertThat(diff.getUpdates()).isEmpty(); + assertThat(diff.getUnchanged()).containsExactly(element0); + assertThat(diff.getDeletes()).isEmpty(); + } + + @Test + public void testElementSwap() + { + final Element elementA = new Element("a", 0); + final Element elementB = new Element("b", 0); + final List existing = List.of(elementA); + final List modified = List.of(elementB); + + final Difference diff = Difference.between(existing, modified, Element::getKey); + + assertThat(diff.getInserts()).containsExactly(elementB); + assertThat(diff.getUpdates()).isEmpty(); + assertThat(diff.getUnchanged()).isEmpty(); + assertThat(diff.getDeletes()).containsExactly(elementA); + } + + @Test + public void testElementUpdate() + { + final Element elementA0 = new Element("a", 0); + final Element elementA1 = new Element("a", 1); + final List existing = List.of(elementA0); + final List modified = List.of(elementA1); + + final Difference diff = Difference.between(existing, modified, Element::getKey); + + assertThat(diff.getInserts()).isEmpty(); + assertThat(diff.getUpdates()).containsExactly(Pair.create(elementA0, elementA1)); + assertThat(diff.getUnchanged()).isEmpty(); + assertThat(diff.getDeletes()).isEmpty(); + } + + private static class Element + { + private final String key; + private final int value; + + public Element(final String key, final int value) + { + this.key = key; + this.value = value; + } + + public String getKey() + { + return key; + } + + public int getValue() + { + return value; + } + + @Override + public boolean equals(Object o) + { + if (this == o) + { + return true; + } + if (o == null || getClass() != o.getClass()) + { + return false; + } + Element element = (Element)o; + return value == element.value && Objects.equals(key, element.key); + } + + @Override + public int hashCode() + { + return Objects.hash(key, value); + } + } +}