Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTomasz Zarna2011-10-13 11:36:27 +0000
committerTomasz Zarna2011-10-13 11:36:27 +0000
commitf0e53ee2640b02d1f95f87dfcfa88331c3336845 (patch)
tree666e298f81221c128f3c0249c32154b1aef93861 /bundles/org.eclipse.compare.core
parente4281ecfd04ac79545ba778419a1cfa257818d4c (diff)
downloadeclipse.platform.team-f0e53ee2640b02d1f95f87dfcfa88331c3336845.tar.gz
eclipse.platform.team-f0e53ee2640b02d1f95f87dfcfa88331c3336845.tar.xz
eclipse.platform.team-f0e53ee2640b02d1f95f87dfcfa88331c3336845.zip
bug 359032: Move plugins under bundles/org.eclipse.compare/plugins/ to
bundles/
Diffstat (limited to 'bundles/org.eclipse.compare.core')
-rw-r--r--bundles/org.eclipse.compare.core/.classpath7
-rw-r--r--bundles/org.eclipse.compare.core/.project34
-rw-r--r--bundles/org.eclipse.compare.core/.settings/org.eclipse.core.runtime.prefs2
-rw-r--r--bundles/org.eclipse.compare.core/.settings/org.eclipse.jdt.core.prefs31
-rw-r--r--bundles/org.eclipse.compare.core/.settings/org.eclipse.jdt.launching.prefs3
-rw-r--r--bundles/org.eclipse.compare.core/META-INF/MANIFEST.MF17
-rw-r--r--bundles/org.eclipse.compare.core/about.html28
-rw-r--r--bundles/org.eclipse.compare.core/build.properties17
-rw-r--r--bundles/org.eclipse.compare.core/plugin.properties15
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/ComparePlugin.java81
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/LCS.java461
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/Messages.java39
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/TextLineLCS.java207
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/messages.properties22
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/DiffProject.java76
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/FileDiffResult.java338
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/FilePatch2.java266
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/Hunk.java518
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/HunkResult.java273
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/LineReader.java224
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/PatchReader.java683
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/Utilities.java23
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/IFilePatch2.java89
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/IFilePatchResult.java90
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/IHunk.java98
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/IHunkFilter.java29
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchBuilder.java250
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchConfiguration.java158
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchParser.java57
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/ReaderCreator.java42
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/package.html15
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/AbstractRangeDifferenceFactory.java49
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/DifferencesIterator.java77
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/IRangeComparator.java60
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/RangeComparatorLCS.java192
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/RangeDifference.java326
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/RangeDifferencer.java468
-rw-r--r--bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/package.html41
38 files changed, 5406 insertions, 0 deletions
diff --git a/bundles/org.eclipse.compare.core/.classpath b/bundles/org.eclipse.compare.core/.classpath
new file mode 100644
index 000000000..2fbb7a23e
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.4"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/bundles/org.eclipse.compare.core/.project b/bundles/org.eclipse.compare.core/.project
new file mode 100644
index 000000000..fa1288658
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/.project
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.eclipse.compare.core</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.api.tools.apiAnalysisBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ <nature>org.eclipse.pde.api.tools.apiAnalysisNature</nature>
+ </natures>
+</projectDescription>
diff --git a/bundles/org.eclipse.compare.core/.settings/org.eclipse.core.runtime.prefs b/bundles/org.eclipse.compare.core/.settings/org.eclipse.core.runtime.prefs
new file mode 100644
index 000000000..c522e1f4a
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/.settings/org.eclipse.core.runtime.prefs
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+line.separator=\n
diff --git a/bundles/org.eclipse.compare.core/.settings/org.eclipse.jdt.core.prefs b/bundles/org.eclipse.compare.core/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 000000000..4e907f0c6
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,31 @@
+#Tue Feb 01 14:35:28 CET 2011
+eclipse.preferences.version=1
+org.eclipse.jdt.core.builder.cleanOutputFolder=clean
+org.eclipse.jdt.core.builder.duplicateResourceTask=warning
+org.eclipse.jdt.core.builder.invalidClasspath=abort
+org.eclipse.jdt.core.builder.recreateModifiedClassFileInOutputFolder=ignore
+org.eclipse.jdt.core.builder.resourceCopyExclusionFilter=*.launch
+org.eclipse.jdt.core.circularClasspath=error
+org.eclipse.jdt.core.classpath.exclusionPatterns=enabled
+org.eclipse.jdt.core.classpath.multipleOutputLocations=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.2
+org.eclipse.jdt.core.compiler.compliance=1.4
+org.eclipse.jdt.core.compiler.doc.comment.support=enabled
+org.eclipse.jdt.core.compiler.maxProblemPerUnit=100
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=warning
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=warning
+org.eclipse.jdt.core.compiler.problem.invalidJavadoc=error
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=disabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=enabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=protected
+org.eclipse.jdt.core.compiler.problem.missingJavadocComments=ignore
+org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=public
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagDescription=return_tag
+org.eclipse.jdt.core.compiler.problem.missingJavadocTags=warning
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=protected
+org.eclipse.jdt.core.compiler.source=1.3
+org.eclipse.jdt.core.incompatibleJDKLevel=ignore
+org.eclipse.jdt.core.incompleteClasspath=error
diff --git a/bundles/org.eclipse.compare.core/.settings/org.eclipse.jdt.launching.prefs b/bundles/org.eclipse.compare.core/.settings/org.eclipse.jdt.launching.prefs
new file mode 100644
index 000000000..58060f576
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/.settings/org.eclipse.jdt.launching.prefs
@@ -0,0 +1,3 @@
+#Tue Feb 01 14:35:28 CET 2011
+eclipse.preferences.version=1
+org.eclipse.jdt.launching.PREF_STRICTLY_COMPATIBLE_JRE_NOT_AVAILABLE=error
diff --git a/bundles/org.eclipse.compare.core/META-INF/MANIFEST.MF b/bundles/org.eclipse.compare.core/META-INF/MANIFEST.MF
new file mode 100644
index 000000000..9a8a60651
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/META-INF/MANIFEST.MF
@@ -0,0 +1,17 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %pluginName
+Bundle-SymbolicName: org.eclipse.compare.core
+Bundle-Version: 3.5.200.qualifier
+Bundle-Activator: org.eclipse.compare.internal.core.ComparePlugin
+Bundle-Vendor: %providerName
+Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.2.0,4.0.0)"
+Bundle-RequiredExecutionEnvironment: J2SE-1.4
+Bundle-ActivationPolicy: lazy
+Export-Package: org.eclipse.compare.internal.core;x-internal:=true,
+ org.eclipse.compare.internal.core.patch;x-internal:=true,
+ org.eclipse.compare.patch,
+ org.eclipse.compare.rangedifferencer
+Import-Package: com.ibm.icu.text;version="3.6.1",
+ com.ibm.icu.util;version="3.6.1"
+Bundle-Localization: plugin
diff --git a/bundles/org.eclipse.compare.core/about.html b/bundles/org.eclipse.compare.core/about.html
new file mode 100644
index 000000000..460233046
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/about.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
+<title>About</title>
+</head>
+<body lang="EN-US">
+<h2>About This Content</h2>
+
+<p>June 2, 2006</p>
+<h3>License</h3>
+
+<p>The Eclipse Foundation makes available all content in this plug-in (&quot;Content&quot;). Unless otherwise
+indicated below, the Content is provided to you under the terms and conditions of the
+Eclipse Public License Version 1.0 (&quot;EPL&quot;). A copy of the EPL is available
+at <a href="http://www.eclipse.org/legal/epl-v10.html">http://www.eclipse.org/legal/epl-v10.html</a>.
+For purposes of the EPL, &quot;Program&quot; will mean the Content.</p>
+
+<p>If you did not receive this Content directly from the Eclipse Foundation, the Content is
+being redistributed by another party (&quot;Redistributor&quot;) and different terms and conditions may
+apply to your use of any object code in the Content. Check the Redistributor's license that was
+provided with the Content. If no such license exists, contact the Redistributor. Unless otherwise
+indicated below, the terms and conditions of the EPL still apply to any source code in the Content
+and such source code may be obtained at <a href="http://www.eclipse.org">http://www.eclipse.org</a>.</p>
+
+</body>
+</html> \ No newline at end of file
diff --git a/bundles/org.eclipse.compare.core/build.properties b/bundles/org.eclipse.compare.core/build.properties
new file mode 100644
index 000000000..e70ba7acf
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/build.properties
@@ -0,0 +1,17 @@
+###############################################################################
+# 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
+###############################################################################
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ plugin.properties,\
+ about.html
+src.includes = about.html
diff --git a/bundles/org.eclipse.compare.core/plugin.properties b/bundles/org.eclipse.compare.core/plugin.properties
new file mode 100644
index 000000000..30426cc51
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/plugin.properties
@@ -0,0 +1,15 @@
+###############################################################################
+# Copyright (c) 2008, 2009 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
+###############################################################################
+#
+# Resource strings for Compare Core Plug-in
+#
+pluginName = Core Compare Support
+providerName = Eclipse.org \ No newline at end of file
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/ComparePlugin.java b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/ComparePlugin.java
new file mode 100644
index 000000000..fb52f0184
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/ComparePlugin.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.internal.core;
+
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Plugin;
+import org.eclipse.core.runtime.Status;
+import org.osgi.framework.BundleContext;
+
+/**
+ * The activator class controls the plug-in life cycle
+ */
+public class ComparePlugin extends Plugin {
+
+ // The plug-in ID
+ public static final String PLUGIN_ID = "org.eclipse.compare.core"; //$NON-NLS-1$
+
+ // The shared instance
+ private static ComparePlugin plugin;
+
+ private boolean cappingDisabled;
+
+ /**
+ * The constructor
+ */
+ public ComparePlugin() {
+ // nothing to do
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.core.runtime.Plugins#start(org.osgi.framework.BundleContext)
+ */
+ public void start(BundleContext context) throws Exception {
+ super.start(context);
+ plugin = this;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.eclipse.core.runtime.Plugin#stop(org.osgi.framework.BundleContext)
+ */
+ public void stop(BundleContext context) throws Exception {
+ plugin = null;
+ super.stop(context);
+ }
+
+ /**
+ * Returns the shared instance
+ *
+ * @return the shared instance
+ */
+ public static ComparePlugin getDefault() {
+ return plugin;
+ }
+
+ public static void log(Throwable e) {
+ log(new Status(IStatus.ERROR, PLUGIN_ID, 0, Messages.Activator_1, e));
+ }
+
+ public static void log(IStatus status) {
+ getDefault().getLog().log(status);
+ }
+
+ public void setCappingDisabled(boolean disable) {
+ this.cappingDisabled = disable;
+ }
+
+ public boolean isCappingDisabled() {
+ return this.cappingDisabled;
+ }
+
+}
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/LCS.java b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/LCS.java
new file mode 100644
index 000000000..a56285ccb
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/LCS.java
@@ -0,0 +1,461 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.internal.core;
+
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.SubMonitor;
+
+
+/* Used to determine the change set responsible for each line */
+public abstract class LCS {
+ public static final double TOO_LONG = 10000000.0; // the value of N*M when
+ // to start binding the
+ // run time
+
+ private static final double POW_LIMIT = 1.5; // limit the time to
+ // D^POW_LIMIT
+
+ private int max_differences; // the maximum number of differences from
+ // each end to consider
+
+ private int length;
+
+ /**
+ * Myers' algorithm for longest common subsequence. O((M + N)D) worst case
+ * time, O(M + N + D^2) expected time, O(M + N) space
+ * (http://citeseer.ist.psu.edu/myers86ond.html)
+ *
+ * Note: Beyond implementing the algorithm as described in the paper I have
+ * added diagonal range compression which helps when finding the LCS of a
+ * very long and a very short sequence, also bound the running time to (N +
+ * M)^1.5 when both sequences are very long.
+ *
+ * After this method is called, the longest common subsequence is available
+ * by calling getResult() where result[0] is composed of
+ * entries from l1 and result[1] is composed of entries from l2
+ * @param subMonitor
+ */
+ public void longestCommonSubsequence(SubMonitor subMonitor) {
+ int length1 = getLength1();
+ int length2 = getLength2();
+ if (length1 == 0 || length2 == 0) {
+ this.length = 0;
+ return;
+ }
+
+ this.max_differences = (length1 + length2 + 1) / 2; // ceil((N+M)/2)
+ if (!isCappingDisabled() && (double) length1 * (double) length2 > TOO_LONG) {
+ // limit complexity to D^POW_LIMIT for long sequences
+ this.max_differences = (int) Math.pow(this.max_differences, POW_LIMIT - 1.0);
+ }
+
+ initializeLcs(length1);
+
+ subMonitor.beginTask(null, length1);
+
+ /*
+ * The common prefixes and suffixes are always part of some LCS, include
+ * them now to reduce our search space
+ */
+ int forwardBound;
+ int max = Math.min(length1, length2);
+ for (forwardBound = 0; forwardBound < max
+ && isRangeEqual(forwardBound, forwardBound); forwardBound++) {
+ setLcs(forwardBound, forwardBound);
+ worked(subMonitor, 1);
+ }
+
+ int backBoundL1 = length1 - 1;
+ int backBoundL2 = length2 - 1;
+
+ while (backBoundL1 >= forwardBound && backBoundL2 >= forwardBound
+ && isRangeEqual(backBoundL1, backBoundL2)) {
+ setLcs(backBoundL1, backBoundL2);
+ backBoundL1--;
+ backBoundL2--;
+ worked(subMonitor, 1);
+ }
+
+ this.length = forwardBound
+ + length1
+ - backBoundL1
+ - 1
+ + lcs_rec(forwardBound, backBoundL1, forwardBound,
+ backBoundL2, new int[2][length1 + length2 + 1],
+ new int[3], subMonitor);
+
+ }
+
+ private boolean isCappingDisabled() {
+ return ComparePlugin.getDefault().isCappingDisabled();
+ }
+
+ /**
+ * The recursive helper function for Myers' LCS. Computes the LCS of
+ * l1[bottoml1 .. topl1] and l2[bottoml2 .. topl2] fills in the appropriate
+ * location in lcs and returns the length
+ *
+ * @param l1 The 1st sequence
+ * @param bottoml1 Index in the 1st sequence to start from (inclusive)
+ * @param topl1 Index in the 1st sequence to end on (inclusive)
+ * @param l2 The 2nd sequence
+ * @param bottoml2 Index in the 2nd sequence to start from (inclusive)
+ * @param topl2 Index in the 2nd sequence to end on (inclusive)
+ * @param V should be allocated as int[2][l1.length + l2.length + 1], used
+ * to store furthest reaching D-paths
+ * @param snake should be allocated as int[3], used to store the beginning
+ * x, y coordinates and the length of the latest snake traversed
+ * @param subMonitor
+ * @param lcs should be allocated as TextLine[2][l1.length], used to store
+ * the common points found to be part of the LCS where lcs[0]
+ * references lines of l1 and lcs[1] references lines of l2.
+ *
+ * @return the length of the LCS
+ */
+ private int lcs_rec(
+ int bottoml1, int topl1,
+ int bottoml2, int topl2,
+ int[][] V, int[] snake, SubMonitor subMonitor) {
+
+ // check that both sequences are non-empty
+ if (bottoml1 > topl1 || bottoml2 > topl2) {
+ return 0;
+ }
+
+ int d = find_middle_snake(bottoml1, topl1, bottoml2, topl2, V, snake, subMonitor);
+ // System.out.println(snake[0] + " " + snake[1] + " " + snake[2]);
+
+ // need to store these so we don't lose them when they're overwritten by
+ // the recursion
+ int len = snake[2];
+ int startx = snake[0];
+ int starty = snake[1];
+
+ // the middle snake is part of the LCS, store it
+ for (int i = 0; i < len; i++) {
+ setLcs(startx + i, starty + i);
+ worked(subMonitor, 1);
+ }
+
+ if (d > 1) {
+ return len
+ + lcs_rec(bottoml1, startx - 1, bottoml2, starty - 1, V, snake, subMonitor)
+ + lcs_rec(startx + len, topl1, starty + len, topl2, V, snake, subMonitor);
+ } else if (d == 1) {
+ /*
+ * In this case the sequences differ by exactly 1 line. We have
+ * already saved all the lines after the difference in the for loop
+ * above, now we need to save all the lines before the difference.
+ */
+ int max = Math.min(startx - bottoml1, starty - bottoml2);
+ for (int i = 0; i < max; i++) {
+ setLcs(bottoml1 + i, bottoml2 + i);
+ worked(subMonitor, 1);
+ }
+ return max + len;
+ }
+
+ return len;
+ }
+
+ private void worked(SubMonitor subMonitor, int work) {
+ if (subMonitor.isCanceled())
+ throw new OperationCanceledException();
+ subMonitor.worked(work);
+ }
+
+ /**
+ * Helper function for Myers' LCS algorithm to find the middle snake for
+ * l1[bottoml1..topl1] and l2[bottoml2..topl2] The x, y coodrdinates of the
+ * start of the middle snake are saved in snake[0], snake[1] respectively
+ * and the length of the snake is saved in s[2].
+ *
+ * @param l1 The 1st sequence
+ * @param bottoml1 Index in the 1st sequence to start from (inclusive)
+ * @param topl1 Index in the 1st sequence to end on (inclusive)
+ * @param l2 The 2nd sequence
+ * @param bottoml2 Index in the 2nd sequence to start from (inclusive)
+ * @param topl2 Index in the 2nd sequence to end on (inclusive)
+ * @param V should be allocated as int[2][l1.length + l2.length + 1], used
+ * to store furthest reaching D-paths
+ * @param snake should be allocated as int[3], used to store the beginning
+ * x, y coordinates and the length of the middle snake
+ * @subMonitor subMonitor
+ *
+ * @return The number of differences (SES) between l1[bottoml1..topl1] and
+ * l2[bottoml2..topl2]
+ */
+ private int find_middle_snake(
+ int bottoml1, int topl1,
+ int bottoml2, int topl2,
+ int[][] V, int[] snake,
+ SubMonitor subMonitor) {
+ int N = topl1 - bottoml1 + 1;
+ int M = topl2 - bottoml2 + 1;
+ // System.out.println("N: " + N + " M: " + M + " bottom: " + bottoml1 +
+ // ", " +
+ // bottoml2 + " top: " + topl1 + ", " + topl2);
+ int delta = N - M;
+ boolean isEven;
+ if ((delta & 1) == 1) {
+ isEven = false;
+ } else {
+ isEven = true;
+ }
+
+ int limit = Math.min(this.max_differences, (N + M + 1) / 2); // ceil((N+M)/2)
+
+ int value_to_add_forward; // a 0 or 1 that we add to the start offset
+ // to make it odd/even
+ if ((M & 1) == 1) {
+ value_to_add_forward = 1;
+ } else {
+ value_to_add_forward = 0;
+ }
+
+ int value_to_add_backward;
+ if ((N & 1) == 1) {
+ value_to_add_backward = 1;
+ } else {
+ value_to_add_backward = 0;
+ }
+
+ int start_forward = -M;
+ int end_forward = N;
+ int start_backward = -N;
+ int end_backward = M;
+
+ V[0][limit + 1] = 0;
+ V[1][limit - 1] = N;
+ for (int d = 0; d <= limit; d++) {
+
+ int start_diag = Math.max(value_to_add_forward + start_forward, -d);
+ int end_diag = Math.min(end_forward, d);
+ value_to_add_forward = 1 - value_to_add_forward;
+
+ // compute forward furthest reaching paths
+ for (int k = start_diag; k <= end_diag; k += 2) {
+ int x;
+ if (k == -d
+ || (k < d && V[0][limit + k - 1] < V[0][limit + k + 1])) {
+ x = V[0][limit + k + 1];
+ } else {
+ x = V[0][limit + k - 1] + 1;
+ }
+
+ int y = x - k;
+
+ snake[0] = x + bottoml1;
+ snake[1] = y + bottoml2;
+ snake[2] = 0;
+ // System.out.println("1 x: " + x + " y: " + y + " k: " + k + "
+ // d: " + d );
+ while (x < N && y < M
+ && isRangeEqual(x + bottoml1, y + bottoml2)) {
+ x++;
+ y++;
+ snake[2]++;
+ }
+ V[0][limit + k] = x;
+ // System.out.println(x + " " + V[1][limit+k -delta] + " " + k +
+ // " " + delta);
+ if (!isEven && k >= delta - d + 1 && k <= delta + d - 1
+ && x >= V[1][limit + k - delta]) {
+ // System.out.println("Returning: " + (2*d-1));
+ return 2 * d - 1;
+ }
+
+ // check to see if we can cut down the diagonal range
+ if (x >= N && end_forward > k - 1) {
+ end_forward = k - 1;
+ } else if (y >= M) {
+ start_forward = k + 1;
+ value_to_add_forward = 0;
+ }
+ }
+
+ start_diag = Math.max(value_to_add_backward + start_backward, -d);
+ end_diag = Math.min(end_backward, d);
+ value_to_add_backward = 1 - value_to_add_backward;
+
+ // compute backward furthest reaching paths
+ for (int k = start_diag; k <= end_diag; k += 2) {
+ int x;
+ if (k == d
+ || (k != -d && V[1][limit + k - 1] < V[1][limit + k + 1])) {
+ x = V[1][limit + k - 1];
+ } else {
+ x = V[1][limit + k + 1] - 1;
+ }
+
+ int y = x - k - delta;
+
+ snake[2] = 0;
+ // System.out.println("2 x: " + x + " y: " + y + " k: " + k + "
+ // d: " + d);
+ while (x > 0 && y > 0
+ && isRangeEqual(x - 1 + bottoml1, y - 1 + bottoml2)) {
+ x--;
+ y--;
+ snake[2]++;
+ }
+ V[1][limit + k] = x;
+
+ if (isEven && k >= -delta - d && k <= d - delta
+ && x <= V[0][limit + k + delta]) {
+ // System.out.println("Returning: " + 2*d);
+ snake[0] = bottoml1 + x;
+ snake[1] = bottoml2 + y;
+
+ return 2 * d;
+ }
+
+ // check to see if we can cut down our diagonal range
+ if (x <= 0) {
+ start_backward = k + 1;
+ value_to_add_backward = 0;
+ } else if (y <= 0 && end_backward > k - 1) {
+ end_backward = k - 1;
+ }
+ }
+ worked(subMonitor, 1);
+ }
+
+ /*
+ * computing the true LCS is too expensive, instead find the diagonal
+ * with the most progress and pretend a midle snake of length 0 occurs
+ * there.
+ */
+
+ int[] most_progress = findMostProgress(M, N, limit, V);
+
+ snake[0] = bottoml1 + most_progress[0];
+ snake[1] = bottoml2 + most_progress[1];
+ snake[2] = 0;
+ return 5; /*
+ * HACK: since we didn't really finish the LCS computation
+ * we don't really know the length of the SES. We don't do
+ * anything with the result anyway, unless it's <=1. We know
+ * for a fact SES > 1 so 5 is as good a number as any to
+ * return here
+ */
+ }
+
+ /**
+ * Takes the array with furthest reaching D-paths from an LCS computation
+ * and returns the x,y coordinates and progress made in the middle diagonal
+ * among those with maximum progress, both from the front and from the back.
+ *
+ * @param M the length of the 1st sequence for which LCS is being computed
+ * @param N the length of the 2nd sequence for which LCS is being computed
+ * @param limit the number of steps made in an attempt to find the LCS from
+ * the front and back
+ * @param V the array storing the furthest reaching D-paths for the LCS
+ * computation
+ * @return The result as an array of 3 integers where result[0] is the x
+ * coordinate of the current location in the diagonal with the most
+ * progress, result[1] is the y coordinate of the current location
+ * in the diagonal with the most progress and result[2] is the
+ * amount of progress made in that diagonal
+ */
+ private static int[] findMostProgress(int M, int N, int limit, int[][] V) {
+ int delta = N - M;
+
+ int forward_start_diag;
+ if ((M & 1) == (limit & 1)) {
+ forward_start_diag = Math.max(-M, -limit);
+ } else {
+ forward_start_diag = Math.max(1 - M, -limit);
+ }
+
+ int forward_end_diag = Math.min(N, limit);
+
+ int backward_start_diag;
+ if ((N & 1) == (limit & 1)) {
+ backward_start_diag = Math.max(-N, -limit);
+ } else {
+ backward_start_diag = Math.max(1 - N, -limit);
+ }
+
+ int backward_end_diag = Math.min(M, limit);
+
+ int[][] max_progress = new int[Math.max(forward_end_diag
+ - forward_start_diag, backward_end_diag - backward_start_diag) / 2 + 1][3];
+ int num_progress = 0; // the 1st entry is current, it is initialized
+ // with 0s
+
+ // first search the forward diagonals
+ for (int k = forward_start_diag; k <= forward_end_diag; k += 2) {
+ int x = V[0][limit + k];
+ int y = x - k;
+ if (x > N || y > M) {
+ continue;
+ }
+
+ int progress = x + y;
+ if (progress > max_progress[0][2]) {
+ num_progress = 0;
+ max_progress[0][0] = x;
+ max_progress[0][1] = y;
+ max_progress[0][2] = progress;
+ } else if (progress == max_progress[0][2]) {
+ num_progress++;
+ max_progress[num_progress][0] = x;
+ max_progress[num_progress][1] = y;
+ max_progress[num_progress][2] = progress;
+ }
+ }
+
+ boolean max_progress_forward = true; // initially the maximum
+ // progress is in the forward
+ // direction
+
+ // now search the backward diagonals
+ for (int k = backward_start_diag; k <= backward_end_diag; k += 2) {
+ int x = V[1][limit + k];
+ int y = x - k - delta;
+ if (x < 0 || y < 0) {
+ continue;
+ }
+
+ int progress = N - x + M - y;
+ if (progress > max_progress[0][2]) {
+ num_progress = 0;
+ max_progress_forward = false;
+ max_progress[0][0] = x;
+ max_progress[0][1] = y;
+ max_progress[0][2] = progress;
+ } else if (progress == max_progress[0][2] && !max_progress_forward) {
+ num_progress++;
+ max_progress[num_progress][0] = x;
+ max_progress[num_progress][1] = y;
+ max_progress[num_progress][2] = progress;
+ }
+ }
+
+ // return the middle diagonal with maximal progress.
+ return max_progress[num_progress / 2];
+ }
+
+ protected abstract int getLength2();
+
+ protected abstract int getLength1();
+
+ protected abstract boolean isRangeEqual(int i1, int i2);
+
+ protected abstract void setLcs(int sl1, int sl2);
+
+ protected abstract void initializeLcs(int lcsLength);
+
+ public int getLength() {
+ return this.length;
+ }
+}
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/Messages.java b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/Messages.java
new file mode 100644
index 000000000..ba5caeea6
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/Messages.java
@@ -0,0 +1,39 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.internal.core;
+
+import org.eclipse.osgi.util.NLS;
+
+public class Messages extends NLS {
+ private static final String BUNDLE_NAME = "org.eclipse.compare.internal.core.messages"; //$NON-NLS-1$
+ public static String Activator_1;
+ public static String FileDiffResult_0;
+ public static String FileDiffResult_1;
+ public static String FileDiffResult_2;
+ public static String FileDiffResult_3;
+ public static String Patcher_0;
+ public static String Patcher_1;
+ public static String Patcher_2;
+ public static String WorkspacePatcher_0;
+ public static String WorkspacePatcher_1;
+ public static String RangeComparatorLCS_0;
+ static {
+ // initialize resource bundle
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+
+ /*
+ * (non Javadoc) Cannot be instantiated.
+ */
+ private Messages() {
+ // nothing to do
+ }
+}
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/TextLineLCS.java b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/TextLineLCS.java
new file mode 100644
index 000000000..c5178ca20
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/TextLineLCS.java
@@ -0,0 +1,207 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.internal.core;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class TextLineLCS extends LCS {
+
+ private final TextLine[] lines1;
+ private final TextLine[] lines2;
+ private TextLine[][] lcs;
+
+ public TextLineLCS(TextLine[] lines1, TextLine[] lines2) {
+ this.lines1 = lines1;
+ this.lines2 = lines2;
+ }
+
+ public TextLine[][] getResult() {
+ int length = getLength();
+ if (length == 0)
+ return new TextLine[2][0];
+ TextLine[][] result = new TextLine[2][];
+
+ // compact and shift the result
+ result[0] = compactAndShiftLCS(this.lcs[0], length, this.lines1);
+ result[1] = compactAndShiftLCS(this.lcs[1], length, this.lines2);
+
+ return result;
+ }
+
+ protected int getLength2() {
+ return this.lines2.length;
+ }
+
+ protected int getLength1() {
+ return this.lines1.length;
+ }
+
+ protected boolean isRangeEqual(int i1, int i2) {
+ return this.lines1[i1].sameText(this.lines2[i2]);
+ }
+
+ protected void setLcs(int sl1, int sl2) {
+ this.lcs[0][sl1] = this.lines1[sl1];
+ this.lcs[1][sl1] = this.lines2[sl2];
+ }
+
+ protected void initializeLcs(int length) {
+ this.lcs = new TextLine[2][length];
+ }
+
+ /**
+ * This method takes an lcs result interspersed with nulls, compacts it and
+ * shifts the LCS chunks as far towards the front as possible. This tends to
+ * produce good results most of the time.
+ *
+ * TODO: investigate what to do about comments. shifting either up or down
+ * hurts them
+ *
+ * @param lcsSide A subsequence of original, presumably it is the LCS of it and
+ * some other collection of lines
+ * @param len The number of non-null entries in lcs
+ * @param original The original sequence of lines of which lcs is a
+ * subsequence
+ *
+ * @return The subsequence lcs compacted and chunks shifted towards the
+ * front
+ */
+ private TextLine[] compactAndShiftLCS(TextLine[] lcsSide, int len,
+ TextLine[] original) {
+ TextLine[] result = new TextLine[len];
+
+ if (len == 0) {
+ return result;
+ }
+
+ int j = 0;
+
+ while (lcsSide[j] == null) {
+ j++;
+ }
+
+ result[0] = lcsSide[j];
+ j++;
+
+ for (int i = 1; i < len; i++) {
+ while (lcsSide[j] == null) {
+ j++;
+ }
+
+ if (original[result[i - 1].lineNumber() + 1].sameText(lcsSide[j])) {
+ result[i] = original[result[i - 1].lineNumber() + 1];
+ } else {
+ result[i] = lcsSide[j];
+ }
+ j++;
+ }
+
+ return result;
+ }
+
+ /**
+ * Breaks the given text up into lines and returns an array of TextLine
+ * objects each corresponding to a single line, ordered according to the
+ * line number. That is result[i].lineNumber() == i and is the i'th line in
+ * text (starting from 0) Note: there are 1 more lines than there are
+ * newline characters in text. Corollary 1: if the last character is
+ * newline, the last line is empty Corollary 2: the empty string is 1 line
+ *
+ * @param text The text to extract lines from
+ * @return the array of TextLine object each corresponding to a line of text
+ */
+ public static TextLine[] getTextLines(String text) {
+ List lines = new ArrayList();
+ int begin = 0;
+ int end = getEOL(text, 0);
+ int lineNum = 0;
+ while (end != -1) {
+ lines.add(new TextLine(lineNum++, text.substring(begin, end)));
+ begin = end + 1;
+ end = getEOL(text, begin);
+ if (end == begin && text.charAt(begin - 1) == '\r'
+ && text.charAt(begin) == '\n') {
+ // we have '\r' followed by '\n', skip it
+ begin = end + 1;
+ end = getEOL(text, begin);
+ }
+ }
+
+ /*
+ * this is the last line, no more newline characters, so take the rest
+ * of the string
+ */
+ lines.add(new TextLine(lineNum, text.substring(begin)));
+ TextLine[] aLines = new TextLine[lines.size()];
+ lines.toArray(aLines);
+ return aLines;
+ }
+
+ /**
+ * Returns the index of the next end of line marker ('\n' or '\r') after
+ * start
+ *
+ * @param text The string to examine
+ * @param start The location in the string to start looking
+ * @return the index such that text.charAt(index) == '\n' or '\r', -1 if not
+ * found
+ */
+ private static int getEOL(String text, int start) {
+ int max = text.length();
+ for (int i = start; i < max; i++) {
+ char c = text.charAt(i);
+ if (c == '\n' || c == '\r') {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /* used to store information about a single line of text */
+ public static class TextLine {
+ private int number; // the line number
+
+ private String text; // the actual text
+
+ public TextLine(int number, String text) {
+ this.number = number;
+ this.text = text;
+ }
+
+ /**
+ * Compares this TextLine to l and returns true if they have the same
+ * text
+ *
+ * @param l the TextLine to compare to
+ * @return true if this and l have the same text
+ */
+ public boolean sameText(TextLine l) {
+ // compare the hashCode() first since that is much faster and most
+ // of the time the text lines won't match
+ return this.text.hashCode() == l.text.hashCode() && l.text.equals(this.text);
+ }
+
+ /**
+ * Returns the line number of this line
+ *
+ * @return the line number
+ */
+ public int lineNumber() {
+ return this.number;
+ }
+
+ public String toString() {
+ return "" + this.number + " " + this.text + "\n"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ }
+}
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/messages.properties b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/messages.properties
new file mode 100644
index 000000000..8e478831d
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/messages.properties
@@ -0,0 +1,22 @@
+###############################################################################
+# 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
+###############################################################################
+
+Activator_1=Internal error
+FileDiffResult_0=(file already exists)
+FileDiffResult_1=(file does not exist)
+FileDiffResult_2={0} {1}
+FileDiffResult_3={0} (hunk \#{1})
+Patcher_0=Patching
+Patcher_1=Rejected patch
+Patcher_2=Guessing Fuzz Factor...
+WorkspacePatcher_0=Patching
+WorkspacePatcher_1=Rejected patch
+RangeComparatorLCS_0=Finding differences
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/DiffProject.java b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/DiffProject.java
new file mode 100644
index 000000000..630256dbe
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/DiffProject.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.internal.core.patch;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A diff project represents a project that was read from a workspace patch.
+ * It contains the set of file diffs that were associated with the project
+ * in the patch file.
+ */
+public class DiffProject {
+
+ private String project;
+ private Set fDiffs= new HashSet();
+
+ /**
+ * Create a diff project for the given workspace project.
+ * @param project a workspace project
+ */
+ public DiffProject(String project) {
+ this.project= project;
+ }
+
+ /**
+ * Add the file diff to this project.
+ * @param diff the file diff.
+ */
+ public void add(FilePatch2 diff) {
+ this.fDiffs.add(diff);
+ if (diff.getProject() != this)
+ diff.setProject(this);
+ }
+
+ /**
+ * Return the name of this project.
+ * @return the name of this project
+ */
+ public String getName() {
+ return this.project;
+ }
+
+ /**
+ * Remove the file diff from this project.
+ * @param diff the diff to be removed
+ */
+ public void remove(FilePatch2 diff) {
+ this.fDiffs.remove(diff);
+ }
+
+ /**
+ * Return whether this project contains the given diff.
+ * @param diff a file diff
+ * @return whether this project contains the given diff
+ */
+ public boolean contains(FilePatch2 diff) {
+ return this.fDiffs.contains(diff);
+ }
+
+ /**
+ * Return the file diffs associated with this project.
+ * @return the file diffs associated with this project
+ */
+ public FilePatch2[] getFileDiffs() {
+ return (FilePatch2[]) this.fDiffs.toArray(new FilePatch2[this.fDiffs.size()]);
+ }
+}
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/FileDiffResult.java b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/FileDiffResult.java
new file mode 100644
index 000000000..3410270ac
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/FileDiffResult.java
@@ -0,0 +1,338 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.internal.core.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.ComparePlugin;
+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.compare.patch.ReaderCreator;
+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 FilePatch2 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(FilePatch2 diff, PatchConfiguration configuration) {
+ super();
+ this.fDiff = diff;
+ this.configuration = configuration;
+ }
+
+ public PatchConfiguration getConfiguration() {
+ return this.configuration;
+ }
+
+ public boolean canApplyHunk(Hunk hunk) {
+ HunkResult result = getHunkResult(hunk);
+ return result.isOK() && !this.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 content the contents being patched or <code>null</code> for an addition
+ * @param monitor a progress monitor or <code>null</code> if no progress monitoring is desired
+ */
+ public void refresh(ReaderCreator content, IProgressMonitor monitor) {
+ this.fMatches= false;
+ this.fDiffProblem= false;
+ boolean create= false;
+ this.charset = Utilities.getCharset(content);
+ //If this diff is an addition, make sure that it doesn't already exist
+ boolean exists = targetExists(content);
+ if (this.fDiff.getDiffType(getConfiguration().isReversed()) == FilePatch2.ADDITION) {
+ if ((!exists || isEmpty(content)) && canCreateTarget(content)) {
+ this.fMatches= true;
+ } else {
+ // file already exists
+ this.fDiffProblem= true;
+ this.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) {
+ this.fMatches= true;
+ } else {
+ // file doesn't exist
+ this.fDiffProblem= true;
+ this.fErrorMessage= Messages.FileDiffResult_1;
+ }
+ }
+
+ if (this.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
+ this.fBeforeLines = new ArrayList(getLines(content, false));
+ this.fAfterLines = this.fMatches ? new ArrayList() : this.fBeforeLines;
+ IHunk[] hunks = this.fDiff.getHunks();
+ for (int i = 0; i < hunks.length; i++) {
+ Hunk hunk = (Hunk) hunks[i];
+ hunk.setCharset(getCharset());
+ HunkResult result = getHunkResult(hunk);
+ result.setMatches(false);
+ }
+ } else {
+ // If this diff has no problems discovered so far, try applying the patch
+ patch(getLines(content, create), monitor);
+ }
+
+ if (containsProblems()) {
+ if (this.fMatches) {
+ // Check to see if we have at least one hunk that matches
+ this.fMatches = false;
+ IHunk[] hunks = this.fDiff.getHunks();
+ for (int i = 0; i < hunks.length; i++) {
+ Hunk hunk = (Hunk) hunks[i];
+ HunkResult result = getHunkResult(hunk);
+ if (result.isOK()) {
+ this.fMatches = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ protected boolean canCreateTarget(ReaderCreator content) {
+ return true;
+ }
+
+ protected boolean targetExists(ReaderCreator content) {
+ return content != null && content.canCreateReader();
+ }
+
+ protected List getLines(ReaderCreator content, boolean create) {
+ List lines = LineReader.load(content, create);
+ return lines;
+ }
+
+ protected boolean isEmpty(ReaderCreator content) {
+ if (content == null)
+ return true;
+ return LineReader.load(content, 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) {
+ this.fBeforeLines = new ArrayList();
+ this.fBeforeLines.addAll(lines);
+ if (getConfiguration().getFuzz() != 0) {
+ calculateFuzz(this.fBeforeLines, monitor);
+ }
+ int shift= 0;
+ IHunk[] hunks = this.fDiff.getHunks();
+ for (int i = 0; i < hunks.length; i++) {
+ Hunk hunk = (Hunk) hunks[i];
+ hunk.setCharset(getCharset());
+ HunkResult result = getHunkResult(hunk);
+ result.setShift(shift);
+ if (result.patch(lines)) {
+ shift = result.getShift();
+ }
+ }
+ this.fAfterLines = lines;
+ }
+
+ public boolean getDiffProblem() {
+ return this.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 (this.fDiffProblem)
+ return true;
+ for (Iterator iterator = this.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, this.fErrorMessage});
+ return label;
+ }
+
+ public boolean hasMatches() {
+ return this.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 this.fAfterLines;
+ }
+
+ /**
+ * Calculate the fuzz factor that will allow the most hunks to be matched.
+ * @param lines the lines of the target file
+ * @param monitor a progress monitor
+ * @return the fuzz factor or <code>-1</code> if no hunks could be matched
+ */
+ public int calculateFuzz(List lines, IProgressMonitor monitor) {
+ if (monitor == null)
+ monitor = new NullProgressMonitor();
+ this.fBeforeLines = new ArrayList(lines);
+ // TODO: What about deletions?
+ if (this.fDiff.getDiffType(getConfiguration().isReversed()) == FilePatch2.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$
+ IHunk[] hunks = this.fDiff.getHunks();
+ for (int j = 0; j < hunks.length; j++) {
+ Hunk h = (Hunk) 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);
+ }
+ this.fAfterLines = lines;
+ return highestFuzz;
+ }
+
+ public IPath getTargetPath() {
+ return this.fDiff.getStrippedPath(getConfiguration().getPrefixSegmentStripCount(), getConfiguration().isReversed());
+ }
+
+ private HunkResult getHunkResult(Hunk hunk) {
+ HunkResult result = (HunkResult)this.fHunkResults.get(hunk);
+ if (result == null) {
+ result = new HunkResult(this, hunk);
+ this.fHunkResults .put(hunk, result);
+ }
+ return result;
+ }
+
+ public List getFailedHunks() {
+ List failedHunks = new ArrayList();
+ IHunk[] hunks = this.fDiff.getHunks();
+ for (int i = 0; i < hunks.length; i++) {
+ HunkResult result = (HunkResult) this.fHunkResults.get(hunks[i]);
+ if (result != null && !result.isOK())
+ failedHunks.add(result.getHunk());
+ }
+ return failedHunks;
+ }
+
+ public FilePatch2 getDiff() {
+ return this.fDiff;
+ }
+
+ public List getBeforeLines() {
+ return this.fBeforeLines;
+ }
+
+ public List getAfterLines() {
+ return this.fAfterLines;
+ }
+
+ public HunkResult[] getHunkResults() {
+ // return hunk results in the same order as hunks are placed in file diff
+ List results = new ArrayList();
+ IHunk[] hunks = this.fDiff.getHunks();
+ for (int i = 0; i < hunks.length; i++) {
+ HunkResult result = (HunkResult) this.fHunkResults.get(hunks[i]);
+ if (result != null) {
+ results.add(result);
+ }
+ }
+ return (HunkResult[]) results.toArray(new HunkResult[results.size()]);
+ }
+
+ public InputStream getOriginalContents() {
+ String contents = LineReader.createString(isPreserveLineDelimeters(), getBeforeLines());
+ return asInputStream(contents, getCharset());
+ }
+
+ public InputStream getPatchedContents() {
+ String contents = LineReader.createString(isPreserveLineDelimeters(), getLines());
+ return asInputStream(contents, getCharset());
+ }
+
+ public String getCharset() {
+ return this.charset;
+ }
+
+ public boolean isPreserveLineDelimeters() {
+ return false;
+ }
+
+ public IHunk[] getRejects() {
+ List failedHunks = getFailedHunks();
+ return (IHunk[]) failedHunks.toArray(new IHunk[failedHunks.size()]);
+ }
+
+ public boolean hasRejects() {
+ return getFailedHunks().size() > 0;
+ }
+
+ public static InputStream asInputStream(String contents, String charSet) {
+ byte[] bytes = null;
+ if (charSet != null) {
+ try {
+ bytes = contents.getBytes(charSet);
+ } catch (UnsupportedEncodingException e) {
+ ComparePlugin.log(e);
+ }
+ }
+ if (bytes == null) {
+ bytes = contents.getBytes();
+ }
+ return new ByteArrayInputStream(bytes);
+ }
+
+}
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/FilePatch2.java b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/FilePatch2.java
new file mode 100644
index 000000000..c1b569df6
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/FilePatch2.java
@@ -0,0 +1,266 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.internal.core.patch;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.compare.patch.IFilePatch2;
+import org.eclipse.compare.patch.IFilePatchResult;
+import org.eclipse.compare.patch.IHunk;
+import org.eclipse.compare.patch.PatchConfiguration;
+import org.eclipse.compare.patch.ReaderCreator;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.Path;
+
+/**
+ * A file diff represents a set of hunks that were associated with the
+ * same path in a patch file.
+ */
+public class FilePatch2 implements IFilePatch2 {
+
+ /**
+ * Difference constant (value 1) indicating one side was added.
+ */
+ public static final int ADDITION= 1;
+ /**
+ * Difference constant (value 2) indicating one side was removed.
+ */
+ public static final int DELETION= 2;
+ /**
+ * Difference constant (value 3) indicating side changed.
+ */
+ public static final int CHANGE= 3;
+
+ private IPath fOldPath, fNewPath;
+ private long oldDate, newDate;
+ private List fHunks= new ArrayList();
+ private DiffProject fProject; //the project that contains this diff
+ private String header;
+ private int addedLines, removedLines;
+
+ /**
+ * Create a file diff for the given path and date information.
+ * @param oldPath the path of the before state of the file
+ * @param oldDate the timestamp of the before state
+ * @param newPath the path of the after state
+ * @param newDate the timestamp of the after state
+ */
+ public FilePatch2(IPath oldPath, long oldDate, IPath newPath, long newDate) {
+ this.fOldPath= oldPath;
+ this.oldDate = oldDate;
+ this.fNewPath= newPath;
+ this.newDate = newDate;
+ }
+
+ /**
+ * Return the parent project or <code>null</code> if there isn't one.
+ * @return the parent project or <code>null</code>
+ */
+ public DiffProject getProject() {
+ return this.fProject;
+ }
+
+ /**
+ * Set the project of this diff to the given project.
+ * This method should only be called from
+ * {@link DiffProject#add(FilePatch2)}
+ * @param diffProject the parent project
+ */
+ void setProject(DiffProject diffProject) {
+ if (this.fProject == diffProject)
+ return;
+ if (this.fProject != null)
+ this.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 this.fOldPath;
+ return this.fNewPath;
+ }
+ if (reverse && this.fNewPath != null)
+ return this.fNewPath;
+ if (this.fOldPath != null)
+ return this.fOldPath;
+ return this.fNewPath;
+ }
+
+ /**
+ * Add the hunk to this file diff.
+ * @param hunk the hunk
+ */
+ public void add(Hunk hunk) {
+ this.fHunks.add(hunk);
+ hunk.setParent(this);
+ }
+
+ /**
+ * Remove the hunk from this file diff
+ * @param hunk the hunk
+ */
+ protected void remove(Hunk hunk) {
+ this.fHunks.remove(hunk);
+ }
+
+ /**
+ * Return the hunks associated with this file diff.
+ * @return the hunks associated with this file diff
+ */
+ public IHunk[] getHunks() {
+ return (IHunk[]) this.fHunks.toArray(new IHunk[this.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 this.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 (this.fHunks.size() == 1) {
+ boolean add = false;
+ boolean delete = false;
+ Iterator iter = this.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
+ */
+ public 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 (this.fOldPath != null)
+ length= Math.min(length, this.fOldPath.segmentCount());
+ if (this.fNewPath != null)
+ length= Math.min(length, this.fNewPath.segmentCount());
+ return length;
+ }
+
+ public IFilePatchResult apply(ReaderCreator content,
+ PatchConfiguration configuration, IProgressMonitor monitor) {
+ FileDiffResult result = new FileDiffResult(this, configuration);
+ result.refresh(content, monitor);
+ return result;
+ }
+
+ public IPath getTargetPath(PatchConfiguration configuration) {
+ return getStrippedPath(configuration.getPrefixSegmentStripCount(), configuration.isReversed());
+ }
+
+ public FilePatch2 asRelativeDiff() {
+ if (this.fProject == null)
+ return this;
+ IPath adjustedOldPath = null;
+ if (this.fOldPath != null) {
+ adjustedOldPath = new Path(null, this.fProject.getName()).append(this.fOldPath);
+ }
+ IPath adjustedNewPath = null;
+ if (this.fNewPath != null) {
+ adjustedNewPath = new Path(null, this.fProject.getName()).append(this.fNewPath);
+ }
+ FilePatch2 diff = create(adjustedOldPath, 0, adjustedNewPath, 0);
+ for (Iterator iterator = this.fHunks.iterator(); iterator.hasNext();) {
+ Hunk hunk = (Hunk) iterator.next();
+ // Creating the hunk adds it to the parent diff
+ new Hunk(diff, hunk);
+ }
+ return diff;
+ }
+
+ protected FilePatch2 create(IPath oldPath, long oldDate, IPath newPath,
+ long newDate) {
+ return new FilePatch2(oldPath, oldDate, newPath, newDate);
+ }
+
+ public void setHeader(String header) {
+ this.header = header;
+ }
+
+ public String getHeader() {
+ return this.header;
+ }
+
+ public long getBeforeDate() {
+ return this.oldDate;
+ }
+
+ public long getAfterDate() {
+ return this.newDate;
+ }
+
+ public void setAddedLines(int addedLines) {
+ this.addedLines = addedLines;
+ }
+
+ public void setRemovedLines(int removedLines) {
+ this.removedLines = removedLines;
+ }
+
+ public int getAddedLines() {
+ return this.addedLines;
+ }
+
+ public int getRemovedLines() {
+ return this.removedLines;
+ }
+
+}
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/Hunk.java b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/Hunk.java
new file mode 100644
index 000000000..27ec848c6
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/Hunk.java
@@ -0,0 +1,518 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.internal.core.patch;
+
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.runtime.Assert;
+
+import org.eclipse.compare.patch.IFilePatchResult;
+import org.eclipse.compare.patch.IHunk;
+import org.eclipse.compare.patch.PatchConfiguration;
+
+/**
+ * A Hunk describes a range of changed lines and some context lines.
+ */
+public class Hunk implements IHunk {
+
+ private FilePatch2 fParent;
+ private int fOldStart, fOldLength;
+ private int fNewStart, fNewLength;
+ private String[] fLines;
+ private int hunkType;
+ private String charset = null;
+
+ public static Hunk createHunk(FilePatch2 parent, int[] oldRange, int[] newRange, List lines, boolean hasLineAdditions, boolean hasLineDeletions, boolean hasContextLines) {
+ int oldStart = 0;
+ int oldLength = 0;
+ int newStart = 0;
+ int newLength = 0;
+ if (oldRange[0] > 0)
+ oldStart= oldRange[0]-1; // line number start at 0!
+ else
+ oldStart= 0;
+ oldLength= oldRange[1];
+ if (newRange[0] > 0)
+ newStart= newRange[0]-1; // line number start at 0!
+ else
+ newStart= 0;
+ newLength= newRange[1];
+ int hunkType = FilePatch2.CHANGE;
+ if (!hasContextLines) {
+ if (hasLineAdditions && !hasLineDeletions) {
+ hunkType = FilePatch2.ADDITION;
+ } else if (!hasLineAdditions && hasLineDeletions) {
+ hunkType = FilePatch2.DELETION;
+ }
+ }
+ return new Hunk(parent, hunkType, oldStart, oldLength, newStart, newLength, (String[]) lines.toArray(new String[lines.size()]));
+ }
+
+ public Hunk(FilePatch2 parent, int hunkType, int oldStart, int oldLength,
+ int newStart, int newLength, String[] lines) {
+ this.fParent = parent;
+ if (this.fParent != null) {
+ this.fParent.add(this);
+ }
+ this.hunkType = hunkType;
+ this.fOldLength = oldLength;
+ this.fOldStart = oldStart;
+ this.fNewLength = newLength;
+ this.fNewStart = newStart;
+ this.fLines = lines;
+ }
+
+ public Hunk(FilePatch2 parent, Hunk toCopy) {
+ this(parent, toCopy.hunkType, toCopy.fOldStart, toCopy.fOldLength, toCopy.fNewStart, toCopy.fNewLength, toCopy.fLines);
+ }
+
+ /*
+ * Returns the contents of this hunk.
+ * Each line starts with a control character. Their meaning is as follows:
+ * <ul>
+ * <li>
+ * '+': add the line
+ * <li>
+ * '-': delete the line
+ * <li>
+ * ' ': no change, context line
+ * </ul>
+ */
+ public String getContent() {
+ StringBuffer sb= new StringBuffer();
+ for (int i= 0; i < this.fLines.length; i++) {
+ String line= this.fLines[i];
+ sb.append(line.substring(0, LineReader.length(line)));
+ sb.append('\n');
+ }
+ return sb.toString();
+ }
+
+ /*
+ * Returns a descriptive String for this hunk.
+ * It is in the form old_start,old_length -> new_start,new_length.
+ */
+ String getDescription() {
+ StringBuffer sb= new StringBuffer();
+ sb.append(Integer.toString(this.fOldStart));
+ sb.append(',');
+ sb.append(Integer.toString(this.fOldLength));
+ sb.append(" -> "); //$NON-NLS-1$
+ sb.append(Integer.toString(this.fNewStart));
+ sb.append(',');
+ sb.append(Integer.toString(this.fNewLength));
+ return sb.toString();
+ }
+
+ public String getRejectedDescription() {
+ StringBuffer sb= new StringBuffer();
+ sb.append("@@ -"); //$NON-NLS-1$
+ sb.append(Integer.toString(this.fOldStart));
+ sb.append(',');
+ sb.append(Integer.toString(this.fOldLength));
+ sb.append(" +"); //$NON-NLS-1$
+ sb.append(Integer.toString(this.fNewStart));
+ sb.append(',');
+ sb.append(Integer.toString(this.fNewLength));
+ sb.append(" @@"); //$NON-NLS-1$
+ return sb.toString();
+ }
+
+ public int getHunkType(boolean reverse) {
+ if (reverse) {
+ if (this.hunkType == FilePatch2.ADDITION)
+ return FilePatch2.DELETION;
+ if (this.hunkType == FilePatch2.DELETION)
+ return FilePatch2.ADDITION;
+ }
+ return this.hunkType;
+ }
+
+ void setHunkType(int hunkType) {
+ this.hunkType = hunkType;
+ }
+
+ public String[] getLines() {
+ return this.fLines;
+ }
+
+ public String[] getUnifiedLines() {
+ String[] ret = new String[this.fLines.length];
+ System.arraycopy(this.fLines, 0, ret, 0, this.fLines.length);
+ return ret;
+ }
+
+ /**
+ * Set the parent of this hunk. This method
+ * should only be invoked from {@link FilePatch2#add(Hunk)}
+ * @param diff the parent of this hunk
+ */
+ void setParent(FilePatch2 diff) {
+ if (this.fParent == diff)
+ return;
+ if (this.fParent != null)
+ this.fParent.remove(this);
+ this.fParent = diff;
+ }
+
+ public FilePatch2 getParent() {
+ return this.fParent;
+ }
+
+ /*
+ * Tries to apply the given hunk on the specified lines.
+ * The parameter shift is added to the line numbers given
+ * in the hunk.
+ */
+ public boolean tryPatch(PatchConfiguration configuration, List lines, int shift, int fuzz) {
+ boolean reverse = configuration.isReversed();
+ int pos = getStart(reverse) + shift;
+ List contextLines = new ArrayList();
+ boolean contextLinesMatched = true;
+ boolean precedingLinesChecked = false;
+ for (int i= 0; i < this.fLines.length; i++) {
+ String s = this.fLines[i];
+ Assert.isTrue(s.length() > 0);
+ String line = s.substring(1);
+ char controlChar = s.charAt(0);
+
+ if (controlChar == ' ') { // context lines
+
+ if (pos < 0 || pos >= lines.size())
+ return false;
+ contextLines.add(line);
+ if (linesMatch(configuration, line, (String) lines.get(pos))) {
+ pos++;
+ continue;
+ } else if (fuzz > 0) {
+ // doesn't match, use the fuzz factor
+ contextLinesMatched = false;
+ pos++;
+ continue;
+ }
+ return false;
+ } else if (isDeletedDelimeter(controlChar, reverse)) {
+ // deleted lines
+
+ if (precedingLinesChecked && !contextLinesMatched && contextLines.size() > 0)
+ // context lines inside hunk don't match
+ return false;
+
+ // check following context lines if exist
+ // use the fuzz factor if needed
+ if (!precedingLinesChecked
+ && !contextLinesMatched
+ && contextLines.size() >= fuzz
+ && !checkPrecedingContextLines(configuration, lines,
+ fuzz, pos, contextLines))
+ return false;
+ // else if there is less or equal context line to the fuzz
+ // factor we ignore them all and treat as matching
+
+ precedingLinesChecked = true;
+ contextLines.clear();
+ contextLinesMatched = true;
+
+ if (pos < 0 || pos >= lines.size()) // out of the file
+ return false;
+ if (linesMatch(configuration, line, (String) lines.get(pos))) {
+ pos++;
+ continue; // line matched, continue with the next one
+ }
+
+ // We must remove all lines at once, return false if this
+ // fails. In other words, all lines considered for deletion
+ // must be found one by one.
+ return false;
+ } else if (isAddedDelimeter(controlChar, reverse)) {
+
+ if (precedingLinesChecked && !contextLinesMatched && contextLines.size() > 0)
+ return false;
+
+ if (!precedingLinesChecked
+ && !contextLinesMatched
+ && contextLines.size() >= fuzz
+ && !checkPrecedingContextLines(configuration, lines,
+ fuzz, pos, contextLines))
+ return false;
+
+ precedingLinesChecked = true;
+ contextLines.clear();
+ contextLinesMatched = true;
+
+ // we don't have to do anything more for a 'try'
+ } else
+ Assert.isTrue(false, "tryPatch: unknown control character: " + controlChar); //$NON-NLS-1$
+ }
+
+ // check following context lines if exist
+ if (!contextLinesMatched
+ && fuzz > 0
+ && contextLines.size() > fuzz
+ && !checkFollowingContextLines(configuration, lines, fuzz, pos,
+ contextLines))
+ return false;
+
+ return true;
+ }
+
+ private boolean checkPrecedingContextLines(
+ PatchConfiguration configuration, List lines, int fuzz, int pos,
+ List contextLines) {
+
+ // ignore from the beginning
+ for (int j = fuzz; j < contextLines.size(); j++) {
+ if (!linesMatch(configuration, (String) contextLines.get(j),
+ (String) lines.get(pos - contextLines.size() + j)))
+ return false;
+ }
+ return true;
+ }
+
+ private boolean checkFollowingContextLines(
+ PatchConfiguration configuration, List lines, int fuzz, int pos,
+ List contextLines) {
+ if (!contextLines.isEmpty()) {
+ // ignore from the end
+ for (int j = 0; j < contextLines.size() - fuzz; j++) {
+ if (!linesMatch(configuration, (String) contextLines.get(j),
+ (String) lines.get(pos - contextLines.size() + j)))
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public int getStart(boolean after) {
+ if (after) {
+ return this.fNewStart;
+ }
+ return this.fOldStart;
+ }
+
+ public void setStart(int start, boolean after) {
+ if (after) {
+ this.fNewStart = start;
+ } else {
+ this.fOldStart = start;
+ }
+ }
+
+ public int getLength(boolean after) {
+ if (after) {
+ return this.fNewLength;
+ }
+ return this.fOldLength;
+ }
+
+ private int getShift(boolean reverse) {
+ if (reverse) {
+ return this.fOldLength - this.fNewLength;
+ }
+ return this.fNewLength - this.fOldLength;
+ }
+
+ int doPatch(PatchConfiguration configuration, List lines, int shift, int fuzz) {
+ boolean reverse = configuration.isReversed();
+ int pos = getStart(reverse) + shift;
+ List contextLines = new ArrayList();
+ boolean contextLinesMatched = true;
+ boolean precedingLinesChecked = false;
+ String lineDelimiter = getLineDelimiter(lines);
+
+ for (int i= 0; i < this.fLines.length; i++) {
+ String s= this.fLines[i];
+ Assert.isTrue(s.length() > 0);
+ String line= s.substring(1);
+ char controlChar= s.charAt(0);
+ if (controlChar == ' ') {
+ // context lines
+ Assert.isTrue(pos < lines.size(), "doPatch: inconsistency in context"); //$NON-NLS-1$
+ contextLines.add(line);
+ if (linesMatch(configuration, line, (String) lines.get(pos))) {
+ pos++;
+ continue;
+ } else if (fuzz > 0) {
+ // doesn't match, use the fuzz factor
+ contextLinesMatched = false;
+ pos++;
+ continue;
+ }
+ Assert.isTrue(false, "doPatch: context doesn't match"); //$NON-NLS-1$
+// pos++;
+ } else if (isDeletedDelimeter(controlChar, reverse)) {
+ // deleted lines
+ if (precedingLinesChecked && !contextLinesMatched && contextLines.size() > 0)
+ // context lines inside hunk don't match
+ Assert.isTrue(false, "doPatch: context lines inside hunk don't match"); //$NON-NLS-1$
+
+ // check following context lines if exist
+ // use the fuzz factor if needed
+ if (!precedingLinesChecked
+ && !contextLinesMatched
+ && contextLines.size() >= fuzz
+ && !checkPrecedingContextLines(configuration, lines,
+ fuzz, pos, contextLines))
+ Assert.isTrue(false, "doPatch: preceding context lines don't match, even though fuzz factor has been used"); //$NON-NLS-1$;
+ // else if there is less or equal context line to the fuzz
+ // factor we ignore them all and treat as matching
+
+ precedingLinesChecked = true;
+ contextLines.clear();
+ contextLinesMatched = true;
+
+ lines.remove(pos);
+ } else if (isAddedDelimeter(controlChar, reverse)) {
+ // added lines
+ if (precedingLinesChecked && !contextLinesMatched && contextLines.size() > 0)
+ Assert.isTrue(false, "doPatch: context lines inside hunk don't match"); //$NON-NLS-1$
+
+ if (!precedingLinesChecked
+ && !contextLinesMatched
+ && contextLines.size() >= fuzz
+ && !checkPrecedingContextLines(configuration, lines,
+ fuzz, pos, contextLines))
+ Assert.isTrue(false, "doPatch: preceding context lines don't match, even though fuzz factor has been used"); //$NON-NLS-1$;
+
+ precedingLinesChecked = true;
+ contextLines.clear();
+ contextLinesMatched = true;
+
+ // if the line contains a delimiter, use a proper one
+ if (line.length() > LineReader.length(line))
+ line = line.substring(0, LineReader.length(line)) + lineDelimiter;
+
+ if (getLength(reverse) == 0 && pos+1 < lines.size())
+ lines.add(pos+1, line);
+ else
+ lines.add(pos, line);
+ pos++;
+ } else
+ Assert.isTrue(false, "doPatch: unknown control character: " + controlChar); //$NON-NLS-1$
+ }
+ return getShift(reverse);
+ }
+
+ private boolean isDeletedDelimeter(char controlChar, boolean reverse) {
+ return (!reverse && controlChar == '-') || (reverse && controlChar == '+');
+ }
+
+ private boolean isAddedDelimeter(char controlChar, boolean reverse) {
+ return (reverse && controlChar == '-') || (!reverse && controlChar == '+');
+ }
+
+ /*
+ * Compares two strings.
+ * If fIgnoreWhitespace is true whitespace is ignored.
+ */
+ private boolean linesMatch(PatchConfiguration configuration, String line1, String line2) {
+ if (configuration.isIgnoreWhitespace())
+ return stripWhiteSpace(line1).equals(stripWhiteSpace(line2));
+ if (isIgnoreLineDelimiter()) {
+ int l1= LineReader.length(line1);
+ int l2= LineReader.length(line2);
+ if (l1 != l2)
+ return false;
+ return line1.regionMatches(0, line2, 0, l1);
+ }
+ return line1.equals(line2);
+ }
+
+ private boolean isIgnoreLineDelimiter() {
+ return true;
+ }
+
+ private String getLineDelimiter(List lines) {
+ if (lines.size() > 0) {
+ // get a line separator from the file being patched
+ String line0 = (String) lines.get(0);
+ return line0.substring(LineReader.length(line0));
+ } else if (this.fLines.length > 0) {
+ // if the file doesn't exist use a line separator from the patch
+ return this.fLines[0].substring(LineReader.length(this.fLines[0]));
+ }
+ return System.getProperty("line.separator"); //$NON-NLS-1$
+ }
+
+ /*
+ * Returns the given string with all whitespace characters removed.
+ * Whitespace is defined by <code>Character.isWhitespace(...)</code>.
+ */
+ private String stripWhiteSpace(String s) {
+ StringBuffer sb= new StringBuffer();
+ int l= s.length();
+ for (int i= 0; i < l; i++) {
+ char c= s.charAt(i);
+ if (!Character.isWhitespace(c))
+ sb.append(c);
+ }
+ return sb.toString();
+ }
+
+ public String getContents(boolean isAfterState, boolean reverse) {
+ StringBuffer result= new StringBuffer();
+ for (int i= 0; i<this.fLines.length; i++) {
+ String line= this.fLines[i];
+ String rest= line.substring(1);
+ char c = line.charAt(0);
+ if (c == ' ') {
+ result.append(rest);
+ } else if (isDeletedDelimeter(c, reverse) && !isAfterState) {
+ result.append(rest);
+ } else if (isAddedDelimeter(c, reverse) && isAfterState) {
+ result.append(rest);
+ }
+ }
+ return result.toString();
+ }
+
+ public String getLabel() {
+ return getDescription();
+ }
+
+ public int getStartPosition() {
+ return getStart(false);
+ }
+
+ public InputStream getOriginalContents() {
+ String contents = getContents(false, false);
+ return asInputStream(contents);
+ }
+
+ public InputStream getPatchedContents() {
+ String contents = getContents(true, false);
+ return asInputStream(contents);
+ }
+
+ private InputStream asInputStream(String contents) {
+ String charSet = getCharset();
+ return FileDiffResult.asInputStream(contents, charSet);
+ }
+
+ void setCharset(String charset) {
+ this.charset = charset;
+ }
+
+ /**
+ * {@inheritDoc}
+ * @deprecated This method can be called before the first attempt to apply
+ * the hunk when it is impossible to determine the encoding and
+ * in this case it always returns null. Please see
+ * {@link IFilePatchResult#getCharset()} as a proper way to
+ * obtain charset.
+ */
+ public String getCharset() {
+ return this.charset;
+ }
+
+}
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/HunkResult.java b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/HunkResult.java
new file mode 100644
index 000000000..186a49b6c
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/HunkResult.java
@@ -0,0 +1,273 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.internal.core.patch;
+
+import java.util.List;
+
+import org.eclipse.compare.patch.IHunkFilter;
+import org.eclipse.compare.patch.PatchConfiguration;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+
+public class HunkResult {
+
+ private static final boolean DEBUG= false;
+
+ /**
+ * Default maximum fuzz factor equals 2. This is related to the default
+ * number of context lines, which is 3.
+ */
+ private static final int MAXIMUM_FUZZ_FACTOR = 2;
+
+ private Hunk fHunk;
+ private boolean fMatches;
+ private int fShift;
+ private int fFuzz = -1; // not set or couldn't be found
+
+ private final FileDiffResult fDiffResult;
+
+ /**
+ * Create a hunk result for the given hunk
+ * @param diffResult the parent diff result
+ * @param hunk the hunk
+ */
+ public HunkResult(FileDiffResult diffResult, Hunk hunk) {
+ this.fDiffResult = diffResult;
+ this.fHunk = hunk;
+ }
+
+ /**
+ * Try to apply the specified hunk to the given lines.
+ * If the hunk cannot be applied at the original position
+ * the method tries shift lines up and down.
+ * @param lines the lines to be patched
+ * @return whether the hunk could be applied
+ */
+ public boolean patch(List lines) {
+ this.fMatches = false;
+ PatchConfiguration configuration = getConfiguration();
+ // if the fuzz is not set for the current hunk use the one from fDiffResult
+ int fuzz = this.fFuzz != -1 ? this.fFuzz : configuration.getFuzz();
+ if (isEnabled(configuration)) {
+ if (this.fHunk.tryPatch(configuration, lines, this.fShift, fuzz)) {
+ // it's a perfect match, no shifting is needed
+ this.fShift += this.fHunk.doPatch(configuration, lines, this.fShift, fuzz);
+ this.fMatches = true;
+ } else {
+ boolean found= false;
+ int oldShift= this.fShift;
+
+ int hugeShift = lines.size();
+ for (int i = 1; i <= hugeShift; i++) {
+ if (this.fHunk.tryPatch(configuration, lines, this.fShift - i, fuzz)) {
+ if (isAdjustShift())
+ this.fShift -= i;
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ for (int i = 1; i <= hugeShift; i++) {
+ if (this.fHunk.tryPatch(configuration, lines, this.fShift + i, fuzz)) {
+ if (isAdjustShift())
+ this.fShift += i;
+ found = true;
+ break;
+ }
+ }
+ }
+
+ if (found) {
+ if (DEBUG) System.out.println("patched hunk at offset: " + (this.fShift-oldShift)); //$NON-NLS-1$
+ this.fShift+= this.fHunk.doPatch(configuration, lines, this.fShift, fuzz);
+ this.fMatches = true;
+ }
+ }
+ }
+ return this.fMatches;
+ }
+
+ private boolean isAdjustShift() {
+ return true;
+ }
+
+ private PatchConfiguration getConfiguration() {
+ return getDiffResult().getConfiguration();
+ }
+
+ /**
+ * Calculate the fuzz that will allow the most hunks to be matched. Even
+ * though we're interested only in the value of the fuzz, the shifting is
+ * done anyway.
+ *
+ * @param lines
+ * the lines of the target file
+ * @param monitor
+ * a progress monitor
+ * @return the fuzz factor or -1 if the hunk could not be matched
+ */
+ public int calculateFuzz(List lines, IProgressMonitor monitor) {
+ this.fMatches = false;
+ PatchConfiguration configuration = getConfiguration();
+ int fuzz = 0;
+ int maxFuzz = configuration.getFuzz() == -1 ? MAXIMUM_FUZZ_FACTOR
+ : configuration.getFuzz();
+ for (; fuzz <= maxFuzz; fuzz++) {
+ // try to apply using lines coordinates from the patch
+ if (this.fHunk.tryPatch(configuration, lines, this.fShift, fuzz)) {
+ // it's a perfect match, no adjustment is needed
+ this.fShift += this.fHunk.doPatch(configuration, lines, this.fShift, fuzz);
+ this.fMatches = true;
+ break;
+ }
+
+ // TODO (tzarna): hugeShift=lines.size() is more than we need.
+ // Lines to the beg/end of a file would be enough but this can still
+ // in matching hunks out of order. Try to shift using only lines
+ // available "between" hunks.
+ int hugeShift = lines.size();
+
+ // shift up
+ for (int i = 1; i <= hugeShift; i++) {
+ if (monitor.isCanceled()) {
+ throw new OperationCanceledException();
+ }
+ if (this.fHunk.tryPatch(configuration, lines, this.fShift - i, fuzz)) {
+ if (isAdjustShift())
+ this.fShift -= i;
+ this.fMatches = true;
+ break;
+ }
+ }
+
+ // shift down
+ if (!this.fMatches) {
+ for (int i = 1; i <= hugeShift; i++) {
+ if (monitor.isCanceled()) {
+ throw new OperationCanceledException();
+ }
+ if (this.fHunk.tryPatch(configuration, lines, this.fShift + i, fuzz)) {
+ if (isAdjustShift())
+ this.fShift += i;
+ this.fMatches = true;
+ break;
+ }
+ }
+ }
+
+ if (this.fMatches) {
+ this.fShift += this.fHunk.doPatch(configuration, lines, this.fShift, fuzz);
+ break;
+ }
+ }
+ // set fuzz for the current hunk
+ this.fFuzz = this.fMatches ? fuzz : -1;
+ return this.fFuzz;
+ }
+
+ /**
+ * Return the amount that this hunk should be shifted when a match with the file
+ * is attempted. The shift is needed to compensate for previous hunks that have
+ * been applied.
+ * @return the amount that this hunk should be shifted when applied
+ */
+ public int getShift() {
+ return this.fShift;
+ }
+
+ /**
+ * Set the amount that this hunk should be shifted when a match with the file
+ * is attempted. The shift is needed to compensate for previous hunks that have
+ * been applied.
+ * @param shift the amount to shift this hunk
+ */
+ public void setShift(int shift) {
+ this.fShift = shift;
+ }
+
+ /**
+ * Return the hunk to which this result applies.
+ * @return the hunk to which this result applies
+ */
+ public Hunk getHunk() {
+ return this.fHunk;
+ }
+
+ /**
+ * Return the parent diff result.
+ * @return the parent diff result
+ */
+ public FileDiffResult getDiffResult() {
+ return this.fDiffResult;
+ }
+
+ /**
+ * Return whether the hunk was matched with the target file.
+ * @return whether the hunk was matched with the target file
+ */
+ public boolean isOK() {
+ return this.fMatches;
+ }
+
+ /**
+ * Return the contents that should be displayed for the hunk result.
+ * @param afterState whether the after state or before state of the hunk is desired
+ * @param fullContext whether the hunk should be displayed with the entire file or
+ * only the lines in the hunk itself
+ * @return the contents to be display
+ */
+ public String getContents(boolean afterState, boolean fullContext) {
+ if (fullContext) {
+ boolean problemFound = false;
+ List lines = getDiffResult().getBeforeLines();
+ if (afterState) {
+ if (isOK()) {
+ int oldShift = this.fShift;
+ try {
+ this.fShift = 0;
+ problemFound = !patch(lines);
+ } finally {
+ this.fShift = oldShift;
+ }
+ } else {
+ problemFound = true;
+ }
+ }
+ // Only return the full context if we could apply the hunk
+ if (!problemFound)
+ return LineReader.createString(this.fDiffResult.isPreserveLineDelimeters(), lines);
+ }
+ return getHunk().getContents(afterState, getConfiguration().isReversed());
+ }
+
+ private boolean isEnabled(PatchConfiguration configuration) {
+ IHunkFilter[] filters = configuration.getHunkFilters();
+ for (int i = 0; i < filters.length; i++) {
+ if (!filters[i].select(this.fHunk)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ public void setMatches(boolean matches) {
+ this.fMatches = matches;
+ }
+
+ public String getCharset() {
+ return this.fDiffResult.getCharset();
+ }
+
+ public int getFuzz() {
+ return this.fFuzz;
+ }
+}
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/LineReader.java b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/LineReader.java
new file mode 100644
index 000000000..fc83e493e
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/LineReader.java
@@ -0,0 +1,224 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ * Brock Janiczak <brockj@tpg.com.au> - Bug 181919 LineReader creating unneeded garbage
+ *******************************************************************************/
+package org.eclipse.compare.internal.core.patch;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.compare.internal.core.ComparePlugin;
+import org.eclipse.compare.patch.ReaderCreator;
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.Platform;
+
+public class LineReader {
+
+ /*
+ * Reads the contents and returns them as a List of lines.
+ */
+ public static List load(ReaderCreator content, boolean create) {
+ List lines = null;
+ BufferedReader bufferedReader = null;
+ if (!create && content != null && content.canCreateReader()) {
+ // read current contents
+ try {
+ bufferedReader = new BufferedReader(content.createReader());
+ lines = readLines(bufferedReader);
+ } catch (CoreException ex) {
+ ComparePlugin.log(ex);
+ } finally {
+ if (bufferedReader != null)
+ try {
+ bufferedReader.close();
+ } catch (IOException ex) {
+ // silently ignored
+ }
+ }
+ }
+
+ if (lines == null)
+ lines = new ArrayList();
+ return lines;
+ }
+
+ public static List readLines(BufferedReader reader) {
+ List lines;
+ LineReader lr= new LineReader(reader);
+ if (!Platform.WS_CARBON.equals(Platform.getWS()))
+ lr.ignoreSingleCR(); // Don't treat single CRs as line feeds to be consistent with command line patch
+ lines= lr.readLines();
+ return lines;
+ }
+
+ /*
+ * 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();
+ }
+
+ /*
+ * 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;
+ }
+
+ private boolean fHaveChar= false;
+ private int fLastChar;
+ private boolean fSawEOF= false;
+ private BufferedReader fReader;
+ private boolean fIgnoreSingleCR= false;
+ private StringBuffer fBuffer= new StringBuffer();
+
+ public LineReader(BufferedReader reader) {
+ this.fReader= reader;
+ Assert.isNotNull(reader);
+ }
+
+ public void ignoreSingleCR() {
+ this.fIgnoreSingleCR= true;
+ }
+
+ /**
+ * Reads a line of text. A line is considered to be terminated by any one
+ * of a line feed ('\n'), a carriage return ('\r'), or a carriage return
+ * followed immediately by a line-feed.
+ * @return A string containing the contents of the line including
+ * the line-termination characters, or <code>null</code> if the end of the
+ * stream has been reached
+ * @exception IOException If an I/O error occurs
+ */
+ /* package */ String readLine() throws IOException {
+ try {
+ while (!this.fSawEOF) {
+ int c= readChar();
+ if (c == -1) {
+ this.fSawEOF= true;
+ break;
+ }
+ this.fBuffer.append((char)c);
+ if (c == '\n')
+ break;
+ if (c == '\r') {
+ c= readChar();
+ if (c == -1) {
+ this.fSawEOF= true;
+ break; // EOF
+ }
+ if (c != '\n') {
+ if (this.fIgnoreSingleCR) {
+ this.fBuffer.append((char)c);
+ continue;
+ }
+ this.fHaveChar= true;
+ this.fLastChar= c;
+ } else
+ this.fBuffer.append((char)c);
+ break;
+ }
+ }
+
+ if (this.fBuffer.length() != 0) {
+ return this.fBuffer.toString();
+ }
+ return null;
+ } finally {
+ this.fBuffer.setLength(0);
+ }
+ }
+
+ /* package */ void close() {
+ try {
+ this.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 (this.fHaveChar) {
+ this.fHaveChar= false;
+ return this.fLastChar;
+ }
+ return this.fReader.read();
+ }
+}
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/PatchReader.java b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/PatchReader.java
new file mode 100644
index 000000000..adf5deb6a
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/PatchReader.java
@@ -0,0 +1,683 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2010 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.internal.core.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.IFilePatch2;
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
+import org.eclipse.core.runtime.Platform;
+
+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 FilePatch2[] fDiffs;
+
+ // API for writing new multi-project patch format
+ public static final String MULTIPROJECTPATCH_HEADER= "### Eclipse Workspace Patch"; //$NON-NLS-1$
+
+ public static final String MULTIPROJECTPATCH_VERSION= "1.0"; //$NON-NLS-1$
+
+ public static final String MULTIPROJECTPATCH_PROJECT= "#P"; //$NON-NLS-1$
+
+ /**
+ * Create a patch reader for the default date formats.
+ */
+ public PatchReader() {
+ // nothing here
+ }
+
+ /**
+ * Create a patch reader for the given date formats.
+ *
+ * @param dateFormats
+ * Array of <code>DateFormat</code>s to be used when
+ * extracting dates from the patch.
+ */
+ public PatchReader(DateFormat[] dateFormats) {
+ this();
+ 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 projectName= ""; //$NON-NLS-1$
+ this.fIsWorkspacePatch= false;
+
+ LineReader lr= new LineReader(reader);
+ if (!Platform.WS_CARBON.equals(Platform.getWS()))
+ 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)) {
+ this.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)) {
+ projectName= 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(projectName)) {
+ diffProject= new DiffProject(projectName);
+ diffProjects.put(projectName, diffProject);
+ } else {
+ diffProject= (DiffProject) diffProjects.get(projectName);
+ }
+
+ line= readUnifiedDiff(diffs, lr, line, diffArgs, fileName, diffProject);
+ diffArgs= fileName= null;
+ reread= true;
+ }
+ }
+
+ lr.close();
+
+ this.fDiffProjects= (DiffProject[]) diffProjects.values().toArray(new DiffProject[diffProjects.size()]);
+ this.fDiffs = (FilePatch2[]) diffs.toArray(new FilePatch2[diffs.size()]);
+ }
+
+ protected FilePatch2 createFileDiff(IPath oldPath, long oldDate,
+ IPath newPath, long newDate) {
+ return new FilePatch2(oldPath, oldDate, newPath, newDate);
+ }
+
+ 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();) {
+ FilePatch2 diff= (FilePatch2) 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((FilePatch2)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((FilePatch2)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();
+
+ this.fDiffs = (FilePatch2[]) diffs.toArray(new FilePatch2[diffs.size()]);
+ }
+
+ private void setHeader(FilePatch2 diff, List headerLines) {
+ String header = LineReader.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));
+
+ FilePatch2 diff = createFileDiff(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) {
+ Hunk.createHunk(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)
+ Hunk.createHunk(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));
+
+ FilePatch2 diff = createFileDiff(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) {
+ Hunk.createHunk(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);
+ if (oldRange[0] == 0) {
+ oldRange[1] = 0; // In case of the file addition
+ } else {
+ 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);
+ if (newRange[0] == 0) {
+ newRange[1] = 0; // In case of the file removal
+ } else {
+ 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)
+ Hunk.createHunk(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 < this.fDateFormats.length; i++) {
+ this.fDateFormats[i].setLenient(true);
+ try {
+ Date date= this.fDateFormats[i].parse(line);
+ return date.getTime();
+ } catch (ParseException ex) {
+ // silently ignored
+ }
+ }
+ // System.err.println("can't parse date: <" + line + ">");
+ }
+ return IFilePatch2.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 this.fIsWorkspacePatch;
+ }
+
+ public DiffProject[] getDiffProjects() {
+ return this.fDiffProjects;
+ }
+
+ public FilePatch2[] getDiffs() {
+ return this.fDiffs;
+ }
+
+ public FilePatch2[] getAdjustedDiffs() {
+ if (!isWorkspacePatch() || this.fDiffs.length == 0)
+ return this.fDiffs;
+ List result = new ArrayList();
+ for (int i = 0; i < this.fDiffs.length; i++) {
+ FilePatch2 diff = this.fDiffs[i];
+ result.add(diff.asRelativeDiff());
+ }
+ return (FilePatch2[]) result.toArray(new FilePatch2[result.size()]);
+ }
+
+}
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/Utilities.java b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/Utilities.java
new file mode 100644
index 000000000..6dfbfe2c9
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/internal/core/patch/Utilities.java
@@ -0,0 +1,23 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2009 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.compare.internal.core.patch;
+
+import java.io.InputStreamReader;
+
+public class Utilities {
+
+ public static String getCharset(Object resource) {
+ if (resource instanceof InputStreamReader) {
+ return ((InputStreamReader) resource).getEncoding();
+ }
+ return null;
+ }
+}
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/IFilePatch2.java b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/IFilePatch2.java
new file mode 100644
index 000000000..9138e8b68
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/IFilePatch2.java
@@ -0,0 +1,89 @@
+/*******************************************************************************
+ * Copyright (c) 2009 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.runtime.IPath;
+import org.eclipse.core.runtime.IProgressMonitor;
+
+/**
+ * A representation of a file patch that can be applied to an input stream.
+ *
+ * @noimplement This interface is not intended to be implemented by clients.
+ * @since org.eclipse.compare.core 3.5
+ */
+public interface IFilePatch2 {
+
+ /**
+ * Special constant that will be returned from get getBeforeDate() or
+ * getAfterDate() if the date is unknown. Equal to Midnight, Jan 1, 1970
+ * GMT.
+ */
+ 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 contents. The result provides the
+ * original and patch contents and also indicates whether some portions of
+ * the patch (called hunks) failed to apply.
+ *
+ * @param content
+ * the contents
+ * @param configuration
+ * the patch configuration
+ * @param monitor
+ * a progress monitor
+ * @return the result of the patch application
+ */
+ public IFilePatchResult apply(ReaderCreator content,
+ PatchConfiguration configuration, IProgressMonitor monitor);
+
+ /**
+ * Return the header information of the patch or <code>null</code> if there
+ * was no header text. The header may be multi-line.
+ *
+ * @return the header information of the patch or <code>null</code>
+ */
+ public String getHeader();
+
+ /**
+ * Returns the milliseconds time value of the before date from the patch, or
+ * DATE_UNKNOWN if the date is unknown.
+ *
+ * @return milliseconds time value of the before date from the patch
+ */
+ 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
+ */
+ public long getAfterDate();
+
+ /**
+ * Returns all the hunks this file patch contains.
+ *
+ * @return array of hunks
+ */
+ public IHunk[] getHunks();
+
+}
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/IFilePatchResult.java b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/IFilePatchResult.java
new file mode 100644
index 000000000..9c0667f11
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/IFilePatchResult.java
@@ -0,0 +1,90 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2009 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.runtime.CoreException;
+
+/**
+ * A file patch result provides the results of an attempt to apply an
+ * {@link IFilePatch2} to the contents of a file. *
+ *
+ * @see IFilePatch2
+ * @since 3.3
+ * @noimplement This interface is not intended to be implemented by clients.
+ * Clients can obtain patch results from an {@link IFilePatch2}.
+ */
+public interface IFilePatchResult {
+
+ /**
+ * Return a stream the contains the original contents of the file before
+ * any portions of the patch have been applied.
+ * @return a stream to the original contents of the file before
+ * any portions of the patch have been applied
+ * @see #getPatchedContents()
+ */
+ public InputStream getOriginalContents();
+
+ /**
+ * Return a stream that contains the file with as much of the patch
+ * applied as possible. if {@link #hasMatches()} returns <code>false</code>
+ * then the patched contents will match the original contents. Otherwise,
+ * at least a portion of the patch could be successfully applied. if
+ * {@link #hasRejects()} returns <code>false</code>, then the entire patch was
+ * applied. Otherwise, portions could not be applied. The portions that could
+ * not be applied can be obtained by calling {@link #getRejects()}.
+ *
+ * @return a stream that contains the file with as much of the patch
+ * applied as possible.
+ */
+ public InputStream getPatchedContents();
+
+ /**
+ * Return whether the patch has portions that were successfully applied.
+ * @return whether the patch has portions that were successfully applied
+ * @see #getPatchedContents()
+ */
+ public boolean hasMatches();
+
+ /**
+ * Return whether the patch has portions that were not successfully applied.
+ * @return whether the patch has portions that were not successfully applied
+ * @see #getPatchedContents()
+ */
+ public boolean hasRejects();
+
+ /**
+ * Return the portions of the patch (referred to a hunks) that could not
+ * be applied.
+ * @return the portions of the patch (referred to a hunks) that could not
+ * be applied
+ * @see #getPatchedContents()
+ */
+ public IHunk[] getRejects();
+
+ /**
+ * Returns the name of a charset encoding to be used when decoding the contents
+ * of this result into characters. Returns <code>null</code> if a proper
+ * encoding cannot be determined.
+ * <p>
+ * Note that this method does not check whether the result is a supported
+ * charset name. Callers should be prepared to handle
+ * <code>UnsupportedEncodingException</code> where this charset is used.
+ * </p>
+ *
+ * @return the name of a charset, or <code>null</code>
+ * @exception CoreException if an error happens while determining
+ * the charset. See any refinements for more information.
+ */
+ public String getCharset() throws CoreException;
+
+}
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/IHunk.java b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/IHunk.java
new file mode 100644
index 000000000..b918e0d0f
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/IHunk.java
@@ -0,0 +1,98 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2009 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.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}.
+ *
+ * @since 3.3
+ * @noimplement This interface is not intended to be implemented by clients but
+ * can be obtained from an {@link IFilePatchResult}
+ */
+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();
+
+ /**
+ * Returns hunk's content in the unified format. This is an internal format in
+ * which hunk stores its content and is always the same even if the hunk was
+ * extracted from a patch stored in a different format. In the unified format
+ * each line is prefixed with one of the following:
+ * <ul>
+ * <li> <code>' '</code> for context
+ * <li> <code>'+'</code> for addition
+ * <li> <code>'-'</code> for removal
+ * </ul>
+ *
+ * @return hunk's content in the unified format
+ * @since org.eclipse.compare 3.5
+ */
+ public String[] getUnifiedLines();
+
+ /**
+ * Return the original contents from which the hunk was generated.
+ * The returned contents usually only represent a portion of the
+ * file from which the hunk was generated.
+ * @return the original contents from which the hunk was generated
+ */
+ public InputStream getOriginalContents();
+
+ /**
+ * Return the contents that contain the modifications for this hunk.
+ * The returned contents usually only represent a portion of the
+ * file that was modified.
+ * @return the contents that contain the modifications for this hunk
+ */
+ public InputStream getPatchedContents();
+
+ /**
+ * Returns the name of a charset encoding to be used when decoding the contents
+ * of this hunk into characters. Returns <code>null</code> if a proper
+ * encoding cannot be determined.
+ * <p>
+ * Note that this method does not check whether the result is a supported
+ * charset name. Callers should be prepared to handle
+ * <code>UnsupportedEncodingException</code> where this charset is used.
+ * </p>
+ *
+ * @return the name of a charset, or <code>null</code>
+ * @exception CoreException if an error happens while determining
+ * the charset. See any refinements for more information.
+ * @deprecated This method can be called before the first attempt to apply
+ * the hunk when it is impossible to determine the encoding and
+ * in this case it always returns null. Please see
+ * {@link IFilePatchResult#getCharset()} as a proper way to
+ * obtain charset.
+ */
+ public String getCharset() throws CoreException;
+
+
+}
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/IHunkFilter.java b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/IHunkFilter.java
new file mode 100644
index 000000000..c32079fc6
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/IHunkFilter.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2009 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;
+
+/**
+ * Filter that is used to determine if a hunk should be applied or not
+ *
+ * @since org.eclipse.compare.core 3.5
+ */
+public interface IHunkFilter {
+
+ /**
+ * Returns true if the given hunk should be applied
+ *
+ * @param hunk
+ * the hunk
+ * @return true if the given hunk should be applied
+ */
+ public boolean select(IHunk hunk);
+
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchBuilder.java b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchBuilder.java
new file mode 100644
index 000000000..40d717758
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchBuilder.java
@@ -0,0 +1,250 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2010 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.Arrays;
+import java.util.Comparator;
+
+import org.eclipse.compare.internal.core.patch.FilePatch2;
+import org.eclipse.compare.internal.core.patch.Hunk;
+import org.eclipse.core.runtime.IPath;
+
+/**
+ * Builder for creating IFilePatch2 and IHunk objects as well as building
+ * relationship between them.
+ *
+ * @noextend This class is not intended to be subclassed by clients.
+ * @noinstantiate This class is not intended to be instantiated by clients.
+ *
+ * @since org.eclipse.compare.core 3.5
+ */
+public class PatchBuilder {
+
+ /**
+ * Line prefix used to mark context lines.
+ */
+ public static final char CONTEXT_PREFIX = ' ';
+ /**
+ * Line prefix used to mark an added lines.
+ */
+ public static final char ADDITION_PREFIX = '+';
+ /**
+ * Line prefix used to mark an removed lines.
+ */
+ public static final char REMOVAL_PREFIX = '-';
+
+ /**
+ * Creates an IHunk instance.
+ *
+ * @param start
+ * the start position in the before file
+ * @param lines
+ * content of the hunk. Each line starts with a control
+ * character. Their meaning is as follows:
+ * <ul>
+ * <li>
+ * '+': add the line
+ * <li>
+ * '-': delete the line
+ * <li>
+ * ' ': no change, context line
+ * </ul>
+ * @return IHunk instance
+ */
+ public static IHunk createHunk(int start, String[] lines) {
+ int type = getHunkType(lines);
+ int oldLength = getHunkLength(lines, true);
+ int newLength = getHunkLength(lines, false);
+ return new Hunk(null, type, start, oldLength, start, newLength, lines);
+ }
+
+ /**
+ * Creates an IFilePatch2 instance and performs recalculation of all hunks'
+ * after positions. Hunk's after position is position in the file state
+ * after applying a patch. It is affected by all the hunks that are to be
+ * applied before a given one. This recalculation is necessary to keep
+ * IFilePatch2's state coherent.
+ *
+ * @param oldPath
+ * the path of the before state of the file
+ * @param oldDate
+ * the timestamp of the before state of the file, see also
+ * {@link IFilePatch2#DATE_UNKNOWN}
+ * @param newPath
+ * the path of the after state of the file
+ * @param newDate
+ * the timestamp of the after state of the file, see also
+ * {@link IFilePatch2#DATE_UNKNOWN}
+ * @param hunks
+ * a set of hunks to insert into IFilePatch2
+ * @return IFilePatch2 instance
+ */
+ public static IFilePatch2 createFilePatch(IPath oldPath, long oldDate,
+ IPath newPath, long newDate, IHunk[] hunks) {
+ reorder(hunks);
+ FilePatch2 fileDiff = new FilePatch2(oldPath, oldDate, newPath, newDate);
+ for (int i = 0; i < hunks.length; i++) {
+ fileDiff.add((Hunk) hunks[i]);
+ }
+ return fileDiff;
+ }
+
+ /**
+ * Adds IHunks to a given IFilePatch2 and performs recalculation of all
+ * hunks' after positions. Hunk's after position is position in the file
+ * state after applying a patch. It is affected by all the hunks that are to
+ * be applied before a given one. This recalculation is necessary to keep
+ * IFilePatch2's state coherent.
+ *
+ * @param filePatch
+ * a file patch to add hunks to
+ * @param toAdd
+ * a set of IHunks to add
+ * @return newly created file patch with added hunks
+ */
+ public static IFilePatch2 addHunks(IFilePatch2 filePatch, IHunk[] toAdd) {
+ IHunk[] result = addHunks(filePatch.getHunks(), toAdd);
+ reorder(result);
+ return createFilePatch(filePatch, result);
+ }
+
+ /**
+ * Removes IHunks from a given IFilePatch2 and performs recalculation of all
+ * hunks' after positions. Hunk's after position is position in the file
+ * state after applying a patch. It is affected by all the hunks that are to
+ * be applied before a given one. This recalculation is necessary to keep
+ * IFilePatch2's state coherent.
+ *
+ * @param filePatch
+ * a file patch to add hunks to
+ * @param toRemove
+ * a set of IHunks to add
+ * @return newly created file patch with removed hunks
+ */
+ public static IFilePatch2 removeHunks(IFilePatch2 filePatch,
+ IHunk[] toRemove) {
+ IHunk[] result = removeHunks(filePatch.getHunks(), toRemove);
+ reorder(result);
+ return createFilePatch(filePatch, result);
+ }
+
+ private static IFilePatch2 createFilePatch(IFilePatch2 filePatch,
+ IHunk[] hunks) {
+ PatchConfiguration config = new PatchConfiguration();
+ IPath beforePath = filePatch.getTargetPath(config);
+ config.setReversed(true);
+ IPath afterPath = filePatch.getTargetPath(config);
+ return createFilePatch(beforePath, filePatch.getBeforeDate(),
+ afterPath, filePatch.getAfterDate(), hunks);
+ }
+
+ private static int getHunkType(String[] lines) {
+ boolean hasContextLines = checkForPrefix(CONTEXT_PREFIX, lines);
+ if (!hasContextLines) {
+ boolean hasLineAdditions = checkForPrefix(ADDITION_PREFIX, lines);
+ boolean hasLineDeletions = checkForPrefix(REMOVAL_PREFIX, lines);
+ if (hasLineAdditions && !hasLineDeletions) {
+ return FilePatch2.ADDITION;
+ } else if (!hasLineAdditions && hasLineDeletions) {
+ return FilePatch2.DELETION;
+ }
+ }
+ return FilePatch2.CHANGE;
+ }
+
+ private static int getHunkLength(String[] lines, boolean old) {
+ int length = 0;
+ for (int i = 0; i < lines.length; i++) {
+ if (lines[i].length() > 0) {
+ switch (lines[i].charAt(0)) {
+ case ' ':
+ length++;
+ break;
+ case '+':
+ if (!old) {
+ length++;
+ }
+ break;
+ case '-':
+ if (old) {
+ length++;
+ }
+ break;
+ default:
+ throw new IllegalArgumentException(""); //$NON-NLS-1$
+ }
+ }
+ }
+ return length;
+ }
+
+ private static boolean checkForPrefix(char prefix, String[] lines) {
+ for (int i = 0; i < lines.length; i++) {
+ if (lines[i].length() > 0) {
+ if (lines[i].charAt(0) == prefix) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ private static IHunk[] addHunks(IHunk[] hunks, IHunk[] toAdd) {
+ IHunk[] ret = new IHunk[hunks.length + toAdd.length];
+ System.arraycopy(hunks, 0, ret, 0, hunks.length);
+ System.arraycopy(toAdd, 0, ret, hunks.length, toAdd.length);
+ return ret;
+ }
+
+ private static IHunk[] removeHunks(IHunk[] hunks, IHunk[] toRemove) {
+ int removed = 0;
+ for (int i = 0; i < hunks.length; i++) {
+ for (int j = 0; j < toRemove.length; j++) {
+ if (toRemove[j] == hunks[i]) {
+ hunks[i] = null;
+ removed++;
+ }
+ }
+ }
+ IHunk[] ret = new IHunk[hunks.length - removed];
+ for (int i = 0, j = 0; i < hunks.length; i++) {
+ if (hunks[i] != null) {
+ ret[j++] = hunks[i];
+ }
+ }
+ return ret;
+ }
+
+ private static void reorder(IHunk[] hunks) {
+ Arrays.sort(hunks, new HunkComparator());
+ int shift = 0;
+ for (int i = 0; i < hunks.length; i++) {
+ Hunk hunk = (Hunk) hunks[i];
+ int start = hunk.getStart(false) + shift;
+ hunk.setStart(start, true);
+ shift += hunk.getLength(true) - hunk.getLength(false);
+ }
+ }
+
+ static class HunkComparator implements Comparator {
+ public int compare(Object arg0, Object arg1) {
+ if ((arg0 != null && arg0 instanceof Hunk)
+ && (arg1 != null && arg1 instanceof Hunk)) {
+ Hunk hunk0 = (Hunk) arg0;
+ Hunk hunk1 = (Hunk) arg1;
+ int shift = hunk0.getStart(true) - hunk1.getStart(true);
+ return shift;
+ }
+ return 0;
+ }
+ }
+
+} \ No newline at end of file
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchConfiguration.java b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchConfiguration.java
new file mode 100644
index 000000000..2266965fb
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchConfiguration.java
@@ -0,0 +1,158 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 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.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * A patch configuration allows clients to set parameters that control how a
+ * patch is applied.
+ *
+ * @see IFilePatch2
+ * @since 3.3
+ * @noextend This class may be instantiated by clients but is not intended to be
+ * subclassed.
+ */
+public class PatchConfiguration {
+
+ private int fStripPrefixSegments;
+ private int fFuzz;
+ private boolean fIgnoreWhitespace= false;
+ private boolean fReverse= false;
+ private HashMap properties = new HashMap();
+ private List hunkFilters = new ArrayList();
+
+ /**
+ * Return whether the patch should be reversed when applied.
+ * @return whether the patch should be reversed when applied
+ */
+ public boolean isReversed() {
+ return this.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 this.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 this.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) {
+ this.fFuzz = fuzz;
+ }
+
+ /**
+ * Return whether whitespace should be ignored.
+ * @return whether whitespace should be ignored
+ */
+ public boolean isIgnoreWhitespace() {
+ return this.fIgnoreWhitespace;
+ }
+
+ /**
+ * Set whether whitespace should be ignored
+ * @param ignoreWhitespace whether whitespace should be ignored
+ */
+ public void setIgnoreWhitespace(boolean ignoreWhitespace) {
+ this.fIgnoreWhitespace = ignoreWhitespace;
+ }
+
+ /**
+ * Return the property associated with the given key or
+ * <code>null</code> if there is no property for the key.
+ * @param key the key
+ * @return the property associated with the given key or
+ * <code>null</code>
+ */
+ public Object getProperty(String key) {
+ return this.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) {
+ this.properties.put(key, value);
+ }
+
+ /**
+ * Adds a hunk filter.
+ *
+ * @param filter the filter
+ * @since org.eclipse.compare.core 3.5
+ */
+ public void addHunkFilter(IHunkFilter filter) {
+ this.hunkFilters.add(filter);
+ }
+
+ /**
+ * Removes a hunk filter.
+ *
+ * @param filter the filter
+ * @since org.eclipse.compare.core 3.5
+ */
+ public void removeHunkFilter(IHunkFilter filter) {
+ this.hunkFilters.remove(filter);
+ }
+
+ /**
+ * Return an array of hunk filters that have been added to this
+ * configuration.
+ *
+ * @return an array of hunk filters that have been added to this configuration
+ * @since org.eclipse.compare.core 3.5
+ */
+ public IHunkFilter[] getHunkFilters() {
+ return (IHunkFilter[]) this.hunkFilters.toArray(new IHunkFilter[this.hunkFilters
+ .size()]);
+ }
+
+}
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchParser.java b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchParser.java
new file mode 100644
index 000000000..cd77da853
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/PatchParser.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2009 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.ComparePlugin;
+import org.eclipse.compare.internal.core.patch.PatchReader;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+
+/**
+ * Helper class for parsing patches.
+ *
+ * @since org.eclipse.compare.core 3.5
+ */
+public class PatchParser {
+
+ /**
+ * Parse the given patch and return the set of file patches that it
+ * contains.
+ *
+ * @param content
+ * a patch reader creator
+ * @return the set of file patches that the patch contains
+ * @throws CoreException
+ * if an error occurs reading the contents
+ */
+ public static IFilePatch2[] parsePatch(ReaderCreator content)
+ throws CoreException {
+ BufferedReader reader = new BufferedReader(content.createReader());
+ try {
+ PatchReader patchReader = new PatchReader();
+ patchReader.parse(reader);
+ return patchReader.getAdjustedDiffs();
+ } catch (IOException e) {
+ throw new CoreException(new Status(IStatus.ERROR,
+ ComparePlugin.PLUGIN_ID, 0, e.getMessage(), e));
+ } finally {
+ try {
+ reader.close();
+ } catch (IOException e) {
+ // ignored
+ }
+ }
+ }
+}
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/ReaderCreator.java b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/ReaderCreator.java
new file mode 100644
index 000000000..e5846bd9e
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/ReaderCreator.java
@@ -0,0 +1,42 @@
+/*******************************************************************************
+ * Copyright (c) 2009 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.Reader;
+
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * Abstract class for creating readers.
+ *
+ * @since org.eclipse.compare.core 3.5
+ */
+public abstract class ReaderCreator {
+
+ /**
+ * Creates new reader. The caller is responsible for closing the reader when
+ * finished.
+ *
+ * @return a reader
+ * @exception CoreException
+ * if the reader can't be created
+ */
+ public abstract Reader createReader() throws CoreException;
+
+ /**
+ * Returns whether the reader can be created.
+ *
+ * @return true if the reader can be created, false otherwise
+ */
+ public boolean canCreateReader() {
+ return true;
+ }
+}
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/package.html b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/package.html
new file mode 100644
index 000000000..e1c40fd3d
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/patch/package.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Package-level Javadoc</title>
+</head>
+<body>
+Provides support for applying and working with patches.
+<h2>
+Package Specification</h2>
+<p>
+This package specifies API for applying and working with patches.
+</p>
+</body>
+</html>
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/AbstractRangeDifferenceFactory.java b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/AbstractRangeDifferenceFactory.java
new file mode 100644
index 000000000..04af7cb4f
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/AbstractRangeDifferenceFactory.java
@@ -0,0 +1,49 @@
+/*******************************************************************************
+ * Copyright (c) 2009 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.rangedifferencer;
+
+/**
+ * @since org.eclipse.compare.core 3.5
+ */
+public abstract class AbstractRangeDifferenceFactory {
+ protected abstract RangeDifference createRangeDifference();
+
+ RangeDifference createRangeDifference(int changeKind) {
+ RangeDifference rangeDifference = createRangeDifference();
+ rangeDifference.kind = changeKind;
+ return rangeDifference;
+ }
+
+ RangeDifference createRangeDifference(int kind, int rightStart,
+ int rightLength, int leftStart, int leftLength) {
+ RangeDifference rangeDifference = createRangeDifference();
+ rangeDifference.kind = kind;
+ rangeDifference.rightStart = rightStart;
+ rangeDifference.rightLength = rightLength;
+ rangeDifference.leftStart = leftStart;
+ rangeDifference.leftLength = leftLength;
+ return rangeDifference;
+ }
+
+ RangeDifference createRangeDifference(int kind, int rightStart,
+ int rightLength, int leftStart, int leftLength, int ancestorStart,
+ int ancestorLength) {
+ RangeDifference rangeDifference = createRangeDifference();
+ rangeDifference.kind = kind;
+ rangeDifference.rightStart = rightStart;
+ rangeDifference.rightLength = rightLength;
+ rangeDifference.leftStart = leftStart;
+ rangeDifference.leftLength = leftLength;
+ rangeDifference.ancestorStart = ancestorStart;
+ rangeDifference.ancestorLength = ancestorLength;
+ return rangeDifference;
+ }
+}
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/DifferencesIterator.java b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/DifferencesIterator.java
new file mode 100644
index 000000000..d42c4bda3
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/DifferencesIterator.java
@@ -0,0 +1,77 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2010 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.rangedifferencer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A custom iterator to iterate over a List of <code>RangeDifferences</code>.
+ * It is used internally by the <code>RangeDifferencer</code>.
+ */
+/* package */ class DifferencesIterator {
+
+ List fRange;
+ int fIndex;
+ RangeDifference[] fArray;
+ RangeDifference fDifference;
+
+ /*
+ * Creates a differences iterator on an array of <code>RangeDifference</code>s.
+ */
+ DifferencesIterator(RangeDifference[] differenceRanges) {
+
+ this.fArray= differenceRanges;
+ this.fIndex= 0;
+ this.fRange= new ArrayList();
+ if (this.fIndex < this.fArray.length)
+ this.fDifference= this.fArray[this.fIndex++];
+ else
+ this.fDifference= null;
+ }
+
+ /*
+ * Returns the number of RangeDifferences
+ */
+ int getCount() {
+ return this.fRange.size();
+ }
+
+ /*
+ * Appends the edit to its list and moves to the next <code>RangeDifference</code>.
+ */
+ void next() {
+ this.fRange.add(this.fDifference);
+ if (this.fDifference != null) {
+ if (this.fIndex < this.fArray.length)
+ this.fDifference= this.fArray[this.fIndex++];
+ else
+ this.fDifference= null;
+ }
+ }
+
+ /*
+ * Difference iterators are used in pairs.
+ * This method returns the other iterator.
+ */
+ DifferencesIterator other(DifferencesIterator right, DifferencesIterator left) {
+ if (this == right)
+ return left;
+ return right;
+ }
+
+ /*
+ * Removes all <code>RangeDifference</code>s
+ */
+ void removeAll() {
+ this.fRange.clear();
+ }
+}
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/IRangeComparator.java b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/IRangeComparator.java
new file mode 100644
index 000000000..6af289eef
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/IRangeComparator.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * 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
+ *******************************************************************************/
+package org.eclipse.compare.rangedifferencer;
+
+/**
+ * For breaking an object to compare into a sequence of comparable entities.
+ * <p>
+ * It is used by <code>RangeDifferencer</code> to find longest sequences of
+ * matching and non-matching ranges.
+ * <p>
+ * For example, to compare two text documents and find longest common sequences
+ * of matching and non-matching lines, the implementation must break the document
+ * into lines. <code>getRangeCount</code> would return the number of lines in the
+ * document, and <code>rangesEqual</code> would compare a specified line given
+ * with one in another <code>IRangeComparator</code>.
+ * </p>
+ * <p>
+ * Clients should implement this interface; there is no standard implementation.
+ * </p>
+ */
+public interface IRangeComparator {
+
+ /**
+ * Returns the number of comparable entities.
+ *
+ * @return the number of comparable entities
+ */
+ int getRangeCount();
+
+ /**
+ * Returns whether the comparable entity given by the first index
+ * matches an entity specified by the other <code>IRangeComparator</code> and index.
+ *
+ * @param thisIndex the index of the comparable entity within this <code>IRangeComparator</code>
+ * @param other the IRangeComparator to compare this with
+ * @param otherIndex the index of the comparable entity within the other <code>IRangeComparator</code>
+ * @return <code>true</code> if the comparable entities are equal
+ */
+ boolean rangesEqual(int thisIndex, IRangeComparator other, int otherIndex);
+
+ /**
+ * Returns whether a comparison should be skipped because it would be too costly (or lengthy).
+ *
+ * @param length a number on which to base the decision whether to return
+ * <code>true</code> or <code>false</code>
+ * @param maxLength another number on which to base the decision whether to return
+ * <code>true</code> or <code>false</code>
+ * @param other the other <code>IRangeComparator</code> to compare with
+ * @return <code>true</code> to avoid a too lengthy range comparison
+ */
+ boolean skipRangeComparison(int length, int maxLength, IRangeComparator other);
+}
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/RangeComparatorLCS.java b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/RangeComparatorLCS.java
new file mode 100644
index 000000000..3689800b2
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/RangeComparatorLCS.java
@@ -0,0 +1,192 @@
+/*******************************************************************************
+ * Copyright (c) 2006, 2010 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.rangedifferencer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.compare.internal.core.LCS;
+import org.eclipse.compare.internal.core.Messages;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.core.runtime.SubMonitor;
+
+/* package */ class RangeComparatorLCS extends LCS {
+
+ private final IRangeComparator comparator1, comparator2;
+ private int[][] lcs;
+
+ public static RangeDifference[] findDifferences(AbstractRangeDifferenceFactory factory, IProgressMonitor pm, IRangeComparator left, IRangeComparator right) {
+ RangeComparatorLCS lcs = new RangeComparatorLCS(left, right);
+ SubMonitor monitor = SubMonitor.convert(pm, Messages.RangeComparatorLCS_0, 100);
+ try {
+ lcs.longestCommonSubsequence(monitor.newChild(95));
+ return lcs.getDifferences(monitor.newChild(5), factory);
+ } finally {
+ if (pm != null)
+ pm.done();
+ }
+ }
+
+ public RangeComparatorLCS(IRangeComparator comparator1, IRangeComparator comparator2) {
+ this.comparator1 = comparator1;
+ this.comparator2 = comparator2;
+ }
+
+ protected int getLength1() {
+ return this.comparator1.getRangeCount();
+ }
+
+ protected int getLength2() {
+ return this.comparator2.getRangeCount();
+ }
+
+ protected void initializeLcs(int lcsLength) {
+ this.lcs = new int[2][lcsLength];
+ }
+
+ protected boolean isRangeEqual(int i1, int i2) {
+ return this.comparator1.rangesEqual(i1, this.comparator2, i2);
+ }
+
+ protected void setLcs(int sl1, int sl2) {
+ // Add one to the values so that 0 can mean that the slot is empty
+ this.lcs[0][sl1] = sl1 + 1;
+ this.lcs[1][sl1] = sl2 + 1;
+ }
+
+ public RangeDifference[] getDifferences(SubMonitor subMonitor, AbstractRangeDifferenceFactory factory) {
+ try {
+ List differences = new ArrayList();
+ int length = getLength();
+ if (length == 0) {
+ differences.add(factory.createRangeDifference(RangeDifference.CHANGE, 0, this.comparator2.getRangeCount(), 0, this.comparator1.getRangeCount()));
+ } else {
+ subMonitor.beginTask(null, length);
+ int index1, index2;
+ index1 = index2 = 0;
+ int l1, l2;
+ int s1 = -1;
+ int s2 = -1;
+ while(index1 < this.lcs[0].length && index2 < this.lcs[1].length) {
+ // Move both LCS lists to the next occupied slot
+ while ((l1= this.lcs[0][index1]) == 0) {
+ index1++;
+ if (index1 >= this.lcs[0].length)
+ break;
+ }
+ if (index1 >= this.lcs[0].length)
+ break;
+ while ((l2= this.lcs[1][index2]) == 0) {
+ index2++;
+ if (index2 >= this.lcs[1].length)
+ break;
+ }
+ if (index2 >= this.lcs[1].length)
+ break;
+ // Convert the entry to an array index (see setLcs(int, int))
+ int end1 = l1 - 1;
+ int end2 = l2 - 1;
+ if (s1 == -1 && (end1 != 0 || end2 != 0)) {
+ // There is a diff at the beginning
+ // TODO: We need to conform that this is the proper order
+ differences.add(factory.createRangeDifference(RangeDifference.CHANGE, 0, end2, 0, end1));
+ } else if (end1 != s1 + 1 || end2 != s2 + 1) {
+ // A diff was found on one of the sides
+ int leftStart = s1 + 1;
+ int leftLength = end1 - leftStart;
+ int rightStart = s2 + 1;
+ int rightLength = end2 - rightStart;
+ // TODO: We need to conform that this is the proper order
+ differences.add(factory.createRangeDifference(RangeDifference.CHANGE, rightStart, rightLength, leftStart, leftLength));
+ }
+ s1 = end1;
+ s2 = end2;
+ index1++;
+ index2++;
+ worked(subMonitor, 1);
+ }
+ if (s1 != -1 && (s1 + 1 < this.comparator1.getRangeCount() || s2 + 1 < this.comparator2.getRangeCount())) {
+ // TODO: we need to find the proper way of representing an append
+ int leftStart = s1 < this.comparator1.getRangeCount() ? s1 + 1 : s1;
+ int rightStart = s2 < this.comparator2.getRangeCount() ? s2 + 1 : s2;
+ // TODO: We need to confirm that this is the proper order
+ differences.add(factory.createRangeDifference(RangeDifference.CHANGE, rightStart, this.comparator2.getRangeCount() - (s2 + 1), leftStart, this.comparator1.getRangeCount() - (s1 + 1)));
+ }
+
+ }
+ return (RangeDifference[]) differences.toArray(new RangeDifference[differences.size()]);
+ } finally {
+ subMonitor.done();
+ }
+ }
+
+ private void worked(SubMonitor subMonitor, int work) {
+ if (subMonitor.isCanceled())
+ throw new OperationCanceledException();
+ subMonitor.worked(work);
+ }
+
+ /**
+ * This method takes an LCS result interspersed with zeros (i.e. empty slots
+ * from the LCS algorithm), compacts it and shifts the LCS chunks as far towards
+ * the front as possible. This tends to produce good results most of the time.
+ *
+ * @param lcsSide A subsequence of original, presumably it is the LCS of it and
+ * some other collection of lines
+ * @param length The number of non-empty (i.e non-zero) entries in LCS
+ * @param comparator The comparator used to generate the LCS
+ */
+ private void compactAndShiftLCS(int[] lcsSide, int length,
+ IRangeComparator comparator) {
+ // If the LCS is empty, just return
+ if (length == 0)
+ return;
+ // Skip any leading empty slots
+ int j = 0;
+ while (lcsSide[j] == 0) {
+ j++;
+ }
+ // Put the first non-empty value in position 0
+ lcsSide[0] = lcsSide[j];
+ j++;
+ // Push all non-empty values down into the first N slots (where N is the length)
+ for (int i = 1; i < length; i++) {
+ while (lcsSide[j] == 0) {
+ j++;
+ }
+ // Push the difference down as far as possible by comparing the line at the
+ // start of the diff with the line and the end and adjusting if they are the same
+ int nextLine = lcsSide[i - 1] + 1;
+ if (nextLine != lcsSide[j] && comparator.rangesEqual(nextLine - 1, comparator, lcsSide[j] - 1)) {
+ lcsSide[i] = nextLine;
+ } else {
+ lcsSide[i] = lcsSide[j];
+ }
+ j++;
+ }
+ // Zero all slots after the length
+ for (int i = length; i < lcsSide.length; i++) {
+ lcsSide[i] = 0;
+ }
+ }
+
+ /* (non-Javadoc)
+ * @see org.eclipse.compare.internal.LCS#longestCommonSubsequence(org.eclipse.core.runtime.SubMonitor)
+ */
+ public void longestCommonSubsequence(SubMonitor subMonitor) {
+ super.longestCommonSubsequence(subMonitor);
+ if (this.lcs != null) { // The LCS can be null if one of the sides is empty
+ compactAndShiftLCS(this.lcs[0], getLength(), this.comparator1);
+ compactAndShiftLCS(this.lcs[1], getLength(), this.comparator2);
+ }
+ }
+}
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/RangeDifference.java b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/RangeDifference.java
new file mode 100644
index 000000000..9881bc14a
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/RangeDifference.java
@@ -0,0 +1,326 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2010 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.rangedifferencer;
+
+/**
+ * Description of a change between two or three ranges of comparable entities.
+ * <p>
+ * <code>RangeDifference</code> objects are the elements of a compare result returned from
+ * the <code>RangeDifferencer</code> <code>find* </code> methods.
+ * Clients use these objects as they are returned from the differencer.
+ * This class is not intended to be instantiated outside of the Compare framework.
+ * <p>
+ * Note: A range in the <code>RangeDifference</code> object is given as a start index
+ * and length in terms of comparable entities. However, these entity indices and counts
+ * are not necessarily character positions. For example, if an entity represents a line
+ * in a document, the start index would be a line number and the count would be in lines.
+ * </p>
+ *
+ * @see RangeDifferencer
+ * @noinstantiate This class is not intended to be instantiated by clients.
+ */
+public class RangeDifference {
+
+ /** Two-way change constant indicating no change. */
+ public final static int NOCHANGE = 0;
+
+ /**
+ * Two-way change constant indicating two-way change (same as
+ * <code>RIGHT</code>)
+ */
+ public final static int CHANGE = 2;
+
+ /** Three-way change constant indicating a change in both right and left. */
+ public final static int CONFLICT = 1;
+
+ /** Three-way change constant indicating a change in right. */
+ public final static int RIGHT = 2;
+
+ /** Three-way change constant indicating a change in left. */
+ public final static int LEFT = 3;
+
+ /**
+ * Three-way change constant indicating the same change in both right and
+ * left, that is only the ancestor is different.
+ */
+ public final static int ANCESTOR = 4;
+
+ /** Constant indicating an unknown change kind. */
+ public final static int ERROR = 5;
+
+ /**
+ * the kind of change: NOCHANGE, CHANGE, LEFT, RIGHT, ANCESTOR, CONFLICT,
+ * ERROR
+ *
+ * @since org.eclipse.compare.core 3.5
+ */
+ protected int kind;
+
+ /**
+ * @since org.eclipse.compare.core 3.5
+ */
+ protected int leftStart;
+
+ /**
+ * @since org.eclipse.compare.core 3.5
+ */
+ protected int leftLength;
+
+ /**
+ * @since org.eclipse.compare.core 3.5
+ */
+ protected int rightStart;
+
+ /**
+ * @since org.eclipse.compare.core 3.5
+ */
+ protected int rightLength;
+
+ /**
+ * @since org.eclipse.compare.core 3.5
+ */
+ protected int ancestorStart;
+
+ /**
+ * @since org.eclipse.compare.core 3.5
+ */
+ protected int ancestorLength;
+
+ /**
+ * Creates a new range difference with the given change kind.
+ *
+ * @param changeKind
+ * the kind of change
+ * @since org.eclipse.compare.core 3.5
+ */
+ protected RangeDifference(int changeKind) {
+ this.kind = changeKind;
+ }
+
+ /**
+ * Creates a new <code>RangeDifference</code> with the given change kind and
+ * left and right ranges.
+ *
+ * @param kind
+ * the kind of change
+ * @param rightStart
+ * start index of entity on right side
+ * @param rightLength
+ * number of entities on right side
+ * @param leftStart
+ * start index of entity on left side
+ * @param leftLength
+ * number of entities on left side
+ * @since org.eclipse.compare.core 3.5
+ */
+ protected RangeDifference(int kind, int rightStart, int rightLength, int leftStart, int leftLength) {
+ this.kind= kind;
+ this.rightStart= rightStart;
+ this.rightLength= rightLength;
+ this.leftStart= leftStart;
+ this.leftLength= leftLength;
+ }
+
+ /**
+ * Creates a new <code>RangeDifference</code> with the given change kind and
+ * left, right, and ancestor ranges.
+ *
+ * @param kind
+ * the kind of change
+ * @param rightStart
+ * start index of entity on right side
+ * @param rightLength
+ * number of entities on right side
+ * @param leftStart
+ * start index of entity on left side
+ * @param leftLength
+ * number of entities on left side
+ * @param ancestorStart
+ * start index of entity on ancestor side
+ * @param ancestorLength
+ * number of entities on ancestor side
+ * @since org.eclipse.compare.core 3.5
+ */
+ protected RangeDifference(int kind, int rightStart, int rightLength, int leftStart, int leftLength,
+ int ancestorStart, int ancestorLength) {
+ this(kind, rightStart, rightLength, leftStart, leftLength);
+ this.ancestorStart= ancestorStart;
+ this.ancestorLength= ancestorLength;
+ }
+
+ /**
+ * Returns the kind of difference.
+ *
+ * @return the kind of difference, one of
+ * <code>NOCHANGE</code>, <code>CHANGE</code>, <code>LEFT</code>, <code>RIGHT</code>,
+ * <code>ANCESTOR</code>, <code>CONFLICT</code>, <code>ERROR</code>
+ */
+ public int kind() {
+ return this.kind;
+ }
+
+ /**
+ * Returns the start index of the entity range on the ancestor side.
+ *
+ * @return the start index of the entity range on the ancestor side
+ */
+ public int ancestorStart() {
+ return this.ancestorStart;
+ }
+
+ /**
+ * Returns the number of entities on the ancestor side.
+ *
+ * @return the number of entities on the ancestor side
+ */
+ public int ancestorLength() {
+ return this.ancestorLength;
+ }
+
+ /**
+ * Returns the end index of the entity range on the ancestor side.
+ *
+ * @return the end index of the entity range on the ancestor side
+ */
+ public int ancestorEnd() {
+ return this.ancestorStart + this.ancestorLength;
+ }
+
+ /**
+ * Returns the start index of the entity range on the right side.
+ *
+ * @return the start index of the entity range on the right side
+ */
+ public int rightStart() {
+ return this.rightStart;
+ }
+
+ /**
+ * Returns the number of entities on the right side.
+ *
+ * @return the number of entities on the right side
+ */
+ public int rightLength() {
+ return this.rightLength;
+ }
+
+ /**
+ * Returns the end index of the entity range on the right side.
+ *
+ * @return the end index of the entity range on the right side
+ */
+ public int rightEnd() {
+ return this.rightStart + this.rightLength;
+ }
+
+ /**
+ * Returns the start index of the entity range on the left side.
+ *
+ * @return the start index of the entity range on the left side
+ */
+ public int leftStart() {
+ return this.leftStart;
+ }
+
+ /**
+ * Returns the number of entities on the left side.
+ *
+ * @return the number of entities on the left side
+ */
+ public int leftLength() {
+ return this.leftLength;
+ }
+
+ /**
+ * Returns the end index of the entity range on the left side.
+ *
+ * @return the end index of the entity range on the left side
+ */
+ public int leftEnd() {
+ return this.leftStart + this.leftLength;
+ }
+
+ /**
+ * Returns the maximum number of entities in the left, right, and ancestor sides of this range.
+ *
+ * @return the maximum number of entities in the left, right, and ancestor sides of this range
+ */
+ public int maxLength() {
+ return Math.max(this.rightLength, Math.max(this.leftLength, this.ancestorLength));
+ }
+
+ public boolean equals(Object obj) {
+ if (obj instanceof RangeDifference) {
+ RangeDifference other = (RangeDifference) obj;
+ return this.kind == other.kind
+ && this.leftStart == other.leftStart
+ && this.leftLength == other.leftLength
+ && this.rightStart == other.rightStart
+ && this.rightLength == other.rightLength
+ && this.ancestorStart == other.ancestorStart
+ && this.ancestorLength == other.ancestorLength;
+ }
+ return super.equals(obj);
+ }
+
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + this.kind;
+ result = prime * result + this.leftStart;
+ result = prime * result + this.leftLength;
+ result = prime * result + this.rightStart;
+ result = prime * result + this.rightLength;
+ result = prime * result + this.ancestorStart;
+ result = prime * result + this.ancestorLength;
+ return result;
+ }
+
+ public String toString() {
+ StringBuffer buf = new StringBuffer("RangeDifference {"); //$NON-NLS-1$
+ switch (this.kind) {
+ case NOCHANGE:
+ buf.append("NOCHANGE"); //$NON-NLS-1$
+ break;
+ case CHANGE:
+ buf.append("CHANGE/RIGHT"); //$NON-NLS-1$
+ break;
+ case CONFLICT:
+ buf.append("CONFLICT"); //$NON-NLS-1$
+ break;
+ case LEFT:
+ buf.append("LEFT"); //$NON-NLS-1$
+ break;
+ case ERROR:
+ buf.append("ERROR"); //$NON-NLS-1$
+ break;
+ case ANCESTOR:
+ buf.append("ANCESTOR"); //$NON-NLS-1$
+ break;
+ default:
+ break;
+ }
+
+ buf.append(", "); //$NON-NLS-1$
+
+ buf.append("Left: " + toRangeString(this.leftStart, this.leftLength) + " Right: " + toRangeString(this.rightStart, this.rightLength)); //$NON-NLS-1$ //$NON-NLS-2$
+ if (this.ancestorLength > 0 || this.ancestorStart > 0)
+ buf.append(" Ancestor: " + toRangeString(this.ancestorStart, this.ancestorLength)); //$NON-NLS-1$
+
+ buf.append("}"); //$NON-NLS-1$
+ return buf.toString();
+ }
+
+ private String toRangeString(int start, int length) {
+ return "(" + start + ", " + length + ")"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+}
+
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/RangeDifferencer.java b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/RangeDifferencer.java
new file mode 100644
index 000000000..e39c04d1e
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/RangeDifferencer.java
@@ -0,0 +1,468 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2010 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.rangedifferencer;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.compare.internal.core.Messages;
+import org.eclipse.core.runtime.Assert;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.SubMonitor;
+
+/**
+ * A <code>RangeDifferencer</code> finds the differences between two or three <code>IRangeComparator</code>s.
+ * <p>
+ * To use the differencer, clients provide an <code>IRangeComparator</code>
+ * that breaks their input data into a sequence of comparable entities. The differencer
+ * returns the differences among these sequences as an array of <code>RangeDifference</code> objects
+ * (<code>findDifferences</code> methods).
+ * Every <code>RangeDifference</code> represents a single kind of difference
+ * and the corresponding ranges of the underlying comparable entities in the
+ * left, right, and optionally ancestor sides.
+ * </p>
+ * <p>
+ * Alternatively, the <code>findRanges</code> methods not only return objects for
+ * the differing ranges but for non-differing ranges too.
+ * </p>
+ *
+ * @see IRangeComparator
+ * @see RangeDifference
+ */
+public final class RangeDifferencer {
+
+ private static final RangeDifference[] EMPTY_RESULT= new RangeDifference[0];
+
+ private static final AbstractRangeDifferenceFactory defaultFactory = new AbstractRangeDifferenceFactory() {
+ protected RangeDifference createRangeDifference() {
+ return new RangeDifference(RangeDifference.NOCHANGE);
+ }
+ };
+
+ /* (non Javadoc)
+ * Cannot be instantiated!
+ */
+ private RangeDifferencer() {
+ // nothing to do
+ }
+
+ /**
+ * Finds the differences between two <code>IRangeComparator</code>s.
+ * The differences are returned as an array of <code>RangeDifference</code>s.
+ * If no differences are detected an empty array is returned.
+ *
+ * @param left the left range comparator
+ * @param right the right range comparator
+ * @return an array of range differences, or an empty array if no differences were found
+ */
+ public static RangeDifference[] findDifferences(IRangeComparator left, IRangeComparator right) {
+ return findDifferences((IProgressMonitor)null, left, right);
+ }
+
+ /**
+ * Finds the differences between two <code>IRangeComparator</code>s.
+ * The differences are returned as an array of <code>RangeDifference</code>s.
+ * If no differences are detected an empty array is returned.
+ *
+ * @param pm if not <code>null</code> used to report progress
+ * @param left the left range comparator
+ * @param right the right range comparator
+ * @return an array of range differences, or an empty array if no differences were found
+ * @since 2.0
+ */
+ public static RangeDifference[] findDifferences(IProgressMonitor pm, IRangeComparator left, IRangeComparator right) {
+ return findDifferences(defaultFactory, null, left, right);
+ }
+
+ /**
+ * Finds the differences between two <code>IRangeComparator</code>s.
+ * The differences are returned as an array of <code>RangeDifference</code>s.
+ * If no differences are detected an empty array is returned.
+ *
+ * @param factory
+ * @param pm if not <code>null</code> used to report progress
+ * @param left the left range comparator
+ * @param right the right range comparator
+ * @return an array of range differences, or an empty array if no differences were found
+ * @since org.eclipse.compare.core 3.5
+ */
+ public static RangeDifference[] findDifferences(AbstractRangeDifferenceFactory factory, IProgressMonitor pm, IRangeComparator left, IRangeComparator right) {
+ return RangeComparatorLCS.findDifferences(factory, pm, left, right);
+ }
+
+ /**
+ * Finds the differences among three <code>IRangeComparator</code>s.
+ * The differences are returned as a list of <code>RangeDifference</code>s.
+ * If no differences are detected an empty list is returned.
+ * If the ancestor range comparator is <code>null</code>, a two-way
+ * comparison is performed.
+ *
+ * @param ancestor the ancestor range comparator or <code>null</code>
+ * @param left the left range comparator
+ * @param right the right range comparator
+ * @return an array of range differences, or an empty array if no differences were found
+ */
+ public static RangeDifference[] findDifferences(IRangeComparator ancestor, IRangeComparator left, IRangeComparator right) {
+ return findDifferences(null, ancestor, left, right);
+ }
+
+ /**
+ * Finds the differences among three <code>IRangeComparator</code>s.
+ * The differences are returned as a list of <code>RangeDifference</code>s.
+ * If no differences are detected an empty list is returned.
+ * If the ancestor range comparator is <code>null</code>, a two-way
+ * comparison is performed.
+ *
+ * @param pm if not <code>null</code> used to report progress
+ * @param ancestor the ancestor range comparator or <code>null</code>
+ * @param left the left range comparator
+ * @param right the right range comparator
+ * @return an array of range differences, or an empty array if no differences were found
+ * @since 2.0
+ */
+ public static RangeDifference[] findDifferences(IProgressMonitor pm, IRangeComparator ancestor, IRangeComparator left, IRangeComparator right) {
+ return findDifferences(defaultFactory, pm, ancestor, left, right);
+ }
+
+ /**
+ * Finds the differences among three <code>IRangeComparator</code>s.
+ * The differences are returned as a list of <code>RangeDifference</code>s.
+ * If no differences are detected an empty list is returned.
+ * If the ancestor range comparator is <code>null</code>, a two-way
+ * comparison is performed.
+ *
+ * @param factory
+ * @param pm if not <code>null</code> used to report progress
+ * @param ancestor the ancestor range comparator or <code>null</code>
+ * @param left the left range comparator
+ * @param right the right range comparator
+ * @return an array of range differences, or an empty array if no differences were found
+ * @since org.eclipse.compare.core 3.5
+ */
+ public static RangeDifference[] findDifferences(AbstractRangeDifferenceFactory factory, IProgressMonitor pm, IRangeComparator ancestor, IRangeComparator left, IRangeComparator right) {
+ try {
+ if (ancestor == null)
+ return findDifferences(factory, pm, left, right);
+ SubMonitor monitor = SubMonitor.convert(pm, Messages.RangeComparatorLCS_0, 100);
+ RangeDifference[] leftAncestorScript= null;
+ RangeDifference[] rightAncestorScript= findDifferences(factory, monitor.newChild(50), ancestor, right);
+ if (rightAncestorScript != null) {
+ monitor.setWorkRemaining(100);
+ leftAncestorScript= findDifferences(factory, monitor.newChild(50), ancestor, left);
+ }
+ if (rightAncestorScript == null || leftAncestorScript == null)
+ return null;
+
+ DifferencesIterator myIter= new DifferencesIterator(rightAncestorScript);
+ DifferencesIterator yourIter= new DifferencesIterator(leftAncestorScript);
+
+ List diff3= new ArrayList();
+ diff3.add(factory.createRangeDifference(RangeDifference.ERROR)); // add a sentinel
+
+ int changeRangeStart= 0;
+ int changeRangeEnd= 0;
+ //
+ // Combine the two two-way edit scripts into one
+ //
+ monitor.setWorkRemaining(rightAncestorScript.length + leftAncestorScript.length);
+ while (myIter.fDifference != null || yourIter.fDifference != null) {
+
+ DifferencesIterator startThread;
+ myIter.removeAll();
+ yourIter.removeAll();
+ //
+ // take the next diff that is closer to the start
+ //
+ if (myIter.fDifference == null)
+ startThread= yourIter;
+ else if (yourIter.fDifference == null)
+ startThread= myIter;
+ else { // not at end of both scripts take the lowest range
+ if (myIter.fDifference.leftStart < yourIter.fDifference.leftStart) { // 2 -> common (Ancestor) change range
+ startThread= myIter;
+ } else if (myIter.fDifference.leftStart > yourIter.fDifference.leftStart) {
+ startThread= yourIter;
+ } else {
+ if (myIter.fDifference.leftLength == 0 && yourIter.fDifference.leftLength == 0) {
+ //insertion into the same position is conflict.
+ changeRangeStart= myIter.fDifference.leftStart;
+ changeRangeEnd= myIter.fDifference.leftEnd();
+ myIter.next();
+ yourIter.next();
+ diff3.add(createRangeDifference3(factory, myIter, yourIter, diff3, right, left, changeRangeStart, changeRangeEnd));
+ continue;
+ } else if (myIter.fDifference.leftLength == 0) {
+ //insertion into a position, and modification to the next line, is not conflict.
+ startThread= myIter;
+ } else if (yourIter.fDifference.leftLength == 0) {
+ startThread = yourIter;
+ } else {
+ //modifications to overlapping lines is conflict.
+ startThread= myIter;
+ }
+ }
+
+ }
+ changeRangeStart= startThread.fDifference.leftStart;
+ changeRangeEnd= startThread.fDifference.leftEnd();
+
+ startThread.next();
+ monitor.worked(1);
+ //
+ // check for overlapping changes with other thread
+ // merge overlapping changes with this range
+ //
+ DifferencesIterator other= startThread.other(myIter, yourIter);
+ while (other.fDifference != null && other.fDifference.leftStart < changeRangeEnd) {
+ int newMax= other.fDifference.leftEnd();
+ other.next();
+ monitor.worked(1);
+ if (newMax > changeRangeEnd) {
+ changeRangeEnd= newMax;
+ other= other.other(myIter, yourIter);
+ }
+ }
+ diff3.add(createRangeDifference3(factory, myIter, yourIter, diff3, right, left, changeRangeStart, changeRangeEnd));
+ }
+
+ // remove sentinel
+ diff3.remove(0);
+ return (RangeDifference[]) diff3.toArray(EMPTY_RESULT);
+ } finally {
+ if (pm != null)
+ pm.done();
+ }
+ }
+
+ /**
+ * Finds the differences among two <code>IRangeComparator</code>s.
+ * In contrast to <code>findDifferences</code>, the result
+ * contains <code>RangeDifference</code> elements for non-differing ranges too.
+ *
+ * @param left the left range comparator
+ * @param right the right range comparator
+ * @return an array of range differences
+ */
+ public static RangeDifference[] findRanges(IRangeComparator left,
+ IRangeComparator right) {
+ return findRanges((IProgressMonitor) null, left, right);
+ }
+
+ /**
+ * Finds the differences among two <code>IRangeComparator</code>s.
+ * In contrast to <code>findDifferences</code>, the result
+ * contains <code>RangeDifference</code> elements for non-differing ranges too.
+ *
+ * @param pm if not <code>null</code> used to report progress
+ * @param left the left range comparator
+ * @param right the right range comparator
+ * @return an array of range differences
+ * @since 2.0
+ */
+ public static RangeDifference[] findRanges(IProgressMonitor pm, IRangeComparator left, IRangeComparator right) {
+ return findRanges(defaultFactory, pm, left, right);
+ }
+
+ /**
+ * Finds the differences among two <code>IRangeComparator</code>s.
+ * In contrast to <code>findDifferences</code>, the result
+ * contains <code>RangeDifference</code> elements for non-differing ranges too.
+ *
+ * @param factory
+ * @param pm if not <code>null</code> used to report progress
+ * @param left the left range comparator
+ * @param right the right range comparator
+ * @return an array of range differences
+ * @since org.eclipse.compare.core 3.5
+ */
+ public static RangeDifference[] findRanges(AbstractRangeDifferenceFactory factory, IProgressMonitor pm, IRangeComparator left, IRangeComparator right) {
+ RangeDifference[] in= findDifferences(factory, pm, left, right);
+ List out= new ArrayList();
+
+ RangeDifference rd;
+
+ int mstart= 0;
+ int ystart= 0;
+
+ for (int i= 0; i < in.length; i++) {
+ RangeDifference es= in[i];
+
+ rd= factory.createRangeDifference(RangeDifference.NOCHANGE, mstart, es.rightStart() - mstart, ystart, es.leftStart() - ystart);
+ if (rd.maxLength() != 0)
+ out.add(rd);
+
+ out.add(es);
+
+ mstart= es.rightEnd();
+ ystart= es.leftEnd();
+ }
+ rd= factory.createRangeDifference(RangeDifference.NOCHANGE, mstart, right.getRangeCount() - mstart, ystart, left.getRangeCount() - ystart);
+ if (rd.maxLength() > 0)
+ out.add(rd);
+
+ return (RangeDifference[]) out.toArray(EMPTY_RESULT);
+ }
+
+ /**
+ * Finds the differences among three <code>IRangeComparator</code>s.
+ * In contrast to <code>findDifferences</code>, the result
+ * contains <code>RangeDifference</code> elements for non-differing ranges too.
+ * If the ancestor range comparator is <code>null</code>, a two-way
+ * comparison is performed.
+ *
+ * @param ancestor the ancestor range comparator or <code>null</code>
+ * @param left the left range comparator
+ * @param right the right range comparator
+ * @return an array of range differences
+ */
+ public static RangeDifference[] findRanges(IRangeComparator ancestor, IRangeComparator left, IRangeComparator right) {
+ return findRanges(null, ancestor, left, right);
+ }
+
+ /**
+ * Finds the differences among three <code>IRangeComparator</code>s.
+ * In contrast to <code>findDifferences</code>, the result
+ * contains <code>RangeDifference</code> elements for non-differing ranges too.
+ * If the ancestor range comparator is <code>null</code>, a two-way
+ * comparison is performed.
+ *
+ * @param pm if not <code>null</code> used to report progress
+ * @param ancestor the ancestor range comparator or <code>null</code>
+ * @param left the left range comparator
+ * @param right the right range comparator
+ * @return an array of range differences
+ * @since 2.0
+ */
+ public static RangeDifference[] findRanges(IProgressMonitor pm, IRangeComparator ancestor, IRangeComparator left, IRangeComparator right) {
+ return findRanges(defaultFactory, pm, ancestor, left, right);
+ }
+
+ /**
+ * Finds the differences among three <code>IRangeComparator</code>s.
+ * In contrast to <code>findDifferences</code>, the result
+ * contains <code>RangeDifference</code> elements for non-differing ranges too.
+ * If the ancestor range comparator is <code>null</code>, a two-way
+ * comparison is performed.
+ *
+ * @param factory
+ * @param pm if not <code>null</code> used to report progress
+ * @param ancestor the ancestor range comparator or <code>null</code>
+ * @param left the left range comparator
+ * @param right the right range comparator
+ * @return an array of range differences
+ * @since org.eclipse.compare.core 3.5
+ */
+ public static RangeDifference[] findRanges(AbstractRangeDifferenceFactory factory, IProgressMonitor pm, IRangeComparator ancestor, IRangeComparator left, IRangeComparator right) {
+ if (ancestor == null)
+ return findRanges(factory,pm, left, right);
+
+ RangeDifference[] in= findDifferences(factory, pm, ancestor, left, right);
+ List out= new ArrayList();
+
+ RangeDifference rd;
+
+ int mstart= 0;
+ int ystart= 0;
+ int astart= 0;
+
+ for (int i= 0; i < in.length; i++) {
+ RangeDifference es= in[i];
+
+ rd= factory.createRangeDifference(RangeDifference.NOCHANGE, mstart, es.rightStart() - mstart, ystart, es.leftStart() - ystart, astart, es.ancestorStart() - astart);
+ if (rd.maxLength() > 0)
+ out.add(rd);
+
+ out.add(es);
+
+ mstart= es.rightEnd();
+ ystart= es.leftEnd();
+ astart= es.ancestorEnd();
+ }
+ rd= factory.createRangeDifference(RangeDifference.NOCHANGE, mstart, right.getRangeCount() - mstart, ystart, left.getRangeCount() - ystart, astart, ancestor.getRangeCount() - astart);
+ if (rd.maxLength() > 0)
+ out.add(rd);
+
+ return (RangeDifference[]) out.toArray(EMPTY_RESULT);
+ }
+
+ //---- private methods
+
+ /*
+ * Creates a <code>RangeDifference3</code> given the
+ * state of two DifferenceIterators.
+ */
+ private static RangeDifference createRangeDifference3(AbstractRangeDifferenceFactory configurator, DifferencesIterator myIter, DifferencesIterator yourIter, List diff3,
+ IRangeComparator right, IRangeComparator left, int changeRangeStart, int changeRangeEnd) {
+
+ int rightStart, rightEnd;
+ int leftStart, leftEnd;
+ int kind= RangeDifference.ERROR;
+ RangeDifference last= (RangeDifference) diff3.get(diff3.size() - 1);
+
+ Assert.isTrue((myIter.getCount() != 0 || yourIter.getCount() != 0)); // At least one range array must be non-empty
+ //
+ // find corresponding lines to fChangeRangeStart/End in right and left
+ //
+ if (myIter.getCount() == 0) { // only left changed
+ rightStart= changeRangeStart - last.ancestorEnd() + last.rightEnd();
+ rightEnd= changeRangeEnd - last.ancestorEnd() + last.rightEnd();
+ kind= RangeDifference.LEFT;
+ } else {
+ RangeDifference f= (RangeDifference) myIter.fRange.get(0);
+ RangeDifference l= (RangeDifference) myIter.fRange.get(myIter.fRange.size() - 1);
+ rightStart= changeRangeStart - f.leftStart + f.rightStart;
+ rightEnd= changeRangeEnd - l.leftEnd() + l.rightEnd();
+ }
+
+ if (yourIter.getCount() == 0) { // only right changed
+ leftStart= changeRangeStart - last.ancestorEnd() + last.leftEnd();
+ leftEnd= changeRangeEnd - last.ancestorEnd() + last.leftEnd();
+ kind= RangeDifference.RIGHT;
+ } else {
+ RangeDifference f= (RangeDifference) yourIter.fRange.get(0);
+ RangeDifference l= (RangeDifference) yourIter.fRange.get(yourIter.fRange.size() - 1);
+ leftStart= changeRangeStart - f.leftStart + f.rightStart;
+ leftEnd= changeRangeEnd - l.leftEnd() + l.rightEnd();
+ }
+
+ if (kind == RangeDifference.ERROR) { // overlapping change (conflict) -> compare the changed ranges
+ if (rangeSpansEqual(right, rightStart, rightEnd - rightStart, left, leftStart, leftEnd - leftStart))
+ kind= RangeDifference.ANCESTOR;
+ else
+ kind= RangeDifference.CONFLICT;
+ }
+ return configurator.createRangeDifference(kind, rightStart, rightEnd - rightStart, leftStart, leftEnd - leftStart, changeRangeStart, changeRangeEnd - changeRangeStart);
+ }
+
+ /*
+ * Tests whether <code>right</code> and <code>left</code> changed in the same way
+ */
+ private static boolean rangeSpansEqual(IRangeComparator right, int rightStart, int rightLen, IRangeComparator left, int leftStart, int leftLen) {
+ if (rightLen == leftLen) {
+ int i= 0;
+ for (i= 0; i < rightLen; i++) {
+ if (!rangesEqual(right, rightStart + i, left, leftStart + i))
+ break;
+ }
+ if (i == rightLen)
+ return true;
+ }
+ return false;
+ }
+
+ /*
+ * Tests if two ranges are equal
+ */
+ private static boolean rangesEqual(IRangeComparator a, int ai, IRangeComparator b, int bi) {
+ return a.rangesEqual(ai, b, bi);
+ }
+}
+
diff --git a/bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/package.html b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/package.html
new file mode 100644
index 000000000..8dd70e808
--- /dev/null
+++ b/bundles/org.eclipse.compare.core/src/org/eclipse/compare/rangedifferencer/package.html
@@ -0,0 +1,41 @@
+<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <meta name="Author" content="IBM">
+ <meta name="GENERATOR" content="Mozilla/4.75 [en] (WinNT; U) [Netscape]">
+ <title>Package-level Javadoc</title>
+</head>
+<body>
+Provides support for finding the differences between
+two or three sequences of comparable entities.
+<h2>
+Package Specification</h2>
+
+The class <b>RangeDifferencer</b> finds longest sequences of matching and
+non-matching comparable entities.
+<p>
+
+Clients must supply the input to the differencer as an implementation
+of the <b>IRangeComparator</b> interface.
+An <b>IRangeComparator</b> breaks the input data into a sequence
+of entities and provides a method for comparing
+one entity with the entity in another <b>IRangeComparator</b>.
+<p>
+
+For example, to compare two text documents and find longest common
+sequences of matching and non-matching lines,
+the implementation of <b>IRangeComparator</b>
+must break the document into lines and provide a method for testing
+whether two lines are considered equal.
+See <b>org.eclipse.compare.internal.DocLineComparator</b> for how this can be done.
+<p>
+
+The differencer returns the differences among these sequences as an
+array of <b>RangeDifference</b> objects.
+Every single <b>RangeDifference</b> describes the kind of difference
+(no change, change, addition, deletion) and the corresponding ranges
+of the underlying comparable entities in the two or three inputs.
+
+</body>
+</html>

Back to the top