001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.modules.merge.builtin.visualizer;
043:
044: import java.awt.Color;
045: import java.awt.event.ActionEvent;
046: import java.awt.event.ActionListener;
047: import java.beans.PropertyChangeEvent;
048: import java.beans.PropertyVetoException;
049: import java.beans.VetoableChangeListener;
050: import java.io.IOException;
051: import java.util.ArrayList;
052: import java.util.Set;
053: import java.util.HashSet;
054:
055: import org.openide.util.NbBundle;
056:
057: import org.netbeans.api.diff.Difference;
058: import org.netbeans.api.diff.StreamSource;
059:
060: /**
061: * This class controls the merge process.
062: *
063: * @author Martin Entlicher
064: */
065: public class MergeControl extends Object implements ActionListener,
066: VetoableChangeListener {
067:
068: private Color colorUnresolvedConflict;
069: private Color colorResolvedConflict;
070: private Color colorOtherConflict;
071:
072: private MergePanel panel;
073: private Difference[] diffs;
074: /** The shift of differences */
075: private int[][] diffShifts;
076: /** The current diff */
077: private int currentDiffLine = 0;
078: private int[] resultDiffLocations;
079: private Set<Difference> resolvedConflicts = new HashSet<Difference>();
080: private StreamSource resultSource;
081:
082: private boolean firstNewlineIsFake;
083: private boolean secondNewlineIsFake;
084:
085: /** Creates a new instance of MergeControl */
086: public MergeControl(MergePanel panel) {
087: this .panel = panel;
088: }
089:
090: public void initialize(Difference[] diffs, StreamSource source1,
091: StreamSource source2, StreamSource result,
092: Color colorUnresolvedConflict, Color colorResolvedConflict,
093: Color colorOtherConflict) {
094: if (diffs.length > 0) {
095: // all lines must end with a newline
096: Difference ld = diffs[diffs.length - 1];
097: if (!ld.getFirstText().endsWith("\n")) {
098: firstNewlineIsFake = true;
099: }
100: if (!ld.getSecondText().endsWith("\n")) {
101: secondNewlineIsFake = true;
102: }
103: if (firstNewlineIsFake || secondNewlineIsFake) {
104: diffs[diffs.length - 1] = new Difference(ld.getType(),
105: ld.getFirstStart(), ld.getFirstEnd(), ld
106: .getSecondStart(), ld.getSecondEnd(),
107: ld.getFirstText()
108: + (firstNewlineIsFake ? "\n" : ""), ld
109: .getSecondText()
110: + (secondNewlineIsFake ? "\n" : ""));
111: }
112: }
113: this .diffs = diffs;
114: this .diffShifts = new int[diffs.length][2];
115: this .resultDiffLocations = new int[diffs.length];
116: panel.setMimeType1(source1.getMIMEType());
117: panel.setMimeType2(source2.getMIMEType());
118: panel.setMimeType3(result.getMIMEType());
119: panel.setSource1Title(source1.getTitle());
120: panel.setSource2Title(source2.getTitle());
121: panel.setResultSourceTitle(result.getTitle());
122: panel.setName(source1.getName());
123: try {
124: panel.setSource1(source1.createReader());
125: panel.setSource2(source2.createReader());
126: panel.setResultSource(new java.io.StringReader(""));
127: } catch (IOException ioex) {
128: org.openide.ErrorManager.getDefault().notify(ioex);
129: }
130: this .colorUnresolvedConflict = colorUnresolvedConflict;
131: this .colorResolvedConflict = colorResolvedConflict;
132: this .colorOtherConflict = colorOtherConflict;
133: insertEmptyLines(true);
134: setDiffHighlight(true);
135: copyToResult();
136: panel.setNumConflicts(diffs.length);
137: panel.addControlActionListener(this );
138: showCurrentLine();
139: this .resultSource = result;
140: }
141:
142: private void insertEmptyLines(boolean updateActionLines) {
143: int n = diffs.length;
144: for (int i = 0; i < n; i++) {
145: Difference action = diffs[i];
146: int n1 = action.getFirstStart() + diffShifts[i][0];
147: int n2 = action.getFirstEnd() + diffShifts[i][0];
148: int n3 = action.getSecondStart() + diffShifts[i][1];
149: int n4 = action.getSecondEnd() + diffShifts[i][1];
150: if (updateActionLines && i < n - 1) {
151: diffShifts[i + 1][0] = diffShifts[i][0];
152: diffShifts[i + 1][1] = diffShifts[i][1];
153: }
154: switch (action.getType()) {
155: case Difference.DELETE:
156: panel.addEmptyLines2(n3, n2 - n1 + 1);
157: if (updateActionLines && i < n - 1) {
158: diffShifts[i + 1][1] += n2 - n1 + 1;
159: }
160: break;
161: case Difference.ADD:
162: panel.addEmptyLines1(n1, n4 - n3 + 1);
163: if (updateActionLines && i < n - 1) {
164: diffShifts[i + 1][0] += n4 - n3 + 1;
165: }
166: break;
167: case Difference.CHANGE:
168: int r1 = n2 - n1;
169: int r2 = n4 - n3;
170: if (r1 < r2) {
171: panel.addEmptyLines1(n2, r2 - r1);
172: if (updateActionLines && i < n - 1) {
173: diffShifts[i + 1][0] += r2 - r1;
174: }
175: } else if (r1 > r2) {
176: panel.addEmptyLines2(n4, r1 - r2);
177: if (updateActionLines && i < n - 1) {
178: diffShifts[i + 1][1] += r1 - r2;
179: }
180: }
181: break;
182: }
183: }
184: }
185:
186: private void setDiffHighlight(boolean set) {
187: int n = diffs.length;
188: //D.deb("Num Actions = "+n); // NOI18N
189: for (int i = 0; i < n; i++) {
190: Difference action = diffs[i];
191: int n1 = action.getFirstStart() + diffShifts[i][0];
192: int n2 = action.getFirstEnd() + diffShifts[i][0];
193: int n3 = action.getSecondStart() + diffShifts[i][1];
194: int n4 = action.getSecondEnd() + diffShifts[i][1];
195: //D.deb("Action: "+action.getAction()+": ("+n1+","+n2+","+n3+","+n4+")"); // NOI18N
196: switch (action.getType()) {
197: case Difference.DELETE:
198: if (set)
199: panel.highlightRegion1(n1, n2,
200: colorUnresolvedConflict);
201: else
202: panel
203: .highlightRegion1(n1, n2,
204: java.awt.Color.white);
205: break;
206: case Difference.ADD:
207: if (set)
208: panel.highlightRegion2(n3, n4,
209: colorUnresolvedConflict);
210: else
211: panel
212: .highlightRegion2(n3, n4,
213: java.awt.Color.white);
214: break;
215: case Difference.CHANGE:
216: if (set) {
217: panel.highlightRegion1(n1, n2,
218: colorUnresolvedConflict);
219: panel.highlightRegion2(n3, n4,
220: colorUnresolvedConflict);
221: } else {
222: panel
223: .highlightRegion1(n1, n2,
224: java.awt.Color.white);
225: panel
226: .highlightRegion2(n3, n4,
227: java.awt.Color.white);
228: }
229: break;
230: }
231: }
232: }
233:
234: private void copyToResult() {
235: int n = diffs.length;
236: int line1 = 1;
237: int line3 = 1;
238: for (int i = 0; i < n; i++) {
239: Difference action = diffs[i];
240: int n1 = action.getFirstStart() + diffShifts[i][0];
241: int n2 = action.getFirstEnd() + diffShifts[i][0];
242: int n3 = action.getSecondStart() + diffShifts[i][1];
243: int n4 = action.getSecondEnd() + diffShifts[i][1];
244: int endcopy = (action.getType() != Difference.ADD) ? (n1 - 1)
245: : n1;
246: //System.out.println("diff = "+n1+", "+n2+", "+n3+", "+n4+", endcopy = "+endcopy+((endcopy >= line1) ? "; copy("+line1+", "+endcopy+", "+line3+")" : ""));
247: if (endcopy >= line1) {
248: panel.copySource1ToResult(line1, endcopy, line3);
249: line3 += endcopy + 1 - line1;
250: }
251: int length = Math.max(n2 - n1, n4 - n3);
252: //System.out.println(" length = "+length+", addEmptyLines3("+line3+", "+(length + 1)+")");
253: panel.addEmptyLines3(line3, length + 1);
254: panel.highlightRegion3(line3, line3 + length,
255: colorUnresolvedConflict);
256: resultDiffLocations[i] = line3;
257: line3 += length + 1;
258: line1 = Math.max(n2, n4) + 1;
259: }
260: //System.out.println("copy("+line1+", -1, "+line3+")");
261: panel.copySource1ToResult(line1, -1, line3);
262: }
263:
264: private void showCurrentLine() {
265: Difference diff = diffs[currentDiffLine];
266: int line = diff.getFirstStart()
267: + diffShifts[currentDiffLine][0];
268: if (diff.getType() == Difference.ADD)
269: line++;
270: int lf1 = diff.getFirstEnd() - diff.getFirstStart() + 1;
271: int lf2 = diff.getSecondEnd() - diff.getSecondStart() + 1;
272: int length = Math.max(lf1, lf2);
273: panel.setCurrentLine(line, length, currentDiffLine,
274: resultDiffLocations[currentDiffLine]);
275: }
276:
277: /**
278: * Resolve the merge conflict with left or right part.
279: * This will reduce the number of conflicts by one.
280: * @param right If true, use the right part, left otherwise
281: * @param conflNum The number of conflict.
282: */
283: private void doResolveConflict(boolean right, int conflNum) {
284: Difference diff = diffs[conflNum];
285: int[] shifts = diffShifts[conflNum];
286: int line1, line2, line3, line4;
287: if (diff.getType() == Difference.ADD) {
288: line1 = diff.getFirstStart() + shifts[0] + 1;
289: line2 = line1 - 1;
290: } else {
291: line1 = diff.getFirstStart() + shifts[0];
292: line2 = diff.getFirstEnd() + shifts[0];
293: }
294: if (diff.getType() == Difference.DELETE) {
295: line3 = diff.getSecondStart() + shifts[1] + 1;
296: line4 = line3 - 1;
297: } else {
298: line3 = diff.getSecondStart() + shifts[1];
299: line4 = diff.getSecondEnd() + shifts[1];
300: }
301: //System.out.println(" diff lines = "+line1+", "+line2+", "+line3+", "+line4);
302: int rlength; // The length of the area before the conflict is resolved
303: if (resolvedConflicts.contains(diff)) {
304: rlength = (right) ? (line2 - line1) : (line4 - line3);
305: } else {
306: rlength = Math.max(line2 - line1, line4 - line3);
307: }
308: int shift;
309: if (right) {
310: panel.replaceSource2InResult(line3,
311: Math.max(line4, 0), // Correction for possibly negative value
312: resultDiffLocations[conflNum],
313: resultDiffLocations[conflNum] + rlength);
314: shift = rlength - (line4 - line3);
315: panel.highlightRegion1(line1, Math.max(line2, 0),
316: colorOtherConflict);
317: panel.highlightRegion2(line3, Math.max(line4, 0),
318: colorResolvedConflict);
319: } else {
320: panel.replaceSource1InResult(line1,
321: Math.max(line2, 0), // Correction for possibly negative value
322: resultDiffLocations[conflNum],
323: resultDiffLocations[conflNum] + rlength);
324: shift = rlength - (line2 - line1);
325: panel.highlightRegion1(line1, Math.max(line2, 0),
326: colorResolvedConflict);
327: panel.highlightRegion2(line3, Math.max(line4, 0),
328: colorOtherConflict);
329: }
330: if (right && (line4 >= line3) || !right && (line2 >= line1)) {
331: panel.highlightRegion3(resultDiffLocations[conflNum],
332: resultDiffLocations[conflNum] + rlength - shift,
333: colorResolvedConflict);
334: } else {
335: panel.unhighlightRegion3(resultDiffLocations[conflNum],
336: resultDiffLocations[conflNum]);
337: }
338: for (int i = conflNum + 1; i < diffs.length; i++) {
339: resultDiffLocations[i] -= shift;
340: }
341: resolvedConflicts.add(diff);
342: panel.setNeedsSaveState(true);
343: }
344:
345: public void actionPerformed(ActionEvent actionEvent) {
346: String actionCommand = actionEvent.getActionCommand();
347: if (MergePanel.ACTION_FIRST_CONFLICT.equals(actionCommand)) {
348: currentDiffLine = 0;
349: showCurrentLine();
350: } else if (MergePanel.ACTION_LAST_CONFLICT
351: .equals(actionCommand)) {
352: currentDiffLine = diffs.length - 1;
353: showCurrentLine();
354: } else if (MergePanel.ACTION_PREVIOUS_CONFLICT
355: .equals(actionCommand)) {
356: currentDiffLine--;
357: if (currentDiffLine < 0)
358: currentDiffLine = diffs.length - 1;
359: showCurrentLine();
360: } else if (MergePanel.ACTION_NEXT_CONFLICT
361: .equals(actionCommand)) {
362: currentDiffLine++;
363: if (currentDiffLine >= diffs.length)
364: currentDiffLine = 0;
365: showCurrentLine();
366: } else if (MergePanel.ACTION_ACCEPT_RIGHT.equals(actionCommand)) {
367: doResolveConflict(true, currentDiffLine);
368: } else if (MergePanel.ACTION_ACCEPT_LEFT.equals(actionCommand)) {
369: doResolveConflict(false, currentDiffLine);
370: }
371: }
372:
373: public void vetoableChange(PropertyChangeEvent propertyChangeEvent)
374: throws PropertyVetoException {
375: if (MergeDialogComponent.PROP_PANEL_SAVE
376: .equals(propertyChangeEvent.getPropertyName())) {
377: MergePanel panel = (MergePanel) propertyChangeEvent
378: .getNewValue();
379: if (this .panel == panel) {
380: ArrayList<Difference> unresolvedConflicts = new ArrayList<Difference>();//java.util.Arrays.asList(diffs));
381: int diffLocationShift = 0;
382: for (int i = 0; i < diffs.length; i++) {
383: if (!resolvedConflicts.contains(diffs[i])) {
384: int diffLocation = resultDiffLocations[i]
385: - diffLocationShift;
386: Difference conflict = new Difference(diffs[i]
387: .getType(), diffLocation, diffLocation
388: + diffs[i].getFirstEnd()
389: - diffs[i].getFirstStart(),
390: diffLocation, diffLocation
391: + diffs[i].getSecondEnd()
392: - diffs[i].getSecondStart(),
393: diffs[i].getFirstText(), diffs[i]
394: .getSecondText());
395: unresolvedConflicts.add(conflict);
396: diffLocationShift += Math
397: .max(
398: diffs[i].getFirstEnd()
399: - diffs[i]
400: .getFirstStart()
401: + 1,
402: diffs[i].getSecondEnd()
403: - diffs[i]
404: .getSecondStart()
405: + 1);
406: }
407: }
408: try {
409: panel
410: .writeResult(
411: resultSource
412: .createWriter(unresolvedConflicts
413: .toArray(new Difference[unresolvedConflicts
414: .size()])),
415: firstNewlineIsFake
416: | secondNewlineIsFake);
417: panel.setNeedsSaveState(false);
418: } catch (IOException ioex) {
419: PropertyVetoException pvex = new PropertyVetoException(
420: NbBundle.getMessage(MergeControl.class,
421: "MergeControl.failedToSave", ioex
422: .getLocalizedMessage()),
423: propertyChangeEvent);
424: pvex.initCause(ioex);
425: throw pvex;
426: }
427: }
428: }
429: if (MergeDialogComponent.PROP_PANEL_CLOSING
430: .equals(propertyChangeEvent.getPropertyName())) {
431: MergePanel panel = (MergePanel) propertyChangeEvent
432: .getNewValue();
433: if (this.panel == panel) {
434: resultSource.close();
435: }
436: }
437: if (MergeDialogComponent.PROP_ALL_CLOSED
438: .equals(propertyChangeEvent.getPropertyName())
439: || MergeDialogComponent.PROP_ALL_CANCELLED
440: .equals(propertyChangeEvent.getPropertyName())) {
441: resultSource.close();
442: }
443: }
444:
445: }
|