1
20 package org.crosswire.common.diff;
21
22 import java.util.ArrayList;
23 import java.util.List;
24 import java.util.ListIterator;
25 import java.util.regex.Matcher;
26 import java.util.regex.Pattern;
27
28
38 public class Patch {
39
42 public Patch() {
43 patches = new ArrayList<PatchEntry>();
44 margin = PatchEntry.getMargin();
45 }
46
47
53 public Patch(String input) {
54 this();
55 fromText(input);
56 }
57
58
66 public Patch(String source, String target) {
67 this(source, target, null);
68 }
69
70
81 public Patch(String source, String target, List<Difference> diffs) {
82 this();
83 make(source, target, diffs);
84 }
85
86
98 public Patch make(String source, String target, List<Difference> diffList) {
99 List<Difference> diffs = diffList;
100 if (diffs == null) {
101 Diff diff = new Diff(source, target);
102 diffs = diff.compare();
103 if (diffs.size() > 2) {
104 DiffCleanup.cleanupSemantic(diffs);
105 DiffCleanup.cleanupEfficiency(diffs);
106 }
107 }
108
109 patches.clear();
110
111 if (diffs.isEmpty()) {
112 return this; }
114
115 PatchEntry patch = new PatchEntry();
116 int charCount1 = 0; int charCount2 = 0; String prePatchText = source;
120 String postPatchText = source;
121 int x = 0;
122 for (Difference diff : diffs) {
123 EditType editType = diff.getEditType();
124 String diffText = diff.getText();
125 int len = diffText.length();
126
127 if (!patch.hasDifferences() && !EditType.EQUAL.equals(editType)) {
128 patch.setSourceStart(charCount1);
130 patch.setTargetStart(charCount2);
131 }
132
133 if (EditType.INSERT.equals(editType)) {
134 patch.addDifference(diff);
136 patch.adjustTargetLength(len);
137 postPatchText = postPatchText.substring(0, charCount2) + diffText + postPatchText.substring(charCount2);
138 } else if (EditType.DELETE.equals(editType)) {
139 patch.adjustSourceLength(len);
141 patch.addDifference(diff);
142 postPatchText = postPatchText.substring(0, charCount2) + postPatchText.substring(charCount2 + len);
143 } else if (EditType.EQUAL.equals(editType) && len <= 2 * margin && patch.hasDifferences() && diffs.size() != x + 1) {
144 patch.addDifference(diff);
146 patch.adjustSourceLength(len);
147 patch.adjustTargetLength(len);
148 }
149
150 if (EditType.EQUAL.equals(editType) && len >= 2 * margin && patch.hasDifferences()) {
152 patch.addContext(prePatchText);
153 patches.add(patch);
154 patch = new PatchEntry();
155 prePatchText = postPatchText;
156 }
157
158 if (!EditType.INSERT.equals(editType)) {
160 charCount1 += len;
161 }
162
163 if (!EditType.DELETE.equals(editType)) {
164 charCount2 += len;
165 }
166
167 x++;
168 }
169
170 if (patch.hasDifferences()) {
172 patch.addContext(prePatchText);
173 patches.add(patch);
174 }
175
176 return this;
177 }
178
179
187 public PatchResults apply(String text) {
188 splitMax();
189 boolean[] results = new boolean[patches.size()];
190 String resultText = text;
191 int delta = 0;
192 int expectedLoc = 0;
193 int startLoc = -1;
194 String text1 = "";
195 String text2 = "";
196 List<Difference> diffs;
197 int index1 = 0;
198 int index2 = 0;
199 int x = 0;
200 for (PatchEntry aPatch : patches) {
201 expectedLoc = aPatch.getTargetStart() + delta;
202 text1 = aPatch.getSourceText();
203 Match match = new Match(resultText, text1, expectedLoc);
204 startLoc = match.locate();
205 if (startLoc == -1) {
206 results[x] = false;
208 } else {
209 results[x] = true;
211 delta = startLoc - expectedLoc;
212 text2 = resultText.substring(startLoc, startLoc + text1.length());
213 if (text1.equals(text2)) {
214 resultText = resultText.substring(0, startLoc) + aPatch.getTargetText() + resultText.substring(startLoc + text1.length());
216 } else {
217 Diff diff = new Diff(text1, text2, false);
220 diffs = diff.compare();
221 index1 = 0;
222 for (Difference aDiff : aPatch) {
223 EditType editType = aDiff.getEditType();
224 if (!EditType.EQUAL.equals(editType)) {
225 index2 = diff.xIndex(diffs, index1);
226 }
227
228 if (EditType.INSERT.equals(editType)) {
229 resultText = resultText.substring(0, startLoc + index2) + aDiff.getText() + resultText.substring(startLoc + index2);
231 } else if (EditType.DELETE.equals(editType)) {
232 resultText = resultText.substring(0, startLoc + index2)
234 + resultText.substring(startLoc + diff.xIndex(diffs, index1 + aDiff.getText().length()));
235 }
236
237 if (!EditType.DELETE.equals(editType)) {
238 index1 += aDiff.getText().length();
239 }
240 }
241 }
242 }
243 x++;
244 }
245 return new PatchResults(resultText, results);
246 }
247
248
252 public void splitMax() {
253 int maxPatternLength = new Match().maxPatternLength();
254 ListIterator<PatchEntry> pointer = patches.listIterator();
255 PatchEntry bigPatch = pointer.hasNext() ? pointer.next() : null;
256 while (bigPatch != null) {
257 if (bigPatch.getSourceLength() <= maxPatternLength) {
258 if (!pointer.hasNext()) {
259 break;
260 }
261
262 bigPatch = pointer.next();
263 }
264
265 pointer.remove();
267 int patchSize = maxPatternLength;
268 int start1 = bigPatch.getSourceStart();
269 int start2 = bigPatch.getTargetStart();
270 String preContext = "";
271 while (bigPatch.hasDifferences()) {
272 PatchEntry patch = new PatchEntry();
274 boolean empty = true;
275
276 int len = preContext.length();
277 patch.setSourceStart(start1 - len);
278 patch.setTargetStart(start2 - len);
279 if (len > 0) {
280 patch.setSourceLength(len);
281 patch.setTargetLength(len);
282 patch.addDifference(new Difference(EditType.EQUAL, preContext));
283 }
284
285 while (bigPatch.hasDifferences() && patch.getSourceLength() < patchSize - margin) {
286 Difference bigDiff = bigPatch.getFirstDifference();
287 EditType editType = bigDiff.getEditType();
288 String diffText = bigDiff.getText();
289 if (EditType.INSERT.equals(editType)) {
290 len = diffText.length();
292 patch.adjustTargetLength(len);
293 start2 += len;
294 patch.addDifference(bigPatch.removeFirstDifference());
295 empty = false;
296 } else {
297 diffText = diffText.substring(0, Math.min(diffText.length(), patchSize - patch.getSourceLength() - margin));
300 len = diffText.length();
301 patch.adjustSourceLength(len);
302 start1 += len;
303 if (EditType.EQUAL.equals(editType)) {
304 patch.adjustTargetLength(len);
305 start2 += len;
306 } else {
307 empty = false;
308 }
309
310 patch.addDifference(new Difference(editType, diffText));
311
312 if (diffText.equals(bigDiff.getText())) {
313 bigPatch.removeFirstDifference();
314 } else {
315 bigDiff.setText(bigDiff.getText().substring(len));
316 }
317 }
318 }
319
320 preContext = patch.getTargetText();
322 preContext = preContext.substring(Math.max(0, preContext.length() - margin));
323
324 String postcontext = null;
326 if (bigPatch.getSourceText().length() > margin) {
327 postcontext = bigPatch.getSourceText().substring(0, margin);
328 } else {
329 postcontext = bigPatch.getSourceText();
330 }
331 if (postcontext.length() > 0) {
332 patch.adjustSourceLength(postcontext.length());
333 patch.adjustTargetLength(postcontext.length());
334 if (patch.getDifferenceCount() > 0 && EditType.EQUAL.equals(patch.getLastDifference().getEditType())) {
335 Difference diff = patch.getLastDifference();
336 diff.appendText(postcontext);
337 } else {
338 patch.addDifference(new Difference(EditType.EQUAL, postcontext));
339 }
340 }
341
342 if (!empty) {
343 pointer.add(patch);
344 }
345 }
346
347 bigPatch = pointer.hasNext() ? pointer.next() : null;
348 }
349 }
350
351
356 public String toText() {
357 StringBuilder text = new StringBuilder();
358 for (PatchEntry entry : patches) {
359 text.append(entry);
360 }
361 return text.toString();
362 }
363
364
372 public Patch fromText(String input) {
373 patches.clear();
374
375 Matcher m = patchBoundaryPattern.matcher(input);
376
377 int index = 0;
379 while (m.find()) {
380 int start = m.start();
381 String match = input.substring(index, start);
382 patches.add(new PatchEntry(match));
383 index = start + 1;
384 }
385
386 if (index == 0) {
387 patches.add(new PatchEntry(input));
389 } else {
390 patches.add(new PatchEntry(input.substring(index)));
392 }
393
394 return this;
395 }
396
397
401 public static class PatchResults {
402
406 public PatchResults(String text, boolean[] results) {
407 this.text = text;
408 this.results = results.clone();
409 }
410
411
414 public boolean[] getResults() {
415 return results.clone();
416 }
417
418
421 public String getText() {
422 return text;
423 }
424
425 private String text;
426 private boolean[] results;
427 }
428
429 private static Pattern patchBoundaryPattern = Pattern.compile("\n@@");
432
433 private List<PatchEntry> patches;
434 private int margin;
435 }
436