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).
This commit is contained in:
committed by
Tobias Brunner
parent
97cb35afe5
commit
9cb23f650a
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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 + "}";
|
||||
}
|
||||
}
|
||||
+151
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user