package net.mcforkage.ant;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
// I am more surprised than you that this algorithm works (and isn't horrendously inefficient).
public class CreateDiffTask extends Task {
private File from, to, output;
public void setFrom(File f) {from = f;}
public void setTo(File f) {to = f;}
public void setOutput(File f) {output = f;}
public static void main(String[] args) {
CreateDiffTask t = new CreateDiffTask();
t.from = new File("../../build/bytecode-old.txt");
t.to = new File("../../build/bytecode-new.txt");
t.output = new File("../../build/bytecode.patch");
t.execute();
}
private int countCommonFirstLines(List<String> a, List<String> b) {
for(int k = 0; k < a.size() && k < b.size(); k++)
if(!a.get(k).equals(b.get(k)))
return k;
return Math.min(a.size(), b.size());
}
private static class IntPair {
int a, b;
IntPair(int a, int b) {this.a = a; this.b = b;}
}
private static final int COARSE_CHECKLEN = 30;
private IntPair isResyncPoint(List<String> a, List<String> b, int da, int db, int checklen) {
if(da > a.size() - checklen || db > b.size() - checklen)
return null;
for(int k = 0; k < checklen; k++)
if(!a.get(da+k).equals(b.get(db+k)))
return null;
return new IntPair(da, db);
}
private IntPair findNextResyncPoint(List<String> a, List<String> b, int checklen) {
IntPair r;
for(int da = 0; da < a.size(); da++) {
r = isResyncPoint(a, b, da, da, checklen);
if(r != null) return r;
for(int db = 0; db < da; db++) {
r = isResyncPoint(a, b, da, db, checklen);
if(r != null) return r;
r = isResyncPoint(a, b, db, da, checklen);
if(r != null) return r;
}
}
return null;
}
@Override
public void execute() throws BuildException {
if(from == null) throw new BuildException("From file not set");
if(to == null) throw new BuildException("To file not set");
if(output == null) throw new BuildException("Output file not set");
List<String> fromLines = readFile(from);
List<String> toLines = readFile(to);
try (PrintWriter out = new PrintWriter(new OutputStreamWriter(new FileOutputStream(output), StandardCharsets.UTF_8))) {
out.println("--- oldfile");
out.println("+++ newfile");
int nextFromLine = 1;
int nextToLine = 1;
while(fromLines.size() > 0 && toLines.size() > 0) {
int common = countCommonFirstLines(fromLines, toLines);
if(common > 0) {
nextFromLine += common;
nextToLine += common;
fromLines = fromLines.subList(common, fromLines.size());
toLines = toLines.subList(common, toLines.size());
continue;
}
IntPair rp = findNextResyncPoint(fromLines, toLines, COARSE_CHECKLEN);
if(rp == null) {
break;
}
List<String> deleted = fromLines.subList(0, rp.a);
List<String> inserted = toLines.subList(0, rp.b);
refineHunk(deleted, inserted, nextFromLine, nextToLine, COARSE_CHECKLEN, out);
fromLines = fromLines.subList(rp.a, fromLines.size());
toLines = toLines.subList(rp.b, toLines.size());
nextFromLine += rp.a;
nextToLine += rp.b;
}
if(!toLines.isEmpty() || !fromLines.isEmpty()) {
refineHunk(fromLines, toLines, nextFromLine, nextToLine, COARSE_CHECKLEN, out);
}
} catch(IOException e) {
throw new BuildException(e);
}
}
/*private void write_hunk(change hunk, PrintWriter out, Map<Integer, String> equiv_to_line, String[] oldLines, String[] newLines) {
out.println("@@ -"+hunk.line0+","+hunk.deleted+" +"+hunk.line1+","+hunk.inserted+" @@");
for(int k = 0; k < hunk.deleted; k++)
out.println("-" + oldLines[hunk.line0+k]);
for(int k = 0; k < hunk.inserted; k++)
out.println("+" + newLines[hunk.line1+k]);
}*/
private void printHunk(List<String> deleted, List<String> inserted, int nextFromLine, int nextToLine, PrintWriter out) {
if(inserted.isEmpty() && deleted.isEmpty())
return;
out.println("@@ -"+(nextFromLine - (deleted.isEmpty() ? 1 : 0))+","+deleted.size()+" +"+(nextToLine - (inserted.isEmpty() ? 1 : 0))+","+inserted.size()+" @@");
for(String s : deleted) out.println("-" + s);
for(String s : inserted) out.println("+" + s);
}
private void refineHunk(List<String> fromLines, List<String> toLines, int nextFromLine, int nextToLine, int checklen, PrintWriter out) {
if(fromLines.isEmpty() || toLines.isEmpty() || checklen == 1) {
printHunk(fromLines, toLines, nextFromLine, nextToLine, out);
return;
}
checklen--;
while(fromLines.size() > 0 && toLines.size() > 0) {
int common = countCommonFirstLines(fromLines, toLines);
if(common > 0) {
nextFromLine += common;
nextToLine += common;
fromLines = fromLines.subList(common, fromLines.size());
toLines = toLines.subList(common, toLines.size());
continue;
}
IntPair rp = findNextResyncPoint(fromLines, toLines, checklen);
if(rp == null) {
break;
}
List<String> deleted = fromLines.subList(0, rp.a);
List<String> inserted = toLines.subList(0, rp.b);
refineHunk(deleted, inserted, nextFromLine, nextToLine, checklen, out);
nextFromLine += deleted.size();
nextToLine += inserted.size();
fromLines = fromLines.subList(rp.a, fromLines.size());
toLines = toLines.subList(rp.b, toLines.size());
}
refineHunk(fromLines, toLines, nextFromLine, nextToLine, checklen, out);
}
private List<String> readFile(File f) throws BuildException {
ArrayList<String> lines = new ArrayList<>();
try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(f), StandardCharsets.UTF_8))) {
String line;
while((line = in.readLine()) != null) {
if(line.endsWith("\r")) throw new BuildException("x");
lines.add(line);
}
} catch(IOException e) {
throw new BuildException("Error reading "+f, e);
}
lines.trimToSize();
return lines;
}
}