++
сравнение
This commit is contained in:
1
.idea/artifacts/VisualSapfor_jar.xml
generated
1
.idea/artifacts/VisualSapfor_jar.xml
generated
@@ -23,6 +23,7 @@
|
||||
<element id="extracted-dir" path="$PROJECT_DIR$/libs/log4j-1.2.17.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$PROJECT_DIR$/libs/xmlbeans-2.6.0.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$PROJECT_DIR$/libs/commons-lang-2.6.0.jar" path-in-jar="/" />
|
||||
<element id="extracted-dir" path="$PROJECT_DIR$/libs/java-diff-utils.jar" path-in-jar="/" />
|
||||
</root>
|
||||
</artifact>
|
||||
</component>
|
||||
49
.idea/workspace.xml
generated
49
.idea/workspace.xml
generated
@@ -7,9 +7,46 @@
|
||||
</component>
|
||||
<component name="ChangeListManager">
|
||||
<list default="true" id="e42177c3-2328-4b27-8a01-35779b2beb99" name="Default Changelist" comment="">
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/DiffUtils.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/UnifiedDiffUtils.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/algorithm/Change.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/algorithm/DiffAlgorithmFactory.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/algorithm/DiffAlgorithmI.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/algorithm/DiffAlgorithmListener.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/algorithm/myers/MyersDiff.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/algorithm/myers/MyersDiffWithLinearSpace.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/algorithm/myers/PathNode.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/patch/AbstractDelta.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/patch/ChangeDelta.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/patch/Chunk.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/patch/ConflictOutput.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/patch/DeleteDelta.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/patch/DeltaType.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/patch/DiffException.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/patch/EqualDelta.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/patch/InsertDelta.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/patch/Patch.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/patch/PatchFailedException.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/patch/VerifyChunk.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/text/DiffRow.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/text/DiffRowGenerator.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/text/StringUtils.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/text/deltamerge/DeltaMergeUtils.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/text/deltamerge/InlineDeltaMergeInfo.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/unifieddiff/UnifiedDiff.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/unifieddiff/UnifiedDiffFile.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/unifieddiff/UnifiedDiffParserException.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/unifieddiff/UnifiedDiffReader.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/unifieddiff/UnifiedDiffWriter.java" afterDir="false" />
|
||||
<change afterPath="$PROJECT_DIR$/src/com/github/difflib/unifieddiff/package-info.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/artifacts/VisualSapfor_jar.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/artifacts/VisualSapfor_jar.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/properties" beforeDir="false" afterPath="$PROJECT_DIR$/properties" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/_VisualDVM/Constants.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/_VisualDVM/Constants.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/_VisualDVM/TestingSystem/Common/TestsDatabase.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/_VisualDVM/TestingSystem/Common/TestsDatabase.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/_VisualDVM/Passes/All/TestPass.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/_VisualDVM/Passes/All/TestPass.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/_VisualDVM/Visual/Menus/MainMenuBar/MainMenuBar.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/_VisualDVM/Visual/Menus/MainMenuBar/MainMenuBar.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/_VisualDVM/Visual/Menus/MainMenuBar/VisualiserSettingsMenu/VersionsComparisonMenu.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/_VisualDVM/Visual/Menus/MainMenuBar/VisualiserSettingsMenu/VersionsComparisonMenu.java" afterDir="false" />
|
||||
<change beforePath="$PROJECT_DIR$/src/_VisualDVM/Visual/Windows/ComparisonForm.java" beforeDir="false" afterPath="$PROJECT_DIR$/src/_VisualDVM/Visual/Windows/ComparisonForm.java" afterDir="false" />
|
||||
</list>
|
||||
<option name="SHOW_DIALOG" value="false" />
|
||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||
@@ -89,8 +126,8 @@
|
||||
<property name="UI_DESIGNER_EDITOR_MODE.UIDesignerToolWindowManager.SHOW" value="true" />
|
||||
<property name="UI_DESIGNER_EDITOR_MODE.UIDesignerToolWindowManager.WIDTH" value="509" />
|
||||
<property name="extract.method.default.visibility" value="public" />
|
||||
<property name="last_opened_file_path" value="$PROJECT_DIR$/src/icons/Transformations" />
|
||||
<property name="project.structure.last.edited" value="Libraries" />
|
||||
<property name="last_opened_file_path" value="$PROJECT_DIR$/src" />
|
||||
<property name="project.structure.last.edited" value="Artifacts" />
|
||||
<property name="project.structure.proportion" value="0.15" />
|
||||
<property name="project.structure.side.proportion" value="0.27322906" />
|
||||
<property name="run.code.analysis.last.selected.profile" value="pProject Default" />
|
||||
@@ -102,11 +139,11 @@
|
||||
<recent name="controls.Trees" />
|
||||
</key>
|
||||
<key name="CopyFile.RECENT_KEYS">
|
||||
<recent name="C:\Users\misha\Documents\visual_sapfor_2023\src" />
|
||||
<recent name="C:\Users\misha\Documents\visual_sapfor_2023\src\_dif_utils" />
|
||||
<recent name="C:\Users\misha\Documents\visual_sapfor_2023\libs" />
|
||||
<recent name="C:\Users\misha\Documents\visual_sapfor_2023\src\icons\Transformations" />
|
||||
<recent name="C:\Users\misha\Documents\visual_sapfor_2023\src\_VisualDVM\TestingSystem\DVM\DVMTasks\UI" />
|
||||
<recent name="C:\Users\misha\Documents\visual_sapfor_2023\src\_VisualDVM\Passes\All" />
|
||||
<recent name="C:\Users\misha\Documents\visual_sapfor_2023\src\icons" />
|
||||
<recent name="C:\Users\misha\Documents\visual_sapfor_2023\src\icons\versions" />
|
||||
</key>
|
||||
<key name="MoveMembersDialog.RECENTS_KEY">
|
||||
<recent name="_VisualDVM.ComponentsServer.Component.Sapfor.Sapfor" />
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"ServerUserPassword": "mprit_2011",
|
||||
"OfferRegistrationOnStart": true,
|
||||
"Workspace": "E:\\Tests",
|
||||
"ProjectsSearchDirectory": "E:\\BUG\\JAC",
|
||||
"ProjectsSearchDirectory": "E:\\Tests\\Downloads\\bugreport_1742890241",
|
||||
"DocumentsDirectory": "C:\\Users\\misha\\Documents\\_testing_system",
|
||||
"VisualiserPath": "C:\\Users\\misha\\Downloads",
|
||||
"Sapfor_FPath": "E:\\_sapfor_x64\\Components\\Sapfor_F",
|
||||
|
||||
@@ -3,7 +3,7 @@ import Common.Utils.Vector_;
|
||||
|
||||
import java.util.Vector;
|
||||
public class Constants {
|
||||
public static final int version = 1226;
|
||||
public static final int version = 1227;
|
||||
public static final int planner_version = 24;
|
||||
public static final int testingMaxKernels = 64;
|
||||
//--
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package _VisualDVM.Passes.All;
|
||||
import Common.Passes.Pass;
|
||||
import Common.Utils.Vector_;
|
||||
import com.github.difflib.text.DiffRow;
|
||||
import com.github.difflib.text.DiffRowGenerator;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ForkJoinPool;
|
||||
public class TestPass extends Pass {
|
||||
@@ -52,5 +55,23 @@ public class TestPass extends Pass {
|
||||
System.out.println("Square of " + num + " is " + square);
|
||||
})
|
||||
).join();
|
||||
System.out.println("DONE");
|
||||
//----
|
||||
|
||||
DiffRowGenerator generator = DiffRowGenerator.create()
|
||||
.showInlineDiffs(true)
|
||||
.inlineDiffByWord(true)
|
||||
.oldTag(f -> "~")
|
||||
.newTag(f -> "**")
|
||||
.build();
|
||||
List<DiffRow> rows = generator.generateDiffRows(
|
||||
Arrays.asList("This is a test senctence.", "This is the second line.", "And here is the finish."),
|
||||
Arrays.asList("This is a test for diffutils.", "This is the second line."));
|
||||
|
||||
System.out.println("|original|new|");
|
||||
System.out.println("|--------|---|");
|
||||
for (DiffRow row : rows) {
|
||||
System.out.println("|" + row.getOldLine() + "|" + row.getNewLine() + "|");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ public class MainMenuBar extends VisualiserMenuBar {
|
||||
setPreferredSize(new Dimension(0, 30));
|
||||
//--
|
||||
|
||||
/*
|
||||
|
||||
add(new MenuBarButton() {
|
||||
{
|
||||
setIcon("/Common/icons/Apply.png");
|
||||
@@ -77,7 +77,6 @@ public class MainMenuBar extends VisualiserMenuBar {
|
||||
});
|
||||
}
|
||||
});
|
||||
*/
|
||||
//--
|
||||
add(new JSeparator());
|
||||
add(MachineButton = new MenuBarButton() {
|
||||
|
||||
@@ -5,10 +5,10 @@ public class VersionsComparisonMenu extends PropertiesSubmenu {
|
||||
public VersionsComparisonMenu() {
|
||||
super("Сравнение версий", null,
|
||||
Global.normalProperties,
|
||||
"RegisterOn",
|
||||
"SpacesOn",
|
||||
"EmptyLinesOn",
|
||||
"FortranWrapsOn",
|
||||
// "RegisterOn",
|
||||
// "SpacesOn",
|
||||
// "EmptyLinesOn",
|
||||
// "FortranWrapsOn",
|
||||
"ExtensionsOn",
|
||||
"ComparsionDiffMergeOn"
|
||||
);
|
||||
|
||||
@@ -51,30 +51,6 @@
|
||||
<constraints/>
|
||||
<properties/>
|
||||
</component>
|
||||
<component id="75549" class="javax.swing.JButton" binding="bPrevious">
|
||||
<constraints/>
|
||||
<properties>
|
||||
<borderPainted value="false"/>
|
||||
<icon value="Common/icons/Previous.png"/>
|
||||
<maximumSize width="30" height="30"/>
|
||||
<minimumSize width="30" height="30"/>
|
||||
<preferredSize width="30" height="30"/>
|
||||
<text value=""/>
|
||||
<toolTipText value="Перейти к предыдущему различию "/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="f8e56" class="javax.swing.JButton" binding="bNext">
|
||||
<constraints/>
|
||||
<properties>
|
||||
<borderPainted value="false"/>
|
||||
<icon value="Common/icons/Next.png"/>
|
||||
<maximumSize width="30" height="30"/>
|
||||
<minimumSize width="30" height="30"/>
|
||||
<preferredSize width="30" height="30"/>
|
||||
<text value=""/>
|
||||
<toolTipText value="Перейти к следующему различию"/>
|
||||
</properties>
|
||||
</component>
|
||||
<component id="42c3c" class="javax.swing.JButton" binding="bCompare">
|
||||
<constraints/>
|
||||
<properties>
|
||||
|
||||
@@ -9,20 +9,24 @@ import Common.Visual.Menus.VisualiserMenuBar;
|
||||
import Common.Visual.UI;
|
||||
import _VisualDVM.ProjectData.Files.UI.Editor.SPFEditor;
|
||||
import _VisualDVM.Utils;
|
||||
import com.github.difflib.text.DiffRow;
|
||||
import com.github.difflib.text.DiffRowGenerator;
|
||||
import javafx.util.Pair;
|
||||
import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaHighlighter;
|
||||
import org.fife.ui.rtextarea.RTextScrollPane;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
public abstract class ComparisonForm<T> {
|
||||
public Class<T> t; //класс объектов.
|
||||
//-->>
|
||||
public Vector<String> lines = new Vector<>(); //строки с учетом/неучетом пробелов. для сравнения
|
||||
public Vector<String> visible_lines = new Vector<>(); //строки с нетронутыми пробелами. для отображения
|
||||
//подсветка.
|
||||
public LinkedHashMap<Integer, Pair<Integer, Boolean>> colors = new LinkedHashMap<>();
|
||||
public RSyntaxTextAreaHighlighter slave_highlighter = null; //погонщик рабов
|
||||
protected JToolBar tools;
|
||||
protected JLabel lObjectName;
|
||||
@@ -39,8 +43,6 @@ public abstract class ComparisonForm<T> {
|
||||
//-->>
|
||||
private JPanel content;
|
||||
private JPanel editorPanel;
|
||||
private JButton bPrevious;
|
||||
private JButton bNext;
|
||||
private JButton bCompare;
|
||||
private RTextScrollPane Scroll;
|
||||
//-----
|
||||
@@ -59,8 +61,6 @@ public abstract class ComparisonForm<T> {
|
||||
t = t_in;
|
||||
this_ = this;
|
||||
slave = slave_in;
|
||||
bPrevious.setVisible(isMaster());
|
||||
bNext.setVisible(isMaster());
|
||||
Scroll.setLineNumbersEnabled(true);
|
||||
bApplyObject.addActionListener(e -> {
|
||||
ApplyObject();
|
||||
@@ -85,24 +85,6 @@ public abstract class ComparisonForm<T> {
|
||||
});
|
||||
//</editor-fold>
|
||||
slave.master = this;
|
||||
bPrevious.addActionListener(e -> {
|
||||
if (current_diff_line != CommonConstants.Nan) {
|
||||
if (current_diff_line > 0)
|
||||
current_diff_line--;
|
||||
else
|
||||
current_diff_line = colors.size() - 1;
|
||||
ShowCurrentDiff();
|
||||
}
|
||||
});
|
||||
bNext.addActionListener(e -> {
|
||||
if (current_diff_line != CommonConstants.Nan) {
|
||||
if (current_diff_line < colors.size() - 1)
|
||||
current_diff_line++;
|
||||
else
|
||||
current_diff_line = 0;
|
||||
ShowCurrentDiff();
|
||||
}
|
||||
});
|
||||
bCompare.addActionListener(e -> {
|
||||
DoComparePass(isReady() && slave.isReady());
|
||||
});
|
||||
@@ -146,7 +128,7 @@ public abstract class ComparisonForm<T> {
|
||||
showObject();
|
||||
}
|
||||
private void ShowCurrentDiff() {
|
||||
Body.gotoLine_(colors.get(current_diff_line).getKey());
|
||||
// Body.gotoLine_(colors.get(current_diff_line).getKey());
|
||||
}
|
||||
private void getLines() {
|
||||
lines.clear();
|
||||
@@ -164,7 +146,6 @@ public abstract class ComparisonForm<T> {
|
||||
protected void Compare() throws Exception {
|
||||
events_on = false;
|
||||
current_diff_line = CommonConstants.Nan;
|
||||
colors.clear();
|
||||
//-----------------------------------------------------------------------------------------------
|
||||
Body.setText("");
|
||||
slave.Body.setText("");
|
||||
@@ -175,62 +156,49 @@ public abstract class ComparisonForm<T> {
|
||||
Vector<String> t1 = new Vector<>();
|
||||
Vector<String> t2 = new Vector<>();
|
||||
//------
|
||||
int old_j = 0;
|
||||
int j = 0;
|
||||
for (int i = 0; i < lines.size(); ++i) {
|
||||
if (Utils.Contains(slave.lines, lines.get(i), old_j)) {
|
||||
for (int k = old_j; k < slave.lines.size(); ++k) {
|
||||
j = k;
|
||||
if (Utils.CompareLines(lines.get(i), slave.lines.get(k))) {
|
||||
j++;
|
||||
t1.add(visible_lines.get(i));
|
||||
t2.add(slave.visible_lines.get(k));
|
||||
break;
|
||||
} else {
|
||||
t1.add("+");
|
||||
t2.add("+ " + slave.visible_lines.get(k));
|
||||
colors.put(d, new Pair(t2.size() - 1, true));
|
||||
++d;
|
||||
DiffRowGenerator generator = DiffRowGenerator.create()
|
||||
.showInlineDiffs(true)
|
||||
.inlineDiffByWord(true)
|
||||
.ignoreWhiteSpaces(true)
|
||||
.oldTag(f -> "~")
|
||||
.newTag(f -> "**")
|
||||
.build();
|
||||
List<DiffRow> rows = generator.generateDiffRows(
|
||||
visible_lines,
|
||||
slave.visible_lines);
|
||||
|
||||
for (DiffRow row : rows) {
|
||||
t1.add(row.getOldLine());
|
||||
t2.add(row.getNewLine());
|
||||
}
|
||||
}
|
||||
old_j = j;
|
||||
} else {
|
||||
//строки гарантированно нет.
|
||||
t1.add("- " + visible_lines.get(i));
|
||||
t2.add("- " + visible_lines.get(i));
|
||||
colors.put(d, new Pair(t2.size() - 1, false));
|
||||
++d;
|
||||
}
|
||||
}
|
||||
//теперь граничное условие. если первый файл кончился а второй нет, его остаток это добавление.
|
||||
for (int i = j; i < slave.lines.size(); ++i) {
|
||||
t1.add("+");
|
||||
t2.add("+ " + slave.visible_lines.get(i));
|
||||
colors.put(d, new Pair(t2.size() - 1, true));
|
||||
++d;
|
||||
}
|
||||
///----------------
|
||||
Body.setText(String.join("\n", t1));
|
||||
slave.Body.setText(String.join("\n", t2));
|
||||
Body.setCaretPosition(0);
|
||||
slave.Body.setCaretPosition(0);
|
||||
//теперь покрас.
|
||||
for (Integer diff_num : colors.keySet()) {
|
||||
slave_highlighter.addHighlight(
|
||||
slave.Body.getLineStartOffset(colors.get(diff_num).getKey()),
|
||||
slave.Body.getLineEndOffset(colors.get(diff_num).getKey()),
|
||||
colors.get(diff_num).getValue() ?
|
||||
SPFEditor.GreenTextPainter :
|
||||
//--
|
||||
Pattern master_pattern = Pattern.compile("~.*~");
|
||||
Matcher master_matcher = master_pattern.matcher(Body.getText());
|
||||
while (master_matcher.find()) {
|
||||
Body.getHighlighter().addHighlight(
|
||||
master_matcher.start(),
|
||||
master_matcher.end(),
|
||||
SPFEditor.RedTextPainter
|
||||
);
|
||||
}
|
||||
if (colors.size() > 0) current_diff_line = 0;
|
||||
Pattern slave_pattern = Pattern.compile("\\*.*\\*");
|
||||
Matcher slave_matcher = slave_pattern.matcher(slave.Body.getText());
|
||||
while (slave_matcher.find()) {
|
||||
slave_highlighter.addHighlight(
|
||||
slave_matcher.start(),
|
||||
slave_matcher.end(),
|
||||
SPFEditor.GreenTextPainter
|
||||
);
|
||||
}
|
||||
events_on = true;
|
||||
}
|
||||
public void Show() throws Exception {
|
||||
events_on = false;
|
||||
current_diff_line = CommonConstants.Nan;
|
||||
colors.clear();
|
||||
//----------------------------------------------------------------------------------------------
|
||||
Body.setText("");
|
||||
slave.Body.setText("");
|
||||
|
||||
228
src/com/github/difflib/DiffUtils.java
Normal file
228
src/com/github/difflib/DiffUtils.java
Normal file
@@ -0,0 +1,228 @@
|
||||
/*
|
||||
* Copyright 2009-2017 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;
|
||||
|
||||
import com.github.difflib.algorithm.DiffAlgorithmFactory;
|
||||
import com.github.difflib.algorithm.DiffAlgorithmI;
|
||||
import com.github.difflib.algorithm.DiffAlgorithmListener;
|
||||
import com.github.difflib.algorithm.myers.MyersDiff;
|
||||
import com.github.difflib.patch.AbstractDelta;
|
||||
import com.github.difflib.patch.Patch;
|
||||
import com.github.difflib.patch.PatchFailedException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
/**
|
||||
* Utility class to implement the difference and patching engine.
|
||||
*/
|
||||
public final class DiffUtils {
|
||||
|
||||
/**
|
||||
* This factory generates the DEFAULT_DIFF algorithm for all these routines.
|
||||
*/
|
||||
static DiffAlgorithmFactory DEFAULT_DIFF = MyersDiff.factory();
|
||||
|
||||
/**
|
||||
* Sets the default diff algorithm factory to be used by all diff routines.
|
||||
*
|
||||
* @param factory a {@link DiffAlgorithmFactory} representing the new default diff algorithm factory.
|
||||
*/
|
||||
public static void withDefaultDiffAlgorithmFactory(DiffAlgorithmFactory factory) {
|
||||
DEFAULT_DIFF = factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the difference between two sequences of elements using the default diff algorithm.
|
||||
*
|
||||
* @param <T> a generic representing the type of the elements to be compared.
|
||||
* @param original a {@link List} representing the original sequence of elements. Must not be {@code null}.
|
||||
* @param revised a {@link List} representing the revised sequence of elements. Must not be {@code null}.
|
||||
* @param progress a {@link DiffAlgorithmListener} representing the progress listener. Can be {@code null}.
|
||||
* @return The patch describing the difference between the original and revised sequences. Never {@code null}.
|
||||
*/
|
||||
public static <T> Patch<T> diff(List<T> original, List<T> revised, DiffAlgorithmListener progress) {
|
||||
return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), progress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the difference between two sequences of elements using the default diff algorithm.
|
||||
*
|
||||
* @param <T> a generic representing the type of the elements to be compared.
|
||||
* @param original a {@link List} representing the original sequence of elements. Must not be {@code null}.
|
||||
* @param revised a {@link List} representing the revised sequence of elements. Must not be {@code null}.
|
||||
* @return The patch describing the difference between the original and revised sequences. Never {@code null}.
|
||||
*/
|
||||
public static <T> Patch<T> diff(List<T> original, List<T> revised) {
|
||||
return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the difference between two sequences of elements using the default diff algorithm.
|
||||
*
|
||||
* @param <T> a generic representing the type of the elements to be compared.
|
||||
* @param original a {@link List} representing the original sequence of elements. Must not be {@code null}.
|
||||
* @param revised a {@link List} representing the revised sequence of elements. Must not be {@code null}.
|
||||
* @param includeEqualParts a {@link boolean} representing whether to include equal parts in the resulting patch.
|
||||
* @return The patch describing the difference between the original and revised sequences. Never {@code null}.
|
||||
*/
|
||||
public static <T> Patch<T> diff(List<T> original, List<T> revised, boolean includeEqualParts) {
|
||||
return DiffUtils.diff(original, revised, DEFAULT_DIFF.create(), null, includeEqualParts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the difference between two strings using the default diff algorithm.
|
||||
*
|
||||
* @param sourceText a {@link String} representing the original string. Must not be {@code null}.
|
||||
* @param targetText a {@link String} representing the revised string. Must not be {@code null}.
|
||||
* @param progress a {@link DiffAlgorithmListener} representing the progress listener. Can be {@code null}.
|
||||
* @return The patch describing the difference between the original and revised strings. Never {@code null}.
|
||||
*/
|
||||
public static Patch<String> diff(String sourceText, String targetText,
|
||||
DiffAlgorithmListener progress) {
|
||||
return DiffUtils.diff(
|
||||
Arrays.asList(sourceText.split("\n")),
|
||||
Arrays.asList(targetText.split("\n")), progress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the difference between the original and revised list of elements
|
||||
* with default diff algorithm
|
||||
*
|
||||
* @param source a {@link List} representing the original text. Must not be {@code null}.
|
||||
* @param target a {@link List} representing the revised text. Must not be {@code null}.
|
||||
* @param equalizer a {@link BiPredicate} representing the equalizer object to replace the default compare
|
||||
* algorithm (Object.equals). If {@code null} the default equalizer of the
|
||||
* default algorithm is used.
|
||||
* @return The patch describing the difference between the original and
|
||||
* revised sequences. Never {@code null}.
|
||||
*/
|
||||
public static <T> Patch<T> diff(List<T> source, List<T> target,
|
||||
BiPredicate<T, T> equalizer) {
|
||||
if (equalizer != null) {
|
||||
return DiffUtils.diff(source, target,
|
||||
DEFAULT_DIFF.create(equalizer));
|
||||
}
|
||||
return DiffUtils.diff(source, target, new MyersDiff<>());
|
||||
}
|
||||
|
||||
public static <T> Patch<T> diff(List<T> original, List<T> revised,
|
||||
DiffAlgorithmI<T> algorithm, DiffAlgorithmListener progress) {
|
||||
return diff(original, revised, algorithm, progress, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the difference between the original and revised list of elements
|
||||
* with default diff algorithm
|
||||
*
|
||||
* @param original a {@link List} representing the original text. Must not be {@code null}.
|
||||
* @param revised a {@link List} representing the revised text. Must not be {@code null}.
|
||||
* @param algorithm a {@link DiffAlgorithmI} representing the diff algorithm. Must not be {@code null}.
|
||||
* @param progress a {@link DiffAlgorithmListener} representing the diff algorithm listener.
|
||||
* @param includeEqualParts Include equal data parts into the patch.
|
||||
* @return The patch describing the difference between the original and
|
||||
* revised sequences. Never {@code null}.
|
||||
*/
|
||||
public static <T> Patch<T> diff(List<T> original, List<T> revised,
|
||||
DiffAlgorithmI<T> algorithm, DiffAlgorithmListener progress,
|
||||
boolean includeEqualParts) {
|
||||
Objects.requireNonNull(original, "original must not be null");
|
||||
Objects.requireNonNull(revised, "revised must not be null");
|
||||
Objects.requireNonNull(algorithm, "algorithm must not be null");
|
||||
|
||||
return Patch.generate(original, revised, algorithm.computeDiff(original, revised, progress), includeEqualParts);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Computes the difference between the original and revised list of elements
|
||||
* with default diff algorithm
|
||||
*
|
||||
* @param original a {@link List} representing the original text. Must not be {@code null}.
|
||||
* @param revised a {@link List} representing the revised text. Must not be {@code null}.
|
||||
* @param algorithm a {@link DiffAlgorithmI} representing the diff algorithm. Must not be {@code null}.
|
||||
* @return The patch describing the difference between the original and
|
||||
* revised sequences. Never {@code null}.
|
||||
*/
|
||||
public static <T> Patch<T> diff(List<T> original, List<T> revised, DiffAlgorithmI<T> algorithm) {
|
||||
return diff(original, revised, algorithm, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the difference between the given texts inline. This one uses the
|
||||
* "trick" to make out of texts lists of characters, like DiffRowGenerator
|
||||
* does and merges those changes at the end together again.
|
||||
*
|
||||
* @param original a {@link String} representing the original text. Must not be {@code null}.
|
||||
* @param revised a {@link String} representing the revised text. Must not be {@code null}.
|
||||
* @return The patch describing the difference between the original and
|
||||
* revised sequences. Never {@code null}.
|
||||
*/
|
||||
public static Patch<String> diffInline(String original, String revised) {
|
||||
List<String> origList = new ArrayList<>();
|
||||
List<String> revList = new ArrayList<>();
|
||||
for (Character character : original.toCharArray()) {
|
||||
origList.add(character.toString());
|
||||
}
|
||||
for (Character character : revised.toCharArray()) {
|
||||
revList.add(character.toString());
|
||||
}
|
||||
Patch<String> patch = DiffUtils.diff(origList, revList);
|
||||
for (AbstractDelta<String> delta : patch.getDeltas()) {
|
||||
delta.getSource().setLines(compressLines(delta.getSource().getLines(), ""));
|
||||
delta.getTarget().setLines(compressLines(delta.getTarget().getLines(), ""));
|
||||
}
|
||||
return patch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the given patch to the original list and returns the revised list.
|
||||
*
|
||||
* @param original a {@link List} representing the original list.
|
||||
* @param patch a {@link List} representing the patch to apply.
|
||||
* @return the revised list.
|
||||
* @throws PatchFailedException if the patch cannot be applied.
|
||||
*/
|
||||
public static <T> List<T> patch(List<T> original, Patch<T> patch)
|
||||
throws PatchFailedException {
|
||||
return patch.applyTo(original);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the given patch to the revised list and returns the original list.
|
||||
*
|
||||
* @param revised a {@link List} representing the revised list.
|
||||
* @param patch a {@link Patch} representing the patch to apply.
|
||||
* @return the original list.
|
||||
* @throws PatchFailedException if the patch cannot be applied.
|
||||
*/
|
||||
public static <T> List<T> unpatch(List<T> revised, Patch<T> patch) {
|
||||
return patch.restore(revised);
|
||||
}
|
||||
|
||||
private static List<String> compressLines(List<String> lines, String delimiter) {
|
||||
if (lines.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return Collections.singletonList(String.join(delimiter, lines));
|
||||
}
|
||||
|
||||
private DiffUtils() {
|
||||
}
|
||||
}
|
||||
467
src/com/github/difflib/UnifiedDiffUtils.java
Normal file
467
src/com/github/difflib/UnifiedDiffUtils.java
Normal file
@@ -0,0 +1,467 @@
|
||||
/*
|
||||
* Copyright 2017 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;
|
||||
|
||||
import com.github.difflib.patch.ChangeDelta;
|
||||
import com.github.difflib.patch.Chunk;
|
||||
import com.github.difflib.patch.AbstractDelta;
|
||||
import com.github.difflib.patch.Patch;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author toben
|
||||
*/
|
||||
public final class UnifiedDiffUtils {
|
||||
|
||||
private static final Pattern UNIFIED_DIFF_CHUNK_REGEXP = Pattern
|
||||
.compile("^@@\\s+-(\\d+)(?:,(\\d+))?\\s+\\+(\\d+)(?:,(\\d+))?\\s+@@.*$");
|
||||
private static final String NULL_FILE_INDICATOR = "/dev/null";
|
||||
|
||||
/**
|
||||
* Parse the given text in unified format and creates the list of deltas for it.
|
||||
*
|
||||
* @param diff the text in unified format
|
||||
* @return the patch with deltas.
|
||||
*/
|
||||
public static Patch<String> parseUnifiedDiff(List<String> diff) {
|
||||
boolean inPrelude = true;
|
||||
List<String[]> rawChunk = new ArrayList<>();
|
||||
Patch<String> patch = new Patch<>();
|
||||
|
||||
int old_ln = 0;
|
||||
int new_ln = 0;
|
||||
String tag;
|
||||
String rest;
|
||||
for (String line : diff) {
|
||||
// Skip leading lines until after we've seen one starting with '+++'
|
||||
if (inPrelude) {
|
||||
if (line.startsWith("+++")) {
|
||||
inPrelude = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
Matcher m = UNIFIED_DIFF_CHUNK_REGEXP.matcher(line);
|
||||
if (m.find()) {
|
||||
// Process the lines in the previous chunk
|
||||
processLinesInPrevChunk(rawChunk, patch, old_ln, new_ln);
|
||||
// Parse the @@ header
|
||||
old_ln = m.group(1) == null ? 1 : Integer.parseInt(m.group(1));
|
||||
new_ln = m.group(3) == null ? 1 : Integer.parseInt(m.group(3));
|
||||
|
||||
if (old_ln == 0) {
|
||||
old_ln = 1;
|
||||
}
|
||||
if (new_ln == 0) {
|
||||
new_ln = 1;
|
||||
}
|
||||
} else {
|
||||
if (line.length() > 0) {
|
||||
tag = line.substring(0, 1);
|
||||
rest = line.substring(1);
|
||||
if (" ".equals(tag) || "+".equals(tag) || "-".equals(tag)) {
|
||||
rawChunk.add(new String[]{tag, rest});
|
||||
}
|
||||
} else {
|
||||
rawChunk.add(new String[]{" ", ""});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Process the lines in the last chunk
|
||||
processLinesInPrevChunk(rawChunk, patch, old_ln, new_ln);
|
||||
|
||||
return patch;
|
||||
}
|
||||
|
||||
private static void processLinesInPrevChunk(List<String[]> rawChunk, Patch<String> patch, int old_ln, int new_ln) {
|
||||
String tag;
|
||||
String rest;
|
||||
if (!rawChunk.isEmpty()) {
|
||||
List<String> oldChunkLines = new ArrayList<>();
|
||||
List<String> newChunkLines = new ArrayList<>();
|
||||
|
||||
List<Integer> removePosition = new ArrayList<>();
|
||||
List<Integer> addPosition = new ArrayList<>();
|
||||
int removeNum = 0;
|
||||
int addNum = 0;
|
||||
for (String[] raw_line : rawChunk) {
|
||||
tag = raw_line[0];
|
||||
rest = raw_line[1];
|
||||
if (" ".equals(tag) || "-".equals(tag)) {
|
||||
removeNum++;
|
||||
oldChunkLines.add(rest);
|
||||
if ("-".equals(tag)) {
|
||||
removePosition.add(old_ln - 1 + removeNum);
|
||||
}
|
||||
}
|
||||
if (" ".equals(tag) || "+".equals(tag)) {
|
||||
addNum++;
|
||||
newChunkLines.add(rest);
|
||||
if ("+".equals(tag)) {
|
||||
addPosition.add(new_ln - 1 + addNum);
|
||||
}
|
||||
}
|
||||
}
|
||||
patch.addDelta(new ChangeDelta<>(new Chunk<>(
|
||||
old_ln - 1, oldChunkLines, removePosition), new Chunk<>(
|
||||
new_ln - 1, newChunkLines, addPosition)));
|
||||
rawChunk.clear();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* generateUnifiedDiff takes a Patch and some other arguments, returning the Unified Diff format
|
||||
* text representing the Patch. Author: Bill James (tankerbay@gmail.com).
|
||||
*
|
||||
* @param originalFileName - Filename of the original (unrevised file)
|
||||
* @param revisedFileName - Filename of the revised file
|
||||
* @param originalLines - Lines of the original file
|
||||
* @param patch - Patch created by the diff() function
|
||||
* @param contextSize - number of lines of context output around each difference in the file.
|
||||
* @return List of strings representing the Unified Diff representation of the Patch argument.
|
||||
*/
|
||||
public static List<String> generateUnifiedDiff(String originalFileName,
|
||||
String revisedFileName, List<String> originalLines, Patch<String> patch,
|
||||
int contextSize) {
|
||||
if (!patch.getDeltas().isEmpty()) {
|
||||
List<String> ret = new ArrayList<>();
|
||||
ret.add("--- " + Optional.ofNullable(originalFileName).orElse(NULL_FILE_INDICATOR));
|
||||
ret.add("+++ " + Optional.ofNullable(revisedFileName).orElse(NULL_FILE_INDICATOR));
|
||||
|
||||
List<AbstractDelta<String>> patchDeltas = new ArrayList<>(
|
||||
patch.getDeltas());
|
||||
|
||||
// code outside the if block also works for single-delta issues.
|
||||
List<AbstractDelta<String>> deltas = new ArrayList<>(); // current
|
||||
// list
|
||||
// of
|
||||
// Delta's to
|
||||
// process
|
||||
AbstractDelta<String> 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(); // store
|
||||
// the
|
||||
// current
|
||||
// position
|
||||
// of
|
||||
// the first Delta
|
||||
|
||||
// Check if the next Delta is too close to the current
|
||||
// position.
|
||||
// And if it is, add it to the current set
|
||||
AbstractDelta<String> 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.
|
||||
List<String> curBlock = processDeltas(originalLines,
|
||||
deltas, contextSize, false);
|
||||
ret.addAll(curBlock);
|
||||
deltas.clear();
|
||||
deltas.add(nextDelta);
|
||||
}
|
||||
delta = nextDelta;
|
||||
}
|
||||
|
||||
}
|
||||
// don't forget to process the last set of Deltas
|
||||
List<String> curBlock = processDeltas(originalLines, deltas,
|
||||
contextSize, patchDeltas.size() == 1 && originalFileName == null);
|
||||
ret.addAll(curBlock);
|
||||
return ret;
|
||||
}
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* processDeltas takes a list of Deltas and outputs them together in a single block of
|
||||
* Unified-Diff-format text. Author: Bill James (tankerbay@gmail.com).
|
||||
*
|
||||
* @param origLines - the lines of the original file
|
||||
* @param deltas - the Deltas to be output as a single block
|
||||
* @param contextSize - the number of lines of context to place around block
|
||||
* @return
|
||||
*/
|
||||
private static List<String> processDeltas(List<String> origLines,
|
||||
List<AbstractDelta<String>> deltas, int contextSize, boolean newFile) {
|
||||
List<String> 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<String> 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++) { //
|
||||
buffer.add(" " + origLines.get(line));
|
||||
origTotal++;
|
||||
revTotal++;
|
||||
}
|
||||
|
||||
// output the first Delta
|
||||
buffer.addAll(getDeltaText(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<String> nextDelta = deltas.get(deltaIndex);
|
||||
int intermediateStart = curDelta.getSource().getPosition()
|
||||
+ curDelta.getSource().getLines().size();
|
||||
for (line = intermediateStart; line < nextDelta.getSource()
|
||||
.getPosition(); line++) {
|
||||
// output the code between the last Delta and this one
|
||||
buffer.add(" " + origLines.get(line));
|
||||
origTotal++;
|
||||
revTotal++;
|
||||
}
|
||||
buffer.addAll(getDeltaText(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
|
||||
StringBuilder header = new StringBuilder();
|
||||
header.append("@@ -");
|
||||
header.append(origStart);
|
||||
header.append(",");
|
||||
header.append(origTotal);
|
||||
header.append(" +");
|
||||
header.append(revStart);
|
||||
header.append(",");
|
||||
header.append(revTotal);
|
||||
header.append(" @@");
|
||||
buffer.add(0, header.toString());
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* getDeltaText returns the lines to be added to the Unified Diff text from the Delta parameter. Author: Bill James (tankerbay@gmail.com).
|
||||
*
|
||||
* @param delta - the Delta to output
|
||||
* @return list of String lines of code.
|
||||
*/
|
||||
private static List<String> getDeltaText(AbstractDelta<String> delta) {
|
||||
List<String> buffer = new ArrayList<>();
|
||||
for (String line : delta.getSource().getLines()) {
|
||||
buffer.add("-" + line);
|
||||
}
|
||||
for (String line : delta.getTarget().getLines()) {
|
||||
buffer.add("+" + line);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
private UnifiedDiffUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare the differences between two files and return to the original file and diff format
|
||||
*
|
||||
* (This method compares the original file with the comparison file to obtain a diff, and inserts the diff into the corresponding position of the original file.
|
||||
* You can see all the differences and unmodified places from the original file.
|
||||
* Also, this will be very easy and useful for making side-by-side comparison display applications,
|
||||
* for example, if you use diff2html (https://github.com/rtfpessoa/diff2html#usage)
|
||||
* Wait for tools to display your differences on html pages, you only need to insert the return value into your js code)
|
||||
*
|
||||
* @param original Original file content
|
||||
* @param revised revised file content
|
||||
*
|
||||
*/
|
||||
public static List<String> generateOriginalAndDiff(List<String> original, List<String> revised) {
|
||||
return generateOriginalAndDiff(original, revised, null, null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compare the differences between two files and return to the original file and diff format
|
||||
*
|
||||
* (This method compares the original file with the comparison file to obtain a diff, and inserts the diff into the corresponding position of the original file.
|
||||
* You can see all the differences and unmodified places from the original file.
|
||||
* Also, this will be very easy and useful for making side-by-side comparison display applications,
|
||||
* for example, if you use diff2html (https://github.com/rtfpessoa/diff2html#usage)
|
||||
* Wait for tools to display your differences on html pages, you only need to insert the return value into your js code)
|
||||
*
|
||||
* @param original Original file content
|
||||
* @param revised revised file content
|
||||
* @param originalFileName Original file name
|
||||
* @param revisedFileName revised file name
|
||||
*/
|
||||
public static List<String> generateOriginalAndDiff(List<String> original, List<String> revised, String originalFileName, String revisedFileName) {
|
||||
String originalFileNameTemp = originalFileName;
|
||||
String revisedFileNameTemp = revisedFileName;
|
||||
if (originalFileNameTemp == null) {
|
||||
originalFileNameTemp = "original";
|
||||
}
|
||||
if (revisedFileNameTemp == null) {
|
||||
revisedFileNameTemp = "revised";
|
||||
}
|
||||
Patch<String> patch = DiffUtils.diff(original, revised);
|
||||
List<String> unifiedDiff = generateUnifiedDiff(originalFileNameTemp, revisedFileNameTemp, original, patch, 0);
|
||||
if (unifiedDiff.isEmpty()) {
|
||||
unifiedDiff.add("--- " + originalFileNameTemp);
|
||||
unifiedDiff.add("+++ " + revisedFileNameTemp);
|
||||
unifiedDiff.add("@@ -0,0 +0,0 @@");
|
||||
} else if (unifiedDiff.size() >= 3 && !unifiedDiff.get(2).contains("@@ -1,")) {
|
||||
unifiedDiff.set(1, unifiedDiff.get(1));
|
||||
unifiedDiff.add(2, "@@ -0,0 +0,0 @@");
|
||||
}
|
||||
List<String> originalWithPrefix = original.stream().map(v -> " " + v).collect(Collectors.toList());
|
||||
return insertOrig(originalWithPrefix, unifiedDiff);
|
||||
}
|
||||
|
||||
//Insert the diff format to the original file
|
||||
private static List<String> insertOrig(List<String> original, List<String> unifiedDiff) {
|
||||
List<String> result = new ArrayList<>();
|
||||
List<List<String>> diffList = new ArrayList<>();
|
||||
List<String> diff = new ArrayList<>();
|
||||
for (int i = 0; i < unifiedDiff.size(); i++) {
|
||||
String u = unifiedDiff.get(i);
|
||||
if (u.startsWith("@@") && !"@@ -0,0 +0,0 @@".equals(u) && !u.contains("@@ -1,")) {
|
||||
List<String> twoList = new ArrayList<>();
|
||||
twoList.addAll(diff);
|
||||
diffList.add(twoList);
|
||||
diff.clear();
|
||||
diff.add(u);
|
||||
continue;
|
||||
}
|
||||
if (i == unifiedDiff.size() - 1) {
|
||||
diff.add(u);
|
||||
List<String> twoList = new ArrayList<>();
|
||||
twoList.addAll(diff);
|
||||
diffList.add(twoList);
|
||||
diff.clear();
|
||||
break;
|
||||
}
|
||||
diff.add(u);
|
||||
}
|
||||
insertOrig(diffList, result, original);
|
||||
return result;
|
||||
}
|
||||
|
||||
//Insert the diff format to the original file
|
||||
private static void insertOrig(List<List<String>> diffList, List<String> result, List<String> original) {
|
||||
for (int i = 0; i < diffList.size(); i++) {
|
||||
List<String> diff = diffList.get(i);
|
||||
List<String> nexDiff = i == diffList.size() - 1 ? null : diffList.get(i + 1);
|
||||
String simb = i == 0 ? diff.get(2) : diff.get(0);
|
||||
String nexSimb = nexDiff == null ? null : nexDiff.get(0);
|
||||
insert(result, diff);
|
||||
Map<String, Integer> map = getRowMap(simb);
|
||||
if (null != nexSimb) {
|
||||
Map<String, Integer> nexMap = getRowMap(nexSimb);
|
||||
int start = 0;
|
||||
if (map.get("orgRow") != 0) {
|
||||
start = map.get("orgRow") + map.get("orgDel") - 1;
|
||||
}
|
||||
int end = nexMap.get("revRow") - 2;
|
||||
insert(result, getOrigList(original, start, end));
|
||||
}
|
||||
int start = map.get("orgRow") + map.get("orgDel") - 1;
|
||||
start = start == -1 ? 0 : start;
|
||||
if (simb.contains("@@ -1,") && null == nexSimb && map.get("orgDel") != original.size()) {
|
||||
insert(result, getOrigList(original, start, original.size() - 1));
|
||||
} else if (null == nexSimb && (map.get("orgRow") + map.get("orgDel") - 1) < original.size()) {
|
||||
insert(result, getOrigList(original, start, original.size() - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Insert the unchanged content in the source file into result
|
||||
private static void insert(List<String> result, List<String> noChangeContent) {
|
||||
for (String ins : noChangeContent) {
|
||||
result.add(ins);
|
||||
}
|
||||
}
|
||||
|
||||
//Parse the line containing @@ to get the modified line number to delete or add a few lines
|
||||
private static Map<String, Integer> getRowMap(String str) {
|
||||
Map<String, Integer> map = new HashMap<>();
|
||||
if (str.startsWith("@@")) {
|
||||
String[] sp = str.split(" ");
|
||||
String org = sp[1];
|
||||
String[] orgSp = org.split(",");
|
||||
map.put("orgRow", Integer.valueOf(orgSp[0].substring(1)));
|
||||
map.put("orgDel", Integer.valueOf(orgSp[1]));
|
||||
String[] revSp = org.split(",");
|
||||
map.put("revRow", Integer.valueOf(revSp[0].substring(1)));
|
||||
map.put("revAdd", Integer.valueOf(revSp[1]));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
//Get the specified part of the line from the original file
|
||||
private static List<String> getOrigList(List<String> originalWithPrefix, int start, int end) {
|
||||
List<String> list = new ArrayList<>();
|
||||
if (originalWithPrefix.size() >= 1 && start <= end && end < originalWithPrefix.size()) {
|
||||
int startTemp = start;
|
||||
for (; startTemp <= end; startTemp++) {
|
||||
list.add(originalWithPrefix.get(startTemp));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
47
src/com/github/difflib/algorithm/Change.java
Normal file
47
src/com/github/difflib/algorithm/Change.java
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2017 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.algorithm;
|
||||
|
||||
import com.github.difflib.patch.DeltaType;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author <a href="t.warneke@gmx.net">Tobias Warneke</a>
|
||||
*/
|
||||
public class Change {
|
||||
|
||||
public final DeltaType deltaType;
|
||||
public final int startOriginal;
|
||||
public final int endOriginal;
|
||||
public final int startRevised;
|
||||
public final int endRevised;
|
||||
|
||||
public Change(DeltaType deltaType, int startOriginal, int endOriginal, int startRevised, int endRevised) {
|
||||
this.deltaType = deltaType;
|
||||
this.startOriginal = startOriginal;
|
||||
this.endOriginal = endOriginal;
|
||||
this.startRevised = startRevised;
|
||||
this.endRevised = endRevised;
|
||||
}
|
||||
|
||||
public Change withEndOriginal(int endOriginal) {
|
||||
return new Change(deltaType, startOriginal, endOriginal, startRevised, endRevised);
|
||||
}
|
||||
|
||||
public Change withEndRevised(int endRevised) {
|
||||
return new Change(deltaType, startOriginal, endOriginal, startRevised, endRevised);
|
||||
}
|
||||
}
|
||||
29
src/com/github/difflib/algorithm/DiffAlgorithmFactory.java
Normal file
29
src/com/github/difflib/algorithm/DiffAlgorithmFactory.java
Normal file
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2021 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.algorithm;
|
||||
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
/**
|
||||
* Tool to create new instances of a diff algorithm. This one is only needed at the moment to
|
||||
* set DiffUtils default diff algorithm.
|
||||
* @author tw
|
||||
*/
|
||||
public interface DiffAlgorithmFactory {
|
||||
<T> DiffAlgorithmI<T> create();
|
||||
|
||||
<T> DiffAlgorithmI<T> create(BiPredicate<T, T> equalizer);
|
||||
}
|
||||
50
src/com/github/difflib/algorithm/DiffAlgorithmI.java
Normal file
50
src/com/github/difflib/algorithm/DiffAlgorithmI.java
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2018 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.algorithm;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Interface of a diff algorithm.
|
||||
*
|
||||
* @author Tobias Warneke (t.warneke@gmx.net)
|
||||
* @param <T> type of data that is diffed.
|
||||
*/
|
||||
public interface DiffAlgorithmI<T> {
|
||||
|
||||
/**
|
||||
* Computes the changeset to patch the source list to the target list.
|
||||
*
|
||||
* @param source source data
|
||||
* @param target target data
|
||||
* @param progress progress listener
|
||||
* @return
|
||||
*/
|
||||
List<Change> computeDiff(List<T> source, List<T> target, DiffAlgorithmListener progress);
|
||||
|
||||
/**
|
||||
* Simple extension to compute a changeset using arrays.
|
||||
*
|
||||
* @param source
|
||||
* @param target
|
||||
* @param progress
|
||||
* @return
|
||||
*/
|
||||
default List<Change> computeDiff(T[] source, T[] target, DiffAlgorithmListener progress) {
|
||||
return computeDiff(Arrays.asList(source), Arrays.asList(target), progress);
|
||||
}
|
||||
}
|
||||
34
src/com/github/difflib/algorithm/DiffAlgorithmListener.java
Normal file
34
src/com/github/difflib/algorithm/DiffAlgorithmListener.java
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2018 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.algorithm;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Tobias Warneke (t.warneke@gmx.net)
|
||||
*/
|
||||
public interface DiffAlgorithmListener {
|
||||
void diffStart();
|
||||
|
||||
/**
|
||||
* This is a step within the diff algorithm. Due to different implementations the value
|
||||
* is not strict incrementing to the max and is not garantee to reach the max. It could
|
||||
* stop before.
|
||||
* @param value
|
||||
* @param max
|
||||
*/
|
||||
void diffStep(int value, int max);
|
||||
void diffEnd();
|
||||
}
|
||||
200
src/com/github/difflib/algorithm/myers/MyersDiff.java
Normal file
200
src/com/github/difflib/algorithm/myers/MyersDiff.java
Normal file
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright 2009-2017 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.algorithm.myers;
|
||||
|
||||
import com.github.difflib.algorithm.Change;
|
||||
import com.github.difflib.algorithm.DiffAlgorithmFactory;
|
||||
import com.github.difflib.algorithm.DiffAlgorithmI;
|
||||
import com.github.difflib.algorithm.DiffAlgorithmListener;
|
||||
import com.github.difflib.patch.DeltaType;
|
||||
import com.github.difflib.patch.Patch;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
/**
|
||||
* A clean-room implementation of Eugene Myers greedy differencing algorithm.
|
||||
*/
|
||||
public final class MyersDiff<T> implements DiffAlgorithmI<T> {
|
||||
|
||||
private final BiPredicate<T, T> equalizer;
|
||||
|
||||
public MyersDiff() {
|
||||
equalizer = Object::equals;
|
||||
}
|
||||
|
||||
public MyersDiff(final BiPredicate<T, T> equalizer) {
|
||||
Objects.requireNonNull(equalizer, "equalizer must not be null");
|
||||
this.equalizer = equalizer;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Return empty diff if get the error while procession the difference.
|
||||
*/
|
||||
@Override
|
||||
public List<Change> computeDiff(final List<T> source, final List<T> target, DiffAlgorithmListener progress) {
|
||||
Objects.requireNonNull(source, "source list must not be null");
|
||||
Objects.requireNonNull(target, "target list must not be null");
|
||||
|
||||
if (progress != null) {
|
||||
progress.diffStart();
|
||||
}
|
||||
PathNode path = buildPath(source, target, progress);
|
||||
List<Change> result = buildRevision(path, source, target);
|
||||
if (progress != null) {
|
||||
progress.diffEnd();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the minimum diffpath that expresses de differences between the
|
||||
* original and revised sequences, according to Gene Myers differencing
|
||||
* algorithm.
|
||||
*
|
||||
* @param orig The original sequence.
|
||||
* @param rev The revised sequence.
|
||||
* @return A minimum {@link PathNode Path} accross the differences graph.
|
||||
* @throws DifferentiationFailedException if a diff path could not be found.
|
||||
*/
|
||||
private PathNode buildPath(final List<T> orig, final List<T> rev, DiffAlgorithmListener progress) {
|
||||
Objects.requireNonNull(orig, "original sequence is null");
|
||||
Objects.requireNonNull(rev, "revised sequence is null");
|
||||
|
||||
// these are local constants
|
||||
final int N = orig.size();
|
||||
final int M = rev.size();
|
||||
|
||||
final int MAX = N + M + 1;
|
||||
final int size = 1 + 2 * MAX;
|
||||
final int middle = size / 2;
|
||||
final PathNode diagonal[] = new PathNode[size];
|
||||
|
||||
diagonal[middle + 1] = new PathNode(0, -1, true, true, null);
|
||||
for (int d = 0; d < MAX; d++) {
|
||||
if (progress != null) {
|
||||
progress.diffStep(d, MAX);
|
||||
}
|
||||
for (int k = -d; k <= d; k += 2) {
|
||||
final int kmiddle = middle + k;
|
||||
final int kplus = kmiddle + 1;
|
||||
final int kminus = kmiddle - 1;
|
||||
PathNode prev;
|
||||
int i;
|
||||
|
||||
if ((k == -d) || (k != d && diagonal[kminus].i < diagonal[kplus].i)) {
|
||||
i = diagonal[kplus].i;
|
||||
prev = diagonal[kplus];
|
||||
} else {
|
||||
i = diagonal[kminus].i + 1;
|
||||
prev = diagonal[kminus];
|
||||
}
|
||||
|
||||
diagonal[kminus] = null; // no longer used
|
||||
|
||||
int j = i - k;
|
||||
|
||||
PathNode node = new PathNode(i, j, false, false, prev);
|
||||
|
||||
while (i < N && j < M && equalizer.test(orig.get(i), rev.get(j))) {
|
||||
i++;
|
||||
j++;
|
||||
}
|
||||
|
||||
if (i != node.i) {
|
||||
node = new PathNode(i, j, true, false, node);
|
||||
}
|
||||
|
||||
diagonal[kmiddle] = node;
|
||||
|
||||
if (i >= N && j >= M) {
|
||||
return diagonal[kmiddle];
|
||||
}
|
||||
}
|
||||
diagonal[middle + d - 1] = null;
|
||||
}
|
||||
// According to Myers, this cannot happen
|
||||
throw new IllegalStateException("could not find a diff path");
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a {@link Patch} from a difference path.
|
||||
*
|
||||
* @param actualPath The path.
|
||||
* @param orig The original sequence.
|
||||
* @param rev The revised sequence.
|
||||
* @return A {@link Patch} script corresponding to the path.
|
||||
* @throws DifferentiationFailedException if a {@link Patch} could not be
|
||||
* built from the given path.
|
||||
*/
|
||||
private List<Change> buildRevision(PathNode actualPath, List<T> orig, List<T> rev) {
|
||||
Objects.requireNonNull(actualPath, "path is null");
|
||||
Objects.requireNonNull(orig, "original sequence is null");
|
||||
Objects.requireNonNull(rev, "revised sequence is null");
|
||||
|
||||
PathNode path = actualPath;
|
||||
List<Change> changes = new ArrayList<>();
|
||||
if (path.isSnake()) {
|
||||
path = path.prev;
|
||||
}
|
||||
while (path != null && path.prev != null && path.prev.j >= 0) {
|
||||
if (path.isSnake()) {
|
||||
throw new IllegalStateException("bad diffpath: found snake when looking for diff");
|
||||
}
|
||||
int i = path.i;
|
||||
int j = path.j;
|
||||
|
||||
path = path.prev;
|
||||
int ianchor = path.i;
|
||||
int janchor = path.j;
|
||||
|
||||
if (ianchor == i && janchor != j) {
|
||||
changes.add(new Change(DeltaType.INSERT, ianchor, i, janchor, j));
|
||||
} else if (ianchor != i && janchor == j) {
|
||||
changes.add(new Change(DeltaType.DELETE, ianchor, i, janchor, j));
|
||||
} else {
|
||||
changes.add(new Change(DeltaType.CHANGE, ianchor, i, janchor, j));
|
||||
}
|
||||
|
||||
if (path.isSnake()) {
|
||||
path = path.prev;
|
||||
}
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory to create instances of this specific diff algorithm.
|
||||
*/
|
||||
public static DiffAlgorithmFactory factory() {
|
||||
return new DiffAlgorithmFactory() {
|
||||
@Override
|
||||
public <T> DiffAlgorithmI<T>
|
||||
create() {
|
||||
return new MyersDiff<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> DiffAlgorithmI<T>
|
||||
create(BiPredicate < T, T > equalizer) {
|
||||
return new MyersDiff<>(equalizer);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
* Copyright 2021 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.algorithm.myers;
|
||||
|
||||
import com.github.difflib.algorithm.Change;
|
||||
import com.github.difflib.algorithm.DiffAlgorithmFactory;
|
||||
import com.github.difflib.algorithm.DiffAlgorithmI;
|
||||
import com.github.difflib.algorithm.DiffAlgorithmListener;
|
||||
import com.github.difflib.patch.DeltaType;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author tw
|
||||
*/
|
||||
public class MyersDiffWithLinearSpace<T> implements DiffAlgorithmI<T> {
|
||||
|
||||
private final BiPredicate<T, T> equalizer;
|
||||
|
||||
public MyersDiffWithLinearSpace() {
|
||||
equalizer = Object::equals;
|
||||
}
|
||||
|
||||
public MyersDiffWithLinearSpace(final BiPredicate<T, T> equalizer) {
|
||||
Objects.requireNonNull(equalizer, "equalizer must not be null");
|
||||
this.equalizer = equalizer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Change> computeDiff(List<T> source, List<T> target, DiffAlgorithmListener progress) {
|
||||
Objects.requireNonNull(source, "source list must not be null");
|
||||
Objects.requireNonNull(target, "target list must not be null");
|
||||
|
||||
if (progress != null) {
|
||||
progress.diffStart();
|
||||
}
|
||||
|
||||
DiffData data = new DiffData(source, target);
|
||||
|
||||
int maxIdx = source.size() + target.size();
|
||||
|
||||
buildScript(data, 0, source.size(), 0, target.size(), idx -> {
|
||||
if (progress != null) {
|
||||
progress.diffStep(idx, maxIdx);
|
||||
}
|
||||
});
|
||||
|
||||
if (progress != null) {
|
||||
progress.diffEnd();
|
||||
}
|
||||
return data.script;
|
||||
}
|
||||
|
||||
private void buildScript(DiffData data, int start1, int end1, int start2, int end2, Consumer<Integer> progress) {
|
||||
if (progress != null) {
|
||||
progress.accept((end1 - start1) / 2 + (end2 - start2) / 2);
|
||||
}
|
||||
final Snake middle = getMiddleSnake(data, start1, end1, start2, end2);
|
||||
if (middle == null
|
||||
|| middle.start == end1 && middle.diag == end1 - end2
|
||||
|| middle.end == start1 && middle.diag == start1 - start2) {
|
||||
int i = start1;
|
||||
int j = start2;
|
||||
while (i < end1 || j < end2) {
|
||||
if (i < end1 && j < end2 && equalizer.test(data.source.get(i), data.target.get(j))) {
|
||||
//script.append(new KeepCommand<>(left.charAt(i)));
|
||||
++i;
|
||||
++j;
|
||||
} else {
|
||||
//TODO: compress these commands.
|
||||
if (end1 - start1 > end2 - start2) {
|
||||
//script.append(new DeleteCommand<>(left.charAt(i)));
|
||||
if (data.script.isEmpty()
|
||||
|| data.script.get(data.script.size() - 1).endOriginal != i
|
||||
|| data.script.get(data.script.size() - 1).deltaType != DeltaType.DELETE) {
|
||||
data.script.add(new Change(DeltaType.DELETE, i, i + 1, j, j));
|
||||
} else {
|
||||
data.script.set(data.script.size() - 1, data.script.get(data.script.size() - 1).withEndOriginal(i + 1));
|
||||
}
|
||||
++i;
|
||||
} else {
|
||||
if (data.script.isEmpty()
|
||||
|| data.script.get(data.script.size() - 1).endRevised != j
|
||||
|| data.script.get(data.script.size() - 1).deltaType != DeltaType.INSERT) {
|
||||
data.script.add(new Change(DeltaType.INSERT, i, i, j, j + 1));
|
||||
} else {
|
||||
data.script.set(data.script.size() - 1, data.script.get(data.script.size() - 1).withEndRevised(j + 1));
|
||||
}
|
||||
++j;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
buildScript(data, start1, middle.start, start2, middle.start - middle.diag, progress);
|
||||
buildScript(data, middle.end, end1, middle.end - middle.diag, end2, progress);
|
||||
}
|
||||
}
|
||||
|
||||
private Snake getMiddleSnake(DiffData data, int start1, int end1, int start2, int end2) {
|
||||
final int m = end1 - start1;
|
||||
final int n = end2 - start2;
|
||||
if (m == 0 || n == 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final int delta = m - n;
|
||||
final int sum = n + m;
|
||||
final int offset = (sum % 2 == 0 ? sum : sum + 1) / 2;
|
||||
data.vDown[1 + offset] = start1;
|
||||
data.vUp[1 + offset] = end1 + 1;
|
||||
|
||||
for (int d = 0; d <= offset; ++d) {
|
||||
// Down
|
||||
for (int k = -d; k <= d; k += 2) {
|
||||
// First step
|
||||
|
||||
final int i = k + offset;
|
||||
if (k == -d || k != d && data.vDown[i - 1] < data.vDown[i + 1]) {
|
||||
data.vDown[i] = data.vDown[i + 1];
|
||||
} else {
|
||||
data.vDown[i] = data.vDown[i - 1] + 1;
|
||||
}
|
||||
|
||||
int x = data.vDown[i];
|
||||
int y = x - start1 + start2 - k;
|
||||
|
||||
while (x < end1 && y < end2 && equalizer.test(data.source.get(x), data.target.get(y))) {
|
||||
data.vDown[i] = ++x;
|
||||
++y;
|
||||
}
|
||||
// Second step
|
||||
if (delta % 2 != 0 && delta - d <= k && k <= delta + d) {
|
||||
if (data.vUp[i - delta] <= data.vDown[i]) {
|
||||
return buildSnake(data, data.vUp[i - delta], k + start1 - start2, end1, end2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Up
|
||||
for (int k = delta - d; k <= delta + d; k += 2) {
|
||||
// First step
|
||||
final int i = k + offset - delta;
|
||||
if (k == delta - d
|
||||
|| k != delta + d && data.vUp[i + 1] <= data.vUp[i - 1]) {
|
||||
data.vUp[i] = data.vUp[i + 1] - 1;
|
||||
} else {
|
||||
data.vUp[i] = data.vUp[i - 1];
|
||||
}
|
||||
|
||||
int x = data.vUp[i] - 1;
|
||||
int y = x - start1 + start2 - k;
|
||||
while (x >= start1 && y >= start2 && equalizer.test(data.source.get(x), data.target.get(y))) {
|
||||
data.vUp[i] = x--;
|
||||
y--;
|
||||
}
|
||||
// Second step
|
||||
if (delta % 2 == 0 && -d <= k && k <= d) {
|
||||
if (data.vUp[i] <= data.vDown[i + delta]) {
|
||||
return buildSnake(data, data.vUp[i], k + start1 - start2, end1, end2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// According to Myers, this cannot happen
|
||||
throw new IllegalStateException("could not find a diff path");
|
||||
}
|
||||
|
||||
private Snake buildSnake(DiffData data, final int start, final int diag, final int end1, final int end2) {
|
||||
int end = start;
|
||||
while (end - diag < end2 && end < end1 && equalizer.test(data.source.get(end), data.target.get(end - diag))) {
|
||||
++end;
|
||||
}
|
||||
return new Snake(start, end, diag);
|
||||
}
|
||||
|
||||
private class DiffData {
|
||||
|
||||
final int size;
|
||||
final int[] vDown;
|
||||
final int[] vUp;
|
||||
final List<Change> script;
|
||||
final List<T> source;
|
||||
final List<T> target;
|
||||
|
||||
public DiffData(List<T> source, List<T> target) {
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
size = source.size() + target.size() + 2;
|
||||
vDown = new int[size];
|
||||
vUp = new int[size];
|
||||
script = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
private class Snake {
|
||||
|
||||
final int start;
|
||||
final int end;
|
||||
final int diag;
|
||||
|
||||
public Snake(final int start, final int end, final int diag) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.diag = diag;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory to create instances of this specific diff algorithm.
|
||||
*/
|
||||
public static DiffAlgorithmFactory factory() {
|
||||
return new DiffAlgorithmFactory() {
|
||||
@Override
|
||||
public <T> DiffAlgorithmI<T>
|
||||
create() {
|
||||
return new MyersDiffWithLinearSpace<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> DiffAlgorithmI<T>
|
||||
create(BiPredicate < T, T > equalizer) {
|
||||
return new MyersDiffWithLinearSpace<>(equalizer);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
110
src/com/github/difflib/algorithm/myers/PathNode.java
Normal file
110
src/com/github/difflib/algorithm/myers/PathNode.java
Normal file
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright 2009-2017 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.algorithm.myers;
|
||||
|
||||
/**
|
||||
* A node in a diffpath.
|
||||
*
|
||||
* @author <a href="mailto:juanco@suigeneris.org">Juanco Anez</a>
|
||||
*/
|
||||
public final class PathNode {
|
||||
|
||||
/**
|
||||
* Position in the original sequence.
|
||||
*/
|
||||
public final int i;
|
||||
/**
|
||||
* Position in the revised sequence.
|
||||
*/
|
||||
public final int j;
|
||||
/**
|
||||
* The previous node in the path.
|
||||
*/
|
||||
public final PathNode prev;
|
||||
|
||||
public final boolean snake;
|
||||
|
||||
public final boolean bootstrap;
|
||||
|
||||
/**
|
||||
* Concatenates a new path node with an existing diffpath.
|
||||
*
|
||||
* @param i The position in the original sequence for the new node.
|
||||
* @param j The position in the revised sequence for the new node.
|
||||
* @param prev The previous node in the path.
|
||||
*/
|
||||
public PathNode(int i, int j, boolean snake, boolean bootstrap, PathNode prev) {
|
||||
this.i = i;
|
||||
this.j = j;
|
||||
this.bootstrap = bootstrap;
|
||||
if (snake) {
|
||||
this.prev = prev;
|
||||
} else {
|
||||
this.prev = prev == null ? null : prev.previousSnake();
|
||||
}
|
||||
this.snake = snake;
|
||||
}
|
||||
|
||||
public boolean isSnake() {
|
||||
return snake;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this a bootstrap node?
|
||||
* <p>
|
||||
* In bottstrap nodes one of the two corrdinates is less than zero.
|
||||
*
|
||||
* @return tru if this is a bootstrap node.
|
||||
*/
|
||||
public boolean isBootstrap() {
|
||||
return bootstrap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips sequences of {@link PathNode PathNodes} until a snake or bootstrap node is found, or the end of the
|
||||
* path is reached.
|
||||
*
|
||||
* @return The next first {@link PathNode} or bootstrap node in the path, or <code>null</code> if none found.
|
||||
*/
|
||||
public final PathNode previousSnake() {
|
||||
if (isBootstrap()) {
|
||||
return null;
|
||||
}
|
||||
if (!isSnake() && prev != null) {
|
||||
return prev.previousSnake();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder buf = new StringBuilder("[");
|
||||
PathNode node = this;
|
||||
while (node != null) {
|
||||
buf.append("(");
|
||||
buf.append(node.i);
|
||||
buf.append(",");
|
||||
buf.append(node.j);
|
||||
buf.append(")");
|
||||
node = node.prev;
|
||||
}
|
||||
buf.append("]");
|
||||
return buf.toString();
|
||||
}
|
||||
}
|
||||
116
src/com/github/difflib/patch/AbstractDelta.java
Normal file
116
src/com/github/difflib/patch/AbstractDelta.java
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright 2018 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.patch;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Abstract delta between a source and a target.
|
||||
* @author Tobias Warneke (t.warneke@gmx.net)
|
||||
*/
|
||||
public abstract class AbstractDelta<T> implements Serializable {
|
||||
private final Chunk<T> source;
|
||||
private final Chunk<T> target;
|
||||
private final DeltaType type;
|
||||
|
||||
public AbstractDelta(DeltaType type, Chunk<T> source, Chunk<T> target) {
|
||||
Objects.requireNonNull(source);
|
||||
Objects.requireNonNull(target);
|
||||
Objects.requireNonNull(type);
|
||||
this.type = type;
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public Chunk<T> getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
public Chunk<T> getTarget() {
|
||||
return target;
|
||||
}
|
||||
|
||||
public DeltaType getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the chunk of this delta, to fit the target.
|
||||
* @param target
|
||||
* @throws PatchFailedException
|
||||
*/
|
||||
protected VerifyChunk verifyChunkToFitTarget(List<T> target) throws PatchFailedException {
|
||||
return getSource().verifyChunk(target);
|
||||
}
|
||||
|
||||
protected VerifyChunk verifyAndApplyTo(List<T> target) throws PatchFailedException {
|
||||
final VerifyChunk verify = verifyChunkToFitTarget(target);
|
||||
if (verify == VerifyChunk.OK) {
|
||||
applyTo(target);
|
||||
}
|
||||
return verify;
|
||||
}
|
||||
|
||||
protected abstract void applyTo(List<T> target) throws PatchFailedException;
|
||||
|
||||
protected abstract void restore(List<T> target);
|
||||
|
||||
/**
|
||||
* Apply patch fuzzy.
|
||||
*
|
||||
* @param target the list this patch will be applied to
|
||||
* @param fuzz the number of elements to ignore before/after the patched elements
|
||||
* @param position the position this patch will be applied to. ignores {@code source.getPosition()}
|
||||
* @see <a href="https://www.gnu.org/software/diffutils/manual/html_node/Inexact.html">Description of Fuzzy Patch</a> for more information.
|
||||
*/
|
||||
@SuppressWarnings("RedundantThrows")
|
||||
protected void applyFuzzyToAt(List<T> target, int fuzz, int position) throws PatchFailedException {
|
||||
throw new UnsupportedOperationException(this.getClass().getSimpleName() + " does not supports applying patch fuzzy");
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new delta of the actual instance with customized chunk data.
|
||||
*/
|
||||
public abstract AbstractDelta<T> withChunks(Chunk<T> original, Chunk<T> revised);
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.source, this.target, this.type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
final AbstractDelta<?> other = (AbstractDelta<?>) obj;
|
||||
if (!Objects.equals(this.source, other.source)) {
|
||||
return false;
|
||||
}
|
||||
if (!Objects.equals(this.target, other.target)) {
|
||||
return false;
|
||||
}
|
||||
return this.type == other.type;
|
||||
}
|
||||
}
|
||||
92
src/com/github/difflib/patch/ChangeDelta.java
Normal file
92
src/com/github/difflib/patch/ChangeDelta.java
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright 2009-2017 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.patch;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Describes the change-delta between original and revised texts.
|
||||
*
|
||||
* @author <a href="dm.naumenko@gmail.com">Dmitry Naumenko</a>
|
||||
* @param <T> The type of the compared elements in the data 'lines'.
|
||||
*/
|
||||
public final class ChangeDelta<T> extends AbstractDelta<T> {
|
||||
|
||||
/**
|
||||
* Creates a change delta with the two given chunks.
|
||||
*
|
||||
* @param source The source chunk. Must not be {@code null}.
|
||||
* @param target The target chunk. Must not be {@code null}.
|
||||
*/
|
||||
public ChangeDelta(Chunk<T> source, Chunk<T> target) {
|
||||
super(DeltaType.CHANGE, source, target);
|
||||
Objects.requireNonNull(source, "source must not be null");
|
||||
Objects.requireNonNull(target, "target must not be null");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyTo(List<T> target) throws PatchFailedException {
|
||||
int position = getSource().getPosition();
|
||||
int size = getSource().size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
target.remove(position);
|
||||
}
|
||||
int i = 0;
|
||||
for (T line : getTarget().getLines()) {
|
||||
target.add(position + i, line);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void restore(List<T> target) {
|
||||
int position = getTarget().getPosition();
|
||||
int size = getTarget().size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
target.remove(position);
|
||||
}
|
||||
int i = 0;
|
||||
for (T line : getSource().getLines()) {
|
||||
target.add(position + i, line);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
protected void applyFuzzyToAt(List<T> target, int fuzz, int position) throws PatchFailedException {
|
||||
int size = getSource().size();
|
||||
for (int i = fuzz; i < size - fuzz; i++) {
|
||||
target.remove(position + fuzz);
|
||||
}
|
||||
|
||||
int i = fuzz;
|
||||
for (T line : getTarget().getLines().subList(fuzz, getTarget().size() - fuzz)) {
|
||||
target.add(position + i, line);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[ChangeDelta, position: " + getSource().getPosition() + ", lines: "
|
||||
+ getSource().getLines() + " to " + getTarget().getLines() + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractDelta<T> withChunks(Chunk<T> original, Chunk<T> revised) {
|
||||
return new ChangeDelta<T>(original, revised);
|
||||
}
|
||||
}
|
||||
195
src/com/github/difflib/patch/Chunk.java
Normal file
195
src/com/github/difflib/patch/Chunk.java
Normal file
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright 2009-2017 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.patch;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Holds the information about the part of text involved in the diff process
|
||||
*
|
||||
* <p>
|
||||
* Text is represented as <code>Object[]</code> because the diff engine is
|
||||
* capable of handling more than plain ascci. In fact, arrays or lists of any
|
||||
* type that implements {@link Object#hashCode hashCode()} and
|
||||
* {@link Object#equals equals()} correctly can be subject to
|
||||
* differencing using this library.
|
||||
* </p>
|
||||
*
|
||||
* @author <a href="dm.naumenko@gmail.com>Dmitry Naumenko</a>
|
||||
* @param <T> The type of the compared elements in the 'lines'.
|
||||
*/
|
||||
public final class Chunk<T> implements Serializable {
|
||||
|
||||
private final int position;
|
||||
private List<T> lines;
|
||||
private final List<Integer> changePosition;
|
||||
|
||||
/**
|
||||
* Creates a chunk and saves a copy of affected lines
|
||||
*
|
||||
* @param position the start position
|
||||
* @param lines the affected lines
|
||||
* @param changePosition the positions of changed lines
|
||||
*/
|
||||
public Chunk(int position, List<T> lines, List<Integer> changePosition) {
|
||||
this.position = position;
|
||||
this.lines = new ArrayList<>(lines);
|
||||
this.changePosition = changePosition != null ? new ArrayList<>(changePosition) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a chunk and saves a copy of affected lines
|
||||
*
|
||||
* @param position the start position
|
||||
* @param lines the affected lines
|
||||
*/
|
||||
public Chunk(int position, List<T> lines) {
|
||||
this(position, lines, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a chunk and saves a copy of affected lines
|
||||
*
|
||||
* @param position the start position
|
||||
* @param lines the affected lines
|
||||
* @param changePosition the positions of changed lines
|
||||
*/
|
||||
public Chunk(int position, T[] lines, List<Integer> changePosition) {
|
||||
this.position = position;
|
||||
this.lines = Arrays.asList(lines);
|
||||
this.changePosition = changePosition != null ? new ArrayList<>(changePosition) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a chunk and saves a copy of affected lines
|
||||
*
|
||||
* @param position the start position
|
||||
* @param lines the affected lines
|
||||
*/
|
||||
public Chunk(int position, T[] lines) {
|
||||
this(position, lines, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that this chunk's saved text matches the corresponding text in
|
||||
* the given sequence.
|
||||
*
|
||||
* @param target the sequence to verify against.
|
||||
* @throws PatchFailedException
|
||||
*/
|
||||
public VerifyChunk verifyChunk(List<T> target) throws PatchFailedException {
|
||||
return verifyChunk(target, 0, getPosition());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies that this chunk's saved text matches the corresponding text in
|
||||
* the given sequence.
|
||||
*
|
||||
* @param target the sequence to verify against.
|
||||
* @param fuzz the count of ignored prefix/suffix
|
||||
* @param position the position of target
|
||||
* @throws PatchFailedException
|
||||
*/
|
||||
public VerifyChunk verifyChunk(List<T> target, int fuzz, int position) throws PatchFailedException {
|
||||
//noinspection UnnecessaryLocalVariable
|
||||
int startIndex = fuzz;
|
||||
int lastIndex = size() - fuzz;
|
||||
int last = position + size() - 1;
|
||||
|
||||
if (position + fuzz > target.size() || last - fuzz > target.size()) {
|
||||
return VerifyChunk.POSITION_OUT_OF_TARGET;
|
||||
}
|
||||
for (int i = startIndex; i < lastIndex; i++) {
|
||||
if (!target.get(position + i).equals(lines.get(i))) {
|
||||
return VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET;
|
||||
}
|
||||
}
|
||||
return VerifyChunk.OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the start position of chunk in the text
|
||||
*/
|
||||
public int getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public void setLines(List<T> lines) {
|
||||
this.lines = lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the affected lines
|
||||
*/
|
||||
public List<T> getLines() {
|
||||
return lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the positions of changed lines of chunk in the text
|
||||
*/
|
||||
public List<Integer> getChangePosition() {
|
||||
return changePosition;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return lines.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the index of the last line of the chunk.
|
||||
*/
|
||||
public int last() {
|
||||
return getPosition() + size() - 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(lines, position, size());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Chunk<?> other = (Chunk<?>) obj;
|
||||
if (lines == null) {
|
||||
if (other.lines != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!lines.equals(other.lines)) {
|
||||
return false;
|
||||
}
|
||||
return position == other.position;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[position: " + position + ", size: " + size() + ", lines: " + lines + "]";
|
||||
}
|
||||
|
||||
}
|
||||
33
src/com/github/difflib/patch/ConflictOutput.java
Normal file
33
src/com/github/difflib/patch/ConflictOutput.java
Normal file
@@ -0,0 +1,33 @@
|
||||
/*-
|
||||
* #%L
|
||||
* java-diff-utils
|
||||
* %%
|
||||
* Copyright (C) 2009 - 2017 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.
|
||||
* #L%
|
||||
*/
|
||||
package com.github.difflib.patch;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author tw
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ConflictOutput<T> extends Serializable {
|
||||
|
||||
public void processConflict(VerifyChunk verifyChunk, AbstractDelta<T> delta, List<T> result) throws PatchFailedException;
|
||||
}
|
||||
66
src/com/github/difflib/patch/DeleteDelta.java
Normal file
66
src/com/github/difflib/patch/DeleteDelta.java
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2009-2017 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.patch;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Describes the delete-delta between original and revised texts.
|
||||
*
|
||||
* @author <a href="dm.naumenko@gmail.com">Dmitry Naumenko</a>
|
||||
* @param <T> The type of the compared elements in the 'lines'.
|
||||
*/
|
||||
public final class DeleteDelta<T> extends AbstractDelta<T> {
|
||||
|
||||
/**
|
||||
* Creates a change delta with the two given chunks.
|
||||
*
|
||||
* @param original The original chunk. Must not be {@code null}.
|
||||
* @param revised The original chunk. Must not be {@code null}.
|
||||
*/
|
||||
public DeleteDelta(Chunk<T> original, Chunk<T> revised) {
|
||||
super(DeltaType.DELETE, original, revised);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyTo(List<T> target) throws PatchFailedException {
|
||||
int position = getSource().getPosition();
|
||||
int size = getSource().size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
target.remove(position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void restore(List<T> target) {
|
||||
int position = this.getTarget().getPosition();
|
||||
List<T> lines = this.getSource().getLines();
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
target.add(position + i, lines.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[DeleteDelta, position: " + getSource().getPosition() + ", lines: "
|
||||
+ getSource().getLines() + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractDelta<T> withChunks(Chunk<T> original, Chunk<T> revised) {
|
||||
return new DeleteDelta<T>(original, revised);
|
||||
}
|
||||
}
|
||||
50
src/com/github/difflib/patch/DeltaType.java
Normal file
50
src/com/github/difflib/patch/DeltaType.java
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Copyright 2017 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.patch;
|
||||
|
||||
/**
|
||||
* Specifies the type of the delta. There are three types of modifications from
|
||||
* the original to get the revised text.
|
||||
*
|
||||
* CHANGE: a block of data of the original is replaced by another block of data.
|
||||
* DELETE: a block of data of the original is removed
|
||||
* INSERT: at a position of the original a block of data is inserted
|
||||
*
|
||||
* to be complete there is also
|
||||
*
|
||||
* EQUAL: a block of data of original and the revised text is equal
|
||||
*
|
||||
* which is no change at all.
|
||||
*
|
||||
*/
|
||||
public enum DeltaType {
|
||||
/**
|
||||
* A change in the original.
|
||||
*/
|
||||
CHANGE,
|
||||
/**
|
||||
* A delete from the original.
|
||||
*/
|
||||
DELETE,
|
||||
/**
|
||||
* An insert into the original.
|
||||
*/
|
||||
INSERT,
|
||||
/**
|
||||
* An do nothing.
|
||||
*/
|
||||
EQUAL
|
||||
}
|
||||
33
src/com/github/difflib/patch/DiffException.java
Normal file
33
src/com/github/difflib/patch/DiffException.java
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2009-2017 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.patch;
|
||||
|
||||
/**
|
||||
* Base class for all exceptions emanating from this package.
|
||||
*
|
||||
* @author <a href="mailto:juanco@suigeneris.org">Juanco Anez</a>
|
||||
*/
|
||||
public class DiffException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public DiffException() {
|
||||
}
|
||||
|
||||
public DiffException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
56
src/com/github/difflib/patch/EqualDelta.java
Normal file
56
src/com/github/difflib/patch/EqualDelta.java
Normal file
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright 2020 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.patch;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This delta contains equal lines of data. Therefore nothing is to do in applyTo and restore.
|
||||
* @author tobens
|
||||
*/
|
||||
public class EqualDelta<T> extends AbstractDelta<T> {
|
||||
|
||||
public EqualDelta(Chunk<T> source, Chunk<T> target) {
|
||||
super(DeltaType.EQUAL, source, target);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyTo(List<T> target) throws PatchFailedException {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void restore(List<T> target) {
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
protected void applyFuzzyToAt(List<T> target, int fuzz, int delta) {
|
||||
// equals so no operations
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[EqualDelta, position: " + getSource().getPosition() + ", lines: "
|
||||
+ getSource().getLines() + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractDelta<T> withChunks(Chunk<T> original, Chunk<T> revised) {
|
||||
return new EqualDelta<T>(original, revised);
|
||||
}
|
||||
}
|
||||
66
src/com/github/difflib/patch/InsertDelta.java
Normal file
66
src/com/github/difflib/patch/InsertDelta.java
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* Copyright 2009-2017 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.patch;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Describes the add-delta between original and revised texts.
|
||||
*
|
||||
* @author <a href="dm.naumenko@gmail.com">Dmitry Naumenko</a>
|
||||
* @param <T> The type of the compared elements in the 'lines'.
|
||||
*/
|
||||
public final class InsertDelta<T> extends AbstractDelta<T> {
|
||||
|
||||
/**
|
||||
* Creates an insert delta with the two given chunks.
|
||||
*
|
||||
* @param original The original chunk. Must not be {@code null}.
|
||||
* @param revised The original chunk. Must not be {@code null}.
|
||||
*/
|
||||
public InsertDelta(Chunk<T> original, Chunk<T> revised) {
|
||||
super(DeltaType.INSERT, original, revised);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void applyTo(List<T> target) throws PatchFailedException {
|
||||
int position = this.getSource().getPosition();
|
||||
List<T> lines = this.getTarget().getLines();
|
||||
for (int i = 0; i < lines.size(); i++) {
|
||||
target.add(position + i, lines.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void restore(List<T> target) {
|
||||
int position = getTarget().getPosition();
|
||||
int size = getTarget().size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
target.remove(position);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[InsertDelta, position: " + getSource().getPosition()
|
||||
+ ", lines: " + getTarget().getLines() + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractDelta<T> withChunks(Chunk<T> original, Chunk<T> revised) {
|
||||
return new InsertDelta<T>(original, revised);
|
||||
}
|
||||
}
|
||||
344
src/com/github/difflib/patch/Patch.java
Normal file
344
src/com/github/difflib/patch/Patch.java
Normal file
@@ -0,0 +1,344 @@
|
||||
/*-
|
||||
* #%L
|
||||
* java-diff-utils
|
||||
* %%
|
||||
* Copyright (C) 2009 - 2017 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.
|
||||
* #L%
|
||||
*/
|
||||
package com.github.difflib.patch;
|
||||
|
||||
import static java.util.Comparator.comparing;
|
||||
import com.github.difflib.algorithm.Change;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.ListIterator;
|
||||
|
||||
/**
|
||||
* Describes the patch holding all deltas between the original and revised
|
||||
* texts.
|
||||
*
|
||||
* @author <a href="dm.naumenko@gmail.com">Dmitry Naumenko</a>
|
||||
* @param <T> The type of the compared elements in the 'lines'.
|
||||
*/
|
||||
public final class Patch<T> implements Serializable {
|
||||
|
||||
private final List<AbstractDelta<T>> deltas;
|
||||
|
||||
public Patch() {
|
||||
this(10);
|
||||
}
|
||||
|
||||
public Patch(int estimatedPatchSize) {
|
||||
deltas = new ArrayList<>(estimatedPatchSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new list, the patch is being applied to.
|
||||
*
|
||||
* @param target The list to apply the changes to.
|
||||
* @return A new list containing the applied patch.
|
||||
* @throws PatchFailedException if the patch cannot be applied
|
||||
*/
|
||||
public List<T> applyTo(List<T> target) throws PatchFailedException {
|
||||
List<T> result = new ArrayList<>(target);
|
||||
applyToExisting(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the patch to the supplied list.
|
||||
*
|
||||
* @param target The list to apply the changes to. This list has to be modifiable,
|
||||
* otherwise exceptions may be thrown, depending on the used type of list.
|
||||
* @throws PatchFailedException if the patch cannot be applied
|
||||
* @throws RuntimeException (or similar) if the list is not modifiable.
|
||||
*/
|
||||
public void applyToExisting(List<T> target) throws PatchFailedException {
|
||||
ListIterator<AbstractDelta<T>> it = getDeltas().listIterator(deltas.size());
|
||||
while (it.hasPrevious()) {
|
||||
AbstractDelta<T> delta = it.previous();
|
||||
VerifyChunk valid = delta.verifyAndApplyTo(target);
|
||||
if (valid != VerifyChunk.OK) {
|
||||
conflictOutput.processConflict(valid, delta, target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class PatchApplyingContext<T> {
|
||||
public final List<T> result;
|
||||
public final int maxFuzz;
|
||||
|
||||
// the position last patch applied to.
|
||||
public int lastPatchEnd = -1;
|
||||
|
||||
///// passing values from find to apply
|
||||
public int currentFuzz = 0;
|
||||
|
||||
public int defaultPosition;
|
||||
public boolean beforeOutRange = false;
|
||||
public boolean afterOutRange = false;
|
||||
|
||||
private PatchApplyingContext(List<T> result, int maxFuzz) {
|
||||
this.result = result;
|
||||
this.maxFuzz = maxFuzz;
|
||||
}
|
||||
}
|
||||
|
||||
public List<T> applyFuzzy(List<T> target, int maxFuzz) throws PatchFailedException {
|
||||
PatchApplyingContext<T> ctx = new PatchApplyingContext<>(new ArrayList<>(target), maxFuzz);
|
||||
|
||||
// the difference between patch's position and actually applied position
|
||||
int lastPatchDelta = 0;
|
||||
|
||||
for (AbstractDelta<T> delta : getDeltas()) {
|
||||
ctx.defaultPosition = delta.getSource().getPosition() + lastPatchDelta;
|
||||
int patchPosition = findPositionFuzzy(ctx, delta);
|
||||
if (0 <= patchPosition) {
|
||||
delta.applyFuzzyToAt(ctx.result, ctx.currentFuzz, patchPosition);
|
||||
lastPatchDelta = patchPosition - delta.getSource().getPosition();
|
||||
ctx.lastPatchEnd = delta.getSource().last() + lastPatchDelta;
|
||||
} else {
|
||||
conflictOutput.processConflict(VerifyChunk.CONTENT_DOES_NOT_MATCH_TARGET, delta, ctx.result);
|
||||
}
|
||||
}
|
||||
|
||||
return ctx.result;
|
||||
}
|
||||
|
||||
// negative for not found
|
||||
private int findPositionFuzzy(PatchApplyingContext<T> ctx, AbstractDelta<T> delta) throws PatchFailedException {
|
||||
for (int fuzz = 0; fuzz <= ctx.maxFuzz; fuzz++) {
|
||||
ctx.currentFuzz = fuzz;
|
||||
int foundPosition = findPositionWithFuzz(ctx, delta, fuzz);
|
||||
if (foundPosition >= 0) {
|
||||
return foundPosition;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
// negative for not found
|
||||
private int findPositionWithFuzz(PatchApplyingContext<T> ctx, AbstractDelta<T> delta, int fuzz) throws PatchFailedException {
|
||||
if (delta.getSource().verifyChunk(ctx.result, fuzz, ctx.defaultPosition) == VerifyChunk.OK) {
|
||||
return ctx.defaultPosition;
|
||||
}
|
||||
|
||||
ctx.beforeOutRange = false;
|
||||
ctx.afterOutRange = false;
|
||||
|
||||
// moreDelta >= 0: just for overflow guard, not a normal condition
|
||||
//noinspection OverflowingLoopIndex
|
||||
for (int moreDelta = 0; moreDelta >= 0; moreDelta++) {
|
||||
int pos = findPositionWithFuzzAndMoreDelta(ctx, delta, fuzz, moreDelta);
|
||||
if (pos >= 0) {
|
||||
return pos;
|
||||
}
|
||||
if (ctx.beforeOutRange && ctx.afterOutRange) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
// negative for not found
|
||||
private int findPositionWithFuzzAndMoreDelta(PatchApplyingContext<T> ctx, AbstractDelta<T> delta, int fuzz, int moreDelta) throws PatchFailedException {
|
||||
// range check: can't apply before end of last patch
|
||||
if (!ctx.beforeOutRange) {
|
||||
int beginAt = ctx.defaultPosition - moreDelta + fuzz;
|
||||
// We can't apply patch before end of last patch.
|
||||
if (beginAt <= ctx.lastPatchEnd) {
|
||||
ctx.beforeOutRange = true;
|
||||
}
|
||||
}
|
||||
// range check: can't apply after end of result
|
||||
if (!ctx.afterOutRange) {
|
||||
int beginAt = ctx.defaultPosition + moreDelta + delta.getSource().size() - fuzz;
|
||||
// We can't apply patch before end of last patch.
|
||||
if (ctx.result.size() < beginAt) {
|
||||
ctx.afterOutRange = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ctx.beforeOutRange) {
|
||||
VerifyChunk before = delta.getSource().verifyChunk(ctx.result, fuzz, ctx.defaultPosition - moreDelta);
|
||||
if (before == VerifyChunk.OK) {
|
||||
return ctx.defaultPosition - moreDelta;
|
||||
}
|
||||
}
|
||||
if (!ctx.afterOutRange) {
|
||||
VerifyChunk after = delta.getSource().verifyChunk(ctx.result, fuzz, ctx.defaultPosition + moreDelta);
|
||||
if (after == VerifyChunk.OK) {
|
||||
return ctx.defaultPosition + moreDelta;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard Patch behaviour to throw an exception for pathching conflicts.
|
||||
*/
|
||||
public final ConflictOutput<T> CONFLICT_PRODUCES_EXCEPTION = (VerifyChunk verifyChunk, AbstractDelta<T> delta, List<T> result) -> {
|
||||
throw new PatchFailedException("could not apply patch due to " + verifyChunk.toString());
|
||||
};
|
||||
|
||||
/**
|
||||
* Git like merge conflict output.
|
||||
*/
|
||||
public static final ConflictOutput<String> CONFLICT_PRODUCES_MERGE_CONFLICT = (VerifyChunk verifyChunk, AbstractDelta<String> delta, List<String> result) -> {
|
||||
if (result.size() > delta.getSource().getPosition()) {
|
||||
List<String> orgData = new ArrayList<>();
|
||||
|
||||
for (int i = 0; i < delta.getSource().size(); i++) {
|
||||
orgData.add(result.get(delta.getSource().getPosition()));
|
||||
result.remove(delta.getSource().getPosition());
|
||||
}
|
||||
|
||||
orgData.add(0, "<<<<<< HEAD");
|
||||
orgData.add("======");
|
||||
orgData.addAll(delta.getSource().getLines());
|
||||
orgData.add(">>>>>>> PATCH");
|
||||
|
||||
result.addAll(delta.getSource().getPosition(), orgData);
|
||||
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
|
||||
}
|
||||
};
|
||||
|
||||
private ConflictOutput<T> conflictOutput = CONFLICT_PRODUCES_EXCEPTION;
|
||||
|
||||
/**
|
||||
* Alter normal conflict output behaviour to e.g. inclide some conflict
|
||||
* statements in the result, like git does it.
|
||||
*/
|
||||
public Patch withConflictOutput(ConflictOutput<T> conflictOutput) {
|
||||
this.conflictOutput = conflictOutput;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new list, containing the restored state of the given list.
|
||||
* Opposite to {@link #applyTo(List)} method.
|
||||
*
|
||||
* @param target The list to copy and apply changes to.
|
||||
* @return A new list, containing the restored state.
|
||||
*/
|
||||
public List<T> restore(List<T> target) {
|
||||
List<T> result = new ArrayList<>(target);
|
||||
restoreToExisting(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Restores all changes within the given list.
|
||||
* Opposite to {@link #applyToExisting(List)} method.
|
||||
*
|
||||
* @param target The list to restore changes in. This list has to be modifiable,
|
||||
* otherwise exceptions may be thrown, depending on the used type of list.
|
||||
* @throws RuntimeException (or similar) if the list is not modifiable.
|
||||
*/
|
||||
public void restoreToExisting(List<T> target) {
|
||||
ListIterator<AbstractDelta<T>> it = getDeltas().listIterator(deltas.size());
|
||||
while (it.hasPrevious()) {
|
||||
AbstractDelta<T> delta = it.previous();
|
||||
delta.restore(target);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the given delta to this patch
|
||||
*
|
||||
* @param delta the given delta
|
||||
*/
|
||||
public void addDelta(AbstractDelta<T> delta) {
|
||||
deltas.add(delta);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of computed deltas
|
||||
*
|
||||
* @return the deltas
|
||||
*/
|
||||
public List<AbstractDelta<T>> getDeltas() {
|
||||
deltas.sort(comparing(d -> d.getSource().getPosition()));
|
||||
return deltas;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Patch{" + "deltas=" + deltas + '}';
|
||||
}
|
||||
|
||||
public static <T> Patch<T> generate(List<T> original, List<T> revised, List<Change> changes) {
|
||||
return generate(original, revised, changes, false);
|
||||
}
|
||||
|
||||
private static <T> Chunk<T> buildChunk(int start, int end, List<T> data) {
|
||||
return new Chunk<>(start, new ArrayList<>(data.subList(start, end)));
|
||||
}
|
||||
|
||||
public static <T> Patch<T> generate(List<T> original, List<T> revised, List<Change> _changes, boolean includeEquals) {
|
||||
Patch<T> patch = new Patch<>(_changes.size());
|
||||
int startOriginal = 0;
|
||||
int startRevised = 0;
|
||||
|
||||
List<Change> changes = _changes;
|
||||
|
||||
if (includeEquals) {
|
||||
changes = new ArrayList<Change>(_changes);
|
||||
Collections.sort(changes, comparing(d -> d.startOriginal));
|
||||
}
|
||||
|
||||
for (Change change : changes) {
|
||||
|
||||
if (includeEquals && startOriginal < change.startOriginal) {
|
||||
patch.addDelta(new EqualDelta<T>(
|
||||
buildChunk(startOriginal, change.startOriginal, original),
|
||||
buildChunk(startRevised, change.startRevised, revised)));
|
||||
}
|
||||
|
||||
Chunk<T> orgChunk = buildChunk(change.startOriginal, change.endOriginal, original);
|
||||
Chunk<T> revChunk = buildChunk(change.startRevised, change.endRevised, revised);
|
||||
switch (change.deltaType) {
|
||||
case DELETE:
|
||||
patch.addDelta(new DeleteDelta<>(orgChunk, revChunk));
|
||||
break;
|
||||
case INSERT:
|
||||
patch.addDelta(new InsertDelta<>(orgChunk, revChunk));
|
||||
break;
|
||||
case CHANGE:
|
||||
patch.addDelta(new ChangeDelta<>(orgChunk, revChunk));
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
||||
startOriginal = change.endOriginal;
|
||||
startRevised = change.endRevised;
|
||||
}
|
||||
|
||||
if (includeEquals && startOriginal < original.size()) {
|
||||
patch.addDelta(new EqualDelta<T>(
|
||||
buildChunk(startOriginal, original.size(), original),
|
||||
buildChunk(startRevised, revised.size(), revised)));
|
||||
}
|
||||
|
||||
return patch;
|
||||
}
|
||||
}
|
||||
33
src/com/github/difflib/patch/PatchFailedException.java
Normal file
33
src/com/github/difflib/patch/PatchFailedException.java
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2009-2017 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.patch;
|
||||
|
||||
/**
|
||||
* Thrown whenever a delta cannot be applied as a patch to a given text.
|
||||
*
|
||||
* @author <a href="mailto:juanco@suigeneris.org">Juanco Anez</a>
|
||||
*/
|
||||
public class PatchFailedException extends DiffException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public PatchFailedException() {
|
||||
}
|
||||
|
||||
public PatchFailedException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
26
src/com/github/difflib/patch/VerifyChunk.java
Normal file
26
src/com/github/difflib/patch/VerifyChunk.java
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright 2021 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.patch;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author tw
|
||||
*/
|
||||
public enum VerifyChunk {
|
||||
OK,
|
||||
POSITION_OUT_OF_TARGET,
|
||||
CONTENT_DOES_NOT_MATCH_TARGET
|
||||
}
|
||||
115
src/com/github/difflib/text/DiffRow.java
Normal file
115
src/com/github/difflib/text/DiffRow.java
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
* Copyright 2009-2017 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.text;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Describes the diff row in form [tag, oldLine, newLine) for showing the difference between two texts
|
||||
*
|
||||
* @author <a href="dm.naumenko@gmail.com">Dmitry Naumenko</a>
|
||||
*/
|
||||
public final class DiffRow implements Serializable {
|
||||
|
||||
private Tag tag;
|
||||
private final String oldLine;
|
||||
private final String newLine;
|
||||
|
||||
public DiffRow(Tag tag, String oldLine, String newLine) {
|
||||
this.tag = tag;
|
||||
this.oldLine = oldLine;
|
||||
this.newLine = newLine;
|
||||
}
|
||||
|
||||
public enum Tag {
|
||||
INSERT, DELETE, CHANGE, EQUAL
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the tag
|
||||
*/
|
||||
public Tag getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tag the tag to set
|
||||
*/
|
||||
public void setTag(Tag tag) {
|
||||
this.tag = tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the oldLine
|
||||
*/
|
||||
public String getOldLine() {
|
||||
return oldLine;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the newLine
|
||||
*/
|
||||
public String getNewLine() {
|
||||
return newLine;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(newLine, oldLine, tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
DiffRow other = (DiffRow) obj;
|
||||
if (newLine == null) {
|
||||
if (other.newLine != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!newLine.equals(other.newLine)) {
|
||||
return false;
|
||||
}
|
||||
if (oldLine == null) {
|
||||
if (other.oldLine != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!oldLine.equals(other.oldLine)) {
|
||||
return false;
|
||||
}
|
||||
if (tag == null) {
|
||||
if (other.tag != null) {
|
||||
return false;
|
||||
}
|
||||
} else if (!tag.equals(other.tag)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[" + this.tag + "," + this.oldLine + "," + this.newLine + "]";
|
||||
}
|
||||
}
|
||||
706
src/com/github/difflib/text/DiffRowGenerator.java
Normal file
706
src/com/github/difflib/text/DiffRowGenerator.java
Normal file
@@ -0,0 +1,706 @@
|
||||
/*
|
||||
* Copyright 2009-2017 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.text;
|
||||
|
||||
import com.github.difflib.DiffUtils;
|
||||
import com.github.difflib.patch.AbstractDelta;
|
||||
import com.github.difflib.patch.ChangeDelta;
|
||||
import com.github.difflib.patch.Chunk;
|
||||
import com.github.difflib.patch.DeleteDelta;
|
||||
import com.github.difflib.patch.DeltaType;
|
||||
import com.github.difflib.patch.InsertDelta;
|
||||
import com.github.difflib.patch.Patch;
|
||||
import com.github.difflib.text.DiffRow.Tag;
|
||||
import com.github.difflib.text.deltamerge.DeltaMergeUtils;
|
||||
import com.github.difflib.text.deltamerge.InlineDeltaMergeInfo;
|
||||
import java.util.*;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Function;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
/**
|
||||
* This class for generating DiffRows for side-by-sidy view. You can customize
|
||||
* the way of generating. For example, show inline diffs on not, ignoring white
|
||||
* spaces or/and blank lines and so on. All parameters for generating are
|
||||
* optional. If you do not specify them, the class will use the default values.
|
||||
*
|
||||
* These values are: showInlineDiffs = false; ignoreWhiteSpaces = true;
|
||||
* ignoreBlankLines = true; ...
|
||||
*
|
||||
* For instantiating the DiffRowGenerator you should use the its builder. Like
|
||||
* in example <code>
|
||||
* DiffRowGenerator generator = new DiffRowGenerator.Builder().showInlineDiffs(true).
|
||||
* ignoreWhiteSpaces(true).columnWidth(100).build();
|
||||
* </code>
|
||||
*/
|
||||
public final class DiffRowGenerator {
|
||||
|
||||
public static final BiPredicate<String, String> DEFAULT_EQUALIZER = Object::equals;
|
||||
|
||||
public static final BiPredicate<String, String> IGNORE_WHITESPACE_EQUALIZER = (original, revised)
|
||||
-> adjustWhitespace(original).equals(adjustWhitespace(revised));
|
||||
|
||||
public static final Function<String, String> LINE_NORMALIZER_FOR_HTML = StringUtils::normalize;
|
||||
|
||||
/**
|
||||
* Splitting lines by character to achieve char by char diff checking.
|
||||
*/
|
||||
public static final Function<String, List<String>> SPLITTER_BY_CHARACTER = line -> {
|
||||
List<String> list = new ArrayList<>(line.length());
|
||||
for (Character character : line.toCharArray()) {
|
||||
list.add(character.toString());
|
||||
}
|
||||
return list;
|
||||
};
|
||||
|
||||
public static final Pattern SPLIT_BY_WORD_PATTERN = Pattern.compile("\\s+|[,.\\[\\](){}/\\\\*+\\-#<>;:&\\']+");
|
||||
|
||||
/**
|
||||
* Splitting lines by word to achieve word by word diff checking.
|
||||
*/
|
||||
public static final Function<String, List<String>> SPLITTER_BY_WORD = line -> splitStringPreserveDelimiter(line, SPLIT_BY_WORD_PATTERN);
|
||||
public static final Pattern WHITESPACE_PATTERN = Pattern.compile("\\s+");
|
||||
|
||||
public static final Function<InlineDeltaMergeInfo, List<AbstractDelta<String>>> DEFAULT_INLINE_DELTA_MERGER = InlineDeltaMergeInfo::getDeltas;
|
||||
|
||||
/**
|
||||
* Merge diffs which are separated by equalities consisting of whitespace only.
|
||||
*/
|
||||
public static final Function<InlineDeltaMergeInfo, List<AbstractDelta<String>>> WHITESPACE_EQUALITIES_MERGER = deltaMergeInfo -> DeltaMergeUtils
|
||||
.mergeInlineDeltas(deltaMergeInfo, equalities -> equalities.stream().allMatch(s -> s==null || s.replaceAll("\\s+", "").equals("")));
|
||||
|
||||
public static Builder create() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
private static String adjustWhitespace(String raw) {
|
||||
return WHITESPACE_PATTERN.matcher(raw.trim()).replaceAll(" ");
|
||||
}
|
||||
|
||||
protected final static List<String> splitStringPreserveDelimiter(String str, Pattern SPLIT_PATTERN) {
|
||||
List<String> list = new ArrayList<>();
|
||||
if (str != null) {
|
||||
Matcher matcher = SPLIT_PATTERN.matcher(str);
|
||||
int pos = 0;
|
||||
while (matcher.find()) {
|
||||
if (pos < matcher.start()) {
|
||||
list.add(str.substring(pos, matcher.start()));
|
||||
}
|
||||
list.add(matcher.group());
|
||||
pos = matcher.end();
|
||||
}
|
||||
if (pos < str.length()) {
|
||||
list.add(str.substring(pos));
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the elements in the sequence with the given tag
|
||||
*
|
||||
* @param startPosition the position from which tag should start. The
|
||||
* counting start from a zero.
|
||||
* @param endPosition the position before which tag should should be closed.
|
||||
* @param tagGenerator the tag generator
|
||||
*/
|
||||
static void wrapInTag(List<String> sequence, int startPosition,
|
||||
int endPosition, Tag tag, BiFunction<Tag, Boolean, String> tagGenerator,
|
||||
Function<String, String> processDiffs, boolean replaceLinefeedWithSpace) {
|
||||
int endPos = endPosition;
|
||||
|
||||
while (endPos >= startPosition) {
|
||||
|
||||
//search position for end tag
|
||||
while (endPos > startPosition) {
|
||||
if (!"\n".equals(sequence.get(endPos - 1))) {
|
||||
break;
|
||||
} else if (replaceLinefeedWithSpace) {
|
||||
sequence.set(endPos - 1, " ");
|
||||
break;
|
||||
}
|
||||
endPos--;
|
||||
}
|
||||
|
||||
if (endPos == startPosition) {
|
||||
break;
|
||||
}
|
||||
|
||||
sequence.add(endPos, tagGenerator.apply(tag, false));
|
||||
if (processDiffs != null) {
|
||||
sequence.set(endPos - 1,
|
||||
processDiffs.apply(sequence.get(endPos - 1)));
|
||||
}
|
||||
endPos--;
|
||||
|
||||
//search position for end tag
|
||||
while (endPos > startPosition) {
|
||||
if ("\n".equals(sequence.get(endPos - 1))) {
|
||||
if (replaceLinefeedWithSpace) {
|
||||
sequence.set(endPos - 1, " ");
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (processDiffs != null) {
|
||||
sequence.set(endPos - 1,
|
||||
processDiffs.apply(sequence.get(endPos - 1)));
|
||||
}
|
||||
endPos--;
|
||||
}
|
||||
|
||||
sequence.add(endPos, tagGenerator.apply(tag, true));
|
||||
endPos--;
|
||||
}
|
||||
}
|
||||
|
||||
private final int columnWidth;
|
||||
private final BiPredicate<String, String> equalizer;
|
||||
private final boolean ignoreWhiteSpaces;
|
||||
private final Function<String, List<String>> inlineDiffSplitter;
|
||||
private final boolean mergeOriginalRevised;
|
||||
private final BiFunction<Tag, Boolean, String> newTag;
|
||||
private final BiFunction<Tag, Boolean, String> oldTag;
|
||||
private final boolean reportLinesUnchanged;
|
||||
private final Function<String, String> lineNormalizer;
|
||||
private final Function<String, String> processDiffs;
|
||||
private final Function<InlineDeltaMergeInfo, List<AbstractDelta<String>>> inlineDeltaMerger;
|
||||
|
||||
private final boolean showInlineDiffs;
|
||||
private final boolean replaceOriginalLinefeedInChangesWithSpaces;
|
||||
private final boolean decompressDeltas;
|
||||
|
||||
private DiffRowGenerator(Builder builder) {
|
||||
showInlineDiffs = builder.showInlineDiffs;
|
||||
ignoreWhiteSpaces = builder.ignoreWhiteSpaces;
|
||||
oldTag = builder.oldTag;
|
||||
newTag = builder.newTag;
|
||||
columnWidth = builder.columnWidth;
|
||||
mergeOriginalRevised = builder.mergeOriginalRevised;
|
||||
inlineDiffSplitter = builder.inlineDiffSplitter;
|
||||
decompressDeltas = builder.decompressDeltas;
|
||||
|
||||
if (builder.equalizer != null) {
|
||||
equalizer = builder.equalizer;
|
||||
} else {
|
||||
equalizer = ignoreWhiteSpaces ? IGNORE_WHITESPACE_EQUALIZER : DEFAULT_EQUALIZER;
|
||||
}
|
||||
|
||||
reportLinesUnchanged = builder.reportLinesUnchanged;
|
||||
lineNormalizer = builder.lineNormalizer;
|
||||
processDiffs = builder.processDiffs;
|
||||
inlineDeltaMerger = builder.inlineDeltaMerger;
|
||||
|
||||
replaceOriginalLinefeedInChangesWithSpaces = builder.replaceOriginalLinefeedInChangesWithSpaces;
|
||||
|
||||
Objects.requireNonNull(inlineDiffSplitter);
|
||||
Objects.requireNonNull(lineNormalizer);
|
||||
Objects.requireNonNull(inlineDeltaMerger);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the DiffRows describing the difference between original and revised
|
||||
* texts using the given patch. Useful for displaying side-by-side diff.
|
||||
*
|
||||
* @param original the original text
|
||||
* @param revised the revised text
|
||||
* @return the DiffRows between original and revised texts
|
||||
*/
|
||||
public List<DiffRow> generateDiffRows(List<String> original, List<String> revised) {
|
||||
return generateDiffRows(original, DiffUtils.diff(original, revised, equalizer));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the DiffRows describing the difference between original and
|
||||
* revised texts using the given patch. Useful for displaying side-by-side
|
||||
* diff.
|
||||
*
|
||||
* @param original the original text
|
||||
* @param patch the given patch
|
||||
* @return the DiffRows between original and revised texts
|
||||
*/
|
||||
public List<DiffRow> generateDiffRows(final List<String> original, Patch<String> patch) {
|
||||
List<DiffRow> diffRows = new ArrayList<>();
|
||||
int endPos = 0;
|
||||
final List<AbstractDelta<String>> deltaList = patch.getDeltas();
|
||||
|
||||
if (decompressDeltas) {
|
||||
for (AbstractDelta<String> originalDelta : deltaList) {
|
||||
for (AbstractDelta<String> delta : decompressDeltas(originalDelta)) {
|
||||
endPos = transformDeltaIntoDiffRow(original, endPos, diffRows, delta);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (AbstractDelta<String> delta : deltaList) {
|
||||
endPos = transformDeltaIntoDiffRow(original, endPos, diffRows, delta);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the final matching chunk if any.
|
||||
for (String line : original.subList(endPos, original.size())) {
|
||||
diffRows.add(buildDiffRow(Tag.EQUAL, line, line));
|
||||
}
|
||||
return diffRows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms one patch delta into a DiffRow object.
|
||||
*/
|
||||
private int transformDeltaIntoDiffRow(final List<String> original, int endPos, List<DiffRow> diffRows, AbstractDelta<String> delta) {
|
||||
Chunk<String> orig = delta.getSource();
|
||||
Chunk<String> rev = delta.getTarget();
|
||||
|
||||
for (String line : original.subList(endPos, orig.getPosition())) {
|
||||
diffRows.add(buildDiffRow(Tag.EQUAL, line, line));
|
||||
}
|
||||
|
||||
switch (delta.getType()) {
|
||||
case INSERT:
|
||||
for (String line : rev.getLines()) {
|
||||
diffRows.add(buildDiffRow(Tag.INSERT, "", line));
|
||||
}
|
||||
break;
|
||||
case DELETE:
|
||||
for (String line : orig.getLines()) {
|
||||
diffRows.add(buildDiffRow(Tag.DELETE, line, ""));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (showInlineDiffs) {
|
||||
diffRows.addAll(generateInlineDiffs(delta));
|
||||
} else {
|
||||
for (int j = 0; j < Math.max(orig.size(), rev.size()); j++) {
|
||||
diffRows.add(buildDiffRow(Tag.CHANGE,
|
||||
orig.getLines().size() > j ? orig.getLines().get(j) : "",
|
||||
rev.getLines().size() > j ? rev.getLines().get(j) : ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return orig.last() + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompresses ChangeDeltas with different source and target size to a
|
||||
* ChangeDelta with same size and a following InsertDelta or DeleteDelta.
|
||||
* With this problems of building DiffRows getting smaller.
|
||||
*
|
||||
* @param deltaList
|
||||
*/
|
||||
private List<AbstractDelta<String>> decompressDeltas(AbstractDelta<String> delta) {
|
||||
if (delta.getType() == DeltaType.CHANGE && delta.getSource().size() != delta.getTarget().size()) {
|
||||
List<AbstractDelta<String>> deltas = new ArrayList<>();
|
||||
//System.out.println("decompress this " + delta);
|
||||
|
||||
int minSize = Math.min(delta.getSource().size(), delta.getTarget().size());
|
||||
Chunk<String> orig = delta.getSource();
|
||||
Chunk<String> rev = delta.getTarget();
|
||||
|
||||
deltas.add(new ChangeDelta<String>(
|
||||
new Chunk<>(orig.getPosition(), orig.getLines().subList(0, minSize)),
|
||||
new Chunk<>(rev.getPosition(), rev.getLines().subList(0, minSize))));
|
||||
|
||||
if (orig.getLines().size() < rev.getLines().size()) {
|
||||
deltas.add(new InsertDelta<String>(
|
||||
new Chunk<>(orig.getPosition() + minSize, Collections.emptyList()),
|
||||
new Chunk<>(rev.getPosition() + minSize, rev.getLines().subList(minSize, rev.getLines().size()))));
|
||||
} else {
|
||||
deltas.add(new DeleteDelta<String>(
|
||||
new Chunk<>(orig.getPosition() + minSize, orig.getLines().subList(minSize, orig.getLines().size())),
|
||||
new Chunk<>(rev.getPosition() + minSize, Collections.emptyList())));
|
||||
}
|
||||
return deltas;
|
||||
}
|
||||
|
||||
return Collections.singletonList(delta);
|
||||
}
|
||||
|
||||
private DiffRow buildDiffRow(Tag type, String orgline, String newline) {
|
||||
if (reportLinesUnchanged) {
|
||||
return new DiffRow(type, orgline, newline);
|
||||
} else {
|
||||
String wrapOrg = preprocessLine(orgline);
|
||||
if (Tag.DELETE == type) {
|
||||
if (mergeOriginalRevised || showInlineDiffs) {
|
||||
wrapOrg = oldTag.apply(type, true) + wrapOrg + oldTag.apply(type, false);
|
||||
}
|
||||
}
|
||||
String wrapNew = preprocessLine(newline);
|
||||
if (Tag.INSERT == type) {
|
||||
if (mergeOriginalRevised) {
|
||||
wrapOrg = newTag.apply(type, true) + wrapNew + newTag.apply(type, false);
|
||||
} else if (showInlineDiffs) {
|
||||
wrapNew = newTag.apply(type, true) + wrapNew + newTag.apply(type, false);
|
||||
}
|
||||
}
|
||||
return new DiffRow(type, wrapOrg, wrapNew);
|
||||
}
|
||||
}
|
||||
|
||||
private DiffRow buildDiffRowWithoutNormalizing(Tag type, String orgline, String newline) {
|
||||
return new DiffRow(type,
|
||||
StringUtils.wrapText(orgline, columnWidth),
|
||||
StringUtils.wrapText(newline, columnWidth));
|
||||
}
|
||||
|
||||
List<String> normalizeLines(List<String> list) {
|
||||
return reportLinesUnchanged
|
||||
? list
|
||||
: list.stream()
|
||||
.map(lineNormalizer::apply)
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the inline diffs for given delta
|
||||
*
|
||||
* @param delta the given delta
|
||||
*/
|
||||
private List<DiffRow> generateInlineDiffs(AbstractDelta<String> delta) {
|
||||
List<String> orig = normalizeLines(delta.getSource().getLines());
|
||||
List<String> rev = normalizeLines(delta.getTarget().getLines());
|
||||
List<String> origList;
|
||||
List<String> revList;
|
||||
String joinedOrig = String.join("\n", orig);
|
||||
String joinedRev = String.join("\n", rev);
|
||||
|
||||
origList = inlineDiffSplitter.apply(joinedOrig);
|
||||
revList = inlineDiffSplitter.apply(joinedRev);
|
||||
|
||||
List<AbstractDelta<String>> originalInlineDeltas = DiffUtils.diff(origList, revList, equalizer)
|
||||
.getDeltas();
|
||||
List<AbstractDelta<String>> inlineDeltas = inlineDeltaMerger
|
||||
.apply(new InlineDeltaMergeInfo(originalInlineDeltas, origList, revList));
|
||||
|
||||
Collections.reverse(inlineDeltas);
|
||||
for (AbstractDelta<String> inlineDelta : inlineDeltas) {
|
||||
Chunk<String> inlineOrig = inlineDelta.getSource();
|
||||
Chunk<String> inlineRev = inlineDelta.getTarget();
|
||||
if (inlineDelta.getType() == DeltaType.DELETE) {
|
||||
wrapInTag(origList, inlineOrig.getPosition(), inlineOrig
|
||||
.getPosition()
|
||||
+ inlineOrig.size(), Tag.DELETE, oldTag, processDiffs, replaceOriginalLinefeedInChangesWithSpaces && mergeOriginalRevised);
|
||||
} else if (inlineDelta.getType() == DeltaType.INSERT) {
|
||||
if (mergeOriginalRevised) {
|
||||
origList.addAll(inlineOrig.getPosition(),
|
||||
revList.subList(inlineRev.getPosition(),
|
||||
inlineRev.getPosition() + inlineRev.size()));
|
||||
wrapInTag(origList, inlineOrig.getPosition(),
|
||||
inlineOrig.getPosition() + inlineRev.size(),
|
||||
Tag.INSERT, newTag, processDiffs, false);
|
||||
} else {
|
||||
wrapInTag(revList, inlineRev.getPosition(),
|
||||
inlineRev.getPosition() + inlineRev.size(),
|
||||
Tag.INSERT, newTag, processDiffs, false);
|
||||
}
|
||||
} else if (inlineDelta.getType() == DeltaType.CHANGE) {
|
||||
if (mergeOriginalRevised) {
|
||||
origList.addAll(inlineOrig.getPosition() + inlineOrig.size(),
|
||||
revList.subList(inlineRev.getPosition(),
|
||||
inlineRev.getPosition() + inlineRev.size()));
|
||||
wrapInTag(origList, inlineOrig.getPosition() + inlineOrig.size(),
|
||||
inlineOrig.getPosition() + inlineOrig.size() + inlineRev.size(),
|
||||
Tag.CHANGE, newTag, processDiffs, false);
|
||||
} else {
|
||||
wrapInTag(revList, inlineRev.getPosition(),
|
||||
inlineRev.getPosition() + inlineRev.size(),
|
||||
Tag.CHANGE, newTag, processDiffs, false);
|
||||
}
|
||||
wrapInTag(origList, inlineOrig.getPosition(),
|
||||
inlineOrig.getPosition() + inlineOrig.size(),
|
||||
Tag.CHANGE, oldTag, processDiffs, replaceOriginalLinefeedInChangesWithSpaces && mergeOriginalRevised);
|
||||
}
|
||||
}
|
||||
StringBuilder origResult = new StringBuilder();
|
||||
StringBuilder revResult = new StringBuilder();
|
||||
for (String character : origList) {
|
||||
origResult.append(character);
|
||||
}
|
||||
for (String character : revList) {
|
||||
revResult.append(character);
|
||||
}
|
||||
|
||||
List<String> original = Arrays.asList(origResult.toString().split("\n"));
|
||||
List<String> revised = Arrays.asList(revResult.toString().split("\n"));
|
||||
List<DiffRow> diffRows = new ArrayList<>();
|
||||
for (int j = 0; j < Math.max(original.size(), revised.size()); j++) {
|
||||
diffRows.
|
||||
add(buildDiffRowWithoutNormalizing(Tag.CHANGE,
|
||||
original.size() > j ? original.get(j) : "",
|
||||
revised.size() > j ? revised.get(j) : ""));
|
||||
}
|
||||
return diffRows;
|
||||
}
|
||||
|
||||
private String preprocessLine(String line) {
|
||||
if (columnWidth == 0) {
|
||||
return lineNormalizer.apply(line);
|
||||
} else {
|
||||
return StringUtils.wrapText(lineNormalizer.apply(line), columnWidth);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class used for building the DiffRowGenerator.
|
||||
*
|
||||
* @author dmitry
|
||||
*
|
||||
*/
|
||||
public static class Builder {
|
||||
|
||||
private boolean showInlineDiffs = false;
|
||||
private boolean ignoreWhiteSpaces = false;
|
||||
private boolean decompressDeltas = true;
|
||||
|
||||
private BiFunction<Tag, Boolean, String> oldTag
|
||||
= (tag, f) -> f ? "<span class=\"editOldInline\">" : "</span>";
|
||||
private BiFunction<Tag, Boolean, String> newTag
|
||||
= (tag, f) -> f ? "<span class=\"editNewInline\">" : "</span>";
|
||||
|
||||
private int columnWidth = 0;
|
||||
private boolean mergeOriginalRevised = false;
|
||||
private boolean reportLinesUnchanged = false;
|
||||
private Function<String, List<String>> inlineDiffSplitter = SPLITTER_BY_CHARACTER;
|
||||
private Function<String, String> lineNormalizer = LINE_NORMALIZER_FOR_HTML;
|
||||
private Function<String, String> processDiffs = null;
|
||||
private BiPredicate<String, String> equalizer = null;
|
||||
private boolean replaceOriginalLinefeedInChangesWithSpaces = false;
|
||||
private Function<InlineDeltaMergeInfo, List<AbstractDelta<String>>> inlineDeltaMerger = DEFAULT_INLINE_DELTA_MERGER;
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Show inline diffs in generating diff rows or not.
|
||||
*
|
||||
* @param val the value to set. Default: false.
|
||||
* @return builder with configured showInlineDiff parameter
|
||||
*/
|
||||
public Builder showInlineDiffs(boolean val) {
|
||||
showInlineDiffs = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignore white spaces in generating diff rows or not.
|
||||
*
|
||||
* @param val the value to set. Default: true.
|
||||
* @return builder with configured ignoreWhiteSpaces parameter
|
||||
*/
|
||||
public Builder ignoreWhiteSpaces(boolean val) {
|
||||
ignoreWhiteSpaces = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Report all lines without markup on the old or new text.
|
||||
*
|
||||
* @param val the value to set. Default: false.
|
||||
* @return builder with configured reportLinesUnchanged parameter
|
||||
*/
|
||||
public Builder reportLinesUnchanged(final boolean val) {
|
||||
reportLinesUnchanged = val;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generator for Old-Text-Tags.
|
||||
*
|
||||
* @param generator the tag generator
|
||||
* @return builder with configured ignoreBlankLines parameter
|
||||
*/
|
||||
public Builder oldTag(BiFunction<Tag, Boolean, String> generator) {
|
||||
this.oldTag = generator;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generator for Old-Text-Tags.
|
||||
*
|
||||
* @param generator the tag generator
|
||||
* @return builder with configured ignoreBlankLines parameter
|
||||
*/
|
||||
public Builder oldTag(Function<Boolean, String> generator) {
|
||||
this.oldTag = (tag, f) -> generator.apply(f);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generator for New-Text-Tags.
|
||||
*
|
||||
* @param generator
|
||||
* @return
|
||||
*/
|
||||
public Builder newTag(BiFunction<Tag, Boolean, String> generator) {
|
||||
this.newTag = generator;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generator for New-Text-Tags.
|
||||
*
|
||||
* @param generator
|
||||
* @return
|
||||
*/
|
||||
public Builder newTag(Function<Boolean, String> generator) {
|
||||
this.newTag = (tag, f) -> generator.apply(f);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processor for diffed text parts. Here e.g. whitecharacters could be
|
||||
* replaced by something visible.
|
||||
*
|
||||
* @param processDiffs
|
||||
* @return
|
||||
*/
|
||||
public Builder processDiffs(Function<String, String> processDiffs) {
|
||||
this.processDiffs = processDiffs;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the column width of generated lines of original and revised
|
||||
* texts.
|
||||
*
|
||||
* @param width the width to set. Making it < 0 doesn't make any
|
||||
* sense. Default 80.
|
||||
* @return builder with config of column width
|
||||
*/
|
||||
public Builder columnWidth(int width) {
|
||||
if (width >= 0) {
|
||||
columnWidth = width;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the DiffRowGenerator. If some parameters is not set, the
|
||||
* default values are used.
|
||||
*
|
||||
* @return the customized DiffRowGenerator
|
||||
*/
|
||||
public DiffRowGenerator build() {
|
||||
return new DiffRowGenerator(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge the complete result within the original text. This makes sense
|
||||
* for one line display.
|
||||
*
|
||||
* @param mergeOriginalRevised
|
||||
* @return
|
||||
*/
|
||||
public Builder mergeOriginalRevised(boolean mergeOriginalRevised) {
|
||||
this.mergeOriginalRevised = mergeOriginalRevised;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deltas could be in a state, that would produce some unreasonable
|
||||
* results within an inline diff. So the deltas are decompressed into
|
||||
* smaller parts and rebuild. But this could result in more differences.
|
||||
*
|
||||
* @param decompressDeltas
|
||||
* @return
|
||||
*/
|
||||
public Builder decompressDeltas(boolean decompressDeltas) {
|
||||
this.decompressDeltas = decompressDeltas;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Per default each character is separatly processed. This variant
|
||||
* introduces processing by word, which does not deliver in word
|
||||
* changes. Therefore the whole word will be tagged as changed:
|
||||
*
|
||||
* <pre>
|
||||
* false: (aBa : aba) -- changed: a(B)a : a(b)a
|
||||
* true: (aBa : aba) -- changed: (aBa) : (aba)
|
||||
* </pre>
|
||||
*/
|
||||
public Builder inlineDiffByWord(boolean inlineDiffByWord) {
|
||||
inlineDiffSplitter = inlineDiffByWord ? SPLITTER_BY_WORD : SPLITTER_BY_CHARACTER;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* To provide some customized splitting a splitter can be provided. Here
|
||||
* someone could think about sentence splitter, comma splitter or stuff
|
||||
* like that.
|
||||
*
|
||||
* @param inlineDiffSplitter
|
||||
* @return
|
||||
*/
|
||||
public Builder inlineDiffBySplitter(Function<String, List<String>> inlineDiffSplitter) {
|
||||
this.inlineDiffSplitter = inlineDiffSplitter;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* By default DiffRowGenerator preprocesses lines for HTML output. Tabs
|
||||
* and special HTML characters like "<" are replaced with its encoded
|
||||
* value. To change this you can provide a customized line normalizer
|
||||
* here.
|
||||
*
|
||||
* @param lineNormalizer
|
||||
* @return
|
||||
*/
|
||||
public Builder lineNormalizer(Function<String, String> lineNormalizer) {
|
||||
this.lineNormalizer = lineNormalizer;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide an equalizer for diff processing.
|
||||
*
|
||||
* @param equalizer equalizer for diff processing.
|
||||
* @return builder with configured equalizer parameter
|
||||
*/
|
||||
public Builder equalizer(BiPredicate<String, String> equalizer) {
|
||||
this.equalizer = equalizer;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sometimes it happens that a change contains multiple lines. If there
|
||||
* is no correspondence in old and new. To keep the merged line more
|
||||
* readable the linefeeds could be replaced by spaces.
|
||||
*
|
||||
* @param replace
|
||||
* @return
|
||||
*/
|
||||
public Builder replaceOriginalLinefeedInChangesWithSpaces(boolean replace) {
|
||||
this.replaceOriginalLinefeedInChangesWithSpaces = replace;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide an inline delta merger for use case specific delta optimizations.
|
||||
*
|
||||
* @param inlineDeltaMerger
|
||||
* @return
|
||||
*/
|
||||
public Builder inlineDeltaMerger(
|
||||
Function<InlineDeltaMergeInfo, List<AbstractDelta<String>>> inlineDeltaMerger) {
|
||||
this.inlineDeltaMerger = inlineDeltaMerger;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
83
src/com/github/difflib/text/StringUtils.java
Normal file
83
src/com/github/difflib/text/StringUtils.java
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2009-2017 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.text;
|
||||
|
||||
import java.util.List;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
final class StringUtils {
|
||||
|
||||
/**
|
||||
* Replaces all opening and closing tags with <code><</code> or <code>></code>.
|
||||
*
|
||||
* @param str
|
||||
* @return str with some HTML meta characters escaped.
|
||||
*/
|
||||
public static String htmlEntites(String str) {
|
||||
return str.replace("<", "<").replace(">", ">");
|
||||
}
|
||||
|
||||
public static String normalize(String str) {
|
||||
return htmlEntites(str).replace("\t", " ");
|
||||
}
|
||||
|
||||
public static List<String> wrapText(List<String> list, int columnWidth) {
|
||||
return list.stream()
|
||||
.map(line -> wrapText(line, columnWidth))
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the text with the given column width
|
||||
*
|
||||
* @param line the text
|
||||
* @param columnWidth the given column
|
||||
* @return the wrapped text
|
||||
*/
|
||||
public static String wrapText(String line, int columnWidth) {
|
||||
if (columnWidth < 0) {
|
||||
throw new IllegalArgumentException("columnWidth may not be less 0");
|
||||
}
|
||||
if (columnWidth == 0) {
|
||||
return line;
|
||||
}
|
||||
int length = line.length();
|
||||
int delimiter = "<br/>".length();
|
||||
int widthIndex = columnWidth;
|
||||
|
||||
StringBuilder b = new StringBuilder(line);
|
||||
|
||||
for (int count = 0; length > widthIndex; count++) {
|
||||
int breakPoint = widthIndex + delimiter * count;
|
||||
if (Character.isHighSurrogate(b.charAt(breakPoint - 1)) &&
|
||||
Character.isLowSurrogate(b.charAt(breakPoint))) {
|
||||
// Shift a breakpoint that would split a supplemental code-point.
|
||||
breakPoint += 1;
|
||||
if (breakPoint == b.length()) {
|
||||
// Break before instead of after if this is the last code-point.
|
||||
breakPoint -= 2;
|
||||
}
|
||||
}
|
||||
b.insert(breakPoint, "<br/>");
|
||||
widthIndex += columnWidth;
|
||||
}
|
||||
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
private StringUtils() {
|
||||
}
|
||||
}
|
||||
79
src/com/github/difflib/text/deltamerge/DeltaMergeUtils.java
Normal file
79
src/com/github/difflib/text/deltamerge/DeltaMergeUtils.java
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2009-2024 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.text.deltamerge;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import com.github.difflib.patch.AbstractDelta;
|
||||
import com.github.difflib.patch.ChangeDelta;
|
||||
import com.github.difflib.patch.Chunk;
|
||||
|
||||
/**
|
||||
* Provides utility features for merge inline deltas
|
||||
*
|
||||
* @author <a href="christian.meier@epictec.ch">Christian Meier</a>
|
||||
*/
|
||||
final public class DeltaMergeUtils {
|
||||
|
||||
public static List<AbstractDelta<String>> mergeInlineDeltas(InlineDeltaMergeInfo deltaMergeInfo,
|
||||
Predicate<List<String>> replaceEquality) {
|
||||
final List<AbstractDelta<String>> originalDeltas = deltaMergeInfo.getDeltas();
|
||||
if (originalDeltas.size() < 2) {
|
||||
return originalDeltas;
|
||||
}
|
||||
|
||||
final List<AbstractDelta<String>> newDeltas = new ArrayList<>();
|
||||
newDeltas.add(originalDeltas.get(0));
|
||||
for (int i = 1; i < originalDeltas.size(); i++) {
|
||||
final AbstractDelta<String> previousDelta = newDeltas.get(newDeltas.size()-1);
|
||||
final AbstractDelta<String> currentDelta = originalDeltas.get(i);
|
||||
|
||||
final List<String> equalities = deltaMergeInfo.getOrigList().subList(
|
||||
previousDelta.getSource().getPosition() + previousDelta.getSource().size(),
|
||||
currentDelta.getSource().getPosition());
|
||||
|
||||
if (replaceEquality.test(equalities)) {
|
||||
// Merge the previous delta, the equality and the current delta into one
|
||||
// ChangeDelta and replace the previous delta by this new ChangeDelta.
|
||||
final List<String> allSourceLines = new ArrayList<>();
|
||||
allSourceLines.addAll(previousDelta.getSource().getLines());
|
||||
allSourceLines.addAll(equalities);
|
||||
allSourceLines.addAll(currentDelta.getSource().getLines());
|
||||
|
||||
final List<String> allTargetLines = new ArrayList<>();
|
||||
allTargetLines.addAll(previousDelta.getTarget().getLines());
|
||||
allTargetLines.addAll(equalities);
|
||||
allTargetLines.addAll(currentDelta.getTarget().getLines());
|
||||
|
||||
final ChangeDelta<String> replacement = new ChangeDelta<>(
|
||||
new Chunk<>(previousDelta.getSource().getPosition(), allSourceLines),
|
||||
new Chunk<>(previousDelta.getTarget().getPosition(), allTargetLines));
|
||||
|
||||
newDeltas.remove(newDeltas.size()-1);
|
||||
newDeltas.add(replacement);
|
||||
} else {
|
||||
newDeltas.add(currentDelta);
|
||||
}
|
||||
}
|
||||
|
||||
return newDeltas;
|
||||
}
|
||||
|
||||
private DeltaMergeUtils() {
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2009-2024 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.text.deltamerge;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import com.github.difflib.patch.AbstractDelta;
|
||||
|
||||
/**
|
||||
* Holds the information required to merge deltas originating from an inline
|
||||
* diff
|
||||
*
|
||||
* @author <a href="christian.meier@epictec.ch">Christian Meier</a>
|
||||
*/
|
||||
public final class InlineDeltaMergeInfo {
|
||||
|
||||
private final List<AbstractDelta<String>> deltas;
|
||||
private final List<String> origList;
|
||||
private final List<String> revList;
|
||||
|
||||
public InlineDeltaMergeInfo(List<AbstractDelta<String>> deltas, List<String> origList, List<String> revList) {
|
||||
this.deltas = deltas;
|
||||
this.origList = origList;
|
||||
this.revList = revList;
|
||||
}
|
||||
|
||||
public List<AbstractDelta<String>> getDeltas() {
|
||||
return deltas;
|
||||
}
|
||||
|
||||
public List<String> getOrigList() {
|
||||
return origList;
|
||||
}
|
||||
|
||||
public List<String> getRevList() {
|
||||
return revList;
|
||||
}
|
||||
}
|
||||
78
src/com/github/difflib/unifieddiff/UnifiedDiff.java
Normal file
78
src/com/github/difflib/unifieddiff/UnifiedDiff.java
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.PatchFailedException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Tobias Warneke (t.warneke@gmx.net)
|
||||
*/
|
||||
public final class UnifiedDiff {
|
||||
|
||||
private String header;
|
||||
private String tail;
|
||||
private final List<UnifiedDiffFile> files = new ArrayList<>();
|
||||
|
||||
public String getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
public void setHeader(String header) {
|
||||
this.header = header;
|
||||
}
|
||||
|
||||
void addFile(UnifiedDiffFile file) {
|
||||
files.add(file);
|
||||
}
|
||||
|
||||
public List<UnifiedDiffFile> getFiles() {
|
||||
return Collections.unmodifiableList(files);
|
||||
}
|
||||
|
||||
void setTailTxt(String tailTxt) {
|
||||
this.tail = tailTxt;
|
||||
}
|
||||
|
||||
public String getTail() {
|
||||
return tail;
|
||||
}
|
||||
|
||||
public List<String> applyPatchTo(Predicate<String> findFile, List<String> originalLines) throws PatchFailedException {
|
||||
UnifiedDiffFile file = files.stream()
|
||||
.filter(diff -> findFile.test(diff.getFromFile()))
|
||||
.findFirst().orElse(null);
|
||||
if (file != null) {
|
||||
return file.getPatch().applyTo(originalLines);
|
||||
} else {
|
||||
return originalLines;
|
||||
}
|
||||
}
|
||||
|
||||
public static UnifiedDiff from(String header, String tail, UnifiedDiffFile... files) {
|
||||
UnifiedDiff diff = new UnifiedDiff();
|
||||
diff.setHeader(header);
|
||||
diff.setTailTxt(tail);
|
||||
for (UnifiedDiffFile file : files) {
|
||||
diff.addFile(file);
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
}
|
||||
211
src/com/github/difflib/unifieddiff/UnifiedDiffFile.java
Normal file
211
src/com/github/difflib/unifieddiff/UnifiedDiffFile.java
Normal file
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* 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.Patch;
|
||||
|
||||
/**
|
||||
* Data structure for one patched file from a unified diff file.
|
||||
*
|
||||
* @author Tobias Warneke (t.warneke@gmx.net)
|
||||
*/
|
||||
public final class UnifiedDiffFile {
|
||||
|
||||
private String diffCommand;
|
||||
private String fromFile;
|
||||
private String fromTimestamp;
|
||||
private String toFile;
|
||||
private String renameFrom;
|
||||
private String renameTo;
|
||||
private String copyFrom;
|
||||
private String copyTo;
|
||||
private String toTimestamp;
|
||||
private String index;
|
||||
private String newFileMode;
|
||||
private String oldMode;
|
||||
private String newMode;
|
||||
private String deletedFileMode;
|
||||
private String binaryAdded;
|
||||
private String binaryDeleted;
|
||||
private String binaryEdited;
|
||||
private Patch<String> patch = new Patch<>();
|
||||
private boolean noNewLineAtTheEndOfTheFile = false;
|
||||
private Integer similarityIndex;
|
||||
|
||||
public String getDiffCommand() {
|
||||
return diffCommand;
|
||||
}
|
||||
|
||||
public void setDiffCommand(String diffCommand) {
|
||||
this.diffCommand = diffCommand;
|
||||
}
|
||||
|
||||
public String getFromFile() {
|
||||
return fromFile;
|
||||
}
|
||||
|
||||
public void setFromFile(String fromFile) {
|
||||
this.fromFile = fromFile;
|
||||
}
|
||||
|
||||
public String getToFile() {
|
||||
return toFile;
|
||||
}
|
||||
|
||||
public void setToFile(String toFile) {
|
||||
this.toFile = toFile;
|
||||
}
|
||||
|
||||
public void setIndex(String index) {
|
||||
this.index = index;
|
||||
}
|
||||
|
||||
public String getIndex() {
|
||||
return index;
|
||||
}
|
||||
|
||||
public Patch<String> getPatch() {
|
||||
return patch;
|
||||
}
|
||||
|
||||
public String getFromTimestamp() {
|
||||
return fromTimestamp;
|
||||
}
|
||||
|
||||
public void setFromTimestamp(String fromTimestamp) {
|
||||
this.fromTimestamp = fromTimestamp;
|
||||
}
|
||||
|
||||
public String getToTimestamp() {
|
||||
return toTimestamp;
|
||||
}
|
||||
|
||||
public void setToTimestamp(String toTimestamp) {
|
||||
this.toTimestamp = toTimestamp;
|
||||
}
|
||||
|
||||
public Integer getSimilarityIndex() {
|
||||
return similarityIndex;
|
||||
}
|
||||
|
||||
public void setSimilarityIndex(Integer similarityIndex) {
|
||||
this.similarityIndex = similarityIndex;
|
||||
}
|
||||
|
||||
public String getRenameFrom() {
|
||||
return renameFrom;
|
||||
}
|
||||
|
||||
public void setRenameFrom(String renameFrom) {
|
||||
this.renameFrom = renameFrom;
|
||||
}
|
||||
|
||||
public String getRenameTo() {
|
||||
return renameTo;
|
||||
}
|
||||
|
||||
public void setRenameTo(String renameTo) {
|
||||
this.renameTo = renameTo;
|
||||
}
|
||||
|
||||
public String getCopyFrom() {
|
||||
return copyFrom;
|
||||
}
|
||||
|
||||
public void setCopyFrom(String copyFrom) {
|
||||
this.copyFrom = copyFrom;
|
||||
}
|
||||
|
||||
public String getCopyTo() {
|
||||
return copyTo;
|
||||
}
|
||||
|
||||
public void setCopyTo(String copyTo) {
|
||||
this.copyTo = copyTo;
|
||||
}
|
||||
|
||||
public static UnifiedDiffFile from(String fromFile, String toFile, Patch<String> patch) {
|
||||
UnifiedDiffFile file = new UnifiedDiffFile();
|
||||
file.setFromFile(fromFile);
|
||||
file.setToFile(toFile);
|
||||
file.patch = patch;
|
||||
return file;
|
||||
}
|
||||
|
||||
public void setNewFileMode(String newFileMode) {
|
||||
this.newFileMode = newFileMode;
|
||||
}
|
||||
|
||||
public String getNewFileMode() {
|
||||
return newFileMode;
|
||||
}
|
||||
|
||||
public String getDeletedFileMode() {
|
||||
return deletedFileMode;
|
||||
}
|
||||
|
||||
public void setDeletedFileMode(String deletedFileMode) {
|
||||
this.deletedFileMode = deletedFileMode;
|
||||
}
|
||||
|
||||
public String getOldMode() {
|
||||
return oldMode;
|
||||
}
|
||||
|
||||
public void setOldMode(String oldMode) {
|
||||
this.oldMode = oldMode;
|
||||
}
|
||||
|
||||
public String getNewMode() {
|
||||
return newMode;
|
||||
}
|
||||
|
||||
public void setNewMode(String newMode) {
|
||||
this.newMode = newMode;
|
||||
}
|
||||
|
||||
public String getBinaryAdded() {
|
||||
return binaryAdded;
|
||||
}
|
||||
|
||||
public void setBinaryAdded(String binaryAdded) {
|
||||
this.binaryAdded = binaryAdded;
|
||||
}
|
||||
|
||||
public String getBinaryDeleted() {
|
||||
return binaryDeleted;
|
||||
}
|
||||
|
||||
public void setBinaryDeleted(String binaryDeleted) {
|
||||
this.binaryDeleted = binaryDeleted;
|
||||
}
|
||||
|
||||
public String getBinaryEdited() {
|
||||
return binaryEdited;
|
||||
}
|
||||
|
||||
public void setBinaryEdited(String binaryEdited) {
|
||||
this.binaryEdited = binaryEdited;
|
||||
}
|
||||
|
||||
public boolean isNoNewLineAtTheEndOfTheFile() {
|
||||
return noNewLineAtTheEndOfTheFile;
|
||||
}
|
||||
|
||||
public void setNoNewLineAtTheEndOfTheFile(boolean noNewLineAtTheEndOfTheFile) {
|
||||
this.noNewLineAtTheEndOfTheFile = noNewLineAtTheEndOfTheFile;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Tobias Warneke (t.warneke@gmx.net)
|
||||
*/
|
||||
public class UnifiedDiffParserException extends RuntimeException {
|
||||
|
||||
public UnifiedDiffParserException() {
|
||||
}
|
||||
|
||||
public UnifiedDiffParserException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public UnifiedDiffParserException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public UnifiedDiffParserException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public UnifiedDiffParserException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
|
||||
super(message, cause, enableSuppression, writableStackTrace);
|
||||
}
|
||||
|
||||
}
|
||||
475
src/com/github/difflib/unifieddiff/UnifiedDiffReader.java
Normal file
475
src/com/github/difflib/unifieddiff/UnifiedDiffReader.java
Normal file
@@ -0,0 +1,475 @@
|
||||
/*
|
||||
* 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.ChangeDelta;
|
||||
import com.github.difflib.patch.Chunk;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.MatchResult;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Tobias Warneke (t.warneke@gmx.net)
|
||||
*/
|
||||
public final class UnifiedDiffReader {
|
||||
|
||||
static final Pattern UNIFIED_DIFF_CHUNK_REGEXP = Pattern.compile("^@@\\s+-(?:(\\d+)(?:,(\\d+))?)\\s+\\+(?:(\\d+)(?:,(\\d+))?)\\s+@@");
|
||||
static final Pattern TIMESTAMP_REGEXP = Pattern.compile("(\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}:\\d{2}\\.\\d{3,})(?: [+-]\\d+)?");
|
||||
|
||||
private final InternalUnifiedDiffReader READER;
|
||||
private final UnifiedDiff data = new UnifiedDiff();
|
||||
|
||||
private final UnifiedDiffLine DIFF_COMMAND = new UnifiedDiffLine(true, "^diff\\s", this::processDiff);
|
||||
private final UnifiedDiffLine SIMILARITY_INDEX = new UnifiedDiffLine(true, "^similarity index (\\d+)%$", this::processSimilarityIndex);
|
||||
private final UnifiedDiffLine INDEX = new UnifiedDiffLine(true, "^index\\s[\\da-zA-Z]+\\.\\.[\\da-zA-Z]+(\\s(\\d+))?$", this::processIndex);
|
||||
private final UnifiedDiffLine FROM_FILE = new UnifiedDiffLine(true, "^---\\s", this::processFromFile);
|
||||
private final UnifiedDiffLine TO_FILE = new UnifiedDiffLine(true, "^\\+\\+\\+\\s", this::processToFile);
|
||||
private final UnifiedDiffLine RENAME_FROM = new UnifiedDiffLine(true, "^rename\\sfrom\\s(.+)$", this::processRenameFrom);
|
||||
private final UnifiedDiffLine RENAME_TO = new UnifiedDiffLine(true, "^rename\\sto\\s(.+)$", this::processRenameTo);
|
||||
|
||||
private final UnifiedDiffLine COPY_FROM = new UnifiedDiffLine(true, "^copy\\sfrom\\s(.+)$", this::processCopyFrom);
|
||||
private final UnifiedDiffLine COPY_TO = new UnifiedDiffLine(true, "^copy\\sto\\s(.+)$", this::processCopyTo);
|
||||
|
||||
private final UnifiedDiffLine NEW_FILE_MODE = new UnifiedDiffLine(true, "^new\\sfile\\smode\\s(\\d+)", this::processNewFileMode);
|
||||
|
||||
private final UnifiedDiffLine DELETED_FILE_MODE = new UnifiedDiffLine(true, "^deleted\\sfile\\smode\\s(\\d+)", this::processDeletedFileMode);
|
||||
private final UnifiedDiffLine OLD_MODE = new UnifiedDiffLine(true, "^old\\smode\\s(\\d+)", this::processOldMode);
|
||||
private final UnifiedDiffLine NEW_MODE = new UnifiedDiffLine(true, "^new\\smode\\s(\\d+)", this::processNewMode);
|
||||
private final UnifiedDiffLine BINARY_ADDED = new UnifiedDiffLine(true, "^Binary\\sfiles\\s/dev/null\\sand\\sb/(.+)\\sdiffer", this::processBinaryAdded);
|
||||
private final UnifiedDiffLine BINARY_DELETED = new UnifiedDiffLine(true, "^Binary\\sfiles\\sa/(.+)\\sand\\s/dev/null\\sdiffer", this::processBinaryDeleted);
|
||||
private final UnifiedDiffLine BINARY_EDITED = new UnifiedDiffLine(true, "^Binary\\sfiles\\sa/(.+)\\sand\\sb/(.+)\\sdiffer", this::processBinaryEdited);
|
||||
private final UnifiedDiffLine CHUNK = new UnifiedDiffLine(false, UNIFIED_DIFF_CHUNK_REGEXP, this::processChunk);
|
||||
private final UnifiedDiffLine LINE_NORMAL = new UnifiedDiffLine("^\\s", this::processNormalLine);
|
||||
private final UnifiedDiffLine LINE_DEL = new UnifiedDiffLine("^-", this::processDelLine);
|
||||
private final UnifiedDiffLine LINE_ADD = new UnifiedDiffLine("^\\+", this::processAddLine);
|
||||
|
||||
private UnifiedDiffFile actualFile;
|
||||
|
||||
UnifiedDiffReader(Reader reader) {
|
||||
this.READER = new InternalUnifiedDiffReader(reader);
|
||||
}
|
||||
|
||||
// schema = [[/^\s+/, normal], [/^diff\s/, start], [/^new file mode \d+$/, new_file],
|
||||
// [/^deleted file mode \d+$/, deleted_file], [/^index\s[\da-zA-Z]+\.\.[\da-zA-Z]+(\s(\d+))?$/, index],
|
||||
// [/^---\s/, from_file], [/^\+\+\+\s/, to_file], [/^@@\s+\-(\d+),?(\d+)?\s+\+(\d+),?(\d+)?\s@@/, chunk],
|
||||
// [/^-/, del], [/^\+/, add], [/^\\ No newline at end of file$/, eof]];
|
||||
private UnifiedDiff parse() throws IOException, UnifiedDiffParserException {
|
||||
// String headerTxt = "";
|
||||
// LOG.log(Level.FINE, "header parsing");
|
||||
// String line = null;
|
||||
// while (READER.ready()) {
|
||||
// line = READER.readLine();
|
||||
// LOG.log(Level.FINE, "parsing line {0}", line);
|
||||
// if (DIFF_COMMAND.validLine(line) || INDEX.validLine(line)
|
||||
// || FROM_FILE.validLine(line) || TO_FILE.validLine(line)
|
||||
// || NEW_FILE_MODE.validLine(line)) {
|
||||
// break;
|
||||
// } else {
|
||||
// headerTxt += line + "\n";
|
||||
// }
|
||||
// }
|
||||
// if (!"".equals(headerTxt)) {
|
||||
// data.setHeader(headerTxt);
|
||||
// }
|
||||
|
||||
String line = READER.readLine();
|
||||
while (line != null) {
|
||||
String headerTxt = "";
|
||||
LOG.log(Level.FINE, "header parsing");
|
||||
while (line != null) {
|
||||
LOG.log(Level.FINE, "parsing line {0}", line);
|
||||
if (validLine(line, DIFF_COMMAND, SIMILARITY_INDEX, INDEX,
|
||||
FROM_FILE, TO_FILE,
|
||||
RENAME_FROM, RENAME_TO,
|
||||
COPY_FROM, COPY_TO,
|
||||
NEW_FILE_MODE, DELETED_FILE_MODE,
|
||||
OLD_MODE, NEW_MODE,
|
||||
BINARY_ADDED, BINARY_DELETED,
|
||||
BINARY_EDITED, CHUNK)) {
|
||||
break;
|
||||
} else {
|
||||
headerTxt += line + "\n";
|
||||
}
|
||||
line = READER.readLine();
|
||||
}
|
||||
if (!"".equals(headerTxt)) {
|
||||
data.setHeader(headerTxt);
|
||||
}
|
||||
if (line != null && !CHUNK.validLine(line)) {
|
||||
initFileIfNecessary();
|
||||
while (line != null && !CHUNK.validLine(line)) {
|
||||
if (!processLine(line, DIFF_COMMAND, SIMILARITY_INDEX, INDEX,
|
||||
FROM_FILE, TO_FILE,
|
||||
RENAME_FROM, RENAME_TO,
|
||||
COPY_FROM, COPY_TO,
|
||||
NEW_FILE_MODE, DELETED_FILE_MODE,
|
||||
OLD_MODE, NEW_MODE,
|
||||
BINARY_ADDED , BINARY_DELETED,
|
||||
BINARY_EDITED)) {
|
||||
throw new UnifiedDiffParserException("expected file start line not found");
|
||||
}
|
||||
line = READER.readLine();
|
||||
}
|
||||
}
|
||||
if (line != null) {
|
||||
processLine(line, CHUNK);
|
||||
while ((line = READER.readLine()) != null) {
|
||||
line = checkForNoNewLineAtTheEndOfTheFile(line);
|
||||
|
||||
if (!processLine(line, LINE_NORMAL, LINE_ADD, LINE_DEL)) {
|
||||
throw new UnifiedDiffParserException("expected data line not found");
|
||||
}
|
||||
if ((originalTxt.size() == old_size && revisedTxt.size() == new_size)
|
||||
|| (old_size == 0 && new_size == 0 && originalTxt.size() == this.old_ln
|
||||
&& revisedTxt.size() == this.new_ln)) {
|
||||
finalizeChunk();
|
||||
break;
|
||||
}
|
||||
}
|
||||
line = READER.readLine();
|
||||
|
||||
line = checkForNoNewLineAtTheEndOfTheFile(line);
|
||||
}
|
||||
if (line == null || (line.startsWith("--") && !line.startsWith("---"))) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (READER.ready()) {
|
||||
String tailTxt = "";
|
||||
while (READER.ready()) {
|
||||
if (tailTxt.length() > 0) {
|
||||
tailTxt += "\n";
|
||||
}
|
||||
tailTxt += READER.readLine();
|
||||
}
|
||||
data.setTailTxt(tailTxt);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
private String checkForNoNewLineAtTheEndOfTheFile(String line) throws IOException {
|
||||
if ("\\ No newline at end of file".equals(line)) {
|
||||
actualFile.setNoNewLineAtTheEndOfTheFile(true);
|
||||
return READER.readLine();
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
static String[] parseFileNames(String line) {
|
||||
String[] split = line.split(" ");
|
||||
return new String[]{
|
||||
split[2].replaceAll("^a/", ""),
|
||||
split[3].replaceAll("^b/", "")
|
||||
};
|
||||
}
|
||||
|
||||
private static final Logger LOG = Logger.getLogger(UnifiedDiffReader.class.getName());
|
||||
|
||||
/**
|
||||
* To parse a diff file use this method.
|
||||
*
|
||||
* @param stream This is the diff file data.
|
||||
* @return In a UnifiedDiff structure this diff file data is returned.
|
||||
* @throws IOException
|
||||
* @throws UnifiedDiffParserException
|
||||
*/
|
||||
public static UnifiedDiff parseUnifiedDiff(InputStream stream) throws IOException, UnifiedDiffParserException {
|
||||
UnifiedDiffReader parser = new UnifiedDiffReader(new BufferedReader(new InputStreamReader(stream)));
|
||||
return parser.parse();
|
||||
}
|
||||
|
||||
private boolean processLine(String line, UnifiedDiffLine... rules) throws UnifiedDiffParserException {
|
||||
if (line == null) {
|
||||
return false;
|
||||
}
|
||||
for (UnifiedDiffLine rule : rules) {
|
||||
if (rule.processLine(line)) {
|
||||
LOG.fine(" >>> processed rule " + rule.toString());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
LOG.warning(" >>> no rule matched " + line);
|
||||
return false;
|
||||
//throw new UnifiedDiffParserException("parsing error at line " + line);
|
||||
}
|
||||
|
||||
private boolean validLine(String line, UnifiedDiffLine ... rules) {
|
||||
if (line == null) {
|
||||
return false;
|
||||
}
|
||||
for (UnifiedDiffLine rule : rules) {
|
||||
if (rule.validLine(line)) {
|
||||
LOG.fine(" >>> accepted rule " + rule.toString());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void initFileIfNecessary() {
|
||||
if (!originalTxt.isEmpty() || !revisedTxt.isEmpty()) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
actualFile = null;
|
||||
if (actualFile == null) {
|
||||
actualFile = new UnifiedDiffFile();
|
||||
data.addFile(actualFile);
|
||||
}
|
||||
}
|
||||
|
||||
private void processDiff(MatchResult match, String line) {
|
||||
//initFileIfNecessary();
|
||||
LOG.log(Level.FINE, "start {0}", line);
|
||||
String[] fromTo = parseFileNames(READER.lastLine());
|
||||
actualFile.setFromFile(fromTo[0]);
|
||||
actualFile.setToFile(fromTo[1]);
|
||||
actualFile.setDiffCommand(line);
|
||||
}
|
||||
|
||||
private void processSimilarityIndex(MatchResult match, String line) {
|
||||
actualFile.setSimilarityIndex(Integer.valueOf(match.group(1)));
|
||||
}
|
||||
|
||||
private List<String> originalTxt = new ArrayList<>();
|
||||
private List<String> revisedTxt = new ArrayList<>();
|
||||
private List<Integer> addLineIdxList = new ArrayList<>();
|
||||
private List<Integer> delLineIdxList = new ArrayList<>();
|
||||
private int old_ln;
|
||||
private int old_size;
|
||||
private int new_ln;
|
||||
private int new_size;
|
||||
private int delLineIdx = 0;
|
||||
private int addLineIdx = 0;
|
||||
|
||||
private void finalizeChunk() {
|
||||
if (!originalTxt.isEmpty() || !revisedTxt.isEmpty()) {
|
||||
actualFile.getPatch().addDelta(new ChangeDelta<>(new Chunk<>(
|
||||
old_ln - 1, originalTxt, delLineIdxList), new Chunk<>(
|
||||
new_ln - 1, revisedTxt, addLineIdxList)));
|
||||
old_ln = 0;
|
||||
new_ln = 0;
|
||||
originalTxt.clear();
|
||||
revisedTxt.clear();
|
||||
addLineIdxList.clear();
|
||||
delLineIdxList.clear();
|
||||
delLineIdx = 0;
|
||||
addLineIdx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void processNormalLine(MatchResult match, String line) {
|
||||
String cline = line.substring(1);
|
||||
originalTxt.add(cline);
|
||||
revisedTxt.add(cline);
|
||||
delLineIdx++;
|
||||
addLineIdx++;
|
||||
}
|
||||
|
||||
private void processAddLine(MatchResult match, String line) {
|
||||
String cline = line.substring(1);
|
||||
revisedTxt.add(cline);
|
||||
addLineIdx++;
|
||||
addLineIdxList.add(new_ln - 1 + addLineIdx);
|
||||
}
|
||||
|
||||
private void processDelLine(MatchResult match, String line) {
|
||||
String cline = line.substring(1);
|
||||
originalTxt.add(cline);
|
||||
delLineIdx++;
|
||||
delLineIdxList.add(old_ln - 1 + delLineIdx);
|
||||
}
|
||||
|
||||
private void processChunk(MatchResult match, String chunkStart) {
|
||||
// finalizeChunk();
|
||||
old_ln = toInteger(match, 1, 1);
|
||||
old_size = toInteger(match, 2, 1);
|
||||
new_ln = toInteger(match, 3, 1);
|
||||
new_size = toInteger(match, 4, 1);
|
||||
if (old_ln == 0) {
|
||||
old_ln = 1;
|
||||
}
|
||||
if (new_ln == 0) {
|
||||
new_ln = 1;
|
||||
}
|
||||
}
|
||||
|
||||
private static Integer toInteger(MatchResult match, int group, int defValue) throws NumberFormatException {
|
||||
return Integer.valueOf(Objects.toString(match.group(group), "" + defValue));
|
||||
}
|
||||
|
||||
private void processIndex(MatchResult match, String line) {
|
||||
//initFileIfNecessary();
|
||||
LOG.log(Level.FINE, "index {0}", line);
|
||||
actualFile.setIndex(line.substring(6));
|
||||
}
|
||||
|
||||
private void processFromFile(MatchResult match, String line) {
|
||||
//initFileIfNecessary();
|
||||
actualFile.setFromFile(extractFileName(line));
|
||||
actualFile.setFromTimestamp(extractTimestamp(line));
|
||||
}
|
||||
|
||||
private void processToFile(MatchResult match, String line) {
|
||||
//initFileIfNecessary();
|
||||
actualFile.setToFile(extractFileName(line));
|
||||
actualFile.setToTimestamp(extractTimestamp(line));
|
||||
}
|
||||
|
||||
private void processRenameFrom(MatchResult match, String line) {
|
||||
actualFile.setRenameFrom(match.group(1));
|
||||
}
|
||||
|
||||
private void processRenameTo(MatchResult match, String line) {
|
||||
actualFile.setRenameTo(match.group(1));
|
||||
}
|
||||
|
||||
private void processCopyFrom(MatchResult match, String line) {
|
||||
actualFile.setCopyFrom(match.group(1));
|
||||
}
|
||||
|
||||
private void processCopyTo(MatchResult match, String line) {
|
||||
actualFile.setCopyTo(match.group(1));
|
||||
}
|
||||
|
||||
private void processNewFileMode(MatchResult match, String line) {
|
||||
//initFileIfNecessary();
|
||||
actualFile.setNewFileMode(match.group(1));
|
||||
}
|
||||
|
||||
private void processDeletedFileMode(MatchResult match, String line) {
|
||||
//initFileIfNecessary();
|
||||
actualFile.setDeletedFileMode(match.group(1));
|
||||
}
|
||||
|
||||
private void processOldMode(MatchResult match, String line) {
|
||||
actualFile.setOldMode(match.group(1));
|
||||
}
|
||||
|
||||
private void processNewMode(MatchResult match, String line) {
|
||||
actualFile.setNewMode(match.group(1));
|
||||
}
|
||||
|
||||
private void processBinaryAdded(MatchResult match, String line) {
|
||||
actualFile.setBinaryAdded(match.group(1));
|
||||
}
|
||||
|
||||
private void processBinaryDeleted(MatchResult match, String line) {
|
||||
actualFile.setBinaryDeleted(match.group(1));
|
||||
}
|
||||
|
||||
private void processBinaryEdited(MatchResult match, String line) {
|
||||
actualFile.setBinaryEdited(match.group(1));
|
||||
}
|
||||
|
||||
private String extractFileName(String _line) {
|
||||
Matcher matcher = TIMESTAMP_REGEXP.matcher(_line);
|
||||
String line = _line;
|
||||
if (matcher.find()) {
|
||||
line = line.substring(0, matcher.start());
|
||||
}
|
||||
line = line.split("\t")[0];
|
||||
return line.substring(4).replaceFirst("^(a|b|old|new)/", "")
|
||||
.trim();
|
||||
}
|
||||
|
||||
private String extractTimestamp(String line) {
|
||||
Matcher matcher = TIMESTAMP_REGEXP.matcher(line);
|
||||
if (matcher.find()) {
|
||||
return matcher.group();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
final class UnifiedDiffLine {
|
||||
|
||||
private final Pattern pattern;
|
||||
private final BiConsumer<MatchResult, String> command;
|
||||
private final boolean stopsHeaderParsing;
|
||||
|
||||
public UnifiedDiffLine(String pattern, BiConsumer<MatchResult, String> command) {
|
||||
this(false, pattern, command);
|
||||
}
|
||||
|
||||
public UnifiedDiffLine(boolean stopsHeaderParsing, String pattern, BiConsumer<MatchResult, String> command) {
|
||||
this.pattern = Pattern.compile(pattern);
|
||||
this.command = command;
|
||||
this.stopsHeaderParsing = stopsHeaderParsing;
|
||||
}
|
||||
|
||||
public UnifiedDiffLine(boolean stopsHeaderParsing, Pattern pattern, BiConsumer<MatchResult, String> command) {
|
||||
this.pattern = pattern;
|
||||
this.command = command;
|
||||
this.stopsHeaderParsing = stopsHeaderParsing;
|
||||
}
|
||||
|
||||
public boolean validLine(String line) {
|
||||
Matcher m = pattern.matcher(line);
|
||||
return m.find();
|
||||
}
|
||||
|
||||
public boolean processLine(String line) throws UnifiedDiffParserException {
|
||||
Matcher m = pattern.matcher(line);
|
||||
if (m.find()) {
|
||||
command.accept(m.toMatchResult(), line);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isStopsHeaderParsing() {
|
||||
return stopsHeaderParsing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "UnifiedDiffLine{" + "pattern=" + pattern + ", stopsHeaderParsing=" + stopsHeaderParsing + '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class InternalUnifiedDiffReader extends BufferedReader {
|
||||
|
||||
private String lastLine;
|
||||
|
||||
public InternalUnifiedDiffReader(Reader reader) {
|
||||
super(reader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String readLine() throws IOException {
|
||||
lastLine = super.readLine();
|
||||
return lastLine();
|
||||
}
|
||||
|
||||
String lastLine() {
|
||||
return lastLine;
|
||||
}
|
||||
}
|
||||
211
src/com/github/difflib/unifieddiff/UnifiedDiffWriter.java
Normal file
211
src/com/github/difflib/unifieddiff/UnifiedDiffWriter.java
Normal file
@@ -0,0 +1,211 @@
|
||||
/*
|
||||
* 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<String, List<String>> 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<String, List<String>> originalLinesProvider, Consumer<String> writer, int contextSize) throws IOException {
|
||||
if (diff.getHeader() != null) {
|
||||
writer.accept(diff.getHeader());
|
||||
}
|
||||
|
||||
for (UnifiedDiffFile file : diff.getFiles()) {
|
||||
List<AbstractDelta<String>> 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<String> originalLines = originalLinesProvider.apply(file.getFromFile());
|
||||
|
||||
List<AbstractDelta<String>> deltas = new ArrayList<>();
|
||||
|
||||
AbstractDelta<String> 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<String> 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<String> writer,
|
||||
List<String> origLines, List<AbstractDelta<String>> deltas,
|
||||
int contextSize, boolean newFile) {
|
||||
List<String> 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<String> 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<String> 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<String> writer, AbstractDelta<String> delta) {
|
||||
for (String line : delta.getSource().getLines()) {
|
||||
writer.accept("-" + line);
|
||||
}
|
||||
for (String line : delta.getTarget().getLines()) {
|
||||
writer.accept("+" + line);
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeOrNothing(Consumer<String> writer, String str) throws IOException {
|
||||
if (str != null) {
|
||||
writer.accept(str);
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/com/github/difflib/unifieddiff/package-info.java
Normal file
25
src/com/github/difflib/unifieddiff/package-info.java
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
/**
|
||||
* This is the new implementation of UnifiedDiff Tools. This version is multi file aware.
|
||||
* <p/>
|
||||
* To read a unified diff file you should use {@link UnifiedDiffReader#parseUnifiedDiff}.
|
||||
* You will get a {@link UnifiedDiff} that holds all informations about the
|
||||
* diffs and the files.
|
||||
* <p/>
|
||||
* To process the UnifiedDiff use {@link UnifiedDiffWriter#write}.
|
||||
*/
|
||||
package com.github.difflib.unifieddiff;
|
||||
Reference in New Issue
Block a user