Skip to content

Feature request: Diff viewer #131

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
<junit-jupiter-api.version>5.9.2</junit-jupiter-api.version>
<launch4j.version>2.0.1</launch4j.version>
<logback.version>1.2.11</logback.version>
<diff.version>0.0.1-SNAPSHOT</diff.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<sonar.projectName>RUPS</sonar.projectName>
<maven.compiler.target>11</maven.compiler.target>
Expand Down Expand Up @@ -93,6 +94,11 @@
<artifactId>dom4j</artifactId>
<version>${dom4j.version}</version>
</dependency>
<dependency>
<groupId>be.ysebie</groupId>
<artifactId>diff</artifactId>
<version>${diff.version}</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>pdftest</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,4 +91,6 @@ public interface IRupsController {
* Closes the current file and tries to open it as an owner again.
*/
void reopenAsOwner();

void openDiffViewer(File fileA, File fileB);
}
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,11 @@ public void reopenAsOwner() {
}
}

@Override
public void openDiffViewer(File fileA, File fileB) {
this.rupsTabbedPane.openDiffViewer(fileA, fileB);
}

@Override
public final IPdfFile getCurrentFile() {
return this.rupsTabbedPane.getCurrentFile();
Expand Down
9 changes: 8 additions & 1 deletion src/main/java/com/itextpdf/rups/view/RupsMenuBar.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ This file is part of the iText (R) project.

import java.awt.event.ActionListener;
import java.awt.event.InputEvent;
import java.io.File;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;
Expand Down Expand Up @@ -123,7 +124,13 @@ public RupsMenuBar(RupsController controller) {
}
);
add(edit);

final JMenu diffViewer = new JMenu("DiffViewer");
addItem(diffViewer, "DiffViewer", e -> {
File fileA = new File("src/main/resources/simpleParagraphTest.pdf");
File fileB = new File("src/main/resources/cmp_simpleParagraphTest.pdf");
controller.openDiffViewer(fileB,fileA);
} );
add(diffViewer);
add(Box.createGlue());

final JMenu help = new JMenu(Language.MENU_BAR_HELP.getString());
Expand Down
16 changes: 10 additions & 6 deletions src/main/java/com/itextpdf/rups/view/RupsTabbedPane.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,13 @@ This file is part of the iText (R) project.
import com.itextpdf.rups.Rups;
import com.itextpdf.rups.controller.RupsInstanceController;
import com.itextpdf.rups.model.IPdfFile;
import com.itextpdf.rups.view.diff.DiffViewer;

import java.awt.Component;
import java.awt.Dimension;
import java.io.File;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import java.awt.*;
import java.io.File;

/**
* The class holding the JTabbedPane that holds the Rups tabs. This class is responsible for loading, closing, and
Expand Down Expand Up @@ -170,4 +168,10 @@ private void showReadOnlyWarning() {
Snackbar.make(Rups.getMainFrame(), Language.WARNING_OPENED_IN_READ_ONLY_MODE.getString()).show();
}
}

public void openDiffViewer(File fileA, File fileB) {
DiffViewer diffViewer = new DiffViewer(fileA,fileB);
this.jTabbedPane.addTab("DiffView", null, diffViewer);
this.jTabbedPane.setSelectedComponent(diffViewer);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.itextpdf.rups.view.diff;

import be.ysebie.diff.lib.DiffStrategy;
import com.itextpdf.io.source.ByteArrayOutputStream;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.zip.DataFormatException;

public class DiffGenerationStrategy implements DiffStrategy<ByteBuffer> {

private final ViewerOptions options;

private ByteBuffer[] tokenizedDataA;
private ByteBuffer[] tokenizedDataB;

DiffGenerationStrategy(ViewerOptions options) throws IOException, DataFormatException {
this.options = options;
setupDiffData();
}

private void setupDiffData() throws IOException, DataFormatException {
if (options.fileA == null || options.fileB == null) {
throw new IOException("Files not set");
}
tokenizedDataA = generateData(parsePdfDocument(Files.readAllBytes(options.fileA.toPath())));
tokenizedDataB = generateData(parsePdfDocument(Files.readAllBytes(options.fileB.toPath())));
}

@Override
public ByteBuffer[] getDiffDataA() {
return tokenizedDataA;
}

@Override
public ByteBuffer[] getDiffDataB() {
return tokenizedDataB;
}

private ByteBuffer[] generateData(byte[] dataA) {
// if the byte is a new line, split the array
int start = 0;
int end = 0;
List<ByteBuffer> result = new ArrayList<>();
for (int i = 0; i < dataA.length; i++) {
if (dataA[i] == '\n') {
end = i;
byte[] tmp = Arrays.copyOfRange(dataA, start, end);
start = end + 1;
result.add(ByteBuffer.wrap(tmp));
}
}
if (start < dataA.length) {
result.add(ByteBuffer.wrap(Arrays.copyOfRange(dataA, start, dataA.length)));
}
return result.toArray(new ByteBuffer[0]);
}

private byte[] parsePdfDocument(byte[] data) throws IOException, DataFormatException {
if (!options.decompress) {
return data;
}
PdfDiffModifier diffViewerTokenizer = new PdfDiffModifier(data);
ByteArrayOutputStream baos = diffViewerTokenizer.next();
return baos.toByteArray();
}
}

232 changes: 232 additions & 0 deletions src/main/java/com/itextpdf/rups/view/diff/DiffViewer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
package com.itextpdf.rups.view.diff;

import be.ysebie.diff.lib.Delta;
import be.ysebie.diff.lib.Diff;
import be.ysebie.diff.lib.deltaimpl.ChangeDelta;
import be.ysebie.diff.lib.deltaimpl.DeleteDelta;
import be.ysebie.diff.lib.deltaimpl.EqualDelta;
import be.ysebie.diff.lib.deltaimpl.InsertDelta;

import javax.swing.*;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;
import java.awt.*;
import java.io.File;
import java.nio.ByteBuffer;
import java.util.List;

public class DiffViewer extends JPanel {

private final ViewerOptions options;
private final JFileChooser filePicker = new JFileChooser();
private final JCheckBox cbDecompress = new JCheckBox("Decompress");
private final JPanel optionsBox = new JPanel();
JPanel mainPanel = new JPanel();

public DiffViewer(File a, File b) {
options = new ViewerOptions();
options.fileA = a;
options.fileB = b;
BorderLayout layout = new BorderLayout();
layout.setHgap(1);
layout.setVgap(1);
setLayout(layout);
GridLayout layout2 = new GridLayout(1, 2);
mainPanel.setLayout(layout2);

add(mainPanel, BorderLayout.CENTER);
setupOptionSelector();
setupMainView();
}

private static String convertByteBufferToString(ByteBuffer byteBuffer) {
return new String(byteBuffer.array());
}

private void setupOptionSelector() {
cbDecompress.setSelected(options.decompress);
cbDecompress.addActionListener((l) -> {
options.decompress = cbDecompress.isSelected();
rerenderDiff();
});
optionsBox.add(cbDecompress);
add(optionsBox, BorderLayout.NORTH);
}

private void rerenderDiff() {
SwingUtilities.invokeLater(this::setupMainView);

}

private void setupMainView() {
mainPanel.removeAll();

JPanel panelA = new JPanel();
JPanel panelB = new JPanel();
Box vboxA = Box.createVerticalBox();
Box vboxB = Box.createVerticalBox();


vboxA.setSize(100, 100);
vboxB.setSize(100, 100);

panelA.setLayout(new BoxLayout(panelA, BoxLayout.Y_AXIS));
panelB.setLayout(new BoxLayout(panelB, BoxLayout.Y_AXIS));

vboxA.setVisible(true);
vboxB.setVisible(true);

JButton btnFileA = new JButton("File A");
btnFileA.addActionListener((l) -> {
int returnVal = filePicker.showOpenDialog(this);
if (returnVal == JFileChooser.APPROVE_OPTION) {
options.fileA = filePicker.getSelectedFile();
rerenderDiff();
}
});

JButton btnFileB = new JButton("File B");
btnFileB.addActionListener((l) -> {
int returnVal = filePicker.showOpenDialog(this);
if (returnVal == JFileChooser.APPROVE_OPTION) {
options.fileB = filePicker.getSelectedFile();
rerenderDiff();
}
});

JLabel labelA = new JLabel("File A: " + (options.fileA != null ? options.fileA.getName() : "None"));
JLabel labelB = new JLabel("File B: " + (options.fileB != null ? options.fileB.getName() : "None"));

labelA.setHorizontalAlignment(SwingConstants.CENTER);
labelB.setHorizontalAlignment(SwingConstants.CENTER);

labelA.setSize(100, 100);
labelB.setSize(100, 100);

vboxA.add(labelA);
vboxA.add(btnFileA);

vboxB.add(labelB);
vboxB.add(btnFileB);

JTextPane textPaneA = new JTextPane();
textPaneA.setEditable(false);
JTextPane textPaneB = new JTextPane();
textPaneB.setEditable(false);


JScrollPane scrollPaneA = new JScrollPane(textPaneA);
JScrollPane scrollPaneB = new JScrollPane(textPaneB);

scrollPaneA.getVerticalScrollBar().addAdjustmentListener((l) -> {
scrollPaneB.getVerticalScrollBar().setValue(scrollPaneA.getVerticalScrollBar().getValue());
});
scrollPaneA.getHorizontalScrollBar().addAdjustmentListener((l) -> {
scrollPaneB.getHorizontalScrollBar().setValue(scrollPaneA.getHorizontalScrollBar().getValue());
});

scrollPaneB.getVerticalScrollBar().addAdjustmentListener((l) -> {
scrollPaneA.getVerticalScrollBar().setValue(scrollPaneB.getVerticalScrollBar().getValue());
});

scrollPaneB.getHorizontalScrollBar().addAdjustmentListener((l) -> {
scrollPaneA.getHorizontalScrollBar().setValue(scrollPaneB.getHorizontalScrollBar().getValue());
});

StyledDocument docA = textPaneA.getStyledDocument();
StyledDocument docB = textPaneB.getStyledDocument();

vboxA.add(scrollPaneA);
vboxB.add(scrollPaneB);

if (options.fileA != null && options.fileB != null) {
System.out.println("Generating diff");
try {
DiffGenerationStrategy diffGenerationStrategy = new DiffGenerationStrategy(options);
Diff<ByteBuffer> diff = new Diff<ByteBuffer>(diffGenerationStrategy);
List<Delta<ByteBuffer>> deltas = diff.generateDeltas();
for (Delta<ByteBuffer> delta : deltas) {
switch (delta.getType()) {
case Change:
System.out.println("Change");
ChangeDelta<ByteBuffer> changeDelta = (ChangeDelta<ByteBuffer>) delta;
StringBuilder sbChangeA = new StringBuilder();
StringBuilder sbChangeB = new StringBuilder();
for (ByteBuffer data : changeDelta.getDeletedData()) {
sbChangeA.append(convertByteBufferToString(data)).append("\n");
}
for (ByteBuffer data : changeDelta.getInsertedData()) {
sbChangeB.append(convertByteBufferToString(data)).append("\n");
}
SimpleAttributeSet keyWord = new SimpleAttributeSet();
StyleConstants.setForeground(keyWord, Color.BLACK);
StyleConstants.setBackground(keyWord, Color.YELLOW);
StyleConstants.setBold(keyWord, true);

docA.insertString(docA.getLength(), sbChangeA.toString(), keyWord);
docB.insertString(docB.getLength(), sbChangeB.toString(), keyWord);
break;
case Delete:
DeleteDelta<ByteBuffer> deleteDelta = (DeleteDelta<ByteBuffer>) delta;
StringBuilder sbADelete = new StringBuilder();
StringBuilder sbBDelete = new StringBuilder();
for (ByteBuffer data : deleteDelta.getDeletedData()) {
sbADelete.append(convertByteBufferToString(data)).append("\n");
sbBDelete.append("\n");
}
SimpleAttributeSet keyWordDelete = new SimpleAttributeSet();
StyleConstants.setForeground(keyWordDelete, Color.BLACK);
StyleConstants.setBackground(keyWordDelete, Color.RED);

docA.insertString(docA.getLength(), sbADelete.toString(), keyWordDelete);
docB.insertString(docB.getLength(), sbBDelete.toString(), keyWordDelete);

break;
case Equal:
EqualDelta<ByteBuffer> equalDelta = (EqualDelta<ByteBuffer>) delta;
StringBuilder sb = new StringBuilder();
for (ByteBuffer data : equalDelta.getData()) {
sb.append(convertByteBufferToString(data)).append("\n");
}
docA.insertString(docA.getLength(), sb.toString(), null);
docB.insertString(docB.getLength(), sb.toString(), null);
break;
case Insert:
System.out.println("inserted");
InsertDelta<ByteBuffer> insertDelta = (InsertDelta<ByteBuffer>) delta;
StringBuilder sbA = new StringBuilder();
StringBuilder sbB = new StringBuilder();
for (ByteBuffer data : insertDelta.getInsertedData()) {
sbA.append("\n");
sbB.append(convertByteBufferToString(data)).append("\n");
}
SimpleAttributeSet keyWordInsert = new SimpleAttributeSet();
StyleConstants.setForeground(keyWordInsert, Color.BLACK);
StyleConstants.setBackground(keyWordInsert, Color.GREEN);

docA.insertString(docA.getLength(), sbA.toString(), keyWordInsert);
docB.insertString(docB.getLength(), sbB.toString(), keyWordInsert);

break;
}
}
} catch (Exception e) {
System.out.println("Error generating diff: " + e.getMessage());
e.printStackTrace();
}

}



panelA.add(vboxA);
panelB.add(vboxB);

mainPanel.add(panelA);
mainPanel.add(panelB);
revalidate();
}

}

Loading