1
20 package org.crosswire.common.diff;
21
22 import java.util.ArrayList;
23 import java.util.Iterator;
24 import java.util.List;
25 import java.util.regex.Matcher;
26 import java.util.regex.Pattern;
27
28
39 public class PatchEntry implements Iterable<Difference> {
40 public PatchEntry() {
42 this.diffs = new ArrayList<Difference>();
43 this.sourceStart = 0;
44 this.targetStart = 0;
45 this.sourceLength = 0;
46 this.targetLength = 0;
47 }
48
49 public PatchEntry(String patchText) {
51 this();
52 fromText(patchText);
53 }
54
55
58 public int getSourceStart() {
59 return sourceStart;
60 }
61
62
66 public void setSourceStart(int start) {
67 this.sourceStart = start;
68 }
69
70
74 public void adjustSourceStart(int adjustment) {
75 this.sourceStart += adjustment;
76 }
77
78
81 public int getTargetStart() {
82 return targetStart;
83 }
84
85
89 public void setTargetStart(int start) {
90 this.targetStart = start;
91 }
92
93
97 public void adjustTargetStart(int adjustment) {
98 this.targetStart += adjustment;
99 }
100
101
104 public int getSourceLength() {
105 return sourceLength;
106 }
107
108
112 public void setSourceLength(int length) {
113 this.sourceLength = length;
114 }
115
116
120 public void adjustSourceLength(int adjustment) {
121 this.sourceLength += adjustment;
122 }
123
124
127 public int getTargetLength() {
128 return targetLength;
129 }
130
131
135 public void setTargetLength(int length) {
136 this.targetLength = length;
137 }
138
139
143 public void adjustTargetLength(int adjustment) {
144 this.targetLength += adjustment;
145 }
146
147 @Override
151 public String toString() {
152 StringBuilder txt = new StringBuilder();
153 txt.append("@@ -");
154 txt.append(getCoordinates(sourceStart, sourceLength));
155 txt.append(" +");
156 txt.append(getCoordinates(targetStart, targetLength));
157 txt.append(" @@\n");
158
159 for (Difference diff : diffs) {
160 txt.append(diff.getEditType().getSymbol());
161 txt.append(encode(diff.getText()));
162 txt.append('\n');
163 }
164 return txt.toString();
174 }
175
176
184 public PatchEntry fromText(String input) {
185 diffs.clear();
186 String[] text = newlinePattern.split(input);
187 char sign = '\0';
188 String line = "";
189
190 Matcher matcher = patchPattern.matcher(text[0]);
191 matcher.matches();
192 assert matcher.groupCount() == 4 : "Invalid patch string:\n" + text[0];
193
195 sourceStart = Integer.parseInt(matcher.group(1));
196
197 if (matcher.group(2).length() == 0) {
198 sourceStart--;
199 sourceLength = 1;
200 } else if (matcher.group(2).charAt(0) == '0') {
201 setSourceLength(0);
202 } else {
203 sourceStart--;
204 sourceLength = Integer.parseInt(matcher.group(2));
205 }
206
207 targetStart = Integer.parseInt(matcher.group(3));
208 if (matcher.group(4).length() == 0) {
209 targetStart--;
210 targetLength = 1;
211 } else if (matcher.group(4).charAt(0) == '0') {
212 targetLength = 0;
213 } else {
214 targetStart--;
215 targetLength = Integer.parseInt(matcher.group(4));
216 }
217
218 for (int lineCount = 1; lineCount < text.length; lineCount++) {
219 line = text[lineCount];
220 if (line.length() > 0) {
221 sign = line.charAt(0);
222 line = decode(line.substring(1));
223 diffs.add(new Difference(EditType.fromSymbol(sign), line));
224 }
225 }
226 return this;
227 }
228
229 public String getSourceText() {
231 StringBuilder txt = new StringBuilder();
232 for (Difference diff : diffs) {
233 if (!EditType.INSERT.equals(diff.getEditType())) {
234 txt.append(diff.getText());
235 }
236 }
237 return txt.toString();
238 }
239
240 public String getTargetText() {
242 StringBuilder txt = new StringBuilder();
243 for (Difference diff : diffs) {
244 if (!EditType.DELETE.equals(diff.getEditType())) {
245 txt.append(diff.getText());
246 }
247 }
248 return txt.toString();
249 }
250
251 public void addContext(String text) {
252 int maxPatternLength = new Match().maxPatternLength();
253 int padding = 0;
254 String pattern = text.substring(targetStart, targetStart + sourceLength);
255 int textLength = text.length();
256
257 int end = maxPatternLength - PatchEntry.margin - PatchEntry.margin;
261 while (text.indexOf(pattern) != text.lastIndexOf(pattern) && pattern.length() < end) {
262 padding += PatchEntry.margin;
263 pattern = text.substring(Math.max(0, targetStart - padding), Math.min(textLength, targetStart + sourceLength + padding));
264 }
265
266 padding += PatchEntry.margin;
268
269 String prefix = text.substring(Math.max(0, targetStart - padding), targetStart);
271 int prefixLength = prefix.length();
272 if (prefixLength > 0) {
273 diffs.add(0, new Difference(EditType.EQUAL, prefix));
274 }
275
276 String suffix = text.substring(targetStart + sourceLength, Math.min(textLength, targetStart + sourceLength + padding));
278 int suffixLength = suffix.length();
279 if (suffixLength > 0) {
280 diffs.add(new Difference(EditType.EQUAL, suffix));
281 }
282
283 sourceStart -= prefixLength;
285 targetStart -= prefixLength;
286
287 sourceLength += prefixLength + suffixLength;
289 targetLength += prefixLength + suffixLength;
290 }
291
292 public void addDifference(Difference diff) {
293 diffs.add(diff);
294 }
295
296 public int getDifferenceCount() {
297 return diffs.size();
298 }
299
300 public boolean hasDifferences() {
301 return !diffs.isEmpty();
302 }
303
304 public Iterator<Difference> iterator() {
305 return diffs.iterator();
306 }
307
308 public Difference getFirstDifference() {
309 if (diffs.isEmpty()) {
310 return null;
311 }
312 return diffs.get(0);
313 }
314
315 public Difference removeFirstDifference() {
316 if (diffs.isEmpty()) {
317 return null;
318 }
319 return diffs.remove(0);
320 }
321
322 public Difference getLastDifference() {
323 if (diffs.isEmpty()) {
324 return null;
325 }
326 return diffs.get(diffs.size() - 1);
327 }
328
329 protected void setDifferences(List<Difference> newDiffs) {
330 diffs = newDiffs;
331 }
332
333
337 public static void setMargin(int newMargin) {
338 PatchEntry.margin = newMargin;
339 }
340
341
344 public static int getMargin() {
345 return margin;
346 }
347
348 private String getCoordinates(int start, int length) {
349 StringBuilder buf = new StringBuilder();
350
351 if (length == 0) {
352 buf.append(start);
353 buf.append(",0");
354 } else if (length == 1) {
355 buf.append(sourceStart + 1);
356 } else {
357 buf.append(start + 1);
358 buf.append(',');
359 buf.append(length);
360 }
361
362 return buf.toString();
363 }
364
365
374 private String encode(String str) {
375 int strlen = str.length();
376 StringBuilder buf = new StringBuilder(2 * strlen);
377 for (int i = 0; i < strlen; i++) {
378 char c = str.charAt(i);
379 switch (c) {
380 case '%':
381 buf.append("%25");
382 break;
383 case '\n':
384 buf.append("%0A");
385 break;
386 default:
387 buf.append(c);
388 }
389 }
390 return buf.toString();
391 }
392
393
400 private String decode(String str) {
401 int strlen = str.length();
402 StringBuilder buf = new StringBuilder(2 * strlen);
403 int i = 0;
404 for (i = 0; i < strlen; i++) {
405 char c = str.charAt(i);
406 if (c == '%') {
407 if ("%0A".equals(str.substring(i, i + 3))) {
408 buf.append('\n');
409 } else { buf.append('%');
411 }
412 i += 2;
413 } else {
414 buf.append(c);
415 }
416 }
417 return buf.toString();
418 }
419
420
423 private static final int MARGIN = 4;
424 private static int margin = MARGIN;
425 private static Pattern newlinePattern = Pattern.compile("\n");
426 private static Pattern patchPattern = Pattern.compile("^@@ -(\\d+),?(\\d*) \\+(\\d+),?(\\d*) @@$");
427
428 private List<Difference> diffs;
429 private int sourceStart;
430 private int targetStart;
431 private int sourceLength;
432 private int targetLength;
433 }
434