blob: c39bbaf87a211c74fff341ac4f20d4458d4d21ac [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2007 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.patch;
import java.util.List;
import org.eclipse.compare.patch.PatchConfiguration;
import org.eclipse.core.runtime.Assert;
/**
* A Hunk describes a range of changed lines and some context lines.
*/
public class Hunk {
public static final int ADDED = 0x1;
public static final int DELETED = 0x2;
public static final int CHANGED = 0x4;
public static final int UNKNOWN = 0x8;
private FileDiff fParent;
private int fOldStart, fOldLength;
private int fNewStart, fNewLength;
private String[] fLines;
private int hunkType;
public Hunk(FileDiff parent, Hunk toCopy) {
fParent = parent;
if (fParent != null) {
fParent.add(this);
}
fOldStart = toCopy.fOldStart;
fOldLength = toCopy.fOldLength;
fNewStart = toCopy.fNewStart;
fNewLength = toCopy.fOldLength;
fLines = toCopy.fLines;
hunkType = toCopy.hunkType;
}
public Hunk(FileDiff parent, int[] oldRange, int[] newRange, List lines, boolean encounteredPlus, boolean encounteredMinus, boolean encounteredSpace) {
fParent= parent;
if (fParent != null)
fParent.add(this);
if (oldRange[0] > 0)
fOldStart= oldRange[0]-1; // line number start at 0!
else
fOldStart= 0;
fOldLength= oldRange[1];
if (newRange[0] > 0)
fNewStart= newRange[0]-1; // line number start at 0!
else
fNewStart= 0;
fNewLength= newRange[1];
fLines= (String[]) lines.toArray(new String[lines.size()]);
if (encounteredSpace && (encounteredPlus || encounteredMinus)){
hunkType = CHANGED;
} else if (encounteredPlus && !encounteredMinus && !encounteredSpace){
hunkType = ADDED;
} else if (!encounteredPlus && encounteredMinus && !encounteredSpace) {
hunkType = DELETED;
} else {
hunkType = UNKNOWN;
}
}
/*
* Returns the contents of this hunk.
* Each line starts with a control character. Their meaning is as follows:
* <ul>
* <li>
* '+': add the line
* <li>
* '-': delete the line
* <li>
* ' ': no change, context line
* </ul>
*/
String getContent() {
StringBuffer sb= new StringBuffer();
for (int i= 0; i < fLines.length; i++) {
String line= fLines[i];
sb.append(line.substring(0, Patcher.length(line)));
sb.append('\n');
}
return sb.toString();
}
/*
* Returns a descriptive String for this hunk.
* It is in the form old_start,old_length -> new_start,new_length.
*/
String getDescription() {
StringBuffer sb= new StringBuffer();
sb.append(Integer.toString(fOldStart));
sb.append(',');
sb.append(Integer.toString(fOldLength));
sb.append(" -> "); //$NON-NLS-1$
sb.append(Integer.toString(fNewStart));
sb.append(',');
sb.append(Integer.toString(fNewLength));
return sb.toString();
}
String getRejectedDescription() {
StringBuffer sb= new StringBuffer();
sb.append("@@ -"); //$NON-NLS-1$
sb.append(Integer.toString(fOldStart));
sb.append(',');
sb.append(Integer.toString(fOldLength));
sb.append(" +"); //$NON-NLS-1$
sb.append(Integer.toString(fNewStart));
sb.append(',');
sb.append(Integer.toString(fNewLength));
sb.append(" @@"); //$NON-NLS-1$
return sb.toString();
}
int getHunkType(boolean reverse) {
if (reverse) {
if (hunkType == ADDED)
return DELETED;
if (hunkType == DELETED)
return ADDED;
}
return hunkType;
}
void setHunkType(int hunkType) {
this.hunkType = hunkType;
}
public String[] getLines() {
return fLines;
}
/**
* Set the parent of this hunk. This method
* should only be invoked from {@link FileDiff#add(Hunk)}
* @param diff the parent of this hunk
*/
void setParent(FileDiff diff) {
if (fParent == diff)
return;
if (fParent != null)
fParent.remove(this);
fParent = diff;
}
public FileDiff getParent() {
return fParent;
}
/*
* Tries to apply the given hunk on the specified lines.
* The parameter shift is added to the line numbers given
* in the hunk.
*/
public boolean tryPatch(PatchConfiguration configuration, List lines, int shift) {
boolean reverse = configuration.isReversed();
int pos= getStart(reverse) + shift;
int deleteMatches= 0;
for (int i= 0; i < fLines.length; i++) {
String s= fLines[i];
Assert.isTrue(s.length() > 0);
String line= s.substring(1);
char controlChar= s.charAt(0);
if (controlChar == ' ') { // context lines
while (true) {
if (pos < 0 || pos >= lines.size())
return false;
if (linesMatch(configuration, line, (String) lines.get(pos))) {
pos++;
break;
}
return false;
}
} else if (isDeletedDelimeter(controlChar, reverse)) {
// deleted lines
while (true) {
if (pos < 0 || pos >= lines.size())
return false;
if (linesMatch(configuration, line, (String) lines.get(pos))) {
deleteMatches++;
pos++;
break;
}
// We must remove all lines at once, return false if this
// fails. In other words, all lines considered for deletion
// must be found one by one.
// if (deleteMatches <= 0)
return false;
// pos++;
}
} else if (isAddedDelimeter(controlChar, reverse)) {
// added lines
// we don't have to do anything for a 'try'
} else
Assert.isTrue(false, "tryPatch: unknown control character: " + controlChar); //$NON-NLS-1$
}
return true;
}
int getStart(boolean reverse) {
if (reverse) {
return fNewStart;
}
return fOldStart;
}
private int getLength(boolean reverse) {
if (reverse) {
return fNewLength;
}
return fOldLength;
}
private int getShift(boolean reverse) {
if (reverse) {
return fOldLength - fNewLength;
}
return fNewLength - fOldLength;
}
int doPatch(PatchConfiguration configuration, List lines, int shift) {
boolean reverse = configuration.isReversed();
int pos = getStart(reverse) + shift;
for (int i= 0; i < fLines.length; i++) {
String s= fLines[i];
Assert.isTrue(s.length() > 0);
String line= s.substring(1);
char controlChar= s.charAt(0);
if (controlChar == ' ') { // context lines
while (true) {
Assert.isTrue(pos < lines.size(), "doPatch: inconsistency in context"); //$NON-NLS-1$
if (linesMatch(configuration, line, (String) lines.get(pos))) {
pos++;
break;
}
pos++;
}
} else if (isDeletedDelimeter(controlChar, reverse)) {
// deleted lines
while (true) {
Assert.isTrue(pos < lines.size(), "doPatch: inconsistency in deleted lines"); //$NON-NLS-1$
if (linesMatch(configuration, line, (String) lines.get(pos))) {
break;
}
pos++;
}
lines.remove(pos);
} else if (isAddedDelimeter(controlChar, reverse)) {
// added lines
if (getLength(reverse) == 0 && pos+1 < lines.size())
lines.add(pos+1, line);
else
lines.add(pos, line);
pos++;
} else
Assert.isTrue(false, "doPatch: unknown control character: " + controlChar); //$NON-NLS-1$
}
return getShift(reverse);
}
private boolean isDeletedDelimeter(char controlChar, boolean reverse) {
return (!reverse && controlChar == '-') || (reverse && controlChar == '+');
}
private boolean isAddedDelimeter(char controlChar, boolean reverse) {
return (reverse && controlChar == '-') || (!reverse && controlChar == '+');
}
/*
* Compares two strings.
* If fIgnoreWhitespace is true whitespace is ignored.
*/
private boolean linesMatch(PatchConfiguration configuration, String line1, String line2) {
if (configuration.isIgnoreWhitespace())
return stripWhiteSpace(line1).equals(stripWhiteSpace(line2));
if (isIgnoreLineDelimiter()) {
int l1= Patcher.length(line1);
int l2= Patcher.length(line2);
if (l1 != l2)
return false;
return line1.regionMatches(0, line2, 0, l1);
}
return line1.equals(line2);
}
private boolean isIgnoreLineDelimiter() {
return true;
}
/*
* Returns the given string with all whitespace characters removed.
* Whitespace is defined by <code>Character.isWhitespace(...)</code>.
*/
private String stripWhiteSpace(String s) {
StringBuffer sb= new StringBuffer();
int l= s.length();
for (int i= 0; i < l; i++) {
char c= s.charAt(i);
if (!Character.isWhitespace(c))
sb.append(c);
}
return sb.toString();
}
public String getContents(boolean isAfterState, boolean reverse) {
StringBuffer result= new StringBuffer();
for (int i= 0; i<fLines.length; i++) {
String line= fLines[i];
String rest= line.substring(1);
char c = line.charAt(0);
if (c == ' ') {
result.append(rest);
} else if (isDeletedDelimeter(c, reverse) && !isAfterState) {
result.append(rest);
} else if (isAddedDelimeter(c, reverse) && isAfterState) {
result.append(rest);
}
}
return result.toString();
}
}