blob: 395c661a68fb90a82cfc92a1288b030447048e2a [file] [log] [blame]
package org.eclipse.compare.internal.patch;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.compare.CompareConfiguration;
import org.eclipse.compare.CompareEditorInput;
import org.eclipse.compare.CompareUI;
import org.eclipse.compare.CompareViewerPane;
import org.eclipse.compare.IStreamContentAccessor;
import org.eclipse.compare.ITypedElement;
import org.eclipse.compare.ResourceNode;
import org.eclipse.compare.internal.CompareUIPlugin;
import org.eclipse.compare.internal.DiffImage;
import org.eclipse.compare.internal.Utilities;
import org.eclipse.compare.structuremergeviewer.DiffNode;
import org.eclipse.compare.structuremergeviewer.Differencer;
import org.eclipse.compare.structuremergeviewer.IDiffContainer;
import org.eclipse.compare.structuremergeviewer.IDiffElement;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.viewers.CheckStateChangedEvent;
import org.eclipse.jface.viewers.ICheckStateListener;
import org.eclipse.jface.viewers.ILabelDecorator;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Composite;
public class PatcherCompareEditorInput extends CompareEditorInput {
class PatchedFileNode implements ITypedElement, IStreamContentAccessor {
byte[] bytes;
String type;
String name;
public PatchedFileNode(byte[] bytes, String type, String name){
this.bytes = bytes;
this.type = type;
this.name = name;
}
public Image getImage() {
return null;
}
public String getName() {
return name;
}
public String getType() {
return type;
}
public InputStream getContents() throws CoreException {
return new ByteArrayInputStream(bytes);
}
}
class MyDiffNode extends DiffNode {
//Diff associated with this MyDiffNode
private Diff diff = null;
//Hunk associated with this MyDiffNode
private Hunk hunk = null;
public MyDiffNode(IDiffContainer parent, int kind, ITypedElement ancestor, ITypedElement left, ITypedElement right, Diff diff) {
super(parent, kind, ancestor, left, right);
this.diff = diff;
}
public MyDiffNode(IDiffContainer parent, int kind, ITypedElement ancestor, ITypedElement left, ITypedElement right, Hunk hunk) {
super(parent, kind, ancestor, left, right);
this.hunk = hunk;
}
public String getName() {
if (diff != null)
return diff.getLabel(diff);
if (hunk != null)
return hunk.getLabel(hunk);
return ""; //$NON-NLS-1$
}
Diff getDiff() {
return diff;
}
Hunk getHunk() {
return hunk;
}
}
class PatcherCompareEditorDecorator implements ILabelDecorator {
/** Maps strings to images */
private Map fImages= new Hashtable(10);
private List fDisposeOnShutdownImages= new ArrayList();
ImageDescriptor errId= CompareUIPlugin.getImageDescriptor("ovr16/error_ov.gif"); //$NON-NLS-1$
static final String error = "error"; //$NON-NLS-1$
static final String add = "add"; //$NON-NLS-1$
static final String delete = "del"; //$NON-NLS-1$
public Image decorateImage(Image image, Object element) {
if (element instanceof MyDiffNode){
MyDiffNode myDiffNode = (MyDiffNode) element;
Diff diff = myDiffNode.getDiff();
Hunk hunk = myDiffNode.getHunk();
if (diff != null){
switch (diff.getType()){
case Differencer.ADDITION:
return getImageFor(add + (diff.fMatches ? "" : error), image, diff.fMatches); //$NON-NLS-1$
case Differencer.DELETION:
return getImageFor(delete + (diff.fMatches ? "" : error), image, diff.fMatches); //$NON-NLS-1$
default:
return getImageFor(diff.fMatches ? "" : error, image, diff.fMatches); //$NON-NLS-1$
}
} else if (hunk != null){
return getImageFor((hunk.fMatches ? "" : error),image, hunk.fMatches); //$NON-NLS-1$
}
}
return null;
}
private Image getImageFor(String id, Image image, boolean hasMatches) {
Image cached_image = (Image) fImages.get(id);
if (cached_image == null){
DiffImage diffImage = new DiffImage(image, hasMatches ? null : errId, 16, false);
cached_image = diffImage.createImage();
fImages.put(id, cached_image);
fDisposeOnShutdownImages.add(cached_image);
}
return cached_image;
}
public String decorateText(String text, Object element) {
if (element instanceof DiffNode){
ITypedElement typedElement = ((DiffNode) element).getLeft();
if (typedElement != null && typedElement instanceof DiffProject){
DiffProject project = (DiffProject) typedElement;
if (!project.getName().equals(project.getOriginalProjectName()))
return NLS.bind(PatchMessages.Diff_2Args,
new String[]{project.getOriginalProjectName(),
NLS.bind(PatchMessages.PreviewPatchPage_Target, new String[]{project.getName()})});
}
}
return null;
}
public void dispose() {
if (fDisposeOnShutdownImages != null) {
Iterator i= fDisposeOnShutdownImages.iterator();
while (i.hasNext()) {
Image img= (Image) i.next();
if (!img.isDisposed())
img.dispose();
}
fImages= null;
}
}
public boolean isLabelProperty(Object element, String property) {
return false;
}
public void addListener(ILabelProviderListener listener) {
//don't need listener
}
public void removeListener(ILabelProviderListener listener) {
//don't need listener
}
}
private DiffNode root;
private List failedHunks;
private CheckboxDiffTreeViewer viewer;
private final static int LEFT = 0;
private final static int RIGHT = 1;
private PreviewPatchPage2 previewPatchPage;
private HashMap nodesToDiffs;
public PatcherCompareEditorInput() {
super(new CompareConfiguration());
root = new DiffNode(Differencer.NO_CHANGE) {
public boolean hasChildren() {
return true;
}
};
}
protected Object prepareInput(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
initLabels();
return root;
}
private void initLabels() {
CompareConfiguration cc = getCompareConfiguration();
cc.setLeftEditable(false);
cc.setRightEditable(false);
String leftLabel = PatchMessages.PatcherCompareEditorInput_LocalCopy;
cc.setLeftLabel(leftLabel);
String rightLabel = PatchMessages.PatcherCompareEditorInput_AfterPatch;
cc.setRightLabel(rightLabel);
}
public void updateInput(WorkspacePatcher patcher) {
buildTree(patcher);
updateTree(patcher);
}
private void updateTree(WorkspacePatcher patcher){
if (viewer == null)
return;
int strip= previewPatchPage.getStripPrefixSegments();
//Get the elements from the content provider
ITreeContentProvider contentProvider= (ITreeContentProvider) viewer.getContentProvider();
Object[] projects= contentProvider.getElements(root);
ArrayList hunksToCheck= new ArrayList();
ArrayList nodesToCheck=new ArrayList();
//Iterate through projects and call reset on each project
for (int j= 0; j<projects.length; j++) {
if (!(projects[j] instanceof MyDiffNode)) {
DiffNode projectNode = (DiffNode) projects[j];
ITypedElement project = projectNode.getLeft();
Assert.isNotNull(project);
Assert.isTrue(project instanceof DiffProject);
hunksToCheck.addAll(((DiffProject)project).reset(patcher, strip, previewPatchPage.getFuzzFactor()));
IDiffElement[] diffNodes = projectNode.getChildren();
Iterator iter = hunksToCheck.iterator();
while (iter.hasNext()){
Hunk hunkToMatch = (Hunk) iter.next();
Object matchingHunkNode = nodesToDiffs.get(hunkToMatch);
if (matchingHunkNode != null)
nodesToCheck.add(matchingHunkNode);
}
for (int i = 0; i < diffNodes.length; i++) {
viewer.update(diffNodes[i], null);
IDiffElement[] hunkNodes =((MyDiffNode) diffNodes[i]).getChildren();
for (int k = 0; k < hunkNodes.length; k++) {
viewer.update(hunkNodes[k],null);
}
}
} else {
if (projects[j] instanceof MyDiffNode) {
Diff diff= ((MyDiffNode) projects[j]).getDiff();
hunksToCheck.addAll(diff.reset(patcher, strip, previewPatchPage.getFuzzFactor()));
viewer.update(diff, null);
}
}
}
viewer.refresh();
viewer.setCheckedElements(nodesToCheck.toArray());
updateEnablements();
}
private void buildTree(WorkspacePatcher patcher) {
if (patcher.isWorkspacePatch()) {
if (root.hasChildren()) {
IDiffElement[] children = root.getChildren();
for (int i = 0; i < children.length; i++) {
root.remove(children[i]);
}
}
nodesToDiffs = new HashMap();
DiffProject[] projects = patcher.getDiffProjects();
try {
for (int i = 0; i < projects.length; i++) {
DiffNode projectNode = new DiffNode(root, Differencer.CHANGE, null, projects[i], null);
Iterator iter = projects[i].fDiffs.iterator();
while (iter.hasNext()) {
Object obj = iter.next();
if (obj instanceof Diff) {
Diff diff = (Diff) obj;
IFile tempFile = projects[i].getFile(new Path(diff.getLabel(diff)));
byte[] bytes = quickPatch(tempFile, patcher, diff);
int differencer = Differencer.CHANGE;
if (failedHunks.size() != 0) {
differencer += Differencer.CONFLICTING;
}
if (!tempFile.exists())
tempFile = null;
ResourceNode tempNode = new ResourceNode(tempFile);
PatchedFileNode patchedNode = new PatchedFileNode(bytes, tempNode.getType(), tempFile.getProjectRelativePath().toString());
MyDiffNode allFile = new MyDiffNode(projectNode, differencer, tempNode, tempNode, patchedNode, diff);
//Add individual hunks to each Diff node
Hunk[] hunks = diff.getHunks();
for (int j = 0; j < hunks.length; j++) {
Diff tempDiff = new Diff(diff.fOldPath, diff.fOldDate, diff.fNewPath, diff.fNewDate);
tempDiff.add(hunks[j]);
bytes = quickPatch(tempFile, patcher, tempDiff);
differencer = Differencer.NO_CHANGE;
switch (hunks[j].getHunkType()) {
case Hunk.ADDED :
differencer += Differencer.ADDITION;
break;
case Hunk.CHANGED :
differencer += Differencer.CHANGE;
break;
case Hunk.DELETED :
differencer += Differencer.DELETION;
break;
}
if (failedHunks.size() != 0) {
differencer += Differencer.CONFLICTING;
String[] hunkContents = createInput(hunks[j]);
PatchedFileNode ancestor = new PatchedFileNode(hunkContents[LEFT].getBytes(), hunks[j].fParent.getPath().getFileExtension(), hunks[j].getDescription());
patchedNode = new PatchedFileNode(hunkContents[RIGHT].getBytes(), tempNode.getType(), hunks[j].getDescription());
MyDiffNode hunkNode = new MyDiffNode(allFile, differencer, ancestor, tempNode, patchedNode, hunks[j]);
nodesToDiffs.put(hunks[j], hunkNode);
} else {
patchedNode = new PatchedFileNode(bytes, tempNode.getType(), hunks[j].getDescription());
MyDiffNode hunkNode = new MyDiffNode(allFile, differencer, tempNode, tempNode, patchedNode, hunks[j]);
nodesToDiffs.put(hunks[j], hunkNode);
}
}
}
}
}
} catch (CoreException e) {
//ignore
}
viewer.setInput(root);
viewer.refresh();
} else {
if (root.hasChildren()) {
IDiffElement[] children = root.getChildren();
for (int i = 0; i < children.length; i++) {
root.remove(children[i]);
}
}
nodesToDiffs = new HashMap();
Diff[] diffs = patcher.getDiffs();
try {
for (int i = 0; i < diffs.length; i++) {
Diff diff = diffs[i];
IFile tempFile = patcher.existsInTarget(new Path(diff.getLabel(diff)));
byte[] bytes = quickPatch(tempFile, patcher, diff);
int differencer = Differencer.CHANGE;
if (failedHunks.size() != 0) {
differencer += Differencer.CONFLICTING;
}
ResourceNode tempNode = new ResourceNode(tempFile);
PatchedFileNode patchedNode = new PatchedFileNode(bytes, tempNode.getType(), tempFile.getProjectRelativePath().toString());
MyDiffNode allFile = new MyDiffNode(root, differencer, tempNode, tempNode, patchedNode, diff);
//Add individual hunks to each Diff node
Hunk[] hunks = diff.getHunks();
for (int j = 0; j < hunks.length; j++) {
Diff tempDiff = new Diff(diff.fOldPath, diff.fOldDate, diff.fNewPath, diff.fNewDate);
tempDiff.add(hunks[j]);
bytes = quickPatch(tempFile, patcher, tempDiff);
differencer = Differencer.NO_CHANGE;
switch (hunks[j].getHunkType()) {
case Hunk.ADDED :
differencer += Differencer.ADDITION;
break;
case Hunk.CHANGED :
differencer += Differencer.CHANGE;
break;
case Hunk.DELETED :
differencer += Differencer.DELETION;
break;
}
if (failedHunks.size() != 0) {
differencer += Differencer.CONFLICTING;
String[] hunkContents = createInput(hunks[j]);
PatchedFileNode ancestor = new PatchedFileNode(hunkContents[LEFT].getBytes(), hunks[j].fParent.getPath().getFileExtension(), hunks[j].getDescription());
patchedNode = new PatchedFileNode(hunkContents[RIGHT].getBytes(), tempNode.getType(), hunks[j].getDescription());
MyDiffNode hunkNode = new MyDiffNode(allFile, differencer, ancestor, tempNode, patchedNode, hunks[j]);
nodesToDiffs.put(hunks[j], hunkNode);
} else {
patchedNode = new PatchedFileNode(bytes, tempNode.getType(), hunks[j].getDescription());
MyDiffNode hunkNode = new MyDiffNode(allFile, differencer, tempNode, tempNode, patchedNode, hunks[j]);
nodesToDiffs.put(hunks[j], hunkNode);
}
}
}
} catch (CoreException ex) {//ignore
}
}
}
private byte[] quickPatch(IFile tempFile, WorkspacePatcher patcher, Diff diff) throws CoreException {
failedHunks = new ArrayList();
List result = patcher.apply(diff, tempFile, diff.getType() == Differencer.ADDITION, failedHunks);
String patchedResults = patcher.createString(result);
byte[] bytes;
try {
bytes = patchedResults.getBytes(Utilities.getCharset(tempFile));
} catch (UnsupportedEncodingException e) {
// uses default encoding
bytes = patchedResults.getBytes();
}
return bytes;
}
/* (non-Javadoc)
* @see org.eclipse.compare.CompareEditorInput#createDiffViewer(org.eclipse.swt.widgets.Composite)
*/
public Viewer createDiffViewer(Composite parent) {
viewer = new CheckboxDiffTreeViewer(parent, getCompareConfiguration());
viewer.setLabelDecorator(new PatcherCompareEditorDecorator());
viewer.getTree().setData(CompareUI.COMPARE_VIEWER_TITLE, PatchMessages.PatcherCompareEditorInput_PatchContents);
viewer.addCheckStateListener(new ICheckStateListener() {
public void checkStateChanged(CheckStateChangedEvent event) {
viewer.setSubtreeChecked(event.getElement(),event.getChecked());
updateEnablements();
}
});
viewer.setInput(this);
return viewer;
}
private String[] createInput(Hunk hunk) {
String[] lines= hunk.fLines;
StringBuffer left= new StringBuffer();
StringBuffer right= new StringBuffer();
for (int i= 0; i<lines.length; i++) {
String line= lines[i];
String rest= line.substring(1);
switch (line.charAt(0)) {
case ' ' :
left.append(rest);
right.append(rest);
break;
case '-' :
left.append(rest);
break;
case '+' :
right.append(rest);
break;
}
}
return new String[]{left.toString(),right.toString()};
}
public void contributeDiffViewerToolbarItems(Action[] actions, boolean workspacePatch){
ToolBarManager tbm= CompareViewerPane.getToolBarManager(viewer.getControl().getParent());
if (tbm != null) {
tbm.removeAll();
tbm.add(new Separator("contributed")); //$NON-NLS-1$
for (int i = 0; i < actions.length; i++) {
//If this is a workspace patch, make sure all actions are enabled
if (workspacePatch)
actions[i].setEnabled(true);
tbm.appendToGroup("contributed", actions[i]); //$NON-NLS-1$
}
tbm.update(true);
}
}
public CheckboxDiffTreeViewer getViewer() {
return viewer;
}
/**
* Makes sure that at least one hunk is checked off in the tree before
* allowing the patch to be applied.
*/
/* private */void updateEnablements() {
boolean atLeastOneIsEnabled= false;
if (viewer!=null) {
ITreeContentProvider contentProvider= (ITreeContentProvider) viewer.getContentProvider();
Object[] projects= contentProvider.getElements(root);
//Iterate through projects
for (int j= 0; j<projects.length; j++) {
if (!(projects[j] instanceof MyDiffNode)) {
DiffNode project = (DiffNode) projects[j];
//Iterate through project diffs
Object[] diffs= project.getChildren();
for (int i= 0; i<diffs.length; i++) {
MyDiffNode diff= (MyDiffNode) diffs[i];
atLeastOneIsEnabled= updateEnablement(atLeastOneIsEnabled, diff);
}
} else if (projects[j] instanceof MyDiffNode) {
atLeastOneIsEnabled= updateEnablement(atLeastOneIsEnabled, (MyDiffNode) projects[j]);
}
}
}
previewPatchPage.setPageComplete(atLeastOneIsEnabled);
}
private boolean updateEnablement(boolean oneIsEnabled, MyDiffNode diffNode) {
boolean checked= viewer.getChecked(diffNode);
Diff diff = diffNode.getDiff();
Assert.isNotNull(diff);
diff.setEnabled(checked);
if (checked) {
Object[] hunkItems= diffNode.getChildren();
for (int h= 0; h<hunkItems.length; h++) {
MyDiffNode hunkNode = (MyDiffNode) hunkItems[h];
checked= viewer.getChecked(hunkNode);
Hunk hunk= hunkNode.getHunk();
Assert.isNotNull(hunk);
hunk.setEnabled(checked);
if (checked) {
//For workspace patch: before setting enabled flag, make sure that the project
//that contains this hunk actually exists in the workspace. This is to guard against the
//case of having a new file in a patch that is being applied to a project that
//doesn't currently exist.
boolean projectExists= true;
DiffProject project= (DiffProject)diff.getParent(null);
if (project!= null){
projectExists=project.getProject().exists();
}
if (projectExists)
oneIsEnabled= true;
}
}
}
return oneIsEnabled;
}
/**
* Stores a pointer back to the PreviewPatchPage
* @param page
*/
public void setPreviewPatchPage(PreviewPatchPage2 page) {
previewPatchPage = page;
}
}