DiffCommandsCalculator { public List<AdapterCommand> diff(@NonNull List<T> newList) { if (newList == null) { throw new NullPointerException("newList == null"); } int newSize = newList.size(); if (oldList == null) { oldList = new ArrayList<>(); oldList.addAll(newList); List<AdapterCommand> commands = new ArrayList<>(1); if (newSize == 0 || !itemRangeInsertedOnFirstDiff) { commands.add(new EntireDataSetChangedCommand()); } else { commands.add(new ItemRangeInsertedCommand(0, newSize)); } return commands; } if (newList.isEmpty()) { if (oldList.isEmpty()){ return Collections.emptyList(); } List<AdapterCommand> commands = new ArrayList<>(1); commands.add(new ItemRangeRemovedCommand(0, oldList.size())); oldList.clear(); return commands; } List<AdapterCommand> commands = new ArrayList<>(newSize); int M = oldList.size(); int N = newList.size(); int[][] opt = new int[M + 1][N + 1]; for (int i = M - 1; i >= 0; i--) { for (int j = N - 1; j >= 0; j--) { if (oldList.get(i).equals(newList.get(j))) { opt[i][j] = opt[i + 1][j + 1] + 1; } else { opt[i][j] = Math.max(opt[i + 1][j], opt[i][j + 1]); } } } int insertRemoveOffset = 0; int i = 0, j = 0; while (i < M && j < N) { T oldItem = oldList.get(i); T newItem = newList.get(j); if (oldItem.equals(newItem)) { if (detector != null && detector.hasChanged(oldItem, newItem)) { commands.add(new ItemChangedCommand(j)); } i++; j++; } else if (opt[i + 1][j] >= opt[i][j + 1]) { commands.add(new ItemRemovedCommand(i + insertRemoveOffset)); insertRemoveOffset--; i++; } else { commands.add(new ItemInsertedCommand(j)); insertRemoveOffset++; j++; } } while (i < M || j < N) { if (i == M) { commands.add(new ItemInsertedCommand(j)); insertRemoveOffset++; j++; } else if (j == N) { commands.add(new ItemRemovedCommand(i + insertRemoveOffset)); insertRemoveOffset--; i++; } } oldList.clear(); oldList.addAll(newList); return commands; } DiffCommandsCalculator();  DiffCommandsCalculator(boolean itemRangeInsertedOnFirstDiff);  DiffCommandsCalculator(ItemChangedDetector<T> detector);  DiffCommandsCalculator(boolean itemRangeInsertedOnFirstDiff,
      @Nullable ItemChangedDetector<T> detector); List<AdapterCommand> diff(@NonNull List<T> newList);  }
@Test public void firstTime() { List<Item> newItems = newList("a", "b"); List<AdapterCommand> commands = calculator.diff(newItems); Assert.assertEquals(1, commands.size()); Assert.assertTrue(commands.get(0) instanceof EntireDataSetChangedCommand); }
@Test public void firstTimeItemRangeInserted() { DiffCommandsCalculator<Item> calculator = new DiffCommandsCalculator<>(true); List<Item> newItems = newList("a", "b"); List<AdapterCommand> commands = calculator.diff(newItems); Assert.assertEquals(1, commands.size()); assertContainCommand(commands, new ItemRangeInsertedCommand(0, 2)); }
@Test public void firstTimeEmptyList() { List<Item> newItems = new ArrayList<>(); List<AdapterCommand> commands = calculator.diff(newItems); Assert.assertEquals(1, commands.size()); Assert.assertTrue(commands.get(0) instanceof EntireDataSetChangedCommand); }
@Test public void nullList() { try { calculator.diff(null); Assert.fail("NullPointerException expected"); } catch (NullPointerException e) { Assert.assertEquals("newList == null", e.getMessage()); } }
@Test public void insertAndChange() { List<Item> items = newList("a", "b", "c"); calculator.diff(items); items.add(1, new Item("a2")); items.add(3, new Item("b2")); items.add(4, new Item("b3")); items.add(5, new Item("b4")); items.add(new Item("c2")); List<AdapterCommand> commands = calculator.diff(items); Assert.assertEquals(5, commands.size()); assertContainCommand(commands, new ItemInsertedCommand(1)); assertContainCommand(commands, new ItemInsertedCommand(3)); assertContainCommand(commands, new ItemInsertedCommand(4)); assertContainCommand(commands, new ItemInsertedCommand(5)); assertContainCommand(commands, new ItemInsertedCommand(7)); items.set(0, new Item("a", "newOtherValueA")); items.set(2, new Item("b", "newOtherValueB")); items.set(3, new Item("b2", "newOtherValueB2")); items.set(4, new Item("b3", "newOtherValueB3")); items.set(7, new Item("c2", "newOtherValuec2")); commands = calculator.diff(items); Assert.assertEquals(5, commands.size()); assertContainCommand(commands, new ItemChangedCommand(0)); assertContainCommand(commands, new ItemChangedCommand(2)); assertContainCommand(commands, new ItemChangedCommand(3)); assertContainCommand(commands, new ItemChangedCommand(4)); assertContainCommand(commands, new ItemChangedCommand(7)); }
@Test public void changesWithoutDetector() { List<Item> items = newList("a", "b", "c"); items.add(1, new Item("a2")); items.add(3, new Item("b2")); items.add(4, new Item("b3")); items.add(5, new Item("b4")); items.add(new Item("c2")); calculator = new DiffCommandsCalculator<>(); calculator.diff(items); items.set(0, new Item("a", "newValueA")); items.set(2, new Item("b", "newValueB")); items.set(3, new Item("b2", "newValueB2")); items.set(4, new Item("b3", "newValueB3")); items.set(7, new Item("c2", "newValuec2")); List<AdapterCommand> commands = calculator.diff(items); Assert.assertTrue(commands.isEmpty()); }
@Test public void move() { ItemChangedDetector<Item> changeDetector = new Detector(); List<Item> items = newList("a", "b", "c", "d", "e"); calculator.diff(items); Item a = items.remove(0); items.add(3, a); Item c = items.remove(1); items.add(4, c); List<AdapterCommand> commands = calculator.diff(items); Assert.assertEquals(4, commands.size()); assertContainCommand(commands, new ItemRemovedCommand(0)); assertContainCommand(commands, new ItemInsertedCommand(2)); assertContainCommand(commands, new ItemRemovedCommand(1)); assertContainCommand(commands, new ItemInsertedCommand(4)); }
@Test public void remove() { ItemChangedDetector<Item> changeDetector = new Detector(); List<Item> items = newList("a", "b", "c", "d", "e", "f"); calculator.diff(items); items.remove(1); items.remove(3); List<AdapterCommand> commands = calculator.diff(items); Assert.assertEquals(2, commands.size()); assertContainCommand(commands, new ItemRemovedCommand(1)); assertContainCommand(commands, new ItemRemovedCommand(3)); }
@Test public void comparingEmptyList() { List<AdapterCommand> commands = calculator.diff(Collections.<Item>emptyList()); Assert.assertEquals(1, commands.size()); Assert.assertTrue(commands.get(0) instanceof EntireDataSetChangedCommand); List<AdapterCommand> commands2 = calculator.diff(Collections.<Item>emptyList()); Assert.assertTrue(commands2.isEmpty()); }