mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-24 21:21:56 +00:00
Merge remote-tracking branch 'origin/GT-2824-dragonmacher'
Conflicts: Ghidra/Features/Base/src/main/java/ghidra/app/script/ScriptInfo.java
This commit is contained in:
commit
041e59aeaf
@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -27,6 +26,7 @@ import javax.swing.text.*;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.program.model.listing.CommentHistory;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.Msg;
|
||||
|
||||
/**
|
||||
* Panel that shows comment history for a particular comment type; uses
|
||||
@ -34,13 +34,13 @@ import ghidra.program.model.listing.Program;
|
||||
* readability.
|
||||
*/
|
||||
class CommentHistoryPanel extends JPanel {
|
||||
|
||||
|
||||
private final static String NO_HISTORY = "No History Found";
|
||||
private SimpleAttributeSet userAttrSet;
|
||||
private SimpleAttributeSet dateAttrSet;
|
||||
private SimpleAttributeSet textAttrSet;
|
||||
private SimpleAttributeSet tabAttrSet;
|
||||
|
||||
|
||||
private StyledDocument doc;
|
||||
private JTextPane textPane;
|
||||
|
||||
@ -52,87 +52,87 @@ class CommentHistoryPanel extends JPanel {
|
||||
* @param commentType comment type
|
||||
*/
|
||||
CommentHistoryPanel(int commentType) {
|
||||
|
||||
|
||||
super(new BorderLayout());
|
||||
setUpAttributes();
|
||||
this.commentType = commentType;
|
||||
formatter = new SimpleDateFormat("yyyy MMM dd hh:mm aaa");
|
||||
create();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Show the comment history
|
||||
* @param program program
|
||||
* @param addr address of comment history
|
||||
*/
|
||||
void showCommentHistory(Program program, Address addr) {
|
||||
|
||||
|
||||
textPane.setText("");
|
||||
|
||||
CommentHistory[] historyItems =
|
||||
program.getListing().getCommentHistory(addr, commentType);
|
||||
|
||||
CommentHistory[] historyItems = program.getListing().getCommentHistory(addr, commentType);
|
||||
try {
|
||||
if (historyItems.length == 0) {
|
||||
doc.insertString(0, NO_HISTORY, null);
|
||||
doc.setCharacterAttributes(0, NO_HISTORY.length(), textAttrSet, true);
|
||||
return;
|
||||
}
|
||||
for (int i=0; i<historyItems.length; i++) {
|
||||
formatHistory(historyItems[i]);
|
||||
for (CommentHistory historyItem : historyItems) {
|
||||
formatHistory(historyItem);
|
||||
}
|
||||
} catch (BadLocationException e) {
|
||||
}
|
||||
catch (BadLocationException e) {
|
||||
// shouldn't happen
|
||||
Msg.debug(this, "Error setting comment text field text", e);
|
||||
}
|
||||
textPane.setCaretPosition(0);
|
||||
}
|
||||
|
||||
|
||||
private void create() {
|
||||
textPane = new JTextPane();
|
||||
textPane.setEditable(false);
|
||||
add(textPane, BorderLayout.CENTER);
|
||||
doc = textPane.getStyledDocument();
|
||||
doc = textPane.getStyledDocument();
|
||||
}
|
||||
|
||||
private void formatHistory(CommentHistory history)
|
||||
throws BadLocationException {
|
||||
|
||||
private void formatHistory(CommentHistory history) throws BadLocationException {
|
||||
|
||||
int offset = doc.getLength();
|
||||
String userName = history.getUserName();
|
||||
|
||||
|
||||
if (offset > 0) {
|
||||
userName = "\n" + userName;
|
||||
}
|
||||
doc.insertString(offset, userName, userAttrSet);
|
||||
|
||||
|
||||
offset = doc.getLength();
|
||||
doc.insertString(offset, "\t" + formatter.format(history.getModificationDate()),
|
||||
dateAttrSet);
|
||||
doc.insertString(offset, "\t" + formatter.format(history.getModificationDate()),
|
||||
dateAttrSet);
|
||||
doc.setParagraphAttributes(offset, 1, tabAttrSet, false);
|
||||
|
||||
|
||||
offset = doc.getLength();
|
||||
doc.insertString(offset, "\n"+ history.getComments()+"\n", textAttrSet);
|
||||
doc.insertString(offset, "\n" + history.getComments() + "\n", textAttrSet);
|
||||
}
|
||||
|
||||
|
||||
private void setUpAttributes() {
|
||||
textAttrSet = new SimpleAttributeSet();
|
||||
textAttrSet.addAttribute(StyleConstants.FontFamily, "Monospaced");
|
||||
textAttrSet.addAttribute(StyleConstants.FontSize, new Integer(12));
|
||||
textAttrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(12));
|
||||
textAttrSet.addAttribute(StyleConstants.Foreground, Color.BLUE);
|
||||
|
||||
|
||||
userAttrSet = new SimpleAttributeSet();
|
||||
userAttrSet.addAttribute(StyleConstants.FontFamily, "Tahoma");
|
||||
userAttrSet.addAttribute(StyleConstants.FontSize, new Integer(12));
|
||||
userAttrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(12));
|
||||
userAttrSet.addAttribute(StyleConstants.Bold, Boolean.TRUE);
|
||||
|
||||
dateAttrSet = new SimpleAttributeSet();
|
||||
dateAttrSet.addAttribute(StyleConstants.FontFamily, "Tahoma");
|
||||
dateAttrSet.addAttribute(StyleConstants.FontSize, new Integer(11));
|
||||
dateAttrSet.addAttribute(StyleConstants.FontSize, Integer.valueOf(11));
|
||||
dateAttrSet.addAttribute(StyleConstants.Bold, Boolean.TRUE);
|
||||
dateAttrSet.addAttribute(StyleConstants.Foreground,
|
||||
new Color(124,37,18));
|
||||
|
||||
dateAttrSet.addAttribute(StyleConstants.Foreground, new Color(124, 37, 18));
|
||||
|
||||
tabAttrSet = new SimpleAttributeSet();
|
||||
TabStop tabs = new TabStop(100, StyleConstants.ALIGN_LEFT, TabStop.LEAD_NONE);
|
||||
StyleConstants.setTabSet(tabAttrSet, new TabSet(new TabStop[]{tabs}));
|
||||
StyleConstants.setTabSet(tabAttrSet, new TabSet(new TabStop[] { tabs }));
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -27,6 +27,8 @@ import javax.swing.text.html.HTMLEditorKit;
|
||||
import javax.swing.tree.TreePath;
|
||||
import javax.swing.tree.TreeSelectionModel;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.KeyBindingData;
|
||||
import docking.event.mouse.GMouseListenerAdapter;
|
||||
@ -44,7 +46,8 @@ import ghidra.app.services.ConsoleService;
|
||||
import ghidra.framework.options.SaveState;
|
||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||
import ghidra.program.model.listing.Program;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.HelpLocation;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.datastruct.WeakDataStructureFactory;
|
||||
import ghidra.util.datastruct.WeakSet;
|
||||
import ghidra.util.table.GhidraTableFilterPanel;
|
||||
@ -226,7 +229,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
||||
reader.close();
|
||||
writer.close();
|
||||
|
||||
FileUtilities.copyFile(temp, renameFile, TaskMonitorAdapter.DUMMY_MONITOR);
|
||||
FileUtilities.copyFile(temp, renameFile, TaskMonitor.DUMMY);
|
||||
|
||||
if (!renameFile.exists()) {
|
||||
Msg.showWarn(getClass(), getComponent(), "Unable to rename script",
|
||||
@ -378,8 +381,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
||||
|
||||
checkNewScriptDirectoryEnablement(newFile);
|
||||
|
||||
String category = StringUtilities.convertStringArray(getSelectedCategoryPath(),
|
||||
ScriptInfo.DELIMITTER);
|
||||
String category = StringUtils.join(getSelectedCategoryPath(), ScriptInfo.DELIMITTER);
|
||||
provider.createNewScript(newFile, category);
|
||||
|
||||
GhidraScriptEditorComponentProvider editor =
|
||||
@ -477,7 +479,7 @@ public class GhidraScriptComponentProvider extends ComponentProviderAdapter {
|
||||
tableModel.fireTableDataChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
/*
|
||||
* is more than just root node selected?
|
||||
*/
|
||||
boolean isSelectedCategory() {
|
||||
|
@ -4,9 +4,9 @@
|
||||
* 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.
|
||||
@ -26,9 +26,12 @@ import java.util.regex.Pattern;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.KeyStroke;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import docking.DockingKeyBindingAction;
|
||||
import generic.jar.ResourceFile;
|
||||
import ghidra.util.*;
|
||||
import ghidra.util.HTMLUtilities;
|
||||
import ghidra.util.Msg;
|
||||
import resources.ResourceManager;
|
||||
|
||||
/**
|
||||
@ -305,7 +308,7 @@ public class ScriptInfo {
|
||||
String token = tokenizer.nextToken();
|
||||
|
||||
if (i == tokenCount - 1) { // the key char value is the last element
|
||||
// ...all key character values must be upper case
|
||||
// ...all key character values must be upper case
|
||||
buildy.append(token.toUpperCase());
|
||||
}
|
||||
else {
|
||||
@ -439,11 +442,12 @@ public class ScriptInfo {
|
||||
String htmlAuthor = HTMLUtilities.bold("Author:") + HTML_SPACE +
|
||||
HTMLUtilities.escapeHTML(toToolTip(author));
|
||||
String htmlCategory = HTMLUtilities.bold("Category:") + HTML_SPACE +
|
||||
HTMLUtilities.escapeHTML(toToolTip(StringUtilities.convertStringArray(category, ".")));
|
||||
HTMLUtilities.escapeHTML(toToolTip(StringUtils.join(category, DELIMITTER)));
|
||||
|
||||
String htmlKeyBinding =
|
||||
HTMLUtilities.bold("Key Binding:") + HTML_SPACE + getKeybindingToolTip();
|
||||
String htmlMenuPath = HTMLUtilities.bold("Menu Path:") + HTML_SPACE +
|
||||
HTMLUtilities.escapeHTML(toToolTip(StringUtilities.convertStringArray(menupath, ".")));
|
||||
HTMLUtilities.escapeHTML(toToolTip(StringUtils.join(menupath, DELIMITTER)));
|
||||
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
buffer.append("<h3>").append(HTML_SPACE).append(HTMLUtilities.escapeHTML(getName())).append(
|
||||
|
@ -32,6 +32,8 @@ import javax.swing.JTree;
|
||||
import javax.swing.tree.DefaultTreeCellEditor;
|
||||
import javax.swing.tree.TreePath;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import docking.ActionContext;
|
||||
import docking.action.DockingActionIf;
|
||||
import docking.action.ToggleDockingAction;
|
||||
@ -46,7 +48,6 @@ import ghidra.program.model.data.Undefined1DataType;
|
||||
import ghidra.program.model.listing.*;
|
||||
import ghidra.program.model.symbol.*;
|
||||
import ghidra.test.ToyProgramBuilder;
|
||||
import ghidra.util.StringUtilities;
|
||||
|
||||
/**
|
||||
* Utility class that has common methods needed by the Junit tests.
|
||||
@ -410,8 +411,7 @@ class SymbolTreeTestUtils {
|
||||
if (!rootNode.getName().equals(rootName)) {
|
||||
throw new RuntimeException(
|
||||
"When selecting paths by name the first path element must be the " +
|
||||
"name of the root node - path: " +
|
||||
StringUtilities.convertStringArray(path, "."));
|
||||
"name of the root node - path: " + StringUtils.join(path, '.'));
|
||||
}
|
||||
GTreeNode node = rootNode;
|
||||
for (int i = 1; i < path.length; i++) {
|
||||
|
@ -1989,15 +1989,14 @@ public abstract class AbstractDockingTest extends AbstractGenericTest {
|
||||
if (!rootNode.getName().equals(rootName)) {
|
||||
throw new RuntimeException(
|
||||
"When selecting paths by name the first path element must be the " +
|
||||
"name of the root node - path: " +
|
||||
StringUtilities.convertStringArray(path, "."));
|
||||
"name of the root node - path: " + StringUtils.join(path, '.'));
|
||||
}
|
||||
GTreeNode node = rootNode;
|
||||
for (int i = 1; i < path.length; i++) {
|
||||
GTreeNode child = node.getChild(path[i]);
|
||||
if (child == null) {
|
||||
throw new RuntimeException("Can't find path " +
|
||||
StringUtilities.convertStringArray(path, ".") + " failed at " + path[i]);
|
||||
throw new RuntimeException(
|
||||
"Can't find path " + StringUtils.join(path, '.') + " failed at " + path[i]);
|
||||
}
|
||||
node = child;
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -16,17 +15,17 @@
|
||||
*/
|
||||
package docking.widgets.tree.tasks;
|
||||
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.StringUtilities;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
import javax.swing.JTree;
|
||||
import javax.swing.tree.TreePath;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import docking.widgets.tree.*;
|
||||
import docking.widgets.tree.internal.GTreeSelectionModel;
|
||||
import docking.widgets.tree.support.GTreeSelectionEvent.EventOrigin;
|
||||
import ghidra.util.Msg;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
public class GTreeSelectNodeByNameTask extends GTreeTask {
|
||||
|
||||
@ -49,7 +48,7 @@ public class GTreeSelectNodeByNameTask extends GTreeTask {
|
||||
String rootName = names[0];
|
||||
if (!node.getName().equals(rootName)) {
|
||||
Msg.debug(this, "When selecting paths by name the first path element must be the " +
|
||||
"name of the root node - path: " + StringUtilities.convertStringArray(names, "."));
|
||||
"name of the root node - path: " + StringUtils.join(names, '.'));
|
||||
return;
|
||||
}
|
||||
|
||||
@ -57,10 +56,8 @@ public class GTreeSelectNodeByNameTask extends GTreeTask {
|
||||
monitor.checkCanceled();
|
||||
node = findNodeByName(node, names[i], monitor);
|
||||
if (node == null) {
|
||||
Msg.debug(
|
||||
this,
|
||||
"Could not find node to select - path: " +
|
||||
StringUtilities.convertStringArray(names, "."));
|
||||
Msg.debug(this,
|
||||
"Could not find node to select - path: " + StringUtils.join(names, '.'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -80,17 +77,14 @@ public class GTreeSelectNodeByNameTask extends GTreeTask {
|
||||
}
|
||||
|
||||
private void selectPath(final TreePath treePath, final TaskMonitor monitor) {
|
||||
runOnSwingThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (monitor.isCancelled()) {
|
||||
return; // we can be cancelled while waiting for Swing to run us
|
||||
}
|
||||
|
||||
GTreeSelectionModel selectionModel = tree.getGTSelectionModel();
|
||||
selectionModel.setSelectionPaths(new TreePath[] { treePath }, origin);
|
||||
jTree.scrollPathToVisible(treePath);
|
||||
runOnSwingThread(() -> {
|
||||
if (monitor.isCancelled()) {
|
||||
return; // we can be cancelled while waiting for Swing to run us
|
||||
}
|
||||
|
||||
GTreeSelectionModel selectionModel = tree.getGTSelectionModel();
|
||||
selectionModel.setSelectionPaths(new TreePath[] { treePath }, origin);
|
||||
jTree.scrollPathToVisible(treePath);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,150 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* 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 generic.algorithms;
|
||||
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
import ghidra.util.task.TaskMonitorAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Abstract class for finding the LCS between two sequences of Matchable
|
||||
* objects.
|
||||
*
|
||||
*
|
||||
*
|
||||
* @param <T> the type of the objects being compared.
|
||||
*/
|
||||
public abstract class LCS<T> {
|
||||
private int[][] c;
|
||||
|
||||
/**
|
||||
* Convenient constructor for initializing elements in subclasses
|
||||
*/
|
||||
protected LCS() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the length of the X sequence.
|
||||
*/
|
||||
protected abstract int lengthOfX();
|
||||
|
||||
/**
|
||||
* @return the length of the Y sequence.
|
||||
*/
|
||||
protected abstract int lengthOfY();
|
||||
|
||||
/**
|
||||
* @param index the position of interest in the X sequence.
|
||||
* @return the value in the X sequence at <code>index</code>.
|
||||
* Assumes 1-indexing.
|
||||
*/
|
||||
protected abstract T valueOfX(int index);
|
||||
|
||||
/**
|
||||
* @param index the position of interest in the Y sequence.
|
||||
* @return the value in the Y sequence at <code>index</code>.
|
||||
* Assumes 1-indexing.
|
||||
*/
|
||||
protected abstract T valueOfY(int index);
|
||||
|
||||
/**
|
||||
* @param x the X-sequence element of interest
|
||||
* @param y the Y-sequence element of interest
|
||||
* @return true if <code>x</code> matches <code>y</code>; false otherwise.
|
||||
*/
|
||||
protected abstract boolean matches(T x, T y);
|
||||
|
||||
/**
|
||||
* Compute the LCS
|
||||
* @param monitor
|
||||
*/
|
||||
private void calculateLCS(TaskMonitor monitor) throws CancelledException {
|
||||
if (c != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int[][] tempC = new int[lengthOfX() + 1][];
|
||||
|
||||
monitor.setMessage("Calculating LCS...");
|
||||
monitor.initialize(tempC.length);
|
||||
|
||||
for (int i = 0; i < tempC.length; i++) {
|
||||
// Java int arrays are automatically initialized to 0
|
||||
tempC[i] = new int[lengthOfY() + 1];
|
||||
}
|
||||
|
||||
for (int i = 1; i < tempC.length; i++) {
|
||||
monitor.checkCanceled();
|
||||
for (int j = 1; j < tempC[i].length; j++) {
|
||||
if (matches(valueOfX(i), valueOfY(j))) {
|
||||
tempC[i][j] = tempC[i - 1][j - 1] + 1;
|
||||
}
|
||||
else {
|
||||
tempC[i][j] = Math.max(tempC[i][j - 1], tempC[i - 1][j]);
|
||||
}
|
||||
}
|
||||
monitor.incrementProgress(1);
|
||||
}
|
||||
|
||||
c = tempC;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return a <code>List<T></code> of elements in the LCS.
|
||||
*/
|
||||
public List<T> getLCS() {
|
||||
try {
|
||||
return getLCS(TaskMonitorAdapter.DUMMY_MONITOR);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// can't happen with a dummy monitor
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<T> getLCS(TaskMonitor monitor) throws CancelledException {
|
||||
calculateLCS(monitor);
|
||||
return getLCSHelperIterative(lengthOfX(), lengthOfY());
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterative helper function for getLCS().
|
||||
* @param i the current row index
|
||||
* @param j the current column index
|
||||
* @return the LCS after analyzing element c[i, j].
|
||||
*/
|
||||
private List<T> getLCSHelperIterative(int i, int j) {
|
||||
ArrayList<T> result = new ArrayList<T>();
|
||||
while (i > 0 && j > 0) {
|
||||
if (c[i][j] == c[i - 1][j - 1] + 1 && matches(valueOfX(i), valueOfY(j))) {
|
||||
result.add(0, valueOfX(i));
|
||||
--i;
|
||||
--j;
|
||||
}
|
||||
else if (c[i][j] == c[i - 1][j]) {
|
||||
--i;
|
||||
}
|
||||
else {
|
||||
--j;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,214 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 generic.algorithms;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Abstract class for finding the Longest Common Subsequence (LCS) between two
|
||||
* sequences of Matchable objects, <code>x</code> and <code>y</code>.
|
||||
*
|
||||
* <p>The performance of this algorithm is O(n^2). Thus, large inputs can cause much processor
|
||||
* and memory usage. This class has an upper limit (see {@link #getSizeLimit()}) to prevent
|
||||
* accidental system failure.
|
||||
*
|
||||
* @param <T> the type of the objects being compared
|
||||
*/
|
||||
public abstract class Lcs<T> {
|
||||
|
||||
/**
|
||||
* Somewhat arbitrary upper-bound restriction. 1M is 1000 * 1000
|
||||
*/
|
||||
private static int DEFAULT_SIZE_LIMIT = 1_000_000;
|
||||
private int sizeLimit = DEFAULT_SIZE_LIMIT;
|
||||
|
||||
private int[][] c;
|
||||
|
||||
/**
|
||||
* Returns the length of the x sequence
|
||||
* @return the length of the x sequence
|
||||
*/
|
||||
protected abstract int lengthOfX();
|
||||
|
||||
/**
|
||||
* Returns the length of the y sequence
|
||||
* @return the length of the y sequence
|
||||
*/
|
||||
protected abstract int lengthOfY();
|
||||
|
||||
/**
|
||||
* Gets the value of the x sequence at the given index, where index is 1-based
|
||||
*
|
||||
* @param index the 1-based position of interest in the x sequence
|
||||
* @return the value in the x sequence at <code>index</code>
|
||||
*/
|
||||
protected abstract T valueOfX(int index);
|
||||
|
||||
/**
|
||||
* Gets the value of the y sequence at the given index, where index is 1-based
|
||||
*
|
||||
* @param index the 1-based position of interest in the Y sequence
|
||||
* @return the value in the y sequence at <code>index</code>
|
||||
*/
|
||||
protected abstract T valueOfY(int index);
|
||||
|
||||
/**
|
||||
* Returns true if the value of x and y match
|
||||
*
|
||||
* @param x the x-sequence element of interest
|
||||
* @param y the y-sequence element of interest
|
||||
* @return true if <code>x</code> matches <code>y</code>; false otherwise
|
||||
*/
|
||||
protected abstract boolean matches(T x, T y);
|
||||
|
||||
/**
|
||||
* Compute the LCS
|
||||
* @param monitor the task monitor
|
||||
*/
|
||||
private void calculateLCS(TaskMonitor monitor) throws CancelledException {
|
||||
if (c != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tooBig()) {
|
||||
c = new int[0][0];
|
||||
return;
|
||||
}
|
||||
|
||||
int[][] tempC = new int[lengthOfX() + 1][];
|
||||
|
||||
monitor.setMessage("Calculating LCS...");
|
||||
monitor.initialize(tempC.length);
|
||||
|
||||
// create the zero-initialized matrix
|
||||
for (int i = 0; i < tempC.length; i++) {
|
||||
tempC[i] = new int[lengthOfY() + 1];
|
||||
}
|
||||
|
||||
for (int i = 1; i < tempC.length; i++) {
|
||||
monitor.checkCanceled();
|
||||
for (int j = 1; j < tempC[i].length; j++) {
|
||||
if (matches(valueOfX(i), valueOfY(j))) {
|
||||
tempC[i][j] = tempC[i - 1][j - 1] + 1;
|
||||
}
|
||||
else {
|
||||
tempC[i][j] = Math.max(tempC[i][j - 1], tempC[i - 1][j]);
|
||||
}
|
||||
}
|
||||
monitor.incrementProgress(1);
|
||||
}
|
||||
|
||||
c = tempC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines an limit in the overall size of the inputs that above which no processing will
|
||||
* take place. Any value over the limit will produce an empty LCS.
|
||||
*
|
||||
* @return true if too big
|
||||
*/
|
||||
private boolean tooBig() {
|
||||
return lengthOfX() * lengthOfY() > sizeLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the size limit of this LCS, past which no calculations will be performed
|
||||
*
|
||||
* @param newLimit the new limit
|
||||
*/
|
||||
public void setSizeLimit(int newLimit) {
|
||||
this.sizeLimit = newLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current size limit, past which no calculations will be performed
|
||||
*
|
||||
* @return the size limit
|
||||
* @see #setSizeLimit(int)
|
||||
*/
|
||||
public int getSizeLimit() {
|
||||
return sizeLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of the longest common subsequence. This result will be empty if the
|
||||
* {@link #getSizeLimit()} has been reached.
|
||||
*
|
||||
* @return the list
|
||||
*/
|
||||
public List<T> getLcs() {
|
||||
try {
|
||||
return getLcs(TaskMonitor.DUMMY);
|
||||
}
|
||||
catch (CancelledException e) {
|
||||
// can't happen with a dummy monitor
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of the longest common subsequence. This result will be empty if the
|
||||
* {@link #getSizeLimit()} has been reached.
|
||||
*
|
||||
* @param monitor the task monitor
|
||||
* @return the LCS list
|
||||
* @throws CancelledException if the monitor is cancelled
|
||||
*/
|
||||
public List<T> getLcs(TaskMonitor monitor) throws CancelledException {
|
||||
calculateLCS(monitor);
|
||||
return doGetLcs(monitor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actual LCS based upon the already created matrix
|
||||
*
|
||||
* @param monitor the task monitor
|
||||
* @return the LCS list
|
||||
* @throws CancelledException if the monitor is cancelled
|
||||
*/
|
||||
protected List<T> doGetLcs(TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
|
||||
if (c.length > 0) {
|
||||
x = lengthOfX();
|
||||
y = lengthOfY();
|
||||
}
|
||||
|
||||
List<T> result = new ArrayList<>();
|
||||
while (x > 0 && y > 0) {
|
||||
monitor.checkCanceled();
|
||||
|
||||
if (c[x][y] == c[x - 1][y - 1] + 1 && matches(valueOfX(x), valueOfY(y))) {
|
||||
result.add(0, valueOfX(x));
|
||||
--x;
|
||||
--y;
|
||||
}
|
||||
else if (c[x][y] == c[x - 1][y]) {
|
||||
--x;
|
||||
}
|
||||
else {
|
||||
--y;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,201 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 generic.algorithms;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
/**
|
||||
* Calculates the longest common subsequence (LCS) between two sequences of Matchable
|
||||
* objects, <code>x</code> and <code>y</code>.
|
||||
*
|
||||
* <p>This is an optimizing version of the {@link Lcs} that will pre-calculate all similar
|
||||
* items from the beginning and end of the two given sequences. Doing this will reduce
|
||||
* the size of the matrix created by the parent class, greatly so in the case that the
|
||||
* two inputs are mostly the same in the beginning and end. (Imagine an edit of a source
|
||||
* code file, where the typical change is somewhere in the middle of the file. In this example,
|
||||
* the optimization performed here can greatly decrease the amount of work to be performed when
|
||||
* calculating the LCS.)
|
||||
*
|
||||
* <p>Note: the parent LCS algorithm is bound by {@link #getSizeLimit()}. However, this class
|
||||
* allows clients to work around this restriction when the data has a similar beginning and ending,
|
||||
* as the similar parts will not be counted against the size limit.
|
||||
*
|
||||
* @param <I> The input sequence type
|
||||
* @param <T> the individual element type of the input sequence
|
||||
*/
|
||||
public abstract class ReducingLcs<I, T> extends Lcs<T> {
|
||||
|
||||
private I xSource; // full input x
|
||||
private I ySource; // full input y
|
||||
|
||||
private I x; // the reduced input x
|
||||
private I y; // the reduced input y
|
||||
|
||||
private int startn; // number of beginning same entries
|
||||
private int endn; // number of trailing same entries
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param ix the input sequence <code>x</code>
|
||||
* @param iy the input sequence <code>y</code>
|
||||
*/
|
||||
public ReducingLcs(I ix, I iy) {
|
||||
this.xSource = ix;
|
||||
this.ySource = iy;
|
||||
|
||||
startn = getMatchCountFromStart();
|
||||
endn = getMatchCountFromEnd();
|
||||
int endx = getEnd(xSource);
|
||||
int endy = getEnd(ySource);
|
||||
this.x = reduce(ix, startn, endx);
|
||||
this.y = reduce(iy, startn, endy);
|
||||
}
|
||||
|
||||
private int getEnd(I i) {
|
||||
int end = lengthOf(i) - endn;
|
||||
if (end <= startn) {
|
||||
// boundary condition when the change is only a delete or insert
|
||||
end = startn;
|
||||
}
|
||||
return end;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a subsequence from the given input sequence.
|
||||
*
|
||||
* @param i the input sequence; 0-based (x or y)
|
||||
* @param start the start index; 0-based (inclusive)
|
||||
* @param end the end index (exclusive)
|
||||
* @return the subsequence
|
||||
*/
|
||||
protected abstract I reduce(I i, int start, int end);
|
||||
|
||||
/**
|
||||
* Return the length of the given sequence
|
||||
*
|
||||
* @param i the input sequence (x or y)
|
||||
* @return the length
|
||||
*/
|
||||
protected abstract int lengthOf(I i);
|
||||
|
||||
/**
|
||||
* Return the value at the given 0-based offset
|
||||
*
|
||||
* @param i the input sequence (x or y)
|
||||
* @param offset the offset
|
||||
* @return the value
|
||||
*/
|
||||
protected abstract T valueOf(I i, int offset);
|
||||
|
||||
@Override
|
||||
protected List<T> doGetLcs(TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
List<T> reducedLcs = super.doGetLcs(monitor);
|
||||
int size = reducedLcs.size() + lengthOf(x) + lengthOf(y);
|
||||
List<T> lcs = new ArrayList<>(size);
|
||||
|
||||
// add the shared beginning
|
||||
for (int i = 0; i < startn; i++) {
|
||||
monitor.checkCanceled();
|
||||
lcs.add(valueOf(xSource, i));
|
||||
}
|
||||
|
||||
// add the calculated LCS
|
||||
lcs.addAll(reducedLcs);
|
||||
|
||||
// add the shared end
|
||||
int length = lengthOf(xSource);
|
||||
int endx = getEnd(xSource);
|
||||
for (int i = endx; i < length; i++) {
|
||||
monitor.checkCanceled();
|
||||
lcs.add(valueOf(xSource, i));
|
||||
}
|
||||
|
||||
return lcs;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int lengthOfX() {
|
||||
return lengthOf(x);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int lengthOfY() {
|
||||
return lengthOf(y);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected T valueOfX(int index) {
|
||||
return valueOf(x, index - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected T valueOfY(int index) {
|
||||
return valueOf(y, index - 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean matches(T tx, T ty) {
|
||||
return tx.equals(ty);
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Private Methods
|
||||
//==================================================================================================
|
||||
|
||||
private int getMatchCountFromStart() {
|
||||
|
||||
// scan past the beginning of all equal items
|
||||
int n = 0;
|
||||
int xl = lengthOf(xSource);
|
||||
int yl = lengthOf(ySource);
|
||||
while (n < xl && n < yl) {
|
||||
T xt = valueOf(xSource, n);
|
||||
T yt = valueOf(ySource, n);
|
||||
if (!matches(xt, yt)) {
|
||||
return n;
|
||||
}
|
||||
n++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private int getMatchCountFromEnd() {
|
||||
|
||||
// scan past the trailing equal items
|
||||
int xi = lengthOf(xSource) - 1;
|
||||
int yi = lengthOf(ySource) - 1;
|
||||
|
||||
int n = 0;
|
||||
for (; xi >= 0 && yi >= 0; xi--, yi--) {
|
||||
T xt = valueOf(xSource, xi);
|
||||
T yt = valueOf(ySource, yi);
|
||||
if (!matches(xt, yt)) {
|
||||
return n == 0 ? 0 : n - 1;
|
||||
}
|
||||
n++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 generic.algorithms;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An implementation of the {@link ReducingLcs} that takes as its input a list of <T> items, where
|
||||
* the list is the 'sequence' being checked for the Longest Common Subsequence.
|
||||
*
|
||||
* @param <T> the type of the item in the sequence of items
|
||||
*/
|
||||
public class ReducingListBasedLcs<T> extends ReducingLcs<List<T>, T> {
|
||||
|
||||
public ReducingListBasedLcs(List<T> x, List<T> y) {
|
||||
super(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean matches(T x, T y) {
|
||||
return x.equals(y);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<T> reduce(List<T> i, int start, int end) {
|
||||
return i.subList(start, end);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int lengthOf(List<T> i) {
|
||||
return i.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected T valueOf(List<T> i, int offset) {
|
||||
return i.get(offset);
|
||||
}
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.util;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
/**
|
||||
* Container object that holds a start and end position within a string.
|
||||
* A list of StringDiffs is used to keep track of changes made to a string.
|
||||
*
|
||||
*/
|
||||
public class StringDiff {
|
||||
/**
|
||||
* Start position of the string.
|
||||
*/
|
||||
public int pos1;
|
||||
/**
|
||||
* End position of the string used when part of the string is replaced.
|
||||
*/
|
||||
public int pos2;
|
||||
/**
|
||||
* String being inserted.
|
||||
*/
|
||||
public String insertData;
|
||||
|
||||
/**
|
||||
* Construct a new StringDiff with pos1 and pos2 are initialized to -1.
|
||||
* @param replaceData string
|
||||
*/
|
||||
public StringDiff(String replaceData) {
|
||||
pos1 = -1;
|
||||
pos2 = -1;
|
||||
insertData = replaceData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new StringDiff that indicates text was deleted from
|
||||
* pos1 to pos2.
|
||||
* @param pos1 position 1 for the diff
|
||||
* @param pos2 position 2 for the diff
|
||||
*/
|
||||
public StringDiff(int pos1, int pos2) {
|
||||
this.pos1 = pos1;
|
||||
this.pos2 = pos2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new StringDiff that indicates that insertData was
|
||||
* inserted at pos.
|
||||
* @param pos position where the insertData was inserted
|
||||
* @param insertData inserted string
|
||||
*/
|
||||
public StringDiff(int pos, String insertData) {
|
||||
this.pos1 = pos;
|
||||
this.insertData = insertData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new StringDiff that indicates given data is inserted
|
||||
* from pos1 to pos2.
|
||||
* @param pos1 position 1
|
||||
* @param pos2 position 2
|
||||
* @param data data the replaces string data
|
||||
*/
|
||||
public StringDiff(int pos1, int pos2, String data) {
|
||||
this.pos1 = pos1;
|
||||
this.pos2 = pos2;
|
||||
insertData = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof StringDiff) {
|
||||
StringDiff other = (StringDiff) obj;
|
||||
return pos1 == other.pos1 && pos2 == other.pos2 &&
|
||||
StringUtils.equals(insertData, other.insertData);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (insertData != null) {
|
||||
if (pos1 >= 0) {
|
||||
return "StringDiff: inserted <" + insertData + "> at " + pos1;
|
||||
}
|
||||
|
||||
return "StringDiff: replace with <" + insertData + ">";
|
||||
}
|
||||
return "StringDiff: deleted text from " + pos1 + " to " + pos2;
|
||||
}
|
||||
}
|
@ -138,7 +138,9 @@ public class StringUtilities {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the character is displayable.
|
||||
* Returns true if the character is in displayable character range
|
||||
* @param c the character
|
||||
* @return true if the character is in displayable character range
|
||||
*/
|
||||
public static boolean isDisplayable(int c) {
|
||||
return c >= 0x20 && c < 0x7F;
|
||||
@ -547,33 +549,6 @@ public class StringUtilities {
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string array to single string with new line chars.
|
||||
*/
|
||||
public static String convertStringArray(String[] strings) {
|
||||
return convertStringArray(strings, "\n");
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a string array to single string with the given delimiter.
|
||||
*/
|
||||
public static String convertStringArray(String[] strings, String delimiter) {
|
||||
if (strings == null || strings.length == 0) {
|
||||
return null;
|
||||
}
|
||||
StringBuffer sb = new StringBuffer();
|
||||
for (int i = 0; i < strings.length; i++) {
|
||||
if (strings[i] == null) {
|
||||
continue;
|
||||
}
|
||||
sb.append(strings[i]);
|
||||
if (i < strings.length - 1) {
|
||||
sb.append(delimiter);
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a string containing multiple lines into an array where each
|
||||
* element in the array contains only a single line. The "\n" character is
|
||||
@ -638,6 +613,7 @@ public class StringUtilities {
|
||||
* @param source the original string to pad.
|
||||
* @param filler the type of characters with which to pad
|
||||
* @param length the length of padding to add (0 results in no changes)
|
||||
* @return the padded string
|
||||
* @deprecated use {@link #pad(String, char, int)}; functionally the same, but smaller
|
||||
* and more consistent name
|
||||
*/
|
||||
@ -654,6 +630,7 @@ public class StringUtilities {
|
||||
* @param source the original string to pad.
|
||||
* @param filler the type of characters with which to pad
|
||||
* @param length the length of padding to add (0 results in no changes)
|
||||
* @return the padded string
|
||||
*/
|
||||
public static String pad(String source, char filler, int length) {
|
||||
|
||||
@ -690,6 +667,7 @@ public class StringUtilities {
|
||||
* This is useful for constructing complicated <code>toString()</code> representations.
|
||||
*
|
||||
* @param s the input string
|
||||
* @param indent the indent string; this will be appended as needed
|
||||
* @return the output string
|
||||
*/
|
||||
public static String indentLines(String s, String indent) {
|
||||
|
@ -16,21 +16,18 @@
|
||||
package generic.algorithms;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
import generic.test.AbstractGenericTest;
|
||||
|
||||
public class LCSTest extends AbstractGenericTest {
|
||||
|
||||
public LCSTest() {
|
||||
super();
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIdentical() {
|
||||
compareStrings("DEADBEEF", "DEADBEEF", "DEADBEEF");
|
||||
@ -46,36 +43,53 @@ public class LCSTest extends AbstractGenericTest {
|
||||
"Hooray for really loooooong strings that span multiple lines in java!",
|
||||
"Some really long string that might complicate things." +
|
||||
"Hooray for really long strings that span multiple lines!");
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDifferent() {
|
||||
|
||||
compareStrings("DEAD", "CANND", "AD");
|
||||
compareStrings("DEADBEEFISGOOD", "CANNDBEEFISBAD", "ADBEEFISD");
|
||||
compareStrings("this here is one string", "here a different string is", "here in string");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSizeLimit() {
|
||||
|
||||
String input = "This is more than 5 characters";
|
||||
StringLcs slcs = new StringLcs(input, input);
|
||||
List<Character> lcs = slcs.getLcs();
|
||||
String result = StringUtils.join(lcs, "");
|
||||
assertEquals(input, result);
|
||||
|
||||
slcs = new StringLcs(input, input);
|
||||
slcs.setSizeLimit(10);
|
||||
List<Character> actual = slcs.getLcs();
|
||||
assertTrue(actual.isEmpty());
|
||||
}
|
||||
|
||||
private void compareStrings(String x, String y, String expected) {
|
||||
StringLCS slcs = new StringLCS(x, y);
|
||||
List<Character> actual = slcs.getLCS();
|
||||
|
||||
StringLcs slcs = new StringLcs(x, y);
|
||||
List<Character> actual = slcs.getLcs();
|
||||
|
||||
assertEquals(convertString(expected), actual);
|
||||
}
|
||||
|
||||
private List<Character> convertString(String s) {
|
||||
List<Character> charList = new ArrayList<Character>();
|
||||
for (char c : s.toCharArray())
|
||||
List<Character> charList = new ArrayList<>();
|
||||
for (char c : s.toCharArray()) {
|
||||
charList.add(c);
|
||||
}
|
||||
return charList;
|
||||
}
|
||||
|
||||
private class StringLCS extends LCS<Character> {
|
||||
private class StringLcs extends Lcs<Character> {
|
||||
|
||||
private String x;
|
||||
private String y;
|
||||
|
||||
public StringLCS(String x, String y) {
|
||||
public StringLcs(String x, String y) {
|
||||
super();
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
@ -105,6 +119,5 @@ public class LCSTest extends AbstractGenericTest {
|
||||
protected Character valueOfY(int index) {
|
||||
return y.charAt(index - 1);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,124 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 generic.algorithms;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
public class ReducingLCSTest {
|
||||
|
||||
@Test
|
||||
public void testIdentical() {
|
||||
compareStrings("DEADBEEF", "DEADBEEF", "DEADBEEF");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSimilar() {
|
||||
compareStrings("DEADBEEF", "DEEDBEAD", "DEDBE");
|
||||
compareStrings(
|
||||
"Some really long string that might complicate things." +
|
||||
"Hooray for really long strings that span multiple lines!",
|
||||
"Some other really long string that might complicate things." +
|
||||
"Hooray for really loooooong strings that span multiple lines in java!",
|
||||
"Some really long string that might complicate things." +
|
||||
"Hooray for really long strings that span multiple lines!");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDifferent() {
|
||||
|
||||
compareStrings("DEAD", "CANND", "AD");
|
||||
compareStrings("DEADBEEFISGOOD", "CANNDBEEFISBAD", "ADBEEFISD");
|
||||
compareStrings("this here is one string", "here a different string is", "here in string");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInsertOnly() {
|
||||
|
||||
String x = "Line not modified";
|
||||
String y = "Line not not modified";
|
||||
compareStrings(x, y, x);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemovalOnly() {
|
||||
|
||||
String x = "Line not modified";
|
||||
String y = "Line modified";
|
||||
compareStrings(x, y, y);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSizeLimit() {
|
||||
|
||||
String x = "This is a line that has not been modified";
|
||||
String y = "This is a line that has been modified";
|
||||
|
||||
StringLcs slcs = new StringLcs(x, y);
|
||||
slcs.setSizeLimit(10);
|
||||
List<Character> lcs = slcs.getLcs();
|
||||
String result = StringUtils.join(lcs, "");
|
||||
assertEquals(y, result); // 'y' is common, since it is 'x', with only a delete
|
||||
|
||||
String z = "Start Mod " + x + " End Mod"; // same as 'x', but with different start/end
|
||||
slcs = new StringLcs(x, z);
|
||||
slcs.setSizeLimit(10);
|
||||
List<Character> actual = slcs.getLcs();
|
||||
assertTrue(actual.isEmpty());
|
||||
}
|
||||
|
||||
private void compareStrings(String x, String y, String expected) {
|
||||
StringLcs slcs = new StringLcs(x, y);
|
||||
List<Character> actual = slcs.getLcs();
|
||||
assertEquals(convertString(expected), actual);
|
||||
}
|
||||
|
||||
private List<Character> convertString(String s) {
|
||||
List<Character> charList = new ArrayList<>();
|
||||
for (char c : s.toCharArray()) {
|
||||
charList.add(c);
|
||||
}
|
||||
return charList;
|
||||
}
|
||||
|
||||
private class StringLcs extends ReducingLcs<String, Character> {
|
||||
|
||||
public StringLcs(String x, String y) {
|
||||
super(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String reduce(String input, int start, int end) {
|
||||
return input.substring(start, end);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int lengthOf(String s) {
|
||||
return s.length();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Character valueOf(String s, int offset) {
|
||||
return s.charAt(offset);
|
||||
}
|
||||
}
|
||||
}
|
@ -3365,12 +3365,15 @@ public class CodeManager implements ErrorHandler, ManagerDB {
|
||||
if (newComment == null) {
|
||||
newComment = "";
|
||||
}
|
||||
StringDiff[] diffs = getLineDiffs(newComment, oldComment);
|
||||
|
||||
StringDiff[] diffs = StringDiffUtils.getLineDiffs(newComment, oldComment);
|
||||
|
||||
long date = System.currentTimeMillis();
|
||||
long addr = addrMap.getKey(address, true);
|
||||
try {
|
||||
for (StringDiff diff : diffs) {
|
||||
historyAdapter.createRecord(addr, (byte) commentType, diff.pos1, diff.pos2,
|
||||
diff.insertData);
|
||||
historyAdapter.createRecord(addr, (byte) commentType, diff.start, diff.end,
|
||||
diff.text, date);
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
@ -3379,7 +3382,8 @@ public class CodeManager implements ErrorHandler, ManagerDB {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the comment history for the comment type at the given address.
|
||||
* Get the comment history for the comment type at the given address
|
||||
*
|
||||
* @param addr address for the comment history
|
||||
* @param commentType comment type
|
||||
* @return zero length array if no history exists
|
||||
@ -3387,47 +3391,37 @@ public class CodeManager implements ErrorHandler, ManagerDB {
|
||||
public CommentHistory[] getCommentHistory(Address addr, int commentType) {
|
||||
lock.acquire();
|
||||
try {
|
||||
RecordIterator iter = historyAdapter.getRecordsByAddress(addr);
|
||||
|
||||
List<Record> list = new ArrayList<>();
|
||||
while (iter.hasNext()) {
|
||||
Record rec = iter.next();
|
||||
// records are sorted by date ascending
|
||||
List<Record> allRecords = getHistoryRecords(addr, commentType);
|
||||
|
||||
if (rec.getByteValue(CommentHistoryAdapter.HISTORY_TYPE_COL) == commentType) {
|
||||
list.add(rec);
|
||||
}
|
||||
}
|
||||
List<CommentHistory> historyList = new ArrayList<>(); // CommentHistory objects
|
||||
String comments = getComments(addr, commentType);
|
||||
while (list.size() > 0) {
|
||||
Record rec = list.get(list.size() - 1);
|
||||
List<CommentHistory> results = new ArrayList<>();
|
||||
String comment = getComment(addr, commentType);
|
||||
while (!allRecords.isEmpty()) {
|
||||
|
||||
Record rec = allRecords.get(allRecords.size() - 1);
|
||||
long date = rec.getLongValue(CommentHistoryAdapter.HISTORY_DATE_COL);
|
||||
List<Record> subList = findHistoryRecords(date, list);
|
||||
StringDiff[] diffs = new StringDiff[subList.size()];
|
||||
List<Record> records = subListByDate(allRecords, date);
|
||||
|
||||
String userName = null;
|
||||
Date modDate = null;
|
||||
for (int j = 0; j < subList.size(); j++) {
|
||||
Record r = subList.get(j);
|
||||
userName = r.getString(CommentHistoryAdapter.HISTORY_USER_COL);
|
||||
modDate = new Date(r.getLongValue(CommentHistoryAdapter.HISTORY_DATE_COL));
|
||||
List<StringDiff> diffs = new ArrayList<>(records.size());
|
||||
|
||||
diffs[j] = new StringDiff(r.getIntValue(CommentHistoryAdapter.HISTORY_POS1_COL),
|
||||
r.getIntValue(CommentHistoryAdapter.HISTORY_POS2_COL),
|
||||
r.getString(CommentHistoryAdapter.HISTORY_STRING_COL));
|
||||
String user = null;
|
||||
for (Record r : records) {
|
||||
user = r.getString(CommentHistoryAdapter.HISTORY_USER_COL);
|
||||
int pos1 = r.getIntValue(CommentHistoryAdapter.HISTORY_POS1_COL);
|
||||
int pos2 = r.getIntValue(CommentHistoryAdapter.HISTORY_POS2_COL);
|
||||
String data = r.getString(CommentHistoryAdapter.HISTORY_STRING_COL);
|
||||
diffs.add(StringDiff.restore(data, pos1, pos2));
|
||||
}
|
||||
if (comments == null) {
|
||||
comments = "";
|
||||
}
|
||||
historyList.add(new CommentHistory(addr, commentType, userName, comments, modDate));
|
||||
comments = applyDiffs(comments, diffs);
|
||||
|
||||
int from = list.size() - subList.size();
|
||||
// remove the subList elements from the list
|
||||
list.subList(from, list.size()).clear();
|
||||
results.add(new CommentHistory(addr, commentType, user, comment, new Date(date)));
|
||||
comment = StringDiffUtils.applyDiffs(comment, diffs);
|
||||
|
||||
records.clear(); // remove the subList elements from the list
|
||||
}
|
||||
CommentHistory[] h = new CommentHistory[historyList.size()];
|
||||
return historyList.toArray(h);
|
||||
|
||||
CommentHistory[] h = new CommentHistory[results.size()];
|
||||
return results.toArray(h);
|
||||
}
|
||||
catch (IOException e) {
|
||||
dbError(e);
|
||||
@ -3438,23 +3432,39 @@ public class CodeManager implements ErrorHandler, ManagerDB {
|
||||
return new CommentHistory[0];
|
||||
}
|
||||
|
||||
private List<Record> findHistoryRecords(long date, List<Record> recList) {
|
||||
int i;
|
||||
for (i = recList.size() - 1; i >= 0; i--) {
|
||||
Record rec = recList.get(i);
|
||||
if (date != rec.getLongValue(CommentHistoryAdapter.HISTORY_DATE_COL)) {
|
||||
break;
|
||||
// note: you must have the lock when calling this method
|
||||
private List<Record> getHistoryRecords(Address addr, int commentType) throws IOException {
|
||||
RecordIterator it = historyAdapter.getRecordsByAddress(addr);
|
||||
List<Record> list = new ArrayList<>();
|
||||
while (it.hasNext()) {
|
||||
Record rec = it.next();
|
||||
if (rec.getByteValue(CommentHistoryAdapter.HISTORY_TYPE_COL) == commentType) {
|
||||
list.add(rec);
|
||||
}
|
||||
}
|
||||
return recList.subList(i + 1, recList.size());
|
||||
return list;
|
||||
}
|
||||
|
||||
private String getComments(Address addr, int commentType) throws IOException {
|
||||
// note: records are sorted by date; assume that the date we seek is at the end
|
||||
private List<Record> subListByDate(List<Record> records, long date) {
|
||||
|
||||
for (int i = records.size() - 1; i >= 0; i--) {
|
||||
Record rec = records.get(i);
|
||||
if (date != rec.getLongValue(CommentHistoryAdapter.HISTORY_DATE_COL)) {
|
||||
return records.subList(i + 1, records.size());
|
||||
}
|
||||
}
|
||||
|
||||
// all records have the same date
|
||||
return records.subList(0, records.size());
|
||||
}
|
||||
|
||||
private String getComment(Address addr, int commentType) throws IOException {
|
||||
Record record = commentAdapter.getRecord(addrMap.getKey(addr, false));
|
||||
if (record != null) {
|
||||
return record.getString(commentType);
|
||||
}
|
||||
return null;
|
||||
return "";
|
||||
}
|
||||
|
||||
public void replaceDataTypes(long oldDataTypeID, long newDataTypeID) {
|
||||
@ -3631,181 +3641,4 @@ public class CodeManager implements ErrorHandler, ManagerDB {
|
||||
return protoMgr.getPrototype(protoID);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of StringDiff objects that if applied to s1 would result in s2; The
|
||||
* given text will look only for whole lines using '\n'.
|
||||
*
|
||||
* @param s1 the original string
|
||||
* @param s2 the result string
|
||||
* this value, then a completely different string will be returned
|
||||
* @return an array of StringDiff objects that change s1 into s2;
|
||||
*/
|
||||
private static StringDiff[] getLineDiffs(String s1, String s2) {
|
||||
|
||||
/**
|
||||
* Minimum size used to determine whether a new StringDiff object will be
|
||||
* created just using a string (no positions)
|
||||
* in the <code>getDiffs(String, String)</code> method.
|
||||
* @see #getLineDiffs(String, String)
|
||||
*/
|
||||
int MINIMUM_DIFF_SIZE = 100;
|
||||
return getLineDiffs(s1, s2, MINIMUM_DIFF_SIZE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of StringDiff objects that if applied to s1 would result in s2; The
|
||||
* given text will look only for whole lines using '\n'.
|
||||
*
|
||||
* @param s1 the original string
|
||||
* @param s2 the result string
|
||||
* @param minimumDiffSize the minimum length of s2 required for a diff; if s2 is less than
|
||||
* this value, then a completely different string will be returned
|
||||
* @return an array of StringDiff objects that change s1 into s2;
|
||||
*/
|
||||
private static StringDiff[] getLineDiffs(String s1, String s2, int minimumDiffSize) {
|
||||
if (s2.length() < minimumDiffSize) {
|
||||
return new StringDiff[] { new StringDiff(s2) };
|
||||
}
|
||||
|
||||
List<StringDiff> list = new LinkedList<>();
|
||||
int pos1 = 0;
|
||||
int pos2 = 0;
|
||||
int len1 = s1.length();
|
||||
int len2 = s2.length();
|
||||
int origPos;
|
||||
|
||||
while (pos1 < len1 || pos2 < len2) {
|
||||
String line1 = getLine(s1, pos1);
|
||||
String line2 = getLine(s2, pos2);
|
||||
if (line1.equals(line2)) {
|
||||
pos1 += line1.length();
|
||||
pos2 += line2.length();
|
||||
continue;
|
||||
}
|
||||
int posInOther1 = findLine(s2, pos2, line1);
|
||||
origPos = pos1;
|
||||
while (posInOther1 < 0) {
|
||||
pos1 += line1.length();
|
||||
line1 = getLine(s1, pos1);
|
||||
posInOther1 = findLine(s2, pos2, line1);
|
||||
}
|
||||
if (pos1 > origPos) {
|
||||
list.add(new StringDiff(origPos, pos1));
|
||||
}
|
||||
int posInOther2 = findLine(s1, pos1, line2);
|
||||
origPos = pos2;
|
||||
while (posInOther2 < 0) {
|
||||
pos2 += line2.length();
|
||||
line2 = getLine(s2, pos2);
|
||||
posInOther2 = findLine(s1, pos1, line2);
|
||||
}
|
||||
if (pos2 > origPos) {
|
||||
list.add(new StringDiff(pos1, s2.substring(origPos, pos2)));
|
||||
continue;
|
||||
}
|
||||
int advance1 = posInOther2 - pos1;
|
||||
int advance2 = posInOther1 - pos2;
|
||||
if (advance1 > advance2) {
|
||||
list.add(new StringDiff(pos1, s2.substring(pos2, posInOther1)));
|
||||
pos2 = posInOther1;
|
||||
}
|
||||
else if (advance2 > advance1) {
|
||||
list.add(new StringDiff(pos1, posInOther2));
|
||||
pos1 = posInOther2;
|
||||
}
|
||||
}
|
||||
return list.toArray(new StringDiff[list.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a position in s that contains the string line. The matching string in
|
||||
* s must be a "complete" line, in other words if pos > 0 then s.charAt(index-1) must be
|
||||
* a newLine character and s.charAt(index+line.length()) must be a newLine or the end of
|
||||
* the string.
|
||||
* @param s the string to scan
|
||||
* @param pos the position to begin the scan.
|
||||
* @param line the line to scan for
|
||||
* @return the position in s containing the line string.
|
||||
*/
|
||||
private static int findLine(String s, int pos, String line) {
|
||||
if (line.length() == 0) {
|
||||
return pos;
|
||||
}
|
||||
while (true) {
|
||||
int index = s.indexOf(line, pos);
|
||||
if (index < 0) {
|
||||
return index;
|
||||
}
|
||||
if (index > 0 && s.charAt(index - 1) != '\n') {
|
||||
pos = index + line.length();
|
||||
continue;
|
||||
}
|
||||
if (line.endsWith("\n")) {
|
||||
return index;
|
||||
}
|
||||
if (index + line.length() == s.length()) {
|
||||
return index;
|
||||
}
|
||||
pos = index + line.length();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a substring of s beginning at start and ending at either the end of the string or
|
||||
* the first newLine at or after start.
|
||||
* @param s the string to scan
|
||||
* @param start the starting position for the scan
|
||||
* @return A string that represents a line within s.
|
||||
*/
|
||||
public static String getLine(String s, int start) {
|
||||
int n = s.length();
|
||||
if (start >= n) {
|
||||
return "";
|
||||
}
|
||||
int pos = start;
|
||||
while (pos < n && s.charAt(pos) != '\n') {
|
||||
pos++;
|
||||
}
|
||||
if (pos < n) {
|
||||
pos++;
|
||||
}
|
||||
return s.substring(start, pos);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the array of StringObjects to the string s to produce a new string. Warning - the
|
||||
* diff objects cannot be applied to an arbitrary string, the String s must be the original
|
||||
* String used to compute the diffs.
|
||||
* @param s the original string
|
||||
* @param diffs the array of StringDiff object to apply
|
||||
* @return a new String resulting from applying the diffs to s.
|
||||
*/
|
||||
private static String applyDiffs(String s, StringDiff[] diffs) {
|
||||
if (diffs.length == 0) {
|
||||
return s;
|
||||
}
|
||||
if (diffs[0].pos1 < 0) {
|
||||
return diffs[0].insertData;
|
||||
}
|
||||
StringBuffer buf = new StringBuffer(s.length());
|
||||
int pos = 0;
|
||||
|
||||
for (StringDiff element : diffs) {
|
||||
if (element.pos1 > pos) {
|
||||
buf.append(s.substring(pos, element.pos1));
|
||||
pos = element.pos1;
|
||||
}
|
||||
if (element.insertData != null) {
|
||||
buf.append(element.insertData);
|
||||
}
|
||||
else {
|
||||
pos = element.pos2;
|
||||
}
|
||||
}
|
||||
if (pos < s.length()) {
|
||||
buf.append(s.substring(pos));
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -20,6 +20,8 @@ import java.math.BigInteger;
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.Iterator;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import db.Record;
|
||||
import ghidra.program.database.*;
|
||||
import ghidra.program.model.address.Address;
|
||||
@ -512,7 +514,7 @@ abstract class CodeUnitDB extends DatabaseObject implements CodeUnit, ProcessorC
|
||||
|
||||
@Override
|
||||
public void setCommentAsArray(int commentType, String[] comment) {
|
||||
setComment(commentType, StringUtilities.convertStringArray(comment));
|
||||
setComment(commentType, StringUtils.join(comment, '\n'));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -16,16 +15,15 @@
|
||||
*/
|
||||
package ghidra.program.database.code;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import db.*;
|
||||
import ghidra.program.database.map.AddressMap;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.util.exception.CancelledException;
|
||||
import ghidra.util.exception.VersionException;
|
||||
import ghidra.util.task.TaskMonitor;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import db.*;
|
||||
|
||||
/**
|
||||
* Adapter for accessing records in the CommentHistory table.
|
||||
*/
|
||||
@ -33,10 +31,10 @@ abstract class CommentHistoryAdapter {
|
||||
|
||||
static final String COMMENT_HISTORY_TABLE_NAME = "Comment History";
|
||||
|
||||
static final Schema COMMENT_HISTORY_SCHEMA = new Schema(0, "Key", new Class[] {
|
||||
LongField.class, ByteField.class, IntField.class, IntField.class, StringField.class,
|
||||
StringField.class, LongField.class }, new String[] { "Address", "Comment Type", "Pos1",
|
||||
"Pos2", "String Data", "User", "Date" });
|
||||
static final Schema COMMENT_HISTORY_SCHEMA = new Schema(0, "Key",
|
||||
new Class[] { LongField.class, ByteField.class, IntField.class, IntField.class,
|
||||
StringField.class, StringField.class, LongField.class },
|
||||
new String[] { "Address", "Comment Type", "Pos1", "Pos2", "String Data", "User", "Date" });
|
||||
|
||||
static final int HISTORY_ADDRESS_COL = 0;
|
||||
static final int HISTORY_TYPE_COL = 1;
|
||||
@ -79,14 +77,15 @@ abstract class CommentHistoryAdapter {
|
||||
return new CommentHistoryAdapterV0(handle, addrMap.getOldAddressMap(), false);
|
||||
}
|
||||
catch (VersionException e) {
|
||||
// use the 'no table' below
|
||||
}
|
||||
|
||||
return new CommentHistoryAdapterNoTable();
|
||||
}
|
||||
|
||||
private static CommentHistoryAdapter upgrade(DBHandle dbHandle, AddressMap addrMap,
|
||||
CommentHistoryAdapter oldAdapter, TaskMonitor monitor) throws VersionException,
|
||||
IOException, CancelledException {
|
||||
CommentHistoryAdapter oldAdapter, TaskMonitor monitor)
|
||||
throws VersionException, IOException, CancelledException {
|
||||
|
||||
AddressMap oldAddrMap = addrMap.getOldAddressMap();
|
||||
|
||||
@ -128,7 +127,8 @@ abstract class CommentHistoryAdapter {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns record count
|
||||
* Returns the record count
|
||||
* @return the record count
|
||||
*/
|
||||
abstract int getRecordCount();
|
||||
|
||||
@ -139,15 +139,16 @@ abstract class CommentHistoryAdapter {
|
||||
* @param pos1 position 1 of change
|
||||
* @param pos2 position 2 of change
|
||||
* @param data string from the comment change
|
||||
* @throws IOException
|
||||
* @param date the date of the history entry
|
||||
* @throws IOException if there was a problem accessing the database
|
||||
*/
|
||||
abstract void createRecord(long addr, byte commentType, int pos1, int pos2, String data)
|
||||
throws IOException;
|
||||
abstract void createRecord(long addr, byte commentType, int pos1, int pos2, String data,
|
||||
long date) throws IOException;
|
||||
|
||||
/**
|
||||
* Update record
|
||||
* @param rec
|
||||
* @throws IOException
|
||||
* @param rec the record to update
|
||||
* @throws IOException if there was a problem accessing the database
|
||||
*/
|
||||
abstract void updateRecord(Record rec) throws IOException;
|
||||
|
||||
@ -162,12 +163,15 @@ abstract class CommentHistoryAdapter {
|
||||
|
||||
/**
|
||||
* Get an iterator over records with the given address.
|
||||
* @param addr the address for which to get records
|
||||
* @return the iterator
|
||||
* @throws IOException if there was a problem accessing the database
|
||||
*/
|
||||
abstract RecordIterator getRecordsByAddress(Address addr) throws IOException;
|
||||
|
||||
/**
|
||||
* Get an iterator over all records.
|
||||
* Get an iterator over all records
|
||||
* @return the iterator
|
||||
* @throws IOException if there was a problem accessing the database
|
||||
*/
|
||||
abstract RecordIterator getAllRecords() throws IOException;
|
||||
|
@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -16,13 +15,12 @@
|
||||
*/
|
||||
package ghidra.program.database.code;
|
||||
|
||||
import ghidra.program.database.util.EmptyRecordIterator;
|
||||
import ghidra.program.model.address.Address;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import db.Record;
|
||||
import db.RecordIterator;
|
||||
import ghidra.program.database.util.EmptyRecordIterator;
|
||||
import ghidra.program.model.address.Address;
|
||||
|
||||
/**
|
||||
* Adapter needed for a read-only version of Program that is not going
|
||||
@ -30,53 +28,34 @@ import db.RecordIterator;
|
||||
*/
|
||||
class CommentHistoryAdapterNoTable extends CommentHistoryAdapter {
|
||||
|
||||
/* (non Javadoc)
|
||||
* @see ghidra.program.database.code.CommentHistoryAdapter#createRecord(long, byte, int, int, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public void createRecord(long addr, byte commentType, int pos1, int pos2, String data)
|
||||
throws IOException {
|
||||
public void createRecord(long addr, byte commentType, int pos1, int pos2, String data,
|
||||
long date) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ghidra.program.database.code.CommentHistoryAdapter#getRecordsByAddress(long)
|
||||
*/
|
||||
@Override
|
||||
public RecordIterator getRecordsByAddress(Address addr) throws IOException {
|
||||
return new EmptyRecordIterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ghidra.program.database.code.CommentHistoryAdapter#getAllRecords()
|
||||
*/
|
||||
@Override
|
||||
public RecordIterator getAllRecords() throws IOException {
|
||||
return new EmptyRecordIterator();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ghidra.program.database.code.CommentHistoryAdapter#updateRecord(db.Record)
|
||||
*/
|
||||
@Override
|
||||
void updateRecord(Record rec) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ghidra.program.database.code.CommentHistoryAdapter#deleteRecords(ghidra.program.model.address.Address, ghidra.program.model.address.Address)
|
||||
*/
|
||||
@Override
|
||||
boolean deleteRecords(Address start, Address end) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ghidra.program.database.code.CommentHistoryAdapter#getRecordCount()
|
||||
*/
|
||||
@Override
|
||||
int getRecordCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -19,13 +19,12 @@ import java.io.IOException;
|
||||
|
||||
import db.*;
|
||||
import ghidra.program.database.map.*;
|
||||
import ghidra.program.database.util.DatabaseVersionException;
|
||||
import ghidra.program.model.address.Address;
|
||||
import ghidra.util.SystemUtilities;
|
||||
import ghidra.util.exception.VersionException;
|
||||
|
||||
/**
|
||||
* Adapter for Version 0 of the Comment History table.
|
||||
* Adapter for Version 0 of the Comment History table
|
||||
*/
|
||||
class CommentHistoryAdapterV0 extends CommentHistoryAdapter {
|
||||
|
||||
@ -36,16 +35,17 @@ class CommentHistoryAdapterV0 extends CommentHistoryAdapter {
|
||||
/**
|
||||
* Construct a new Version 0 comment history adapter.
|
||||
* @param handle database handle
|
||||
* @throws DatabaseVersionException if the table was not found
|
||||
* @param addrMap the address map used to generate keys for addresses
|
||||
* @param create true if to create a new table; false to load an existing table
|
||||
* @throws VersionException if the table was not found
|
||||
* @throws IOException if an error occurred while accessing the database
|
||||
*/
|
||||
CommentHistoryAdapterV0(DBHandle handle, AddressMap addrMap, boolean create)
|
||||
throws VersionException, IOException {
|
||||
this.addrMap = addrMap;
|
||||
if (create) {
|
||||
table =
|
||||
handle.createTable(COMMENT_HISTORY_TABLE_NAME, COMMENT_HISTORY_SCHEMA,
|
||||
new int[] { HISTORY_ADDRESS_COL });
|
||||
table = handle.createTable(COMMENT_HISTORY_TABLE_NAME, COMMENT_HISTORY_SCHEMA,
|
||||
new int[] { HISTORY_ADDRESS_COL });
|
||||
}
|
||||
else {
|
||||
table = handle.getTable(COMMENT_HISTORY_TABLE_NAME);
|
||||
@ -59,11 +59,8 @@ class CommentHistoryAdapterV0 extends CommentHistoryAdapter {
|
||||
userName = SystemUtilities.getUserName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ghidra.program.database.code.CommentHistoryAdapter#createRecord(long, byte, int, int, java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
void createRecord(long addr, byte commentType, int pos1, int pos2, String data)
|
||||
void createRecord(long addr, byte commentType, int pos1, int pos2, String data, long date)
|
||||
throws IOException {
|
||||
|
||||
Record rec = table.getSchema().createRecord(table.getKey());
|
||||
@ -73,47 +70,32 @@ class CommentHistoryAdapterV0 extends CommentHistoryAdapter {
|
||||
rec.setIntValue(HISTORY_POS2_COL, pos2);
|
||||
rec.setString(HISTORY_STRING_COL, data);
|
||||
rec.setString(HISTORY_USER_COL, userName);
|
||||
rec.setLongValue(HISTORY_DATE_COL, System.currentTimeMillis() ); //new Date().getTime());
|
||||
rec.setLongValue(HISTORY_DATE_COL, date);
|
||||
|
||||
table.putRecord(rec);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ghidra.program.database.code.CommentHistoryAdapter#getRecordsByAddress(long)
|
||||
*/
|
||||
@Override
|
||||
RecordIterator getRecordsByAddress(Address address) throws IOException {
|
||||
LongField field = new LongField(addrMap.getKey(address, false));
|
||||
return table.indexIterator(HISTORY_ADDRESS_COL, field, field, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ghidra.program.database.code.CommentHistoryAdapter#getAllRecords()
|
||||
*/
|
||||
@Override
|
||||
RecordIterator getAllRecords() throws IOException {
|
||||
return new AddressKeyRecordIterator(table, addrMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ghidra.program.database.code.CommentHistoryAdapter#updateRecord(db.Record)
|
||||
*/
|
||||
@Override
|
||||
void updateRecord(Record rec) throws IOException {
|
||||
table.putRecord(rec);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ghidra.program.database.code.CommentHistoryAdapter#deleteRecords(ghidra.program.model.address.Address, ghidra.program.model.address.Address)
|
||||
*/
|
||||
@Override
|
||||
boolean deleteRecords(Address start, Address end) throws IOException {
|
||||
return AddressRecordDeleter.deleteRecords(table, addrMap, start, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ghidra.program.database.code.CommentHistoryAdapter#getRecordCount()
|
||||
*/
|
||||
@Override
|
||||
int getRecordCount() {
|
||||
return table.getRecordCount();
|
||||
|
@ -0,0 +1,132 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.program.database.code;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
|
||||
/**
|
||||
* Container object that holds a start and end position within a string. A list of StringDiffs
|
||||
* is used to keep track of changes made to a string.
|
||||
*/
|
||||
public class StringDiff {
|
||||
|
||||
/**
|
||||
* Start position of the string used when text is inserted or replaced
|
||||
*/
|
||||
int start;
|
||||
|
||||
/**
|
||||
* End position of the string used when part of the string is replaced
|
||||
*/
|
||||
int end;
|
||||
|
||||
/**
|
||||
* String being inserted. This can be an insert or a complete replace (the positions will both
|
||||
* be -1 in a replace; pos1 will be non-negative during an insert).
|
||||
*/
|
||||
public String text;
|
||||
|
||||
/**
|
||||
* Construct a new StringDiff with pos1 and pos2 are initialized to -1
|
||||
*
|
||||
* @param newText string
|
||||
* @return the new diff
|
||||
*/
|
||||
public static StringDiff allTextReplaced(String newText) {
|
||||
return new StringDiff(-1, -1, newText);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new StringDiff that indicates text was deleted from pos1 to pos2
|
||||
*
|
||||
* @param start position 1 for the diff
|
||||
* @param end position 2 for the diff
|
||||
* @return the new diff
|
||||
*/
|
||||
public static StringDiff textDeleted(int start, int end) {
|
||||
return new StringDiff(start, end, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct a new StringDiff that indicates that insertData was inserted at the given position
|
||||
*
|
||||
* @param newText inserted string
|
||||
* @param start position where the text was inserted
|
||||
* @return the new diff
|
||||
*/
|
||||
public static StringDiff textInserted(String newText, int start) {
|
||||
return new StringDiff(start, -1, newText);
|
||||
}
|
||||
|
||||
// for restoring from saved record
|
||||
public static StringDiff restore(String text, int start, int end) {
|
||||
return new StringDiff(start, end, text);
|
||||
}
|
||||
|
||||
private StringDiff(int start, int end, String text) {
|
||||
this.start = start;
|
||||
this.end = end;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + ((text == null) ? 0 : text.hashCode());
|
||||
result = prime * result + start;
|
||||
result = prime * result + end;
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
StringDiff other = (StringDiff) obj;
|
||||
if (!Objects.equal(text, other.text)) {
|
||||
return false;
|
||||
}
|
||||
if (start != other.start) {
|
||||
return false;
|
||||
}
|
||||
if (end != other.end) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (text != null) {
|
||||
if (start >= 0) {
|
||||
return "StringDiff: inserted <" + text + "> at " + start;
|
||||
}
|
||||
|
||||
return "StringDiff: replace with <" + text + ">";
|
||||
}
|
||||
return "StringDiff: deleted text from " + start + " to " + end;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,351 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.program.database.code;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import generic.algorithms.ReducingListBasedLcs;
|
||||
|
||||
class StringDiffUtils {
|
||||
|
||||
/**
|
||||
* Minimum size used to determine whether a new StringDiff object will be
|
||||
* created just using a string (no positions)
|
||||
* in the <code>getDiffs(String, String)</code> method.
|
||||
* @see #getLineDiffs(String, String)
|
||||
*/
|
||||
private static int MINIMUM_DIFF_SIZE = 100;
|
||||
|
||||
/**
|
||||
* Returns the list of StringDiff objects that if applied to s1 would result in s2; The
|
||||
* given text will look only for whole lines using '\n'.
|
||||
*
|
||||
* @param s1 the original string
|
||||
* @param s2 the result string
|
||||
* this value, then a completely different string will be returned
|
||||
* @return an array of StringDiff objects that change s1 into s2;
|
||||
*/
|
||||
static StringDiff[] getLineDiffs(String s1, String s2) {
|
||||
return getLineDiffs(s1, s2, MINIMUM_DIFF_SIZE);
|
||||
}
|
||||
|
||||
static StringDiff[] getLineDiffs(String s1, String s2, int minimumDiffSize) {
|
||||
if (s2.length() < minimumDiffSize) {
|
||||
return new StringDiff[] { StringDiff.allTextReplaced(s2) };
|
||||
}
|
||||
|
||||
List<Line> aList = split(s1);
|
||||
List<Line> bList = split(s2);
|
||||
LineLcs lcs = new LineLcs(aList, bList);
|
||||
List<Line> commons = lcs.getLcs();
|
||||
if (commons.isEmpty()) {
|
||||
// no common text--complete replacement
|
||||
return new StringDiff[] { StringDiff.allTextReplaced(s2) };
|
||||
}
|
||||
|
||||
int aIndex = 0;
|
||||
int bIndex = 0;
|
||||
int aLastIndex = 0;
|
||||
int bLastIndex = 0;
|
||||
List<StringDiff> results = new LinkedList<>();
|
||||
for (Line common : commons) {
|
||||
|
||||
aIndex = indexOf(aList, common, aLastIndex);
|
||||
bIndex = indexOf(bList, common, bLastIndex);
|
||||
|
||||
int aDelta = aIndex - aLastIndex;
|
||||
int bDelta = bIndex - bLastIndex;
|
||||
|
||||
int aEnd = aIndex;
|
||||
int aStart = aEnd - aDelta;
|
||||
List<Line> aPrevious = aList.subList(aStart, aEnd);
|
||||
StringDiff delete = createDelete(aPrevious);
|
||||
if (delete != null) {
|
||||
results.add(delete);
|
||||
}
|
||||
|
||||
int bEnd = bIndex;
|
||||
int bStart = bEnd - bDelta;
|
||||
List<Line> bPrevious = bList.subList(bStart, bEnd);
|
||||
StringDiff insert = createInsert(bPrevious, charOffset(aList, aIndex));
|
||||
if (insert != null) {
|
||||
results.add(insert);
|
||||
}
|
||||
|
||||
// note: nothing is needed for the 'common' string, since we don't track unchanged text
|
||||
|
||||
aLastIndex = aIndex + 1;
|
||||
bLastIndex = bIndex + 1;
|
||||
}
|
||||
|
||||
// grab remainder
|
||||
StringDiff trailingDeleted = createDeleteAtEnd(aList, aLastIndex, aList.size());
|
||||
if (trailingDeleted != null) {
|
||||
results.add(trailingDeleted);
|
||||
}
|
||||
|
||||
StringDiff trailingInserted =
|
||||
createInsertAtEnd(bList, bLastIndex, bList.size(), s1.length());
|
||||
if (trailingInserted != null) {
|
||||
results.add(trailingInserted);
|
||||
}
|
||||
|
||||
return results.toArray(new StringDiff[results.size()]);
|
||||
}
|
||||
|
||||
private static int charOffset(List<Line> list, int index) {
|
||||
Line line = list.get(index);
|
||||
return line.start;
|
||||
}
|
||||
|
||||
private static StringDiff createInsertAtEnd(List<Line> list, int start, int end,
|
||||
int insertIndex) {
|
||||
if (start - 1 == end) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Line> toDo = list.subList(start, end);
|
||||
boolean newlineNeeded = true; // we are at the end--need a newline
|
||||
StringDiff insert = createInsert(toDo, insertIndex, newlineNeeded);
|
||||
return insert;
|
||||
}
|
||||
|
||||
private static StringDiff createInsert(List<Line> lines, int insertIndex) {
|
||||
return createInsert(lines, insertIndex, false);
|
||||
}
|
||||
|
||||
private static StringDiff createInsert(List<Line> lines, int insertIndex, boolean isAtEnd) {
|
||||
if (lines.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
StringBuilder buffy = new StringBuilder();
|
||||
|
||||
// special case: if this insert is for the end of the line, then we want to add
|
||||
// a newline before the remaining text is added since the original text
|
||||
// did not have this newline
|
||||
if (isAtEnd) {
|
||||
buffy.append('\n');
|
||||
}
|
||||
|
||||
for (Line line : lines) {
|
||||
buffy.append(line.getText());
|
||||
}
|
||||
|
||||
return StringDiff.textInserted(buffy.toString(), insertIndex);
|
||||
}
|
||||
|
||||
private static StringDiff createDeleteAtEnd(List<Line> list, int start, int end) {
|
||||
|
||||
if (start - 1 == end) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Line> toDo = list.subList(start, end);
|
||||
boolean includeLastNewline = false; // we are at the end--do not include artificial newline
|
||||
StringDiff delete = createDelete(toDo, includeLastNewline);
|
||||
return delete;
|
||||
}
|
||||
|
||||
private static StringDiff createDelete(List<Line> lines) {
|
||||
return createDelete(lines, true);
|
||||
}
|
||||
|
||||
private static StringDiff createDelete(List<Line> lines, boolean includeLastNewline) {
|
||||
if (lines.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int start = 0;
|
||||
int end = 0;
|
||||
for (Line line : lines) {
|
||||
start = line.start;
|
||||
end = line.start + line.text.length();
|
||||
}
|
||||
|
||||
// special case: if this delete is for the last line, then we want to remove the remaining
|
||||
// trailing newline
|
||||
Line last = lines.get(lines.size() - 1);
|
||||
if (!includeLastNewline && last.isLastLine) {
|
||||
start -= 1; // remove previous newline
|
||||
}
|
||||
|
||||
return StringDiff.textDeleted(start, end);
|
||||
}
|
||||
|
||||
private static int indexOf(List<Line> list, Line line, int from) {
|
||||
for (int i = from; i < list.size(); i++) {
|
||||
if (list.get(i).textMatches(line)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return list.size(); // should not get here since 's' is known to be in list
|
||||
}
|
||||
|
||||
private static List<Line> split(String s) {
|
||||
|
||||
LinkedList<Line> result = new LinkedList<>();
|
||||
List<String> lines = Arrays.asList(StringUtils.splitPreserveAllTokens(s, '\n'));
|
||||
int start = 0;
|
||||
for (String line : lines) {
|
||||
Line l = new Line(line + '\n', start);
|
||||
result.add(l);
|
||||
start += l.text.length();
|
||||
}
|
||||
|
||||
// strip off the trailing newline that we added above
|
||||
Line last = result.peekLast();
|
||||
last.markAsLast();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the array of StringObjects to the string s to produce a new string. Warning - the
|
||||
* diff objects cannot be applied to an arbitrary string, the Strings must be the original
|
||||
* String used to compute the diffs.
|
||||
* @param s the original string
|
||||
* @param diffs the array of StringDiff object to apply
|
||||
* @return a new String resulting from applying the diffs to s.
|
||||
*/
|
||||
static String applyDiffs(String s, List<StringDiff> diffs) {
|
||||
|
||||
if (diffs.isEmpty()) {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (diffs.get(0).start < 0) {
|
||||
// all replaced or all deleted
|
||||
String data = diffs.get(0).text;
|
||||
return data == null ? "" : data;
|
||||
}
|
||||
|
||||
int pos = 0;
|
||||
StringBuilder buf = new StringBuilder(s.length());
|
||||
for (StringDiff element : diffs) {
|
||||
if (element.start > pos) {
|
||||
buf.append(s.substring(pos, element.start));
|
||||
pos = element.start;
|
||||
}
|
||||
|
||||
String data = element.text;
|
||||
if (data != null) {
|
||||
buf.append(data);
|
||||
}
|
||||
else {
|
||||
// null data is a delete; move to the end of the delete
|
||||
pos = element.end;
|
||||
}
|
||||
}
|
||||
|
||||
if (pos < s.length()) {
|
||||
buf.append(s.substring(pos));
|
||||
}
|
||||
return buf.toString();
|
||||
}
|
||||
|
||||
//==================================================================================================
|
||||
// Inner Classes
|
||||
//==================================================================================================
|
||||
|
||||
private static class Line {
|
||||
|
||||
private String text;
|
||||
private int start;
|
||||
private boolean isLastLine;
|
||||
|
||||
public Line(String line, int start) {
|
||||
this.text = line;
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
String getText() {
|
||||
if (isLastLine) {
|
||||
return textWithoutNewline(); // last line and do not include the newline
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
void markAsLast() {
|
||||
isLastLine = true;
|
||||
}
|
||||
|
||||
private String textWithoutNewline() {
|
||||
if (text.charAt(text.length() - 1) == '\n') {
|
||||
return text.substring(0, text.length() - 1);
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return textWithoutNewline() + " @ " + start;
|
||||
}
|
||||
|
||||
boolean textMatches(Line other) {
|
||||
return Objects.equals(text, other.text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + start;
|
||||
result = prime * result + ((text == null) ? 0 : text.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null) {
|
||||
return false;
|
||||
}
|
||||
if (getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
Line other = (Line) obj;
|
||||
if (start != other.start) {
|
||||
return false;
|
||||
}
|
||||
if (text == null) {
|
||||
if (other.text != null) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!text.equals(other.text)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private static class LineLcs extends ReducingListBasedLcs<Line> {
|
||||
|
||||
LineLcs(List<Line> x, List<Line> y) {
|
||||
super(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean matches(Line x, Line y) {
|
||||
return x.text.equals(y.text);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -18,13 +17,15 @@ package ghidra.program.model.listing;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ghidra.program.model.address.Address;
|
||||
|
||||
/**
|
||||
* Container class for information about changes to a comment.
|
||||
*/
|
||||
public class CommentHistory {
|
||||
|
||||
|
||||
private Address addr;
|
||||
private int commentType;
|
||||
private Date modificationDate;
|
||||
@ -39,8 +40,8 @@ public class CommentHistory {
|
||||
* @param comments the list of comments.
|
||||
* @param modificationDate the date the comment was changed.
|
||||
*/
|
||||
public CommentHistory(Address addr, int commentType, String userName,
|
||||
String comments, Date modificationDate) {
|
||||
public CommentHistory(Address addr, int commentType, String userName, String comments,
|
||||
Date modificationDate) {
|
||||
this.addr = addr;
|
||||
this.commentType = commentType;
|
||||
this.userName = userName;
|
||||
@ -49,35 +50,55 @@ public class CommentHistory {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get address for this label history object.
|
||||
* Get address for this label history object
|
||||
* @return address for this label history object.
|
||||
*/
|
||||
public Address getAddress() {
|
||||
return addr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user that made the change.
|
||||
* Get the user that made the change
|
||||
* @return the user that made the change
|
||||
*/
|
||||
public String getUserName() {
|
||||
return userName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the comments for this history object.
|
||||
* Get the comments for this history object
|
||||
* @return the comments for this history object
|
||||
*/
|
||||
public String getComments() {
|
||||
return comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the comment type.
|
||||
* Get the comment type
|
||||
* @return the comment type
|
||||
*/
|
||||
public int getCommentType() {
|
||||
return commentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the modification date
|
||||
* @return the modification date
|
||||
*/
|
||||
public Date getModificationDate() {
|
||||
return modificationDate;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
|
||||
//@formatter:off
|
||||
return "{\n" +
|
||||
"\tuser: " + userName + ",\n" +
|
||||
"\tdate: " + modificationDate + ",\n" +
|
||||
"\taddress: " + addr + ",\n" +
|
||||
"\tcomment: " + StringUtils.abbreviate(comments, 10) + "\n" +
|
||||
"}";
|
||||
//@formatter:on
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,5 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
* REVIEWED: YES
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -16,11 +15,11 @@
|
||||
*/
|
||||
package ghidra.program.util;
|
||||
|
||||
import generic.algorithms.LCS;
|
||||
import generic.algorithms.Lcs;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CodeUnitLCS extends LCS<CodeUnitContainer> {
|
||||
public class CodeUnitLCS extends Lcs<CodeUnitContainer> {
|
||||
|
||||
private List<CodeUnitContainer> xList;
|
||||
private List<CodeUnitContainer> yList;
|
||||
|
@ -0,0 +1,203 @@
|
||||
/* ###
|
||||
* IP: GHIDRA
|
||||
*
|
||||
* 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 ghidra.program.database.code;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
import generic.test.AbstractGTest;
|
||||
|
||||
public class StringDiffTest {
|
||||
|
||||
@Test
|
||||
public void testGetDiffLines_Insert_AtFront() {
|
||||
|
||||
String[] a1 = new String[] { "This", "is", "four", "friends" };
|
||||
String[] a2 = new String[] { "Inserted", "This", "is", "four", "friends" };
|
||||
String v1 = StringUtils.join(a1, '\n');
|
||||
String v2 = StringUtils.join(a2, '\n');
|
||||
|
||||
StringDiff[] diffs = StringDiffUtils.getLineDiffs(v1, v2, 1);
|
||||
String restoredV2 = StringDiffUtils.applyDiffs(v1, Arrays.asList(diffs));
|
||||
assertEquals(v2, restoredV2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDiffLines_Insert_AtEnd() {
|
||||
|
||||
String[] a1 = new String[] { "This", "is", "four", "friends" };
|
||||
String[] a2 = new String[] { "This", "is", "four", "friends", "Inserted" };
|
||||
String v1 = StringUtils.join(a1, '\n');
|
||||
String v2 = StringUtils.join(a2, '\n');
|
||||
|
||||
StringDiff[] diffs = StringDiffUtils.getLineDiffs(v1, v2, 1);
|
||||
String restoredV2 = StringDiffUtils.applyDiffs(v1, Arrays.asList(diffs));
|
||||
assertEquals(v2, restoredV2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDiffLines_Insert_AtMiddle() {
|
||||
|
||||
String[] a1 = new String[] { "This", "is", "four", "friends" };
|
||||
String[] a2 = new String[] { "This", "is", "Inserted", "four", "friends" };
|
||||
String v1 = StringUtils.join(a1, '\n');
|
||||
String v2 = StringUtils.join(a2, '\n');
|
||||
|
||||
StringDiff[] diffs = StringDiffUtils.getLineDiffs(v1, v2, 1);
|
||||
String restoredV2 = StringDiffUtils.applyDiffs(v1, Arrays.asList(diffs));
|
||||
assertEquals(v2, restoredV2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDiffLines_Delete_AtStart() {
|
||||
|
||||
String[] a1 = new String[] { "DELETED", "This", "is", "the", "best" };
|
||||
String[] a2 = new String[] { "This", "is", "the", "best" };
|
||||
String v1 = StringUtils.join(a1, '\n');
|
||||
String v2 = StringUtils.join(a2, '\n');
|
||||
|
||||
StringDiff[] diffs = StringDiffUtils.getLineDiffs(v1, v2, 1);
|
||||
String restoredV2 = StringDiffUtils.applyDiffs(v1, Arrays.asList(diffs));
|
||||
assertEquals(v2, restoredV2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDiffLines_Delete_AtEnd() {
|
||||
|
||||
String[] a1 = new String[] { "This", "is", "the", "best", "DELETED" };
|
||||
String[] a2 = new String[] { "This", "is", "the", "best" };
|
||||
String v1 = StringUtils.join(a1, '\n');
|
||||
String v2 = StringUtils.join(a2, '\n');
|
||||
|
||||
StringDiff[] diffs = StringDiffUtils.getLineDiffs(v1, v2, 1);
|
||||
String restoredV2 = StringDiffUtils.applyDiffs(v1, Arrays.asList(diffs));
|
||||
assertEquals(v2, restoredV2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDiffLines_Delete_AtMiddle() {
|
||||
|
||||
String[] a1 = new String[] { "This", "is", "DELETED", "the", "best" };
|
||||
String[] a2 = new String[] { "This", "is", "the", "best" };
|
||||
String v1 = StringUtils.join(a1, '\n');
|
||||
String v2 = StringUtils.join(a2, '\n');
|
||||
|
||||
StringDiff[] diffs = StringDiffUtils.getLineDiffs(v1, v2, 1);
|
||||
String restoredV2 = StringDiffUtils.applyDiffs(v1, Arrays.asList(diffs));
|
||||
assertEquals(v2, restoredV2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDiffLines_Delete_MultipleDeletes() {
|
||||
|
||||
String[] a1 = new String[] { "This", "is", "DELETED", "the", "best", "DELETED" };
|
||||
String[] a2 = new String[] { "This", "is", "the", "best" };
|
||||
String v1 = StringUtils.join(a1, '\n');
|
||||
String v2 = StringUtils.join(a2, '\n');
|
||||
|
||||
StringDiff[] diffs = StringDiffUtils.getLineDiffs(v1, v2, 1);
|
||||
String restoredV2 = StringDiffUtils.applyDiffs(v1, Arrays.asList(diffs));
|
||||
assertEquals(v2, restoredV2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDiffLines_Rearrange_EqualLineLength() {
|
||||
|
||||
// note: this text used to cause an infinite loop bug that tripped when two words were
|
||||
// swapped at some point in the two strings *and* had the same length
|
||||
|
||||
String[] a1 = new String[] { "This", "is", "best", "four", "friends" };
|
||||
String[] a2 = new String[] { "This", "is", "four", "best", "friends" };
|
||||
String v1 = StringUtils.join(a1, '\n');
|
||||
String v2 = StringUtils.join(a2, '\n');
|
||||
|
||||
StringDiff[] diffs = StringDiffUtils.getLineDiffs(v1, v2, 1);
|
||||
String restoredV2 = StringDiffUtils.applyDiffs(v1, Arrays.asList(diffs));
|
||||
assertEquals(v2, restoredV2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDiffLines_Rearrange_DifferentLineLength_LongerThanNewSpot() {
|
||||
|
||||
String[] a1 = new String[] { "This", "is", "besties", "four", "friends" };
|
||||
String[] a2 = new String[] { "This", "is", "four", "besties", "friends" };
|
||||
String v1 = StringUtils.join(a1, '\n');
|
||||
String v2 = StringUtils.join(a2, '\n');
|
||||
|
||||
StringDiff[] diffs = StringDiffUtils.getLineDiffs(v1, v2, 1);
|
||||
String restoredV2 = StringDiffUtils.applyDiffs(v1, Arrays.asList(diffs));
|
||||
assertEquals(v2, restoredV2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDiffLines_Rearrange_DifferentLineLength_ShorterThanNewSpot() {
|
||||
|
||||
String[] a1 = new String[] { "This", "is", "be", "four", "friends" };
|
||||
String[] a2 = new String[] { "This", "is", "four", "be", "friends" };
|
||||
String v1 = StringUtils.join(a1, '\n');
|
||||
String v2 = StringUtils.join(a2, '\n');
|
||||
|
||||
StringDiff[] diffs = StringDiffUtils.getLineDiffs(v1, v2, 1);
|
||||
String restoredV2 = StringDiffUtils.applyDiffs(v1, Arrays.asList(diffs));
|
||||
assertEquals(v2, restoredV2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReplace() {
|
||||
String[] a1 = new String[] { "In", "the", "beginning" };
|
||||
String[] a2 = new String[] { "There", "was", "vastness" };
|
||||
String v1 = StringUtils.join(a1, '\n');
|
||||
String v2 = StringUtils.join(a2, '\n');
|
||||
|
||||
StringDiff[] diffs = StringDiffUtils.getLineDiffs(v1, v2, 1);
|
||||
String restoredV2 = StringDiffUtils.applyDiffs(v1, Arrays.asList(diffs));
|
||||
assertEquals(v2, restoredV2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTheBiggness_NoOptimization() throws Exception {
|
||||
|
||||
List<String> bigLines = generateLines(1200);
|
||||
List<String> bigLines2 = new ArrayList<>(bigLines);
|
||||
|
||||
bigLines2.set(0, "a new line at 0");
|
||||
bigLines2.set(bigLines2.size() - 1, "a new line at length");
|
||||
|
||||
String v1 = StringUtils.join(bigLines, '\n');
|
||||
String v2 = StringUtils.join(bigLines2, '\n');
|
||||
|
||||
StringDiff[] diffs = StringDiffUtils.getLineDiffs(v1, v2, 1);
|
||||
assertEquals(1, diffs.length); // 1 diff--completely different, due to size restriction on Lcs
|
||||
String restoredV2 = StringDiffUtils.applyDiffs(v1, Arrays.asList(diffs));
|
||||
assertEquals(v2, restoredV2);
|
||||
}
|
||||
|
||||
private List<String> generateLines(int size) {
|
||||
|
||||
List<String> results = new ArrayList<>();
|
||||
for (int i = 0; i < size; i++) {
|
||||
String random = AbstractGTest.getRandomString(0, 50);
|
||||
random = random.replaceAll("\n", "");
|
||||
results.add("Line " + (i + 1) + ": " + random);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user