~ubuntu-branches/ubuntu/quantal/netbeans/quantal

« back to all changes in this revision

Viewing changes to diff/src/org/netbeans/modules/diff/builtin/Patch.java

  • Committer: Bazaar Package Importer
  • Author(s): Marek Slama
  • Date: 2008-01-29 14:11:22 UTC
  • Revision ID: james.westby@ubuntu.com-20080129141122-fnzjbo11ntghxfu7
Tags: upstream-6.0.1
ImportĀ upstreamĀ versionĀ 6.0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 
3
 *
 
4
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 
5
 *
 
6
 * The contents of this file are subject to the terms of either the GNU
 
7
 * General Public License Version 2 only ("GPL") or the Common
 
8
 * Development and Distribution License("CDDL") (collectively, the
 
9
 * "License"). You may not use this file except in compliance with the
 
10
 * License. You can obtain a copy of the License at
 
11
 * http://www.netbeans.org/cddl-gplv2.html
 
12
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
 
13
 * specific language governing permissions and limitations under the
 
14
 * License.  When distributing the software, include this License Header
 
15
 * Notice in each file and include the License file at
 
16
 * nbbuild/licenses/CDDL-GPL-2-CP.  Sun designates this
 
17
 * particular file as subject to the "Classpath" exception as provided
 
18
 * by Sun in the GPL Version 2 section of the License file that
 
19
 * accompanied this code. If applicable, add the following below the
 
20
 * License Header, with the fields enclosed by brackets [] replaced by
 
21
 * your own identifying information:
 
22
 * "Portions Copyrighted [year] [name of copyright owner]"
 
23
 *
 
24
 * Contributor(s):
 
25
 *
 
26
 * The Original Software is NetBeans. The Initial Developer of the Original
 
27
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
 
28
 * Microsystems, Inc. All Rights Reserved.
 
29
 *
 
30
 * If you wish your version of this file to be governed by only the CDDL
 
31
 * or only the GPL Version 2, indicate your decision by adding
 
32
 * "[Contributor] elects to include this software in this distribution
 
33
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
 
34
 * single choice of license, a recipient has the option to distribute
 
35
 * your version of this file under either the CDDL, the GPL Version 2 or
 
36
 * to extend the choice of license to its licensees as provided above.
 
37
 * However, if you add GPL Version 2 code and therefore, elected the GPL
 
38
 * Version 2 license, then the option applies only if the new code is
 
39
 * made subject to such option by the copyright holder.
 
40
 */
 
41
 
 
42
package org.netbeans.modules.diff.builtin;
 
43
 
 
44
import java.io.BufferedReader;
 
45
import java.io.PushbackReader;
 
46
import java.io.Reader;
 
47
import java.io.IOException;
 
48
import java.util.ArrayList;
 
49
import java.util.List;
 
50
import java.util.Iterator;
 
51
import java.util.regex.Pattern;
 
52
import java.util.regex.PatternSyntaxException;
 
53
 
 
54
import org.netbeans.api.diff.Difference;
 
55
 
 
56
import org.netbeans.modules.diff.cmdline.CmdlineDiffProvider;
 
57
 
 
58
/**
 
59
 * Utility class for patch application.
 
60
 *
 
61
 * @author  Martin Entlicher
 
62
 */
 
63
public class Patch extends Reader {
 
64
 
 
65
    private static final int CONTEXT_DIFF = 0;
 
66
    private static final int NORMAL_DIFF = 1;
 
67
    private static final int UNIFIED_DIFF = 2;
 
68
    
 
69
    private Difference[] diffs;
 
70
    private PushbackReader source;
 
71
    private int currDiff = 0;
 
72
    private int line = 1;
 
73
    private String newLine = null; // String, that is used to separate lines
 
74
    private StringBuffer buff = new StringBuffer();
 
75
    
 
76
    /** Creates a new instance of Patch */
 
77
    private Patch(Difference[] diffs, Reader source) {
 
78
        this.diffs = diffs;
 
79
        this.source = new PushbackReader(new BufferedReader(source), 1);
 
80
    }
 
81
    
 
82
    /**
 
83
     * Apply the patch to the source.
 
84
     * @param diffs The differences to patch
 
85
     * @param source The source stream
 
86
     * @return The patched stream
 
87
     * @throws IOException When reading from the source stread fails
 
88
     * @throws ParseException When the source does not match the patch to be applied
 
89
     */
 
90
    public static Reader apply(Difference[] diffs, Reader source) {//throws IOException, ParseException {
 
91
        return new Patch(diffs, source);
 
92
    }
 
93
    
 
94
    /**
 
95
     * Parse the differences.
 
96
     *
 
97
    public static Difference[] parse(Reader source) throws IOException {
 
98
        return parseContextDiff(source);
 
99
    }
 
100
     */
 
101
    
 
102
    /**
 
103
     * Parse the differences and corresponding file names.
 
104
     */
 
105
    public static FileDifferences[] parse(Reader source) throws IOException {
 
106
        List<FileDifferences> fileDifferences = new ArrayList<FileDifferences>();
 
107
        //int pushBackLimit = DIFFERENCE_DELIMETER.length();
 
108
        //PushbackReader recognizedSource = new PushbackReader(source, pushBackLimit);
 
109
        Patch.SinglePatchReader patchReader = new Patch.SinglePatchReader(source);
 
110
        int[] diffType = new int[1];
 
111
        String[] fileName = new String[2];
 
112
        while (patchReader.hasNextPatch(diffType, fileName)) {
 
113
            //System.out.println("Have a next patch of name '"+fileName[0]+"'");
 
114
            Difference[] diffs = null;
 
115
            switch (diffType[0]) {
 
116
                case CONTEXT_DIFF:
 
117
                    diffs = parseContextDiff(patchReader);
 
118
                    break;
 
119
                case UNIFIED_DIFF:
 
120
                    diffs = parseUnifiedDiff(patchReader);
 
121
                    break;
 
122
                case NORMAL_DIFF:
 
123
                    diffs = parseNormalDiff(patchReader);
 
124
                    break;
 
125
            }
 
126
            if (diffs != null) {
 
127
                fileDifferences.add(new FileDifferences(fileName[0], fileName[1], diffs));
 
128
            }
 
129
        }
 
130
        return fileDifferences.toArray(new FileDifferences[fileDifferences.size()]);
 
131
    }
 
132
    
 
133
    public int read(char[] cbuf, int off, int length) throws java.io.IOException {
 
134
        if (buff.length() < length) {
 
135
            doRetrieve(length - buff.length());
 
136
        }
 
137
        int ret = Math.min(buff.length(), length);
 
138
        if (ret == 0) return -1;
 
139
        String retStr = buff.substring(0, ret);
 
140
        char[] retChars = retStr.toCharArray();
 
141
        System.arraycopy(retChars, 0, cbuf, off, ret);
 
142
        buff.delete(0, ret);
 
143
        return ret;
 
144
    }
 
145
    
 
146
    public void close() throws java.io.IOException {
 
147
        if (currDiff < diffs.length) {
 
148
            throw new IOException("There are " + (diffs.length - currDiff) + " pending hunks!");
 
149
        }
 
150
        source.close();
 
151
    }
 
152
    
 
153
    private void doRetrieve(int length) throws IOException {
 
154
        for (int size = 0; size < length; line++) {
 
155
            if (currDiff < diffs.length &&
 
156
                ((Difference.ADD == diffs[currDiff].getType() &&
 
157
                  line == (diffs[currDiff].getFirstStart() + 1)) ||
 
158
                 (Difference.ADD != diffs[currDiff].getType() &&
 
159
                  line == diffs[currDiff].getFirstStart()))) {
 
160
                if (compareText(source, diffs[currDiff].getFirstText())) {
 
161
                    String text = convertNewLines(diffs[currDiff].getSecondText(), newLine);
 
162
                    buff.append(text);
 
163
                    currDiff++;
 
164
                } else {
 
165
                    throw new IOException("Patch not applicable.");
 
166
                }
 
167
            }
 
168
            StringBuffer newLineBuffer = null;
 
169
            if (newLine == null) {
 
170
                newLineBuffer = new StringBuffer();
 
171
            }
 
172
            String lineStr = readLine(source, newLineBuffer);
 
173
            if (newLineBuffer != null) newLine = newLineBuffer.toString();
 
174
            if (lineStr == null) break;
 
175
            buff.append(lineStr);
 
176
            buff.append(newLine);
 
177
        }
 
178
    }
 
179
    
 
180
    /** Reads a line and returns the char sequence for newline */
 
181
    private static String readLine(PushbackReader r, StringBuffer nl) throws IOException {
 
182
        StringBuffer line = new StringBuffer();
 
183
        int ic = r.read();
 
184
        if (ic == -1) return null;
 
185
        char c = (char) ic;
 
186
        while (c != '\n' && c != '\r') {
 
187
            line.append(c);
 
188
            ic = r.read();
 
189
            if (ic == -1) break;
 
190
            c = (char) ic;
 
191
        }
 
192
        if (nl != null) {
 
193
            nl.append(c);
 
194
        }
 
195
        if (c == '\r') {
 
196
            try {
 
197
                ic = r.read();
 
198
                if (ic != -1) {
 
199
                    c = (char) ic;
 
200
                    if (c != '\n') r.unread(c);
 
201
                    else if (nl != null) nl.append(c);
 
202
                }
 
203
            } catch (IOException ioex) {}
 
204
        }
 
205
        return line.toString();
 
206
    }
 
207
    
 
208
    private static String convertNewLines(String text, String newLine) {
 
209
        if (text == null) return ""; // NOI18N
 
210
        if (newLine == null) return text;
 
211
        StringBuffer newText = new StringBuffer();
 
212
        for (int i = 0; i < text.length(); i++) {
 
213
            char c = text.charAt(i);
 
214
            if (c == '\n') newText.append(newLine);
 
215
            else if (c == '\r') {
 
216
                if ((i + 1) < text.length() && text.charAt(i + 1) == '\n') {
 
217
                    i++;
 
218
                    newText.append(newLine);
 
219
                }
 
220
            } else newText.append(c);
 
221
        }
 
222
        return newText.toString();
 
223
    }
 
224
    
 
225
    private boolean compareText(PushbackReader source, String text) throws IOException {
 
226
        if (text == null || text.length() == 0) return true;
 
227
        text = adjustTextNL(text);
 
228
        char[] chars = new char[text.length()];
 
229
        int pos = 0;
 
230
        int n;
 
231
        String readStr = "";
 
232
        do {
 
233
            n = source.read(chars, 0, chars.length - pos);
 
234
            if (n > 0) {
 
235
                pos += n;
 
236
                readStr = readStr + new String(chars, 0, n);
 
237
            }
 
238
            if (readStr.endsWith("\r")) {
 
239
                try {
 
240
                    char c = (char) source.read();
 
241
                    if (c != '\n') source.unread(c);
 
242
                    else readStr += c;
 
243
                } catch (IOException ioex) {}
 
244
            }
 
245
            readStr = adjustTextNL(readStr);
 
246
            pos = readStr.length();
 
247
        } while (n > 0 && pos < chars.length);
 
248
        readStr.getChars(0, readStr.length(), chars, 0);
 
249
        line += numChars('\n', chars);
 
250
        //System.out.println("Comparing text of the diff:\n'"+text+"'\nWith the read text:\n'"+readStr+"'\n");
 
251
        //System.out.println("  EQUALS = "+readStr.equals(text));
 
252
        return readStr.equals(text);
 
253
    }
 
254
    
 
255
    /**
 
256
     * When comparing the two texts, it's important to ignore different line endings.
 
257
     * This method assures, that only '\n' is used as the line ending.
 
258
     */
 
259
    private String adjustTextNL(String text) {
 
260
        text = org.openide.util.Utilities.replaceString(text, "\r\n", "\n");
 
261
        text = org.openide.util.Utilities.replaceString(text, "\n\r", "\n");
 
262
        text = org.openide.util.Utilities.replaceString(text, "\r", "\n");
 
263
        return text;
 
264
    }
 
265
    
 
266
    private static int numChars(char c, char[] chars) {
 
267
        int n = 0;
 
268
        for (int i = 0; i < chars.length; i++) {
 
269
            if (chars[i] == c) n++;
 
270
        }
 
271
        return n;
 
272
    }
 
273
    
 
274
    private static final String CONTEXT_MARK1B = "*** ";
 
275
//    private static final String CONTEXT_MARK1E = " ****";
 
276
    private static final String CONTEXT_MARK2B = "--- ";
 
277
//    private static final String CONTEXT_MARK2E = " ----";
 
278
    private static final String CONTEXT_MARK_DELIMETER = ",";
 
279
    private static final String DIFFERENCE_DELIMETER = "***************";
 
280
//    private static final String LINE_PREP = "  ";
 
281
    private static final String LINE_PREP_ADD = "+ ";
 
282
    private static final String LINE_PREP_REMOVE = "- ";
 
283
    private static final String LINE_PREP_CHANGE = "! ";
 
284
    
 
285
    private static Difference[] parseContextDiff(Reader in) throws IOException {
 
286
        BufferedReader br = new BufferedReader(in);
 
287
        ArrayList<Difference> diffs = new ArrayList<Difference>();
 
288
        String line = null;
 
289
        do {
 
290
            if (line == null || !DIFFERENCE_DELIMETER.equals(line)) {
 
291
                do {
 
292
                    line = br.readLine();
 
293
                } while (line != null && !DIFFERENCE_DELIMETER.equals(line));
 
294
            }
 
295
            int[] firstInterval = new int[2];
 
296
            line = br.readLine();
 
297
            if (line != null && line.startsWith(CONTEXT_MARK1B)) {
 
298
                try {
 
299
                    readNums(line, CONTEXT_MARK1B.length(), firstInterval);
 
300
                } catch (NumberFormatException nfex) {
 
301
                    throw new IOException(nfex.getLocalizedMessage());
 
302
                }
 
303
            } else continue;
 
304
            ArrayList<Object> firstChanges = new ArrayList<Object>(); // List of intervals and texts
 
305
            line = fillChanges(firstInterval, br, CONTEXT_MARK2B, firstChanges);
 
306
            int[] secondInterval = new int[2];
 
307
            if (line != null && line.startsWith(CONTEXT_MARK2B)) {
 
308
                try {
 
309
                    readNums(line, CONTEXT_MARK2B.length(), secondInterval);
 
310
                } catch (NumberFormatException nfex) {
 
311
                    throw new IOException(nfex.getLocalizedMessage());
 
312
                }
 
313
            } else continue;
 
314
            ArrayList<Object> secondChanges = new ArrayList<Object>(); // List of intervals and texts
 
315
            line = fillChanges(secondInterval, br, DIFFERENCE_DELIMETER, secondChanges);
 
316
            if (changesCountInvariant(firstChanges, secondChanges) == false) {
 
317
                throw new IOException("Diff file format error. Number of new and old file changes in one hunk must be same!");   // NOI18N
 
318
            }
 
319
            mergeChanges(firstInterval, secondInterval, firstChanges, secondChanges, diffs);
 
320
        } while (line != null);
 
321
        return diffs.toArray(new Difference[diffs.size()]);
 
322
    }
 
323
 
 
324
 
 
325
    private static boolean changesCountInvariant(List<Object> changes1, List<Object> changes2) { // both are Union<int[],String>
 
326
        int i1 = 0;
 
327
        Iterator it = changes1.iterator();
 
328
        while (it.hasNext()) {
 
329
            int[] ints = (int[]) it.next();
 
330
            if (ints[2] == 2) {
 
331
                i1++;
 
332
            }
 
333
            String skip = (String) it.next();
 
334
        }
 
335
 
 
336
        int i2 = 0;
 
337
        it = changes2.iterator();
 
338
        while (it.hasNext()) {
 
339
            int[] ints = (int[]) it.next();
 
340
            if (ints[2] == 2) {
 
341
                i2++;
 
342
            }
 
343
            String skip = (String) it.next();
 
344
        }
 
345
 
 
346
        return i1 == i2;
 
347
    }
 
348
 
 
349
    private static void readNums(String str, int off, int[] values) throws NumberFormatException {
 
350
        int end = str.indexOf(CONTEXT_MARK_DELIMETER, off);
 
351
        if (end > 0) {
 
352
            values[0] = Integer.parseInt(str.substring(off, end).trim());
 
353
        } else throw new NumberFormatException("Missing comma.");
 
354
        off = end + 1;
 
355
        end = str.indexOf(' ', off);
 
356
        if (end > 0) {
 
357
            values[1] = Integer.parseInt(str.substring(off, end).trim());
 
358
        } else throw new NumberFormatException("Missing final space.");
 
359
    }
 
360
 
 
361
    private static String fillChanges(int[] interval, BufferedReader br,
 
362
                                      String untilStartsWith, List<Object/* int[3] or String*/> changes) throws IOException {
 
363
        String line = br.readLine();
 
364
        for (int pos = interval[0]; pos <= interval[1]; pos++) {
 
365
            if (line == null || line.startsWith(untilStartsWith)) break;
 
366
            if (line.startsWith(LINE_PREP_ADD)) {
 
367
                int[] changeInterval = new int[3];
 
368
                changeInterval[0] = pos;
 
369
                changeInterval[2] = Difference.ADD;
 
370
                StringBuffer changeText = new StringBuffer();
 
371
                changeText.append(line.substring(LINE_PREP_ADD.length()));
 
372
                changeText.append('\n');
 
373
                do {
 
374
                    line = br.readLine();
 
375
                    if (line == null)
 
376
                        break;
 
377
                    if (line.startsWith(LINE_PREP_ADD)) {
 
378
                        changeText.append(line.substring(LINE_PREP_ADD.length()));
 
379
                        changeText.append('\n');
 
380
                    } else {
 
381
                        break;
 
382
                    }
 
383
                    pos++;
 
384
                } while (true);
 
385
                changeInterval[1] = pos;
 
386
                changes.add(changeInterval);
 
387
                changes.add(changeText.toString());
 
388
            } else if (line.startsWith(LINE_PREP_REMOVE)) {
 
389
                int[] changeInterval = new int[3];
 
390
                changeInterval[0] = pos;
 
391
                changeInterval[2] = Difference.DELETE;
 
392
                StringBuffer changeText = new StringBuffer();
 
393
                changeText.append(line.substring(LINE_PREP_REMOVE.length()));
 
394
                changeText.append('\n');
 
395
                do {
 
396
                    line = br.readLine();
 
397
                    if (line == null)
 
398
                        break;
 
399
                    if (line.startsWith(LINE_PREP_REMOVE)) {
 
400
                        changeText.append(line.substring(LINE_PREP_REMOVE.length()));
 
401
                        changeText.append('\n');
 
402
                    } else {
 
403
                        break;
 
404
                    }
 
405
                    pos++;
 
406
                } while (true);
 
407
                changeInterval[1] = pos;
 
408
                changes.add(changeInterval);
 
409
                changes.add(changeText.toString());
 
410
            } else if (line.startsWith(LINE_PREP_CHANGE)) {
 
411
                int[] changeInterval = new int[3];
 
412
                changeInterval[0] = pos;
 
413
                changeInterval[2] = Difference.CHANGE;
 
414
                StringBuffer changeText = new StringBuffer();
 
415
                changeText.append(line.substring(LINE_PREP_CHANGE.length()));
 
416
                changeText.append('\n');
 
417
                do {
 
418
                    line = br.readLine();
 
419
                    if (line == null)
 
420
                        break;
 
421
                    if (line.startsWith(LINE_PREP_CHANGE)) {
 
422
                        changeText.append(line.substring(LINE_PREP_CHANGE.length()));
 
423
                        changeText.append('\n');
 
424
                    } else {
 
425
                        break;
 
426
                    }
 
427
                    pos++;
 
428
                } while (true);
 
429
                changeInterval[1] = pos;
 
430
                changes.add(changeInterval);
 
431
                changes.add(changeText.toString());
 
432
            } else {
 
433
                line = br.readLine();
 
434
            }
 
435
        }
 
436
        return line;
 
437
    }
 
438
    
 
439
    private static void mergeChanges(int[] firstInterval, int[] secondInterval,
 
440
                              List firstChanges, List secondChanges, List<Difference> diffs) {
 
441
 
 
442
 
 
443
        int p1, p2;
 
444
        int n1 = firstChanges.size();
 
445
        int n2 = secondChanges.size();
 
446
        //System.out.println("mergeChanges(("+firstInterval[0]+", "+firstInterval[1]+"), ("+secondInterval[0]+", "+secondInterval[1]+"))");
 
447
        //System.out.println("firstChanges.size() = "+n1);
 
448
        //System.out.println("secondChanges.size() = "+n2);
 
449
        int firstToSecondIntervalShift = secondInterval[0] - firstInterval[0];
 
450
        //System.out.println("shift = "+firstToSecondIntervalShift);
 
451
        for (p1 = p2 = 0; p1 < n1 || p2 < n2; ) {
 
452
            boolean isAddRemove = true;
 
453
            while (isAddRemove && p1 < n1) {
 
454
                int[] interval = (int[]) firstChanges.get(p1);
 
455
                if (p2 < n2) {
 
456
                    int[] interval2 = (int[]) secondChanges.get(p2);
 
457
                    if (interval[0] + firstToSecondIntervalShift > interval2[0]) {
 
458
                        break;
 
459
                    }
 
460
                    // We need to set differences successively. Differences with
 
461
                    // higher line numbers must not precede differences with
 
462
                    // smaller line numbers
 
463
                }
 
464
                isAddRemove = interval[2] == Difference.ADD || interval[2] == Difference.DELETE;
 
465
                if (isAddRemove) {
 
466
                    if (interval[2] == Difference.ADD) {
 
467
                        diffs.add(new Difference(interval[2], interval[0] - 1, 0,
 
468
                                                 interval[0] + firstToSecondIntervalShift,
 
469
                                                 interval[1] + firstToSecondIntervalShift,
 
470
                                                 (String) firstChanges.get(p1 + 1), ""));
 
471
                        firstToSecondIntervalShift += interval[1] - interval[0] + 1;
 
472
                    } else {
 
473
                        diffs.add(new Difference(interval[2], interval[0], interval[1],
 
474
                                                 interval[0] + firstToSecondIntervalShift - 1, 0,
 
475
                                                 (String) firstChanges.get(p1 + 1), ""));
 
476
                        firstToSecondIntervalShift -= interval[1] - interval[0] + 1;
 
477
                    }
 
478
                    p1 += 2;
 
479
                    //System.out.println("added diff = "+diffs.get(diffs.size() - 1));
 
480
                    //System.out.println("new shift = "+firstToSecondIntervalShift);
 
481
                }
 
482
            }
 
483
            isAddRemove = true;
 
484
            while (isAddRemove && p2 < n2) {
 
485
                int[] interval = (int[]) secondChanges.get(p2);
 
486
                isAddRemove = interval[2] == Difference.ADD || interval[2] == Difference.DELETE;
 
487
                if (isAddRemove) {
 
488
                    if (interval[2] == Difference.ADD) {
 
489
                        diffs.add(new Difference(interval[2],
 
490
                                                 interval[0] - firstToSecondIntervalShift - 1, 0,
 
491
                                                 interval[0], interval[1],
 
492
                                                 "", (String) secondChanges.get(p2 + 1)));
 
493
                        firstToSecondIntervalShift += interval[1] - interval[0] + 1;
 
494
                    } else {
 
495
                        diffs.add(new Difference(interval[2],
 
496
                                                 interval[0] - firstToSecondIntervalShift,
 
497
                                                 interval[1] - firstToSecondIntervalShift,
 
498
                                                 interval[0] - 1, 0,
 
499
                                                 "", (String) secondChanges.get(p2 + 1)));
 
500
                        firstToSecondIntervalShift -= interval[1] - interval[0] + 1;
 
501
                    }
 
502
                    p2 += 2;
 
503
                    //System.out.println("added diff = "+diffs.get(diffs.size() - 1));
 
504
                    //System.out.println("new shift = "+firstToSecondIntervalShift);
 
505
                }
 
506
            }
 
507
            // Change is remaining
 
508
            if (p1 < n1 && p2 < n2) {
 
509
                int[] interval1 = (int[]) firstChanges.get(p1);
 
510
                if (interval1[2] == Difference.CHANGE) {  // double check the break above
 
511
                    int[] interval2 = (int[]) secondChanges.get(p2);
 
512
                    diffs.add(new Difference(interval1[2], interval1[0], interval1[1],
 
513
                                             interval2[0], interval2[1],
 
514
                                             (String) firstChanges.get(p1 + 1),
 
515
                                             (String) secondChanges.get(p2 + 1)));
 
516
                    p1 += 2;
 
517
                    p2 += 2;
 
518
                    firstToSecondIntervalShift += interval2[1] - interval2[0] - (interval1[1] - interval1[0]);
 
519
                    //System.out.println("added diff = "+diffs.get(diffs.size() - 1));
 
520
                    //System.out.println("new shift = "+firstToSecondIntervalShift);
 
521
                }
 
522
            }
 
523
        }
 
524
    }
 
525
    
 
526
    private static final String UNIFIED_MARK = "@@";
 
527
    private static final String UNIFIED_MARK1 = "--- ";
 
528
//    private static final String UNIFIED_MARK2 = "+++ ";
 
529
    private static final String LINE_PREP_UNIF_ADD = "+";
 
530
    private static final String LINE_PREP_UNIF_REMOVE = "-";
 
531
//    private static final String LINE_PREP_UNIF_CHANGE = null;
 
532
 
 
533
    private static Difference[] parseUnifiedDiff(Reader in) throws IOException {
 
534
        BufferedReader br = new BufferedReader(in);
 
535
        List<Difference> diffs = new ArrayList<Difference>();
 
536
        String line = null;
 
537
        do {
 
538
            while (line == null || !(line.startsWith(UNIFIED_MARK) &&
 
539
                                     line.length() > UNIFIED_MARK.length() &&
 
540
                                     line.endsWith(UNIFIED_MARK))) {
 
541
                line = br.readLine();
 
542
                if (line == null) break;
 
543
            }
 
544
            if (line == null) continue;
 
545
            int[] intervals = new int[4];
 
546
            try {
 
547
                readUnifiedNums(line, UNIFIED_MARK.length(), intervals);
 
548
            } catch (NumberFormatException nfex) {
 
549
                IOException ioex = new IOException("Can not parse: " + line);
 
550
                ioex.initCause(nfex);
 
551
                throw ioex;
 
552
            }
 
553
            line = fillUnidifChanges(intervals, br, diffs);
 
554
        } while (line != null);
 
555
        return diffs.toArray(new Difference[diffs.size()]);
 
556
    }
 
557
    
 
558
    private static void readUnifiedNums(String str, int off, int[] values) throws NumberFormatException {
 
559
        while (str.charAt(off) == ' ' || str.charAt(off) == '-') off++;
 
560
        int end = str.indexOf(CONTEXT_MARK_DELIMETER, off);
 
561
        if (end > 0) {
 
562
            values[0] = Integer.parseInt(str.substring(off, end).trim());
 
563
        } else throw new NumberFormatException("Missing comma.");
 
564
        off = end + 1;
 
565
        end = str.indexOf(' ', off);
 
566
        if (end > 0) {
 
567
            values[1] = Integer.parseInt(str.substring(off, end).trim());
 
568
        } else throw new NumberFormatException("Missing middle space.");
 
569
        off = end + 1;
 
570
        while (str.charAt(off) == ' ' || str.charAt(off) == '+') off++;
 
571
        end = str.indexOf(CONTEXT_MARK_DELIMETER, off);
 
572
        if (end > 0) {
 
573
            values[2] = Integer.parseInt(str.substring(off, end).trim());
 
574
        } else throw new NumberFormatException("Missing second comma.");
 
575
        off = end + 1;
 
576
        end = str.indexOf(' ', off);
 
577
        if (end > 0) {
 
578
            values[3] = Integer.parseInt(str.substring(off, end).trim());
 
579
        } else throw new NumberFormatException("Missing final space.");
 
580
        values[1] += values[0] - 1;
 
581
        values[3] += values[2] - 1;
 
582
    }
 
583
 
 
584
    private static String fillUnidifChanges(int[] interval, BufferedReader br,
 
585
                                            List<Difference> diffs) throws IOException {
 
586
        String line = br.readLine();
 
587
        int pos1 = interval[0];
 
588
        int pos2 = interval[2];
 
589
        while (line != null && pos1 <= interval[1] && pos2 <= interval[3]) {
 
590
            if (line.startsWith(LINE_PREP_UNIF_ADD)) {
 
591
                int begin = pos2;
 
592
                StringBuffer changeText = new StringBuffer();
 
593
                changeText.append(line.substring(LINE_PREP_UNIF_ADD.length()));
 
594
                changeText.append('\n');
 
595
                do {
 
596
                    line = br.readLine();
 
597
                    pos2++;
 
598
                    if (line.startsWith(LINE_PREP_UNIF_ADD)) {
 
599
                        changeText.append(line.substring(LINE_PREP_UNIF_ADD.length()));
 
600
                        changeText.append('\n');
 
601
                    } else {
 
602
                        break;
 
603
                    }
 
604
                } while (true);
 
605
                Difference diff = null;
 
606
                if (diffs.size() > 0) {
 
607
                    Difference previousDiff = (Difference) diffs.get(diffs.size() - 1);
 
608
                    if (Difference.DELETE == previousDiff.getType() && previousDiff.getFirstEnd() == (pos1 - 1)) {
 
609
                        diff = new Difference(Difference.CHANGE,
 
610
                            previousDiff.getFirstStart(), previousDiff.getFirstEnd(),
 
611
                            begin, pos2 - 1, previousDiff.getFirstText(), changeText.toString());
 
612
                        diffs.remove(diffs.size() - 1);
 
613
                    }
 
614
                }
 
615
                if (diff == null) {
 
616
                    diff = new Difference(Difference.ADD, pos1 - 1, 0, begin, pos2 - 1, null, changeText.toString());
 
617
                }
 
618
                diffs.add(diff);
 
619
            } else if (line.startsWith(LINE_PREP_UNIF_REMOVE)) {
 
620
                int begin = pos1;
 
621
                StringBuffer changeText = new StringBuffer();
 
622
                changeText.append(line.substring(LINE_PREP_UNIF_REMOVE.length()));
 
623
                changeText.append('\n');
 
624
                do {
 
625
                    line = br.readLine();
 
626
                    pos1++;
 
627
                    if (line.startsWith(LINE_PREP_UNIF_REMOVE)) {
 
628
                        changeText.append(line.substring(LINE_PREP_UNIF_REMOVE.length()));
 
629
                        changeText.append('\n');
 
630
                    } else {
 
631
                        break;
 
632
                    }
 
633
                } while (true);
 
634
                Difference diff = null;
 
635
                if (diffs.size() > 0) {
 
636
                    Difference previousDiff = (Difference) diffs.get(diffs.size() - 1);
 
637
                    if (Difference.ADD == previousDiff.getType() && previousDiff.getSecondEnd() == (pos2 - 1)) {
 
638
                        diff = new Difference(Difference.CHANGE, begin, pos1 - 1,
 
639
                            previousDiff.getFirstStart(), previousDiff.getFirstEnd(),
 
640
                            changeText.toString(), previousDiff.getFirstText());
 
641
                        diffs.remove(diffs.size() - 1);
 
642
                    }
 
643
                }
 
644
                if (diff == null) {
 
645
                    diff = new Difference(Difference.DELETE, begin, pos1 - 1, pos2 - 1, 0, changeText.toString(), null);
 
646
                }
 
647
                diffs.add(diff);
 
648
            } else {
 
649
                line = br.readLine();
 
650
                pos1++;
 
651
                pos2++;
 
652
            }
 
653
        }
 
654
        return line;
 
655
    }
 
656
    
 
657
    private static Difference[] parseNormalDiff(Reader in) throws IOException {
 
658
        Pattern normRegexp;
 
659
        try {
 
660
            normRegexp = Pattern.compile(CmdlineDiffProvider.DIFF_REGEXP);
 
661
        } catch (PatternSyntaxException rsex) {
 
662
            normRegexp = null;
 
663
        }
 
664
        StringBuffer firstText = new StringBuffer();
 
665
        StringBuffer secondText = new StringBuffer();
 
666
        BufferedReader br = new BufferedReader(in);
 
667
        List<Difference> diffs = new ArrayList<Difference>();
 
668
        String line;
 
669
        while ((line = br.readLine()) != null) {
 
670
            CmdlineDiffProvider.outputLine(line, normRegexp, diffs, firstText, secondText);
 
671
        }
 
672
        CmdlineDiffProvider.setTextOnLastDifference(diffs, firstText, secondText);
 
673
        return diffs.toArray(new Difference[diffs.size()]);
 
674
    }
 
675
    
 
676
    /**
 
677
     * A reader, that will not read more, than a single patch content
 
678
     * from the supplied reader with possibly more patches.
 
679
     */
 
680
    private static class SinglePatchReader extends Reader {
 
681
        
 
682
        private static final int BUFF_SIZE = 512;
 
683
        private PushbackReader in;
 
684
        private char[] buffer = new char[BUFF_SIZE];
 
685
        private int buffLength = 0;
 
686
        private int buffPos = 0;
 
687
        private boolean isAtEndOfPatch = false;
 
688
        
 
689
        public SinglePatchReader(Reader in) {
 
690
            this.in = new PushbackReader(in, BUFF_SIZE);
 
691
        }
 
692
        
 
693
        public int read(char[] values, int offset, int length) throws java.io.IOException {
 
694
            //System.out.println("SinglePatchReader.read("+offset+", "+length+")");
 
695
            int totRead = 0;
 
696
            while (length > 0) {
 
697
                int buffCopyLength;
 
698
                if (length < buffLength) {
 
699
                    buffCopyLength = length;
 
700
                    length = 0;
 
701
                } else {
 
702
                    if (buffLength > 0) {
 
703
                        buffCopyLength = buffLength;
 
704
                        length -= buffLength;
 
705
                    } else {
 
706
                        if (isAtEndOfPatch) {
 
707
                            length = 0;
 
708
                            buffCopyLength = -1;
 
709
                        } else {
 
710
                            buffLength = readTillEndOfPatch(buffer);
 
711
                            buffPos = 0;
 
712
                            if (buffLength <= 0) {
 
713
                                buffCopyLength = -1;
 
714
                            } else {
 
715
                                buffCopyLength = Math.min(length, buffLength);
 
716
                                length -= buffCopyLength;
 
717
                            }
 
718
                        }
 
719
                    }
 
720
                }
 
721
                if (buffCopyLength > 0) {
 
722
                    System.arraycopy(buffer, buffPos, values, offset, buffCopyLength);
 
723
                    offset += buffCopyLength;
 
724
                    buffLength -= buffCopyLength;
 
725
                    buffPos += buffCopyLength;
 
726
                    totRead += buffCopyLength;
 
727
                } else {
 
728
                    length = 0;
 
729
                }
 
730
            }
 
731
            if (totRead == 0) totRead = -1;
 
732
            //System.out.println("  read = '"+((totRead >= 0) ? new String(values, 0, totRead) : "NOTHING")+"', totRead = "+totRead);
 
733
            return totRead;
 
734
        }
 
735
        
 
736
        private int readTillEndOfPatch(char[] buffer) throws IOException {
 
737
            int length = in.read(buffer);
 
738
            String input = new String(buffer);
 
739
            int end = 0;
 
740
            if (input.startsWith(FILE_INDEX) || ((end = input.indexOf("\n"+FILE_INDEX))) >= 0) {
 
741
                isAtEndOfPatch = true;
 
742
            } else {
 
743
                end = input.lastIndexOf('\n');
 
744
                if (end >= 0) end++;
 
745
            }
 
746
            if (end >= 0 && end < length) {
 
747
                in.unread(buffer, end, length - end);
 
748
                length = end;
 
749
            }
 
750
            if (end == 0) length = -1;
 
751
            return length;
 
752
        }
 
753
        
 
754
        public void close() throws java.io.IOException {
 
755
            // Do nothing!
 
756
        }
 
757
        
 
758
        private static final String FILE_INDEX = "Index: "; // NOI18N
 
759
        
 
760
        private boolean hasNextPatch(int[] diffType, String[] fileName) throws IOException {
 
761
            isAtEndOfPatch = false; // We're prepared for the next patch
 
762
            PushbackReader patchSource = in;
 
763
            char[] buff = new char[DIFFERENCE_DELIMETER.length()];
 
764
            int length;
 
765
            Pattern normRegexp;
 
766
            boolean contextBeginDetected = false;
 
767
            try {
 
768
                normRegexp = Pattern.compile(CmdlineDiffProvider.DIFF_REGEXP);
 
769
            } catch (PatternSyntaxException rsex) {
 
770
                normRegexp = null;
 
771
            }
 
772
            while ((length = patchSource.read(buff)) > 0) {
 
773
                String input = new String(buff, 0, length);
 
774
                int nl;
 
775
                int nln = input.indexOf('\n');
 
776
                int nlr = input.indexOf('\r');
 
777
                if (nln < 0) nl = nlr;
 
778
                else nl = nln;
 
779
                if (nl >= 0) {
 
780
                    if (nln > 0 && nln == nlr + 1) {
 
781
                        input = input.substring(0, nl - 1);
 
782
                    } else {
 
783
                        input = input.substring(0, nl);
 
784
                    }
 
785
                    if (nl + 1 < length) {
 
786
                        patchSource.unread(buff, nl + 1, length - (nl + 1));
 
787
                        length = nl + 1;
 
788
                    }
 
789
                }
 
790
                if (input.equals(DIFFERENCE_DELIMETER)) {
 
791
                    diffType[0] = CONTEXT_DIFF;
 
792
                    patchSource.unread(buff, 0, length);
 
793
                    return true;
 
794
                } else if (input.startsWith(UNIFIED_MARK + " ")) {
 
795
                    diffType[0] = UNIFIED_DIFF;
 
796
                    patchSource.unread(buff, 0, length);
 
797
                    return true;
 
798
                } else if (input.startsWith(FILE_INDEX)) {
 
799
                    StringBuffer name = new StringBuffer(input.substring(FILE_INDEX.length()));
 
800
                    if (nl < 0) {
 
801
                        int r;
 
802
                        char c;
 
803
                        while ((c = (char) (r = patchSource.read())) != '\n' && r != -1 && r != '\r') {
 
804
                            name.append(c);
 
805
                        }
 
806
                    }
 
807
                    fileName[1] = name.toString();
 
808
                } else if (input.startsWith(CONTEXT_MARK1B) || !contextBeginDetected && input.startsWith(UNIFIED_MARK1)) {
 
809
                    StringBuffer name;
 
810
                    if (input.startsWith(CONTEXT_MARK1B)) {
 
811
                        contextBeginDetected = true;
 
812
                        name = new StringBuffer(input.substring(CONTEXT_MARK1B.length()));
 
813
                    } else {
 
814
                        name = new StringBuffer(input.substring(UNIFIED_MARK1.length()));
 
815
                    }
 
816
                    String sname = name.toString();
 
817
                    int spaceIndex = sname.indexOf('\t');
 
818
                    if (spaceIndex > 0) {
 
819
                        name = name.delete(spaceIndex, name.length());
 
820
                    }
 
821
                    if (nl < 0) {
 
822
                        int r = 0;
 
823
                        char c = 0;
 
824
                        if (spaceIndex < 0) {
 
825
                            while ((c = (char) (r = patchSource.read())) != '\n' && c != '\r' && c != '\t' && r != -1) {
 
826
                                name.append(c);
 
827
                            }
 
828
                        }
 
829
                        if (c != '\n' && c != '\r' && r != -1) {
 
830
                            while ((c = (char) (r = patchSource.read())) != '\n' && c != '\r' && r != -1) ; // Read the rest of the line
 
831
                        }
 
832
                        if (c == '\r') {
 
833
                            r = patchSource.read();
 
834
                            if (r != -1) {
 
835
                                c = (char) r;
 
836
                                if (c != '\n') patchSource.unread(c);
 
837
                            }
 
838
                        }
 
839
                    }
 
840
                    fileName[0] = name.toString();
 
841
                } else if (normRegexp != null && normRegexp.matcher(input).matches()) {
 
842
                    diffType[0] = NORMAL_DIFF;
 
843
                    patchSource.unread(buff, 0, length);
 
844
                    return true;
 
845
                } else { // Read the rest of the garbaged line
 
846
                    if (nl < 0) {
 
847
                        int r;
 
848
                        char c;
 
849
                        while ((c = (char) (r = patchSource.read())) != '\n' && c != '\r' && r != -1) ;
 
850
                        if (c == '\r') {
 
851
                            r = patchSource.read();
 
852
                            if (r != -1) {
 
853
                                c = (char) r;
 
854
                                if (c != '\n') patchSource.unread(c);
 
855
                            }
 
856
                        }
 
857
                    }
 
858
                }
 
859
            }
 
860
            return false;
 
861
        }
 
862
        
 
863
    }
 
864
    
 
865
    public static class FileDifferences extends Object {
 
866
        
 
867
        private String fileName;
 
868
        private String indexName;
 
869
        private Difference[] diffs;
 
870
        
 
871
        public FileDifferences(String fileName, String indexName, Difference[] diffs) {
 
872
            this.fileName = fileName;
 
873
            this.diffs = diffs;
 
874
            this.indexName = indexName;
 
875
        }
 
876
 
 
877
        /**
 
878
         * @return header filename (typically absolute path on source host) or null
 
879
         */
 
880
        public final String getFileName() {
 
881
            return fileName;
 
882
        }
 
883
 
 
884
        /**
 
885
         * @return relative Index: file name or null
 
886
         */
 
887
        public final String getIndexName() {
 
888
            return indexName;
 
889
        }
 
890
 
 
891
        public final Difference[] getDifferences() {
 
892
            return diffs;
 
893
        }
 
894
    }
 
895
    
 
896
}