/** * Copyright (c) 2009--2012 Red Hat, Inc. * * This software is licensed to you under the GNU General Public License, * version 2 (GPLv2). There is NO WARRANTY for this software, express or * implied, including the implied warranties of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2 * along with this software; if not, see * http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. * * Red Hat trademarks are not licensed under GPLv2. No permission is * granted to use or replicate Red Hat trademarks that are incorporated * in this software or its documentation. */ package com.redhat.rhn.common.filediff; import java.util.Date; import java.util.Iterator; import java.util.List; /** * Converts a list of hunks from a file diff to a unified output format * for use with the Unix patch tool. * @version $Rev$ */ public class RhnPatchDiffWriter implements DiffVisitor, DiffWriter { private static final int DEFAULT_CONTEXT_LINES = 3; private static final char FROM_LABEL = '-'; private static final char TO_LABEL = '+'; private static final char MATCH_LABEL = ' '; private static final String HUNK_LABEL = "@@"; //diff the entire result private final StringBuffer diff; private final int contextLines; //stores the current edit, which can consist of multiple hunks with context lines. private EditPoint currentEdit; //needed when the last hunk is not a MatchHunk private int oldEndLine; private int newEndLine; /** * @param fromPath The from(old, first) file's path * @param toPath The to(new, second) file's path * @param fromDate The from(old, first) file's last modified date. * @param toDate The to(new, second) file's last modified date. */ public RhnPatchDiffWriter(String fromPath, String toPath, Date fromDate, Date toDate) { diff = new StringBuffer(); String dateString = fromDate.toString(); //TODO: format the date writeHeader(FROM_LABEL, fromPath, dateString); dateString = toDate.toString(); writeHeader(TO_LABEL, toPath, dateString); contextLines = DEFAULT_CONTEXT_LINES; currentEdit = null; } private void writeHeader(char label, String path, String date) { //show the label three times for (int i = 0; i < 3; i++) { diff.append(label); } diff.append(" "); diff.append(path); diff.append("\t"); //just doing what GNU diff does. diff.append(date); diff.append("\n"); } /** * {@inheritDoc} */ public void writeHunk(Hunk hunkIn) { hunkIn.visit(this); } /** * {@inheritDoc} */ public void accept(ChangeHunk hunk) { processEditHunk(hunk); } /** * {@inheritDoc} */ public void accept(DeleteHunk hunk) { processEditHunk(hunk); } /** * {@inheritDoc} */ public void accept(InsertHunk hunk) { processEditHunk(hunk); } private void processEditHunk(Hunk hunk) { //This should only ever happen if this edit hunk is the very first hunk. if (currentEdit == null) { int newStartLine = hunk.getNewLines().getFromLine(); int oldStartLine = hunk.getOldLines().getFromLine(); currentEdit = new EditPoint(oldStartLine, newStartLine); } //according to GNU patch, the order doesn't matter, but GNU diff //always shows the 'from' lines first, so I do the same. addEditLines(hunk.getOldLines().getLines(), FROM_LABEL); addEditLines(hunk.getNewLines().getLines(), TO_LABEL); //remember in case this hunk goes to the end of the file. oldEndLine = hunk.getOldLines().getToLine(); newEndLine = hunk.getNewLines().getToLine(); } private void addEditLines(List<String> lines, char edit) { //adding lines to the edit. Iterator<String> i = lines.iterator(); while (i.hasNext()) { currentEdit.addLine(i.next(), edit); } } /** * {@inheritDoc} */ public void accept(MatchHunk hunk) { int startLine = hunk.getOldLines().getFromLine(); int endLine = hunk.getOldLines().getToLine(); int numLines = endLine - startLine; //Get the matching lines. Iterator<String> lines = hunk.getOldLines().getLines().iterator(); int counter = 0; if (currentEdit != null) { //There was an edit hunk before us. //Add context after a previous edit. while (lines.hasNext() && counter < contextLines) { currentEdit.addLine(lines.next(), MATCH_LABEL); counter++; } } //if this is a separation of two hunks. if (currentEdit != null && numLines > 2 * contextLines) { //writes one entire edit. writeLines(hunk.getOldLines().getFromLine() + counter, hunk.getNewLines().getFromLine() + counter); } //skip all the lines outside of our context. while ((numLines - counter) > contextLines) { lines.next(); counter++; } if (lines.hasNext() && currentEdit == null) { int fromStart = startLine + counter; int toStart = hunk.getNewLines().getFromLine() + counter; currentEdit = new EditPoint(fromStart, toStart); } while (lines.hasNext()) { //add context before an edit. currentEdit.addLine(lines.next(), MATCH_LABEL); } } private void writeLines(int fromEnd, int toEnd) { diff.append(currentEdit.write(fromEnd, toEnd, HUNK_LABEL, FROM_LABEL, TO_LABEL)); currentEdit = null; } /** * @return The patch diff. */ public String getResult() { //so, we may have not written the last change to the buffer, do it now. if (currentEdit != null) { diff.append(currentEdit.write(oldEndLine, newEndLine, HUNK_LABEL, FROM_LABEL, TO_LABEL)); } return diff.toString(); } private class EditPoint { private final int fromStart; private final int toStart; private boolean writable; private final StringBuffer lines; /** * @param fromLine Starting line for from file * @param toLine Starting line for to file */ EditPoint(int fromLine, int toLine) { fromStart = fromLine; toStart = toLine; lines = new StringBuffer(); writable = false; } public String write(int fromEnd, int toEnd, String edit, char from, char to) { if (!writable) { //don't write something that is purely matching lines. return new String(); } StringBuffer retval = new StringBuffer(); retval.append(edit); retval.append(" "); retval.append(from); writeLines(fromStart, fromEnd, retval); retval.append(" "); retval.append(to); writeLines(toStart, toEnd, retval); retval.append(" "); retval.append(edit); retval.append("\n"); retval.append(lines); return retval.toString(); } private void writeLines(int from, int to, StringBuffer buffy) { buffy.append(from); if (from + 1 != to) { //more than one line shown. buffy.append(","); buffy.append(to - from); //the number of lines from file shown. } } public void addLine(String line, char type) { if (type != MATCH_LABEL) { writable = true; } lines.append(type); lines.append(line); lines.append("\n"); } } }