mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-23 03:55:31 +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
|
||||
public void tearDown() throws Exception {
|
||||
waitForSwing();
|
||||
env.closeTool(tool);
|
||||
env.dispose();
|
||||
}
|
||||
|
||||
|
||||
@@ -216,7 +216,10 @@ public class GlobalMenuAndToolBarManager implements DockingWindowListener {
|
||||
// has not yet completed
|
||||
DialogComponentProvider provider = dialog.getDialogComponent();
|
||||
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) {
|
||||
if (windowNode == null) {
|
||||
return null;
|
||||
return new DefaultActionContext();
|
||||
}
|
||||
|
||||
ActionContext context = null;
|
||||
|
||||
+24
-6
@@ -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.
|
||||
@@ -17,6 +17,8 @@ package docking.widgets.fieldpanel.support;
|
||||
|
||||
import java.awt.Color;
|
||||
|
||||
import generic.json.Json;
|
||||
|
||||
public class Highlight {
|
||||
private int start;
|
||||
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() {
|
||||
return start + offset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ending position (inclusive) of the highlight.
|
||||
* {@return the ending position (inclusive) of the highlight}
|
||||
*/
|
||||
public int getEnd() {
|
||||
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() {
|
||||
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
|
||||
* 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.
|
||||
*/
|
||||
public void setOffset(int newOffset) {
|
||||
offset = newOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Json.toString(this);
|
||||
}
|
||||
}
|
||||
|
||||
+46
-3
@@ -16,6 +16,7 @@
|
||||
package docking.widgets.search;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.geom.Rectangle2D;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
@@ -307,7 +308,11 @@ public class TextComponentSearchResults extends SearchResults {
|
||||
try {
|
||||
int start = location.getStartIndexInclusive();
|
||||
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();
|
||||
endR.width += 20; // a little extra space so the view is not right at the text end
|
||||
Rectangle union = startR.union(endR);
|
||||
@@ -449,7 +454,7 @@ public class TextComponentSearchResults extends SearchResults {
|
||||
}
|
||||
|
||||
Color c = location.isActive() ? activeHighlightColor : highlightColor;
|
||||
HighlightPainter painter = new DefaultHighlightPainter(c);
|
||||
HighlightPainter painter = new SearchResultsHighlightPainter(c);
|
||||
int start = location.getStartIndexInclusive();
|
||||
int end = location.getEndIndexInclusive() + 1; // +1 to make inclusive be exclusive
|
||||
try {
|
||||
@@ -463,6 +468,7 @@ public class TextComponentSearchResults extends SearchResults {
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
|
||||
caretUpdater.dispose();
|
||||
|
||||
if (editorPane != null) {
|
||||
@@ -557,7 +563,11 @@ public class TextComponentSearchResults extends SearchResults {
|
||||
}
|
||||
|
||||
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);
|
||||
restoreNonSearchHighlights(nonSearchHighlights);
|
||||
}
|
||||
else {
|
||||
editorPane.setHighlighter(null);
|
||||
@@ -566,7 +576,30 @@ public class TextComponentSearchResults extends SearchResults {
|
||||
|
||||
@Override
|
||||
public void install(JTextComponent c) {
|
||||
Highlight[] nonSearchHighlights = delegate.getHighlights();
|
||||
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
|
||||
@@ -599,7 +632,11 @@ public class TextComponentSearchResults extends SearchResults {
|
||||
|
||||
@Override
|
||||
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
|
||||
@@ -611,6 +648,12 @@ public class TextComponentSearchResults extends SearchResults {
|
||||
public Highlight[] 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.Document;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import docking.widgets.CursorPosition;
|
||||
import docking.widgets.SearchLocation;
|
||||
import ghidra.util.Msg;
|
||||
@@ -235,6 +237,11 @@ public class TextComponentSearcher implements FindDialogSearcher {
|
||||
return;
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(fullText)) {
|
||||
Msg.error(this, "Cannot search a blank document");
|
||||
return;
|
||||
}
|
||||
|
||||
TreeMap<Integer, Line> lineRangeMap = mapLines(fullText);
|
||||
|
||||
Pattern pattern = createSearchPattern(searchText, useRegex);
|
||||
@@ -252,7 +259,6 @@ public class TextComponentSearcher implements FindDialogSearcher {
|
||||
context);
|
||||
matchesByPosition.put(start, location);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private TreeMap<Integer, Line> mapLines(String fullText) {
|
||||
|
||||
+30
-12
@@ -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.
|
||||
@@ -62,10 +62,14 @@ public abstract class GDynamicColumnTableModel<ROW_TYPE, DATA_SOURCE>
|
||||
protected ServiceProvider serviceProvider;
|
||||
|
||||
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<>();
|
||||
|
||||
/** 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;
|
||||
|
||||
public GDynamicColumnTableModel(ServiceProvider serviceProvider) {
|
||||
@@ -298,7 +302,7 @@ public abstract class GDynamicColumnTableModel<ROW_TYPE, DATA_SOURCE>
|
||||
protected void addTableColumns(Set<DynamicTableColumn<ROW_TYPE, ?, ?>> columns,
|
||||
boolean isDefault) {
|
||||
for (DynamicTableColumn<ROW_TYPE, ?, ?> column : columns) {
|
||||
doAddTableColumn(column, getDefaultTableColumns().size(), isDefault);
|
||||
doAddTableColumn(column, -1, isDefault);
|
||||
}
|
||||
fireTableStructureChanged();
|
||||
}
|
||||
@@ -327,16 +331,30 @@ public abstract class GDynamicColumnTableModel<ROW_TYPE, DATA_SOURCE>
|
||||
private void doAddTableColumn(DynamicTableColumn<ROW_TYPE, ?, ?> column, int index,
|
||||
boolean isDefault) {
|
||||
|
||||
if (index < 0 || index > tableColumns.size()) {
|
||||
index = getDefaultTableColumns().size();
|
||||
int adjustedIndex = index;
|
||||
if (adjustedIndex < 0 || adjustedIndex > tableColumns.size()) {
|
||||
adjustedIndex = tableColumns.size();
|
||||
}
|
||||
|
||||
tableColumns.add(index, column);
|
||||
tableColumns.add(adjustedIndex, column);
|
||||
columnSettings.put(column, new SettingsImpl(this, column));
|
||||
if (isDefault) {
|
||||
List<DynamicTableColumn<ROW_TYPE, ?, ?>> defaultColumns = getDefaultTableColumns();
|
||||
defaultColumns.add(index, column);
|
||||
|
||||
if (!isDefault) {
|
||||
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();
|
||||
|
||||
@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 templating will be simpler.
|
||||
DynamicTableColumn<ROW_TYPE, ?, DATA_SOURCE> column =
|
||||
|
||||
+11
-10
@@ -470,29 +470,30 @@ public class TableColumnModelState implements SortListener {
|
||||
if (preferenceKey != null) {
|
||||
return preferenceKey;
|
||||
}
|
||||
TableModel tableModel = table.getModel();
|
||||
|
||||
int columnCount = getDefaultColumnCount();
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
TableModel model = table.getModel();
|
||||
int n = model.getColumnCount();
|
||||
StringBuilder buffer = new StringBuilder();
|
||||
buffer.append(getTableModelName());
|
||||
buffer.append(":");
|
||||
for (int i = 0; i < columnCount; i++) {
|
||||
String columnName = tableModel.getColumnName(i);
|
||||
buffer.append(columnName).append(":");
|
||||
for (int i = 0; i < n; i++) {
|
||||
if (isDefaultColumn(i)) {
|
||||
String columnName = model.getColumnName(i);
|
||||
buffer.append(columnName).append(":");
|
||||
}
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
private int getDefaultColumnCount() {
|
||||
|
||||
private boolean isDefaultColumn(int col) {
|
||||
TableModel tableModel = table.getUnwrappedTableModel();
|
||||
if (tableModel instanceof VariableColumnTableModel) {
|
||||
VariableColumnTableModel variableTableModel = (VariableColumnTableModel) tableModel;
|
||||
// VariableColumnTableModels have default columns and 'found' columns. We only want to
|
||||
// create a key based upon the default columns
|
||||
return variableTableModel.getDefaultColumnCount();
|
||||
return variableTableModel.isDefaultColumn(col);
|
||||
}
|
||||
return tableModel.getColumnCount();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void setDefaultColumnsVisible() {
|
||||
|
||||
+6
-2
@@ -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.
|
||||
@@ -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
|
||||
* model is not such an instance, then null is returned.
|
||||
*
|
||||
* @param m the model
|
||||
* @return the variable column model
|
||||
*/
|
||||
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
|
||||
* 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
|
||||
* @return the identifier
|
||||
*/
|
||||
public String getUniqueIdentifier(int column);
|
||||
|
||||
@@ -56,7 +58,9 @@ public interface VariableColumnTableModel extends TableModel {
|
||||
* calling methods like {@link #getColumnName(int)}.
|
||||
*
|
||||
* @return Gets the count of the default columns for this model.
|
||||
* @deprecated no longer needed
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "12.1")
|
||||
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");
|
||||
* 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.
|
||||
@@ -35,12 +35,12 @@ public class StringDiff {
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @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 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 start position where the text was inserted
|
||||
|
||||
+7
-5
@@ -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.
|
||||
@@ -219,9 +219,11 @@ class StringDiffUtils {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Applies the array of StringDiffs to the string s to produce a new string.
|
||||
*
|
||||
* <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 diffs the array of StringDiff object to apply
|
||||
* @return a new String resulting from applying the diffs to s.
|
||||
|
||||
@@ -221,7 +221,8 @@ public class ApplicationVersion implements Comparable<ApplicationVersion> {
|
||||
}
|
||||
catch (NumberFormatException e) {
|
||||
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