package org.xpect.text;
import java.util.Arrays;
import java.util.List;
import org.xpect.text.ITextDifferencer.ILineDiff;
import org.xpect.text.ITextDifferencer.ISegment;
import org.xpect.text.ITextDifferencer.ISegmentDiff;
import org.xpect.text.ITextDifferencer.ITextDiff;
import org.xpect.util.IDifferencer.MatchKind;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
public class TextDiffToString implements Function<ITextDiff, String> {
protected static class Chunk {
private final boolean equal;
private final String text;
public Chunk(String text, boolean equal) {
super();
this.text = text;
this.equal = equal;
}
public String getText() {
return text;
}
public boolean isEqual() {
return equal;
}
@Override
public String toString() {
return text;
}
}
private boolean allowSingleLineDiff = true;
private boolean allowSingleSegmentDiff = true;
private int linesAfterDiff = 4;
private int linesBeforeDiff = 4;
public String apply(ITextDiff input) {
return apply(input.getLines());
}
public String apply(List<ILineDiff> lines) {
if (allowSingleLineDiff || allowSingleSegmentDiff) {
ILineDiff line = findSingleChangedLine(lines);
if (line != null) {
List<Chunk> chunks = toChunks(line);
if (allowSingleSegmentDiff) {
Chunk chunk = findSingleChangedChunk(chunks);
if (chunk != null)
return chunk.getText();
}
return Joiner.on("").join(chunks);
}
}
return toMultiLineDiff(lines);
}
protected ILineDiff findSingleChangedLine(List<ILineDiff> lines) {
ILineDiff changed = null;
for (ILineDiff line : lines) {
switch (line.getKind()) {
case SIMILAR: {
if (changed == null)
changed = line;
else
return null;
break;
}
case LEFT_ONLY:
case RIGHT_ONLY:
return null;
default: {
}
}
}
return changed;
}
protected Chunk findSingleChangedChunk(List<Chunk> chunks) {
Chunk changed = null;
for (Chunk chunk : chunks) {
if (!chunk.isEqual()) {
if (changed == null)
changed = chunk;
else
return null;
}
}
return changed;
}
protected String getHiddenBetween(ISegment seg1, ISegment seg2) {
StringBuilder result1 = new StringBuilder();
ISegment next = seg1.getNext();
while (next != null && next.isHidden() && !next.isWrap()) {
result1.append(next);
next = next.getNext();
}
if (next == seg2)
return result1.toString();
StringBuilder result2 = new StringBuilder();
ISegment prev = seg2.getPrevious();
while (prev != null && prev.isHidden() && !prev.isWrap()) {
result2.insert(0, prev);
prev = prev.getPrevious();
}
if (result1.length() > 0 && result2.length() > 0) {
if (result1.length() < result2.length())
return result1.toString();
return result2.toString();
} else if (result1.length() > 0) {
return result1.toString();
}
return result2.toString();
}
protected List<ISegment> getLeftSegments(ILineDiff line) {
List<ISegment> result = Lists.newArrayList();
for (ISegmentDiff match : line.getSegmentDiffs()) {
ISegment left = match.getLeft();
if (left != null)
result.add(left);
}
return result;
}
public int getLinesAfterDiff() {
return linesAfterDiff;
}
public int getLinesBeforeDiff() {
return linesBeforeDiff;
}
protected List<ISegment> getRightSegments(ILineDiff line) {
List<ISegment> result = Lists.newArrayList();
for (ISegmentDiff match : line.getSegmentDiffs()) {
ISegment right = match.getRight();
if (right != null)
result.add(right);
}
return result;
}
public boolean isAllowSingleLineDiff() {
return allowSingleLineDiff;
}
public boolean isAllowSingleSegmentDiff() {
return allowSingleSegmentDiff;
}
public TextDiffToString setAllowSingleLineDiff(boolean allowSingleLineDiff) {
this.allowSingleLineDiff = allowSingleLineDiff;
return this;
}
public TextDiffToString setAllowSingleSegmentDiff(boolean allowSingleTokenDiff) {
this.allowSingleSegmentDiff = allowSingleTokenDiff;
return this;
}
public TextDiffToString setLinesAfterDiff(int linesAfterDiff) {
this.linesAfterDiff = linesAfterDiff;
return this;
}
public TextDiffToString setLinesBeforeDiff(int linesBeforeDiff) {
this.linesBeforeDiff = linesBeforeDiff;
return this;
}
private List<Chunk> toChunks(ILineDiff line) {
List<ISegmentDiff> segmentDiffs = line.getSegmentDiffs();
List<Chunk> result = Lists.newArrayList();
List<ISegment> left = Lists.newArrayList();
List<ISegment> right = Lists.newArrayList();
List<ISegment> equal = Lists.newArrayList();
for (int i = 0; i < segmentDiffs.size(); i++) {
ISegmentDiff match = segmentDiffs.get(i);
switch (match.getKind()) {
case EQUAL:
equal.add(match.getLeft());
break;
case LEFT_ONLY:
left.add(match.getLeft());
break;
case RIGHT_ONLY:
right.add(match.getRight());
break;
case SIMILAR:
right.add(match.getRight());
left.add(match.getLeft());
break;
}
if (match.getKind() == MatchKind.EQUAL) {
if (left.size() > 0 || right.size() > 0) {
result.add(new Chunk("[" + toString(left, false, false) + "|" + toString(right, false, false) + "]", false));
left.clear();
right.clear();
}
}
if (match.getKind() != MatchKind.EQUAL) {
if (equal.size() > 0) {
result.add(new Chunk(toString(equal, true, true), true));
equal.clear();
}
}
if (i == segmentDiffs.size() - 1) {
if (left.size() > 0 || right.size() > 0)
result.add(new Chunk("[" + toString(left, false, false) + "|" + toString(right, false, false) + "]", false));
if (equal.size() > 0)
result.add(new Chunk(toString(equal, true, true), true));
}
}
return result;
}
public String toMultiLineDiff(List<ILineDiff> lines) {
boolean enabled[] = new boolean[lines.size()];
Arrays.fill(enabled, false);
int lastDiffLine = -linesAfterDiff;
for (int line = 0; line < enabled.length; line++) {
if (lines.get(line).getKind() != MatchKind.EQUAL)
lastDiffLine = line;
if (line - lastDiffLine < linesAfterDiff)
enabled[line] = true;
}
lastDiffLine = enabled.length + linesBeforeDiff;
for (int lastLine = enabled.length - 1; lastLine >= 0; lastLine--) {
if (lines.get(lastLine).getKind() != MatchKind.EQUAL)
lastDiffLine = lastLine;
if (lastDiffLine - lastLine < linesBeforeDiff)
enabled[lastLine] = true;
}
List<String> filtered = Lists.newArrayList();
boolean out = false;
for (int i = 0; i < enabled.length; i++) {
if (enabled[i]) {
filtered.add(toPrefixedLine(lines.get(i)));
out = false;
} else {
if (!out) {
filtered.add("(...)");
out = true;
}
}
}
return Joiner.on('\n').join(filtered);
}
public String toPrefixedLine(ILineDiff line) {
switch (line.getKind()) {
case EQUAL:
return " " + toString(getLeftSegments(line), true, true);
case LEFT_ONLY:
return "- " + toString(getLeftSegments(line), true, true);
case RIGHT_ONLY:
return "+ " + toString(getRightSegments(line), true, true);
case SIMILAR:
return "| " + Joiner.on("").join(toChunks(line));
}
throw new IllegalStateException("unknown MatchKind: " + line.getKind());
}
public String toSingleLineDiff(ILineDiff line) {
return line.toString();
}
public String toSingleSegmentDiff(ISegmentDiff segment) {
return segment.toString();
}
protected String toString(List<ISegment> segments, boolean prefix, boolean postfix) {
if (segments.isEmpty())
return "";
StringBuilder builder = new StringBuilder();
if (prefix) {
ISegment prev = segments.get(0).getPrevious();
while (prev != null && prev.isHidden() && !prev.isWrap()) {
builder.insert(0, prev);
prev = prev.getPrevious();
}
}
ISegment last = null;
for (ISegment seg : segments) {
if (last != null)
builder.append(getHiddenBetween(last, seg));
builder.append(seg);
last = seg;
}
if (postfix) {
ISegment next = segments.get(segments.size() - 1).getNext();
while (next != null && next.isHidden() && !next.isWrap()) {
builder.append(next);
next = next.getNext();
}
}
return builder.toString();
}
}