mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2024-11-27 14:40:28 +00:00
Merge remote-tracking branch 'origin/GP-2050-dragonmacher-symbol-table-update'
This commit is contained in:
commit
624a12b9e5
@ -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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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.
|
||||
* <p>
|
||||
* 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:
|
||||
* <pre>
|
||||
* 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
|
||||
* </pre>
|
||||
*
|
||||
* Step 3, processing failed removals, is done to avoid a brute force lookup at each removal
|
||||
* request.
|
||||
*
|
||||
* <P>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<SymbolRowObject> {
|
||||
|
||||
@Override
|
||||
public void process(List<AddRemoveListItem<SymbolRowObject>> addRemoveList,
|
||||
TableData<SymbolRowObject> tableData,
|
||||
TaskMonitor monitor) throws CancelledException {
|
||||
|
||||
Set<AddRemoveListItem<SymbolRowObject>> 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<SymbolRowObject, SymbolRowObject> hashed = new HashMap<>();
|
||||
for (SymbolRowObject rowObject : tableData) {
|
||||
hashed.put(rowObject, rowObject);
|
||||
}
|
||||
|
||||
Set<SymbolRowObject> failedToRemove = new HashSet<>();
|
||||
|
||||
int n = items.size();
|
||||
monitor.setMessage("Removing " + n + " items...");
|
||||
monitor.initialize(n);
|
||||
|
||||
Iterator<AddRemoveListItem<SymbolRowObject>> it = items.iterator();
|
||||
while (it.hasNext()) {
|
||||
AddRemoveListItem<SymbolRowObject> 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<SymbolRowObject> 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<AddRemoveListItem<SymbolRowObject>> coalesceAddRemoveItems(
|
||||
List<AddRemoveListItem<SymbolRowObject>> addRemoveList) {
|
||||
|
||||
Map<Long, AddRemoveListItem<SymbolRowObject>> map = new HashMap<>();
|
||||
|
||||
for (AddRemoveListItem<SymbolRowObject> 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<SymbolRowObject> item,
|
||||
Map<Long, AddRemoveListItem<SymbolRowObject>> map) {
|
||||
|
||||
long id = item.getValue().getID();
|
||||
AddRemoveListItem<SymbolRowObject> 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<SymbolRowObject> item,
|
||||
Map<Long, AddRemoveListItem<SymbolRowObject>> map) {
|
||||
|
||||
long id = item.getValue().getID();
|
||||
AddRemoveListItem<SymbolRowObject> 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<SymbolRowObject> item,
|
||||
Map<Long, AddRemoveListItem<SymbolRowObject>> map) {
|
||||
|
||||
long id = item.getValue().getID();
|
||||
AddRemoveListItem<SymbolRowObject> 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<SymbolRowObject> tableData, SymbolRowObject symbol,
|
||||
Set<SymbolRowObject> 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<SymbolRowObject> expungeLostItems(Set<SymbolRowObject> toRemove,
|
||||
List<SymbolRowObject> data,
|
||||
TableSortingContext<SymbolRowObject> 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<SymbolRowObject> 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;
|
||||
}
|
||||
}
|
@ -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<SymbolRowObject> {
|
||||
private SymbolRowObject lastSymbol;
|
||||
private SymbolFilter filter;
|
||||
|
||||
private TableAddRemoveStrategy<SymbolRowObject> deletedDbObjectAddRemoveStrategy =
|
||||
new SymbolTableAddRemoveStrategy();
|
||||
|
||||
SymbolTableModel(SymbolProvider provider, PluginTool tool) {
|
||||
super("Symbols", tool, null, null);
|
||||
this.provider = provider;
|
||||
@ -91,11 +87,6 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TableAddRemoveStrategy<SymbolRowObject> getAddRemoveStrategy() {
|
||||
return deletedDbObjectAddRemoveStrategy;
|
||||
}
|
||||
|
||||
void setFilter(SymbolFilter filter) {
|
||||
this.filter = filter;
|
||||
reload();
|
||||
@ -364,12 +355,12 @@ class SymbolTableModel extends AddressBasedTableModel<SymbolRowObject> {
|
||||
protected Comparator<SymbolRowObject> createSortComparator(int columnIndex) {
|
||||
DynamicTableColumn<SymbolRowObject, ?, ?> 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<SymbolRowObject> {
|
||||
|
||||
@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;
|
||||
|
@ -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<SymbolRowObject> modelData;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
strategy = new SymbolTableAddRemoveStrategy();
|
||||
modelData = createModelData();
|
||||
spyTableData = createTableData();
|
||||
}
|
||||
|
||||
private SpyTableData createTableData() {
|
||||
Comparator<SymbolRowObject> comparator = (s1, s2) -> {
|
||||
return s1.toString().compareTo(s2.toString()); // based on symbol name
|
||||
};
|
||||
TableSortState sortState = TableSortState.createDefaultSortState(0);
|
||||
TableSortingContext<SymbolRowObject> sortContext =
|
||||
new TableSortingContext<>(sortState, comparator);
|
||||
|
||||
modelData.sort(comparator);
|
||||
|
||||
return new SpyTableData(modelData, sortContext);
|
||||
}
|
||||
|
||||
private List<SymbolRowObject> createModelData() {
|
||||
List<SymbolRowObject> 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<AddRemoveListItem<SymbolRowObject>> 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<AddRemoveListItem<SymbolRowObject>> 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<AddRemoveListItem<SymbolRowObject>> 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<AddRemoveListItem<SymbolRowObject>> 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<AddRemoveListItem<SymbolRowObject>> 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<AddRemoveListItem<SymbolRowObject>> 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<AddRemoveListItem<SymbolRowObject>> 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<AddRemoveListItem<SymbolRowObject>> 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<AddRemoveListItem<SymbolRowObject>> 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<AddRemoveListItem<SymbolRowObject>> 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<AddRemoveListItem<SymbolRowObject>> 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<AddRemoveListItem<SymbolRowObject>> 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<AddRemoveListItem<SymbolRowObject>> 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<AddRemoveListItem<SymbolRowObject>> 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<AddRemoveListItem<SymbolRowObject>> 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<AddRemoveListItem<SymbolRowObject>> 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<SymbolRowObject> {
|
||||
|
||||
private int removeCount;
|
||||
private int insertCount;
|
||||
|
||||
SpyTableData(List<SymbolRowObject> data, TableSortingContext<SymbolRowObject> 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();
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user