Convert most of the code to use for-each loops
[nbgit.git] / src / org / nbgit / ui / update / ResolveConflictsExecutor.java
blob73854a3142ed8cd186aea08afd8e8880573ea2d9
1 /*
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]"
24 * Contributor(s):
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.
29 * Portions Copyright 2008 Alexander Coles (Ikonoklastik Productions).
31 * If you wish your version of this file to be governed by only the CDDL
32 * or only the GPL Version 2, indicate your decision by adding
33 * "[Contributor] elects to include this software in this distribution
34 * under the [CDDL or GPL Version 2] license." If you do not indicate a
35 * single choice of license, a recipient has the option to distribute
36 * your version of this file under either the CDDL, the GPL Version 2 or
37 * to extend the choice of license to its licensees as provided above.
38 * However, if you add GPL Version 2 code and therefore, elected the GPL
39 * Version 2 license, then the option applies only if the new code is
40 * made subject to such option by the copyright holder.
42 package org.nbgit.ui.update;
44 import java.awt.Component;
45 import java.io.BufferedReader;
46 import java.io.BufferedWriter;
47 import java.io.File;
48 import java.io.FileOutputStream;
49 import java.io.FileReader;
50 import java.io.FileWriter;
51 import java.io.FilterWriter;
52 import java.io.IOException;
53 import java.io.OutputStreamWriter;
54 import java.io.Reader;
55 import java.io.Writer;
56 import java.nio.charset.Charset;
57 import java.util.ArrayList;
58 import java.util.Set;
59 import javax.swing.SwingUtilities;
60 import org.netbeans.api.diff.Difference;
61 import org.netbeans.api.diff.StreamSource;
62 import org.netbeans.api.queries.FileEncodingQuery;
63 import org.nbgit.GitProgressSupport;
64 import org.netbeans.spi.diff.MergeVisualizer;
65 import org.openide.filesystems.FileAlreadyLockedException;
66 import org.openide.filesystems.FileLock;
67 import org.openide.filesystems.FileObject;
68 import org.openide.filesystems.FileUtil;
69 import org.openide.util.Lookup;
70 import org.openide.windows.TopComponent;
72 /**
73 * Shows basic conflict resolver UI.
75 * This class is copy&pasted from javacvs
77 * @author Martin Entlicher
79 public class ResolveConflictsExecutor extends GitProgressSupport {
81 private static final String TMP_PREFIX = "merge"; // NOI18N
82 private static final String ORIG_SUFFIX = ".orig."; // NOI18N
83 static final String CHANGE_LEFT = "<<<<<<< "; // NOI18N
84 static final String CHANGE_RIGHT = ">>>>>>> "; // NOI18N
85 static final String CHANGE_DELIMETER = "======="; // NOI18N
86 static final String CHANGE_BASE_DELIMETER = "|||||||"; // NOI18N
87 private String leftFileRevision = null;
88 private String rightFileRevision = null;
89 private final File file;
91 public ResolveConflictsExecutor(File file) {
92 super();
93 this.file = file;
96 public void exec() {
97 assert SwingUtilities.isEventDispatchThread();
98 MergeVisualizer merge = Lookup.getDefault().lookup(MergeVisualizer.class);
99 if (merge == null) {
100 throw new IllegalStateException("No Merge engine found."); // NOI18N
103 try {
104 FileObject fo = FileUtil.toFileObject(file);
105 handleMergeFor(file, fo, fo.lock(), merge);
106 } catch (FileAlreadyLockedException e) {
107 Set<TopComponent> components = TopComponent.getRegistry().getOpened();
108 for (TopComponent tc : components) {
109 if (tc.getClientProperty(ResolveConflictsExecutor.class.getName()) != null) {
110 tc.requestActive();
113 } catch (IOException ioex) {
114 org.openide.ErrorManager.getDefault().notify(ioex);
118 private void handleMergeFor(final File file, FileObject fo, FileLock lock,
119 final MergeVisualizer merge) throws IOException {
120 String mimeType = (fo == null) ? "text/plain" : fo.getMIMEType(); // NOI18N
121 String ext = "." + fo.getExt(); // NOI18N
122 File f1 = FileUtil.normalizeFile(File.createTempFile(TMP_PREFIX, ext));
123 File f2 = FileUtil.normalizeFile(File.createTempFile(TMP_PREFIX, ext));
124 File f3 = FileUtil.normalizeFile(File.createTempFile(TMP_PREFIX, ext));
125 f1.deleteOnExit();
126 f2.deleteOnExit();
127 f3.deleteOnExit();
129 final Difference[] diffs = copyParts(true, file, f1, true);
130 if (diffs.length == 0) {
131 ConflictResolvedAction.resolved(file); // remove conflict status
132 return;
135 copyParts(false, file, f2, false);
136 //GraphicalMergeVisualizer merge = new GraphicalMergeVisualizer();
137 String originalLeftFileRevision = leftFileRevision;
138 String originalRightFileRevision = rightFileRevision;
139 if (leftFileRevision != null) {
140 leftFileRevision.trim();
142 if (rightFileRevision != null) {
143 rightFileRevision.trim();
145 if (leftFileRevision == null || leftFileRevision.equals(file.getAbsolutePath() + ORIG_SUFFIX)) {
146 leftFileRevision = org.openide.util.NbBundle.getMessage(ResolveConflictsExecutor.class, "Diff.titleWorkingFile"); // NOI18N
147 } else {
148 leftFileRevision = org.openide.util.NbBundle.getMessage(ResolveConflictsExecutor.class, "Diff.titleRevision", leftFileRevision); // NOI18N
150 if (rightFileRevision == null || rightFileRevision.equals(file.getAbsolutePath() + ORIG_SUFFIX)) {
151 rightFileRevision = org.openide.util.NbBundle.getMessage(ResolveConflictsExecutor.class, "Diff.titleWorkingFile"); // NOI18N
152 } else {
153 rightFileRevision = org.openide.util.NbBundle.getMessage(ResolveConflictsExecutor.class, "Diff.titleRevision", rightFileRevision); // NOI18N
156 final StreamSource s1;
157 final StreamSource s2;
158 Charset encoding = FileEncodingQuery.getEncoding(fo);
159 s1 = StreamSource.createSource(file.getName(), leftFileRevision, mimeType, f1);
160 s2 = StreamSource.createSource(file.getName(), rightFileRevision, mimeType, f2);
161 final StreamSource result = new MergeResultWriterInfo(f1, f2, f3, file, mimeType,
162 originalLeftFileRevision,
163 originalRightFileRevision,
164 fo, lock, encoding);
166 try {
167 Component c = merge.createView(diffs, s1, s2, result);
168 if (c instanceof TopComponent) {
169 ((TopComponent) c).putClientProperty(ResolveConflictsExecutor.class.getName(), Boolean.TRUE);
171 } catch (IOException ioex) {
172 org.openide.ErrorManager.getDefault().notify(ioex);
177 * Copy the file and conflict parts into another file.
179 private Difference[] copyParts(boolean generateDiffs, File source,
180 File dest, boolean leftPart) throws IOException {
181 BufferedReader r = new BufferedReader(new FileReader(source));
182 BufferedWriter w = new BufferedWriter(new FileWriter(dest));
183 ArrayList<Difference> diffList = null;
184 if (generateDiffs) {
185 diffList = new ArrayList<Difference>();
187 try {
188 String line;
189 boolean isChangeLeft = false;
190 boolean isChangeRight = false;
191 boolean isChangeBase = false;
192 int f1l1 = 0, f1l2 = 0, f2l1 = 0, f2l2 = 0;
193 StringBuffer text1 = new StringBuffer();
194 StringBuffer text2 = new StringBuffer();
195 int i = 1, j = 1;
196 while ((line = r.readLine()) != null) {
197 // As the Graphical Merge Visualizer does not support 3 way diff,
198 // remove the base diff itself.
199 // Only show the diffs of the two heads against the base
200 if (line.startsWith(CHANGE_BASE_DELIMETER)) {
201 isChangeBase = true;
202 continue;
204 if (isChangeBase && line.startsWith(CHANGE_DELIMETER)) {
205 isChangeBase = false;
206 } else if (isChangeBase) {
207 continue;
209 if (line.startsWith(CHANGE_LEFT)) {
210 if (generateDiffs) {
211 if (leftFileRevision == null) {
212 leftFileRevision = line.substring(CHANGE_LEFT.length());
214 if (isChangeLeft) {
215 f1l2 = i - 1;
216 diffList.add((f1l1 > f1l2) ? new Difference(Difference.ADD,
217 f1l1 - 1, 0, f2l1, f2l2,
218 text1.toString(),
219 text2.toString()) : (f2l1 > f2l2) ? new Difference(Difference.DELETE,
220 f1l1, f1l2, f2l1 - 1, 0,
221 text1.toString(),
222 text2.toString())
223 : new Difference(Difference.CHANGE,
224 f1l1, f1l2, f2l1, f2l2,
225 text1.toString(),
226 text2.toString()));
227 f1l1 = f1l2 = f2l1 = f2l2 = 0;
228 text1.delete(0, text1.length());
229 text2.delete(0, text2.length());
230 } else {
231 f1l1 = i;
234 isChangeLeft = !isChangeLeft;
235 continue;
236 } else if (line.startsWith(CHANGE_RIGHT)) {
237 if (generateDiffs) {
238 if (rightFileRevision == null) {
239 rightFileRevision = line.substring(CHANGE_RIGHT.length());
241 if (isChangeRight) {
242 f2l2 = j - 1;
243 diffList.add((f1l1 > f1l2) ? new Difference(Difference.ADD,
244 f1l1 - 1, 0, f2l1, f2l2,
245 text1.toString(),
246 text2.toString()) : (f2l1 > f2l2) ? new Difference(Difference.DELETE,
247 f1l1, f1l2, f2l1 - 1, 0,
248 text1.toString(),
249 text2.toString())
250 : new Difference(Difference.CHANGE,
251 f1l1, f1l2, f2l1, f2l2,
252 text1.toString(),
253 text2.toString()));
255 diffList.add(new Difference((f1l1 > f1l2) ? Difference.ADD :
256 (f2l1 > f2l2) ? Difference.DELETE :
257 Difference.CHANGE,
258 f1l1, f1l2, f2l1, f2l2));
260 f1l1 = f1l2 = f2l1 = f2l2 = 0;
261 text1.delete(0, text1.length());
262 text2.delete(0, text2.length());
263 } else {
264 f2l1 = j;
267 isChangeRight = !isChangeRight;
268 continue;
269 } else if (isChangeRight && line.indexOf(CHANGE_RIGHT) != -1) {
270 String lineText = line.substring(0, line.lastIndexOf(CHANGE_RIGHT));
271 if (generateDiffs) {
272 if (rightFileRevision == null) {
273 rightFileRevision = line.substring(line.lastIndexOf(CHANGE_RIGHT) + CHANGE_RIGHT.length());
275 text2.append(lineText);
276 f2l2 = j;
277 diffList.add((f1l1 > f1l2) ? new Difference(Difference.ADD,
278 f1l1 - 1, 0, f2l1, f2l2,
279 text1.toString(),
280 text2.toString()) : (f2l1 > f2l2) ? new Difference(Difference.DELETE,
281 f1l1, f1l2, f2l1 - 1, 0,
282 text1.toString(),
283 text2.toString())
284 : new Difference(Difference.CHANGE,
285 f1l1, f1l2, f2l1, f2l2,
286 text1.toString(),
287 text2.toString()));
288 f1l1 = f1l2 = f2l1 = f2l2 = 0;
289 text1.delete(0, text1.length());
290 text2.delete(0, text2.length());
292 if (!leftPart) {
293 w.write(lineText);
294 w.newLine();
296 isChangeRight = !isChangeRight;
297 continue;
298 } else if (line.equals(CHANGE_DELIMETER)) {
299 if (isChangeLeft) {
300 isChangeLeft = false;
301 isChangeRight = true;
302 f1l2 = i - 1;
303 f2l1 = j;
304 continue;
305 } else if (isChangeRight) {
306 isChangeRight = false;
307 isChangeLeft = true;
308 f2l2 = j - 1;
309 f1l1 = i;
310 continue;
312 } else if (line.endsWith(CHANGE_DELIMETER)) {
313 String lineText = line.substring(0, line.length() - CHANGE_DELIMETER.length()) + "\n"; // NOI18N
314 if (isChangeLeft) {
315 text1.append(lineText);
316 if (leftPart) {
317 w.write(lineText);
318 w.newLine();
320 isChangeLeft = false;
321 isChangeRight = true;
322 f1l2 = i;
323 f2l1 = j;
324 } else if (isChangeRight) {
325 text2.append(lineText);
326 if (!leftPart) {
327 w.write(lineText);
328 w.newLine();
330 isChangeRight = false;
331 isChangeLeft = true;
332 f2l2 = j;
333 f1l1 = i;
335 continue;
337 if (!isChangeLeft && !isChangeRight || leftPart == isChangeLeft) {
338 w.write(line);
339 w.newLine();
341 if (isChangeLeft) {
342 text1.append(line + "\n"); // NOI18N
344 if (isChangeRight) {
345 text2.append(line + "\n"); // NOI18N
347 if (generateDiffs) {
348 if (isChangeLeft) {
349 i++;
350 } else if (isChangeRight) {
351 j++;
352 } else {
353 i++;
354 j++;
358 } finally {
359 try {
360 r.close();
361 } finally {
362 w.close();
365 if (generateDiffs) {
366 return diffList.toArray(new Difference[diffList.size()]);
367 } else {
368 return null;
372 public void perform() {
373 exec();
376 @Override
377 public void run() {
378 throw new RuntimeException("Not implemented"); // NOI18N
381 private static class MergeResultWriterInfo extends StreamSource {
383 private File tempf1, tempf2, tempf3, outputFile;
384 private File fileToRepairEntriesOf;
385 private String mimeType;
386 private String leftFileRevision;
387 private String rightFileRevision;
388 private FileObject fo;
389 private FileLock lock;
390 private Charset encoding;
392 public MergeResultWriterInfo(File tempf1, File tempf2, File tempf3,
393 File outputFile, String mimeType,
394 String leftFileRevision, String rightFileRevision,
395 FileObject fo, FileLock lock, Charset encoding) {
396 this.tempf1 = tempf1;
397 this.tempf2 = tempf2;
398 this.tempf3 = tempf3;
399 this.outputFile = outputFile;
400 this.mimeType = mimeType;
401 this.leftFileRevision = leftFileRevision;
402 this.rightFileRevision = rightFileRevision;
403 this.fo = fo;
404 this.lock = lock;
405 if (encoding == null) {
406 encoding = FileEncodingQuery.getEncoding(FileUtil.toFileObject(tempf1));
408 this.encoding = encoding;
411 public String getName() {
412 return outputFile.getName();
415 public String getTitle() {
416 return org.openide.util.NbBundle.getMessage(ResolveConflictsExecutor.class, "Merge.titleResult"); // NOI18N
419 public String getMIMEType() {
420 return mimeType;
423 public Reader createReader() throws IOException {
424 throw new IOException("No reader of merge result"); // NOI18N
428 * Create a writer, that writes to the source.
429 * @param conflicts The list of conflicts remaining in the source.
430 * Can be <code>null</code> if there are no conflicts.
431 * @return The writer or <code>null</code>, when no writer can be created.
433 public Writer createWriter(Difference[] conflicts) throws IOException {
434 Writer w;
435 if (fo != null) {
436 w = new OutputStreamWriter(fo.getOutputStream(lock), encoding);
437 } else {
438 w = new OutputStreamWriter(new FileOutputStream(outputFile), encoding);
440 if (conflicts == null || conflicts.length == 0) {
441 fileToRepairEntriesOf = outputFile;
442 return w;
443 } else {
444 return new MergeConflictFileWriter(w, fo, conflicts,
445 leftFileRevision, rightFileRevision);
450 * This method is called when the visual merging process is finished.
451 * All possible writting processes are finished before this method is called.
453 @Override
454 public void close() {
455 tempf1.delete();
456 tempf2.delete();
457 tempf3.delete();
458 if (lock != null) {
459 lock.releaseLock();
460 lock = null;
462 fo = null;
463 if (fileToRepairEntriesOf != null) {
464 repairEntries(fileToRepairEntriesOf);
465 fileToRepairEntriesOf = null;
469 private void repairEntries(File file) {
470 ConflictResolvedAction.resolved(file); // remove conflict status
474 private static class MergeConflictFileWriter extends FilterWriter {
476 private Difference[] conflicts;
477 private int lineNumber;
478 private int currentConflict;
479 private String leftName;
480 private String rightName;
481 private FileObject fo;
483 public MergeConflictFileWriter(Writer delegate, FileObject fo,
484 Difference[] conflicts, String leftName,
485 String rightName) throws IOException {
486 super(delegate);
487 this.conflicts = conflicts;
488 this.leftName = leftName;
489 this.rightName = rightName;
490 this.lineNumber = 1;
491 this.currentConflict = 0;
492 if (lineNumber == conflicts[currentConflict].getFirstStart()) {
493 writeConflict(conflicts[currentConflict]);
494 currentConflict++;
496 this.fo = fo;
499 @Override
500 public void write(String str) throws IOException {
501 super.write(str);
502 lineNumber += numChars('\n', str);
503 if (currentConflict < conflicts.length && lineNumber >= conflicts[currentConflict].getFirstStart()) {
504 writeConflict(conflicts[currentConflict]);
505 currentConflict++;
509 private void writeConflict(Difference conflict) throws IOException {
510 super.write(CHANGE_LEFT + leftName + "\n"); // NOI18N
511 super.write(conflict.getFirstText());
512 super.write(CHANGE_DELIMETER + "\n"); // NOI18N
513 super.write(conflict.getSecondText());
514 super.write(CHANGE_RIGHT + rightName + "\n"); // NOI18N
517 private static int numChars(char c, String str) {
518 int n = 0;
519 for (int pos = str.indexOf(c); pos >= 0 && pos < str.length(); pos = str.indexOf(c, pos + 1)) {
520 n++;
522 return n;
525 @Override
526 public void close() throws IOException {
527 super.close();
528 if (fo != null) {
529 fo.refresh(true);