1 /*******************************************************************************
2  * Copyright (c) 2006, 2011 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.compare.internal.core.patch;
15 
16 import java.util.List;
17 
18 import org.eclipse.compare.patch.IHunkFilter;
19 import org.eclipse.compare.patch.PatchConfiguration;
20 import org.eclipse.core.runtime.IProgressMonitor;
21 import org.eclipse.core.runtime.OperationCanceledException;
22 
23 public class HunkResult {
24 
25 	private static final boolean DEBUG= false;
26 
27 	/**
28 	 * Default maximum fuzz factor equals 2. This is related to the default
29 	 * number of context lines, which is 3.
30 	 */
31 	private static final int MAXIMUM_FUZZ_FACTOR = 2;
32 
33 	private Hunk fHunk;
34 	private boolean fMatches;
35 	private int fShift;
36 	private int fFuzz = -1; // not set or couldn't be found
37 
38 	private final FileDiffResult fDiffResult;
39 
40 	/**
41 	 * Create a hunk result for the given hunk
42 	 * @param diffResult the parent diff result
43 	 * @param hunk the hunk
44 	 */
HunkResult(FileDiffResult diffResult, Hunk hunk)45 	public HunkResult(FileDiffResult diffResult, Hunk hunk) {
46 		this.fDiffResult = diffResult;
47 		this.fHunk = hunk;
48 	}
49 
50 	/**
51 	 * Try to apply the specified hunk to the given lines.
52 	 * If the hunk cannot be applied at the original position
53 	 * the method tries shift lines up and down.
54 	 * @param lines the lines to be patched
55 	 * @return whether the hunk could be applied
56 	 */
patch(List<String> lines)57 	public boolean patch(List<String> lines) {
58 		this.fMatches = false;
59 		PatchConfiguration configuration = getConfiguration();
60 		// if the fuzz is not set for the current hunk use the one from fDiffResult
61 		int fuzz = this.fFuzz != -1 ? this.fFuzz : configuration.getFuzz();
62 		if (isEnabled(configuration)) {
63 			if (this.fHunk.tryPatch(configuration, lines, this.fShift, fuzz)) {
64 				// it's a perfect match, no shifting is needed
65 				this.fShift += this.fHunk.doPatch(configuration, lines, this.fShift, fuzz);
66 				this.fMatches = true;
67 			} else {
68 				boolean found= false;
69 				int oldShift= this.fShift;
70 
71 				int hugeShift = lines.size();
72 				for (int i = 1; i <= hugeShift; i++) {
73 					if (this.fHunk.tryPatch(configuration, lines, this.fShift - i, fuzz)) {
74 						if (isAdjustShift())
75 							this.fShift -= i;
76 						found = true;
77 						break;
78 					}
79 				}
80 
81 				if (!found) {
82 					for (int i = 1; i <= hugeShift; i++) {
83 						if (this.fHunk.tryPatch(configuration, lines, this.fShift + i, fuzz)) {
84 							if (isAdjustShift())
85 								this.fShift += i;
86 							found = true;
87 							break;
88 						}
89 					}
90 				}
91 
92 				if (found) {
93 					if (DEBUG) System.out.println("patched hunk at offset: " + (this.fShift-oldShift)); //$NON-NLS-1$
94 					this.fShift+= this.fHunk.doPatch(configuration, lines, this.fShift, fuzz);
95 					this.fMatches = true;
96 				}
97 			}
98 		}
99 		return this.fMatches;
100 	}
101 
isAdjustShift()102 	private boolean isAdjustShift() {
103 		return true;
104 	}
105 
getConfiguration()106 	private PatchConfiguration getConfiguration() {
107 		return getDiffResult().getConfiguration();
108 	}
109 
110 	/**
111 	 * Calculate the fuzz that will allow the most hunks to be matched. Even
112 	 * though we're interested only in the value of the fuzz, the shifting is
113 	 * done anyway.
114 	 *
115 	 * @param lines
116 	 *            the lines of the target file
117 	 * @param monitor
118 	 *            a progress monitor
119 	 * @return the fuzz factor or -1 if the hunk could not be matched
120 	 */
calculateFuzz(List<String> lines, IProgressMonitor monitor)121 	public int calculateFuzz(List<String> lines, IProgressMonitor monitor) {
122 		this.fMatches = false;
123 		PatchConfiguration configuration = getConfiguration();
124 		int fuzz = 0;
125 		int maxFuzz = configuration.getFuzz() == -1 ? MAXIMUM_FUZZ_FACTOR
126 				: configuration.getFuzz();
127 		for (; fuzz <= maxFuzz; fuzz++) {
128 			// try to apply using lines coordinates from the patch
129 			if (this.fHunk.tryPatch(configuration, lines, this.fShift, fuzz)) {
130 				// it's a perfect match, no adjustment is needed
131 				this.fShift += this.fHunk.doPatch(configuration, lines, this.fShift, fuzz);
132 				this.fMatches = true;
133 				break;
134 			}
135 
136 			// TODO (tzarna): hugeShift=lines.size() is more than we need.
137 			// Lines to the beg/end of a file would be enough but this can still
138 			// in matching hunks out of order. Try to shift using only lines
139 			// available "between" hunks.
140 			int hugeShift = lines.size();
141 
142 			// shift up
143 			for (int i = 1; i <= hugeShift; i++) {
144 				if (monitor.isCanceled()) {
145 					throw new OperationCanceledException();
146 				}
147 				if (this.fHunk.tryPatch(configuration, lines, this.fShift - i, fuzz)) {
148 					if (isAdjustShift())
149 						this.fShift -= i;
150 					this.fMatches = true;
151 					break;
152 				}
153 			}
154 
155 			// shift down
156 			if (!this.fMatches) {
157 				for (int i = 1; i <= hugeShift; i++) {
158 					if (monitor.isCanceled()) {
159 						throw new OperationCanceledException();
160 					}
161 					if (this.fHunk.tryPatch(configuration, lines, this.fShift + i, fuzz)) {
162 						if (isAdjustShift())
163 							this.fShift += i;
164 						this.fMatches = true;
165 						break;
166 					}
167 				}
168 			}
169 
170 			if (this.fMatches) {
171 				this.fShift += this.fHunk.doPatch(configuration, lines, this.fShift, fuzz);
172 				break;
173 			}
174 		}
175 		// set fuzz for the current hunk
176 		this.fFuzz = this.fMatches ? fuzz : -1;
177 		return this.fFuzz;
178 	}
179 
180 	/**
181 	 * Return the amount that this hunk should be shifted when a match with the file
182 	 * is attempted. The shift is needed to compensate for previous hunks that have
183 	 * been applied.
184 	 * @return the amount that this hunk should be shifted when applied
185 	 */
getShift()186 	public int getShift() {
187 		return this.fShift;
188 	}
189 
190 	/**
191 	 * Set the amount that this hunk should be shifted when a match with the file
192 	 * is attempted. The shift is needed to compensate for previous hunks that have
193 	 * been applied.
194 	 * @param shift the amount to shift this hunk
195 	 */
setShift(int shift)196 	public void setShift(int shift) {
197 		this.fShift = shift;
198 	}
199 
200 	/**
201 	 * Return the hunk to which this result applies.
202 	 * @return the hunk to which this result applies
203 	 */
getHunk()204 	public Hunk getHunk() {
205 		return this.fHunk;
206 	}
207 
208 	/**
209 	 * Return the parent diff result.
210 	 * @return the parent diff result
211 	 */
getDiffResult()212 	public FileDiffResult getDiffResult() {
213 		return this.fDiffResult;
214 	}
215 
216 	/**
217 	 * Return whether the hunk was matched with the target file.
218 	 * @return whether the hunk was matched with the target file
219 	 */
isOK()220 	public boolean isOK() {
221 		return this.fMatches;
222 	}
223 
224 	/**
225 	 * Return the contents that should be displayed for the hunk result.
226 	 * @param afterState whether the after state or before state of the hunk is desired
227 	 * @param fullContext whether the hunk should be displayed with the entire file or
228 	 * only the lines in the hunk itself
229 	 * @return the contents to be display
230 	 */
getContents(boolean afterState, boolean fullContext)231 	public String getContents(boolean afterState, boolean fullContext) {
232 		if (fullContext) {
233 			boolean problemFound = false;
234 			List<String> lines = getDiffResult().getBeforeLines();
235 			if (afterState) {
236 				if (isOK()) {
237 					int oldShift = this.fShift;
238 					try {
239 						this.fShift = 0;
240 						problemFound = !patch(lines);
241 					} finally {
242 						this.fShift = oldShift;
243 					}
244 				} else {
245 					problemFound = true;
246 				}
247 			}
248 			// Only return the full context if we could apply the hunk
249 			if (!problemFound)
250 				return LineReader.createString(this.fDiffResult.isPreserveLineDelimeters(), lines);
251 		}
252 		return getHunk().getContents(afterState, getConfiguration().isReversed());
253 	}
254 
isEnabled(PatchConfiguration configuration)255 	private boolean isEnabled(PatchConfiguration configuration) {
256 		IHunkFilter[] filters = configuration.getHunkFilters();
257 		for (IHunkFilter filter : filters) {
258 			if (!filter.select(this.fHunk)) {
259 				return false;
260 			}
261 		}
262 		return true;
263 	}
264 
setMatches(boolean matches)265 	public void setMatches(boolean matches) {
266 		this.fMatches = matches;
267 	}
268 
getCharset()269 	public String getCharset() {
270 		return this.fDiffResult.getCharset();
271 	}
272 
getFuzz()273 	public int getFuzz() {
274 		return this.fFuzz;
275 	}
276 }
277