/* * Copyright 2019 java-diff-utils. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.github.difflib.unifieddiff; import com.github.difflib.patch.AbstractDelta; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.function.Consumer; import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; /** * @todo use an instance to store contextSize and originalLinesProvider. * @author Tobias Warneke (t.warneke@gmx.net) */ public class UnifiedDiffWriter { private static final Logger LOG = Logger.getLogger(UnifiedDiffWriter.class.getName()); public static void write(UnifiedDiff diff, Function> originalLinesProvider, Writer writer, int contextSize) throws IOException { Objects.requireNonNull(originalLinesProvider, "original lines provider needs to be specified"); write(diff, originalLinesProvider, line -> { try { writer.append(line).append("\n"); } catch (IOException ex) { LOG.log(Level.SEVERE, null, ex); } }, contextSize); } public static void write(UnifiedDiff diff, Function> originalLinesProvider, Consumer writer, int contextSize) throws IOException { if (diff.getHeader() != null) { writer.accept(diff.getHeader()); } for (UnifiedDiffFile file : diff.getFiles()) { List> patchDeltas = new ArrayList<>( file.getPatch().getDeltas()); if (!patchDeltas.isEmpty()) { writeOrNothing(writer, file.getDiffCommand()); if (file.getIndex() != null) { writer.accept("index " + file.getIndex()); } writer.accept("--- " + (file.getFromFile() == null ? "/dev/null" : file.getFromFile())); if (file.getToFile() != null) { writer.accept("+++ " + file.getToFile()); } List originalLines = originalLinesProvider.apply(file.getFromFile()); List> deltas = new ArrayList<>(); AbstractDelta delta = patchDeltas.get(0); deltas.add(delta); // add the first Delta to the current set // if there's more than 1 Delta, we may need to output them together if (patchDeltas.size() > 1) { for (int i = 1; i < patchDeltas.size(); i++) { int position = delta.getSource().getPosition(); // Check if the next Delta is too close to the current // position. // And if it is, add it to the current set AbstractDelta nextDelta = patchDeltas.get(i); if ((position + delta.getSource().size() + contextSize) >= (nextDelta .getSource().getPosition() - contextSize)) { deltas.add(nextDelta); } else { // if it isn't, output the current set, // then create a new set and add the current Delta to // it. processDeltas(writer, originalLines, deltas, contextSize, false); deltas.clear(); deltas.add(nextDelta); } delta = nextDelta; } } // don't forget to process the last set of Deltas processDeltas(writer, originalLines, deltas, contextSize, patchDeltas.size() == 1 && file.getFromFile() == null); } } if (diff.getTail() != null) { writer.accept("--"); writer.accept(diff.getTail()); } } private static void processDeltas(Consumer writer, List origLines, List> deltas, int contextSize, boolean newFile) { List buffer = new ArrayList<>(); int origTotal = 0; // counter for total lines output from Original int revTotal = 0; // counter for total lines output from Original int line; AbstractDelta curDelta = deltas.get(0); int origStart; if (newFile) { origStart = 0; } else { // NOTE: +1 to overcome the 0-offset Position origStart = curDelta.getSource().getPosition() + 1 - contextSize; if (origStart < 1) { origStart = 1; } } int revStart = curDelta.getTarget().getPosition() + 1 - contextSize; if (revStart < 1) { revStart = 1; } // find the start of the wrapper context code int contextStart = curDelta.getSource().getPosition() - contextSize; if (contextStart < 0) { contextStart = 0; // clamp to the start of the file } // output the context before the first Delta for (line = contextStart; line < curDelta.getSource().getPosition() && line < origLines.size(); line++) { // buffer.add(" " + origLines.get(line)); origTotal++; revTotal++; } // output the first Delta getDeltaText(txt -> buffer.add(txt), curDelta); origTotal += curDelta.getSource().getLines().size(); revTotal += curDelta.getTarget().getLines().size(); int deltaIndex = 1; while (deltaIndex < deltas.size()) { // for each of the other Deltas AbstractDelta nextDelta = deltas.get(deltaIndex); int intermediateStart = curDelta.getSource().getPosition() + curDelta.getSource().getLines().size(); for (line = intermediateStart; line < nextDelta.getSource().getPosition() && line < origLines.size(); line++) { // output the code between the last Delta and this one buffer.add(" " + origLines.get(line)); origTotal++; revTotal++; } getDeltaText(txt -> buffer.add(txt), nextDelta); // output the Delta origTotal += nextDelta.getSource().getLines().size(); revTotal += nextDelta.getTarget().getLines().size(); curDelta = nextDelta; deltaIndex++; } // Now output the post-Delta context code, clamping the end of the file contextStart = curDelta.getSource().getPosition() + curDelta.getSource().getLines().size(); for (line = contextStart; (line < (contextStart + contextSize)) && (line < origLines.size()); line++) { buffer.add(" " + origLines.get(line)); origTotal++; revTotal++; } // Create and insert the block header, conforming to the Unified Diff // standard writer.accept("@@ -" + origStart + "," + origTotal + " +" + revStart + "," + revTotal + " @@"); buffer.forEach(txt -> { writer.accept(txt); }); } /** * getDeltaText returns the lines to be added to the Unified Diff text from the Delta parameter. * * @param writer consumer for the list of String lines of code * @param delta the Delta to output */ private static void getDeltaText(Consumer writer, AbstractDelta delta) { for (String line : delta.getSource().getLines()) { writer.accept("-" + line); } for (String line : delta.getTarget().getLines()) { writer.accept("+" + line); } } private static void writeOrNothing(Consumer writer, String str) throws IOException { if (str != null) { writer.accept(str); } } }