blob: ab64d83a3e488640dfe7075d47d72181ccf15cc0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2011 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* 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<Hunk, HunkResult> fHunkResults = new HashMap<>();
private List<String> 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<String> getLines(ReaderCreator content, boolean create) {
List<String> 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<String> 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<HunkResult> iterator = this.fHunkResults.values().iterator(); iterator.hasNext();) {
HunkResult result = 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;
}
@Override
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<String> 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<String> 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 = this.fHunkResults.get(hunk);
if (result == null) {
result = new HunkResult(this, hunk);
this.fHunkResults.put(hunk, result);
}
return result;
}
public List<Hunk> getFailedHunks() {
List<Hunk> failedHunks = new ArrayList<>();
IHunk[] hunks = this.fDiff.getHunks();
for (int i = 0; i < hunks.length; i++) {
HunkResult result = this.fHunkResults.get(hunks[i]);
if (result != null && !result.isOK())
failedHunks.add(result.getHunk());
}
return failedHunks;
}
public FilePatch2 getDiff() {
return this.fDiff;
}
public List<String> getBeforeLines() {
return this.fBeforeLines;
}
public List<String> getAfterLines() {
return this.fAfterLines;
}
public HunkResult[] getHunkResults() {
// return hunk results in the same order as hunks are placed in file diff
List<HunkResult> results = new ArrayList<>();
IHunk[] hunks = this.fDiff.getHunks();
for (int i = 0; i < hunks.length; i++) {
HunkResult result = this.fHunkResults.get(hunks[i]);
if (result != null) {
results.add(result);
}
}
return results.toArray(new HunkResult[results.size()]);
}
@Override
public InputStream getOriginalContents() {
String contents = LineReader.createString(isPreserveLineDelimeters(), getBeforeLines());
return asInputStream(contents, getCharset());
}
@Override
public InputStream getPatchedContents() {
String contents = LineReader.createString(isPreserveLineDelimeters(), getLines());
return asInputStream(contents, getCharset());
}
@Override
public String getCharset() {
return this.charset;
}
public boolean isPreserveLineDelimeters() {
return false;
}
@Override
public IHunk[] getRejects() {
List<Hunk> failedHunks = getFailedHunks();
return failedHunks.toArray(new IHunk[failedHunks.size()]);
}
@Override
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);
}
}