Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to 'bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src')
-rw-r--r--bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/Activator.java70
-rw-r--r--bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/Messages.java32
-rw-r--r--bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/messages.properties10
-rw-r--r--bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/DiffProject.java98
-rw-r--r--bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/FileDiff.java260
-rw-r--r--bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/FileDiffResult.java335
-rw-r--r--bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/Hunk.java445
-rw-r--r--bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/HunkResult.java294
-rw-r--r--bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/LineReader.java137
-rw-r--r--bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/PatchReader.java669
-rw-r--r--bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/Patcher.java877
-rw-r--r--bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/Utilities.java31
-rw-r--r--bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/WorkspaceFileDiffResult.java53
-rw-r--r--bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/WorkspacePatcher.java377
-rw-r--r--bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/IFilePatch.java89
-rw-r--r--bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/IFilePatchResult.java94
-rw-r--r--bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/IHunk.java82
-rw-r--r--bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchConfiguration.java122
-rw-r--r--bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchParser.java60
19 files changed, 4135 insertions, 0 deletions
diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/Activator.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/Activator.java
new file mode 100644
index 000000000..e8f86548f
--- /dev/null
+++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/Activator.java
@@ -0,0 +1,70 @@
+/*******************************************************************************
+ * Copyright (c) 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.internal.core;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Plugin;
+import org.eclipse.core.runtime.Status;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The activator class controls the plug-in life cycle
+ */
+public class Activator extends Plugin {
+
+ // The plug-in ID
+ public static final String PLUGIN_ID = "org.eclipse.compare.core"; //$NON-NLS-1$
+
+ // The shared instance
+ private static Activator plugin;
+
+ /**
+ * The constructor
+ */
+ public Activator() {
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.core.runtime.Plugins#start(org.osgi.framework.BundleContext)
+ */
+ public void start(BundleContext context) throws Exception {
+ super.start(context);
+ plugin = this;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.core.runtime.Plugin#stop(org.osgi.framework.BundleContext)
+ */
+ public void stop(BundleContext context) throws Exception {
+ plugin = null;
+ super.stop(context);
+ }
+
+ /**
+ * Returns the shared instance
+ *
+ * @return the shared instance
+ */
+ public static Activator getDefault() {
+ return plugin;
+ }
+
+ public static void log(Throwable e) {
+ log(new Status(IStatus.ERROR, PLUGIN_ID, 0, Messages.Activator_1, e));
+ }
+
+ public static void log(IStatus status) {
+ getDefault().getLog().log(status);
+ }
+
+}
diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/Messages.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/Messages.java
new file mode 100644
index 000000000..b43efaaee
--- /dev/null
+++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/Messages.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Licensed Materials - Property of IBM
+ * (c) Copyright IBM Corporation 2008. All Rights Reserved.
+ *
+ * Note to U.S. Government Users Restricted Rights: Use,
+ * duplication or disclosure restricted by GSA ADP Schedule
+ * Contract with IBM Corp.
+ *******************************************************************************/
+package org.eclipse.compare.internal.core;
+
+import org.eclipse.osgi.util.NLS;
+
+public class Messages extends NLS {
+ private static final String BUNDLE_NAME = "org.eclipse.compare.internal.core.messages"; //$NON-NLS-1$
+ public static String Activator_1;
+ public static String FileDiffResult_0;
+ public static String FileDiffResult_1;
+ public static String FileDiffResult_2;
+ public static String FileDiffResult_3;
+ public static String Patcher_0;
+ public static String Patcher_1;
+ public static String Patcher_2;
+ public static String WorkspacePatcher_0;
+ public static String WorkspacePatcher_1;
+ static {
+ // initialize resource bundle
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ private Messages() {
+ }
+}
diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/messages.properties b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/messages.properties
new file mode 100644
index 000000000..8db9bece1
--- /dev/null
+++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/messages.properties
@@ -0,0 +1,10 @@
+Activator_1=Internal error
+FileDiffResult_0=(file already exists)
+FileDiffResult_1=(file does not exist)
+FileDiffResult_2={0} {1}
+FileDiffResult_3={0} (hunk \#{1})
+Patcher_0=Patching
+Patcher_1=Rejected patch
+Patcher_2=Guessing Fuzz Factor...
+WorkspacePatcher_0=Patching
+WorkspacePatcher_1=Rejected patch
diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/DiffProject.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/DiffProject.java
new file mode 100644
index 000000000..ddb461292
--- /dev/null
+++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/DiffProject.java
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.internal.patch;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.runtime.IPath;
+
+/**
+ * A diff project represents a project that was read from a workspace patch.
+ * It contains the set of file diffs that were associated with the project
+ * in the patch file.
+ */
+public class DiffProject {
+
+ private IProject fProject;
+ private Set fDiffs= new HashSet();
+
+ /**
+ * Create a diff project for the given workspace project.
+ * @param project a workspace project
+ */
+ public DiffProject(IProject project) {
+ this.fProject= project;
+ }
+
+ /**
+ * Add the file diff to this project.
+ * @param diff the file diff.
+ */
+ void add(FileDiff diff) {
+ fDiffs.add(diff);
+ if (diff.getProject() != this)
+ diff.setProject(this);
+ }
+
+
+ /**
+ * Return the workspace project associated with this diff project.
+ * @return the workspace project associated with this project
+ */
+ public IProject getProject() {
+ return this.fProject;
+ }
+
+ /**
+ * Return the name of this project.
+ * @return the name of this project
+ */
+ public String getName() {
+ return fProject.getName();
+ }
+
+ /**
+ * Return the file at the given path relative to this project.
+ * @param path the relative path
+ * @return the file at the given path relative to this project
+ */
+ public IFile getFile(IPath path) {
+ return fProject.getFile(path);
+ }
+
+ /**
+ * Remove the file diff from this project.
+ * @param diff the diff to be removed
+ */
+ public void remove(FileDiff diff) {
+ fDiffs.remove(diff);
+ }
+
+ /**
+ * Return whether this project contains the given diff.
+ * @param diff a file diff
+ * @return whether this project contains the given diff
+ */
+ public boolean contains(FileDiff diff) {
+ return fDiffs.contains(diff);
+ }
+
+ /**
+ * Return the file diffs associated with this project.
+ * @return the file diffs associated with this project
+ */
+ public FileDiff[] getFileDiffs() {
+ return (FileDiff[]) fDiffs.toArray(new FileDiff[fDiffs.size()]);
+ }
+}
diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/FileDiff.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/FileDiff.java
new file mode 100644
index 000000000..be69fb4f2
--- /dev/null
+++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/FileDiff.java
@@ -0,0 +1,260 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.internal.patch;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.compare.patch.IFilePatch;
+import org.eclipse.compare.patch.IFilePatchResult;
+import org.eclipse.compare.patch.PatchConfiguration;
+import org.eclipse.core.resources.IStorage;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.Path;
+
+/**
+ * A file diff represents a set of hunks that were associated with the
+ * same path in a patch file.
+ */
+public class FileDiff implements IFilePatch {
+
+ /**
+ * Difference constant (value 1) indicating one side was added.
+ */
+ public static final int ADDITION= 1;
+ /**
+ * Difference constant (value 2) indicating one side was removed.
+ */
+ public static final int DELETION= 2;
+ /**
+ * Difference constant (value 3) indicating side changed.
+ */
+ public static final int CHANGE= 3;
+
+ private IPath fOldPath, fNewPath;
+ private long oldDate, newDate;
+ private List fHunks= new ArrayList();
+ private DiffProject fProject; //the project that contains this diff
+ private String header;
+ private int addedLines, removedLines;
+
+ /**
+ * Create a file diff for the given path and date information.
+ * @param oldPath the path of the before state of the file
+ * @param oldDate the timestamp of the before state
+ * @param newPath the path of the after state
+ * @param newDate the timestamp of the after state
+ */
+ protected FileDiff(IPath oldPath, long oldDate, IPath newPath, long newDate) {
+ fOldPath= oldPath;
+ this.oldDate = oldDate;
+ fNewPath= newPath;
+ this.newDate = newDate;
+ }
+
+ /**
+ * Return the parent project or <code>null</code> if there isn't one.
+ * @return the parent project or <code>null</code>
+ */
+ public DiffProject getProject() {
+ return fProject;
+ }
+
+ /**
+ * Set the project of this diff to the given project.
+ * This method should only be called from
+ * {@link DiffProject#add(FileDiff)}
+ * @param diffProject the parent project
+ */
+ void setProject(DiffProject diffProject) {
+ if (fProject == diffProject)
+ return;
+ if (fProject != null)
+ fProject.remove(this);
+ this.fProject= diffProject;
+ }
+
+ /**
+ * Get the path of the file diff.
+ * @param reverse whether the path of the before state or after state
+ * should be used
+ * @return the path of the file diff
+ */
+ public IPath getPath(boolean reverse) {
+ if (getDiffType(reverse) == ADDITION) {
+ if (reverse)
+ return fOldPath;
+ return fNewPath;
+ }
+ if (reverse && fNewPath != null)
+ return fNewPath;
+ if (fOldPath != null)
+ return fOldPath;
+ return fNewPath;
+ }
+
+ /**
+ * Add the hunk to this file diff.
+ * @param hunk the hunk
+ */
+ protected void add(Hunk hunk) {
+ fHunks.add(hunk);
+ hunk.setParent(this);
+ }
+
+ /**
+ * Remove the hunk from this file diff
+ * @param hunk the hunk
+ */
+ protected void remove(Hunk hunk) {
+ fHunks.remove(hunk);
+ }
+
+ /**
+ * Return the hunks associated with this file diff.
+ * @return the hunks associated with this file diff
+ */
+ public Hunk[] getHunks() {
+ return (Hunk[]) fHunks.toArray(new Hunk[fHunks.size()]);
+ }
+
+ /**
+ * Return the number of hunks associated with this file diff.
+ * @return the number of hunks associated with this file diff
+ */
+ public int getHunkCount() {
+ return fHunks.size();
+ }
+
+ /**
+ * Return the difference type of this file diff.
+ * @param reverse whether the patch is being reversed
+ * @return the type of this file diff
+ */
+ public int getDiffType(boolean reverse) {
+ if (fHunks.size() == 1) {
+ boolean add = false;
+ boolean delete = false;
+ Iterator iter = fHunks.iterator();
+ while (iter.hasNext()){
+ Hunk hunk = (Hunk) iter.next();
+ int type =hunk.getHunkType(reverse);
+ if (type == ADDITION){
+ add = true;
+ } else if (type == DELETION ){
+ delete = true;
+ }
+ }
+ if (add && !delete){
+ return ADDITION;
+ } else if (!add && delete){
+ return DELETION;
+ }
+ }
+ return CHANGE;
+ }
+
+ /**
+ * Return the path of this file diff with the specified number
+ * of leading segments striped.
+ * @param strip the number of leading segments to strip from the path
+ * @param reverse whether the patch is being reversed
+ * @return the path of this file diff with the specified number
+ * of leading segments striped
+ */
+ protected IPath getStrippedPath(int strip, boolean reverse) {
+ IPath path= getPath(reverse);
+ if (strip > 0 && strip < path.segmentCount())
+ path= path.removeFirstSegments(strip);
+ return path;
+ }
+
+ /**
+ * Return the segment count of the path of this file diff.
+ * @return the segment count of the path of this file diff
+ */
+ public int segmentCount() {
+ //Update prefix count - go through all of the diffs and find the smallest
+ //path segment contained in all diffs.
+ int length= 99;
+ if (fOldPath != null)
+ length= Math.min(length, fOldPath.segmentCount());
+ if (fNewPath != null)
+ length= Math.min(length, fNewPath.segmentCount());
+ return length;
+ }
+
+ public IFilePatchResult apply(IStorage contents,
+ PatchConfiguration configuration, IProgressMonitor monitor) {
+ FileDiffResult result = new FileDiffResult(this, configuration);
+ result.refresh(contents, monitor);
+ return result;
+ }
+
+ public IPath getTargetPath(PatchConfiguration configuration) {
+ return getStrippedPath(configuration.getPrefixSegmentStripCount(), configuration.isReversed());
+ }
+
+ public FileDiff asRelativeDiff() {
+ if (fProject == null)
+ return this;
+ IPath adjustedOldPath = null;
+ if (fOldPath != null) {
+ adjustedOldPath = new Path(null, fProject.getName()).append(fOldPath);
+ }
+ IPath adjustedNewPath = null;
+ if (fNewPath != null) {
+ adjustedNewPath = new Path(null, fProject.getName()).append(fNewPath);
+ }
+ FileDiff diff = new FileDiff(adjustedOldPath, 0, adjustedNewPath, 0);
+ for (Iterator iterator = fHunks.iterator(); iterator.hasNext();) {
+ Hunk hunk = (Hunk) iterator.next();
+ // Creating the hunk adds it to the parent diff
+ new Hunk(diff, hunk);
+ }
+ return diff;
+ }
+
+ public void setHeader(String header) {
+ this.header = header;
+ }
+
+ public String getHeader() {
+ return header;
+ }
+
+ public long getBeforeDate() {
+ return oldDate;
+ }
+
+ public long getAfterDate() {
+ return newDate;
+ }
+
+ public void setAddedLines(int addedLines) {
+ this.addedLines = addedLines;
+ }
+
+ public void setRemovedLines(int removedLines) {
+ this.removedLines = removedLines;
+ }
+
+ public int getAddedLines() {
+ return addedLines;
+ }
+
+ public int getRemovedLines() {
+ return removedLines;
+ }
+
+}
diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/FileDiffResult.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/FileDiffResult.java
new file mode 100644
index 000000000..1d3d74d4b
--- /dev/null
+++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/FileDiffResult.java
@@ -0,0 +1,335 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2007 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.internal.patch;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.compare.internal.core.Activator;
+import org.eclipse.compare.internal.core.Messages;
+import org.eclipse.compare.patch.IFilePatchResult;
+import org.eclipse.compare.patch.IHunk;
+import org.eclipse.compare.patch.PatchConfiguration;
+import org.eclipse.core.resources.IStorage;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.osgi.util.NLS;
+
+public class FileDiffResult implements IFilePatchResult {
+
+ private FileDiff fDiff;
+ private boolean fMatches= false;
+ private boolean fDiffProblem;
+ private String fErrorMessage;
+ private Map fHunkResults = new HashMap();
+ private List fBeforeLines, fAfterLines;
+ private final PatchConfiguration configuration;
+ private String charset;
+
+ public FileDiffResult(FileDiff diff, PatchConfiguration configuration) {
+ super();
+ fDiff = diff;
+ this.configuration = configuration;
+ }
+
+ public PatchConfiguration getConfiguration() {
+ return configuration;
+ }
+
+ public boolean canApplyHunk(Hunk hunk) {
+ HunkResult result = getHunkResult(hunk);
+ return result.isOK() && !fDiffProblem;
+ }
+
+ /**
+ * Refreshes the state of the diff to {no matches, no problems} and checks to see what hunks contained
+ * by this Diff can actually be applied.
+ *
+ * Checks to see:
+ * 1) if the target file specified in fNewPath exists and is patchable
+ * 2) which hunks contained by this diff can actually be applied to the file
+ * @param storage the contents being patched or <code>null</code> for an addition
+ * @param monitor a progress monitor or <code>null</code> if no progress monitoring is desired
+ */
+ public void refresh(IStorage storage, IProgressMonitor monitor) {
+ fMatches= false;
+ fDiffProblem= false;
+ boolean create= false;
+ charset = Utilities.getCharset(storage);
+ //If this diff is an addition, make sure that it doesn't already exist
+ boolean exists = targetExists(storage);
+ if (fDiff.getDiffType(getConfiguration().isReversed()) == FileDiff.ADDITION) {
+ if ((!exists || isEmpty(storage)) && canCreateTarget(storage)) {
+ fMatches= true;
+ } else {
+ // file already exists
+ fDiffProblem= true;
+ fErrorMessage= Messages.FileDiffResult_0;
+ }
+ create= true;
+ } else { //This diff is not an addition, try to find a match for it
+ //Ensure that the file described by the path exists and is modifiable
+ if (exists) {
+ fMatches= true;
+ } else {
+ // file doesn't exist
+ fDiffProblem= true;
+ fErrorMessage= Messages.FileDiffResult_1;
+ }
+ }
+
+ if (fDiffProblem) {
+ // We couldn't find the target file or the patch is trying to add a
+ // file that already exists but we need to initialize the hunk
+ // results for display
+ fBeforeLines = new ArrayList(getLines(storage, false));
+ fAfterLines = fMatches ? new ArrayList() : fBeforeLines;
+ Hunk[] hunks = fDiff.getHunks();
+ for (int i = 0; i < hunks.length; i++) {
+ Hunk hunk = hunks[i];
+ HunkResult result = getHunkResult(hunk);
+ result.setMatches(false);
+ }
+ } else {
+ // If this diff has no problems discovered so far, try applying the patch
+ patch(getLines(storage, create), monitor);
+ }
+
+ if (containsProblems()) {
+ if (fMatches) {
+ // Check to see if we have at least one hunk that matches
+ fMatches = false;
+ Hunk[] hunks = fDiff.getHunks();
+ for (int i = 0; i < hunks.length; i++) {
+ Hunk hunk = hunks[i];
+ HunkResult result = getHunkResult(hunk);
+ if (result.isOK()) {
+ fMatches = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ protected boolean canCreateTarget(IStorage storage) {
+ return true;
+ }
+
+ protected boolean targetExists(IStorage storage) {
+ return storage != null;
+ }
+
+ protected List getLines(IStorage storage, boolean create) {
+ List lines = Patcher.load(storage, create);
+ return lines;
+ }
+
+ protected boolean isEmpty(IStorage storage) {
+ if (storage == null)
+ return true;
+ return Patcher.load(storage, false).isEmpty();
+ }
+
+ /*
+ * Tries to patch the given lines with the specified Diff.
+ * Any hunk that couldn't be applied is returned in the list failedHunks.
+ */
+ public void patch(List lines, IProgressMonitor monitor) {
+ fBeforeLines = new ArrayList();
+ fBeforeLines.addAll(lines);
+ if (getConfiguration().getFuzz() != 0) {
+ calculateFuzz(fBeforeLines, monitor);
+ }
+ int shift= 0;
+ Hunk[] hunks = fDiff.getHunks();
+ for (int i = 0; i < hunks.length; i++) {
+ Hunk hunk = hunks[i];
+ HunkResult result = getHunkResult(hunk);
+ result.setShift(shift);
+ if (result.patch(lines)) {
+ shift = result.getShift();
+ }
+ }
+ fAfterLines = lines;
+ }
+
+ public boolean getDiffProblem() {
+ return fDiffProblem;
+ }
+
+ /**
+ * Returns whether this Diff has any problems
+ * @return true if this Diff or any of its children Hunks have a problem, false if it doesn't
+ */
+ public boolean containsProblems() {
+ if (fDiffProblem)
+ return true;
+ for (Iterator iterator = fHunkResults.values().iterator(); iterator.hasNext();) {
+ HunkResult result = (HunkResult) iterator.next();
+ if (!result.isOK())
+ return true;
+ }
+ return false;
+ }
+
+ public String getLabel() {
+ String label= getTargetPath().toString();
+ if (this.fDiffProblem)
+ return NLS.bind(Messages.FileDiffResult_2, new String[] {label, fErrorMessage});
+ return label;
+ }
+
+ public boolean hasMatches() {
+ return fMatches;
+ }
+
+ /**
+ * Return the lines of the target file with all matched hunks applied.
+ * @return the lines of the target file with all matched hunks applied
+ */
+ public List getLines() {
+ return fAfterLines;
+ }
+
+ /**
+ * Calculate the fuzz factor that will allow the most hunks to be matched.
+ * @param lines the lines of the target file
+ * @param monitor a progress monitor
+ * @return the fuzz factor or <code>-1</code> if no hunks could be matched
+ */
+ public int calculateFuzz(List lines, IProgressMonitor monitor) {
+ if (monitor == null)
+ monitor = new NullProgressMonitor();
+ fBeforeLines = new ArrayList(lines);
+ // TODO: What about deletions?
+ if (fDiff.getDiffType(getConfiguration().isReversed()) == FileDiff.ADDITION) {
+ // Additions don't need to adjust the fuzz factor
+ // TODO: What about the after lines?
+ return -1;
+ }
+ int shift= 0;
+ int highestFuzz = -1; // the maximum fuzz factor for all hunks
+ String name = getTargetPath() != null ? getTargetPath().lastSegment() : ""; //$NON-NLS-1$
+ Hunk[] hunks = fDiff.getHunks();
+ for (int j = 0; j < hunks.length; j++) {
+ Hunk h = hunks[j];
+ monitor.subTask(NLS.bind(Messages.FileDiffResult_3, new String[] {name, Integer.toString(j + 1)}));
+ HunkResult result = getHunkResult(h);
+ result.setShift(shift);
+ int fuzz = result.calculateFuzz(lines, monitor);
+ shift = result.getShift();
+ if (fuzz > highestFuzz)
+ highestFuzz = fuzz;
+ monitor.worked(1);
+ }
+ fAfterLines = lines;
+ return highestFuzz;
+ }
+
+ public IPath getTargetPath() {
+ return fDiff.getStrippedPath(getConfiguration().getPrefixSegmentStripCount(), getConfiguration().isReversed());
+ }
+
+ private HunkResult getHunkResult(Hunk hunk) {
+ HunkResult result = (HunkResult)fHunkResults.get(hunk);
+ if (result == null) {
+ result = new HunkResult(this, hunk);
+ fHunkResults .put(hunk, result);
+ }
+ return result;
+ }
+
+ List getFailedHunks() {
+ List failedHunks = new ArrayList();
+ for (Iterator iterator = fHunkResults.values().iterator(); iterator.hasNext();) {
+ HunkResult result = (HunkResult) iterator.next();
+ if (!result.isOK())
+ failedHunks.add(result.getHunk());
+ }
+ return failedHunks;
+ }
+
+ private HunkResult[] getFailedHunkResults() {
+ List failedHunks = new ArrayList();
+ for (Iterator iterator = fHunkResults.values().iterator(); iterator.hasNext();) {
+ HunkResult result = (HunkResult) iterator.next();
+ if (!result.isOK())
+ failedHunks.add(result);
+ }
+ return (HunkResult[]) failedHunks.toArray(new HunkResult[failedHunks.size()]);
+ }
+
+ public FileDiff getDiff() {
+ return fDiff;
+ }
+
+ public List getBeforeLines() {
+ return fBeforeLines;
+ }
+
+ public List getAfterLines() {
+ return fAfterLines;
+ }
+
+ public HunkResult[] getHunkResults() {
+ return (HunkResult[]) fHunkResults.values().toArray(new HunkResult[fHunkResults.size()]);
+ }
+
+ public InputStream getOriginalContents() {
+ String contents = Patcher.createString(isPreserveLineDelimeters(), getBeforeLines());
+ return asInputStream(contents, getCharset());
+ }
+
+ public InputStream getPatchedContents() {
+ String contents = Patcher.createString(isPreserveLineDelimeters(), getLines());
+ return asInputStream(contents, getCharset());
+ }
+
+ public String getCharset() {
+ return charset;
+ }
+
+ public boolean isPreserveLineDelimeters() {
+ return false;
+ }
+
+ public IHunk[] getRejects() {
+ return getFailedHunkResults();
+ }
+
+ public boolean hasRejects() {
+ return getFailedHunkResults().length > 0;
+ }
+
+ public static InputStream asInputStream(String contents, String charSet) {
+ byte[] bytes = null;
+ if (charSet != null) {
+ try {
+ bytes = contents.getBytes(charSet);
+ } catch (UnsupportedEncodingException e) {
+ Activator.log(e);
+ }
+ }
+ if (bytes == null) {
+ bytes = contents.getBytes();
+ }
+ return new ByteArrayInputStream(bytes);
+ }
+
+}
diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/Hunk.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/Hunk.java
new file mode 100644
index 000000000..bd3844030
--- /dev/null
+++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/Hunk.java
@@ -0,0 +1,445 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.internal.patch;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.compare.patch.PatchConfiguration;
+import org.eclipse.core.runtime.Assert;
+
+/**
+ * A Hunk describes a range of changed lines and some context lines.
+ */
+public class Hunk {
+
+ private FileDiff fParent;
+ private int fOldStart, fOldLength;
+ private int fNewStart, fNewLength;
+ private String[] fLines;
+ private int hunkType;
+
+ public Hunk(FileDiff parent, Hunk toCopy) {
+ fParent = parent;
+ if (fParent != null) {
+ fParent.add(this);
+ }
+
+ fOldStart = toCopy.fOldStart;
+ fOldLength = toCopy.fOldLength;
+ fNewStart = toCopy.fNewStart;
+ fNewLength = toCopy.fOldLength;
+ fLines = toCopy.fLines;
+ hunkType = toCopy.hunkType;
+ }
+
+ public Hunk(FileDiff parent, int[] oldRange, int[] newRange, List lines, boolean hasLineAdditions, boolean hasLineDeletions, boolean hasContextLines) {
+
+ fParent= parent;
+ if (fParent != null)
+ fParent.add(this);
+
+ if (oldRange[0] > 0)
+ fOldStart= oldRange[0]-1; // line number start at 0!
+ else
+ fOldStart= 0;
+ fOldLength= oldRange[1];
+ if (newRange[0] > 0)
+ fNewStart= newRange[0]-1; // line number start at 0!
+ else
+ fNewStart= 0;
+ fNewLength= newRange[1];
+
+ fLines= (String[]) lines.toArray(new String[lines.size()]);
+
+ hunkType = FileDiff.CHANGE;
+ if (!hasContextLines) {
+ if (hasLineAdditions && !hasLineDeletions) {
+ hunkType = FileDiff.ADDITION;
+ } else if (!hasLineAdditions && hasLineDeletions) {
+ hunkType = FileDiff.DELETION;
+ }
+ }
+ }
+
+ /*
+ * Returns the contents of this hunk.
+ * Each line starts with a control character. Their meaning is as follows:
+ * <ul>
+ * <li>
+ * '+': add the line
+ * <li>
+ * '-': delete the line
+ * <li>
+ * ' ': no change, context line
+ * </ul>
+ */
+ String getContent() {
+ StringBuffer sb= new StringBuffer();
+ for (int i= 0; i < fLines.length; i++) {
+ String line= fLines[i];
+ sb.append(line.substring(0, Patcher.length(line)));
+ sb.append('\n');
+ }
+ return sb.toString();
+ }
+
+ /*
+ * Returns a descriptive String for this hunk.
+ * It is in the form old_start,old_length -> new_start,new_length.
+ */
+ String getDescription() {
+ StringBuffer sb= new StringBuffer();
+ sb.append(Integer.toString(fOldStart));
+ sb.append(',');
+ sb.append(Integer.toString(fOldLength));
+ sb.append(" -> "); //$NON-NLS-1$
+ sb.append(Integer.toString(fNewStart));
+ sb.append(',');
+ sb.append(Integer.toString(fNewLength));
+ return sb.toString();
+ }
+
+ String getRejectedDescription() {
+ StringBuffer sb= new StringBuffer();
+ sb.append("@@ -"); //$NON-NLS-1$
+ sb.append(Integer.toString(fOldStart));
+ sb.append(',');
+ sb.append(Integer.toString(fOldLength));
+ sb.append(" +"); //$NON-NLS-1$
+ sb.append(Integer.toString(fNewStart));
+ sb.append(',');
+ sb.append(Integer.toString(fNewLength));
+ sb.append(" @@"); //$NON-NLS-1$
+ return sb.toString();
+ }
+
+ int getHunkType(boolean reverse) {
+ if (reverse) {
+ if (hunkType == FileDiff.ADDITION)
+ return FileDiff.DELETION;
+ if (hunkType == FileDiff.DELETION)
+ return FileDiff.ADDITION;
+ }
+ return hunkType;
+ }
+
+ void setHunkType(int hunkType) {
+ this.hunkType = hunkType;
+ }
+
+ public String[] getLines() {
+ return fLines;
+ }
+
+ /**
+ * Set the parent of this hunk. This method
+ * should only be invoked from {@link FileDiff#add(Hunk)}
+ * @param diff the parent of this hunk
+ */
+ void setParent(FileDiff diff) {
+ if (fParent == diff)
+ return;
+ if (fParent != null)
+ fParent.remove(this);
+ fParent = diff;
+ }
+
+ public FileDiff getParent() {
+ return fParent;
+ }
+
+ /*
+ * Tries to apply the given hunk on the specified lines.
+ * The parameter shift is added to the line numbers given
+ * in the hunk.
+ */
+ public boolean tryPatch(PatchConfiguration configuration, List lines, int shift, int fuzz) {
+ boolean reverse = configuration.isReversed();
+ int pos = getStart(reverse) + shift;
+ int deleteMatches = 0;
+ List contextLines = new ArrayList();
+ boolean contextLinesMatched = true;
+ boolean precedingLinesChecked = false;
+ for (int i= 0; i < fLines.length; i++) {
+ String s = fLines[i];
+ Assert.isTrue(s.length() > 0);
+ String line = s.substring(1);
+ char controlChar = s.charAt(0);
+
+ if (controlChar == ' ') { // context lines
+
+ if (pos < 0 || pos >= lines.size())
+ return false;
+ contextLines.add(line);
+ if (linesMatch(configuration, line, (String) lines.get(pos))) {
+ pos++;
+ continue;
+ } else if (fuzz > 0) {
+ // doesn't match, use the fuzz factor
+ contextLinesMatched = false;
+ pos++;
+ continue;
+ }
+ return false;
+ } else if (isDeletedDelimeter(controlChar, reverse)) {
+ // deleted lines
+
+ if (precedingLinesChecked && !contextLinesMatched && contextLines.size() > 0)
+ // context lines inside hunk don't match
+ return false;
+
+ // check following context lines if exist
+ // use the fuzz factor if needed
+ if (!precedingLinesChecked
+ && !contextLinesMatched
+ && contextLines.size() >= fuzz
+ && !checkPrecedingContextLines(configuration, lines,
+ fuzz, pos, contextLines))
+ return false;
+ // else if there is less or equal context line to the fuzz
+ // factor we ignore them all and treat as matching
+
+ precedingLinesChecked = true;
+ contextLines.clear();
+ contextLinesMatched = true;
+
+ if (pos < 0 || pos >= lines.size()) // out of the file
+ return false;
+ if (linesMatch(configuration, line, (String) lines.get(pos))) {
+ deleteMatches++;
+ pos++;
+ continue; // line matched, continue with the next one
+ }
+
+ // We must remove all lines at once, return false if this
+ // fails. In other words, all lines considered for deletion
+ // must be found one by one.
+
+ // if (deleteMatches <= 0)
+ return false;
+ // pos++;
+ } else if (isAddedDelimeter(controlChar, reverse)) {
+
+ if (precedingLinesChecked && !contextLinesMatched && contextLines.size() > 0)
+ return false;
+
+ if (!precedingLinesChecked
+ && !contextLinesMatched
+ && contextLines.size() >= fuzz
+ && !checkPrecedingContextLines(configuration, lines,
+ fuzz, pos, contextLines))
+ return false;
+
+ precedingLinesChecked = true;
+ contextLines.clear();
+ contextLinesMatched = true;
+
+ // we don't have to do anything more for a 'try'
+ } else
+ Assert.isTrue(false, "tryPatch: unknown control character: " + controlChar); //$NON-NLS-1$
+ }
+
+ // check following context lines if exist
+ if (!contextLinesMatched
+ && fuzz > 0
+ && contextLines.size() > fuzz
+ && !checkFollowingContextLines(configuration, lines, fuzz, pos,
+ contextLines))
+ return false;
+
+ return true;
+ }
+
+ private boolean checkPrecedingContextLines(
+ PatchConfiguration configuration, List lines, int fuzz, int pos,
+ List contextLines) {
+
+ // ignore from the beginning
+ for (int j = fuzz; j < contextLines.size(); j++) {
+ if (!linesMatch(configuration, (String) contextLines.get(j),
+ (String) lines.get(pos - contextLines.size() + j)))
+ return false;
+ }
+ return true;
+ }
+
+ private boolean checkFollowingContextLines(
+ PatchConfiguration configuration, List lines, int fuzz, int pos,
+ List contextLines) {
+ if (!contextLines.isEmpty()) {
+ // ignore from the end
+ for (int j = 0; j < contextLines.size() - fuzz; j++) {
+ if (!linesMatch(configuration, (String) contextLines.get(j),
+ (String) lines.get(pos - contextLines.size() + j)))
+ return false;
+ }
+ }
+ return true;
+ }
+
+ int getStart(boolean reverse) {
+ if (reverse) {
+ return fNewStart;
+ }
+ return fOldStart;
+ }
+
+ private int getLength(boolean reverse) {
+ if (reverse) {
+ return fNewLength;
+ }
+ return fOldLength;
+ }
+
+ private int getShift(boolean reverse) {
+ if (reverse) {
+ return fOldLength - fNewLength;
+ }
+ return fNewLength - fOldLength;
+ }
+
+ int doPatch(PatchConfiguration configuration, List lines, int shift, int fuzz) {
+ boolean reverse = configuration.isReversed();
+ int pos = getStart(reverse) + shift;
+ List contextLines = new ArrayList();
+ boolean contextLinesMatched = true;
+ boolean precedingLinesChecked = false;
+ for (int i= 0; i < fLines.length; i++) {
+ String s= fLines[i];
+ Assert.isTrue(s.length() > 0);
+ String line= s.substring(1);
+ char controlChar= s.charAt(0);
+ if (controlChar == ' ') {
+ // context lines
+ Assert.isTrue(pos < lines.size(), "doPatch: inconsistency in context"); //$NON-NLS-1$
+ contextLines.add(line);
+ if (linesMatch(configuration, line, (String) lines.get(pos))) {
+ pos++;
+ continue;
+ } else if (fuzz > 0) {
+ // doesn't match, use the fuzz factor
+ contextLinesMatched = false;
+ pos++;
+ continue;
+ }
+ Assert.isTrue(false, "doPatch: context doesn't match"); //$NON-NLS-1$
+// pos++;
+ } else if (isDeletedDelimeter(controlChar, reverse)) {
+ // deleted lines
+ if (precedingLinesChecked && !contextLinesMatched && contextLines.size() > 0)
+ // context lines inside hunk don't match
+ Assert.isTrue(false, "doPatch: context lines inside hunk don't match"); //$NON-NLS-1$
+
+ // check following context lines if exist
+ // use the fuzz factor if needed
+ if (!precedingLinesChecked
+ && !contextLinesMatched
+ && contextLines.size() >= fuzz
+ && !checkPrecedingContextLines(configuration, lines,
+ fuzz, pos, contextLines))
+ Assert.isTrue(false, "doPatch: preceding context lines don't match, even though fuzz factor has been used"); //$NON-NLS-1$;
+ // else if there is less or equal context line to the fuzz
+ // factor we ignore them all and treat as matching
+
+ precedingLinesChecked = true;
+ contextLines.clear();
+ contextLinesMatched = true;
+
+ lines.remove(pos);
+ } else if (isAddedDelimeter(controlChar, reverse)) {
+ // added lines
+ if (precedingLinesChecked && !contextLinesMatched && contextLines.size() > 0)
+ Assert.isTrue(false, "doPatch: context lines inside hunk don't match"); //$NON-NLS-1$
+
+ if (!precedingLinesChecked
+ && !contextLinesMatched
+ && contextLines.size() >= fuzz
+ && !checkPrecedingContextLines(configuration, lines,
+ fuzz, pos, contextLines))
+ Assert.isTrue(false, "doPatch: preceding context lines don't match, even though fuzz factor has been used"); //$NON-NLS-1$;
+
+ precedingLinesChecked = true;
+ contextLines.clear();
+ contextLinesMatched = true;
+
+ if (getLength(reverse) == 0 && pos+1 < lines.size())
+ lines.add(pos+1, line);
+ else
+ lines.add(pos, line);
+ pos++;
+ } else
+ Assert.isTrue(false, "doPatch: unknown control character: " + controlChar); //$NON-NLS-1$
+ }
+ return getShift(reverse);
+ }
+
+ private boolean isDeletedDelimeter(char controlChar, boolean reverse) {
+ return (!reverse && controlChar == '-') || (reverse && controlChar == '+');
+ }
+
+ private boolean isAddedDelimeter(char controlChar, boolean reverse) {
+ return (reverse && controlChar == '-') || (!reverse && controlChar == '+');
+ }
+
+ /*
+ * Compares two strings.
+ * If fIgnoreWhitespace is true whitespace is ignored.
+ */
+ private boolean linesMatch(PatchConfiguration configuration, String line1, String line2) {
+ if (configuration.isIgnoreWhitespace())
+ return stripWhiteSpace(line1).equals(stripWhiteSpace(line2));
+ if (isIgnoreLineDelimiter()) {
+ int l1= Patcher.length(line1);
+ int l2= Patcher.length(line2);
+ if (l1 != l2)
+ return false;
+ return line1.regionMatches(0, line2, 0, l1);
+ }
+ return line1.equals(line2);
+ }
+
+ private boolean isIgnoreLineDelimiter() {
+ return true;
+ }
+
+ /*
+ * Returns the given string with all whitespace characters removed.
+ * Whitespace is defined by <code>Character.isWhitespace(...)</code>.
+ */
+ private String stripWhiteSpace(String s) {
+ StringBuffer sb= new StringBuffer();
+ int l= s.length();
+ for (int i= 0; i < l; i++) {
+ char c= s.charAt(i);
+ if (!Character.isWhitespace(c))
+ sb.append(c);
+ }
+ return sb.toString();
+ }
+
+ public String getContents(boolean isAfterState, boolean reverse) {
+ StringBuffer result= new StringBuffer();
+ for (int i= 0; i<fLines.length; i++) {
+ String line= fLines[i];
+ String rest= line.substring(1);
+ char c = line.charAt(0);
+ if (c == ' ') {
+ result.append(rest);
+ } else if (isDeletedDelimeter(c, reverse) && !isAfterState) {
+ result.append(rest);
+ } else if (isAddedDelimeter(c, reverse) && isAfterState) {
+ result.append(rest);
+ }
+ }
+ return result.toString();
+ }
+}
diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/HunkResult.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/HunkResult.java
new file mode 100644
index 000000000..3077bd8eb
--- /dev/null
+++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/HunkResult.java
@@ -0,0 +1,294 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.internal.patch;
+
+import java.io.InputStream;
+import java.util.List;
+
+import org.eclipse.compare.patch.IHunk;
+import org.eclipse.compare.patch.PatchConfiguration;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+
+public class HunkResult implements IHunk {
+
+ private static final boolean DEBUG= false;
+
+ /**
+ * Default maximum fuzz factor equals 2. This is related to the default
+ * number of context lines, which is 3.
+ */
+ private static final int MAXIMUM_FUZZ_FACTOR = 2;
+
+ private Hunk fHunk;
+ private boolean fMatches;
+ private int fShift;
+ private int fFuzz = -1; // not set or couldn't be found
+
+ private final FileDiffResult fDiffResult;
+
+ /**
+ * Create a hunk result for the given hunk
+ * @param diffResult the parent diff result
+ * @param hunk the hunk
+ */
+ public HunkResult(FileDiffResult diffResult, Hunk hunk) {
+ fDiffResult = diffResult;
+ fHunk = hunk;
+ }
+
+ /**
+ * Try to apply the specified hunk to the given lines.
+ * If the hunk cannot be applied at the original position
+ * the method tries shift lines up and down.
+ * @param lines the lines to be patched
+ * @return whether the hunk could be applied
+ */
+ public boolean patch(List lines) {
+ fMatches = false;
+ PatchConfiguration configuration = getConfiguration();
+ // if the fuzz is not set for the current hunk use the one from fDiffResult
+ int fuzz = fFuzz != -1 ? fFuzz : configuration.getFuzz();
+ if (isEnabled(configuration)) {
+ if (fHunk.tryPatch(configuration, lines, fShift, fuzz)) {
+ // it's a perfect match, no shifting is needed
+ fShift += fHunk.doPatch(configuration, lines, fShift, fuzz);
+ fMatches = true;
+ } else {
+ boolean found= false;
+ int oldShift= fShift;
+
+ int hugeShift = lines.size();
+ for (int i = 1; i <= hugeShift; i++) {
+ if (fHunk.tryPatch(configuration, lines, fShift - i, fuzz)) {
+ if (isAdjustShift())
+ fShift -= i;
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ for (int i = 1; i <= hugeShift; i++) {
+ if (fHunk.tryPatch(configuration, lines, fShift + i, fuzz)) {
+ if (isAdjustShift())
+ fShift += i;
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (found) {
+ if (DEBUG) System.out.println("patched hunk at offset: " + (fShift-oldShift)); //$NON-NLS-1$
+ fShift+= fHunk.doPatch(configuration, lines, fShift, fuzz);
+ fMatches = true;
+ }
+ }
+ }
+ return fMatches;
+ }
+
+ private boolean isAdjustShift() {
+ return true;
+ }
+
+ private PatchConfiguration getConfiguration() {
+ return getDiffResult().getConfiguration();
+ }
+
+ /**
+ * Calculate the fuzz that will allow the most hunks to be matched. Even
+ * though we're interested only in the value of the fuzz, the shifting is
+ * done anyway.
+ *
+ * @param lines
+ * the lines of the target file
+ * @param monitor
+ * a progress monitor
+ * @return the fuzz factor or -1 if the hunk could not be matched
+ */
+ public int calculateFuzz(List lines, IProgressMonitor monitor) {
+ fMatches = false;
+ PatchConfiguration configuration = getConfiguration();
+ int fuzz = 0;
+ int maxFuzz = configuration.getFuzz() == -1 ? MAXIMUM_FUZZ_FACTOR
+ : configuration.getFuzz();
+ for (; fuzz <= maxFuzz; fuzz++) {
+ // try to apply using lines coordinates from the patch
+ if (fHunk.tryPatch(configuration, lines, fShift, fuzz)) {
+ // it's a perfect match, no adjustment is needed
+ fShift += fHunk.doPatch(configuration, lines, fShift, fuzz);
+ fMatches = true;
+ break;
+ }
+
+ // TODO (tzarna): hugeShift=lines.size() is more than we need.
+ // Lines to the beg/end of a file would be enough but this can still
+ // in matching hunks out of order. Try to shift using only lines
+ // available "between" hunks.
+ int hugeShift = lines.size();
+
+ // shift up
+ for (int i = 1; i <= hugeShift; i++) {
+ if (monitor.isCanceled()) {
+ throw new OperationCanceledException();
+ }
+ if (fHunk.tryPatch(configuration, lines, fShift - i, fuzz)) {
+ if (isAdjustShift())
+ fShift -= i;
+ fMatches = true;
+ break;
+ }
+ }
+
+ // shift down
+ if (!fMatches) {
+ for (int i = 1; i <= hugeShift; i++) {
+ if (monitor.isCanceled()) {
+ throw new OperationCanceledException();
+ }
+ if (fHunk.tryPatch(configuration, lines, fShift + i, fuzz)) {
+ if (isAdjustShift())
+ fShift += i;
+ fMatches = true;
+ break;
+ }
+ }
+ }
+
+ if (fMatches) {
+ fShift += fHunk.doPatch(configuration, lines, fShift, fuzz);
+ break;
+ }
+ }
+ // set fuzz for the current hunk
+ fFuzz = fMatches ? fuzz : -1;
+ return fFuzz;
+ }
+
+ /**
+ * Return the amount that this hunk should be shifted when a match with the file
+ * is attempted. The shift is needed to compensate for previous hunks that have
+ * been applied.
+ * @return the amount that this hunk should be shifted when applied
+ */
+ public int getShift() {
+ return fShift;
+ }
+
+ /**
+ * Set the amount that this hunk should be shifted when a match with the file
+ * is attempted. The shift is needed to compensate for previous hunks that have
+ * been applied.
+ * @param shift the amount to shift this hunk
+ */
+ public void setShift(int shift) {
+ fShift = shift;
+ }
+
+ /**
+ * Return the hunk to which this result applies.
+ * @return the hunk to which this result applies
+ */
+ public Hunk getHunk() {
+ return fHunk;
+ }
+
+ /**
+ * Return the parent diff result.
+ * @return the parent diff result
+ */
+ public FileDiffResult getDiffResult() {
+ return fDiffResult;
+ }
+
+ /**
+ * Return whether the hunk was matched with the target file.
+ * @return whether the hunk was matched with the target file
+ */
+ public boolean isOK() {
+ return fMatches;
+ }
+
+ /**
+ * Return the contents that should be displayed for the hunk result.
+ * @param afterState whether the after state or before state of the hunk is desired
+ * @param fullContext whether the hunk should be displayed with the entire file or
+ * only the lines in the hunk itself
+ * @return the contents to be display
+ */
+ public String getContents(boolean afterState, boolean fullContext) {
+ if (fullContext) {
+ boolean problemFound = false;
+ List lines = getDiffResult().getBeforeLines();
+ if (afterState) {
+ if (isOK()) {
+ int oldShift = fShift;
+ try {
+ fShift = 0;
+ problemFound = !patch(lines);
+ } finally {
+ fShift = oldShift;
+ }
+ } else {
+ problemFound = true;
+ }
+ }
+ // Only return the full context if we could apply the hunk
+ if (!problemFound)
+ return Patcher.createString(fDiffResult.isPreserveLineDelimeters(), lines);
+ }
+ return getHunk().getContents(afterState, getConfiguration().isReversed());
+ }
+
+ private boolean isEnabled(PatchConfiguration configuration) {
+ Patcher patcher = Patcher.getPatcher(configuration);
+ if (patcher != null)
+ return patcher.isEnabled(fHunk);
+ return true;
+ }
+
+ public void setMatches(boolean matches) {
+ fMatches = matches;
+ }
+
+ public int getStartPosition() {
+ return fHunk.getStart(getConfiguration().isReversed()) + fShift;
+ }
+
+ public String getLabel() {
+ return getHunk().getDescription();
+ }
+
+ public InputStream getOriginalContents() {
+ String contents = getContents(false, false);
+ return asInputStream(contents);
+ }
+
+ public InputStream asInputStream(String contents) {
+ String charSet = getCharset();
+ return FileDiffResult.asInputStream(contents, charSet);
+ }
+
+ public InputStream getPatchedContents() {
+ String contents = getContents(true, false);
+ return asInputStream(contents);
+ }
+
+ public String getCharset() {
+ return fDiffResult.getCharset();
+ }
+
+ public int getFuzz() {
+ return fFuzz;
+ }
+}
diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/LineReader.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/LineReader.java
new file mode 100644
index 000000000..ecf8d7725
--- /dev/null
+++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/LineReader.java
@@ -0,0 +1,137 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Brock Janiczak <brockj@tpg.com.au> - Bug 181919 LineReader creating unneeded garbage
+ *******************************************************************************/
+package org.eclipse.compare.internal.patch;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.runtime.Assert;
+
+public class LineReader {
+
+ private boolean fHaveChar= false;
+ private int fLastChar;
+ private boolean fSawEOF= false;
+ private BufferedReader fReader;
+ private boolean fIgnoreSingleCR= false;
+ private StringBuffer fBuffer= new StringBuffer();
+
+ public LineReader(BufferedReader reader) {
+ fReader= reader;
+ Assert.isNotNull(reader);
+ }
+
+ void ignoreSingleCR() {
+ fIgnoreSingleCR= true;
+ }
+
+ /**
+ * Reads a line of text. A line is considered to be terminated by any one
+ * of a line feed ('\n'), a carriage return ('\r'), or a carriage return
+ * followed immediately by a line-feed.
+ * @return A string containing the contents of the line including
+ * the line-termination characters, or <code>null</code> if the end of the
+ * stream has been reached
+ * @exception IOException If an I/O error occurs
+ */
+ /* package */ String readLine() throws IOException {
+ try {
+ while (!fSawEOF) {
+ int c= readChar();
+ if (c == -1) {
+ fSawEOF= true;
+ break;
+ }
+ fBuffer.append((char)c);
+ if (c == '\n')
+ break;
+ if (c == '\r') {
+ c= readChar();
+ if (c == -1) {
+ fSawEOF= true;
+ break; // EOF
+ }
+ if (c != '\n') {
+ if (fIgnoreSingleCR) {
+ fBuffer.append((char)c);
+ continue;
+ }
+ fHaveChar= true;
+ fLastChar= c;
+ } else
+ fBuffer.append((char)c);
+ break;
+ }
+ }
+
+ if (fBuffer.length() != 0) {
+ return fBuffer.toString();
+ }
+ return null;
+ } finally {
+ fBuffer.setLength(0);
+ }
+ }
+
+ /* package */ void close() {
+ try {
+ fReader.close();
+ } catch (IOException ex) {
+ // silently ignored
+ }
+ }
+
+ public List readLines() {
+ try {
+ List lines= new ArrayList();
+ String line;
+ while ((line= readLine()) != null)
+ lines.add(line);
+ return lines;
+ } catch (IOException ex) {
+ // NeedWork
+ //System.out.println("error while reading file: " + fileName + "(" + ex + ")");
+ } finally {
+ close();
+ }
+ return null;
+ }
+
+ /*
+ * Returns the number of characters in the given string without
+ * counting a trailing line separator.
+ */
+ /* package */ int lineContentLength(String line) {
+ if (line == null)
+ return 0;
+ int length= line.length();
+ for (int i= length-1; i >= 0; i--) {
+ char c= line.charAt(i);
+ if (c =='\n' || c == '\r')
+ length--;
+ else
+ break;
+ }
+ return length;
+ }
+
+ //---- private
+
+ private int readChar() throws IOException {
+ if (fHaveChar) {
+ fHaveChar= false;
+ return fLastChar;
+ }
+ return fReader.read();
+ }
+}
diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/PatchReader.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/PatchReader.java
new file mode 100644
index 000000000..da93ef7fc
--- /dev/null
+++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/PatchReader.java
@@ -0,0 +1,669 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.internal.patch;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.text.ParseException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.StringTokenizer;
+
+import org.eclipse.compare.patch.IFilePatch;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+
+import com.ibm.icu.text.DateFormat;
+import com.ibm.icu.text.SimpleDateFormat;
+
+public class PatchReader {
+
+ private static final boolean DEBUG= false;
+
+ private static final String DEV_NULL= "/dev/null"; //$NON-NLS-1$
+
+ protected static final String MARKER_TYPE= "org.eclipse.compare.rejectedPatchMarker"; //$NON-NLS-1$
+
+ // diff formats
+ // private static final int CONTEXT= 0;
+ // private static final int ED= 1;
+ // private static final int NORMAL= 2;
+ // private static final int UNIFIED= 3;
+
+ // we recognize the following date/time formats
+ private DateFormat[] fDateFormats= new DateFormat[] {
+ new SimpleDateFormat("EEE MMM dd kk:mm:ss yyyy"), //$NON-NLS-1$
+ new SimpleDateFormat("yyyy/MM/dd kk:mm:ss"), //$NON-NLS-1$
+ new SimpleDateFormat("EEE MMM dd kk:mm:ss yyyy", Locale.US) //$NON-NLS-1$
+ };
+
+ private boolean fIsWorkspacePatch;
+ private DiffProject[] fDiffProjects;
+ private FileDiff[] fDiffs;
+
+ // API for writing new multi-project patch format
+ public static final String MULTIPROJECTPATCH_HEADER= "### Eclipse Workspace Patch"; //$NON-NLS-1$
+
+ public static final String MULTIPROJECTPATCH_VERSION= "1.0"; //$NON-NLS-1$
+
+ public static final String MULTIPROJECTPATCH_PROJECT= "#P"; //$NON-NLS-1$
+
+ /**
+ * Create a patch reader for the default date formats.
+ */
+ public PatchReader() {
+ // nothing here
+ }
+
+ /**
+ * Create a patch reader for the given date formats.
+ *
+ * @param dateFormats
+ * Array of <code>DateFormat</code>s to be used when
+ * extracting dates from the patch.
+ */
+ public PatchReader(DateFormat[] dateFormats) {
+ this();
+ fDateFormats = dateFormats;
+ }
+
+ public void parse(BufferedReader reader) throws IOException {
+ List diffs= new ArrayList();
+ HashMap diffProjects= new HashMap(4);
+ String line= null;
+ boolean reread= false;
+ String diffArgs= null;
+ String fileName= null;
+ // no project means this is a single patch,create a placeholder project for now
+ // which will be replaced by the target selected by the user in the preview pane
+ String project= ""; //$NON-NLS-1$
+ fIsWorkspacePatch= false;
+
+ LineReader lr= new LineReader(reader);
+ lr.ignoreSingleCR(); // Don't treat single CRs as line feeds to be consistent with command line patch
+
+ // Test for our format
+ line= lr.readLine();
+ if (line != null && line.startsWith(PatchReader.MULTIPROJECTPATCH_HEADER)) {
+ fIsWorkspacePatch= true;
+ } else {
+ parse(lr, line);
+ return;
+ }
+
+ // read leading garbage
+ while (true) {
+ if (!reread)
+ line= lr.readLine();
+ reread= false;
+ if (line == null)
+ break;
+ if (line.length() < 4)
+ continue; // too short
+
+ if (line.startsWith(PatchReader.MULTIPROJECTPATCH_PROJECT)) {
+ project= line.substring(2).trim();
+ continue;
+ }
+
+ if (line.startsWith("Index: ")) { //$NON-NLS-1$
+ fileName= line.substring(7).trim();
+ continue;
+ }
+ if (line.startsWith("diff")) { //$NON-NLS-1$
+ diffArgs= line.substring(4).trim();
+ continue;
+ }
+
+ if (line.startsWith("--- ")) { //$NON-NLS-1$
+ // if there is no current project or
+ // the current project doesn't equal the newly parsed project
+ // reset the current project to the newly parsed one, create a new DiffProject
+ // and add it to the array
+ DiffProject diffProject;
+ if (!diffProjects.containsKey(project)) {
+ IProject iproject= ResourcesPlugin.getWorkspace().getRoot().getProject(project);
+ diffProject= new DiffProject(iproject);
+ diffProjects.put(project, diffProject);
+ } else {
+ diffProject= (DiffProject) diffProjects.get(project);
+ }
+
+ line= readUnifiedDiff(diffs, lr, line, diffArgs, fileName, diffProject);
+ diffArgs= fileName= null;
+ reread= true;
+ }
+ }
+
+ lr.close();
+
+ fDiffProjects= (DiffProject[]) diffProjects.values().toArray(new DiffProject[diffProjects.size()]);
+ fDiffs = (FileDiff[]) diffs.toArray(new FileDiff[diffs.size()]);
+ }
+
+ private String readUnifiedDiff(List diffs, LineReader lr, String line, String diffArgs, String fileName, DiffProject diffProject) throws IOException {
+ List newDiffs= new ArrayList();
+ String nextLine= readUnifiedDiff(newDiffs, lr, line, diffArgs, fileName);
+ for (Iterator iter= newDiffs.iterator(); iter.hasNext();) {
+ FileDiff diff= (FileDiff) iter.next();
+ diffProject.add(diff);
+ diffs.add(diff);
+ }
+ return nextLine;
+ }
+
+ public void parse(LineReader lr, String line) throws IOException {
+ List diffs= new ArrayList();
+ boolean reread= false;
+ String diffArgs= null;
+ String fileName= null;
+ List headerLines = new ArrayList();
+
+ // read leading garbage
+ reread= line!=null;
+ while (true) {
+ if (!reread)
+ line= lr.readLine();
+ reread= false;
+ if (line == null)
+ break;
+
+ // remember some infos
+ if (line.startsWith("Index: ")) { //$NON-NLS-1$
+ fileName= line.substring(7).trim();
+ } else if (line.startsWith("diff")) { //$NON-NLS-1$
+ diffArgs= line.substring(4).trim();
+ } else if (line.startsWith("--- ")) { //$NON-NLS-1$
+ line= readUnifiedDiff(diffs, lr, line, diffArgs, fileName);
+ if (!headerLines.isEmpty())
+ setHeader((FileDiff)diffs.get(diffs.size() - 1), headerLines);
+ diffArgs= fileName= null;
+ reread= true;
+ } else if (line.startsWith("*** ")) { //$NON-NLS-1$
+ line= readContextDiff(diffs, lr, line, diffArgs, fileName);
+ if (!headerLines.isEmpty())
+ setHeader((FileDiff)diffs.get(diffs.size() - 1), headerLines);
+ diffArgs= fileName= null;
+ reread= true;
+ }
+
+ // Any lines we read here are header lines.
+ // However, if reread is set, we will add them to the header on the next pass through
+ if (!reread) {
+ headerLines.add(line);
+ }
+ }
+
+ lr.close();
+
+ fDiffs = (FileDiff[]) diffs.toArray(new FileDiff[diffs.size()]);
+ }
+
+ private void setHeader(FileDiff diff, List headerLines) {
+ String header = Patcher.createString(false, headerLines);
+ diff.setHeader(header);
+ headerLines.clear();
+ }
+
+ /*
+ * Returns the next line that does not belong to this diff
+ */
+ protected String readUnifiedDiff(List diffs, LineReader reader, String line, String args, String fileName) throws IOException {
+
+ String[] oldArgs= split(line.substring(4));
+
+ // read info about new file
+ line= reader.readLine();
+ if (line == null || !line.startsWith("+++ ")) //$NON-NLS-1$
+ return line;
+
+ String[] newArgs= split(line.substring(4));
+
+ FileDiff diff= new FileDiff(extractPath(oldArgs, 0, fileName), extractDate(oldArgs, 1),
+ extractPath(newArgs, 0, fileName), extractDate(newArgs, 1));
+ diffs.add(diff);
+
+ int[] oldRange= new int[2];
+ int[] newRange= new int[2];
+ List lines= new ArrayList();
+
+ boolean encounteredPlus = false;
+ boolean encounteredMinus = false;
+ boolean encounteredSpace = false;
+
+ try {
+ // read lines of hunk
+ while (true) {
+
+ line= reader.readLine();
+ if (line == null)
+ return null;
+
+ if (reader.lineContentLength(line) == 0) {
+ //System.out.println("Warning: found empty line in hunk; ignored");
+ //lines.add(' ' + line);
+ continue;
+ }
+
+ char c= line.charAt(0);
+ switch (c) {
+ case '@':
+ if (line.startsWith("@@ ")) { //$NON-NLS-1$
+ // flush old hunk
+ if (lines.size() > 0) {
+ new Hunk(diff, oldRange, newRange, lines,encounteredPlus, encounteredMinus, encounteredSpace);
+ lines.clear();
+ }
+
+ // format: @@ -oldStart,oldLength +newStart,newLength @@
+ extractPair(line, '-', oldRange);
+ extractPair(line, '+', newRange);
+ continue;
+ }
+ break;
+ case ' ':
+ encounteredSpace = true;
+ lines.add(line);
+ continue;
+ case '+':
+ encounteredPlus = true;
+ lines.add(line);
+ continue;
+ case '-':
+ encounteredMinus = true;
+ lines.add(line);
+ continue;
+ case '\\':
+ if (line.indexOf("newline at end") > 0) { //$NON-NLS-1$
+ int lastIndex= lines.size();
+ if (lastIndex > 0) {
+ line= (String) lines.get(lastIndex-1);
+ int end= line.length()-1;
+ char lc= line.charAt(end);
+ if (lc == '\n') {
+ end--;
+ if (end > 0 && line.charAt(end) == '\r')
+ end--;
+ } else if (lc == '\r') {
+ end--;
+ }
+ line= line.substring(0, end+1);
+ lines.set(lastIndex-1, line);
+ }
+ continue;
+ }
+ break;
+ default:
+ if (DEBUG) {
+ int a1= c, a2= 0;
+ if (line.length() > 1)
+ a2= line.charAt(1);
+ System.out.println("char: " + a1 + " " + a2); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+ break;
+ }
+ return line;
+ }
+ } finally {
+ if (lines.size() > 0)
+ new Hunk(diff, oldRange, newRange, lines, encounteredPlus, encounteredMinus, encounteredSpace);
+ }
+ }
+
+ /*
+ * Returns the next line that does not belong to this diff
+ */
+ private String readContextDiff(List diffs, LineReader reader, String line, String args, String fileName) throws IOException {
+
+ String[] oldArgs= split(line.substring(4));
+
+ // read info about new file
+ line= reader.readLine();
+ if (line == null || !line.startsWith("--- ")) //$NON-NLS-1$
+ return line;
+
+ String[] newArgs= split(line.substring(4));
+
+ FileDiff diff= new FileDiff(extractPath(oldArgs, 0, fileName), extractDate(oldArgs, 1),
+ extractPath(newArgs, 0, fileName), extractDate(newArgs, 1));
+ diffs.add(diff);
+
+ int[] oldRange= new int[2];
+ int[] newRange= new int[2];
+ List oldLines= new ArrayList();
+ List newLines= new ArrayList();
+ List lines= oldLines;
+
+
+ boolean encounteredPlus = false;
+ boolean encounteredMinus = false;
+ boolean encounteredSpace = false;
+
+ try {
+ // read lines of hunk
+ while (true) {
+
+ line= reader.readLine();
+ if (line == null)
+ return line;
+
+ int l= line.length();
+ if (l == 0)
+ continue;
+ if (l > 1) {
+ switch (line.charAt(0)) {
+ case '*':
+ if (line.startsWith("***************")) { // new hunk //$NON-NLS-1$
+ // flush old hunk
+ if (oldLines.size() > 0 || newLines.size() > 0) {
+ new Hunk(diff, oldRange, newRange, unifyLines(oldLines, newLines), encounteredPlus, encounteredMinus, encounteredSpace);
+ oldLines.clear();
+ newLines.clear();
+ }
+ continue;
+ }
+ if (line.startsWith("*** ")) { // old range //$NON-NLS-1$
+ // format: *** oldStart,oldEnd ***
+ extractPair(line, ' ', oldRange);
+ oldRange[1]= oldRange[1]-oldRange[0]+1;
+ lines= oldLines;
+ continue;
+ }
+ break;
+ case ' ': // context line
+ if (line.charAt(1) == ' ') {
+ lines.add(line);
+ continue;
+ }
+ break;
+ case '+': // addition
+ if (line.charAt(1) == ' ') {
+ encounteredPlus = true;
+ lines.add(line);
+ continue;
+ }
+ break;
+ case '!': // change
+ if (line.charAt(1) == ' ') {
+ encounteredSpace = true;
+ lines.add(line);
+ continue;
+ }
+ break;
+ case '-':
+ if (line.charAt(1) == ' ') { // deletion
+ encounteredMinus = true;
+ lines.add(line);
+ continue;
+ }
+ if (line.startsWith("--- ")) { // new range //$NON-NLS-1$
+ // format: *** newStart,newEnd ***
+ extractPair(line, ' ', newRange);
+ newRange[1]= newRange[1]-newRange[0]+1;
+ lines= newLines;
+ continue;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return line;
+ }
+ } finally {
+ // flush last hunk
+ if (oldLines.size() > 0 || newLines.size() > 0)
+ new Hunk(diff, oldRange, newRange, unifyLines(oldLines, newLines), encounteredPlus, encounteredMinus, encounteredSpace);
+ }
+ }
+
+ /*
+ * Creates a List of lines in the unified format from
+ * two Lists of lines in the 'classic' format.
+ */
+ private List unifyLines(List oldLines, List newLines) {
+ List result= new ArrayList();
+
+ String[] ol= (String[]) oldLines.toArray(new String[oldLines.size()]);
+ String[] nl= (String[]) newLines.toArray(new String[newLines.size()]);
+
+ int oi= 0, ni= 0;
+
+ while (true) {
+
+ char oc= 0;
+ String o= null;
+ if (oi < ol.length) {
+ o= ol[oi];
+ oc= o.charAt(0);
+ }
+
+ char nc= 0;
+ String n= null;
+ if (ni < nl.length) {
+ n= nl[ni];
+ nc= n.charAt(0);
+ }
+
+ // EOF
+ if (oc == 0 && nc == 0)
+ break;
+
+ // deletion in old
+ if (oc == '-') {
+ do {
+ result.add('-' + o.substring(2));
+ oi++;
+ if (oi >= ol.length)
+ break;
+ o= ol[oi];
+ } while (o.charAt(0) == '-');
+ continue;
+ }
+
+ // addition in new
+ if (nc == '+') {
+ do {
+ result.add('+' + n.substring(2));
+ ni++;
+ if (ni >= nl.length)
+ break;
+ n= nl[ni];
+ } while (n.charAt(0) == '+');
+ continue;
+ }
+
+ // differing lines on both sides
+ if (oc == '!' && nc == '!') {
+ // remove old
+ do {
+ result.add('-' + o.substring(2));
+ oi++;
+ if (oi >= ol.length)
+ break;
+ o= ol[oi];
+ } while (o.charAt(0) == '!');
+
+ // add new
+ do {
+ result.add('+' + n.substring(2));
+ ni++;
+ if (ni >= nl.length)
+ break;
+ n= nl[ni];
+ } while (n.charAt(0) == '!');
+
+ continue;
+ }
+
+ // context lines
+ if (oc == ' ' && nc == ' ') {
+ do {
+ Assert.isTrue(o.equals(n), "non matching context lines"); //$NON-NLS-1$
+ result.add(' ' + o.substring(2));
+ oi++;
+ ni++;
+ if (oi >= ol.length || ni >= nl.length)
+ break;
+ o= ol[oi];
+ n= nl[ni];
+ } while (o.charAt(0) == ' ' && n.charAt(0) == ' ');
+ continue;
+ }
+
+ if (oc == ' ') {
+ do {
+ result.add(' ' + o.substring(2));
+ oi++;
+ if (oi >= ol.length)
+ break;
+ o= ol[oi];
+ } while (o.charAt(0) == ' ');
+ continue;
+ }
+
+ if (nc == ' ') {
+ do {
+ result.add(' ' + n.substring(2));
+ ni++;
+ if (ni >= nl.length)
+ break;
+ n= nl[ni];
+ } while (n.charAt(0) == ' ');
+ continue;
+ }
+
+ Assert.isTrue(false, "unexpected char <" + oc + "> <" + nc + ">"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+
+ return result;
+ }
+
+ /*
+ * @return the parsed time/date in milliseconds or IFilePatch.DATE_UNKNOWN
+ * (0) on error
+ */
+ private long extractDate(String[] args, int n) {
+ if (n < args.length) {
+ String line= args[n];
+ for (int i= 0; i < fDateFormats.length; i++) {
+ fDateFormats[i].setLenient(true);
+ try {
+ Date date= fDateFormats[i].parse(line);
+ return date.getTime();
+ } catch (ParseException ex) {
+ // silently ignored
+ }
+ }
+ // System.err.println("can't parse date: <" + line + ">");
+ }
+ return IFilePatch.DATE_UNKNOWN;
+ }
+
+ /*
+ * Returns null if file name is "/dev/null".
+ */
+ private IPath extractPath(String[] args, int n, String path2) {
+ if (n < args.length) {
+ String path= args[n];
+ if (DEV_NULL.equals(path))
+ return null;
+ int pos= path.lastIndexOf(':');
+ if (pos >= 0)
+ path= path.substring(0, pos);
+ if (path2 != null && !path2.equals(path)) {
+ if (DEBUG) System.out.println("path mismatch: " + path2); //$NON-NLS-1$
+ path= path2;
+ }
+ return new Path(path);
+ }
+ return null;
+ }
+
+ /*
+ * Tries to extract two integers separated by a comma.
+ * The parsing of the line starts at the position after
+ * the first occurrence of the given character start an ends
+ * at the first blank (or the end of the line).
+ * If only a single number is found this is assumed to be the start of a one line range.
+ * If an error occurs the range -1,-1 is returned.
+ */
+ private void extractPair(String line, char start, int[] pair) {
+ pair[0]= pair[1]= -1;
+ int startPos= line.indexOf(start);
+ if (startPos < 0) {
+ if (DEBUG) System.out.println("parsing error in extractPair: couldn't find \'" + start + "\'"); //$NON-NLS-1$ //$NON-NLS-2$
+ return;
+ }
+ line= line.substring(startPos+1);
+ int endPos= line.indexOf(' ');
+ if (endPos < 0) {
+ if (DEBUG) System.out.println("parsing error in extractPair: couldn't find end blank"); //$NON-NLS-1$
+ return;
+ }
+ line= line.substring(0, endPos);
+ int comma= line.indexOf(',');
+ if (comma >= 0) {
+ pair[0]= Integer.parseInt(line.substring(0, comma));
+ pair[1]= Integer.parseInt(line.substring(comma+1));
+ } else { // abbreviated form for one line patch
+ pair[0]= Integer.parseInt(line);
+ pair[1]= 1;
+ }
+ }
+
+
+ /*
+ * Breaks the given string into tab separated substrings.
+ * Leading and trailing whitespace is removed from each token.
+ */
+ private String[] split(String line) {
+ List l= new ArrayList();
+ StringTokenizer st= new StringTokenizer(line, "\t"); //$NON-NLS-1$
+ while (st.hasMoreElements()) {
+ String token= st.nextToken().trim();
+ if (token.length() > 0)
+ l.add(token);
+ }
+ return (String[]) l.toArray(new String[l.size()]);
+ }
+
+ public boolean isWorkspacePatch() {
+ return fIsWorkspacePatch;
+ }
+
+ public DiffProject[] getDiffProjects() {
+ return fDiffProjects;
+ }
+
+ public FileDiff[] getDiffs() {
+ return fDiffs;
+ }
+
+ public FileDiff[] getAdjustedDiffs() {
+ if (!isWorkspacePatch() || fDiffs.length == 0)
+ return fDiffs;
+ List result = new ArrayList();
+ for (int i = 0; i < fDiffs.length; i++) {
+ FileDiff diff = fDiffs[i];
+ result.add(diff.asRelativeDiff());
+ }
+ return (FileDiff[]) result.toArray(new FileDiff[result.size()]);
+ }
+
+}
diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/Patcher.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/Patcher.java
new file mode 100644
index 000000000..caf2dcafc
--- /dev/null
+++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/Patcher.java
@@ -0,0 +1,877 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Martin Burger <m@rtin-burger.de> patch for #93810 and #93901
+ *******************************************************************************/
+package org.eclipse.compare.internal.patch;
+
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.compare.internal.core.Activator;
+import org.eclipse.compare.internal.core.Messages;
+import org.eclipse.compare.patch.PatchConfiguration;
+import org.eclipse.core.resources.IContainer;
+import org.eclipse.core.resources.IEncodedStorage;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IFolder;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IStorage;
+import org.eclipse.core.resources.IWorkspaceRoot;
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.SubProgressMonitor;
+
+/**
+ * A Patcher
+ * - knows how to parse various patch file formats into some in-memory structure,
+ * - holds onto the parsed data and the options to use when applying the patches,
+ * - knows how to apply the patches to files and folders.
+ */
+public class Patcher {
+
+ static protected final String REJECT_FILE_EXTENSION= ".rej"; //$NON-NLS-1$
+
+ static protected final String MARKER_TYPE= "org.eclipse.compare.rejectedPatchMarker"; //$NON-NLS-1$
+
+ /**
+ * Property used to associate a patcher with a {@link PatchConfiguration}
+ */
+ public static final String PROP_PATCHER = "org.eclipse.compare.patcher"; //$NON-NLS-1$
+
+ public interface IFileValidator {
+ boolean validateResources(IFile[] array);
+ }
+
+ // diff formats
+ // private static final int CONTEXT= 0;
+ // private static final int ED= 1;
+ // private static final int NORMAL= 2;
+ // private static final int UNIFIED= 3;
+
+ private FileDiff[] fDiffs;
+ private IResource fTarget;
+ // patch options
+ private Set disabledElements = new HashSet();
+ private Map diffResults = new HashMap();
+ private final Map contentCache = new HashMap();
+ private Set mergedHunks = new HashSet();
+
+ private final PatchConfiguration configuration;
+ private boolean fGenerateRejectFile = false;
+
+ public Patcher() {
+ configuration = new PatchConfiguration();
+ configuration.setProperty(PROP_PATCHER, this);
+ }
+
+ /*
+ * Returns an array of Diffs after a sucessfull call to <code>parse</code>.
+ * If <code>parse</code> hasn't been called returns <code>null</code>.
+ */
+ public FileDiff[] getDiffs() {
+ if (fDiffs == null)
+ return new FileDiff[0];
+ return fDiffs;
+ }
+
+ public IPath getPath(FileDiff diff) {
+ return diff.getStrippedPath(getStripPrefixSegments(), isReversed());
+ }
+
+ /*
+ * Returns <code>true</code> if new value differs from old.
+ */
+ public boolean setStripPrefixSegments(int strip) {
+ if (strip != getConfiguration().getPrefixSegmentStripCount()) {
+ getConfiguration().setPrefixSegmentStripCount(strip);
+ return true;
+ }
+ return false;
+ }
+
+ int getStripPrefixSegments() {
+ return getConfiguration().getPrefixSegmentStripCount();
+ }
+
+ /*
+ * Returns <code>true</code> if new value differs from old.
+ */
+ public boolean setFuzz(int fuzz) {
+ if (fuzz != getConfiguration().getFuzz()) {
+ getConfiguration().setFuzz(fuzz);
+ return true;
+ }
+ return false;
+ }
+
+ public int getFuzz(){
+ return getConfiguration().getFuzz();
+ }
+
+ /*
+ * Returns <code>true</code> if new value differs from old.
+ */
+ public boolean setIgnoreWhitespace(boolean ignoreWhitespace) {
+ if (ignoreWhitespace != getConfiguration().isIgnoreWhitespace()) {
+ getConfiguration().setIgnoreWhitespace(ignoreWhitespace);
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isIgnoreWhitespace() {
+ return getConfiguration().isIgnoreWhitespace();
+ }
+
+ public boolean isGenerateRejectFile() {
+ return fGenerateRejectFile;
+ }
+
+ public void setGenerateRejectFile(boolean generateRejectFile) {
+ fGenerateRejectFile = generateRejectFile;
+ }
+
+ //---- parsing patch files
+
+ public void parse(IStorage storage) throws IOException, CoreException {
+ BufferedReader reader = createReader(storage);
+ try {
+ parse(reader);
+ } finally {
+ try {
+ reader.close();
+ } catch (IOException e) { //ignored
+ }
+ }
+ }
+
+ public static BufferedReader createReader(IStorage storage) throws CoreException {
+ String charset = null;
+ if (storage instanceof IEncodedStorage) {
+ IEncodedStorage es = (IEncodedStorage) storage;
+ charset = es.getCharset();
+ }
+ InputStreamReader in = null;
+ if (charset != null) {
+ InputStream contents = storage.getContents();
+ try {
+ in = new InputStreamReader(contents, charset);
+ } catch (UnsupportedEncodingException e) {
+ Activator.log(e);
+ try {
+ contents.close();
+ } catch (IOException e1) {
+ // Ignore
+ }
+ }
+ }
+ if (in == null) {
+ in = new InputStreamReader(storage.getContents());
+ }
+ return new BufferedReader(in);
+ }
+
+ public void parse(BufferedReader reader) throws IOException {
+ PatchReader patchReader= new PatchReader();
+ patchReader.parse(reader);
+ patchParsed(patchReader);
+ }
+
+ protected void patchParsed(PatchReader patchReader) {
+ fDiffs = patchReader.getDiffs();
+ }
+
+ public void countLines() {
+ FileDiff[] fileDiffs = getDiffs();
+ for (int i = 0; i < fileDiffs.length; i++) {
+ int addedLines = 0;
+ int removedLines = 0;
+ FileDiff fileDiff = fileDiffs[i];
+ for (int j = 0; j < fileDiff.getHunkCount(); j++) {
+ Hunk hunk = fileDiff.getHunks()[j];
+ String[] lines = hunk.getLines();
+ for (int k = 0; k < lines.length; k++) {
+ char c = lines[k].charAt(0);
+ switch (c) {
+ case '+':
+ addedLines++;
+ continue;
+ case '-':
+ removedLines++;
+ continue;
+ }
+ }
+ }
+ fileDiff.setAddedLines(addedLines);
+ fileDiff.setRemovedLines(removedLines);
+ }
+ }
+
+ //---- applying a patch file
+
+ public void applyAll(IProgressMonitor pm, IFileValidator validator) throws CoreException {
+
+ int i;
+
+ IFile singleFile= null; // file to be patched
+ IContainer container= null;
+ if (fTarget instanceof IContainer)
+ container= (IContainer) fTarget;
+ else if (fTarget instanceof IFile) {
+ singleFile= (IFile) fTarget;
+ container= singleFile.getParent();
+ } else {
+ Assert.isTrue(false);
+ }
+
+ // get all files to be modified in order to call validateEdit
+ List list= new ArrayList();
+ if (singleFile != null)
+ list.add(singleFile);
+ else {
+ for (i= 0; i < fDiffs.length; i++) {
+ FileDiff diff= fDiffs[i];
+ if (isEnabled(diff)) {
+ switch (diff.getDiffType(isReversed())) {
+ case FileDiff.CHANGE:
+ list.add(createPath(container, getPath(diff)));
+ break;
+ }
+ }
+ }
+ }
+ if (! validator.validateResources((IFile[])list.toArray(new IFile[list.size()]))) {
+ return;
+ }
+
+ final int WORK_UNIT= 10;
+ if (pm != null) {
+ String message= Messages.Patcher_0;
+ pm.beginTask(message, fDiffs.length*WORK_UNIT);
+ }
+
+ for (i= 0; i < fDiffs.length; i++) {
+
+ int workTicks= WORK_UNIT;
+
+ FileDiff diff= fDiffs[i];
+ if (isEnabled(diff)) {
+
+ IPath path= getPath(diff);
+ if (pm != null)
+ pm.subTask(path.toString());
+
+ IFile file= singleFile != null
+ ? singleFile
+ : createPath(container, path);
+
+ List failed= new ArrayList();
+
+ int type= diff.getDiffType(isReversed());
+ switch (type) {
+ case FileDiff.ADDITION:
+ // patch it and collect rejected hunks
+ List result= apply(diff, file, true, failed);
+ if (result != null)
+ store(createString(isPreserveLineDelimeters(), result), file, new SubProgressMonitor(pm, workTicks));
+ workTicks-= WORK_UNIT;
+ break;
+ case FileDiff.DELETION:
+ file.delete(true, true, new SubProgressMonitor(pm, workTicks));
+ workTicks-= WORK_UNIT;
+ break;
+ case FileDiff.CHANGE:
+ // patch it and collect rejected hunks
+ result= apply(diff, file, false, failed);
+ if (result != null)
+ store(createString(isPreserveLineDelimeters(), result), file, new SubProgressMonitor(pm, workTicks));
+ workTicks-= WORK_UNIT;
+ break;
+ }
+
+ if (isGenerateRejectFile() && failed.size() > 0) {
+ IPath pp = getRejectFilePath(path);
+ file= createPath(container, pp);
+ if (file != null) {
+ store(getRejected(failed), file, pm);
+ try {
+ IMarker marker= file.createMarker(MARKER_TYPE);
+ marker.setAttribute(IMarker.MESSAGE, Messages.Patcher_1);
+ marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH);
+ } catch (CoreException ex) {
+ // NeedWork
+ }
+ }
+ }
+ }
+
+ if (pm != null) {
+ if (pm.isCanceled())
+ break;
+ if (workTicks > 0)
+ pm.worked(workTicks);
+ }
+ }
+ }
+
+ private IPath getRejectFilePath(IPath path) {
+ IPath pp= null;
+ if (path.segmentCount() > 1) {
+ pp= path.removeLastSegments(1);
+ pp= pp.append(path.lastSegment() + REJECT_FILE_EXTENSION);
+ } else
+ pp= new Path(path.lastSegment() + REJECT_FILE_EXTENSION);
+ return pp;
+ }
+
+ /*
+ * Reads the contents from the given file and returns them as
+ * a List of lines.
+ */
+ public static List load(IStorage file, boolean create) {
+ List lines= null;
+ if (!create && file != null && exists(file)) {
+ // read current contents
+ String charset = Utilities.getCharset(file);
+ InputStream is= null;
+ try {
+ is= file.getContents();
+
+ Reader streamReader= null;
+ try {
+ streamReader= new InputStreamReader(is, charset);
+ } catch (UnsupportedEncodingException x) {
+ // use default encoding
+ streamReader= new InputStreamReader(is);
+ }
+
+ BufferedReader reader= new BufferedReader(streamReader);
+ lines = readLines(reader);
+ } catch(CoreException ex) {
+ // TODO
+ Activator.log(ex);
+ } finally {
+ if (is != null)
+ try {
+ is.close();
+ } catch(IOException ex) {
+ // silently ignored
+ }
+ }
+ }
+
+ if (lines == null)
+ lines= new ArrayList();
+ return lines;
+ }
+
+ private static boolean exists(IStorage file) {
+ if (file instanceof IFile) {
+ return ((IFile) file).exists();
+ }
+ return true;
+ }
+
+ private static List readLines(BufferedReader reader) {
+ List lines;
+ LineReader lr= new LineReader(reader);
+ lr.ignoreSingleCR(); // Don't treat single CRs as line feeds to be consistent with command line patch
+ lines= lr.readLines();
+ return lines;
+ }
+
+ List apply(FileDiff diff, IFile file, boolean create, List failedHunks) {
+ FileDiffResult result = getDiffResult(diff);
+ List lines = Patcher.load(file, create);
+ result.patch(lines, null);
+ failedHunks.addAll(result.getFailedHunks());
+ if (hasCachedContents(diff)) {
+ // Used the cached contents since they would have been provided by the user
+ return getCachedLines(diff);
+ } else if (!result.hasMatches()) {
+ // Return null if there were no matches
+ return null;
+ }
+ return result.getLines();
+ }
+
+ /*
+ * Converts the string into bytes and stores them in the given file.
+ */
+ protected void store(String contents, IFile file, IProgressMonitor pm) throws CoreException {
+
+ byte[] bytes;
+ try {
+ bytes= contents.getBytes(Utilities.getCharset(file));
+ } catch (UnsupportedEncodingException x) {
+ // uses default encoding
+ bytes= contents.getBytes();
+ }
+
+ store(bytes,file, pm);
+ }
+
+ protected void store(byte[] bytes, IFile file, IProgressMonitor pm) throws CoreException {
+ InputStream is= new ByteArrayInputStream(bytes);
+ try {
+ if (file.exists()) {
+ file.setContents(is, false, true, pm);
+ } else {
+ file.create(is, false, pm);
+ }
+ } finally {
+ if (is != null)
+ try {
+ is.close();
+ } catch(IOException ex) {
+ // silently ignored
+ }
+ }
+ }
+
+
+
+ /*
+ * Concatenates all strings found in the given List.
+ */
+ public static String createString(boolean preserveLineDelimeters, List lines) {
+ StringBuffer sb= new StringBuffer();
+ Iterator iter= lines.iterator();
+ if (preserveLineDelimeters) {
+ while (iter.hasNext())
+ sb.append((String)iter.next());
+ } else {
+ String lineSeparator= System.getProperty("line.separator"); //$NON-NLS-1$
+ while (iter.hasNext()) {
+ String line= (String)iter.next();
+ int l= length(line);
+ if (l < line.length()) { // line has delimiter
+ sb.append(line.substring(0, l));
+ sb.append(lineSeparator);
+ } else {
+ sb.append(line);
+ }
+ }
+ }
+ return sb.toString();
+ }
+
+ public boolean isPreserveLineDelimeters() {
+ return false;
+ }
+
+ public static String getRejected(List failedHunks) {
+ if (failedHunks.size() <= 0)
+ return null;
+
+ String lineSeparator= System.getProperty("line.separator"); //$NON-NLS-1$
+ StringBuffer sb= new StringBuffer();
+ Iterator iter= failedHunks.iterator();
+ while (iter.hasNext()) {
+ Hunk hunk= (Hunk) iter.next();
+ sb.append(hunk.getRejectedDescription());
+ sb.append(lineSeparator);
+ sb.append(hunk.getContent());
+ }
+ return sb.toString();
+ }
+
+ /*
+ * Ensures that a file with the given path exists in
+ * the given container. Folder are created as necessary.
+ */
+ protected IFile createPath(IContainer container, IPath path) throws CoreException {
+ if (path.segmentCount() > 1) {
+ IContainer childContainer;
+ if (container instanceof IWorkspaceRoot) {
+ IProject project = ((IWorkspaceRoot)container).getProject(path.segment(0));
+ if (!project.exists())
+ project.create(null);
+ if (!project.isOpen())
+ project.open(null);
+ childContainer = project;
+ } else {
+ IFolder f= container.getFolder(path.uptoSegment(1));
+ if (!f.exists())
+ f.create(false, true, null);
+ childContainer = f;
+ }
+ return createPath(childContainer, path.removeFirstSegments(1));
+ }
+ // a leaf
+ return container.getFile(path);
+ }
+
+ /*
+ * Returns the length (excluding a line delimiter CR, LF, CR/LF)
+ * of the given string.
+ */
+ /* package */ static int length(String s) {
+ int l= s.length();
+ if (l > 0) {
+ char c= s.charAt(l-1);
+ if (c == '\r')
+ return l-1;
+ if (c == '\n') {
+ if (l > 1 && s.charAt(l-2) == '\r')
+ return l-2;
+ return l-1;
+ }
+ }
+ return l;
+ }
+
+ public IResource getTarget() {
+ return fTarget;
+ }
+
+ public void setTarget(IResource target) {
+ fTarget= target;
+ }
+
+
+ public IFile getTargetFile(FileDiff diff) {
+ IPath path = diff.getStrippedPath(getStripPrefixSegments(), isReversed());
+ return existsInTarget(path);
+ }
+
+ /**
+ * Iterates through all of the resources contained in the Patch Wizard target
+ * and looks to for a match to the passed in file
+ * @param path
+ * @return IFile which matches the passed in path or null if none found
+ */
+ public IFile existsInTarget(IPath path) {
+ if (fTarget instanceof IFile) { // special case
+ IFile file= (IFile) fTarget;
+ if (matches(file.getFullPath(), path))
+ return file;
+ } else if (fTarget instanceof IContainer) {
+ IContainer c= (IContainer) fTarget;
+ if (c.exists(path))
+ return c.getFile(path);
+ }
+ return null;
+ }
+
+ /**
+ * Returns true if path completely matches the end of fullpath
+ * @param fullpath
+ * @param path
+ * @return true if path matches, false otherwise
+ */
+ private boolean matches(IPath fullpath, IPath path) {
+ for (IPath p= fullpath; path.segmentCount()<=p.segmentCount(); p= p.removeFirstSegments(1)) {
+ if (p.equals(path))
+ return true;
+ }
+ return false;
+ }
+
+ public int calculatePrefixSegmentCount() {
+ //Update prefix count - go through all of the diffs and find the smallest
+ //path segment contained in all diffs.
+ int length= 99;
+ if (fDiffs!=null)
+ for (int i= 0; i<fDiffs.length; i++) {
+ FileDiff diff= fDiffs[i];
+ length= Math.min(length, diff.segmentCount());
+ }
+ return length;
+ }
+
+ public void addDiff(FileDiff newDiff){
+ FileDiff[] temp = new FileDiff[fDiffs.length + 1];
+ System.arraycopy(fDiffs,0, temp, 0, fDiffs.length);
+ temp[fDiffs.length] = newDiff;
+ fDiffs = temp;
+ }
+
+ public void removeDiff(FileDiff diffToRemove){
+ FileDiff[] temp = new FileDiff[fDiffs.length - 1];
+ int counter = 0;
+ for (int i = 0; i < fDiffs.length; i++) {
+ if (fDiffs[i] != diffToRemove){
+ temp[counter++] = fDiffs[i];
+ }
+ }
+ fDiffs = temp;
+ }
+
+ public void setEnabled(Object element, boolean enabled) {
+ if (element instanceof DiffProject)
+ setEnabledProject((DiffProject) element, enabled);
+ if (element instanceof FileDiff)
+ setEnabledFile((FileDiff)element, enabled);
+ if (element instanceof Hunk)
+ setEnabledHunk((Hunk) element, enabled);
+ }
+
+ private void setEnabledProject(DiffProject projectDiff, boolean enabled) {
+ FileDiff[] diffFiles = projectDiff.getFileDiffs();
+ for (int i = 0; i < diffFiles.length; i++) {
+ setEnabledFile(diffFiles[i], enabled);
+ }
+ }
+
+ private void setEnabledFile(FileDiff fileDiff, boolean enabled) {
+ Hunk[] hunks = fileDiff.getHunks();
+ for (int i = 0; i < hunks.length; i++) {
+ setEnabledHunk(hunks[i], enabled);
+ }
+ }
+
+ private void setEnabledHunk(Hunk hunk, boolean enabled) {
+ if (enabled) {
+ disabledElements.remove(hunk);
+ FileDiff file = hunk.getParent();
+ disabledElements.remove(file);
+ DiffProject project = file.getProject();
+ if (project != null)
+ disabledElements.remove(project);
+ } else {
+ disabledElements.add(hunk);
+ FileDiff file = hunk.getParent();
+ if (disabledElements.containsAll(Arrays.asList(file.getHunks()))) {
+ disabledElements.add(file);
+ DiffProject project = file.getProject();
+ if (project != null
+ && disabledElements.containsAll(Arrays.asList(project
+ .getFileDiffs())))
+ disabledElements.add(project);
+ }
+ }
+ }
+
+ public boolean isEnabled(Object element) {
+ if (disabledElements.contains(element))
+ return false;
+ Object parent = getElementParent(element);
+ if (parent == null)
+ return true;
+ return isEnabled(parent);
+ }
+
+ protected Object getElementParent(Object element) {
+ if (element instanceof Hunk) {
+ Hunk hunk = (Hunk) element;
+ return hunk.getParent();
+ }
+ return null;
+ }
+
+ /**
+ * Calculate the fuzz factor that will allow the most hunks to be matched.
+ * @param monitor a progress monitor
+ * @return the fuzz factor or <code>-1</code> if no hunks could be matched
+ */
+ public int guessFuzzFactor(IProgressMonitor monitor) {
+ try {
+ monitor.beginTask(Messages.Patcher_2, IProgressMonitor.UNKNOWN);
+ FileDiff[] diffs= getDiffs();
+ if (diffs==null||diffs.length<=0)
+ return -1;
+ int fuzz= -1;
+ for (int i= 0; i<diffs.length; i++) {
+ FileDiff d= diffs[i];
+ IFile file= getTargetFile(d);
+ if (file != null && file.exists()) {
+ List lines= load(file, false);
+ FileDiffResult result = getDiffResult(d);
+ int f = result.calculateFuzz(lines, monitor);
+ if (f > fuzz)
+ fuzz = f;
+ }
+ }
+ return fuzz;
+ } finally {
+ monitor.done();
+ }
+ }
+
+ public void refresh() {
+ diffResults.clear();
+ refresh(getDiffs());
+ }
+
+ protected void refresh(FileDiff[] diffs) {
+ for (int i = 0; i < diffs.length; i++) {
+ FileDiff diff = diffs[i];
+ FileDiffResult result = getDiffResult(diff);
+ ((WorkspaceFileDiffResult)result).refresh();
+ }
+ }
+
+ public FileDiffResult getDiffResult(FileDiff diff) {
+ FileDiffResult result = (FileDiffResult)diffResults.get(diff);
+ if (result == null) {
+ result = new WorkspaceFileDiffResult(diff, getConfiguration());
+ diffResults.put(diff, result);
+ }
+ return result;
+ }
+
+ public PatchConfiguration getConfiguration() {
+ return configuration;
+ }
+
+ /**
+ * Return the project that contains this diff or <code>null</code>
+ * if the patch is not a workspace patch.
+ * @param diff the diff
+ * @return the project that contains the diff
+ */
+ public DiffProject getProject(FileDiff diff) {
+ return diff.getProject();
+ }
+
+ /*
+ * Returns <code>true</code> if new value differs from old.
+ */
+ public boolean setReversed(boolean reverse) {
+ if (getConfiguration().isReversed() != reverse) {
+ getConfiguration().setReversed(reverse);
+ refresh();
+ return true;
+ }
+ return false;
+ }
+
+ public boolean isReversed() {
+ return getConfiguration().isReversed();
+ }
+
+ /**
+ * Cache the contents for the given file diff. These contents
+ * will be used for the diff when the patch is applied. When the
+ * patch is applied, it is assumed that the provided contents
+ * already have all relevant hunks applied.
+ * @param diff the file diff
+ * @param contents the contents for the file diff
+ */
+ public void cacheContents(FileDiff diff, byte[] contents) {
+ contentCache.put(diff, contents);
+ }
+
+ /**
+ * Return whether contents have been cached for the
+ * given file diff.
+ * @param diff the file diff
+ * @return whether contents have been cached for the file diff
+ * @see #cacheContents(FileDiff, byte[])
+ */
+ public boolean hasCachedContents(FileDiff diff) {
+ return contentCache.containsKey(diff);
+ }
+
+ /**
+ * Return the content lines that are cached for the given
+ * file diff.
+ * @param diff the file diff
+ * @return the content lines that are cached for the file diff
+ */
+ public List getCachedLines(FileDiff diff) {
+ byte[] contents = (byte[])contentCache.get(diff);
+ if (contents != null) {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(contents)));
+ return readLines(reader);
+ }
+ return null;
+ }
+
+ /**
+ * Return the contents that are cached for the given diff or
+ * <code>null</code> if there is no contents cached.
+ * @param diff the diff
+ * @return the contents that are cached for the given diff or
+ * <code>null</code>
+ */
+ public byte[] getCachedContents(FileDiff diff) {
+ return (byte[])contentCache.get(diff);
+ }
+
+ /**
+ * Return whether the patcher has any cached contents.
+ * @return whether the patcher has any cached contents
+ */
+ public boolean hasCachedContents() {
+ return !contentCache.isEmpty();
+ }
+
+ /**
+ * Clear any cached contents.
+ */
+ public void clearCachedContents() {
+ contentCache.clear();
+ mergedHunks.clear();
+ }
+
+ public void setProperty(String key, Object value) {
+ getConfiguration().setProperty(key, value);
+ }
+
+ public Object getProperty(String key) {
+ return getConfiguration().getProperty(key);
+ }
+
+ public boolean isManuallyMerged(Hunk hunk) {
+ return mergedHunks.contains(hunk);
+ }
+
+ public void setManuallyMerged(Hunk hunk, boolean merged) {
+ if (merged)
+ mergedHunks.add(hunk);
+ else
+ mergedHunks.remove(hunk);
+ }
+
+ public IProject getTargetProject(FileDiff diff) {
+ DiffProject dp = getProject(diff);
+ if (dp != null)
+ return dp.getProject();
+ IResource tr = getTarget();
+ if (tr instanceof IWorkspaceRoot) {
+ IWorkspaceRoot root = (IWorkspaceRoot) tr;
+ return root.getProject(diff.getPath(isReversed()).segment(0));
+ }
+ return tr.getProject();
+ }
+
+ public static Patcher getPatcher(PatchConfiguration configuration) {
+ return (Patcher)configuration.getProperty(PROP_PATCHER);
+ }
+
+ public boolean hasRejects() {
+ for (Iterator iterator = diffResults.values().iterator(); iterator.hasNext();) {
+ FileDiffResult result = (FileDiffResult) iterator.next();
+ if (result.hasRejects())
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/Utilities.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/Utilities.java
new file mode 100644
index 000000000..291fcfea9
--- /dev/null
+++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/Utilities.java
@@ -0,0 +1,31 @@
+/*******************************************************************************
+ * Copyright (c) 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.internal.patch;
+
+import org.eclipse.compare.internal.core.Activator;
+import org.eclipse.core.resources.IEncodedStorage;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+
+public class Utilities {
+
+ public static String getCharset(Object resource) {
+ if (resource instanceof IEncodedStorage) {
+ try {
+ return ((IEncodedStorage)resource).getCharset();
+ } catch (CoreException ex) {
+ Activator.log(ex);
+ }
+ }
+ return ResourcesPlugin.getEncoding();
+ }
+
+}
diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/WorkspaceFileDiffResult.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/WorkspaceFileDiffResult.java
new file mode 100644
index 000000000..c0690416a
--- /dev/null
+++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/WorkspaceFileDiffResult.java
@@ -0,0 +1,53 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.internal.patch;
+
+import java.util.List;
+
+import org.eclipse.compare.patch.PatchConfiguration;
+import org.eclipse.core.resources.*;
+
+public class WorkspaceFileDiffResult extends FileDiffResult {
+
+ public WorkspaceFileDiffResult(FileDiff diff,
+ PatchConfiguration configuration) {
+ super(diff, configuration);
+ }
+
+ protected boolean canCreateTarget(IStorage storage) {
+ IProject project = getPatcher().getTargetProject(getDiff());
+ return project != null && project.isAccessible();
+ }
+
+ protected boolean targetExists(IStorage storage) {
+ IFile file= (IFile)storage;
+ return file != null && file.isAccessible();
+ }
+
+ protected List getLines(IStorage storage, boolean create) {
+ IFile file= getTargetFile();
+ List lines = Patcher.load(file, create);
+ return lines;
+ }
+
+ protected Patcher getPatcher() {
+ return Patcher.getPatcher(getConfiguration());
+ }
+
+ public IFile getTargetFile() {
+ return getPatcher().getTargetFile(getDiff());
+ }
+
+ public void refresh() {
+ refresh(getTargetFile(), null);
+ }
+
+}
diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/WorkspacePatcher.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/WorkspacePatcher.java
new file mode 100644
index 000000000..331e30023
--- /dev/null
+++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/WorkspacePatcher.java
@@ -0,0 +1,377 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2007 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.internal.patch;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.compare.internal.core.Messages;
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.resources.IMarker;
+import org.eclipse.core.resources.IProject;
+import org.eclipse.core.resources.IProjectDescription;
+import org.eclipse.core.resources.IResource;
+import org.eclipse.core.resources.IResourceRuleFactory;
+import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.SubProgressMonitor;
+import org.eclipse.core.runtime.jobs.ISchedulingRule;
+import org.eclipse.core.runtime.jobs.MultiRule;
+
+/**
+ * A Patcher
+ * - knows how to parse various patch file formats into some in-memory structure,
+ * - holds onto the parsed data and the options to use when applying the patches,
+ * - knows how to apply the patches to files and folders.
+ */
+public class WorkspacePatcher extends Patcher {
+
+ private DiffProject[] fDiffProjects;
+ private boolean fIsWorkspacePatch= false;
+ private final Map retargetedDiffs = new HashMap();
+
+ public WorkspacePatcher() {
+ // nothing to do
+ }
+
+ public WorkspacePatcher(IResource target) {
+ setTarget(target);
+ }
+
+ protected void patchParsed(PatchReader patchReader) {
+ super.patchParsed(patchReader);
+ fDiffProjects = patchReader.getDiffProjects();
+ fIsWorkspacePatch = patchReader.isWorkspacePatch();
+ }
+
+ public DiffProject[] getDiffProjects() {
+ return fDiffProjects;
+ }
+
+ public boolean isWorkspacePatch() {
+ return fIsWorkspacePatch;
+ }
+
+ //---- parsing patch files
+
+ public void applyAll(IProgressMonitor pm, IFileValidator validator) throws CoreException {
+ if (!fIsWorkspacePatch) {
+ super.applyAll(pm, validator);
+ } else {
+ final int WORK_UNIT= 10;
+
+ // get all files to be modified in order to call validateEdit
+ List list= new ArrayList();
+ for (int j= 0; j < fDiffProjects.length; j++) {
+ DiffProject diffProject= fDiffProjects[j];
+ if (diffProject.getProject().isAccessible())
+ list.addAll(Arrays.asList(getTargetFiles(diffProject)));
+ }
+ // validate the files for editing
+ if (!validator.validateResources((IFile[])list.toArray(new IFile[list.size()]))) {
+ return;
+ }
+
+ FileDiff[] diffs = getDiffs();
+ if (pm != null) {
+ String message= Messages.WorkspacePatcher_0;
+ pm.beginTask(message, diffs.length * WORK_UNIT);
+ }
+
+ for (int i= 0; i < diffs.length; i++) {
+
+ int workTicks= WORK_UNIT;
+
+ FileDiff diff= diffs[i];
+ if (isAccessible(diff)) {
+ IFile file= getTargetFile(diff);
+ IPath path= file.getProjectRelativePath();
+ if (pm != null)
+ pm.subTask(path.toString());
+ createPath(file.getProject(), path);
+
+ List failed= new ArrayList();
+
+ int type= diff.getDiffType(isReversed());
+ switch (type) {
+ case FileDiff.ADDITION :
+ // patch it and collect rejected hunks
+ List result= apply(diff, file, true, failed);
+ if (result != null)
+ store(createString(isPreserveLineDelimeters(), result), file, new SubProgressMonitor(pm, workTicks));
+ workTicks -= WORK_UNIT;
+ break;
+ case FileDiff.DELETION :
+ file.delete(true, true, new SubProgressMonitor(pm, workTicks));
+ workTicks -= WORK_UNIT;
+ break;
+ case FileDiff.CHANGE :
+ // patch it and collect rejected hunks
+ result= apply(diff, file, false, failed);
+ if (result != null)
+ store(createString(isPreserveLineDelimeters(), result), file, new SubProgressMonitor(pm, workTicks));
+ workTicks -= WORK_UNIT;
+ break;
+ }
+
+ if (isGenerateRejectFile() && failed.size() > 0) {
+ IPath pp= null;
+ if (path.segmentCount() > 1) {
+ pp= path.removeLastSegments(1);
+ pp= pp.append(path.lastSegment() + REJECT_FILE_EXTENSION);
+ } else
+ pp= new Path(path.lastSegment() + REJECT_FILE_EXTENSION);
+ file= createPath(file.getProject(), pp);
+ if (file != null) {
+ store(getRejected(failed), file, pm);
+ try {
+ IMarker marker= file.createMarker(MARKER_TYPE);
+ marker.setAttribute(IMarker.MESSAGE, Messages.WorkspacePatcher_1);
+ marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH);
+ } catch (CoreException ex) {
+ // NeedWork
+ }
+ }
+ }
+ }
+
+ if (pm != null) {
+ if (pm.isCanceled())
+ break;
+ if (workTicks > 0)
+ pm.worked(workTicks);
+ }
+ }
+ }
+ }
+
+ private boolean isAccessible(FileDiff diff) {
+ return isEnabled(diff) && diff.getProject().getProject().isAccessible();
+ }
+
+ /**
+ * Returns the target files of all the Diffs contained by this
+ * DiffProject.
+ * @param project
+ * @return An array of IFiles that are targeted by the Diffs
+ */
+ public IFile[] getTargetFiles(DiffProject project) {
+ List files= new ArrayList();
+ FileDiff[] diffs = project.getFileDiffs();
+ for (int i = 0; i < diffs.length; i++) {
+ FileDiff diff = diffs[i];
+ if (isEnabled(diff)) {
+ files.add(getTargetFile(diff));
+ }
+ }
+ return (IFile[]) files.toArray(new IFile[files.size()]);
+ }
+
+ public IFile getTargetFile(FileDiff diff) {
+ IPath path = diff.getStrippedPath(getStripPrefixSegments(), isReversed());
+ DiffProject project = getProject(diff);
+ if (project != null)
+ return project.getFile(path);
+ return super.getTargetFile(diff);
+ }
+
+ private IPath getFullPath(FileDiff diff) {
+ IPath path = diff.getStrippedPath(getStripPrefixSegments(), isReversed());
+ DiffProject project = getProject(diff);
+ if (project != null)
+ return project.getFile(path).getFullPath();
+ return getTarget().getFullPath().append(path);
+ }
+
+ public ISchedulingRule[] getTargetProjects() {
+ List projects= new ArrayList();
+ IResourceRuleFactory ruleFactory= ResourcesPlugin.getWorkspace().getRuleFactory();
+ // Determine the appropriate scheduling rules
+ for (int i= 0; i < fDiffProjects.length; i++) {
+ IProject tempProject= fDiffProjects[i].getProject();
+ // The goal here is to lock as little of the workspace as necessary
+ // but still allow the patcher to obtain the locks it needs.
+ // As such, we need to get the modify rules from the rule factory for the .project file. A pessimistic
+ // rule factory will return the root, while others might return just the project. Combining
+ // this rule with the project will result in the smallest possible locking set.
+ ISchedulingRule scheduleRule= ruleFactory.modifyRule(tempProject.getFile(IProjectDescription.DESCRIPTION_FILE_NAME));
+ MultiRule multiRule= new MultiRule(new ISchedulingRule[] { scheduleRule, tempProject } );
+ projects.add(multiRule);
+ }
+
+ return (ISchedulingRule[]) projects.toArray(new ISchedulingRule[projects.size()]);
+ }
+
+ public void setDiffProjects(DiffProject[] newProjectArray) {
+ fDiffProjects = new DiffProject[newProjectArray.length];
+ System.arraycopy(newProjectArray,0, fDiffProjects, 0, newProjectArray.length);
+ }
+
+ public void removeProject(DiffProject project) {
+ DiffProject[] temp = new DiffProject[fDiffProjects.length - 1];
+ int counter = 0;
+ for (int i = 0; i < fDiffProjects.length; i++) {
+ if (fDiffProjects[i] != project){
+ temp[counter++] = fDiffProjects[i];
+ }
+ }
+ fDiffProjects = temp;
+ }
+
+ protected Object getElementParent(Object element) {
+ if (element instanceof FileDiff && fDiffProjects != null) {
+ FileDiff diff = (FileDiff) element;
+ for (int i = 0; i < fDiffProjects.length; i++) {
+ DiffProject project = fDiffProjects[i];
+ if (project.contains(diff))
+ return project;
+ }
+ }
+ return null;
+ }
+
+ public boolean isRetargeted(Object object) {
+ return retargetedDiffs.containsKey(object);
+ }
+
+ public IPath getOriginalPath(Object object) {
+ return (IPath)retargetedDiffs.get(object);
+ }
+
+ public void retargetDiff(FileDiff diff, IFile file) {
+ retargetedDiffs.put(diff, diff.getPath(false));
+ Hunk[] hunks = diff.getHunks();
+
+ if (isWorkspacePatch()){
+ //since the diff has no more hunks to apply, remove it from the parent and the patcher
+ diff.getProject().remove(diff);
+ }
+ removeDiff(diff);
+ FileDiff newDiff = getDiffForFile(file);
+ for (int i = 0; i < hunks.length; i++) {
+ Hunk hunk = hunks[i];
+ newDiff.add(hunk);
+ }
+ }
+
+ private FileDiff getDiffForFile(IFile file) {
+ DiffProject diffProject = null;
+ FileDiff[] diffsToCheck;
+ if (isWorkspacePatch()){
+ // Check if the diff project already exists for the file
+ IProject project = file.getProject();
+ DiffProject[] diffProjects = getDiffProjects();
+ for (int i = 0; i < diffProjects.length; i++) {
+ if (diffProjects[i].getProject().equals(project)){
+ diffProject = diffProjects[i];
+ break;
+ }
+ }
+ // If the project doesn't exist yet, create it and add it to the project list
+ if (diffProject == null){
+ diffProject = addDiffProjectForProject(project);
+ }
+ diffsToCheck = diffProject.getFileDiffs();
+ } else {
+ diffsToCheck = getDiffs();
+ }
+ // Check to see if a diff already exists for the file
+ for (int i = 0; i < diffsToCheck.length; i++) {
+ FileDiff fileDiff = diffsToCheck[i];
+ if (isDiffForFile(fileDiff, file)) {
+ return fileDiff;
+ }
+ }
+
+ // Create a new diff for the file
+ IPath path = getDiffPath(file);
+ FileDiff newDiff = new FileDiff(path, 0, path, 0);
+ if (diffProject != null){
+ diffProject.add(newDiff);
+ }
+ addDiff(newDiff);
+ return newDiff;
+ }
+
+ private IPath getDiffPath(IFile file) {
+ DiffProject project = getDiffProject(file.getProject());
+ if (project != null) {
+ return file.getProjectRelativePath();
+ }
+ return file.getFullPath().removeFirstSegments(getTarget().getFullPath().segmentCount());
+ }
+
+ private boolean isDiffForFile(FileDiff fileDiff, IFile file) {
+ return getFullPath(fileDiff).equals(file.getFullPath());
+ }
+
+ private DiffProject addDiffProjectForProject(IProject project) {
+ DiffProject[] diffProjects = getDiffProjects();
+ DiffProject diffProject = new DiffProject(project);
+ DiffProject[] newProjectArray = new DiffProject[diffProjects.length + 1];
+ System.arraycopy(diffProjects, 0, newProjectArray, 0, diffProjects.length);
+ newProjectArray[diffProjects.length] = diffProject;
+ setDiffProjects(newProjectArray);
+ return diffProject;
+ }
+
+ public void retargetHunk(Hunk hunk, IFile file) {
+ FileDiff newDiff = getDiffForFile(file);
+ newDiff.add(hunk);
+ }
+
+ public void retargetProject(DiffProject project, IProject targetProject) {
+ retargetedDiffs.put(project, project.getProject().getFullPath());
+ FileDiff[] diffs = project.getFileDiffs();
+ DiffProject selectedProject = getDiffProject(targetProject);
+ if (selectedProject == null)
+ selectedProject = addDiffProjectForProject(targetProject);
+ // Copy over the diffs to the new project
+ for (int i = 0; i < diffs.length; i++) {
+ selectedProject.add(diffs[i]);
+ }
+ // Since the project has been retargeted, remove it from the patcher
+ removeProject(project);
+ }
+
+ /**
+ * Return the diff project for the given project
+ * or <code>null</code> if the diff project doesn't exist
+ * or if the patch is not a workspace patch.
+ * @param project the project
+ * @return the diff project for the given project
+ * or <code>null</code>
+ */
+ private DiffProject getDiffProject(IProject project) {
+ if (!isWorkspacePatch())
+ return null;
+ DiffProject[] projects = getDiffProjects();
+ for (int i = 0; i < projects.length; i++) {
+ if (projects[i].getProject().equals(project))
+ return projects[i];
+ }
+ return null;
+ }
+
+ public int getStripPrefixSegments() {
+ // Segments are never stripped from a workspace patch
+ if (isWorkspacePatch())
+ return 0;
+ return super.getStripPrefixSegments();
+ }
+
+}
diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/IFilePatch.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/IFilePatch.java
new file mode 100644
index 000000000..21646d916
--- /dev/null
+++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/IFilePatch.java
@@ -0,0 +1,89 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.patch;
+
+import org.eclipse.core.resources.IStorage;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+
+/**
+ * A representation of a file patch that can be applied to an input stream.
+ * <p>
+ * This interface is not intended to be implemented by clients. Clients can
+ * obtain file patches by calling
+ * {@link ApplyPatchOperation#parsePatch(org.eclipse.core.resources.IStorage)}.
+ * </p>
+ *
+ * @see ApplyPatchOperation#parsePatch(org.eclipse.core.resources.IStorage)
+ * @since 3.3
+ * @noimplement
+ */
+public interface IFilePatch {
+
+ /**
+ * Special constant that will be returned from get getBeforeDate() or
+ * getAfterDate() if the date is unknown. Equal to Midnight, Jan 1, 1970
+ * GMT.
+ *
+ * @since 3.4
+ */
+ public static long DATE_UNKNOWN = 0;
+
+ /**
+ * Return the target path for this patch. The target path may differ
+ * depending on whether the patch is being reversed or not.
+ *
+ * @param configuration the patch configuration
+ * @return the target path for this patch
+ * @see PatchConfiguration#isReversed()
+ */
+ public IPath getTargetPath(PatchConfiguration configuration);
+
+ /**
+ * Apply this patch to the given file contents. The result provides the
+ * original and patch contents and also indicates whether some portions of
+ * the patch (called hunks) failed to apply.
+ *
+ * @param contents the file contents
+ * @param configuration the patch configuration
+ * @param monitor a progress monitor
+ * @return the result of the patch application
+ */
+ public IFilePatchResult apply(IStorage contents,
+ PatchConfiguration configuration, IProgressMonitor monitor);
+
+ /**
+ * Return the header information of the patch or
+ * <code>null</code> if there was no header text.
+ * The header may be multi-line.
+ * @return the header information of the patch or
+ * <code>null</code>
+ */
+ public String getHeader();
+
+ /**
+ * Returns the milliseconds time value of the before date from the patch, or
+ * DATE_UNKNOWN if the date is unknown.
+ *
+ * @return milliseconds time value of the before date from the patch
+ * @since 3.4
+ */
+ public long getBeforeDate();
+
+ /**
+ * Returns the milliseconds time value of the after date from the patch, or
+ * DATE_UNKNOWN if the date is unknown.
+ *
+ * @return milliseconds time value of the after date from the patch
+ * @since 3.4
+ */
+ public long getAfterDate();
+}
diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/IFilePatchResult.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/IFilePatchResult.java
new file mode 100644
index 000000000..4f229c3aa
--- /dev/null
+++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/IFilePatchResult.java
@@ -0,0 +1,94 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.patch;
+
+import java.io.InputStream;
+
+import org.eclipse.core.resources.IEncodedStorage;
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * A file patch result provides the results of an attempt to apply an
+ * {@link IFilePatch} to the contents of a file. *
+ * <p>
+ * This interface is not intended to be implemented by clients. Clients can
+ * obtain patch results from an {@link IFilePatch}.
+ * </p>
+ *
+ * @see IFilePatch
+ * @since 3.3
+ */
+public interface IFilePatchResult {
+
+ /**
+ * Return a stream the contains the original contents of the file before
+ * any portions of the patch have been applied.
+ * @return a stream to the original contents of the file before
+ * any portions of the patch have been applied
+ * @see #getPatchedContents()
+ */
+ public InputStream getOriginalContents();
+
+ /**
+ * Return a stream that contains the file with as much of the patch
+ * applied as possible. if {@link #hasMatches()} returns <code>false</code>
+ * then the patched contents will match the original contents. Otherwise,
+ * at least a portion of the patch could be successfully applied. if
+ * {@link #hasRejects()} returns <code>false</code>, then the entire patch was
+ * applied. Otherwise, portions could not be applied. The portions that could
+ * not be applied can be obtained by calling {@link #getRejects()}.
+ *
+ * @return a stream that contains the file with as much of the patch
+ * applied as possible.
+ */
+ public InputStream getPatchedContents();
+
+ /**
+ * Return whether the patch has portions that were successfully applied.
+ * @return whether the patch has portions that were successfully applied
+ * @see #getPatchedContents()
+ */
+ public boolean hasMatches();
+
+ /**
+ * Return whether the patch has portions that were not successfully applied.
+ * @return whether the patch has portions that were not successfully applied
+ * @see #getPatchedContents()
+ */
+ public boolean hasRejects();
+
+ /**
+ * Return the portions of the patch (referred to a hunks) that could not
+ * be applied.
+ * @return the portions of the patch (referred to a hunks) that could not
+ * be applied
+ * @see #getPatchedContents()
+ */
+ public IHunk[] getRejects();
+
+ /**
+ * Returns the name of a charset encoding to be used when decoding the contents
+ * of this result into characters. Returns <code>null</code> if a proper
+ * encoding cannot be determined.
+ * <p>
+ * Note that this method does not check whether the result is a supported
+ * charset name. Callers should be prepared to handle
+ * <code>UnsupportedEncodingException</code> where this charset is used.
+ * </p>
+ *
+ * @return the name of a charset, or <code>null</code>
+ * @exception CoreException if an error happens while determining
+ * the charset. See any refinements for more information.
+ * @see IEncodedStorage
+ */
+ public String getCharset() throws CoreException;
+
+}
diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/IHunk.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/IHunk.java
new file mode 100644
index 000000000..126150c0d
--- /dev/null
+++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/IHunk.java
@@ -0,0 +1,82 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2007 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.patch;
+
+import java.io.InputStream;
+
+import org.eclipse.core.resources.IEncodedStorage;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IAdaptable;
+
+/**
+ * Interface that represents a hunk. A hunk is a portion of a patch. It
+ * identifies where the hunk is to be located in the target file. One use of
+ * this interface is a means to communicate to content merge viewers that one of
+ * the sides of a compare input is a patch hunk. Clients can determine which
+ * side it is by adapting the side to this interface (see {@link IAdaptable}.
+ * <p>
+ * This interface is not intended to be implemented by clients but can be
+ * obtained from an {@link IFilePatchResult}
+ * </p>
+ *
+ * @since 3.3
+ *
+ */
+public interface IHunk {
+
+ /**
+ * Return a label that can be used to describe the hunk.
+ * @return a label that can be used to describe the hunk
+ */
+ public String getLabel();
+
+ /**
+ * Return the start position of the hunk in the target file.
+ *
+ * @return the start position of the hunk in the target file.
+ */
+ public int getStartPosition();
+
+ /**
+ * Return the original contents from which the hunk was generated.
+ * The returned contents usually only represent a portion of the
+ * file from which the hunk was generated.
+ * @return the original contents from which the hunk was generated
+ */
+ public InputStream getOriginalContents();
+
+ /**
+ * Return the contents that contain the modifications for this hunk.
+ * The returned contents usually only represent a portion of the
+ * file that was modified.
+ * @return the contents that contain the modifications for this hunk
+ */
+ public InputStream getPatchedContents();
+
+ /**
+ * Returns the name of a charset encoding to be used when decoding the contents
+ * of this hunk into characters. Returns <code>null</code> if a proper
+ * encoding cannot be determined.
+ * <p>
+ * Note that this method does not check whether the result is a supported
+ * charset name. Callers should be prepared to handle
+ * <code>UnsupportedEncodingException</code> where this charset is used.
+ * </p>
+ *
+ * @return the name of a charset, or <code>null</code>
+ * @exception CoreException if an error happens while determining
+ * the charset. See any refinements for more information.
+ * @see IEncodedStorage
+ */
+ public String getCharset() throws CoreException;
+
+
+}
diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchConfiguration.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchConfiguration.java
new file mode 100644
index 000000000..3287a4585
--- /dev/null
+++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchConfiguration.java
@@ -0,0 +1,122 @@
+/*******************************************************************************
+ * Copyright (c) 2007 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.patch;
+
+import java.util.HashMap;
+
+/**
+ * A patch configuration allows clients to set parameters that control how a patch
+ * is applied.
+ * <p>
+ * This class may be instantiated by clients but is not intended to be subclassed.
+ * </p>
+ * @see IFilePatch
+ * @since 3.3
+ */
+public class PatchConfiguration {
+
+ private int fStripPrefixSegments;
+ private int fFuzz;
+ private boolean fIgnoreWhitespace= false;
+ private boolean fReverse= false;
+ private HashMap properties = new HashMap();
+
+ /**
+ * Return whether the patch should be reversed when applied.
+ * @return whether the patch should be reversed when applied
+ */
+ public boolean isReversed() {
+ return fReverse;
+ }
+
+ /**
+ * Set whether the patch should be reversed when applied.
+ * @param reversed whether the patch should be reversed when applied
+ */
+ public void setReversed(boolean reversed) {
+ this.fReverse = reversed;
+ }
+
+ /**
+ * Return the number of prefix segments to be stripped when attempting
+ * to apply a patch.
+ * @return the number of prefix segments to be stripped when attempting
+ * to apply a patch
+ */
+ public int getPrefixSegmentStripCount() {
+ return fStripPrefixSegments;
+ }
+
+ /**
+ * Set the number of prefix segments to be stripped when attempting
+ * to apply a patch.
+ * @param stripCount the number of prefix segments to be stripped when attempting
+ * to apply a patch.
+ */
+ public void setPrefixSegmentStripCount(int stripCount) {
+ this.fStripPrefixSegments = stripCount;
+ }
+
+ /**
+ * Return the fuzz factor to be used when applying a patch.
+ * If the fuzz factor is set to -1, then the patcher is to make a best
+ * effort to apply the patch by adjusting the fuzz factor
+ * accordingly.
+ * @return the fuzz factor to be used when applying a patch.
+ */
+ public int getFuzz() {
+ return fFuzz;
+ }
+
+ /**
+ * Set the fuzz factor to be used when applying a patch.
+ * @param fuzz the fuzz factor to be used when applying a patch.
+ */
+ public void setFuzz(int fuzz) {
+ fFuzz = fuzz;
+ }
+
+ /**
+ * Return whether whitespace should be ignored.
+ * @return whether whitespace should be ignored
+ */
+ public boolean isIgnoreWhitespace() {
+ return fIgnoreWhitespace;
+ }
+
+ /**
+ * Set whether whitespace should be ignored
+ * @param ignoreWhitespace whether whitespace should be ignored
+ */
+ public void setIgnoreWhitespace(boolean ignoreWhitespace) {
+ fIgnoreWhitespace = ignoreWhitespace;
+ }
+
+ /**
+ * Return the property associated with the given key or
+ * <code>null</code> if there is no property for the key.
+ * @param key the key
+ * @return the property associated with the given key or
+ * <code>null</code>
+ */
+ public Object getProperty(String key) {
+ return properties.get(key);
+ }
+
+ /**
+ * Set the property associated with the given key
+ * @param key the key
+ * @param value the value to be associated with the key
+ */
+ public void setProperty(String key, Object value) {
+ properties.put(key, value);
+ }
+}
diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchParser.java b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchParser.java
new file mode 100644
index 000000000..f734a957a
--- /dev/null
+++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchParser.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2008 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.patch;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+
+import org.eclipse.compare.internal.core.Activator;
+import org.eclipse.compare.internal.patch.PatchReader;
+import org.eclipse.compare.internal.patch.Patcher;
+import org.eclipse.core.resources.IStorage;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+
+/**
+ * Helper class for parsing patches.
+ */
+public class PatchParser {
+
+ /**
+ * Return whether the given storage contains a patch.
+ * @param storage the storage
+ * @return whether the given storage contains a patch
+ * @throws CoreException if an error occurs reading the contents from the storage
+ */
+ public static boolean isPatch(IStorage storage) throws CoreException {
+ return parsePatch(storage).length > 0;
+ }
+
+ /**
+ * Parse the given patch and return the set of file patches that it contains.
+ * @param storage the storage that contains the patch
+ * @return the set of file patches that the storage contains
+ * @throws CoreException if an error occurs reading the contents from the storage
+ */
+ public static IFilePatch[] parsePatch(IStorage storage) throws CoreException {
+ BufferedReader reader = Patcher.createReader(storage);
+ try {
+ PatchReader patchReader= new PatchReader();
+ patchReader.parse(reader);
+ return patchReader.getAdjustedDiffs();
+ } catch (IOException e) {
+ throw new CoreException(new Status(IStatus.ERROR, Activator.PLUGIN_ID, 0, e.getMessage(), e));
+ } finally {
+ try {
+ reader.close();
+ } catch (IOException e) { //ignored
+ }
+ }
+ }
+}

Back to the top