/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common
* Development and Distribution License("CDDL") (collectively, the
* "License"). You may not use this file except in compliance with the
* License. You can obtain a copy of the License at
* http://www.netbeans.org/cddl-gplv2.html
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
* specific language governing permissions and limitations under the
* License. When distributing the software, include this License Header
* Notice in each file and include the License file at
* nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
* particular file as subject to the "Classpath" exception as provided
* by Sun in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the
* License Header, with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* Contributor(s):
*
* The Original Software is NetBeans. The Initial Developer of the Original
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
* Microsystems, Inc. All Rights Reserved.
* Portions Copyright 2008 Alexander Coles (Ikonoklastik Productions).
*
* If you wish your version of this file to be governed by only the CDDL
* or only the GPL Version 2, indicate your decision by adding
* "[Contributor] elects to include this software in this distribution
* under the [CDDL or GPL Version 2] license." If you do not indicate a
* single choice of license, a recipient has the option to distribute
* your version of this file under either the CDDL, the GPL Version 2 or
* to extend the choice of license to its licensees as provided above.
* However, if you add GPL Version 2 code and therefore, elected the GPL
* Version 2 license, then the option applies only if the new code is
* made subject to such option by the copyright holder.
*/
package org.nbgit.ui.update;
import java.awt.Component;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilterWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Set;
import javax.swing.SwingUtilities;
import org.netbeans.api.diff.Difference;
import org.netbeans.api.diff.StreamSource;
import org.netbeans.api.queries.FileEncodingQuery;
import org.nbgit.GitProgressSupport;
import org.netbeans.spi.diff.MergeVisualizer;
import org.openide.filesystems.FileAlreadyLockedException;
import org.openide.filesystems.FileLock;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.Lookup;
import org.openide.windows.TopComponent;
/**
* Shows basic conflict resolver UI.
*
* This class is copy&pasted from javacvs
*
* @author Martin Entlicher
*/
public class ResolveConflictsExecutor extends GitProgressSupport {
private static final String TMP_PREFIX = "merge"; // NOI18N
private static final String ORIG_SUFFIX = ".orig."; // NOI18N
static final String CHANGE_LEFT = "<<<<<<< "; // NOI18N
static final String CHANGE_RIGHT = ">>>>>>> "; // NOI18N
static final String CHANGE_DELIMETER = "======="; // NOI18N
static final String CHANGE_BASE_DELIMETER = "|||||||"; // NOI18N
private String leftFileRevision = null;
private String rightFileRevision = null;
private final File file;
public ResolveConflictsExecutor(File file) {
super();
this.file = file;
}
public void exec() {
assert SwingUtilities.isEventDispatchThread();
MergeVisualizer merge = Lookup.getDefault().lookup(MergeVisualizer.class);
if (merge == null) {
throw new IllegalStateException("No Merge engine found."); // NOI18N
}
try {
FileObject fo = FileUtil.toFileObject(file);
handleMergeFor(file, fo, fo.lock(), merge);
} catch (FileAlreadyLockedException e) {
Set<TopComponent> components = TopComponent.getRegistry().getOpened();
for (TopComponent tc : components) {
if (tc.getClientProperty(ResolveConflictsExecutor.class.getName()) != null) {
tc.requestActive();
}
}
} catch (IOException ioex) {
org.openide.ErrorManager.getDefault().notify(ioex);
}
}
private void handleMergeFor(final File file, FileObject fo, FileLock lock,
final MergeVisualizer merge) throws IOException {
String mimeType = (fo == null) ? "text/plain" : fo.getMIMEType(); // NOI18N
String ext = "." + fo.getExt(); // NOI18N
File f1 = FileUtil.normalizeFile(File.createTempFile(TMP_PREFIX, ext));
File f2 = FileUtil.normalizeFile(File.createTempFile(TMP_PREFIX, ext));
File f3 = FileUtil.normalizeFile(File.createTempFile(TMP_PREFIX, ext));
f1.deleteOnExit();
f2.deleteOnExit();
f3.deleteOnExit();
final Difference[] diffs = copyParts(true, file, f1, true);
if (diffs.length == 0) {
ConflictResolvedAction.resolved(file); // remove conflict status
return;
}
copyParts(false, file, f2, false);
//GraphicalMergeVisualizer merge = new GraphicalMergeVisualizer();
String originalLeftFileRevision = leftFileRevision;
String originalRightFileRevision = rightFileRevision;
if (leftFileRevision != null) {
leftFileRevision.trim();
}
if (rightFileRevision != null) {
rightFileRevision.trim();
}
if (leftFileRevision == null || leftFileRevision.equals(file.getAbsolutePath() + ORIG_SUFFIX)) {
leftFileRevision = org.openide.util.NbBundle.getMessage(ResolveConflictsExecutor.class, "Diff.titleWorkingFile"); // NOI18N
} else {
leftFileRevision = org.openide.util.NbBundle.getMessage(ResolveConflictsExecutor.class, "Diff.titleRevision", leftFileRevision); // NOI18N
}
if (rightFileRevision == null || rightFileRevision.equals(file.getAbsolutePath() + ORIG_SUFFIX)) {
rightFileRevision = org.openide.util.NbBundle.getMessage(ResolveConflictsExecutor.class, "Diff.titleWorkingFile"); // NOI18N
} else {
rightFileRevision = org.openide.util.NbBundle.getMessage(ResolveConflictsExecutor.class, "Diff.titleRevision", rightFileRevision); // NOI18N
}
final StreamSource s1;
final StreamSource s2;
Charset encoding = FileEncodingQuery.getEncoding(fo);
s1 = StreamSource.createSource(file.getName(), leftFileRevision, mimeType, f1);
s2 = StreamSource.createSource(file.getName(), rightFileRevision, mimeType, f2);
final StreamSource result = new MergeResultWriterInfo(f1, f2, f3, file, mimeType,
originalLeftFileRevision,
originalRightFileRevision,
fo, lock, encoding);
try {
Component c = merge.createView(diffs, s1, s2, result);
if (c instanceof TopComponent) {
((TopComponent) c).putClientProperty(ResolveConflictsExecutor.class.getName(), Boolean.TRUE);
}
} catch (IOException ioex) {
org.openide.ErrorManager.getDefault().notify(ioex);
}
}
/**
* Copy the file and conflict parts into another file.
*/
private Difference[] copyParts(boolean generateDiffs, File source,
File dest, boolean leftPart) throws IOException {
BufferedReader r = new BufferedReader(new FileReader(source));
BufferedWriter w = new BufferedWriter(new FileWriter(dest));
ArrayList<Difference> diffList = null;
if (generateDiffs) {
diffList = new ArrayList<Difference>();
}
try {
String line;
boolean isChangeLeft = false;
boolean isChangeRight = false;
boolean isChangeBase = false;
int f1l1 = 0, f1l2 = 0, f2l1 = 0, f2l2 = 0;
StringBuffer text1 = new StringBuffer();
StringBuffer text2 = new StringBuffer();
int i = 1, j = 1;
while ((line = r.readLine()) != null) {
// As the Graphical Merge Visualizer does not support 3 way diff,
// remove the base diff itself.
// Only show the diffs of the two heads against the base
if (line.startsWith(CHANGE_BASE_DELIMETER)) {
isChangeBase = true;
continue;
}
if (isChangeBase && line.startsWith(CHANGE_DELIMETER)) {
isChangeBase = false;
} else if (isChangeBase) {
continue;
}
if (line.startsWith(CHANGE_LEFT)) {
if (generateDiffs) {
if (leftFileRevision == null) {
leftFileRevision = line.substring(CHANGE_LEFT.length());
}
if (isChangeLeft) {
f1l2 = i - 1;
diffList.add((f1l1 > f1l2) ? new Difference(Difference.ADD,
f1l1 - 1, 0, f2l1, f2l2,
text1.toString(),
text2.toString()) : (f2l1 > f2l2) ? new Difference(Difference.DELETE,
f1l1, f1l2, f2l1 - 1, 0,
text1.toString(),
text2.toString())
: new Difference(Difference.CHANGE,
f1l1, f1l2, f2l1, f2l2,
text1.toString(),
text2.toString()));
f1l1 = f1l2 = f2l1 = f2l2 = 0;
text1.delete(0, text1.length());
text2.delete(0, text2.length());
} else {
f1l1 = i;
}
}
isChangeLeft = !isChangeLeft;
continue;
} else if (line.startsWith(CHANGE_RIGHT)) {
if (generateDiffs) {
if (rightFileRevision == null) {
rightFileRevision = line.substring(CHANGE_RIGHT.length());
}
if (isChangeRight) {
f2l2 = j - 1;
diffList.add((f1l1 > f1l2) ? new Difference(Difference.ADD,
f1l1 - 1, 0, f2l1, f2l2,
text1.toString(),
text2.toString()) : (f2l1 > f2l2) ? new Difference(Difference.DELETE,
f1l1, f1l2, f2l1 - 1, 0,
text1.toString(),
text2.toString())
: new Difference(Difference.CHANGE,
f1l1, f1l2, f2l1, f2l2,
text1.toString(),
text2.toString()));
/*
diffList.add(new Difference((f1l1 > f1l2) ? Difference.ADD :
(f2l1 > f2l2) ? Difference.DELETE :
Difference.CHANGE,
f1l1, f1l2, f2l1, f2l2));
*/
f1l1 = f1l2 = f2l1 = f2l2 = 0;
text1.delete(0, text1.length());
text2.delete(0, text2.length());
} else {
f2l1 = j;
}
}
isChangeRight = !isChangeRight;
continue;
} else if (isChangeRight && line.indexOf(CHANGE_RIGHT) != -1) {
String lineText = line.substring(0, line.lastIndexOf(CHANGE_RIGHT));
if (generateDiffs) {
if (rightFileRevision == null) {
rightFileRevision = line.substring(line.lastIndexOf(CHANGE_RIGHT) + CHANGE_RIGHT.length());
}
text2.append(lineText);
f2l2 = j;
diffList.add((f1l1 > f1l2) ? new Difference(Difference.ADD,
f1l1 - 1, 0, f2l1, f2l2,
text1.toString(),
text2.toString()) : (f2l1 > f2l2) ? new Difference(Difference.DELETE,
f1l1, f1l2, f2l1 - 1, 0,
text1.toString(),
text2.toString())
: new Difference(Difference.CHANGE,
f1l1, f1l2, f2l1, f2l2,
text1.toString(),
text2.toString()));
f1l1 = f1l2 = f2l1 = f2l2 = 0;
text1.delete(0, text1.length());
text2.delete(0, text2.length());
}
if (!leftPart) {
w.write(lineText);
w.newLine();
}
isChangeRight = !isChangeRight;
continue;
} else if (line.equals(CHANGE_DELIMETER)) {
if (isChangeLeft) {
isChangeLeft = false;
isChangeRight = true;
f1l2 = i - 1;
f2l1 = j;
continue;
} else if (isChangeRight) {
isChangeRight = false;
isChangeLeft = true;
f2l2 = j - 1;
f1l1 = i;
continue;
}
} else if (line.endsWith(CHANGE_DELIMETER)) {
String lineText = line.substring(0, line.length() - CHANGE_DELIMETER.length()) + "\n"; // NOI18N
if (isChangeLeft) {
text1.append(lineText);
if (leftPart) {
w.write(lineText);
w.newLine();
}
isChangeLeft = false;
isChangeRight = true;
f1l2 = i;
f2l1 = j;
} else if (isChangeRight) {
text2.append(lineText);
if (!leftPart) {
w.write(lineText);
w.newLine();
}
isChangeRight = false;
isChangeLeft = true;
f2l2 = j;
f1l1 = i;
}
continue;
}
if (!isChangeLeft && !isChangeRight || leftPart == isChangeLeft) {
w.write(line);
w.newLine();
}
if (isChangeLeft) {
text1.append(line + "\n"); // NOI18N
}
if (isChangeRight) {
text2.append(line + "\n"); // NOI18N
}
if (generateDiffs) {
if (isChangeLeft) {
i++;
} else if (isChangeRight) {
j++;
} else {
i++;
j++;
}
}
}
} finally {
try {
r.close();
} finally {
w.close();
}
}
if (generateDiffs) {
return diffList.toArray(new Difference[diffList.size()]);
} else {
return null;
}
}
public void perform() {
exec();
}
@Override
public void run() {
throw new RuntimeException("Not implemented"); // NOI18N
}
private static class MergeResultWriterInfo extends StreamSource {
private File tempf1, tempf2, tempf3, outputFile;
private File fileToRepairEntriesOf;
private String mimeType;
private String leftFileRevision;
private String rightFileRevision;
private FileObject fo;
private FileLock lock;
private Charset encoding;
public MergeResultWriterInfo(File tempf1, File tempf2, File tempf3,
File outputFile, String mimeType,
String leftFileRevision, String rightFileRevision,
FileObject fo, FileLock lock, Charset encoding) {
this.tempf1 = tempf1;
this.tempf2 = tempf2;
this.tempf3 = tempf3;
this.outputFile = outputFile;
this.mimeType = mimeType;
this.leftFileRevision = leftFileRevision;
this.rightFileRevision = rightFileRevision;
this.fo = fo;
this.lock = lock;
if (encoding == null) {
encoding = FileEncodingQuery.getEncoding(FileUtil.toFileObject(tempf1));
}
this.encoding = encoding;
}
public String getName() {
return outputFile.getName();
}
public String getTitle() {
return org.openide.util.NbBundle.getMessage(ResolveConflictsExecutor.class, "Merge.titleResult"); // NOI18N
}
public String getMIMEType() {
return mimeType;
}
public Reader createReader() throws IOException {
throw new IOException("No reader of merge result"); // NOI18N
}
/**
* Create a writer, that writes to the source.
* @param conflicts The list of conflicts remaining in the source.
* Can be <code>null</code> if there are no conflicts.
* @return The writer or <code>null</code>, when no writer can be created.
*/
public Writer createWriter(Difference[] conflicts) throws IOException {
Writer w;
if (fo != null) {
w = new OutputStreamWriter(fo.getOutputStream(lock), encoding);
} else {
w = new OutputStreamWriter(new FileOutputStream(outputFile), encoding);
}
if (conflicts == null || conflicts.length == 0) {
fileToRepairEntriesOf = outputFile;
return w;
} else {
return new MergeConflictFileWriter(w, fo, conflicts,
leftFileRevision, rightFileRevision);
}
}
/**
* This method is called when the visual merging process is finished.
* All possible writting processes are finished before this method is called.
*/
@Override
public void close() {
tempf1.delete();
tempf2.delete();
tempf3.delete();
if (lock != null) {
lock.releaseLock();
lock = null;
}
fo = null;
if (fileToRepairEntriesOf != null) {
repairEntries(fileToRepairEntriesOf);
fileToRepairEntriesOf = null;
}
}
private void repairEntries(File file) {
ConflictResolvedAction.resolved(file); // remove conflict status
}
}
private static class MergeConflictFileWriter extends FilterWriter {
private Difference[] conflicts;
private int lineNumber;
private int currentConflict;
private String leftName;
private String rightName;
private FileObject fo;
public MergeConflictFileWriter(Writer delegate, FileObject fo,
Difference[] conflicts, String leftName,
String rightName) throws IOException {
super(delegate);
this.conflicts = conflicts;
this.leftName = leftName;
this.rightName = rightName;
this.lineNumber = 1;
this.currentConflict = 0;
if (lineNumber == conflicts[currentConflict].getFirstStart()) {
writeConflict(conflicts[currentConflict]);
currentConflict++;
}
this.fo = fo;
}
@Override
public void write(String str) throws IOException {
super.write(str);
lineNumber += numChars('\n', str);
if (currentConflict < conflicts.length && lineNumber >= conflicts[currentConflict].getFirstStart()) {
writeConflict(conflicts[currentConflict]);
currentConflict++;
}
}
private void writeConflict(Difference conflict) throws IOException {
super.write(CHANGE_LEFT + leftName + "\n"); // NOI18N
super.write(conflict.getFirstText());
super.write(CHANGE_DELIMETER + "\n"); // NOI18N
super.write(conflict.getSecondText());
super.write(CHANGE_RIGHT + rightName + "\n"); // NOI18N
}
private static int numChars(char c, String str) {
int n = 0;
for (int pos = str.indexOf(c); pos >= 0 && pos < str.length(); pos = str.indexOf(c, pos + 1)) {
n++;
}
return n;
}
@Override
public void close() throws IOException {
super.close();
if (fo != null) {
fo.refresh(true);
}
}
}
}