++
сравнение
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/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/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/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>
|
</root>
|
||||||
</artifact>
|
</artifact>
|
||||||
</component>
|
</component>
|
||||||
49
.idea/workspace.xml
generated
49
.idea/workspace.xml
generated
@@ -7,9 +7,46 @@
|
|||||||
</component>
|
</component>
|
||||||
<component name="ChangeListManager">
|
<component name="ChangeListManager">
|
||||||
<list default="true" id="e42177c3-2328-4b27-8a01-35779b2beb99" name="Default Changelist" comment="">
|
<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$/.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/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>
|
</list>
|
||||||
<option name="SHOW_DIALOG" value="false" />
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
<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.SHOW" value="true" />
|
||||||
<property name="UI_DESIGNER_EDITOR_MODE.UIDesignerToolWindowManager.WIDTH" value="509" />
|
<property name="UI_DESIGNER_EDITOR_MODE.UIDesignerToolWindowManager.WIDTH" value="509" />
|
||||||
<property name="extract.method.default.visibility" value="public" />
|
<property name="extract.method.default.visibility" value="public" />
|
||||||
<property name="last_opened_file_path" value="$PROJECT_DIR$/src/icons/Transformations" />
|
<property name="last_opened_file_path" value="$PROJECT_DIR$/src" />
|
||||||
<property name="project.structure.last.edited" value="Libraries" />
|
<property name="project.structure.last.edited" value="Artifacts" />
|
||||||
<property name="project.structure.proportion" value="0.15" />
|
<property name="project.structure.proportion" value="0.15" />
|
||||||
<property name="project.structure.side.proportion" value="0.27322906" />
|
<property name="project.structure.side.proportion" value="0.27322906" />
|
||||||
<property name="run.code.analysis.last.selected.profile" value="pProject Default" />
|
<property name="run.code.analysis.last.selected.profile" value="pProject Default" />
|
||||||
@@ -102,11 +139,11 @@
|
|||||||
<recent name="controls.Trees" />
|
<recent name="controls.Trees" />
|
||||||
</key>
|
</key>
|
||||||
<key name="CopyFile.RECENT_KEYS">
|
<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\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\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>
|
||||||
<key name="MoveMembersDialog.RECENTS_KEY">
|
<key name="MoveMembersDialog.RECENTS_KEY">
|
||||||
<recent name="_VisualDVM.ComponentsServer.Component.Sapfor.Sapfor" />
|
<recent name="_VisualDVM.ComponentsServer.Component.Sapfor.Sapfor" />
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"ServerUserPassword": "mprit_2011",
|
"ServerUserPassword": "mprit_2011",
|
||||||
"OfferRegistrationOnStart": true,
|
"OfferRegistrationOnStart": true,
|
||||||
"Workspace": "E:\\Tests",
|
"Workspace": "E:\\Tests",
|
||||||
"ProjectsSearchDirectory": "E:\\BUG\\JAC",
|
"ProjectsSearchDirectory": "E:\\Tests\\Downloads\\bugreport_1742890241",
|
||||||
"DocumentsDirectory": "C:\\Users\\misha\\Documents\\_testing_system",
|
"DocumentsDirectory": "C:\\Users\\misha\\Documents\\_testing_system",
|
||||||
"VisualiserPath": "C:\\Users\\misha\\Downloads",
|
"VisualiserPath": "C:\\Users\\misha\\Downloads",
|
||||||
"Sapfor_FPath": "E:\\_sapfor_x64\\Components\\Sapfor_F",
|
"Sapfor_FPath": "E:\\_sapfor_x64\\Components\\Sapfor_F",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import Common.Utils.Vector_;
|
|||||||
|
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
public class Constants {
|
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 planner_version = 24;
|
||||||
public static final int testingMaxKernels = 64;
|
public static final int testingMaxKernels = 64;
|
||||||
//--
|
//--
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
package _VisualDVM.Passes.All;
|
package _VisualDVM.Passes.All;
|
||||||
import Common.Passes.Pass;
|
import Common.Passes.Pass;
|
||||||
import Common.Utils.Vector_;
|
import Common.Utils.Vector_;
|
||||||
|
import com.github.difflib.text.DiffRow;
|
||||||
|
import com.github.difflib.text.DiffRowGenerator;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileWriter;
|
import java.io.FileWriter;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.ForkJoinPool;
|
import java.util.concurrent.ForkJoinPool;
|
||||||
public class TestPass extends Pass {
|
public class TestPass extends Pass {
|
||||||
@@ -52,5 +55,23 @@ public class TestPass extends Pass {
|
|||||||
System.out.println("Square of " + num + " is " + square);
|
System.out.println("Square of " + num + " is " + square);
|
||||||
})
|
})
|
||||||
).join();
|
).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));
|
setPreferredSize(new Dimension(0, 30));
|
||||||
//--
|
//--
|
||||||
|
|
||||||
/*
|
|
||||||
add(new MenuBarButton() {
|
add(new MenuBarButton() {
|
||||||
{
|
{
|
||||||
setIcon("/Common/icons/Apply.png");
|
setIcon("/Common/icons/Apply.png");
|
||||||
@@ -77,7 +77,6 @@ public class MainMenuBar extends VisualiserMenuBar {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
//--
|
//--
|
||||||
add(new JSeparator());
|
add(new JSeparator());
|
||||||
add(MachineButton = new MenuBarButton() {
|
add(MachineButton = new MenuBarButton() {
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ public class VersionsComparisonMenu extends PropertiesSubmenu {
|
|||||||
public VersionsComparisonMenu() {
|
public VersionsComparisonMenu() {
|
||||||
super("Сравнение версий", null,
|
super("Сравнение версий", null,
|
||||||
Global.normalProperties,
|
Global.normalProperties,
|
||||||
"RegisterOn",
|
// "RegisterOn",
|
||||||
"SpacesOn",
|
// "SpacesOn",
|
||||||
"EmptyLinesOn",
|
// "EmptyLinesOn",
|
||||||
"FortranWrapsOn",
|
// "FortranWrapsOn",
|
||||||
"ExtensionsOn",
|
"ExtensionsOn",
|
||||||
"ComparsionDiffMergeOn"
|
"ComparsionDiffMergeOn"
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -51,30 +51,6 @@
|
|||||||
<constraints/>
|
<constraints/>
|
||||||
<properties/>
|
<properties/>
|
||||||
</component>
|
</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">
|
<component id="42c3c" class="javax.swing.JButton" binding="bCompare">
|
||||||
<constraints/>
|
<constraints/>
|
||||||
<properties>
|
<properties>
|
||||||
|
|||||||
@@ -9,20 +9,24 @@ import Common.Visual.Menus.VisualiserMenuBar;
|
|||||||
import Common.Visual.UI;
|
import Common.Visual.UI;
|
||||||
import _VisualDVM.ProjectData.Files.UI.Editor.SPFEditor;
|
import _VisualDVM.ProjectData.Files.UI.Editor.SPFEditor;
|
||||||
import _VisualDVM.Utils;
|
import _VisualDVM.Utils;
|
||||||
|
import com.github.difflib.text.DiffRow;
|
||||||
|
import com.github.difflib.text.DiffRowGenerator;
|
||||||
import javafx.util.Pair;
|
import javafx.util.Pair;
|
||||||
import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaHighlighter;
|
import org.fife.ui.rsyntaxtextarea.RSyntaxTextAreaHighlighter;
|
||||||
import org.fife.ui.rtextarea.RTextScrollPane;
|
import org.fife.ui.rtextarea.RTextScrollPane;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
public abstract class ComparisonForm<T> {
|
public abstract class ComparisonForm<T> {
|
||||||
public Class<T> t; //класс объектов.
|
public Class<T> t; //класс объектов.
|
||||||
//-->>
|
//-->>
|
||||||
public Vector<String> lines = new Vector<>(); //строки с учетом/неучетом пробелов. для сравнения
|
public Vector<String> lines = new Vector<>(); //строки с учетом/неучетом пробелов. для сравнения
|
||||||
public Vector<String> visible_lines = new Vector<>(); //строки с нетронутыми пробелами. для отображения
|
public Vector<String> visible_lines = new Vector<>(); //строки с нетронутыми пробелами. для отображения
|
||||||
//подсветка.
|
//подсветка.
|
||||||
public LinkedHashMap<Integer, Pair<Integer, Boolean>> colors = new LinkedHashMap<>();
|
|
||||||
public RSyntaxTextAreaHighlighter slave_highlighter = null; //погонщик рабов
|
public RSyntaxTextAreaHighlighter slave_highlighter = null; //погонщик рабов
|
||||||
protected JToolBar tools;
|
protected JToolBar tools;
|
||||||
protected JLabel lObjectName;
|
protected JLabel lObjectName;
|
||||||
@@ -39,8 +43,6 @@ public abstract class ComparisonForm<T> {
|
|||||||
//-->>
|
//-->>
|
||||||
private JPanel content;
|
private JPanel content;
|
||||||
private JPanel editorPanel;
|
private JPanel editorPanel;
|
||||||
private JButton bPrevious;
|
|
||||||
private JButton bNext;
|
|
||||||
private JButton bCompare;
|
private JButton bCompare;
|
||||||
private RTextScrollPane Scroll;
|
private RTextScrollPane Scroll;
|
||||||
//-----
|
//-----
|
||||||
@@ -59,8 +61,6 @@ public abstract class ComparisonForm<T> {
|
|||||||
t = t_in;
|
t = t_in;
|
||||||
this_ = this;
|
this_ = this;
|
||||||
slave = slave_in;
|
slave = slave_in;
|
||||||
bPrevious.setVisible(isMaster());
|
|
||||||
bNext.setVisible(isMaster());
|
|
||||||
Scroll.setLineNumbersEnabled(true);
|
Scroll.setLineNumbersEnabled(true);
|
||||||
bApplyObject.addActionListener(e -> {
|
bApplyObject.addActionListener(e -> {
|
||||||
ApplyObject();
|
ApplyObject();
|
||||||
@@ -85,24 +85,6 @@ public abstract class ComparisonForm<T> {
|
|||||||
});
|
});
|
||||||
//</editor-fold>
|
//</editor-fold>
|
||||||
slave.master = this;
|
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 -> {
|
bCompare.addActionListener(e -> {
|
||||||
DoComparePass(isReady() && slave.isReady());
|
DoComparePass(isReady() && slave.isReady());
|
||||||
});
|
});
|
||||||
@@ -146,7 +128,7 @@ public abstract class ComparisonForm<T> {
|
|||||||
showObject();
|
showObject();
|
||||||
}
|
}
|
||||||
private void ShowCurrentDiff() {
|
private void ShowCurrentDiff() {
|
||||||
Body.gotoLine_(colors.get(current_diff_line).getKey());
|
// Body.gotoLine_(colors.get(current_diff_line).getKey());
|
||||||
}
|
}
|
||||||
private void getLines() {
|
private void getLines() {
|
||||||
lines.clear();
|
lines.clear();
|
||||||
@@ -164,7 +146,6 @@ public abstract class ComparisonForm<T> {
|
|||||||
protected void Compare() throws Exception {
|
protected void Compare() throws Exception {
|
||||||
events_on = false;
|
events_on = false;
|
||||||
current_diff_line = CommonConstants.Nan;
|
current_diff_line = CommonConstants.Nan;
|
||||||
colors.clear();
|
|
||||||
//-----------------------------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------------------------
|
||||||
Body.setText("");
|
Body.setText("");
|
||||||
slave.Body.setText("");
|
slave.Body.setText("");
|
||||||
@@ -175,62 +156,49 @@ public abstract class ComparisonForm<T> {
|
|||||||
Vector<String> t1 = new Vector<>();
|
Vector<String> t1 = new Vector<>();
|
||||||
Vector<String> t2 = new Vector<>();
|
Vector<String> t2 = new Vector<>();
|
||||||
//------
|
//------
|
||||||
int old_j = 0;
|
DiffRowGenerator generator = DiffRowGenerator.create()
|
||||||
int j = 0;
|
.showInlineDiffs(true)
|
||||||
for (int i = 0; i < lines.size(); ++i) {
|
.inlineDiffByWord(true)
|
||||||
if (Utils.Contains(slave.lines, lines.get(i), old_j)) {
|
.ignoreWhiteSpaces(true)
|
||||||
for (int k = old_j; k < slave.lines.size(); ++k) {
|
.oldTag(f -> "~")
|
||||||
j = k;
|
.newTag(f -> "**")
|
||||||
if (Utils.CompareLines(lines.get(i), slave.lines.get(k))) {
|
.build();
|
||||||
j++;
|
List<DiffRow> rows = generator.generateDiffRows(
|
||||||
t1.add(visible_lines.get(i));
|
visible_lines,
|
||||||
t2.add(slave.visible_lines.get(k));
|
slave.visible_lines);
|
||||||
break;
|
|
||||||
} else {
|
for (DiffRow row : rows) {
|
||||||
t1.add("+");
|
t1.add(row.getOldLine());
|
||||||
t2.add("+ " + slave.visible_lines.get(k));
|
t2.add(row.getNewLine());
|
||||||
colors.put(d, new Pair(t2.size() - 1, true));
|
|
||||||
++d;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
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));
|
Body.setText(String.join("\n", t1));
|
||||||
slave.Body.setText(String.join("\n", t2));
|
slave.Body.setText(String.join("\n", t2));
|
||||||
Body.setCaretPosition(0);
|
Body.setCaretPosition(0);
|
||||||
slave.Body.setCaretPosition(0);
|
slave.Body.setCaretPosition(0);
|
||||||
//теперь покрас.
|
//--
|
||||||
for (Integer diff_num : colors.keySet()) {
|
Pattern master_pattern = Pattern.compile("~.*~");
|
||||||
slave_highlighter.addHighlight(
|
Matcher master_matcher = master_pattern.matcher(Body.getText());
|
||||||
slave.Body.getLineStartOffset(colors.get(diff_num).getKey()),
|
while (master_matcher.find()) {
|
||||||
slave.Body.getLineEndOffset(colors.get(diff_num).getKey()),
|
Body.getHighlighter().addHighlight(
|
||||||
colors.get(diff_num).getValue() ?
|
master_matcher.start(),
|
||||||
SPFEditor.GreenTextPainter :
|
master_matcher.end(),
|
||||||
SPFEditor.RedTextPainter
|
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;
|
events_on = true;
|
||||||
}
|
}
|
||||||
public void Show() throws Exception {
|
public void Show() throws Exception {
|
||||||
events_on = false;
|
events_on = false;
|
||||||
current_diff_line = CommonConstants.Nan;
|
current_diff_line = CommonConstants.Nan;
|
||||||
colors.clear();
|
|
||||||
//----------------------------------------------------------------------------------------------
|
//----------------------------------------------------------------------------------------------
|
||||||
Body.setText("");
|
Body.setText("");
|
||||||
slave.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