From 833041cbfa1691a0430fb79fd9950d5125946c15 Mon Sep 17 00:00:00 2001
From: Michael Valenta
Date: Mon, 14 Jul 2008 19:47:27 +0000
Subject: Bug 207704 Decouple org.eclipse.compare from UI
---
.../plugins/org.eclipse.compare.core/.classpath | 7 +
.../plugins/org.eclipse.compare.core/.project | 34 +
.../.settings/org.eclipse.jdt.core.prefs | 7 +
.../org.eclipse.compare.core/META-INF/MANIFEST.MF | 17 +
.../org.eclipse.compare.core/build.properties | 5 +
.../org.eclipse.compare.core/plugin.properties | 3 +
.../eclipse/compare/internal/core/Activator.java | 70 ++
.../eclipse/compare/internal/core/Messages.java | 32 +
.../compare/internal/core/messages.properties | 10 +
.../compare/internal/patch/DiffProject.java | 98 +++
.../eclipse/compare/internal/patch/FileDiff.java | 260 ++++++
.../compare/internal/patch/FileDiffResult.java | 335 ++++++++
.../org/eclipse/compare/internal/patch/Hunk.java | 445 +++++++++++
.../eclipse/compare/internal/patch/HunkResult.java | 294 +++++++
.../eclipse/compare/internal/patch/LineReader.java | 137 ++++
.../compare/internal/patch/PatchReader.java | 669 ++++++++++++++++
.../eclipse/compare/internal/patch/Patcher.java | 877 +++++++++++++++++++++
.../eclipse/compare/internal/patch/Utilities.java | 31 +
.../internal/patch/WorkspaceFileDiffResult.java | 53 ++
.../compare/internal/patch/WorkspacePatcher.java | 377 +++++++++
.../src/org/eclipse/compare/patch/IFilePatch.java | 89 +++
.../eclipse/compare/patch/IFilePatchResult.java | 94 +++
.../src/org/eclipse/compare/patch/IHunk.java | 82 ++
.../eclipse/compare/patch/PatchConfiguration.java | 122 +++
.../src/org/eclipse/compare/patch/PatchParser.java | 60 ++
25 files changed, 4208 insertions(+)
create mode 100644 bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/.classpath
create mode 100644 bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/.project
create mode 100644 bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/.settings/org.eclipse.jdt.core.prefs
create mode 100644 bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/META-INF/MANIFEST.MF
create mode 100644 bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/build.properties
create mode 100644 bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/plugin.properties
create mode 100644 bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/Activator.java
create mode 100644 bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/Messages.java
create mode 100644 bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/messages.properties
create mode 100644 bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/DiffProject.java
create mode 100644 bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/FileDiff.java
create mode 100644 bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/FileDiffResult.java
create mode 100644 bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/Hunk.java
create mode 100644 bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/HunkResult.java
create mode 100644 bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/LineReader.java
create mode 100644 bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/PatchReader.java
create mode 100644 bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/Patcher.java
create mode 100644 bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/Utilities.java
create mode 100644 bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/WorkspaceFileDiffResult.java
create mode 100644 bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/internal/patch/WorkspacePatcher.java
create mode 100644 bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/IFilePatch.java
create mode 100644 bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/IFilePatchResult.java
create mode 100644 bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/IHunk.java
create mode 100644 bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchConfiguration.java
create mode 100644 bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchParser.java
diff --git a/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/.classpath b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/.classpath
new file mode 100644
index 000000000..2fbb7a23e
--- /dev/null
+++ b/bundles/org.eclipse.compare/plugins/org.eclipse.compare.core/.classpath
@@ -0,0 +1,7 @@
+
+null
if there isn't one.
+ * @return the parent project or null
+ */
+ 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 null
for an addition
+ * @param monitor a progress monitor or null
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 -1
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:
+ *
Character.isWhitespace(...)
.
+ */
+ 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; inull
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 DateFormat
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 parse
.
+ * If parse
hasn't been called returns null
.
+ */
+ public FileDiff[] getDiffs() {
+ if (fDiffs == null)
+ return new FileDiff[0];
+ return fDiffs;
+ }
+
+ public IPath getPath(FileDiff diff) {
+ return diff.getStrippedPath(getStripPrefixSegments(), isReversed());
+ }
+
+ /*
+ * Returns true
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 true
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 true
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; inull
+ * 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 true
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
+ * null
if there is no contents cached.
+ * @param diff the diff
+ * @return the contents that are cached for the given diff or
+ * null
+ */
+ 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 null
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 null
+ */
+ 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.
+ * + * 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)}. + *
+ * + * @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 + *null
if there was no header text.
+ * The header may be multi-line.
+ * @return the header information of the patch or
+ * null
+ */
+ 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. *
+ * + * This interface is not intended to be implemented by clients. Clients can + * obtain patch results from an {@link IFilePatch}. + *
+ * + * @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()} returnsfalse
+ * 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 false
, 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 null
if a proper
+ * encoding cannot be determined.
+ *
+ * Note that this method does not check whether the result is a supported
+ * charset name. Callers should be prepared to handle
+ * UnsupportedEncodingException
where this charset is used.
+ *
null
+ * @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}.
+ * + * This interface is not intended to be implemented by clients but can be + * obtained from an {@link IFilePatchResult} + *
+ * + * @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. Returnsnull
if a proper
+ * encoding cannot be determined.
+ *
+ * Note that this method does not check whether the result is a supported
+ * charset name. Callers should be prepared to handle
+ * UnsupportedEncodingException
where this charset is used.
+ *
null
+ * @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.
+ * + * This class may be instantiated by clients but is not intended to be subclassed. + *
+ * @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 + *null
if there is no property for the key.
+ * @param key the key
+ * @return the property associated with the given key or
+ * null
+ */
+ 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
+ }
+ }
+ }
+}
--
cgit v1.2.1