| /******************************************************************************* |
| * 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; |
| } |
| } |
| |
| } |