diff --git a/java/src/SimpleDiff.java b/java/src/SimpleDiff.java
new file mode 100644
index 0000000..1124950
--- /dev/null
+++ b/java/src/SimpleDiff.java
@@ -0,0 +1,193 @@
+/*
+(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;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class SimpleDiff {
+ public static void main(String[] args) {
+ 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 {
+ 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 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 ) {
+ case '+':
+ return ADD;
+ case '-':
+ return DEL;
+ case '=':
+ return EQ;
+ }
+
+ return EQ;
+ }
+
+ public String toString() {
+ return String.valueOf(c);
+ }
+ }
+
+ /** 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 );
+ // Create a map from old values to their indices
+ Map< E, 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.
+
+ // `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
+ // 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();
+ 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]`.
+ 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();
+ }
+
+ /** Return the difference between two strings */
+ 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..d1ed02c
--- /dev/null
+++ b/java/src/SimpleDiffTest.java
@@ -0,0 +1,186 @@
+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;
+
+
+public class SimpleDiffTest {
+
+ static String diffDataCharOld = "The quick brown fox.";
+ static String diffDataCharNew = "The kuick brown fix.";
+ static String[][] diffDataCharExpected = {
+ { "=", "The " },
+ { "-", "q" },
+ { "+", "k" },
+ { "=", "uick brown f" },
+ { "-", "o" },
+ { "+", "i" },
+ { "=", "x." },
+ };
+
+ @Test
+ public void testDiffString() {
+ List< Map.Entry > ret;
+ List< Map.Entry > expected;
+ ret = SimpleDiff.diffString(diffDataCharOld, diffDataCharNew);
+
+ 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);
+ }
+
+ 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() {
+ Character[] listChar = { 't', 'o', 't', 'o' };
+ assertEquals( SimpleDiff.listCharToString( Arrays.asList(listChar) ), "toto" );
+ }
+
+
+
+}
+