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.versioning.system.cvss.ui.actions.diff;
45
import java.util.ArrayList;
47
import java.util.Iterator;
49
import java.nio.charset.Charset;
51
import org.netbeans.lib.cvsclient.file.*;
53
import org.openide.DialogDisplayer;
54
import org.openide.ErrorManager;
55
import org.openide.windows.TopComponent;
56
import org.openide.filesystems.FileLock;
57
import org.openide.filesystems.FileObject;
58
import org.openide.filesystems.FileUtil;
59
import org.openide.filesystems.FileAlreadyLockedException;
60
import org.openide.util.Lookup;
62
import org.netbeans.api.diff.Difference;
63
import org.netbeans.api.diff.StreamSource;
64
import org.netbeans.api.queries.FileEncodingQuery;
65
import org.netbeans.spi.diff.MergeVisualizer;
70
* This class is used to resolve merge conflicts in a graphical way using a merge visualizer.
71
* We parse the file with merge conflicts marked, let the conflicts resolve by the
72
* visual merging tool and after successfull conflicts resolution save it back
73
* to the original file.
75
* @author Martin Entlicher
77
public class ResolveConflictsExecutor {
79
private static final String TMP_PREFIX = "merge"; // NOI18N
81
static final String CHANGE_LEFT = "<<<<<<< "; // NOI18N
82
static final String CHANGE_RIGHT = ">>>>>>> "; // NOI18N
83
static final String CHANGE_DELIMETER = "======="; // NOI18N
85
private String leftFileRevision = null;
86
private String rightFileRevision = null;
88
public void exec(File file) {
89
assert SwingUtilities.isEventDispatchThread();
90
MergeVisualizer merge = (MergeVisualizer) Lookup.getDefault().lookup(MergeVisualizer.class);
92
throw new IllegalStateException("No Merge engine found."); // NOI18N
96
FileObject fo = FileUtil.toFileObject(file);
97
handleMergeFor(file, fo, fo.lock(), merge);
98
} catch (FileAlreadyLockedException e) {
99
Set components = TopComponent.getRegistry().getOpened();
100
for (Iterator i = components.iterator(); i.hasNext();) {
101
TopComponent tc = (TopComponent) i.next();
102
if (tc.getClientProperty(ResolveConflictsExecutor.class.getName()) != null) {
106
} catch (IOException ioex) {
107
org.openide.ErrorManager.getDefault().notify(ioex);
111
private void handleMergeFor(final File file, FileObject fo, FileLock lock,
112
final MergeVisualizer merge) throws IOException {
113
String mimeType = (fo == null) ? "text/plain" : fo.getMIMEType(); // NOI18N
114
String ext = "."+fo.getExt(); // NOI18N
115
File f1 = FileUtil.normalizeFile(File.createTempFile(TMP_PREFIX, ext));
116
File f2 = FileUtil.normalizeFile(File.createTempFile(TMP_PREFIX, ext));
117
File f3 = FileUtil.normalizeFile(File.createTempFile(TMP_PREFIX, ext));
122
final Difference[] diffs = copyParts(true, file, f1, true);
123
if (diffs.length == 0) {
124
DialogDisplayer.getDefault ().notify (new org.openide.NotifyDescriptor.Message(
125
org.openide.util.NbBundle.getMessage(ResolveConflictsExecutor.class, "NoConflictsInFile", file)));
128
copyParts(false, file, f2, false);
129
//GraphicalMergeVisualizer merge = new GraphicalMergeVisualizer();
130
String originalLeftFileRevision = leftFileRevision;
131
String originalRightFileRevision = rightFileRevision;
132
if (leftFileRevision != null) leftFileRevision.trim();
133
if (rightFileRevision != null) rightFileRevision.trim();
134
if (leftFileRevision == null || leftFileRevision.equals(file.getName())) {
135
leftFileRevision = org.openide.util.NbBundle.getMessage(ResolveConflictsExecutor.class, "Diff.titleWorkingFile");
137
leftFileRevision = org.openide.util.NbBundle.getMessage(ResolveConflictsExecutor.class, "Diff.titleRevision", leftFileRevision);
139
if (rightFileRevision == null || rightFileRevision.equals(file.getName())) {
140
rightFileRevision = org.openide.util.NbBundle.getMessage(ResolveConflictsExecutor.class, "Diff.titleWorkingFile");
142
rightFileRevision = org.openide.util.NbBundle.getMessage(ResolveConflictsExecutor.class, "Diff.titleRevision", rightFileRevision);
145
final StreamSource s1;
146
final StreamSource s2;
147
Charset encoding = FileEncodingQuery.getEncoding(fo);
148
s1 = StreamSource.createSource(file.getName(), leftFileRevision, mimeType, f1);
149
s2 = StreamSource.createSource(file.getName(), rightFileRevision, mimeType, f2);
150
final StreamSource result = new MergeResultWriterInfo(f1, f2, f3, file, mimeType,
151
originalLeftFileRevision,
152
originalRightFileRevision,
156
Component c = merge.createView(diffs, s1, s2, result);
157
if (c instanceof TopComponent) {
158
((TopComponent) c).putClientProperty(ResolveConflictsExecutor.class.getName(), Boolean.TRUE);
160
} catch (IOException ioex) {
161
org.openide.ErrorManager.getDefault().notify(ioex);
166
* Copy the file and conflict parts into another file.
168
private Difference[] copyParts(boolean generateDiffs, File source,
169
File dest, boolean leftPart) throws IOException {
170
//System.out.println("copyParts("+generateDiffs+", "+source+", "+dest+", "+leftPart+")");
171
BufferedReader r = new BufferedReader(new FileReader(source));
172
BufferedWriter w = new BufferedWriter(new FileWriter(dest));
173
ArrayList diffList = null;
175
diffList = new ArrayList();
179
boolean isChangeLeft = false;
180
boolean isChangeRight = false;
181
int f1l1 = 0, f1l2 = 0, f2l1 = 0, f2l2 = 0;
182
StringBuffer text1 = new StringBuffer();
183
StringBuffer text2 = new StringBuffer();
185
while ((line = r.readLine()) != null) {
186
if (line.startsWith(CHANGE_LEFT)) {
188
if (leftFileRevision == null) {
189
leftFileRevision = line.substring(CHANGE_LEFT.length());
193
diffList.add((f1l1 > f1l2) ? new Difference(Difference.ADD,
194
f1l1 - 1, 0, f2l1, f2l2,
197
(f2l1 > f2l2) ? new Difference(Difference.DELETE,
198
f1l1, f1l2, f2l1 - 1, 0,
201
: new Difference(Difference.CHANGE,
202
f1l1, f1l2, f2l1, f2l2,
205
f1l1 = f1l2 = f2l1 = f2l2 = 0;
206
text1.delete(0, text1.length());
207
text2.delete(0, text2.length());
212
isChangeLeft = !isChangeLeft;
214
} else if (line.startsWith(CHANGE_RIGHT)) {
216
if (rightFileRevision == null) {
217
rightFileRevision = line.substring(CHANGE_RIGHT.length());
221
diffList.add((f1l1 > f1l2) ? new Difference(Difference.ADD,
222
f1l1 - 1, 0, f2l1, f2l2,
225
(f2l1 > f2l2) ? new Difference(Difference.DELETE,
226
f1l1, f1l2, f2l1 - 1, 0,
229
: new Difference(Difference.CHANGE,
230
f1l1, f1l2, f2l1, f2l2,
234
diffList.add(new Difference((f1l1 > f1l2) ? Difference.ADD :
235
(f2l1 > f2l2) ? Difference.DELETE :
237
f1l1, f1l2, f2l1, f2l2));
239
f1l1 = f1l2 = f2l1 = f2l2 = 0;
240
text1.delete(0, text1.length());
241
text2.delete(0, text2.length());
246
isChangeRight = !isChangeRight;
248
} else if (isChangeRight && line.indexOf(CHANGE_RIGHT) != -1) {
249
String lineText = line.substring(0, line.lastIndexOf(CHANGE_RIGHT));
251
if (rightFileRevision == null) {
252
rightFileRevision = line.substring(line.lastIndexOf(CHANGE_RIGHT) + CHANGE_RIGHT.length());
254
text2.append(lineText);
256
diffList.add((f1l1 > f1l2) ? new Difference(Difference.ADD,
257
f1l1 - 1, 0, f2l1, f2l2,
260
(f2l1 > f2l2) ? new Difference(Difference.DELETE,
261
f1l1, f1l2, f2l1 - 1, 0,
264
: new Difference(Difference.CHANGE,
265
f1l1, f1l2, f2l1, f2l2,
268
f1l1 = f1l2 = f2l1 = f2l2 = 0;
269
text1.delete(0, text1.length());
270
text2.delete(0, text2.length());
276
isChangeRight = !isChangeRight;
278
} else if (line.equals(CHANGE_DELIMETER)) {
280
isChangeLeft = false;
281
isChangeRight = true;
285
} else if (isChangeRight) {
286
isChangeRight = false;
292
} else if (line.endsWith(CHANGE_DELIMETER)) {
293
String lineText = line.substring(0, line.length() - CHANGE_DELIMETER.length()) + "\n"; // NOI18N
295
text1.append(lineText);
300
isChangeLeft = false;
301
isChangeRight = true;
304
} else if (isChangeRight) {
305
text2.append(lineText);
310
isChangeRight = false;
317
if (!isChangeLeft && !isChangeRight || leftPart == isChangeLeft) {
321
if (isChangeLeft) text1.append(line + "\n"); // NOI18N
322
if (isChangeRight) text2.append(line + "\n"); // NOI18N
324
if (isChangeLeft) i++;
325
else if (isChangeRight) j++;
340
return (Difference[]) diffList.toArray(new Difference[diffList.size()]);
347
* Repair the CVS/Entries of the file - remove the conflict.
348
* @param file The file to remove the conflict for
350
static void repairEntries(File file) throws IOException {
351
String name = file.getName();
352
File entries = new File(file.getParentFile(), "CVS"+File.separator+"Entries"); // NOI18N
353
File backup = new File(entries.getAbsolutePath()+".Backup"); // NOI18N
355
while (backup.exists() && attemps-- > 0) {
356
// Someone else is occupying Entries, wait a while...
359
} catch (InterruptedException intrex) {
363
if (attemps <= 0) return ; // Give up, someone else is occupying Entries
364
backup.createNewFile();
366
BufferedReader reader = null;
367
BufferedWriter writer = null;
369
reader = new BufferedReader(new FileReader(entries));
370
writer = new BufferedWriter(new FileWriter(backup));
372
String pattern = "/"+name; // NOI18N
373
while ((line = reader.readLine()) != null) {
374
if (line.startsWith(pattern)) {
375
line = removeConflict(line);
377
writer.write(line+"\n"); // NOI18N
380
if (reader != null) reader.close();
381
if (writer != null) writer.close();
383
FileUtils.renameFile(backup, entries);
385
if (backup.exists()) backup.delete();
389
private static String removeConflict(String line) {
390
StringBuffer result = new StringBuffer();
391
int n = line.length();
393
boolean ignoreField = false;
394
for (int i = 0; i < n; i++) {
395
char c = line.charAt(i);
396
if (!ignoreField) result.append(c);
401
result.append("Result of merge/"); // NOI18N
406
return result.toString();
409
private static class MergeResultWriterInfo extends StreamSource {
411
private File tempf1, tempf2, tempf3, outputFile;
412
private File fileToRepairEntriesOf;
413
private String mimeType;
414
private String leftFileRevision;
415
private String rightFileRevision;
416
private FileObject fo;
417
private FileLock lock;
418
private Charset encoding;
420
public MergeResultWriterInfo(File tempf1, File tempf2, File tempf3,
421
File outputFile, String mimeType,
422
String leftFileRevision, String rightFileRevision,
423
FileObject fo, FileLock lock, Charset encoding) {
424
this.tempf1 = tempf1;
425
this.tempf2 = tempf2;
426
this.tempf3 = tempf3;
427
this.outputFile = outputFile;
428
this.mimeType = mimeType;
429
this.leftFileRevision = leftFileRevision;
430
this.rightFileRevision = rightFileRevision;
433
if (encoding == null) {
434
encoding = FileEncodingQuery.getEncoding(FileUtil.toFileObject(tempf1));
436
this.encoding = encoding;
439
public String getName() {
440
return outputFile.getName();
443
public String getTitle() {
444
return org.openide.util.NbBundle.getMessage(ResolveConflictsExecutor.class, "Merge.titleResult");
447
public String getMIMEType() {
451
public Reader createReader() throws IOException {
452
throw new IOException("No reader of merge result"); // NOI18N
456
* Create a writer, that writes to the source.
457
* @param conflicts The list of conflicts remaining in the source.
458
* Can be <code>null</code> if there are no conflicts.
459
* @return The writer or <code>null</code>, when no writer can be created.
461
public Writer createWriter(Difference[] conflicts) throws IOException {
464
w = new OutputStreamWriter(fo.getOutputStream(lock), encoding);
466
w = new OutputStreamWriter(new FileOutputStream(outputFile), encoding);
468
if (conflicts == null || conflicts.length == 0) {
469
fileToRepairEntriesOf = outputFile;
472
return new MergeConflictFileWriter(w, fo, conflicts,
473
leftFileRevision, rightFileRevision);
478
* This method is called when the visual merging process is finished.
479
* All possible writting processes are finished before this method is called.
481
public void close() {
490
if (fileToRepairEntriesOf != null) {
492
repairEntries(fileToRepairEntriesOf);
493
} catch (IOException ioex) {
494
// The Entries will not be repaired at worse
495
ErrorManager.getDefault().notify(ErrorManager.INFORMATIONAL, ioex);
497
fileToRepairEntriesOf = null;
503
private static class MergeConflictFileWriter extends FilterWriter {
505
private Difference[] conflicts;
506
private int lineNumber;
507
private int currentConflict;
508
private String leftName;
509
private String rightName;
510
private FileObject fo;
512
public MergeConflictFileWriter(Writer delegate, FileObject fo,
513
Difference[] conflicts, String leftName,
514
String rightName) throws IOException {
516
this.conflicts = conflicts;
517
this.leftName = leftName;
518
this.rightName = rightName;
520
this.currentConflict = 0;
521
if (lineNumber == conflicts[currentConflict].getFirstStart()) {
522
writeConflict(conflicts[currentConflict]);
528
public void write(String str) throws IOException {
529
//System.out.println("MergeConflictFileWriter.write("+str+")");
531
lineNumber += numChars('\n', str);
532
//System.out.println(" lineNumber = "+lineNumber+", current conflict start = "+conflicts[currentConflict].getFirstStart());
533
if (currentConflict < conflicts.length && lineNumber >= conflicts[currentConflict].getFirstStart()) {
534
writeConflict(conflicts[currentConflict]);
539
private void writeConflict(Difference conflict) throws IOException {
540
//System.out.println("MergeConflictFileWriter.writeConflict('"+conflict.getFirstText()+"', '"+conflict.getSecondText()+"')");
541
super.write(CHANGE_LEFT + leftName + "\n"); // NOI18N
542
super.write(conflict.getFirstText());
543
super.write(CHANGE_DELIMETER + "\n"); // NOI18N
544
super.write(conflict.getSecondText());
545
super.write(CHANGE_RIGHT + rightName + "\n"); // NOI18N
548
private static int numChars(char c, String str) {
550
for (int pos = str.indexOf(c); pos >= 0 && pos < str.length(); pos = str.indexOf(c, pos + 1)) {
556
public void close() throws IOException {
558
if (fo != null) fo.refresh(true);