blob: ac881a04eaed743e9ff29be12e6443e08ad3ff1e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009, 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.patch;
import java.util.Arrays;
import java.util.Comparator;
import org.eclipse.compare.internal.core.patch.FilePatch2;
import org.eclipse.compare.internal.core.patch.Hunk;
import org.eclipse.core.runtime.IPath;
/**
* Builder for creating IFilePatch2 and IHunk objects as well as building
* relationship between them.
*
* @noextend This class is not intended to be subclassed by clients.
* @noinstantiate This class is not intended to be instantiated by clients.
*
* @since org.eclipse.compare.core 3.5
*/
public class PatchBuilder {
/**
* Line prefix used to mark context lines.
*/
public static final char CONTEXT_PREFIX = ' ';
/**
* Line prefix used to mark an added lines.
*/
public static final char ADDITION_PREFIX = '+';
/**
* Line prefix used to mark an removed lines.
*/
public static final char REMOVAL_PREFIX = '-';
/**
* Creates an IHunk instance.
*
* @param start
* the start position in the before file
* @param lines
* content of the 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>
* @return IHunk instance
*/
public static IHunk createHunk(int start, String[] lines) {
int type = getHunkType(lines);
int oldLength = getHunkLength(lines, true);
int newLength = getHunkLength(lines, false);
return new Hunk(null, type, start, oldLength, start, newLength, lines);
}
/**
* Creates an IFilePatch2 instance and performs recalculation of all hunks'
* after positions. Hunk's after position is position in the file state
* after applying a patch. It is affected by all the hunks that are to be
* applied before a given one. This recalculation is necessary to keep
* IFilePatch2's state coherent.
*
* @param oldPath
* the path of the before state of the file
* @param oldDate
* the timestamp of the before state of the file, see also
* {@link IFilePatch2#DATE_UNKNOWN}
* @param newPath
* the path of the after state of the file
* @param newDate
* the timestamp of the after state of the file, see also
* {@link IFilePatch2#DATE_UNKNOWN}
* @param hunks
* a set of hunks to insert into IFilePatch2
* @return IFilePatch2 instance
*/
public static IFilePatch2 createFilePatch(IPath oldPath, long oldDate,
IPath newPath, long newDate, IHunk[] hunks) {
reorder(hunks);
FilePatch2 fileDiff = new FilePatch2(oldPath, oldDate, newPath, newDate);
for (int i = 0; i < hunks.length; i++) {
fileDiff.add((Hunk) hunks[i]);
}
return fileDiff;
}
/**
* Adds IHunks to a given IFilePatch2 and performs recalculation of all
* hunks' after positions. Hunk's after position is position in the file
* state after applying a patch. It is affected by all the hunks that are to
* be applied before a given one. This recalculation is necessary to keep
* IFilePatch2's state coherent.
*
* @param filePatch
* a file patch to add hunks to
* @param toAdd
* a set of IHunks to add
* @return newly created file patch with added hunks
*/
public static IFilePatch2 addHunks(IFilePatch2 filePatch, IHunk[] toAdd) {
IHunk[] result = addHunks(filePatch.getHunks(), toAdd);
reorder(result);
return createFilePatch(filePatch, result);
}
/**
* Removes IHunks from a given IFilePatch2 and performs recalculation of all
* hunks' after positions. Hunk's after position is position in the file
* state after applying a patch. It is affected by all the hunks that are to
* be applied before a given one. This recalculation is necessary to keep
* IFilePatch2's state coherent.
*
* @param filePatch
* a file patch to add hunks to
* @param toRemove
* a set of IHunks to add
* @return newly created file patch with removed hunks
*/
public static IFilePatch2 removeHunks(IFilePatch2 filePatch,
IHunk[] toRemove) {
IHunk[] result = removeHunks(filePatch.getHunks(), toRemove);
reorder(result);
return createFilePatch(filePatch, result);
}
private static IFilePatch2 createFilePatch(IFilePatch2 filePatch,
IHunk[] hunks) {
PatchConfiguration config = new PatchConfiguration();
IPath beforePath = filePatch.getTargetPath(config);
config.setReversed(true);
IPath afterPath = filePatch.getTargetPath(config);
return createFilePatch(beforePath, filePatch.getBeforeDate(),
afterPath, filePatch.getAfterDate(), hunks);
}
private static int getHunkType(String[] lines) {
boolean hasContextLines = checkForPrefix(CONTEXT_PREFIX, lines);
if (!hasContextLines) {
boolean hasLineAdditions = checkForPrefix(ADDITION_PREFIX, lines);
boolean hasLineDeletions = checkForPrefix(REMOVAL_PREFIX, lines);
if (hasLineAdditions && !hasLineDeletions) {
return FilePatch2.ADDITION;
} else if (!hasLineAdditions && hasLineDeletions) {
return FilePatch2.DELETION;
}
}
return FilePatch2.CHANGE;
}
private static int getHunkLength(String[] lines, boolean old) {
int length = 0;
for (int i = 0; i < lines.length; i++) {
if (lines[i].length() > 0) {
switch (lines[i].charAt(0)) {
case ' ':
length++;
break;
case '+':
if (!old) {
length++;
}
break;
case '-':
if (old) {
length++;
}
break;
default:
throw new IllegalArgumentException(""); //$NON-NLS-1$
}
}
}
return length;
}
private static boolean checkForPrefix(char prefix, String[] lines) {
for (int i = 0; i < lines.length; i++) {
if (lines[i].length() > 0) {
if (lines[i].charAt(0) == prefix) {
return true;
}
}
}
return false;
}
private static IHunk[] addHunks(IHunk[] hunks, IHunk[] toAdd) {
IHunk[] ret = new IHunk[hunks.length + toAdd.length];
System.arraycopy(hunks, 0, ret, 0, hunks.length);
System.arraycopy(toAdd, 0, ret, hunks.length, toAdd.length);
return ret;
}
private static IHunk[] removeHunks(IHunk[] hunks, IHunk[] toRemove) {
int removed = 0;
for (int i = 0; i < hunks.length; i++) {
for (int j = 0; j < toRemove.length; j++) {
if (toRemove[j] == hunks[i]) {
hunks[i] = null;
removed++;
}
}
}
IHunk[] ret = new IHunk[hunks.length - removed];
for (int i = 0, j = 0; i < hunks.length; i++) {
if (hunks[i] != null) {
ret[j++] = hunks[i];
}
}
return ret;
}
private static void reorder(IHunk[] hunks) {
Arrays.sort(hunks, new HunkComparator());
int shift = 0;
for (int i = 0; i < hunks.length; i++) {
Hunk hunk = (Hunk) hunks[i];
int start = hunk.getStart(false) + shift;
hunk.setStart(start, true);
shift += hunk.getLength(true) - hunk.getLength(false);
}
}
static class HunkComparator implements Comparator {
@Override
public int compare(Object arg0, Object arg1) {
if ((arg0 != null && arg0 instanceof Hunk)
&& (arg1 != null && arg1 instanceof Hunk)) {
Hunk hunk0 = (Hunk) arg0;
Hunk hunk1 = (Hunk) arg1;
int shift = hunk0.getStart(true) - hunk1.getStart(true);
return shift;
}
return 0;
}
}
}