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