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.mercurial.ui.update;
043:
044: import java.io.*;
045: import java.util.*;
046: import java.awt.*;
047: import java.nio.charset.Charset;
048: import javax.swing.*;
049: import org.netbeans.spi.diff.*;
050:
051: import org.openide.util.*;
052: import org.openide.windows.TopComponent;
053: import org.openide.filesystems.*;
054:
055: import org.netbeans.api.diff.*;
056: import org.netbeans.api.queries.FileEncodingQuery;
057: import org.netbeans.modules.mercurial.HgProgressSupport;
058:
059: /**
060: * Shows basic conflict resolver UI.
061: *
062: * This class is copy&pasted from javacvs
063: *
064: * @author Martin Entlicher
065: */
066: public class ResolveConflictsExecutor extends HgProgressSupport {
067:
068: private static final String TMP_PREFIX = "merge"; // NOI18N
069: private static final String ORIG_SUFFIX = ".orig."; // NOI18N
070:
071: static final String CHANGE_LEFT = "<<<<<<< "; // NOI18N
072: static final String CHANGE_RIGHT = ">>>>>>> "; // NOI18N
073: static final String CHANGE_DELIMETER = "======="; // NOI18N
074: static final String CHANGE_BASE_DELIMETER = "|||||||"; // NOI18N
075:
076: private String leftFileRevision = null;
077: private String rightFileRevision = null;
078:
079: private final File file;
080:
081: public ResolveConflictsExecutor(File file) {
082: super ();
083: this .file = file;
084: }
085:
086: public void exec() {
087: assert SwingUtilities.isEventDispatchThread();
088: MergeVisualizer merge = (MergeVisualizer) Lookup.getDefault()
089: .lookup(MergeVisualizer.class);
090: if (merge == null) {
091: throw new IllegalStateException("No Merge engine found."); // NOI18N
092: }
093:
094: try {
095: FileObject fo = FileUtil.toFileObject(file);
096: handleMergeFor(file, fo, fo.lock(), merge);
097: } catch (FileAlreadyLockedException e) {
098: Set components = TopComponent.getRegistry().getOpened();
099: for (Iterator i = components.iterator(); i.hasNext();) {
100: TopComponent tc = (TopComponent) i.next();
101: if (tc.getClientProperty(ResolveConflictsExecutor.class
102: .getName()) != null) {
103: tc.requestActive();
104: }
105: }
106: } catch (IOException ioex) {
107: org.openide.ErrorManager.getDefault().notify(ioex);
108: }
109: }
110:
111: private void handleMergeFor(final File file, FileObject fo,
112: FileLock lock, final MergeVisualizer merge)
113: throws IOException {
114: String mimeType = (fo == null) ? "text/plain" : fo
115: .getMIMEType(); // NOI18N
116: String ext = "." + fo.getExt(); // NOI18N
117: File f1 = FileUtil.normalizeFile(File.createTempFile(
118: TMP_PREFIX, ext));
119: File f2 = FileUtil.normalizeFile(File.createTempFile(
120: TMP_PREFIX, ext));
121: File f3 = FileUtil.normalizeFile(File.createTempFile(
122: TMP_PREFIX, ext));
123: f1.deleteOnExit();
124: f2.deleteOnExit();
125: f3.deleteOnExit();
126:
127: final Difference[] diffs = copyParts(true, file, f1, true);
128: if (diffs.length == 0) {
129: ConflictResolvedAction.resolved(file); // remove conflict status
130: return;
131: }
132:
133: copyParts(false, file, f2, false);
134: //GraphicalMergeVisualizer merge = new GraphicalMergeVisualizer();
135: String originalLeftFileRevision = leftFileRevision;
136: String originalRightFileRevision = rightFileRevision;
137: if (leftFileRevision != null)
138: leftFileRevision.trim();
139: if (rightFileRevision != null)
140: rightFileRevision.trim();
141: if (leftFileRevision == null
142: || leftFileRevision.equals(file.getAbsolutePath()
143: + ORIG_SUFFIX)) {
144: leftFileRevision = org.openide.util.NbBundle.getMessage(
145: ResolveConflictsExecutor.class,
146: "Diff.titleWorkingFile"); // NOI18N
147: } else {
148: leftFileRevision = org.openide.util.NbBundle.getMessage(
149: ResolveConflictsExecutor.class,
150: "Diff.titleRevision", leftFileRevision); // NOI18N
151: }
152: if (rightFileRevision == null
153: || rightFileRevision.equals(file.getAbsolutePath()
154: + ORIG_SUFFIX)) {
155: rightFileRevision = org.openide.util.NbBundle.getMessage(
156: ResolveConflictsExecutor.class,
157: "Diff.titleWorkingFile"); // NOI18N
158: } else {
159: rightFileRevision = org.openide.util.NbBundle.getMessage(
160: ResolveConflictsExecutor.class,
161: "Diff.titleRevision", rightFileRevision); // NOI18N
162: }
163:
164: final StreamSource s1;
165: final StreamSource s2;
166: Charset encoding = FileEncodingQuery.getEncoding(fo);
167: s1 = StreamSource.createSource(file.getName(),
168: leftFileRevision, mimeType, f1);
169: s2 = StreamSource.createSource(file.getName(),
170: rightFileRevision, mimeType, f2);
171: final StreamSource result = new MergeResultWriterInfo(f1, f2,
172: f3, file, mimeType, originalLeftFileRevision,
173: originalRightFileRevision, fo, lock, encoding);
174:
175: try {
176: Component c = merge.createView(diffs, s1, s2, result);
177: if (c instanceof TopComponent) {
178: ((TopComponent) c).putClientProperty(
179: ResolveConflictsExecutor.class.getName(),
180: Boolean.TRUE);
181: }
182: } catch (IOException ioex) {
183: org.openide.ErrorManager.getDefault().notify(ioex);
184: }
185: }
186:
187: /**
188: * Copy the file and conflict parts into another file.
189: */
190: private Difference[] copyParts(boolean generateDiffs, File source,
191: File dest, boolean leftPart) throws IOException {
192: BufferedReader r = new BufferedReader(new FileReader(source));
193: BufferedWriter w = new BufferedWriter(new FileWriter(dest));
194: ArrayList<Difference> diffList = null;
195: if (generateDiffs) {
196: diffList = new ArrayList<Difference>();
197: }
198: try {
199: String line;
200: boolean isChangeLeft = false;
201: boolean isChangeRight = false;
202: boolean isChangeBase = false;
203: int f1l1 = 0, f1l2 = 0, f2l1 = 0, f2l2 = 0;
204: StringBuffer text1 = new StringBuffer();
205: StringBuffer text2 = new StringBuffer();
206: int i = 1, j = 1;
207: while ((line = r.readLine()) != null) {
208: // As the Graphical Merge Visualizer does not support 3 way diff,
209: // remove the base diff itself.
210: // Only show the diffs of the two heads against the base
211: if (line.startsWith(CHANGE_BASE_DELIMETER)) {
212: isChangeBase = true;
213: continue;
214: }
215: if (isChangeBase && line.startsWith(CHANGE_DELIMETER)) {
216: isChangeBase = false;
217: } else if (isChangeBase) {
218: continue;
219: }
220: if (line.startsWith(CHANGE_LEFT)) {
221: if (generateDiffs) {
222: if (leftFileRevision == null) {
223: leftFileRevision = line
224: .substring(CHANGE_LEFT.length());
225: }
226: if (isChangeLeft) {
227: f1l2 = i - 1;
228: diffList
229: .add((f1l1 > f1l2) ? new Difference(
230: Difference.ADD, f1l1 - 1,
231: 0, f2l1, f2l2, text1
232: .toString(), text2
233: .toString())
234: : (f2l1 > f2l2) ? new Difference(
235: Difference.DELETE,
236: f1l1, f1l2,
237: f2l1 - 1, 0,
238: text1.toString(),
239: text2.toString())
240: : new Difference(
241: Difference.CHANGE,
242: f1l1,
243: f1l2,
244: f2l1,
245: f2l2,
246: text1
247: .toString(),
248: text2
249: .toString()));
250: f1l1 = f1l2 = f2l1 = f2l2 = 0;
251: text1.delete(0, text1.length());
252: text2.delete(0, text2.length());
253: } else {
254: f1l1 = i;
255: }
256: }
257: isChangeLeft = !isChangeLeft;
258: continue;
259: } else if (line.startsWith(CHANGE_RIGHT)) {
260: if (generateDiffs) {
261: if (rightFileRevision == null) {
262: rightFileRevision = line
263: .substring(CHANGE_RIGHT.length());
264: }
265: if (isChangeRight) {
266: f2l2 = j - 1;
267: diffList
268: .add((f1l1 > f1l2) ? new Difference(
269: Difference.ADD, f1l1 - 1,
270: 0, f2l1, f2l2, text1
271: .toString(), text2
272: .toString())
273: : (f2l1 > f2l2) ? new Difference(
274: Difference.DELETE,
275: f1l1, f1l2,
276: f2l1 - 1, 0,
277: text1.toString(),
278: text2.toString())
279: : new Difference(
280: Difference.CHANGE,
281: f1l1,
282: f1l2,
283: f2l1,
284: f2l2,
285: text1
286: .toString(),
287: text2
288: .toString()));
289: /*
290: diffList.add(new Difference((f1l1 > f1l2) ? Difference.ADD :
291: (f2l1 > f2l2) ? Difference.DELETE :
292: Difference.CHANGE,
293: f1l1, f1l2, f2l1, f2l2));
294: */
295: f1l1 = f1l2 = f2l1 = f2l2 = 0;
296: text1.delete(0, text1.length());
297: text2.delete(0, text2.length());
298: } else {
299: f2l1 = j;
300: }
301: }
302: isChangeRight = !isChangeRight;
303: continue;
304: } else if (isChangeRight
305: && line.indexOf(CHANGE_RIGHT) != -1) {
306: String lineText = line.substring(0, line
307: .lastIndexOf(CHANGE_RIGHT));
308: if (generateDiffs) {
309: if (rightFileRevision == null) {
310: rightFileRevision = line.substring(line
311: .lastIndexOf(CHANGE_RIGHT)
312: + CHANGE_RIGHT.length());
313: }
314: text2.append(lineText);
315: f2l2 = j;
316: diffList.add((f1l1 > f1l2) ? new Difference(
317: Difference.ADD, f1l1 - 1, 0, f2l1,
318: f2l2, text1.toString(), text2
319: .toString())
320: : (f2l1 > f2l2) ? new Difference(
321: Difference.DELETE, f1l1, f1l2,
322: f2l1 - 1, 0, text1.toString(),
323: text2.toString())
324: : new Difference(
325: Difference.CHANGE,
326: f1l1, f1l2, f2l1, f2l2,
327: text1.toString(), text2
328: .toString()));
329: f1l1 = f1l2 = f2l1 = f2l2 = 0;
330: text1.delete(0, text1.length());
331: text2.delete(0, text2.length());
332: }
333: if (!leftPart) {
334: w.write(lineText);
335: w.newLine();
336: }
337: isChangeRight = !isChangeRight;
338: continue;
339: } else if (line.equals(CHANGE_DELIMETER)) {
340: if (isChangeLeft) {
341: isChangeLeft = false;
342: isChangeRight = true;
343: f1l2 = i - 1;
344: f2l1 = j;
345: continue;
346: } else if (isChangeRight) {
347: isChangeRight = false;
348: isChangeLeft = true;
349: f2l2 = j - 1;
350: f1l1 = i;
351: continue;
352: }
353: } else if (line.endsWith(CHANGE_DELIMETER)) {
354: String lineText = line.substring(0, line.length()
355: - CHANGE_DELIMETER.length())
356: + "\n"; // NOI18N
357: if (isChangeLeft) {
358: text1.append(lineText);
359: if (leftPart) {
360: w.write(lineText);
361: w.newLine();
362: }
363: isChangeLeft = false;
364: isChangeRight = true;
365: f1l2 = i;
366: f2l1 = j;
367: } else if (isChangeRight) {
368: text2.append(lineText);
369: if (!leftPart) {
370: w.write(lineText);
371: w.newLine();
372: }
373: isChangeRight = false;
374: isChangeLeft = true;
375: f2l2 = j;
376: f1l1 = i;
377: }
378: continue;
379: }
380: if (!isChangeLeft && !isChangeRight
381: || leftPart == isChangeLeft) {
382: w.write(line);
383: w.newLine();
384: }
385: if (isChangeLeft)
386: text1.append(line + "\n"); // NOI18N
387: if (isChangeRight)
388: text2.append(line + "\n"); // NOI18N
389: if (generateDiffs) {
390: if (isChangeLeft)
391: i++;
392: else if (isChangeRight)
393: j++;
394: else {
395: i++;
396: j++;
397: }
398: }
399: }
400: } finally {
401: try {
402: r.close();
403: } finally {
404: w.close();
405: }
406: }
407: if (generateDiffs) {
408: return diffList.toArray(new Difference[diffList.size()]);
409: } else {
410: return null;
411: }
412: }
413:
414: public void perform() {
415: exec();
416: }
417:
418: public void run() {
419: throw new RuntimeException("Not implemented"); // NOI18N
420: }
421:
422: private static class MergeResultWriterInfo extends StreamSource {
423:
424: private File tempf1, tempf2, tempf3, outputFile;
425: private File fileToRepairEntriesOf;
426: private String mimeType;
427: private String leftFileRevision;
428: private String rightFileRevision;
429: private FileObject fo;
430: private FileLock lock;
431: private Charset encoding;
432:
433: public MergeResultWriterInfo(File tempf1, File tempf2,
434: File tempf3, File outputFile, String mimeType,
435: String leftFileRevision, String rightFileRevision,
436: FileObject fo, FileLock lock, Charset encoding) {
437: this .tempf1 = tempf1;
438: this .tempf2 = tempf2;
439: this .tempf3 = tempf3;
440: this .outputFile = outputFile;
441: this .mimeType = mimeType;
442: this .leftFileRevision = leftFileRevision;
443: this .rightFileRevision = rightFileRevision;
444: this .fo = fo;
445: this .lock = lock;
446: if (encoding == null) {
447: encoding = FileEncodingQuery.getEncoding(FileUtil
448: .toFileObject(tempf1));
449: }
450: this .encoding = encoding;
451: }
452:
453: public String getName() {
454: return outputFile.getName();
455: }
456:
457: public String getTitle() {
458: return org.openide.util.NbBundle
459: .getMessage(ResolveConflictsExecutor.class,
460: "Merge.titleResult"); // NOI18N
461: }
462:
463: public String getMIMEType() {
464: return mimeType;
465: }
466:
467: public Reader createReader() throws IOException {
468: throw new IOException("No reader of merge result"); // NOI18N
469: }
470:
471: /**
472: * Create a writer, that writes to the source.
473: * @param conflicts The list of conflicts remaining in the source.
474: * Can be <code>null</code> if there are no conflicts.
475: * @return The writer or <code>null</code>, when no writer can be created.
476: */
477: public Writer createWriter(Difference[] conflicts)
478: throws IOException {
479: Writer w;
480: if (fo != null) {
481: w = new OutputStreamWriter(fo.getOutputStream(lock),
482: encoding);
483: } else {
484: w = new OutputStreamWriter(new FileOutputStream(
485: outputFile), encoding);
486: }
487: if (conflicts == null || conflicts.length == 0) {
488: fileToRepairEntriesOf = outputFile;
489: return w;
490: } else {
491: return new MergeConflictFileWriter(w, fo, conflicts,
492: leftFileRevision, rightFileRevision);
493: }
494: }
495:
496: /**
497: * This method is called when the visual merging process is finished.
498: * All possible writting processes are finished before this method is called.
499: */
500: public void close() {
501: tempf1.delete();
502: tempf2.delete();
503: tempf3.delete();
504: if (lock != null) {
505: lock.releaseLock();
506: lock = null;
507: }
508: fo = null;
509: if (fileToRepairEntriesOf != null) {
510: repairEntries(fileToRepairEntriesOf);
511: fileToRepairEntriesOf = null;
512: }
513: }
514:
515: private void repairEntries(File file) {
516: ConflictResolvedAction.resolved(file); // remove conflict status
517: }
518: }
519:
520: private static class MergeConflictFileWriter extends FilterWriter {
521:
522: private Difference[] conflicts;
523: private int lineNumber;
524: private int currentConflict;
525: private String leftName;
526: private String rightName;
527: private FileObject fo;
528:
529: public MergeConflictFileWriter(Writer delegate, FileObject fo,
530: Difference[] conflicts, String leftName,
531: String rightName) throws IOException {
532: super (delegate);
533: this .conflicts = conflicts;
534: this .leftName = leftName;
535: this .rightName = rightName;
536: this .lineNumber = 1;
537: this .currentConflict = 0;
538: if (lineNumber == conflicts[currentConflict]
539: .getFirstStart()) {
540: writeConflict(conflicts[currentConflict]);
541: currentConflict++;
542: }
543: this .fo = fo;
544: }
545:
546: public void write(String str) throws IOException {
547: super .write(str);
548: lineNumber += numChars('\n', str);
549: if (currentConflict < conflicts.length
550: && lineNumber >= conflicts[currentConflict]
551: .getFirstStart()) {
552: writeConflict(conflicts[currentConflict]);
553: currentConflict++;
554: }
555: }
556:
557: private void writeConflict(Difference conflict)
558: throws IOException {
559: super .write(CHANGE_LEFT + leftName + "\n"); // NOI18N
560: super .write(conflict.getFirstText());
561: super .write(CHANGE_DELIMETER + "\n"); // NOI18N
562: super .write(conflict.getSecondText());
563: super .write(CHANGE_RIGHT + rightName + "\n"); // NOI18N
564: }
565:
566: private static int numChars(char c, String str) {
567: int n = 0;
568: for (int pos = str.indexOf(c); pos >= 0
569: && pos < str.length(); pos = str
570: .indexOf(c, pos + 1)) {
571: n++;
572: }
573: return n;
574: }
575:
576: public void close() throws IOException {
577: super .close();
578: if (fo != null)
579: fo.refresh(true);
580: }
581: }
582: }
|