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.io.ByteArrayInputStream; 17 import java.io.InputStream; 18 import java.io.UnsupportedEncodingException; 19 import java.util.ArrayList; 20 import java.util.HashMap; 21 import java.util.List; 22 import java.util.Map; 23 24 import org.eclipse.compare.internal.core.ComparePlugin; 25 import org.eclipse.compare.internal.core.Messages; 26 import org.eclipse.compare.patch.IFilePatchResult; 27 import org.eclipse.compare.patch.IHunk; 28 import org.eclipse.compare.patch.PatchConfiguration; 29 import org.eclipse.compare.patch.ReaderCreator; 30 import org.eclipse.core.runtime.IPath; 31 import org.eclipse.core.runtime.IProgressMonitor; 32 import org.eclipse.core.runtime.NullProgressMonitor; 33 import org.eclipse.osgi.util.NLS; 34 35 public class FileDiffResult implements IFilePatchResult { 36 private FilePatch2 fDiff; 37 private boolean fMatches= false; 38 private boolean fDiffProblem; 39 private String fErrorMessage; 40 private Map<Hunk, HunkResult> fHunkResults = new HashMap<>(); 41 private List<String> fBeforeLines, fAfterLines; 42 private final PatchConfiguration configuration; 43 private String charset; 44 FileDiffResult(FilePatch2 diff, PatchConfiguration configuration)45 public FileDiffResult(FilePatch2 diff, PatchConfiguration configuration) { 46 super(); 47 this.fDiff = diff; 48 this.configuration = configuration; 49 } 50 getConfiguration()51 public PatchConfiguration getConfiguration() { 52 return this.configuration; 53 } 54 canApplyHunk(Hunk hunk)55 public boolean canApplyHunk(Hunk hunk) { 56 HunkResult result = getHunkResult(hunk); 57 return result.isOK() && !this.fDiffProblem; 58 } 59 60 /** 61 * Refreshes the state of the diff to {no matches, no problems} and checks to see what hunks contained 62 * by this Diff can actually be applied. 63 * 64 * Checks to see: 65 * 1) if the target file specified in fNewPath exists and is patchable 66 * 2) which hunks contained by this diff can actually be applied to the file 67 * @param content the contents being patched or <code>null</code> for an addition 68 * @param monitor a progress monitor or <code>null</code> if no progress monitoring is desired 69 */ refresh(ReaderCreator content, IProgressMonitor monitor)70 public void refresh(ReaderCreator content, IProgressMonitor monitor) { 71 this.fMatches= false; 72 this.fDiffProblem= false; 73 boolean create= false; 74 this.charset = Utilities.getCharset(content); 75 //If this diff is an addition, make sure that it doesn't already exist 76 boolean exists = targetExists(content); 77 if (this.fDiff.getDiffType(getConfiguration().isReversed()) == FilePatch2.ADDITION) { 78 if ((!exists || isEmpty(content)) && canCreateTarget(content)) { 79 this.fMatches= true; 80 } else { 81 // file already exists 82 this.fDiffProblem= true; 83 this.fErrorMessage= Messages.FileDiffResult_0; 84 } 85 create= true; 86 } else { //This diff is not an addition, try to find a match for it 87 //Ensure that the file described by the path exists and is modifiable 88 if (exists) { 89 this.fMatches= true; 90 } else { 91 // file doesn't exist 92 this.fDiffProblem= true; 93 this.fErrorMessage= Messages.FileDiffResult_1; 94 } 95 } 96 97 if (this.fDiffProblem) { 98 // We couldn't find the target file or the patch is trying to add a 99 // file that already exists but we need to initialize the hunk 100 // results for display 101 this.fBeforeLines = new ArrayList<>(getLines(content, false)); 102 this.fAfterLines = this.fMatches ? new ArrayList<>() : this.fBeforeLines; 103 IHunk[] hunks = this.fDiff.getHunks(); 104 for (IHunk h : hunks) { 105 Hunk hunk = (Hunk) h; 106 hunk.setCharset(getCharset()); 107 HunkResult result = getHunkResult(hunk); 108 result.setMatches(false); 109 } 110 } else { 111 // If this diff has no problems discovered so far, try applying the patch 112 patch(getLines(content, create), monitor); 113 } 114 115 if (containsProblems()) { 116 if (this.fMatches) { 117 // Check to see if we have at least one hunk that matches 118 this.fMatches = false; 119 IHunk[] hunks = this.fDiff.getHunks(); 120 for (IHunk h : hunks) { 121 Hunk hunk = (Hunk) h; 122 HunkResult result = getHunkResult(hunk); 123 if (result.isOK()) { 124 this.fMatches = true; 125 break; 126 } 127 } 128 } 129 } 130 } 131 canCreateTarget(ReaderCreator content)132 protected boolean canCreateTarget(ReaderCreator content) { 133 return true; 134 } 135 targetExists(ReaderCreator content)136 protected boolean targetExists(ReaderCreator content) { 137 return content != null && content.canCreateReader(); 138 } 139 getLines(ReaderCreator content, boolean create)140 protected List<String> getLines(ReaderCreator content, boolean create) { 141 List<String> lines = LineReader.load(content, create); 142 return lines; 143 } 144 isEmpty(ReaderCreator content)145 protected boolean isEmpty(ReaderCreator content) { 146 if (content == null) 147 return true; 148 return LineReader.load(content, false).isEmpty(); 149 } 150 151 /* 152 * Tries to patch the given lines with the specified Diff. 153 * Any hunk that couldn't be applied is returned in the list failedHunks. 154 */ patch(List<String> lines, IProgressMonitor monitor)155 public void patch(List<String> lines, IProgressMonitor monitor) { 156 this.fBeforeLines = new ArrayList<>(); 157 this.fBeforeLines.addAll(lines); 158 if (getConfiguration().getFuzz() != 0) { 159 calculateFuzz(this.fBeforeLines, monitor); 160 } 161 int shift= 0; 162 IHunk[] hunks = this.fDiff.getHunks(); 163 for (IHunk h : hunks) { 164 Hunk hunk = (Hunk) h; 165 hunk.setCharset(getCharset()); 166 HunkResult result = getHunkResult(hunk); 167 result.setShift(shift); 168 if (result.patch(lines)) { 169 shift = result.getShift(); 170 } 171 } 172 this.fAfterLines = lines; 173 } 174 getDiffProblem()175 public boolean getDiffProblem() { 176 return this.fDiffProblem; 177 } 178 179 /** 180 * Returns whether this Diff has any problems 181 * @return true if this Diff or any of its children Hunks have a problem, false if it doesn't 182 */ containsProblems()183 public boolean containsProblems() { 184 if (this.fDiffProblem) 185 return true; 186 for (HunkResult result : this.fHunkResults.values()) { 187 if (!result.isOK()) 188 return true; 189 } 190 return false; 191 } 192 getLabel()193 public String getLabel() { 194 String label= getTargetPath().toString(); 195 if (this.fDiffProblem) 196 return NLS.bind(Messages.FileDiffResult_2, new String[] {label, this.fErrorMessage}); 197 return label; 198 } 199 200 @Override hasMatches()201 public boolean hasMatches() { 202 return this.fMatches; 203 } 204 205 /** 206 * Return the lines of the target file with all matched hunks applied. 207 * @return the lines of the target file with all matched hunks applied 208 */ getLines()209 public List<String> getLines() { 210 return this.fAfterLines; 211 } 212 213 /** 214 * Calculate the fuzz factor that will allow the most hunks to be matched. 215 * @param lines the lines of the target file 216 * @param monitor a progress monitor 217 * @return the fuzz factor or <code>-1</code> if no hunks could be matched 218 */ calculateFuzz(List<String> lines, IProgressMonitor monitor)219 public int calculateFuzz(List<String> lines, IProgressMonitor monitor) { 220 if (monitor == null) 221 monitor = new NullProgressMonitor(); 222 this.fBeforeLines = new ArrayList<>(lines); 223 // TODO: What about deletions? 224 if (this.fDiff.getDiffType(getConfiguration().isReversed()) == FilePatch2.ADDITION) { 225 // Additions don't need to adjust the fuzz factor 226 // TODO: What about the after lines? 227 return -1; 228 } 229 int shift= 0; 230 int highestFuzz = -1; // the maximum fuzz factor for all hunks 231 String name = getTargetPath() != null ? getTargetPath().lastSegment() : ""; //$NON-NLS-1$ 232 IHunk[] hunks = this.fDiff.getHunks(); 233 for (int j = 0; j < hunks.length; j++) { 234 Hunk h = (Hunk) hunks[j]; 235 monitor.subTask(NLS.bind(Messages.FileDiffResult_3, new String[] {name, Integer.toString(j + 1)})); 236 HunkResult result = getHunkResult(h); 237 result.setShift(shift); 238 int fuzz = result.calculateFuzz(lines, monitor); 239 shift = result.getShift(); 240 if (fuzz > highestFuzz) 241 highestFuzz = fuzz; 242 monitor.worked(1); 243 } 244 this.fAfterLines = lines; 245 return highestFuzz; 246 } 247 getTargetPath()248 public IPath getTargetPath() { 249 return this.fDiff.getStrippedPath(getConfiguration().getPrefixSegmentStripCount(), getConfiguration().isReversed()); 250 } 251 getHunkResult(Hunk hunk)252 private HunkResult getHunkResult(Hunk hunk) { 253 HunkResult result = this.fHunkResults.get(hunk); 254 if (result == null) { 255 result = new HunkResult(this, hunk); 256 this.fHunkResults.put(hunk, result); 257 } 258 return result; 259 } 260 getFailedHunks()261 public List<Hunk> getFailedHunks() { 262 List<Hunk> failedHunks = new ArrayList<>(); 263 IHunk[] hunks = this.fDiff.getHunks(); 264 for (IHunk hunk : hunks) { 265 HunkResult result = this.fHunkResults.get(hunk); 266 if (result != null && !result.isOK()) 267 failedHunks.add(result.getHunk()); 268 } 269 return failedHunks; 270 } 271 getDiff()272 public FilePatch2 getDiff() { 273 return this.fDiff; 274 } 275 getBeforeLines()276 public List<String> getBeforeLines() { 277 return this.fBeforeLines; 278 } 279 getAfterLines()280 public List<String> getAfterLines() { 281 return this.fAfterLines; 282 } 283 getHunkResults()284 public HunkResult[] getHunkResults() { 285 // return hunk results in the same order as hunks are placed in file diff 286 List<HunkResult> results = new ArrayList<>(); 287 IHunk[] hunks = this.fDiff.getHunks(); 288 for (IHunk hunk : hunks) { 289 HunkResult result = this.fHunkResults.get(hunk); 290 if (result != null) { 291 results.add(result); 292 } 293 } 294 return results.toArray(new HunkResult[results.size()]); 295 } 296 297 @Override getOriginalContents()298 public InputStream getOriginalContents() { 299 String contents = LineReader.createString(isPreserveLineDelimeters(), getBeforeLines()); 300 return asInputStream(contents, getCharset()); 301 } 302 303 @Override getPatchedContents()304 public InputStream getPatchedContents() { 305 String contents = LineReader.createString(isPreserveLineDelimeters(), getLines()); 306 return asInputStream(contents, getCharset()); 307 } 308 309 @Override getCharset()310 public String getCharset() { 311 return this.charset; 312 } 313 isPreserveLineDelimeters()314 public boolean isPreserveLineDelimeters() { 315 return false; 316 } 317 318 @Override getRejects()319 public IHunk[] getRejects() { 320 List<Hunk> failedHunks = getFailedHunks(); 321 return failedHunks.toArray(new IHunk[failedHunks.size()]); 322 } 323 324 @Override hasRejects()325 public boolean hasRejects() { 326 return getFailedHunks().size() > 0; 327 } 328 asInputStream(String contents, String charSet)329 public static InputStream asInputStream(String contents, String charSet) { 330 byte[] bytes = null; 331 if (charSet != null) { 332 try { 333 bytes = contents.getBytes(charSet); 334 } catch (UnsupportedEncodingException e) { 335 ComparePlugin.log(e); 336 } 337 } 338 if (bytes == null) { 339 bytes = contents.getBytes(); 340 } 341 return new ByteArrayInputStream(bytes); 342 } 343 344 } 345