blob: f73ffa44441f463849b100fc95c52adbcfaa2608 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2006, 2011 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<String> 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<String> 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<String> 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;
}
}