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