package ilarkesto.core.diff;
import java.util.List;
public class TokenDiff {
private List<String> left;
private List<String> right;
private DiffMarker marker;
private DiffTokenizer tokenizer;
private DiffTokenizer subTokenizer;
private List<String> lcs;
private int lcsLen;
private int leftLen;
private int rightLen;
private StringBuilder out = new StringBuilder();
private String chLcs;
private String chLeft;
private String chRight;
private boolean skipBurn;
private String removed;
public static String combinedDiff(String left, String right, DiffMarker marker) {
return new TokenDiff(left, right, marker, new LineTokenizer(), new WordTokenizer()).diff().toString();
}
public TokenDiff(String left, String right, DiffMarker marker, DiffTokenizer tokenizer) {
this(left, right, marker, tokenizer, null);
}
public TokenDiff(String left, String right, DiffMarker marker, DiffTokenizer tokenizer, DiffTokenizer subTokenizer) {
this.left = tokenizer.tokenize(left);
this.right = tokenizer.tokenize(right);
this.marker = marker;
this.tokenizer = tokenizer;
this.subTokenizer = subTokenizer;
}
public TokenDiff diff() {
lcs = LongestCommonSubsequenceList.execute(left, right);
updateLengths();
while (skipBurn || (lcsLen > 0 && leftLen > 0 && rightLen > 0)) {
burnNext();
}
if (removed != null) {
out.append(marker.removed(removed));
removed = null;
}
if (leftLen == 0 && rightLen == 0) return this;
if (leftLen == 0) {
out.append(marker.added(tokenizer.concat(right)));
return this;
}
if (rightLen == 0) {
out.append(marker.removed(tokenizer.concat(left)));
return this;
}
if (lcsLen == 0) {
outReplaced(tokenizer.concat(left), tokenizer.concat(right));
return this;
}
return this;
}
private void burnNext() {
if (!nextChar()) return;
if (chLcs.equals(chLeft) && chLcs.equals(chRight)) {
if (removed != null) {
out.append(marker.removed(removed));
removed = null;
}
burnSame();
return;
}
if (chLcs.equals(chLeft)) {
burnAdded();
return;
}
burnRemoved();
}
private void burnRemoved() {
StringBuilder sb = new StringBuilder();
while (!chLcs.equals(chLeft)) {
sb.append(chLeft);
if (!nextCharLeft()) break;
}
removed = sb.toString();
skipBurn = true;
}
private void burnAdded() {
StringBuilder sb = new StringBuilder();
while (chLcs.equals(chLeft) && !chLcs.equals(chRight)) {
sb.append(chRight);
if (!nextCharRight()) break;
}
String added = sb.toString();
if (removed != null) {
outReplaced(removed, added);
removed = null;
} else {
out.append(marker.added(added));
}
skipBurn = true;
}
private void outReplaced(String removed, String added) {
if (subTokenizer == null) {
out.append(marker.replaced(removed, added));
return;
}
TokenDiff diff = new TokenDiff(removed, added, marker, subTokenizer);
diff.diff();
out.append(diff.toString());
}
private void burnSame() {
StringBuilder sb = new StringBuilder();
boolean nextCharAvailable = false;
while (chLcs.equals(chLeft) && chLcs.equals(chRight)) {
sb.append(chLcs);
nextCharAvailable = nextChar();
if (!nextCharAvailable) break;
}
out.append(marker.same(sb.toString()));
if (nextCharAvailable) skipBurn = true;
}
private boolean nextChar() {
if (skipBurn) {
skipBurn = false;
return true;
}
if (lcsLen == 0) return false;
if (leftLen == 0) return false;
if (rightLen == 0) return false;
chLcs = lcs.get(0);
chLeft = left.get(0);
chRight = right.get(0);
lcs.remove(0);
left.remove(0);
right.remove(0);
lcsLen--;
leftLen--;
rightLen--;
return true;
}
private boolean nextCharRight() {
if (rightLen == 0) return false;
chRight = right.get(0);
right.remove(0);
rightLen--;
return true;
}
private boolean nextCharLeft() {
if (leftLen == 0) return false;
chLeft = left.get(0);
left.remove(0);
leftLen--;
return true;
}
private void updateLengths() {
leftLen = left.size();
rightLen = right.size();
lcsLen = lcs.size();
}
@Override
public String toString() {
return out.toString();
}
}