2
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
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]"
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.
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.
42
package org.netbeans.modules.diff.builtin.visualizer;
47
import javax.swing.text.*;
48
import java.awt.BorderLayout;
50
import org.openide.util.HelpCtx;
51
import org.openide.windows.*;
53
import org.netbeans.api.diff.Difference;
54
import org.netbeans.modules.diff.builtin.DiffPresenter;
56
//import org.netbeans.modules.vcscore.util.Debug;
57
//import org.netbeans.modules.vcscore.util.TopComponentCloseListener;
60
* This class displays two editor panes with two files and marks the differences
61
* by a different color.
63
* @author Martin Entlicher
65
public class DiffComponent extends org.openide.windows.TopComponent {
67
public static final java.awt.Color COLOR_MISSING = new java.awt.Color(255, 160, 180);
68
public static final java.awt.Color COLOR_ADDED = new java.awt.Color(180, 255, 180);
69
public static final java.awt.Color COLOR_CHANGED = new java.awt.Color(160, 200, 255);
71
//private AbstractDiff diff = null;
72
private Difference[] diffs = null;
73
/** The shift of differences */
74
private int[][] diffShifts;
75
private DiffPanel diffPanel = null;
77
private java.awt.Color colorMissing = COLOR_MISSING;
78
private java.awt.Color colorAdded = COLOR_ADDED;
79
private java.awt.Color colorChanged = COLOR_CHANGED;
81
//private ArrayList closeListeners = new ArrayList();
82
private int currentDiffLine = -1;
85
* Used for deserialization.
87
private boolean diffSetSuccess = true;
89
static final long serialVersionUID =3683458237532937983L;
92
* An empty constructor needed by deserialization process.
94
public DiffComponent() {
95
putClientProperty("PersistenceType", "Never");
98
/** Creates new DiffComponent from list of Difference objects */
99
public DiffComponent(final Difference[] diffs, final String mainTitle, final String mimeType,
100
final String sourceName1, final String sourceName2,
101
final String title1, final String title2,
102
final Reader r1, final Reader r2) {
103
this(diffs, mainTitle, mimeType, sourceName1, sourceName2, title1, title2,
107
/** Creates new DiffComponent from list of Difference objects */
108
public DiffComponent(final Difference[] diffs, final String mainTitle, final String mimeType,
109
final String sourceName1, final String sourceName2,
110
final String title1, final String title2,
111
final Reader r1, final Reader r2, java.awt.Color[] colors) {
113
diffShifts = new int[diffs.length][2];
114
setLayout(new BorderLayout());
115
diffPanel = new DiffPanel();
116
putClientProperty(DiffPresenter.PROP_TOOLBAR, diffPanel.getClientProperty(DiffPresenter.PROP_TOOLBAR));
117
diffPanel.addPrevLineButtonListener(new java.awt.event.ActionListener() {
118
public void actionPerformed(java.awt.event.ActionEvent evt) {
119
if (diffs.length == 0) return ;
121
if (currentDiffLine < 0) currentDiffLine = diffs.length - 1;
125
diffPanel.addNextLineButtonListener(new java.awt.event.ActionListener() {
126
public void actionPerformed(java.awt.event.ActionEvent evt) {
127
if (diffs.length == 0) return ;
129
if (currentDiffLine >= diffs.length) currentDiffLine = 0;
133
add(diffPanel, BorderLayout.CENTER);
135
if (colors != null && colors.length >= 3) {
136
colorMissing = colors[0];
137
colorAdded = colors[1];
138
colorChanged = colors[2];
140
// initComponents ();
141
//setTitle(org.openide.util.NbBundle.getBundle(DiffComponent.class).getString("DiffComponent.title"));
142
if (mainTitle == null) {
143
setName(org.openide.util.NbBundle.getBundle(DiffComponent.class).getString("DiffComponent.title"));
147
setIcon(org.openide.util.Utilities.loadImage("org/netbeans/modules/diff/diffSettingsIcon.gif", true));
148
initContent(mimeType, sourceName1, sourceName2, title1, title2, r1, r2);
149
//HelpCtx.setHelpIDString (getRootPane (), DiffComponent.class.getName ());
150
putClientProperty("PersistenceType", "Never");
153
public HelpCtx getHelpCtx() {
154
return new HelpCtx(DiffComponent.class);
159
private void showCurrentLine() {
160
if (currentDiffLine >= diffs.length) return;
161
Difference diff = diffs[currentDiffLine];
162
int line = diff.getFirstStart() + diffShifts[currentDiffLine][0];
163
if (diff.getType() == Difference.ADD) line++;
164
int lf1 = diff.getFirstEnd() - diff.getFirstStart() + 1;
165
int lf2 = diff.getSecondEnd() - diff.getSecondStart() + 1;
166
int length = Math.max(lf1, lf2);
167
diffPanel.setCurrentLine(line, length);
170
private void initContent(String mimeType, String sourceName1, String sourceName2,
171
String title1, String title2, Reader r1, Reader r2) {
172
setMimeType1(mimeType);
173
setMimeType2(mimeType);
177
} catch (IOException ioex) {
178
org.openide.ErrorManager.getDefault().notify(ioex);
180
setSource1Title(title1);
181
setSource2Title(title2);
182
insertEmptyLines(true);
183
setDiffHighlight(true);
184
insertEmptyLinesNotReported();
188
public void open(Workspace workspace) {
189
super.open(workspace);
191
if (currentDiffLine < 0) {
198
* Transfer the focus to the diff panel.
201
public void requestFocus() {
202
super.requestFocus();
203
diffPanel.requestFocus();
207
* Transfer the focus to the diff panel.
210
public boolean requestFocusInWindow() {
211
super.requestFocusInWindow();
212
return diffPanel.requestFocusInWindow();
215
public void addNotify() {
217
if (currentDiffLine < 0) {
218
javax.swing.SwingUtilities.invokeLater(new Runnable() {
221
javax.swing.SwingUtilities.invokeLater(new Runnable() {
223
java.awt.Toolkit.getDefaultToolkit().sync();
224
javax.swing.SwingUtilities.invokeLater(new Runnable() {
226
if (currentDiffLine < 0) {
240
* Override for clean up reasons.
241
* Will be moved to the appropriate method when will be made.
244
public boolean canClose(Workspace workspace, boolean last) {
245
boolean can = super.canClose(workspace, last);
252
public void removeNotify() {
253
System.out.println("removeNotify() called");
255
super.removeNotify();
259
public void setSource1(Reader r) throws IOException {
260
diffPanel.setSource1(r);
263
public void setSource2(Reader r) throws IOException {
264
diffPanel.setSource2(r);
267
public void setSource1Title(String title) {
268
diffPanel.setSource1Title(title);
271
public void setSource2Title(String title) {
272
diffPanel.setSource2Title(title);
275
public void setMimeType1(String mime) {
276
diffPanel.setMimeType1(mime);
279
public void setMimeType2(String mime) {
280
diffPanel.setMimeType2(mime);
283
public void setDocument1(Document doc) {
284
diffPanel.setDocument1(doc);
287
public void setDocument2(Document doc) {
288
diffPanel.setDocument2(doc);
291
public void unhighlightAll() {
292
diffPanel.unhighlightAll();
295
public void highlightRegion1(int line1, int line2, java.awt.Color color) {
296
//D.deb("Highlight region 1"); // NOI18N
297
diffPanel.highlightRegion1(line1, line2, color);
300
public void highlightRegion2(int line1, int line2, java.awt.Color color) {
301
//D.deb("Highlight region 2"); // NOI18N
302
diffPanel.highlightRegion2(line1, line2, color);
305
public void addEmptyLines1(int line, int numLines) {
306
diffPanel.addEmptyLines1(line, numLines);
309
public void addEmptyLines2(int line, int numLines) {
310
diffPanel.addEmptyLines2(line, numLines);
313
public void writeExternal(ObjectOutput out) throws IOException {
314
super.writeExternal(out);
316
out.writeObject(diffs);
318
} catch (IOException exc) {
319
System.out.println("exc = "+exc);
320
exc.printStackTrace();
326
private void insertEmptyLines(boolean updateActionLines) {
327
int n = diffs.length;
330
//D.deb("insertEmptyLines():"); // NOI18N
331
for(int i = 0; i < n; i++) {
332
Difference action = diffs[i];
333
int n1 = action.getFirstStart() + diffShifts[i][0];
334
int n2 = action.getFirstEnd() + diffShifts[i][0];
335
int n3 = action.getSecondStart() + diffShifts[i][1];
336
int n4 = action.getSecondEnd() + diffShifts[i][1];
337
//System.out.println("Action = "+action);
338
//System.out.println("ins1 = "+diffShifts[i][0]+", ins2 = "+diffShifts[i][1]);
339
if (updateActionLines && i < n - 1) {
340
diffShifts[i + 1][0] = diffShifts[i][0];
341
diffShifts[i + 1][1] = diffShifts[i][1];
343
switch (action.getType()) {
344
case Difference.DELETE:
345
addEmptyLines2(n3, n2 - n1 + 1);
346
if (updateActionLines && i < n - 1) {
347
diffShifts[i+1][1] += n2 - n1 + 1;
349
//ins2 += n2 - n1 + 1;
352
addEmptyLines1(n1, n4 - n3 + 1);
353
if (updateActionLines && i < n - 1) {
354
diffShifts[i+1][0] += n4 - n3 + 1;
356
//ins1 += n4 - n3 + 1;
358
case Difference.CHANGE:
362
addEmptyLines1(n2, r2 - r1);
363
if (updateActionLines && i < n - 1) {
364
diffShifts[i+1][0] += r2 - r1;
367
} else if (r1 > r2) {
368
addEmptyLines2(n4, r1 - r2);
369
if (updateActionLines && i < n - 1) {
370
diffShifts[i+1][1] += r1 - r2;
379
private void setDiffHighlight(boolean set) {
380
int n = diffs.length;
381
//D.deb("Num Actions = "+n); // NOI18N
382
for(int i = 0; i < n; i++) {
383
Difference action = diffs[i];
384
int n1 = action.getFirstStart() + diffShifts[i][0];
385
int n2 = action.getFirstEnd() + diffShifts[i][0];
386
int n3 = action.getSecondStart() + diffShifts[i][1];
387
int n4 = action.getSecondEnd() + diffShifts[i][1];
388
//D.deb("Action: "+action.getAction()+": ("+n1+","+n2+","+n3+","+n4+")"); // NOI18N
389
switch (action.getType()) {
390
case Difference.DELETE:
391
if (set) highlightRegion1(n1, n2, colorMissing);
392
else highlightRegion1(n1, n2, java.awt.Color.white);
395
if (set) highlightRegion2(n3, n4, colorAdded);
396
else highlightRegion2(n3, n4, java.awt.Color.white);
398
case Difference.CHANGE:
400
highlightRegion1(n1, n2, colorChanged);
401
highlightRegion2(n3, n4, colorChanged);
403
highlightRegion1(n1, n2, java.awt.Color.white);
404
highlightRegion2(n3, n4, java.awt.Color.white);
412
* Read one line from the given text, from the given position. It returns
413
* the end position of this line and the beginning of the next one.
414
* @param begin Contains just one value - IN: the beginning of the line to read.
415
* OUT: the start of the next line.
416
* @param end Contains just one value - OUT: the end of the line.
417
* @param text The text to read.
420
private static String readLine(int[] begin, int[] end, String text) {
421
int n = text.length();
422
for (int i = begin[0]; i < n; i++) {
423
char c = text.charAt(i);
424
if (c == '\n' || c == '\r') {
429
if (end[0] < begin[0]) end[0] = n;
430
String line = text.substring(begin[0], end[0]);
431
begin[0] = end[0] + 1;
432
if (begin[0] < n && text.charAt(end[0]) == '\r' && text.charAt(begin[0]) == '\n') begin[0]++;
437
* Find the first diff, that is on or below the given line number.
438
* @return The index of the desired difference in the supplied array or
439
* a value, that is bigger then the array size if such a diff does
442
private static int findDiffForLine(int lineNumber, int diffIndex, Difference[] diffs, int[][] diffShifts) {
443
while (diffIndex < diffs.length) {
444
if ((diffs[diffIndex].getFirstEnd() + diffShifts[diffIndex][0]) >= lineNumber ||
445
(diffs[diffIndex].getSecondEnd() + diffShifts[diffIndex][1]) >= lineNumber) break;
452
* Find out whether the line lies in the difference.
453
* @param lineNumber The number of the line.
454
* @param diff The difference
455
* @param diffShifts The shifts of the difference in the current document
456
* @return true if the line lies in the difference, false if does not.
458
private static boolean isLineInDiff(int lineNumber, Difference diff, int[] diffShifts) {
459
int l1 = diff.getFirstStart() + diffShifts[0];
460
int l2 = diff.getFirstEnd() + diffShifts[0];
461
int l3 = diff.getSecondStart() + diffShifts[1];
462
int l4 = diff.getSecondEnd() + diffShifts[1];
463
return (l1 <= lineNumber && ((l2 >= l1) ? (l2 >= lineNumber) : false)) ||
464
(l3 <= lineNumber && ((l4 >= l3) ? (l4 >= lineNumber) : false));
467
private static int numEmptyLines(int beginLine, String text, int endLine) {
468
if (endLine >= 0 && endLine <= beginLine) return 0;
470
int[] begin = { beginLine };
473
String line = readLine(begin, end, text);
474
if (line.trim().length() > 0) break;
476
} while ((endLine < 0 || beginLine + numLines < endLine) && begin[0] < text.length());
481
* We have to keep the balance of lines from the first and the second document,
482
* so that corresponding lines will have the same line number in the document.
483
* Because some diff providers can be set not to report changes in empty lines,
484
* we have to use heuristics to balance the corresponding lines manually
485
* if they do not match.
487
* This method goes through both documents and finds unreported differences
488
* in empty lines. Whenever it encounters an empty line with a corresponding
489
* non-empty line, which in not inside a difference (== unreported),
490
* it checks whether the following lines match (because there can be unreported
491
* difference in the amount of space rather than a missing or added line).
492
* If following lines "match", then silently add an empty line to the other
493
* document. This added line is not highlighted, since it was not reported
496
private void insertEmptyLinesNotReported() {
497
String docText1 = diffPanel.getDocumentText1();
498
String docText2 = diffPanel.getDocumentText2();
499
int[] begin1 = { 0 };
501
int[] begin2 = { 0 };
503
int n1 = docText1.length();
504
int n2 = docText2.length();
508
int lastBegin1 = begin1[0];
509
int lastBegin2 = begin2[0];
510
String line1 = readLine(begin1, end1, docText1);
511
String line2 = readLine(begin2, end2, docText2);
512
if (line1.length() == 0 && line2.length() > 0) {
513
//System.out.println("Detected empty line LEFT "+lineNumber);
514
diffIndex = findDiffForLine(lineNumber, diffIndex, diffs, diffShifts);
515
if (diffIndex >= diffs.length || !isLineInDiff(lineNumber, diffs[diffIndex], diffShifts[diffIndex])) {
516
boolean addMissingLine;
517
if (line2.trim().length() == 0) {
518
int emptyLines1 = numEmptyLines(begin1[0], docText1, (diffIndex < diffs.length) ? diffs[diffIndex].getFirstStart() : -1);
519
int emptyLines2 = numEmptyLines(begin2[0], docText2, (diffIndex < diffs.length) ? diffs[diffIndex].getSecondStart() : -1);
520
addMissingLine = emptyLines1 > emptyLines2;
521
//System.out.println("emptyLines1 = "+emptyLines1+", emptyLines2 = "+emptyLines2);
523
addMissingLine = true;
525
if (addMissingLine) {
526
addEmptyLines2(lineNumber - 1, 1);
527
//highlightRegion2(lineNumber, lineNumber, colorAdded);
528
shiftDiffs(false, lineNumber);
529
begin2[0] = lastBegin2;
530
end2[0] = lastBegin2 - 1;
533
} else if (line2.length() == 0 && line1.length() > 0) {
534
//System.out.println("Detected empty line RIGHT "+lineNumber);
535
diffIndex = findDiffForLine(lineNumber, diffIndex, diffs, diffShifts);
536
if (diffIndex >= diffs.length || !isLineInDiff(lineNumber, diffs[diffIndex], diffShifts[diffIndex])) {
537
boolean addMissingLine;
538
if (line1.trim().length() == 0) {
539
int emptyLines1 = numEmptyLines(begin1[0], docText1, (diffIndex < diffs.length) ? diffs[diffIndex].getFirstStart() : -1);
540
int emptyLines2 = numEmptyLines(begin2[0], docText2, (diffIndex < diffs.length) ? diffs[diffIndex].getSecondStart() : -1);
541
addMissingLine = emptyLines2 > emptyLines1;
542
//System.out.println("emptyLines1 = "+emptyLines1+", emptyLines2 = "+emptyLines2);
544
addMissingLine = true;
546
if (addMissingLine) {
547
addEmptyLines1(lineNumber - 1, 1);
548
//highlightRegion1(lineNumber, lineNumber, colorMissing);
549
shiftDiffs(true, lineNumber);
550
begin1[0] = lastBegin1;
551
end1[0] = lastBegin1 - 1;
556
} while (begin1[0] < n1 && begin2[0] < n2);
560
* Shift the differences by one in the first or the second document from the given line.
561
* @param inFirstDoc True to shift differences the first document, false for the second.
562
* @param fromLine The starting line. Shift all differences after this line.
564
private void shiftDiffs(boolean inFirstDoc, int fromLine) {
565
int n = diffs.length;
566
for(int i = 0; i < n; i++) {
567
Difference action = diffs[i];
569
if (action.getFirstStart() + diffShifts[i][0] >= fromLine) {
573
if (action.getSecondStart() + diffShifts[i][1] >= fromLine) {
580
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
581
super.readExternal(in);
582
Object obj = in.readObject();
583
diffs = (Difference[]) obj;
584
diffPanel = new DiffPanel();
585
//this.diffSetSuccess = diff.setDiffComponent(this);
588
private Object readResolve() throws ObjectStreamException {
589
if (this.diffSetSuccess) return this;
594
* Disable serialization.
596
protected Object writeReplace () throws java.io.ObjectStreamException {
601
/** Exit the Application */
602
private void exitForm(java.awt.event.WindowEvent evt) {
603
SwingUtilities.invokeLater(new Runnable() {
612
org.netbeans.editor.Settings.setValue(null, org.netbeans.editor.SettingsNames.LINE_NUMBER_VISIBLE, lineNumbersVisible);
613
} catch (Throwable exc) {
614
// editor module not found
617
//System.out.println("exitForm() called.");
622
for(Iterator it = closeListeners.iterator(); it.hasNext(); ) {
623
((TopComponentCloseListener) it.next()).closing();