mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-29 04:05:37 +08:00
Merge remote-tracking branch 'origin/GP-2-dragonmacher-rt-v6+--SQUASHED'
This commit is contained in:
-1
@@ -401,7 +401,6 @@ public abstract class AbstractFunctionGraphTest extends AbstractGhidraHeadedInte
|
|||||||
@After
|
@After
|
||||||
public void tearDown() throws Exception {
|
public void tearDown() throws Exception {
|
||||||
waitForSwing();
|
waitForSwing();
|
||||||
env.closeTool(tool);
|
|
||||||
env.dispose();
|
env.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -216,7 +216,10 @@ public class GlobalMenuAndToolBarManager implements DockingWindowListener {
|
|||||||
// has not yet completed
|
// has not yet completed
|
||||||
DialogComponentProvider provider = dialog.getDialogComponent();
|
DialogComponentProvider provider = dialog.getDialogComponent();
|
||||||
if (provider != null) {
|
if (provider != null) {
|
||||||
return provider.getActionContext(null);
|
ActionContext context = provider.getActionContext(null);
|
||||||
|
if (context != null) {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -225,7 +228,7 @@ public class GlobalMenuAndToolBarManager implements DockingWindowListener {
|
|||||||
|
|
||||||
private ActionContext getComponentProviderContext(WindowNode windowNode) {
|
private ActionContext getComponentProviderContext(WindowNode windowNode) {
|
||||||
if (windowNode == null) {
|
if (windowNode == null) {
|
||||||
return null;
|
return new DefaultActionContext();
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionContext context = null;
|
ActionContext context = null;
|
||||||
|
|||||||
+24
-6
@@ -4,9 +4,9 @@
|
|||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@@ -17,6 +17,8 @@ package docking.widgets.fieldpanel.support;
|
|||||||
|
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
|
|
||||||
|
import generic.json.Json;
|
||||||
|
|
||||||
public class Highlight {
|
public class Highlight {
|
||||||
private int start;
|
private int start;
|
||||||
private int end;
|
private int end;
|
||||||
@@ -36,21 +38,28 @@ public class Highlight {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the starting position of the highlight.
|
* {@return the starting position of the highlight}
|
||||||
*/
|
*/
|
||||||
public int getStart() {
|
public int getStart() {
|
||||||
return start + offset;
|
return start + offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the ending position (inclusive) of the highlight.
|
* {@return the ending position (inclusive) of the highlight}
|
||||||
*/
|
*/
|
||||||
public int getEnd() {
|
public int getEnd() {
|
||||||
return end + offset;
|
return end + offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the color to use as the background highlight color.
|
* {@return the number of characters in the match.}
|
||||||
|
*/
|
||||||
|
public int length() {
|
||||||
|
return (end - start) + 1; // +1 because 'end' is inclusive
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@return the color to use as the background highlight color.}
|
||||||
*/
|
*/
|
||||||
public Color getColor() {
|
public Color getColor() {
|
||||||
return color;
|
return color;
|
||||||
@@ -59,11 +68,20 @@ public class Highlight {
|
|||||||
/**
|
/**
|
||||||
* Sets the offset of this highlights start and end values. The effect of the offset is that
|
* Sets the offset of this highlights start and end values. The effect of the offset is that
|
||||||
* calls to {@link #getStart()} and {@link #getEnd()} will return their values with the
|
* calls to {@link #getStart()} and {@link #getEnd()} will return their values with the
|
||||||
* offset added.
|
* offset added.
|
||||||
|
* <p>
|
||||||
|
* This useful when highlights are using offsets for widgets that embedded inside of composite
|
||||||
|
* containers. the parent container turn these relative values into absolute values that work
|
||||||
|
* when all sub-parts are combined.
|
||||||
*
|
*
|
||||||
* @param newOffset The new offset into this highlight.
|
* @param newOffset The new offset into this highlight.
|
||||||
*/
|
*/
|
||||||
public void setOffset(int newOffset) {
|
public void setOffset(int newOffset) {
|
||||||
offset = newOffset;
|
offset = newOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return Json.toString(this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+46
-3
@@ -16,6 +16,7 @@
|
|||||||
package docking.widgets.search;
|
package docking.widgets.search;
|
||||||
|
|
||||||
import java.awt.*;
|
import java.awt.*;
|
||||||
|
import java.awt.geom.Rectangle2D;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -307,7 +308,11 @@ public class TextComponentSearchResults extends SearchResults {
|
|||||||
try {
|
try {
|
||||||
int start = location.getStartIndexInclusive();
|
int start = location.getStartIndexInclusive();
|
||||||
int end = location.getEndIndexInclusive();
|
int end = location.getEndIndexInclusive();
|
||||||
Rectangle startR = editorPane.modelToView2D(start).getBounds();
|
Rectangle2D start2d = editorPane.modelToView2D(start);
|
||||||
|
if (start2d == null) {
|
||||||
|
return; // not yet realized
|
||||||
|
}
|
||||||
|
Rectangle startR = start2d.getBounds();
|
||||||
Rectangle endR = editorPane.modelToView2D(end).getBounds();
|
Rectangle endR = editorPane.modelToView2D(end).getBounds();
|
||||||
endR.width += 20; // a little extra space so the view is not right at the text end
|
endR.width += 20; // a little extra space so the view is not right at the text end
|
||||||
Rectangle union = startR.union(endR);
|
Rectangle union = startR.union(endR);
|
||||||
@@ -449,7 +454,7 @@ public class TextComponentSearchResults extends SearchResults {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Color c = location.isActive() ? activeHighlightColor : highlightColor;
|
Color c = location.isActive() ? activeHighlightColor : highlightColor;
|
||||||
HighlightPainter painter = new DefaultHighlightPainter(c);
|
HighlightPainter painter = new SearchResultsHighlightPainter(c);
|
||||||
int start = location.getStartIndexInclusive();
|
int start = location.getStartIndexInclusive();
|
||||||
int end = location.getEndIndexInclusive() + 1; // +1 to make inclusive be exclusive
|
int end = location.getEndIndexInclusive() + 1; // +1 to make inclusive be exclusive
|
||||||
try {
|
try {
|
||||||
@@ -463,6 +468,7 @@ public class TextComponentSearchResults extends SearchResults {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void dispose() {
|
public void dispose() {
|
||||||
|
|
||||||
caretUpdater.dispose();
|
caretUpdater.dispose();
|
||||||
|
|
||||||
if (editorPane != null) {
|
if (editorPane != null) {
|
||||||
@@ -557,7 +563,11 @@ public class TextComponentSearchResults extends SearchResults {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (nonSearchDelegate) {
|
if (nonSearchDelegate) {
|
||||||
|
// Calling setHighlighter() will cause all old highlights to get removed. We would
|
||||||
|
// like the non-search highlights to remain. Grab them now and put them back after.
|
||||||
|
Highlight[] nonSearchHighlights = delegate.getHighlights();
|
||||||
editorPane.setHighlighter(delegate);
|
editorPane.setHighlighter(delegate);
|
||||||
|
restoreNonSearchHighlights(nonSearchHighlights);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
editorPane.setHighlighter(null);
|
editorPane.setHighlighter(null);
|
||||||
@@ -566,7 +576,30 @@ public class TextComponentSearchResults extends SearchResults {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void install(JTextComponent c) {
|
public void install(JTextComponent c) {
|
||||||
|
Highlight[] nonSearchHighlights = delegate.getHighlights();
|
||||||
delegate.install(c);
|
delegate.install(c);
|
||||||
|
|
||||||
|
// Calling delegate.install() will cause its existing highlights to get removed. We
|
||||||
|
// would like to keep them, so we have save and restore them.
|
||||||
|
restoreNonSearchHighlights(nonSearchHighlights);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restoreNonSearchHighlights(Highlight[] oldHighlights) {
|
||||||
|
for (Highlight hl : oldHighlights) {
|
||||||
|
int start = hl.getStartOffset();
|
||||||
|
int end = hl.getEndOffset();
|
||||||
|
HighlightPainter painter = hl.getPainter();
|
||||||
|
if (painter instanceof SearchResultsHighlightPainter) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
delegate.addHighlight(start, end, painter);
|
||||||
|
}
|
||||||
|
catch (BadLocationException e) {
|
||||||
|
// shouldn't happen
|
||||||
|
Msg.error(this, "Could not add existing highlight", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -599,7 +632,11 @@ public class TextComponentSearchResults extends SearchResults {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeAllHighlights() {
|
public void removeAllHighlights() {
|
||||||
delegate.removeAllHighlights();
|
// only remove our highlights, not any pre-existing client non-search highlights
|
||||||
|
for (TextComponentSearchLocation loc : searchLocations) {
|
||||||
|
Object tag = loc.getHighlightTag();
|
||||||
|
removeHighlight(tag);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -611,6 +648,12 @@ public class TextComponentSearchResults extends SearchResults {
|
|||||||
public Highlight[] getHighlights() {
|
public Highlight[] getHighlights() {
|
||||||
return delegate.getHighlights();
|
return delegate.getHighlights();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// marker class
|
||||||
|
private class SearchResultsHighlightPainter extends DefaultHighlightPainter {
|
||||||
|
public SearchResultsHighlightPainter(Color c) {
|
||||||
|
super(c);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-1
@@ -23,6 +23,8 @@ import javax.swing.JEditorPane;
|
|||||||
import javax.swing.text.BadLocationException;
|
import javax.swing.text.BadLocationException;
|
||||||
import javax.swing.text.Document;
|
import javax.swing.text.Document;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
import docking.widgets.CursorPosition;
|
import docking.widgets.CursorPosition;
|
||||||
import docking.widgets.SearchLocation;
|
import docking.widgets.SearchLocation;
|
||||||
import ghidra.util.Msg;
|
import ghidra.util.Msg;
|
||||||
@@ -235,6 +237,11 @@ public class TextComponentSearcher implements FindDialogSearcher {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (StringUtils.isBlank(fullText)) {
|
||||||
|
Msg.error(this, "Cannot search a blank document");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
TreeMap<Integer, Line> lineRangeMap = mapLines(fullText);
|
TreeMap<Integer, Line> lineRangeMap = mapLines(fullText);
|
||||||
|
|
||||||
Pattern pattern = createSearchPattern(searchText, useRegex);
|
Pattern pattern = createSearchPattern(searchText, useRegex);
|
||||||
@@ -252,7 +259,6 @@ public class TextComponentSearcher implements FindDialogSearcher {
|
|||||||
context);
|
context);
|
||||||
matchesByPosition.put(start, location);
|
matchesByPosition.put(start, location);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TreeMap<Integer, Line> mapLines(String fullText) {
|
private TreeMap<Integer, Line> mapLines(String fullText) {
|
||||||
|
|||||||
+30
-12
@@ -4,9 +4,9 @@
|
|||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@@ -62,10 +62,14 @@ public abstract class GDynamicColumnTableModel<ROW_TYPE, DATA_SOURCE>
|
|||||||
protected ServiceProvider serviceProvider;
|
protected ServiceProvider serviceProvider;
|
||||||
|
|
||||||
private TableColumnDescriptor<ROW_TYPE> columnDescriptor;
|
private TableColumnDescriptor<ROW_TYPE> columnDescriptor;
|
||||||
protected List<DynamicTableColumn<ROW_TYPE, ?, ?>> tableColumns = new ArrayList<>();
|
|
||||||
private List<DynamicTableColumn<ROW_TYPE, ?, ?>> defaultTableColumns = new ArrayList<>();
|
|
||||||
protected Map<DynamicTableColumn<ROW_TYPE, ?, ?>, Settings> columnSettings = new HashMap<>();
|
protected Map<DynamicTableColumn<ROW_TYPE, ?, ?>, Settings> columnSettings = new HashMap<>();
|
||||||
|
|
||||||
|
/** All currently visible columns */
|
||||||
|
protected List<DynamicTableColumn<ROW_TYPE, ?, ?>> tableColumns = new ArrayList<>();
|
||||||
|
|
||||||
|
/** The initially visible columns before user changes or state restoring */
|
||||||
|
private List<DynamicTableColumn<ROW_TYPE, ?, ?>> defaultTableColumns = new ArrayList<>();
|
||||||
|
|
||||||
private boolean ignoreSettingChanges = false;
|
private boolean ignoreSettingChanges = false;
|
||||||
|
|
||||||
public GDynamicColumnTableModel(ServiceProvider serviceProvider) {
|
public GDynamicColumnTableModel(ServiceProvider serviceProvider) {
|
||||||
@@ -298,7 +302,7 @@ public abstract class GDynamicColumnTableModel<ROW_TYPE, DATA_SOURCE>
|
|||||||
protected void addTableColumns(Set<DynamicTableColumn<ROW_TYPE, ?, ?>> columns,
|
protected void addTableColumns(Set<DynamicTableColumn<ROW_TYPE, ?, ?>> columns,
|
||||||
boolean isDefault) {
|
boolean isDefault) {
|
||||||
for (DynamicTableColumn<ROW_TYPE, ?, ?> column : columns) {
|
for (DynamicTableColumn<ROW_TYPE, ?, ?> column : columns) {
|
||||||
doAddTableColumn(column, getDefaultTableColumns().size(), isDefault);
|
doAddTableColumn(column, -1, isDefault);
|
||||||
}
|
}
|
||||||
fireTableStructureChanged();
|
fireTableStructureChanged();
|
||||||
}
|
}
|
||||||
@@ -327,16 +331,30 @@ public abstract class GDynamicColumnTableModel<ROW_TYPE, DATA_SOURCE>
|
|||||||
private void doAddTableColumn(DynamicTableColumn<ROW_TYPE, ?, ?> column, int index,
|
private void doAddTableColumn(DynamicTableColumn<ROW_TYPE, ?, ?> column, int index,
|
||||||
boolean isDefault) {
|
boolean isDefault) {
|
||||||
|
|
||||||
if (index < 0 || index > tableColumns.size()) {
|
int adjustedIndex = index;
|
||||||
index = getDefaultTableColumns().size();
|
if (adjustedIndex < 0 || adjustedIndex > tableColumns.size()) {
|
||||||
|
adjustedIndex = tableColumns.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
tableColumns.add(index, column);
|
tableColumns.add(adjustedIndex, column);
|
||||||
columnSettings.put(column, new SettingsImpl(this, column));
|
columnSettings.put(column, new SettingsImpl(this, column));
|
||||||
if (isDefault) {
|
|
||||||
List<DynamicTableColumn<ROW_TYPE, ?, ?>> defaultColumns = getDefaultTableColumns();
|
if (!isDefault) {
|
||||||
defaultColumns.add(index, column);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note: this method is typically called when 'tableColumns' and 'defaultTableColumns' have
|
||||||
|
// the same columns. But, that is not a requirement. When they have the same columns, the
|
||||||
|
// insertion index is correct for both lists. If they have different columns, then the
|
||||||
|
// insertion index for the default columns may or may not be what the caller intended. In
|
||||||
|
// practice, it should not matter where the column is inserted into the default columns, as
|
||||||
|
// that is only used to query whether a column is in the list or not. If we ever need to
|
||||||
|
// have accurate positioning in the default list when both lists are not equivalent, then we
|
||||||
|
// will have to add a new method or change this method to allow callers to dictate where the
|
||||||
|
// column should go in the default list. For now, just add the column to the end.
|
||||||
|
adjustedIndex = defaultTableColumns.size();
|
||||||
|
List<DynamicTableColumn<ROW_TYPE, ?, ?>> defaultColumns = getDefaultTableColumns();
|
||||||
|
defaultColumns.add(adjustedIndex, column);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -470,7 +488,7 @@ public abstract class GDynamicColumnTableModel<ROW_TYPE, DATA_SOURCE>
|
|||||||
DATA_SOURCE dataSource = getDataSource();
|
DATA_SOURCE dataSource = getDataSource();
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
// TODO: We are casting now, as in practice the type should never be different that
|
// Note: We are casting now, as in practice the type should never be different that
|
||||||
// the declared type. We want to remove entirely the 'dataSource' value and then
|
// the declared type. We want to remove entirely the 'dataSource' value and then
|
||||||
// the templating will be simpler.
|
// the templating will be simpler.
|
||||||
DynamicTableColumn<ROW_TYPE, ?, DATA_SOURCE> column =
|
DynamicTableColumn<ROW_TYPE, ?, DATA_SOURCE> column =
|
||||||
|
|||||||
+11
-10
@@ -470,29 +470,30 @@ public class TableColumnModelState implements SortListener {
|
|||||||
if (preferenceKey != null) {
|
if (preferenceKey != null) {
|
||||||
return preferenceKey;
|
return preferenceKey;
|
||||||
}
|
}
|
||||||
TableModel tableModel = table.getModel();
|
|
||||||
|
|
||||||
int columnCount = getDefaultColumnCount();
|
TableModel model = table.getModel();
|
||||||
StringBuffer buffer = new StringBuffer();
|
int n = model.getColumnCount();
|
||||||
|
StringBuilder buffer = new StringBuilder();
|
||||||
buffer.append(getTableModelName());
|
buffer.append(getTableModelName());
|
||||||
buffer.append(":");
|
buffer.append(":");
|
||||||
for (int i = 0; i < columnCount; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
String columnName = tableModel.getColumnName(i);
|
if (isDefaultColumn(i)) {
|
||||||
buffer.append(columnName).append(":");
|
String columnName = model.getColumnName(i);
|
||||||
|
buffer.append(columnName).append(":");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return buffer.toString();
|
return buffer.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getDefaultColumnCount() {
|
private boolean isDefaultColumn(int col) {
|
||||||
|
|
||||||
TableModel tableModel = table.getUnwrappedTableModel();
|
TableModel tableModel = table.getUnwrappedTableModel();
|
||||||
if (tableModel instanceof VariableColumnTableModel) {
|
if (tableModel instanceof VariableColumnTableModel) {
|
||||||
VariableColumnTableModel variableTableModel = (VariableColumnTableModel) tableModel;
|
VariableColumnTableModel variableTableModel = (VariableColumnTableModel) tableModel;
|
||||||
// VariableColumnTableModels have default columns and 'found' columns. We only want to
|
// VariableColumnTableModels have default columns and 'found' columns. We only want to
|
||||||
// create a key based upon the default columns
|
// create a key based upon the default columns
|
||||||
return variableTableModel.getDefaultColumnCount();
|
return variableTableModel.isDefaultColumn(col);
|
||||||
}
|
}
|
||||||
return tableModel.getColumnCount();
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setDefaultColumnsVisible() {
|
private void setDefaultColumnsVisible() {
|
||||||
|
|||||||
+6
-2
@@ -4,9 +4,9 @@
|
|||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@@ -27,6 +27,7 @@ public interface VariableColumnTableModel extends TableModel {
|
|||||||
* type or is wraps another table model that is an instance of this type. If the given
|
* type or is wraps another table model that is an instance of this type. If the given
|
||||||
* model is not such an instance, then null is returned.
|
* model is not such an instance, then null is returned.
|
||||||
*
|
*
|
||||||
|
* @param m the model
|
||||||
* @return the variable column model
|
* @return the variable column model
|
||||||
*/
|
*/
|
||||||
public static VariableColumnTableModel from(TableModel m) {
|
public static VariableColumnTableModel from(TableModel m) {
|
||||||
@@ -45,6 +46,7 @@ public interface VariableColumnTableModel extends TableModel {
|
|||||||
* Returns a value that is unique for a given table column. This is different than getting
|
* Returns a value that is unique for a given table column. This is different than getting
|
||||||
* the display name, which may be shared by different columns.
|
* the display name, which may be shared by different columns.
|
||||||
* @param column the index (in the model space) of the column for which to get the identifier
|
* @param column the index (in the model space) of the column for which to get the identifier
|
||||||
|
* @return the identifier
|
||||||
*/
|
*/
|
||||||
public String getUniqueIdentifier(int column);
|
public String getUniqueIdentifier(int column);
|
||||||
|
|
||||||
@@ -56,7 +58,9 @@ public interface VariableColumnTableModel extends TableModel {
|
|||||||
* calling methods like {@link #getColumnName(int)}.
|
* calling methods like {@link #getColumnName(int)}.
|
||||||
*
|
*
|
||||||
* @return Gets the count of the default columns for this model.
|
* @return Gets the count of the default columns for this model.
|
||||||
|
* @deprecated no longer needed
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(forRemoval = true, since = "12.1")
|
||||||
public int getDefaultColumnCount();
|
public int getDefaultColumnCount();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
/* ###
|
||||||
|
* 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A reducing LCS that works on Strings.
|
||||||
|
*/
|
||||||
|
public class StringReducingLcs extends ReducingLcs<String, Character> {
|
||||||
|
|
||||||
|
public StringReducingLcs(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,270 @@
|
|||||||
|
/* ###
|
||||||
|
* 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.*;
|
||||||
|
import java.util.Map.Entry;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds differences between two words (any two Strings). The results are available via
|
||||||
|
* {@link #getParts()}.
|
||||||
|
*/
|
||||||
|
public class WordDiffer {
|
||||||
|
|
||||||
|
private List<WordPart> parts = List.of();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Diffs the text between the old and new word. The new word is the current version of two
|
||||||
|
* Strings, the old word is the previous version.
|
||||||
|
*
|
||||||
|
* @param oldWord the previous version of the text
|
||||||
|
* @param newWord the current version of the text
|
||||||
|
*/
|
||||||
|
public WordDiffer(String oldWord, String newWord) {
|
||||||
|
|
||||||
|
LcsMatch lcs = getLcs(newWord, oldWord);
|
||||||
|
if (lcs == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TreeMap<Integer, String> wordsByOffset = buildWordOffsets(lcs);
|
||||||
|
|
||||||
|
parts = createWordParts(newWord, wordsByOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the 'new word' broken into Strings with offsets, with each part being the same text
|
||||||
|
* or different text.
|
||||||
|
* @return the parts; empty if the LCS could not be created
|
||||||
|
*/
|
||||||
|
public List<WordPart> getParts() {
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The same as {@link #getParts()} except that this method will merge differences that are
|
||||||
|
* separated only by {@code maxSize} or less characters. This method allows clients to combine
|
||||||
|
* many smaller differences into larger differences that span similar characters. Merging parts
|
||||||
|
* can reduce visual clutter when displaying the differences, at the expense of accuracy.
|
||||||
|
*
|
||||||
|
* @param maxSize the maximum span of characters past which not to merge two differences
|
||||||
|
* @return the parts
|
||||||
|
*/
|
||||||
|
public List<WordPart> getMergedParts(int maxSize) {
|
||||||
|
|
||||||
|
List<WordPart> newParts = new ArrayList<>();
|
||||||
|
DifferentPart lastDiffPart = null;
|
||||||
|
for (int i = 0; i < parts.size(); i++) {
|
||||||
|
WordPart part = parts.get(i);
|
||||||
|
if (part instanceof DifferentPart diffPart) {
|
||||||
|
lastDiffPart = diffPart;
|
||||||
|
newParts.add(lastDiffPart);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int length = part.length();
|
||||||
|
if (length > maxSize) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastDiffPart != null) {
|
||||||
|
WordPart nextPart = i + 1 < parts.size() ? parts.get(i + 1) : null;
|
||||||
|
WordPart mergedPart = lastDiffPart.merge(part, nextPart);
|
||||||
|
if (mergedPart != null) {
|
||||||
|
i++;
|
||||||
|
newParts.remove(lastDiffPart);
|
||||||
|
newParts.add(mergedPart);
|
||||||
|
lastDiffPart = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// add this non-diff part to the last merged diff part, if any exists
|
||||||
|
else {
|
||||||
|
if (newParts.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
DifferentPart previousDiffPart = (DifferentPart) newParts.getLast();
|
||||||
|
WordPart nextPart = i + 1 < parts.size() ? parts.get(i + 1) : null;
|
||||||
|
WordPart mergedPart = previousDiffPart.merge(part, nextPart);
|
||||||
|
if (mergedPart != null) {
|
||||||
|
i++;
|
||||||
|
newParts.remove(previousDiffPart);
|
||||||
|
newParts.add(mergedPart);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newParts;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return parts.stream().map(p -> p.toString()).collect(Collectors.joining());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turns the LCS match into one or more words that do not match. This uses the common
|
||||||
|
* characters to build a mapping of the different words and their offsets into the new word
|
||||||
|
* originally passed to the WordDiffer.
|
||||||
|
*/
|
||||||
|
private TreeMap<Integer, String> buildWordOffsets(LcsMatch match) {
|
||||||
|
// break each word into parts, splitting on each character
|
||||||
|
|
||||||
|
TreeMap<Integer, String> wordsByOffset = new TreeMap<>();
|
||||||
|
|
||||||
|
StringBuilder buffy = new StringBuilder();
|
||||||
|
String word = match.newWord;
|
||||||
|
int wordIndex = 0; // index into the overall 'new word'
|
||||||
|
for (char c : match.lcs) {
|
||||||
|
|
||||||
|
for (; wordIndex < word.length(); wordIndex++) {
|
||||||
|
|
||||||
|
char wordChar = word.charAt(wordIndex);
|
||||||
|
if (wordChar == c) {
|
||||||
|
int offset = (wordIndex) - buffy.length();
|
||||||
|
saveWord(buffy, offset, wordsByOffset);
|
||||||
|
wordIndex++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
buffy.append(wordChar);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int offset = wordIndex - buffy.length();
|
||||||
|
saveWord(buffy, offset, wordsByOffset);
|
||||||
|
|
||||||
|
if (wordIndex < word.length()) {
|
||||||
|
// the LCS ended; get the rest of the original word
|
||||||
|
buffy.append(word.substring(wordIndex));
|
||||||
|
saveWord(buffy, wordIndex, wordsByOffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
return wordsByOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveWord(StringBuilder buffy, int charPosition,
|
||||||
|
TreeMap<Integer, String> wordIndices) {
|
||||||
|
if (buffy.length() > 0) {
|
||||||
|
wordIndices.put(charPosition, buffy.toString());
|
||||||
|
buffy.setLength(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private LcsMatch getLcs(String x, String y) {
|
||||||
|
StringReducingLcs lcs = new StringReducingLcs(x, y);
|
||||||
|
List<Character> lcsList = lcs.getLcs();
|
||||||
|
if (lcsList.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (lcsList.size() < 3) {
|
||||||
|
return null; // what is the min size?
|
||||||
|
}
|
||||||
|
|
||||||
|
LcsMatch match = new LcsMatch(x, y, lcsList);
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<WordPart> createWordParts(String newWord, TreeMap<Integer, String> wordIndices) {
|
||||||
|
|
||||||
|
List<WordPart> results = new ArrayList<>();
|
||||||
|
|
||||||
|
int lastWrittenIndex = 0;
|
||||||
|
Set<Entry<Integer, String>> entrySet = wordIndices.entrySet();
|
||||||
|
for (Entry<Integer, String> entry : entrySet) {
|
||||||
|
Integer index = entry.getKey();
|
||||||
|
|
||||||
|
if (lastWrittenIndex < index) {
|
||||||
|
String text = newWord.substring(lastWrittenIndex, index);
|
||||||
|
results.add(new SamePart(text, lastWrittenIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
String word = entry.getValue();
|
||||||
|
results.add(new DifferentPart(word, index));
|
||||||
|
lastWrittenIndex = index + word.length();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastWrittenIndex < newWord.length()) {
|
||||||
|
String text = newWord.substring(lastWrittenIndex);
|
||||||
|
results.add(new SamePart(text, lastWrittenIndex));
|
||||||
|
}
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
//=================================================================================================
|
||||||
|
// Inner Classes
|
||||||
|
//=================================================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A String that is part of a larger String. This class also has the offset into the original
|
||||||
|
* String.
|
||||||
|
*/
|
||||||
|
public abstract class WordPart {
|
||||||
|
protected String text;
|
||||||
|
protected int index;
|
||||||
|
|
||||||
|
WordPart(String text, int index) {
|
||||||
|
this.text = text;
|
||||||
|
this.index = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getIndex() {
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int length() {
|
||||||
|
return text.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class SamePart extends WordPart {
|
||||||
|
SamePart(String text, int index) {
|
||||||
|
super(text, index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class DifferentPart extends WordPart {
|
||||||
|
DifferentPart(String text, int index) {
|
||||||
|
super(text, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public WordPart merge(WordPart oldPart, WordPart nextPart) {
|
||||||
|
if (!(nextPart instanceof DifferentPart)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
String updatedText = text + oldPart.text + nextPart.text;
|
||||||
|
return new DifferentPart(updatedText, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return " /" + text + "/ ";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record LcsMatch(String newWord, String oldWord, List<Character> lcs) {}
|
||||||
|
}
|
||||||
+6
-6
@@ -4,9 +4,9 @@
|
|||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@@ -35,12 +35,12 @@ public class StringDiff {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* String being inserted. This can be an insert or a complete replace (the positions will both
|
* 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).
|
* be -1 in a replace; start will be non-negative during an insert).
|
||||||
*/
|
*/
|
||||||
public String text;
|
public String text;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new StringDiff with pos1 and pos2 are initialized to -1
|
* Construct a new StringDiff with start and end are initialized to -1
|
||||||
*
|
*
|
||||||
* @param newText string
|
* @param newText string
|
||||||
* @return the new diff
|
* @return the new diff
|
||||||
@@ -50,7 +50,7 @@ public class StringDiff {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new StringDiff that indicates text was deleted from pos1 to pos2
|
* Construct a new StringDiff that indicates text was deleted from start and end
|
||||||
*
|
*
|
||||||
* @param start position 1 for the diff
|
* @param start position 1 for the diff
|
||||||
* @param end position 2 for the diff
|
* @param end position 2 for the diff
|
||||||
@@ -61,7 +61,7 @@ public class StringDiff {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a new StringDiff that indicates that insertData was inserted at the given position
|
* Construct a new StringDiff that indicates that newText was inserted at the given position
|
||||||
*
|
*
|
||||||
* @param newText inserted string
|
* @param newText inserted string
|
||||||
* @param start position where the text was inserted
|
* @param start position where the text was inserted
|
||||||
|
|||||||
+7
-5
@@ -4,9 +4,9 @@
|
|||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
@@ -219,9 +219,11 @@ class StringDiffUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Applies the array of StringObjects to the string s to produce a new string. Warning - the
|
* Applies the array of StringDiffs to the string s to produce a new string.
|
||||||
* diff objects cannot be applied to an arbitrary string, the Strings must be the original
|
*
|
||||||
* String used to compute the diffs.
|
* <p>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 s the original string
|
||||||
* @param diffs the array of StringDiff object to apply
|
* @param diffs the array of StringDiff object to apply
|
||||||
* @return a new String resulting from applying the diffs to s.
|
* @return a new String resulting from applying the diffs to s.
|
||||||
|
|||||||
@@ -221,7 +221,8 @@ public class ApplicationVersion implements Comparable<ApplicationVersion> {
|
|||||||
}
|
}
|
||||||
catch (NumberFormatException e) {
|
catch (NumberFormatException e) {
|
||||||
throw new IllegalArgumentException(
|
throw new IllegalArgumentException(
|
||||||
"Failed to convert " + versionPartName + " version to integer");
|
"Failed to convert version to integer: '" + versionPartName + "' value '" +
|
||||||
|
versionPart + "'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user