/**
* Compute a list of patches to turn text1 into text2.
* A set of diffs will be computed.
* @param text1 Old text.
* @param text2 New text.
* @return LinkedList of Patch objects.
*/
public LinkedList<Patch> patch_make(String text1, String text2) {
if (text1 == null || text2 == null) {
throw new IllegalArgumentException("Null inputs. (patch_make)");
}
// No diffs provided, compute our own.
LinkedList<Diff> diffs = diff_main(text1, text2, true);
if (diffs.size() > 2) {
diff_cleanupSemantic(diffs);
diff_cleanupEfficiency(diffs);
}
return patch_make(text1, diffs);
}
/**
* Compute a list of patches to turn text1 into text2.
* text1 will be derived from the provided diffs.
* @param diffs Array of Diff objects for text1 to text2.
* @return LinkedList of Patch objects.
*/
public LinkedList<Patch> patch_make(LinkedList<Diff> diffs) {
if (diffs == null) {
throw new IllegalArgumentException("Null inputs. (patch_make)");
}
// No origin string provided, compute our own.
String text1 = diff_text1(diffs);
return patch_make(text1, diffs);
}
/**
* Compute a list of patches to turn text1 into text2.
* text2 is ignored, diffs are the delta between text1 and text2.
* @param text1 Old text
* @param text2 Ignored.
* @param diffs Array of Diff objects for text1 to text2.
* @return LinkedList of Patch objects.
* @deprecated Prefer patch_make(String text1, LinkedList<Diff> diffs).
*/
@Deprecated public LinkedList<Patch> patch_make(String text1, String text2,
LinkedList<Diff> diffs) {
return patch_make(text1, diffs);
}
/**
* Compute a list of patches to turn text1 into text2.
* text2 is not provided, diffs are the delta between text1 and text2.
* @param text1 Old text.
* @param diffs Array of Diff objects for text1 to text2.
* @return LinkedList of Patch objects.
*/
public LinkedList<Patch> patch_make(String text1, LinkedList<Diff> diffs) {
if (text1 == null || diffs == null) {
throw new IllegalArgumentException("Null inputs. (patch_make)");
}
LinkedList<Patch> patches = new LinkedList<Patch>();
if (diffs.isEmpty()) {
return patches; // Get rid of the null case.
}
Patch patch = new Patch();
int char_count1 = 0; // Number of characters into the text1 string.
int char_count2 = 0; // Number of characters into the text2 string.
// Start with text1 (prepatch_text) and apply the diffs until we arrive at
// text2 (postpatch_text). We recreate the patches one by one to determine
// context info.
String prepatch_text = text1;
String postpatch_text = text1;
for (Diff aDiff : diffs) {
if (patch.diffs.isEmpty() && aDiff.operation != Operation.EQUAL) {
// A new patch starts here.
patch.start1 = char_count1;
patch.start2 = char_count2;
}
switch (aDiff.operation) {
case INSERT:
patch.diffs.add(aDiff);
patch.length2 += aDiff.text.length();
postpatch_text = postpatch_text.substring(0, char_count2)
+ aDiff.text + postpatch_text.substring(char_count2);
break;
case DELETE:
patch.length1 += aDiff.text.length();
patch.diffs.add(aDiff);
postpatch_text = postpatch_text.substring(0, char_count2)
+ postpatch_text.substring(char_count2 + aDiff.text.length());
break;
case EQUAL:
if (aDiff.text.length() <= 2 * Patch_Margin
&& !patch.diffs.isEmpty() && aDiff != diffs.getLast()) {
// Small equality inside a patch.
patch.diffs.add(aDiff);
patch.length1 += aDiff.text.length();
patch.length2 += aDiff.text.length();
}
if (aDiff.text.length() >= 2 * Patch_Margin && !patch.diffs.isEmpty()) {
// Time for a new patch.
if (!patch.diffs.isEmpty()) {
patch_addContext(patch, prepatch_text);
patches.add(patch);
patch = new Patch();
// Unlike Unidiff, our patch lists have a rolling context.
// https://github.com/google/diff-match-patch/wiki/Unidiff
// Update prepatch text & pos to reflect the application of the
// just completed patch.
prepatch_text = postpatch_text;
char_count1 = char_count2;
}
}
break;
}
// Update the current character count.
if (aDiff.operation != Operation.INSERT) {
char_count1 += aDiff.text.length();
}
if (aDiff.operation != Operation.DELETE) {
char_count2 += aDiff.text.length();
}
}
// Pick up the leftover patch if not empty.
if (!patch.diffs.isEmpty()) {
patch_addContext(patch, prepatch_text);
patches.add(patch);
}
return patches;
}
/**
* Given an array of patches, return another array that is identical.
* @param patches Array of Patch objects.
* @return Array of Patch objects.
*/
public LinkedList<Patch> patch_deepCopy(LinkedList<Patch> patches) {
LinkedList<Patch> patchesCopy = new LinkedList<Patch>();
for (Patch aPatch : patches) {
Patch patchCopy = new Patch();
for (Diff aDiff : aPatch.diffs) {
Diff diffCopy = new Diff(aDiff.operation, aDiff.text);
patchCopy.diffs.add(diffCopy);
}
patchCopy.start1 = aPatch.start1;
patchCopy.start2 = aPatch.start2;
patchCopy.length1 = aPatch.length1;
patchCopy.length2 = aPatch.length2;
patchesCopy.add(patchCopy);
}
return patchesCopy;
}
/**
* Merge a set of patches onto the text. Return a patched text, as well
* as an array of true/false values indicating which patches were applied.
* @param patches Array of Patch objects
* @param text Old text.
* @return Two element Object array, containing the new text and an array of
* boolean values.
*/
public Object[] patch_apply(LinkedList<Patch> patches, String text) {
if (patches.isEmpty()) {
return new Object[]{text, new boolean[0]};
}
// Deep copy the patches so that no changes are made to originals.
patches = patch_deepCopy(patches);
String nullPadding = patch_addPadding(patches);
text = nullPadding + text + nullPadding;
patch_splitMax(patches);
int x = 0;
// delta keeps track of the offset between the expected and actual location
// of the previous patch. If there are patches expected at positions 10 and
// 20, but the first patch was found at 12, delta is 2 and the second patch
// has an effective expected position of 22.
int delta = 0;
boolean[] results = new boolean[patches.size()];
for (Patch aPatch : patches) {
int expected_loc = aPatch.start2 + delta;
String text1 = diff_text1(aPatch.diffs);
int start_loc;
int end_loc = -1;
if (text1.length() > this.Match_MaxBits) {
// patch_splitMax will only provide an oversized pattern in the case of
// a monster delete.
start_loc = match_main(text,
text1.substring(0, this.Match_MaxBits), expected_loc);
if (start_loc != -1) {
end_loc = match_main(text,
text1.substring(text1.length() - this.Match_MaxBits),
expected_loc + text1.length() - this.Match_MaxBits);
if (end_loc == -1 || start_loc >= end_loc) {
// Can't find valid trailing context. Drop this patch.
start_loc = -1;
}
}
} else {
start_loc = match_main(text, text1, expected_loc);
}
if (start_loc == -1) {
// No match found. :(
results[x] = false;
// Subtract the delta for this failed patch from subsequent patches.
delta -= aPatch.length2 - aPatch.length1;
} else {
// Found a match. :)
results[x] = true;
delta = start_loc - expected_loc;
String text2;
if (end_loc == -1) {
text2 = text.substring(start_loc,
Math.min(start_loc + text1.length(), text.length()));
} else {
text2 = text.substring(start_loc,
Math.min(end_loc + this.Match_MaxBits, text.length()));
}
if (text1.equals(text2)) {
// Perfect match, just shove the replacement text in.
text = text.substring(0, start_loc) + diff_text2(aPatch.diffs)
+ text.substring(start_loc + text1.length());
} else {
// Imperfect match. Run a diff to get a framework of equivalent
// indices.
LinkedList<Diff> diffs = diff_main(text1, text2, false);
if (text1.length() > this.Match_MaxBits
&& diff_levenshtein(diffs) / (float) text1.length()
> this.Patch_DeleteThreshold) {
// The end points match, but the content is unacceptably bad.
results[x] = false;
} else {
diff_cleanupSemanticLossless(diffs);
int index1 = 0;
for (Diff aDiff : aPatch.diffs) {
if (aDiff.operation != Operation.EQUAL) {
int index2 = diff_xIndex(diffs, index1);
if (aDiff.operation == Operation.INSERT) {
// Insertion
text = text.substring(0, start_loc + index2) + aDiff.text
+ text.substring(start_loc + index2);
} else if (aDiff.operation == Operation.DELETE) {
// Deletion
text = text.substring(0, start_loc + index2)
+ text.substring(start_loc + diff_xIndex(diffs,
index1 + aDiff.text.length()));
}
}
if (aDiff.operation != Operation.DELETE) {
index1 += aDiff.text.length();
}
}
}
}
}
x++;
}
// Strip the padding off.
text = text.substring(nullPadding.length(), text.length()
- nullPadding.length());
return new Object[]{text, results};
}