From 520cf81bcd8cc23f895860149aa4f66a9017ad6b Mon Sep 17 00:00:00 2001 From: Philippe Fremy Date: Thu, 19 Mar 2015 22:10:22 +0100 Subject: [PATCH 1/7] First attempt at a Java implementation of simplediff algorithm. --- java/src/SimpleDiff.java | 160 +++++++++++++++++++++++++++++++++++ java/src/SimpleDiffTest.java | 49 +++++++++++ 2 files changed, 209 insertions(+) create mode 100644 java/src/SimpleDiff.java create mode 100644 java/src/SimpleDiffTest.java diff --git a/java/src/SimpleDiff.java b/java/src/SimpleDiff.java new file mode 100644 index 0000000..c02ca88 --- /dev/null +++ b/java/src/SimpleDiff.java @@ -0,0 +1,160 @@ + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +public class SimpleDiff { + public static void main(String[] args) { + List< Map.Entry > ret; + ret = diffString("tito", "toto"); + System.out.println(ret); + } + + static public enum DiffOp { + ADD('+'), DEL('-'), EQ('='); + + private char c; + + DiffOp( char c ) { + this.c = c; + } + + static public DiffOp fromString( String s ) { + return fromChar( s.charAt(0)); + } + + static public DiffOp fromChar( char c ) { + switch( c ) { + case '+': + return ADD; + case '-': + return DEL; + case '=': + return EQ; + } + + return EQ; + } + + public String toString() { + return String.valueOf(c); + } + } + + static public List< Map.Entry > > + diff( List olds, List news ) { + //System.out.printf("diff \"%s\" vs \"%s\"%n", olds, news ); + // Create a map from old values to their indices + Map< Character, List > oldIndexMap = new HashMap<>(); + for( int i=0; i indexList = oldIndexMap.getOrDefault(olds.get(i), new ArrayList()); + indexList.add(i); + oldIndexMap.put(olds.get(i), indexList); + } + + //System.out.printf( "oldIndexMap: %s%n", oldIndexMap ); + + // Find the largest substring common to old and new. + // We use a dynamic programming approach here. + // + // We iterate over each value in the `new` list, calling the + // index `inew`. At each iteration, `overlap[iold]` is the + // length of the largest suffix of `old[:iold]` equal to a suffix + // of `new[:inew]` (or unset when `old[iold]` != `new[inew]`). + // + // At each stage of iteration, the new `overlap` (called + // `_overlap` until the original `overlap` is no longer needed) + // is built from the old one. + // + // If the length of overlap exceeds the largest substring + // seen so far (`sub_length`), we update the largest substring + // to the overlapping strings. + + // `sub_start_old` is the index of the beginning of the largest overlapping + // substring in the old list. `sub_start_new` is the index of the beginning + // of the same substring in the new list. `sub_length` is the length that + // overlaps in both. + + // These track the largest overlapping substring seen so far, so naturally + // we start with a 0-length substring. + int subStartOld=0, subStartNew=0, subLength=0; + HashMap overlap, _overlap; + + overlap = new HashMap<>(); + + for( int inew=0; inew(); + Character val=news.get(inew); + for( Integer iold : oldIndexMap.getOrDefault(val, new ArrayList()) ) { + // now we are considering all values of iold such that + // `old[iold] == new[inew]`. + if (iold == 0) { + _overlap.put(iold, 1); + } else { + _overlap.put(iold, overlap.getOrDefault(iold-1, 0)+1 ); + } + + if (_overlap.get(iold) > subLength) { + // this is the largest substring seen so far, so store its + // indices + subLength = _overlap.get(iold); + subStartOld = iold - subLength + 1; + subStartNew = inew - subLength + 1; + } + } + overlap = _overlap; + //System.out.printf("inew=%d %s%n", inew, overlap); + } + + List< Map.Entry > > ret = new ArrayList<>(); + if (subLength==0) { + // no common substring found + if (olds.size() > 0) { + ret.add( new AbstractMap.SimpleEntry<> (DiffOp.DEL, olds ) ); + } + if (news.size() > 0) { + ret.add( new AbstractMap.SimpleEntry<> (DiffOp.ADD, news ) ); + } + } else { + ret.addAll( diff( olds.subList(0, subStartOld), news.subList(0, subStartNew)) ); + ret.add( new AbstractMap.SimpleEntry<>(DiffOp.EQ, news.subList(subStartNew, subStartNew + subLength) ) ); + ret.addAll( diff( olds.subList(subStartOld+subLength, olds.size()), news.subList(subStartNew+subLength, news.size()))); + } + //System.out.println( ret ); + return ret; + } + + static String listCharToString( List l ) { + StringBuilder builder = new StringBuilder(); + for( Character c : l ) { + builder.append( c ); + } + return builder.toString(); + } + + static public List< Map.Entry > diffString( String olds, String news ) { + List oldList = new ArrayList<>(); + List newList = new ArrayList<>(); + for( int i=0; i > > retList = diff( oldList, newList ); + + List< Map.Entry > retString = retList.stream().map( + entry -> new AbstractMap.SimpleEntry<>( + entry.getKey(), + listCharToString( entry.getValue() ) ) ).collect( Collectors.toList()); + + return retString; + } + +} + diff --git a/java/src/SimpleDiffTest.java b/java/src/SimpleDiffTest.java new file mode 100644 index 0000000..6ef98f1 --- /dev/null +++ b/java/src/SimpleDiffTest.java @@ -0,0 +1,49 @@ +import static org.junit.Assert.*; +import org.junit.Test; + +import java.util.AbstractMap; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + + +import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils.Collections; + + +public class SimpleDiffTest { + + @Test + public void testListCharToString() { + Character[] listChar = { 't', 'o', 't', 'o' }; + assertEquals( SimpleDiff.listCharToString( Arrays.asList(listChar) ), "toto" ); + } + + @Test + public void testDiffString() { + String olds = "The quick brown fox."; + String news = "The kuick brown fix."; + String[][] expectedArray = { + { "=", "The " }, + { "-", "q" }, + { "+", "k" }, + { "=", "uick brown f" }, + { "-", "o" }, + { "+", "i" }, + { "=", "x." }, + }; + + List< Map.Entry > ret; + List< Map.Entry > expected; + ret = SimpleDiff.diffString(olds, news); + + expected = Arrays.asList( expectedArray ).stream() + .map(ar -> new AbstractMap.SimpleEntry( + SimpleDiff.DiffOp.fromString(ar[0]), ar[1]) ) + .collect(Collectors.toList()); + assertEquals(expected, ret);; + + } + +} + From 93428b060f77dda7c74006d51e4f6f5301648e23 Mon Sep 17 00:00:00 2001 From: Philippe Fremy Date: Thu, 19 Mar 2015 22:17:19 +0100 Subject: [PATCH 2/7] Make method generic instead of Character specific. --- java/src/SimpleDiff.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/java/src/SimpleDiff.java b/java/src/SimpleDiff.java index c02ca88..8d32a3c 100644 --- a/java/src/SimpleDiff.java +++ b/java/src/SimpleDiff.java @@ -45,11 +45,11 @@ public String toString() { } } - static public List< Map.Entry > > - diff( List olds, List news ) { + static public List< Map.Entry > > + diff( List olds, List news ) { //System.out.printf("diff \"%s\" vs \"%s\"%n", olds, news ); // Create a map from old values to their indices - Map< Character, List > oldIndexMap = new HashMap<>(); + Map< E, List > oldIndexMap = new HashMap<>(); for( int i=0; i indexList = oldIndexMap.getOrDefault(olds.get(i), new ArrayList()); indexList.add(i); @@ -74,9 +74,9 @@ public String toString() { // seen so far (`sub_length`), we update the largest substring // to the overlapping strings. - // `sub_start_old` is the index of the beginning of the largest overlapping - // substring in the old list. `sub_start_new` is the index of the beginning - // of the same substring in the new list. `sub_length` is the length that + // `subStartOld` is the index of the beginning of the largest overlapping + // substring in the old list. `subStartNew` is the index of the beginning + // of the same substring in the new list. `subLength` is the length that // overlaps in both. // These track the largest overlapping substring seen so far, so naturally @@ -88,7 +88,7 @@ public String toString() { for( int inew=0; inew(); - Character val=news.get(inew); + E val=news.get(inew); for( Integer iold : oldIndexMap.getOrDefault(val, new ArrayList()) ) { // now we are considering all values of iold such that // `old[iold] == new[inew]`. @@ -110,7 +110,7 @@ public String toString() { //System.out.printf("inew=%d %s%n", inew, overlap); } - List< Map.Entry > > ret = new ArrayList<>(); + List< Map.Entry > > ret = new ArrayList<>(); if (subLength==0) { // no common substring found if (olds.size() > 0) { From 759626e944f37d21f2cb4a8c9817334489488bad Mon Sep 17 00:00:00 2001 From: Philippe Fremy Date: Thu, 19 Mar 2015 22:17:42 +0100 Subject: [PATCH 3/7] Move test data up to class level. --- java/src/SimpleDiffTest.java | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/java/src/SimpleDiffTest.java b/java/src/SimpleDiffTest.java index 6ef98f1..1757a39 100644 --- a/java/src/SimpleDiffTest.java +++ b/java/src/SimpleDiffTest.java @@ -13,6 +13,18 @@ public class SimpleDiffTest { + static String diffDataStringOld = "The quick brown fox."; + static String diffDataStringNew = "The kuick brown fix."; + static String[][] diffDataStringExpected = { + { "=", "The " }, + { "-", "q" }, + { "+", "k" }, + { "=", "uick brown f" }, + { "-", "o" }, + { "+", "i" }, + { "=", "x." }, + }; + @Test public void testListCharToString() { Character[] listChar = { 't', 'o', 't', 'o' }; @@ -21,23 +33,11 @@ public void testListCharToString() { @Test public void testDiffString() { - String olds = "The quick brown fox."; - String news = "The kuick brown fix."; - String[][] expectedArray = { - { "=", "The " }, - { "-", "q" }, - { "+", "k" }, - { "=", "uick brown f" }, - { "-", "o" }, - { "+", "i" }, - { "=", "x." }, - }; - List< Map.Entry > ret; List< Map.Entry > expected; - ret = SimpleDiff.diffString(olds, news); + ret = SimpleDiff.diffString(diffDataStringOld, diffDataStringNew); - expected = Arrays.asList( expectedArray ).stream() + expected = Arrays.asList( diffDataStringExpected ).stream() .map(ar -> new AbstractMap.SimpleEntry( SimpleDiff.DiffOp.fromString(ar[0]), ar[1]) ) .collect(Collectors.toList()); From 264cd5fd66a4dbaab9f40029c772105a67daf595 Mon Sep 17 00:00:00 2001 From: Philippe Fremy Date: Thu, 19 Mar 2015 22:39:32 +0100 Subject: [PATCH 4/7] Add tests with lists of words --- java/src/SimpleDiffTest.java | 70 ++++++++++++++++++++++++++++++------ 1 file changed, 59 insertions(+), 11 deletions(-) diff --git a/java/src/SimpleDiffTest.java b/java/src/SimpleDiffTest.java index 1757a39..e8b5ccb 100644 --- a/java/src/SimpleDiffTest.java +++ b/java/src/SimpleDiffTest.java @@ -1,21 +1,24 @@ import static org.junit.Assert.*; + import org.junit.Test; import java.util.AbstractMap; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; + import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils.Collections; public class SimpleDiffTest { - static String diffDataStringOld = "The quick brown fox."; - static String diffDataStringNew = "The kuick brown fix."; - static String[][] diffDataStringExpected = { + static String diffDataCharOld = "The quick brown fox."; + static String diffDataCharNew = "The kuick brown fix."; + static String[][] diffDataCharExpected = { { "=", "The " }, { "-", "q" }, { "+", "k" }, @@ -25,25 +28,70 @@ public class SimpleDiffTest { { "=", "x." }, }; - @Test - public void testListCharToString() { - Character[] listChar = { 't', 'o', 't', 'o' }; - assertEquals( SimpleDiff.listCharToString( Arrays.asList(listChar) ), "toto" ); - } - @Test public void testDiffString() { List< Map.Entry > ret; List< Map.Entry > expected; - ret = SimpleDiff.diffString(diffDataStringOld, diffDataStringNew); + ret = SimpleDiff.diffString(diffDataCharOld, diffDataCharNew); - expected = Arrays.asList( diffDataStringExpected ).stream() + expected = Arrays.asList( diffDataCharExpected ).stream() .map(ar -> new AbstractMap.SimpleEntry( SimpleDiff.DiffOp.fromString(ar[0]), ar[1]) ) .collect(Collectors.toList()); assertEquals(expected, ret);; } + + static String[] diffDataWordOld1 = { "The", "quick", "brown", "fox" }; + static String[] diffDataWordNew1 = { "The", "slow", "green", "turtle" }; + static String[][] diffDataWordExpected1 = { + {"=", "The"}, + {"-", "quick", "brown", "fox"}, + {"+", "slow", "green", "turtle"}, + }; + + static String[] diffDataWordOld2 = { "jumps", "over", "the", "lazy", "dog" }; + static String[] diffDataWordNew2 = { "walks", "around", "the", "orange", "cat" }; + static String[][] diffDataWordExpected2 = { + {"-", "jumps", "over" }, + {"+", "walks", "around" }, + {"=", "the" }, + {"-", "lazy", "dog" }, + {"+", "orange", "cat" }, + }; + + @Test + public void testDiffWord1() { + subDiffWord(diffDataWordOld1, diffDataWordNew1, diffDataWordExpected1 ); + } + + @Test + public void testDiffWord2() { + subDiffWord(diffDataWordOld2, diffDataWordNew2, diffDataWordExpected2 ); + } + + public void subDiffWord( String[] oldWord, String[] newWord, String[][] expectedWord ) { + List< Map.Entry > > ret; + List< Map.Entry > > expected; + ret = SimpleDiff.diff(Arrays.asList(oldWord), Arrays.asList(newWord)); + + expected = Arrays.asList( expectedWord ).stream() + .map(ar -> new AbstractMap.SimpleEntry >( + SimpleDiff.DiffOp.fromString(ar[0]), + Arrays.asList(ar).subList(1, ar.length) ) ) + .collect(Collectors.toList()); + assertEquals(expected, ret); + } + + + + @Test + public void testListCharToString() { + Character[] listChar = { 't', 'o', 't', 'o' }; + assertEquals( SimpleDiff.listCharToString( Arrays.asList(listChar) ), "toto" ); + } + + } From a303d2142b62653e62bbb2849898ce011c211370 Mon Sep 17 00:00:00 2001 From: Philippe Fremy Date: Thu, 19 Mar 2015 23:33:35 +0100 Subject: [PATCH 5/7] Add tests related to int diffing. --- java/src/SimpleDiff.java | 15 ++++ java/src/SimpleDiffTest.java | 133 +++++++++++++++++++++++++++++------ 2 files changed, 126 insertions(+), 22 deletions(-) diff --git a/java/src/SimpleDiff.java b/java/src/SimpleDiff.java index 8d32a3c..b4cefba 100644 --- a/java/src/SimpleDiff.java +++ b/java/src/SimpleDiff.java @@ -26,6 +26,21 @@ static public enum DiffOp { static public DiffOp fromString( String s ) { return fromChar( s.charAt(0)); } + + static public DiffOp fromInt( int i ) { + switch( i ) { + case 0: + return ADD; + case 1: + return DEL; + case 2: + return EQ; + } + + return EQ; + } + + static public DiffOp fromChar( char c ) { switch( c ) { diff --git a/java/src/SimpleDiffTest.java b/java/src/SimpleDiffTest.java index e8b5ccb..d1ed02c 100644 --- a/java/src/SimpleDiffTest.java +++ b/java/src/SimpleDiffTest.java @@ -3,17 +3,12 @@ import org.junit.Test; import java.util.AbstractMap; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.stream.Collectors; - -import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils.Collections; - - public class SimpleDiffTest { static String diffDataCharOld = "The quick brown fox."; @@ -42,23 +37,23 @@ public void testDiffString() { } - static String[] diffDataWordOld1 = { "The", "quick", "brown", "fox" }; - static String[] diffDataWordNew1 = { "The", "slow", "green", "turtle" }; - static String[][] diffDataWordExpected1 = { - {"=", "The"}, - {"-", "quick", "brown", "fox"}, - {"+", "slow", "green", "turtle"}, - }; + static String[] diffDataWordOld1 = { "The", "quick", "brown", "fox" }; + static String[] diffDataWordNew1 = { "The", "slow", "green", "turtle" }; + static String[][] diffDataWordExpected1 = { + {"=", "The"}, + {"-", "quick", "brown", "fox"}, + {"+", "slow", "green", "turtle"}, + }; - static String[] diffDataWordOld2 = { "jumps", "over", "the", "lazy", "dog" }; - static String[] diffDataWordNew2 = { "walks", "around", "the", "orange", "cat" }; - static String[][] diffDataWordExpected2 = { - {"-", "jumps", "over" }, - {"+", "walks", "around" }, - {"=", "the" }, - {"-", "lazy", "dog" }, - {"+", "orange", "cat" }, - }; + static String[] diffDataWordOld2 = { "jumps", "over", "the", "lazy", "dog" }; + static String[] diffDataWordNew2 = { "walks", "around", "the", "orange", "cat" }; + static String[][] diffDataWordExpected2 = { + {"-", "jumps", "over" }, + {"+", "walks", "around" }, + {"=", "the" }, + {"-", "lazy", "dog" }, + {"+", "orange", "cat" }, + }; @Test public void testDiffWord1() { @@ -82,8 +77,102 @@ public void subDiffWord( String[] oldWord, String[] newWord, String[][] expected .collect(Collectors.toList()); assertEquals(expected, ret); } - + static int ADD=0; + static int DEL=1; + static int EQ=2; + + static Integer[] diffDataIntOld1 = {1, 3, 4}; + static Integer[] diffDataIntNew1 = {1, 2, 3, 4}; + static Integer[][] diffDataIntExpected1 = { + {EQ ,1 }, + {ADD, 2}, + {EQ, 3, 4}, + }; + + static Integer[] diffDataIntOld2 = {1, 2, 3, 8, 9, 12, 13 }; + static Integer[] diffDataIntNew2 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}; + static Integer[][] diffDataIntExpected2 = { + { EQ, 1, 2, 3 }, + { ADD, 4, 5, 6, 7 }, + { EQ, 8, 9 }, + { ADD, 10, 11 }, + { EQ, 12, 13 }, + { ADD, 14, 15 }, + }; + + static Integer[] diffDataIntOld34 = {1, 2, 3, 4, 5 }; + static Integer[] diffDataIntNew34 = {1, 2, 2, 3, 4, 5}; + static Integer[][] diffDataIntExpected34 = { + { EQ, 1 }, + { ADD, 2 }, + { EQ, 2, 3, 4, 5 }, + }; + + static Integer[] diffDataIntOld4 = {1, 2, 3, 4, 5 }; + static Integer[] diffDataIntNew4 = {1, 2, 2, 3, 4, 4, 5}; + static Integer[][] diffDataIntExpected4 = { + { EQ, 1 }, + { ADD, 2 }, + { EQ, 2, 3, 4 }, + { ADD, 4 }, + { EQ, 5 }, + }; + + static Integer[] diffDataIntOld5 = {1, 2, 3, 4, 5 }; + static Integer[] diffDataIntNew5 = {1, 2, 1, 2, 3, 3, 2, 1, 4, 5}; + static Integer[][] diffDataIntExpected5 = { + { ADD, 1, 2 }, + { EQ, 1, 2, 3 }, + { ADD, 3, 2, 1 }, + { EQ, 4, 5 }, + }; + + static Integer[] diffDataIntOld6 = {1, 2, 3, 4, 5}; + static Integer[] diffDataIntNew6 = {1, 2, 5}; + static Integer[][] diffDataIntExpected6 = { + { EQ, 1, 2 }, + { DEL, 3, 4 }, + { EQ, 5 }, + }; + + static Integer[] diffDataIntOld7 = {1, 2, 3, 4, 5, 6, 7, 8}; + static Integer[] diffDataIntNew7 = {3, 6, 7}; + static Integer[][] diffDataIntExpected7 = { + { DEL, 1, 2 }, + { EQ, 3 }, + { DEL, 4, 5 }, + { EQ, 6, 7 }, + { DEL, 8 }, + }; + + static Integer[] diffDataIntOld8 = {1, 2, 3, 4, 5, 1, 2, 3, 4, 5}; + static Integer[] diffDataIntNew8 = {1, 2, 3, 4, 5}; + static Integer[][] diffDataIntExpected8 = { + {EQ, 1, 2, 3, 4, 5}, + {DEL, 1, 2, 3, 4, 5}, + }; + + @Test + public void testDiffInt1() { + subDiffInt(Arrays.asList(diffDataIntOld2), + Arrays.asList(diffDataIntNew2), + Arrays.asList(diffDataIntExpected2).stream() + .map(ar -> Arrays.asList(ar)).collect(Collectors.toList())); + } + + public void subDiffInt( List oldIntList, List newIntList, List> expectedIntList ) { + List< Map.Entry > > ret; + List< Map.Entry > > expected; + ret = SimpleDiff.diff(oldIntList, newIntList); + + expected = expectedIntList.stream() + .map(li -> new AbstractMap.SimpleEntry >( + SimpleDiff.DiffOp.fromInt(li.get(0)), + li.subList(1, li.size()) ) ) + .collect(Collectors.toList()); + assertEquals(expected, ret); + } @Test public void testListCharToString() { From 3235764b737a1267d2861e68e17241fd7e86236d Mon Sep 17 00:00:00 2001 From: Philippe Fremy Date: Fri, 20 Mar 2015 13:19:06 +0100 Subject: [PATCH 6/7] Show diffing of list of lines. --- java/src/SimpleDiff.java | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/java/src/SimpleDiff.java b/java/src/SimpleDiff.java index b4cefba..0f5a3c0 100644 --- a/java/src/SimpleDiff.java +++ b/java/src/SimpleDiff.java @@ -1,17 +1,26 @@ import java.util.AbstractMap; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.stream.Collectors; public class SimpleDiff { public static void main(String[] args) { - List< Map.Entry > ret; - ret = diffString("tito", "toto"); - System.out.println(ret); + System.out.println( diffString("tito", "toto") ); + System.out.println( diff( Arrays.asList( + "line1: if (titi) {", + "line2: toto();", + "line3: }" + ), Arrays.asList( + "line1: if (titi && tutu) {", + "line2: toto();", + "line3: }" + ))); + + } static public enum DiffOp { From 15f6a8ceab47466a5dccceda982828410a7cef68 Mon Sep 17 00:00:00 2001 From: Philippe Fremy Date: Mon, 23 Mar 2015 23:13:11 +0100 Subject: [PATCH 7/7] License and doc fixes --- java/src/SimpleDiff.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/java/src/SimpleDiff.java b/java/src/SimpleDiff.java index 0f5a3c0..1124950 100644 --- a/java/src/SimpleDiff.java +++ b/java/src/SimpleDiff.java @@ -1,4 +1,11 @@ +/* +(C) Paul Butler 2008-2012 +May be used and distributed under the zlib/libpng license + +Java implementation by Philippe Fremy, Copyright 2015 + +*/ import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; @@ -69,6 +76,7 @@ public String toString() { } } + /** Return the differences between two generic lists */ static public List< Map.Entry > > diff( List olds, List news ) { //System.out.printf("diff \"%s\" vs \"%s\"%n", olds, news ); @@ -160,6 +168,7 @@ static String listCharToString( List l ) { return builder.toString(); } + /** Return the difference between two strings */ static public List< Map.Entry > diffString( String olds, String news ) { List oldList = new ArrayList<>(); List newList = new ArrayList<>();