From 801eb01f4b4c5cd42e48b3412000dd301a520472 Mon Sep 17 00:00:00 2001
From: dragonmacher <48328597+dragonmacher@users.noreply.github.com>
Date: Wed, 8 Jun 2022 09:34:14 -0400
Subject: [PATCH] GP-2050 - Symbol Table - Removed old add/remove strategy
---
.../SymbolTableAddRemoveStrategy.java | 270 ---------
.../core/symtable/SymbolTableModel.java | 27 +-
.../SymbolTableAddRemoveStrategyTest.java | 567 ------------------
3 files changed, 8 insertions(+), 856 deletions(-)
delete mode 100644 Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableAddRemoveStrategy.java
delete mode 100644 Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symtable/SymbolTableAddRemoveStrategyTest.java
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableAddRemoveStrategy.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableAddRemoveStrategy.java
deleted file mode 100644
index a39df41132..0000000000
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableAddRemoveStrategy.java
+++ /dev/null
@@ -1,270 +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.app.plugin.core.symtable;
-
-import static docking.widgets.table.AddRemoveListItem.Type.*;
-
-import java.util.*;
-
-import docking.widgets.table.AddRemoveListItem;
-import docking.widgets.table.TableSortingContext;
-import docking.widgets.table.threaded.*;
-import ghidra.program.model.symbol.Symbol;
-import ghidra.util.exception.CancelledException;
-import ghidra.util.task.TaskMonitor;
-
-/**
- * The symbol table users a {@link ThreadedTableModel}. This model does not correctly function
- * with data that can change outside of the table. The symbol table's row objects are the
- * {@link Symbol} db objects. These db objects can be changed by the user and by analysis
- * while table is loaded. The problem with this is that the table's sort can be broken when
- * symbols are to be added, removed or re-inserted, as this process requires a binary search which
- * will be broken if the criteria used to sort the data has changed. Effectively, a symbol
- * change can break the binary search if that symbol stays in a previously sorted position, but
- * has updated data that would put the symbol in a new position if sorted again. For example,
- * if the table is sorted on name and the name of a symbol changes, then future uses of the
- * binary search will be broken while that symbol is still in the position that matches its old
- * name.
- *
- * This issue has been around for quite some time. To completely fix this issue, each row object
- * of the symbol table would need to be immutable, at least on the sort criteria. We could fix
- * this in the future if the *mostly correct* sorting behavior is not good enough. For now, the
- * client can trigger a re-sort (e.g., by opening and closing the table) to fix the slightly
- * out-of-sort data.
- *
- * The likelihood of the sort being inconsistent now relates directly to how many changed symbols
- * are in the table at the time of an insert. The more changes symbols, the higher the chance
- * of a stale/misplaced symbol being used during a binary search, thus producing an invalid insert
- * position.
- *
- * This strategy is setup to mitigate the number of invalid symbols in the table at the
- * time the inserts are applied. The basic workflow of this algorithm is:
- *
- * 1) condense the add / remove requests to remove duplicate efforts
- * 2) process all removes first
- * --all pure removes
- * --all removes as part of a re-insert
- * 3) process all items that failed to remove due to the sort data changing
- * 4) process all adds (this step will fail if the data contains mis-sorted items)
- * --all adds as part of a re-insert
- * --all pure adds
- *
- *
- * Step 3, processing failed removals, is done to avoid a brute force lookup at each removal
- * request.
- *
- * This strategy has knowledge of client proxy object usage. The proxy objects
- * are coded such that the {@code hashCode()} and {@code equals()} methods will match those
- * methods of the data's real objects.
- */
-public class SymbolTableAddRemoveStrategy implements TableAddRemoveStrategy {
-
- @Override
- public void process(List> addRemoveList,
- TableData tableData,
- TaskMonitor monitor) throws CancelledException {
-
- Set> items = coalesceAddRemoveItems(addRemoveList);
-
- //
- // Hash map the existing values so that we can use any object inside the add/remove list
- // as a key into this map to get the matching existing value. Using the existing value
- // enables the binary search to work when the add/remove item is a proxy object, but the
- // existing item still has the data used to sort it. If the sort data has changed, then
- // even this step will not allow the TableData to find the item in a search.
- //
- Map hashed = new HashMap<>();
- for (SymbolRowObject rowObject : tableData) {
- hashed.put(rowObject, rowObject);
- }
-
- Set failedToRemove = new HashSet<>();
-
- int n = items.size();
- monitor.setMessage("Removing " + n + " items...");
- monitor.initialize(n);
-
- Iterator> it = items.iterator();
- while (it.hasNext()) {
- AddRemoveListItem item = it.next();
- SymbolRowObject value = item.getValue();
- if (item.isChange()) {
- SymbolRowObject toRemove = hashed.remove(value);
- remove(tableData, toRemove, failedToRemove);
- monitor.incrementProgress(1);
- }
- else if (item.isRemove()) {
- SymbolRowObject toRemove = hashed.remove(value);
- remove(tableData, toRemove, failedToRemove);
- it.remove();
- }
- monitor.checkCanceled();
- }
-
- if (!failedToRemove.isEmpty()) {
- int size = failedToRemove.size();
- String message = size == 1 ? "1 old symbol..." : size + " old symbols...";
- monitor.setMessage("Removing " + message);
-
- tableData.process((data, sortContext) -> {
- return expungeLostItems(failedToRemove, data, sortContext);
- });
- }
-
- n = items.size();
- monitor.setMessage("Adding " + n + " items...");
- it = items.iterator();
- while (it.hasNext()) {
- AddRemoveListItem item = it.next();
- SymbolRowObject value = item.getValue();
- if (item.isChange()) {
- tableData.insert(value);
- hashed.put(value, value);
- }
- else if (item.isAdd()) {
- tableData.insert(value);
- hashed.put(value, value);
- }
- monitor.checkCanceled();
- monitor.incrementProgress(1);
- }
-
- monitor.setMessage("Done adding/removing");
- }
-
- private Set> coalesceAddRemoveItems(
- List> addRemoveList) {
-
- Map> map = new HashMap<>();
-
- for (AddRemoveListItem item : addRemoveList) {
-
- if (item.isChange()) {
- handleChange(item, map);
- }
- else if (item.isAdd()) {
- handleAdd(item, map);
- }
- else {
- handleRemove(item, map);
- }
- }
-
- return new HashSet<>(map.values());
- }
-
- private void handleAdd(AddRemoveListItem item,
- Map> map) {
-
- long id = item.getValue().getID();
- AddRemoveListItem existing = map.get(id);
- if (existing == null) {
- map.put(id, item);
- return;
- }
-
- if (existing.isChange()) {
- return; // change -> add; keep the change
- }
- if (existing.isAdd()) {
- return; // already an add
- }
-
- // remove -> add; make a change
- map.put(id, new AddRemoveListItem<>(CHANGE, existing.getValue()));
- }
-
- private void handleRemove(AddRemoveListItem item,
- Map> map) {
-
- long id = item.getValue().getID();
- AddRemoveListItem existing = map.get(id);
- if (existing == null) {
- map.put(id, item);
- return;
- }
-
- if (existing.isChange()) {
- map.put(id, item); // change -> remove; just do the remove
- return;
- }
- if (existing.isRemove()) {
- return;
- }
-
- // add -> remove; do no work
- map.remove(id);
- }
-
- private void handleChange(AddRemoveListItem item,
- Map> map) {
-
- long id = item.getValue().getID();
- AddRemoveListItem existing = map.get(id);
- if (existing == null) {
- map.put(id, item);
- return;
- }
-
- if (!existing.isChange()) {
- // either add or remove followed by a change; keep the change
- map.put(id, item);
- }
-
- // otherwise, we had a change followed by a change; keep just 1 change
- }
-
- private void remove(TableData tableData, SymbolRowObject symbol,
- Set failedToRemove) {
- if (symbol == null) {
- return;
- }
-
- if (!tableData.remove(symbol)) {
- failedToRemove.add(symbol);
- }
- }
-
- /*
- * Removes the given set of items that were unsuccessfully removed from the table as part of
- * the add/remove process. These items could not be removed because some part of their
- * state has changed such that the binary search performed during the normal remove process
- * cannot locate the item in the table data. This algorithm will check the given set of
- * items against the entire list of table data, locating the item to be removed.
- */
- private List expungeLostItems(Set toRemove,
- List data,
- TableSortingContext sortContext) {
-
- if (sortContext.isUnsorted()) {
- // this can happen if the data is unsorted and we were asked to remove an item that
- // was never in the table for some reason
- return data;
- }
-
- // Copy to a new list those items that are not marked for removal. This saves the
- // list move its items every time a remove takes place
- List newList = new ArrayList<>(data.size() - toRemove.size());
- for (int i = 0; i < data.size(); i++) {
- SymbolRowObject rowObject = data.get(i);
- if (!toRemove.contains(rowObject)) {
- newList.add(rowObject);
- }
- }
-
- return newList;
- }
-}
diff --git a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableModel.java b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableModel.java
index 5b71c4f4a8..319b75ad59 100644
--- a/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableModel.java
+++ b/Ghidra/Features/Base/src/main/java/ghidra/app/plugin/core/symtable/SymbolTableModel.java
@@ -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.
@@ -18,7 +18,6 @@ package ghidra.app.plugin.core.symtable;
import java.util.*;
import docking.widgets.table.*;
-import docking.widgets.table.threaded.TableAddRemoveStrategy;
import ghidra.app.cmd.function.DeleteFunctionCmd;
import ghidra.app.cmd.label.DeleteLabelCmd;
import ghidra.app.cmd.label.RenameLabelCmd;
@@ -61,9 +60,6 @@ class SymbolTableModel extends AddressBasedTableModel {
private SymbolRowObject lastSymbol;
private SymbolFilter filter;
- private TableAddRemoveStrategy deletedDbObjectAddRemoveStrategy =
- new SymbolTableAddRemoveStrategy();
-
SymbolTableModel(SymbolProvider provider, PluginTool tool) {
super("Symbols", tool, null, null);
this.provider = provider;
@@ -91,11 +87,6 @@ class SymbolTableModel extends AddressBasedTableModel {
return descriptor;
}
- @Override
- protected TableAddRemoveStrategy getAddRemoveStrategy() {
- return deletedDbObjectAddRemoveStrategy;
- }
-
void setFilter(SymbolFilter filter) {
this.filter = filter;
reload();
@@ -364,12 +355,12 @@ class SymbolTableModel extends AddressBasedTableModel {
protected Comparator createSortComparator(int columnIndex) {
DynamicTableColumn column = getColumn(columnIndex);
if (column instanceof NameTableColumn) {
- // note: we use our own name comparator to increase sorting speed for the name
- // column. This works because this comparator is called for each *row object*
- // allowing the comparator to compare the Symbols based on name instead of
+ // note: we use our own name comparator to increase sorting speed for the name
+ // column. This works because this comparator is called for each *row object*
+ // allowing the comparator to compare the Symbols based on name instead of
// having to use the table model's code for getting a column value for the
// row object. The code for retrieving a column value is slower than just
- // working with the row object directly. See
+ // working with the row object directly. See
// ThreadedTableModel.getCachedColumnValueForRow for more info.
return NAME_COL_COMPARATOR;
}
@@ -436,16 +427,14 @@ class SymbolTableModel extends AddressBasedTableModel {
@Override
public AddressBasedLocation getValue(SymbolRowObject rowObject, Settings settings,
- Program p,
- ServiceProvider svcProvider) throws IllegalArgumentException {
+ Program p, ServiceProvider svcProvider) throws IllegalArgumentException {
Symbol symbol = rowObject.getSymbol();
return getSymbolLocation(symbol);
}
@Override
public ProgramLocation getProgramLocation(SymbolRowObject rowObject, Settings settings,
- Program p,
- ServiceProvider svcProvider) {
+ Program p, ServiceProvider svcProvider) {
Symbol symbol = rowObject.getSymbol();
if (symbol == null || symbol.isDeleted()) {
return null;
diff --git a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symtable/SymbolTableAddRemoveStrategyTest.java b/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symtable/SymbolTableAddRemoveStrategyTest.java
deleted file mode 100644
index 3c44514d00..0000000000
--- a/Ghidra/Features/Base/src/test.slow/java/ghidra/app/plugin/core/symtable/SymbolTableAddRemoveStrategyTest.java
+++ /dev/null
@@ -1,567 +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.app.plugin.core.symtable;
-
-import static docking.widgets.table.AddRemoveListItem.Type.*;
-import static org.junit.Assert.*;
-
-import java.util.*;
-
-import org.junit.Before;
-import org.junit.Test;
-
-import docking.widgets.table.*;
-import docking.widgets.table.threaded.TestTableData;
-import ghidra.program.model.ProgramTestDouble;
-import ghidra.program.model.address.Address;
-import ghidra.program.model.address.TestAddress;
-import ghidra.program.model.listing.Program;
-import ghidra.program.model.symbol.*;
-import ghidra.program.util.ProgramLocation;
-import ghidra.util.task.TaskMonitor;
-
-public class SymbolTableAddRemoveStrategyTest {
-
- private static Program DUMMY_PROGRAM = new ProgramTestDouble();
-
- private SymbolTableAddRemoveStrategy strategy;
- private SpyTableData spyTableData;
- private List modelData;
-
- @Before
- public void setUp() throws Exception {
- strategy = new SymbolTableAddRemoveStrategy();
- modelData = createModelData();
- spyTableData = createTableData();
- }
-
- private SpyTableData createTableData() {
- Comparator comparator = (s1, s2) -> {
- return s1.toString().compareTo(s2.toString()); // based on symbol name
- };
- TableSortState sortState = TableSortState.createDefaultSortState(0);
- TableSortingContext sortContext =
- new TableSortingContext<>(sortState, comparator);
-
- modelData.sort(comparator);
-
- return new SpyTableData(modelData, sortContext);
- }
-
- private List createModelData() {
- List data = new ArrayList<>();
- data.add(new TestSymbolRowObject(new TestSymbol(1, new TestAddress(101))));
- data.add(new TestSymbolRowObject(new TestSymbol(2, new TestAddress(102))));
- data.add(new TestSymbolRowObject(new TestSymbol(3, new TestAddress(103))));
- data.add(new TestSymbolRowObject(new TestSymbol(4, new TestAddress(104))));
- return data;
- }
-
- @Test
- public void testRemove_DifferentInstance_SameId() throws Exception {
-
- List> addRemoves = new ArrayList<>();
-
- SymbolRowObject s = new TestSymbolRowObject(new TestSymbol(1, new TestAddress(101)));
- addRemoves.add(new AddRemoveListItem<>(REMOVE, s));
-
- strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
-
- assertEquals(1, spyTableData.getRemoveCount());
- assertEquals(0, spyTableData.getInsertCount());
- }
-
- @Test
- public void testInsert_NewSymbol() throws Exception {
-
- List> addRemoves = new ArrayList<>();
-
- SymbolRowObject newSymbol =
- new TestSymbolRowObject(new TestSymbol(10, new TestAddress(1010)));
- addRemoves.add(new AddRemoveListItem<>(ADD, newSymbol));
-
- strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
- assertEquals(0, spyTableData.getRemoveCount());
- assertEquals(1, spyTableData.getInsertCount());
- }
-
- @Test
- public void testInsertAndRemove_NewSymbol() throws Exception {
-
- List> addRemoves = new ArrayList<>();
-
- SymbolRowObject newSymbol =
- new TestSymbolRowObject(new TestSymbol(10, new TestAddress(1010)));
- addRemoves.add(new AddRemoveListItem<>(ADD, newSymbol));
- addRemoves.add(new AddRemoveListItem<>(REMOVE, newSymbol));
-
- strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
-
- // no work was done, since the insert was followed by a remove
- assertEquals(0, spyTableData.getRemoveCount());
- assertEquals(0, spyTableData.getInsertCount());
- }
-
- @Test
- public void testChange_NewSymbol() throws Exception {
- List> addRemoves = new ArrayList<>();
-
- SymbolRowObject newSymbol =
- new TestSymbolRowObject(new TestSymbol(10, new TestAddress(1010)));
- addRemoves.add(new AddRemoveListItem<>(CHANGE, newSymbol));
-
- strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
-
- // no remove, since the symbol was not in the table
- assertEquals(0, spyTableData.getRemoveCount());
- assertEquals(1, spyTableData.getInsertCount());
- }
-
- @Test
- public void testChange_ExisingSymbol() throws Exception {
- List> addRemoves = new ArrayList<>();
-
- SymbolRowObject s = modelData.get(0);
- addRemoves.add(new AddRemoveListItem<>(CHANGE, s));
-
- strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
-
- // no remove, since the symbol was not in the table
- assertEquals(1, spyTableData.getRemoveCount());
- assertEquals(1, spyTableData.getInsertCount());
- }
-
- @Test
- public void testRemoveAndInsert_NewSymbol() throws Exception {
-
- List> addRemoves = new ArrayList<>();
-
- SymbolRowObject newSymbol =
- new TestSymbolRowObject(new TestSymbol(10, new TestAddress(1010)));
- addRemoves.add(new AddRemoveListItem<>(REMOVE, newSymbol));
- addRemoves.add(new AddRemoveListItem<>(ADD, newSymbol));
-
- strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
-
- // the remove does not happen, since the time was not in the table
- assertEquals(0, spyTableData.getRemoveCount());
- assertEquals(1, spyTableData.getInsertCount());
- }
-
- @Test
- public void testRemoveAndInsert_ExistingSymbol() throws Exception {
-
- List> addRemoves = new ArrayList<>();
-
- SymbolRowObject s = modelData.get(0);
- addRemoves.add(new AddRemoveListItem<>(REMOVE, s));
- addRemoves.add(new AddRemoveListItem<>(ADD, s));
-
- strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
-
- // the remove does not happen, since the time was not in the table
- assertEquals(1, spyTableData.getRemoveCount());
- assertEquals(1, spyTableData.getInsertCount());
- }
-
- @Test
- public void testChangeAndInsert_ExistingSymbol() throws Exception {
-
- List> addRemoves = new ArrayList<>();
-
- SymbolRowObject s = modelData.get(0);
- addRemoves.add(new AddRemoveListItem<>(CHANGE, s));
- addRemoves.add(new AddRemoveListItem<>(ADD, s));
-
- strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
-
- // the insert portions get coalesced
- assertEquals(1, spyTableData.getRemoveCount());
- assertEquals(1, spyTableData.getInsertCount());
- }
-
- @Test
- public void testChangeAndRemove_ExistingSymbol() throws Exception {
-
- List> addRemoves = new ArrayList<>();
-
- SymbolRowObject s = modelData.get(0);
- addRemoves.add(new AddRemoveListItem<>(CHANGE, s));
- addRemoves.add(new AddRemoveListItem<>(REMOVE, s));
-
- strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
-
- // the remove portions get coalesced; no insert takes place
- assertEquals(1, spyTableData.getRemoveCount());
- assertEquals(0, spyTableData.getInsertCount());
- }
-
- @Test
- public void testChangeAndChange_ExistingSymbol() throws Exception {
-
- List> addRemoves = new ArrayList<>();
-
- SymbolRowObject s = modelData.get(0);
- addRemoves.add(new AddRemoveListItem<>(CHANGE, s));
- addRemoves.add(new AddRemoveListItem<>(CHANGE, s));
-
- strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
-
- // the changes get coalesced
- assertEquals(1, spyTableData.getRemoveCount());
- assertEquals(1, spyTableData.getInsertCount());
- }
-
- @Test
- public void testRemoveAndRemove_ExistingSymbol() throws Exception {
-
- List> addRemoves = new ArrayList<>();
-
- SymbolRowObject s = modelData.get(0);
- addRemoves.add(new AddRemoveListItem<>(REMOVE, s));
- addRemoves.add(new AddRemoveListItem<>(REMOVE, s));
-
- strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
-
- // the removes get coalesced
- assertEquals(1, spyTableData.getRemoveCount());
- assertEquals(0, spyTableData.getInsertCount());
- }
-
- @Test
- public void testInsertAndInsert_ExistingSymbol() throws Exception {
-
- List> addRemoves = new ArrayList<>();
-
- SymbolRowObject s = modelData.get(0);
- addRemoves.add(new AddRemoveListItem<>(ADD, s));
- addRemoves.add(new AddRemoveListItem<>(ADD, s));
-
- strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
-
- // the inserts get coalesced
- assertEquals(0, spyTableData.getRemoveCount());
- assertEquals(1, spyTableData.getInsertCount());
- }
-
- @Test
- public void testInsertAndChange_ExistingSymbol() throws Exception {
-
- List> addRemoves = new ArrayList<>();
-
- SymbolRowObject s = modelData.get(0);
- addRemoves.add(new AddRemoveListItem<>(ADD, s));
- addRemoves.add(new AddRemoveListItem<>(CHANGE, s));
-
- strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
-
- // the insert portions get coalesced
- assertEquals(1, spyTableData.getRemoveCount());
- assertEquals(1, spyTableData.getInsertCount());
- }
-
- @Test
- public void testRemoveAndChange_ExistingSymbol() throws Exception {
-
- List> addRemoves = new ArrayList<>();
-
- SymbolRowObject s = modelData.get(0);
- addRemoves.add(new AddRemoveListItem<>(REMOVE, s));
- addRemoves.add(new AddRemoveListItem<>(CHANGE, s));
-
- strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
-
- // the remove portions get coalesced
- assertEquals(1, spyTableData.getRemoveCount());
- assertEquals(1, spyTableData.getInsertCount());
- }
-
- @Test
- public void testLostItems_Remove() throws Exception {
-
- //
- // Test that symbols get removed when the data up on which they are sorted changes before
- // the removal takes place
- //
- List> addRemoves = new ArrayList<>();
-
- TestSymbol symbol = (TestSymbol) modelData.get(0).getSymbol();
- symbol.setName("UpdatedName");
- addRemoves.add(new AddRemoveListItem<>(REMOVE, new TestSymbolRowObject(symbol)));
-
- strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
-
- // the insert portions get coalesced
- assertEquals(1, spyTableData.getRemoveCount());
- assertEquals(0, spyTableData.getInsertCount());
- }
-
- @Test
- public void testLostItems_Change() throws Exception {
-
- //
- // Test that symbols get removed when the data up on which they are sorted changes before
- // the removal takes place
- //
- List> addRemoves = new ArrayList<>();
-
- TestSymbol symbol = (TestSymbol) modelData.get(0).getSymbol();
- symbol.setName("UpdatedName");
- addRemoves.add(new AddRemoveListItem<>(CHANGE, new TestSymbolRowObject(symbol)));
-
- strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
-
- // the insert portions get coalesced
- assertEquals(1, spyTableData.getRemoveCount());
- assertEquals(1, spyTableData.getInsertCount());
- }
-
-//==================================================================================================
-// Inner Classes
-//==================================================================================================
-
- private class SpyTableData extends TestTableData {
-
- private int removeCount;
- private int insertCount;
-
- SpyTableData(List data, TableSortingContext sortContext) {
- super(data, sortContext);
- }
-
- @Override
- public boolean remove(SymbolRowObject t) {
- removeCount++;
- return super.remove(t);
- }
-
- @Override
- public void insert(SymbolRowObject value) {
- insertCount++;
- super.insert(value);
- }
-
- int getRemoveCount() {
- return removeCount;
- }
-
- int getInsertCount() {
- return insertCount;
- }
- }
-
- private class TestSymbolRowObject extends SymbolRowObject {
-
- private TestSymbol sym;
-
- public TestSymbolRowObject(TestSymbol s) {
- super(s);
- this.sym = s;
- }
-
- @Override
- public Symbol getSymbol() {
- return sym;
- }
- }
-
- private class TestSymbol implements Symbol {
-
- private long id;
- private Address address;
- private String name;
-
- TestSymbol(long id, Address address) {
- this.id = id;
- this.address = address;
- name = id + "@" + address;
- }
-
- @Override
- public long getID() {
- return id;
- }
-
- @Override
- public Address getAddress() {
- return address;
- }
-
- void setName(String name) {
- this.name = name;
- }
-
- @Override
- public String getName() {
- return name;
- }
-
- @Override
- public boolean isDeleted() {
- return false;
- }
-
- @Override
- public String toString() {
- return name;
- }
-
- @Override
- public Program getProgram() {
- return DUMMY_PROGRAM;
- }
-
- @Override
- public SymbolType getSymbolType() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public ProgramLocation getProgramLocation() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isExternal() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Object getObject() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isPrimary() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isValidParent(Namespace parent) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String[] getPath() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getName(boolean includeNamespace) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Namespace getParentNamespace() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Symbol getParentSymbol() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isDescendant(Namespace namespace) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public int getReferenceCount() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean hasMultipleReferences() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean hasReferences() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Reference[] getReferences(TaskMonitor monitor) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public Reference[] getReferences() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setName(String newName, SourceType source) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setNamespace(Namespace newNamespace) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setNameAndNamespace(String newName, Namespace newNamespace, SourceType source) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean delete() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isPinned() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setPinned(boolean pinned) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isDynamic() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean setPrimary() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isExternalEntryPoint() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean isGlobal() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public void setSource(SourceType source) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public SourceType getSource() {
- throw new UnsupportedOperationException();
- }
- }
-}