]> git.ipfire.org Git - thirdparty/strongswan.git/commitdiff
android: Add utility class to determine differences in two lists of objects
authorMarkus Pfeiffer <markus.pfeiffer@relution.io>
Tue, 21 Nov 2023 14:37:24 +0000 (15:37 +0100)
committerTobias Brunner <tobias@strongswan.org>
Wed, 21 Feb 2024 11:24:53 +0000 (12:24 +0100)
This allows determining the difference between two lists in the form of
inserts, updates and deletes (and unchanged elements).

src/frontends/android/app/build.gradle
src/frontends/android/app/src/main/java/org/strongswan/android/utils/Difference.java [new file with mode: 0644]
src/frontends/android/app/src/test/java/org/strongswan/android/utils/DifferenceTest.java [new file with mode: 0644]

index 2371347716fc3a8fb4f8edd4e4995b0795e5ff22..43135e444bece68db38468f9717e7b76cf5d9bba 100644 (file)
@@ -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 (file)
index 0000000..e3d2fe9
--- /dev/null
@@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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<T>
+{
+       @NonNull
+       private final List<T> inserts;
+       @NonNull
+       private final List<Pair<T, T>> updates;
+       @NonNull
+       private final List<T> unchanged;
+       @NonNull
+       private final List<T> deletes;
+
+       @NonNull
+       public static <K, V> Difference<V> between(
+               @NonNull final List<V> existing,
+               @NonNull final List<V> modified,
+               @NonNull final Function<V, K> getKey)
+       {
+               final Map<K, V> existingMap = mapOf(existing, getKey);
+               final Map<K, V> modifiedMap = mapOf(modified, getKey);
+
+               final List<V> inserts = notIn(existingMap, getKey, modified);
+               final List<V> deletes = notIn(modifiedMap, getKey, existing);
+               final List<Pair<V, V>> updates = new ArrayList<>(modifiedMap.size());
+               final List<V> unchanged = new ArrayList<>(existingMap.size());
+               changeBetween(existingMap, modifiedMap, updates, unchanged);
+
+               return new Difference<>(inserts, updates, unchanged, deletes);
+       }
+
+       @NonNull
+       private static <K, V> Map<K, V> mapOf(
+               @NonNull final List<V> list,
+               @NonNull final Function<V, K> getKey)
+       {
+               final Map<K, V> 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 <K, V> List<V> notIn(
+               @NonNull final Map<K, V> map,
+               @NonNull final Function<V, K> getKey,
+               @NonNull final List<V> list)
+       {
+               final List<V> 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 <K, V> void changeBetween(
+               @NonNull final Map<K, V> existingMap,
+               @NonNull final Map<K, V> modifiedMap,
+               @NonNull List<Pair<V,V>> updates,
+               @NonNull List<V> unchanged)
+       {
+               for (final Map.Entry<K, V> entry : modifiedMap.entrySet())
+               {
+                       final V existingValue = existingMap.get(entry.getKey());
+                       final V modifiedValue = entry.getValue();
+
+                       if (existingValue != null && !Objects.equals(existingValue, modifiedValue))
+                       {
+                               final Pair<V, V> change = Pair.create(existingValue, modifiedValue);
+                               updates.add(change);
+                       }
+                       else if (existingValue != null)
+                       {
+                               unchanged.add(existingValue);
+                       }
+               }
+
+       }
+
+       public Difference(
+               @NonNull List<T> inserts,
+               @NonNull List<Pair<T, T>> updates,
+               @NonNull List<T> unchanged,
+               @NonNull List<T> deletes)
+       {
+               this.inserts = inserts;
+               this.updates = updates;
+               this.unchanged = unchanged;
+               this.deletes = deletes;
+       }
+
+       @NonNull
+       public List<T> getInserts()
+       {
+               return inserts;
+       }
+
+       @NonNull
+       public List<Pair<T, T>> getUpdates()
+       {
+               return updates;
+       }
+
+       @NonNull
+       public List<T> getUnchanged()
+       {
+               return unchanged;
+       }
+
+       @NonNull
+       public List<T> 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 (file)
index 0000000..01fcd71
--- /dev/null
@@ -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 <http://www.fsf.org/copyleft/gpl.txt>.
+ *
+ * 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<Element> existing = List.of();
+               final List<Element> modified = List.of(element);
+
+               final Difference<Element> 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<Element> existing = List.of(element);
+               final List<Element> modified = List.of();
+
+               final Difference<Element> 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<Element> existing = List.of(element0);
+               final List<Element> modified = List.of(element1);
+
+               final Difference<Element> 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<Element> existing = List.of(elementA);
+               final List<Element> modified = List.of(elementB);
+
+               final Difference<Element> 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<Element> existing = List.of(elementA0);
+               final List<Element> modified = List.of(elementA1);
+
+               final Difference<Element> 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);
+               }
+       }
+}