mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2025-10-05 19:42:36 +02:00
Merge remote-tracking branch 'origin/GP-2042-dragonmacher-table-row-update-issue--SQUASHED'
This commit is contained in:
commit
f672ba46b7
8 changed files with 747 additions and 139 deletions
|
@ -18,15 +18,17 @@ package ghidra.app.plugin.core.functionwindow;
|
||||||
import java.awt.BorderLayout;
|
import java.awt.BorderLayout;
|
||||||
import java.awt.Dimension;
|
import java.awt.Dimension;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import javax.swing.table.JTableHeader;
|
import javax.swing.table.*;
|
||||||
|
|
||||||
import docking.ActionContext;
|
import docking.ActionContext;
|
||||||
import ghidra.app.services.GoToService;
|
import ghidra.app.services.GoToService;
|
||||||
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
import ghidra.framework.plugintool.ComponentProviderAdapter;
|
||||||
import ghidra.program.model.listing.Function;
|
import ghidra.program.model.address.Address;
|
||||||
import ghidra.program.model.listing.Program;
|
import ghidra.program.model.listing.*;
|
||||||
import ghidra.program.util.ProgramSelection;
|
import ghidra.program.util.ProgramSelection;
|
||||||
import ghidra.util.HelpLocation;
|
import ghidra.util.HelpLocation;
|
||||||
import ghidra.util.table.*;
|
import ghidra.util.table.*;
|
||||||
|
@ -161,16 +163,44 @@ public class FunctionWindowProvider extends ComponentProviderAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setFunctionTableRenderer() {
|
private void setFunctionTableRenderer() {
|
||||||
functionTable.getColumnModel()
|
TableColumnModel columnModel = functionTable.getColumnModel();
|
||||||
.getColumn(FunctionTableModel.LOCATION_COL)
|
TableColumn column = columnModel.getColumn(FunctionTableModel.LOCATION_COL);
|
||||||
.setPreferredWidth(
|
column.setPreferredWidth(FunctionTableModel.LOCATION_COL_WIDTH);
|
||||||
FunctionTableModel.LOCATION_COL_WIDTH);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void update(Function function) {
|
void update(Function function) {
|
||||||
if (isVisible()) {
|
if (!isVisible()) {
|
||||||
functionModel.update(function);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Set<Function> functions = getRelatedFunctions(function);
|
||||||
|
for (Function f : functions) {
|
||||||
|
functionModel.update(f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gathers this function and any functions that thunk it
|
||||||
|
* @param f the function
|
||||||
|
* @return the related functions
|
||||||
|
*/
|
||||||
|
private Set<Function> getRelatedFunctions(Function f) {
|
||||||
|
|
||||||
|
Program program = f.getProgram();
|
||||||
|
FunctionManager functionManager = program.getFunctionManager();
|
||||||
|
Set<Function> functions = new HashSet<>();
|
||||||
|
Address[] addresses = f.getFunctionThunkAddresses(true);
|
||||||
|
if (addresses != null) {
|
||||||
|
for (Address a : addresses) {
|
||||||
|
Function thunk = functionManager.getFunctionAt(a);
|
||||||
|
if (thunk != null) {
|
||||||
|
functions.add(thunk);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
functions.add(f);
|
||||||
|
return functions;
|
||||||
}
|
}
|
||||||
|
|
||||||
void functionAdded(Function function) {
|
void functionAdded(Function function) {
|
||||||
|
|
|
@ -60,6 +60,8 @@ class SymbolTableModel extends AddressBasedTableModel<Symbol> {
|
||||||
private ReferenceManager refMgr;
|
private ReferenceManager refMgr;
|
||||||
private Symbol lastSymbol;
|
private Symbol lastSymbol;
|
||||||
private SymbolFilter filter;
|
private SymbolFilter filter;
|
||||||
|
|
||||||
|
// TODO this can be removed after GP-2030 is finished
|
||||||
private TableAddRemoveStrategy<Symbol> deletedDbObjectAddRemoveStrategy =
|
private TableAddRemoveStrategy<Symbol> deletedDbObjectAddRemoveStrategy =
|
||||||
new SymbolTableAddRemoveStrategy();
|
new SymbolTableAddRemoveStrategy();
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,264 @@
|
||||||
|
/* ###
|
||||||
|
* 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 docking.widgets.table;
|
||||||
|
|
||||||
|
import static docking.widgets.table.AddRemoveListItem.Type.*;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import docking.widgets.table.threaded.*;
|
||||||
|
import ghidra.util.exception.CancelledException;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link ThreadedTableModel} does not correctly function with data that can change outside of
|
||||||
|
* the table. For example, if a table uses db objects as row objects, these db objects can be
|
||||||
|
* changed by the user and by analysis while table has already been loaded. The problem with this
|
||||||
|
* is that the table's sort can be broken when new items 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 row object change can break the binary search if that item
|
||||||
|
* 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 an item
|
||||||
|
* changes, then future uses of the binary search will be broken while that item 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 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 items are
|
||||||
|
* in the table at the time of an insert. The more changed items, the higher the chance of a
|
||||||
|
* stale/misplaced item being used during a binary search, thus producing an invalid insert
|
||||||
|
* position.
|
||||||
|
* <p>
|
||||||
|
* This strategy is setup to mitigate the number of invalid items 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 allows for the use of client proxy objects. The proxy objects should be coded
|
||||||
|
* such that the {@code hashCode()} and {@code equals()} methods will match those methods of the
|
||||||
|
* data's real objects. These proxy objects allow clients to search for an item without having a
|
||||||
|
* reference to the actual item. In this sense, the proxy object is equal to the existing row
|
||||||
|
* object in the table model, but is not the <b>same</b> instance as the row object.
|
||||||
|
*
|
||||||
|
* @param <T> the row type
|
||||||
|
*/
|
||||||
|
public class CoalescingAddRemoveStrategy<T> implements TableAddRemoveStrategy<T> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process(List<AddRemoveListItem<T>> addRemoveList, TableData<T> tableData,
|
||||||
|
TaskMonitor monitor) throws CancelledException {
|
||||||
|
|
||||||
|
Set<AddRemoveListItem<T>> 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<T, T> hashed = new HashMap<>();
|
||||||
|
for (T t : tableData) {
|
||||||
|
hashed.put(t, t);
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<T> failedToRemove = new HashSet<>();
|
||||||
|
|
||||||
|
int n = items.size();
|
||||||
|
monitor.setMessage("Removing " + n + " items...");
|
||||||
|
monitor.initialize(n);
|
||||||
|
|
||||||
|
Iterator<AddRemoveListItem<T>> it = items.iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
AddRemoveListItem<T> item = it.next();
|
||||||
|
T value = item.getValue();
|
||||||
|
if (item.isChange()) {
|
||||||
|
T toRemove = hashed.remove(value);
|
||||||
|
remove(tableData, toRemove, failedToRemove);
|
||||||
|
monitor.incrementProgress(1);
|
||||||
|
}
|
||||||
|
else if (item.isRemove()) {
|
||||||
|
T 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<T> item = it.next();
|
||||||
|
T 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<T>> coalesceAddRemoveItems(
|
||||||
|
List<AddRemoveListItem<T>> addRemoveList) {
|
||||||
|
|
||||||
|
Map<T, AddRemoveListItem<T>> map = new HashMap<>();
|
||||||
|
|
||||||
|
for (AddRemoveListItem<T> 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<T> item, Map<T, AddRemoveListItem<T>> map) {
|
||||||
|
|
||||||
|
T rowObject = item.getValue();
|
||||||
|
AddRemoveListItem<T> existing = map.get(rowObject);
|
||||||
|
if (existing == null) {
|
||||||
|
map.put(rowObject, 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(rowObject, new AddRemoveListItem<>(CHANGE, existing.getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleRemove(AddRemoveListItem<T> item, Map<T, AddRemoveListItem<T>> map) {
|
||||||
|
|
||||||
|
T rowObject = item.getValue();
|
||||||
|
AddRemoveListItem<T> existing = map.get(rowObject);
|
||||||
|
if (existing == null) {
|
||||||
|
map.put(rowObject, item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existing.isChange()) {
|
||||||
|
map.put(rowObject, item); // change -> remove; just do the remove
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (existing.isRemove()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// add -> remove; do no work
|
||||||
|
map.remove(rowObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleChange(AddRemoveListItem<T> item, Map<T, AddRemoveListItem<T>> map) {
|
||||||
|
|
||||||
|
T rowObject = item.getValue();
|
||||||
|
AddRemoveListItem<T> existing = map.get(rowObject);
|
||||||
|
if (existing == null) {
|
||||||
|
map.put(rowObject, item);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!existing.isChange()) {
|
||||||
|
// either add or remove followed by a change; keep the change
|
||||||
|
map.put(rowObject, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, we had a change followed by a change; keep just 1 change
|
||||||
|
}
|
||||||
|
|
||||||
|
private void remove(TableData<T> tableData, T t, Set<T> failedToRemove) {
|
||||||
|
if (t == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tableData.remove(t)) {
|
||||||
|
failedToRemove.add(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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<T> expungeLostItems(Set<T> toRemove, List<T> data,
|
||||||
|
TableSortingContext<T> 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<T> newList = new ArrayList<>(data.size() - toRemove.size());
|
||||||
|
for (int i = 0; i < data.size(); i++) {
|
||||||
|
T rowObject = data.get(i);
|
||||||
|
if (!toRemove.contains(rowObject)) {
|
||||||
|
newList.add(rowObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newList;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
/* ###
|
/* ###
|
||||||
* IP: GHIDRA
|
* IP: GHIDRA
|
||||||
* REVIEWED: YES
|
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -16,15 +15,15 @@
|
||||||
*/
|
*/
|
||||||
package docking.widgets.table;
|
package docking.widgets.table;
|
||||||
|
|
||||||
import ghidra.util.SystemUtilities;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.swing.table.TableModel;
|
import javax.swing.table.TableModel;
|
||||||
|
|
||||||
|
import ghidra.util.SystemUtilities;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An object that represents a row in a table. Most tables used in the system create tables that
|
* An object that represents a row in a table. Most tables used in the system create models that
|
||||||
* use their own row objects (see {@link AbstractSortedTableModel}). This class exists to
|
* use their own row objects (see {@link AbstractSortedTableModel}). This class exists to
|
||||||
* compensate for those models that do not do this, but instead rely on the classic Java
|
* compensate for those models that do not do this, but instead rely on the classic Java
|
||||||
* {@link TableModel} method {@link TableModel#getValueAt(int, int)}.
|
* {@link TableModel} method {@link TableModel#getValueAt(int, int)}.
|
||||||
|
@ -36,14 +35,18 @@ import javax.swing.table.TableModel;
|
||||||
* row objects that created for non-{@link AbstractSortedTableModel}s will not be equal to
|
* row objects that created for non-{@link AbstractSortedTableModel}s will not be equal to
|
||||||
* those created before the data change. This causes some features to break, such as selection
|
* those created before the data change. This causes some features to break, such as selection
|
||||||
* restoration after user edits.
|
* restoration after user edits.
|
||||||
|
*
|
||||||
|
* @deprecated this class is no longer used and will be removed
|
||||||
*/
|
*/
|
||||||
|
@Deprecated(forRemoval = true, since = "10.1")
|
||||||
public class RowObject {
|
public class RowObject {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory method to create and initialize a row object.
|
* Factory method to create and initialize a row object.
|
||||||
*
|
*
|
||||||
* @param model the model required to gather data for the row object.
|
* @param model the model required to gather data for the row object.
|
||||||
* @param row the row for which to create a row object * @return
|
* @param row the row for which to create a row object
|
||||||
|
* @return the row object
|
||||||
*/
|
*/
|
||||||
public static RowObject createRowObject(TableModel model, int row) {
|
public static RowObject createRowObject(TableModel model, int row) {
|
||||||
RowObject rowObject = new RowObject();
|
RowObject rowObject = new RowObject();
|
||||||
|
@ -54,7 +57,7 @@ public class RowObject {
|
||||||
return rowObject;
|
return rowObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Object> values = new ArrayList<Object>();
|
List<Object> values = new ArrayList<>();
|
||||||
int hash = -1;
|
int hash = -1;
|
||||||
|
|
||||||
void addElement(Object object) {
|
void addElement(Object object) {
|
||||||
|
|
|
@ -1,95 +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 docking.widgets.table.threaded;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import docking.widgets.table.AddRemoveListItem;
|
|
||||||
import docking.widgets.table.TableSortingContext;
|
|
||||||
import ghidra.util.exception.CancelledException;
|
|
||||||
import ghidra.util.task.TaskMonitor;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A strategy that uses the table's sort state to perform a binary search of items to be added
|
|
||||||
* and removed.
|
|
||||||
*
|
|
||||||
* <P>This strategy is aware that some items may not be correctly removed, such as database items
|
|
||||||
* that have been deleted externally to the table. These items may require a brute force update
|
|
||||||
* to achieve removal.
|
|
||||||
*
|
|
||||||
* @param <T> the row type
|
|
||||||
*/
|
|
||||||
public class DefaultAddRemoveStrategy<T> implements TableAddRemoveStrategy<T> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void process(List<AddRemoveListItem<T>> addRemoveList, TableData<T> tableData,
|
|
||||||
TaskMonitor monitor) throws CancelledException {
|
|
||||||
|
|
||||||
int n = addRemoveList.size();
|
|
||||||
monitor.setMessage("Adding/Removing " + n + " items...");
|
|
||||||
monitor.initialize(n);
|
|
||||||
|
|
||||||
Set<T> failedToRemove = new HashSet<>();
|
|
||||||
|
|
||||||
// Note: this class does not directly perform a binary search, but instead relies on that
|
|
||||||
// work to be done by the call to TableData.remove()
|
|
||||||
for (int i = 0; i < n; i++) {
|
|
||||||
AddRemoveListItem<T> item = addRemoveList.get(i);
|
|
||||||
T value = item.getValue();
|
|
||||||
if (item.isChange()) {
|
|
||||||
tableData.remove(value);
|
|
||||||
tableData.insert(value);
|
|
||||||
}
|
|
||||||
else if (item.isRemove()) {
|
|
||||||
if (!tableData.remove(value)) {
|
|
||||||
failedToRemove.add(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (item.isAdd()) {
|
|
||||||
tableData.insert(value);
|
|
||||||
}
|
|
||||||
monitor.checkCanceled();
|
|
||||||
monitor.setProgress(i);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!failedToRemove.isEmpty()) {
|
|
||||||
int size = failedToRemove.size();
|
|
||||||
String message = size == 1 ? "1 deleted item..." : size + " deleted items...";
|
|
||||||
monitor.setMessage("Removing " + message);
|
|
||||||
|
|
||||||
tableData.process((data, sortContext) -> {
|
|
||||||
return expungeLostItems(failedToRemove, data, sortContext);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
monitor.setMessage("Done adding/removing");
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<T> expungeLostItems(Set<T> toRemove, List<T> data,
|
|
||||||
TableSortingContext<T> sortContext) {
|
|
||||||
|
|
||||||
List<T> newList = new ArrayList<>(data.size() - toRemove.size());
|
|
||||||
for (int i = 0; i < data.size(); i++) {
|
|
||||||
T rowObject = data.get(i);
|
|
||||||
if (!toRemove.contains(rowObject)) {
|
|
||||||
newList.add(rowObject);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return newList;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -93,7 +93,7 @@ public abstract class ThreadedTableModel<ROW_OBJECT, DATA_SOURCE>
|
||||||
private int minUpdateDelayMillis;
|
private int minUpdateDelayMillis;
|
||||||
private int maxUpdateDelayMillis;
|
private int maxUpdateDelayMillis;
|
||||||
private TableAddRemoveStrategy<ROW_OBJECT> binarySearchAddRemoveStrategy =
|
private TableAddRemoveStrategy<ROW_OBJECT> binarySearchAddRemoveStrategy =
|
||||||
new DefaultAddRemoveStrategy<>();
|
new CoalescingAddRemoveStrategy<>();
|
||||||
|
|
||||||
protected ThreadedTableModel(String modelName, ServiceProvider serviceProvider) {
|
protected ThreadedTableModel(String modelName, ServiceProvider serviceProvider) {
|
||||||
this(modelName, serviceProvider, null);
|
this(modelName, serviceProvider, null);
|
||||||
|
|
|
@ -0,0 +1,396 @@
|
||||||
|
/* ###
|
||||||
|
* 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 docking.widgets.table;
|
||||||
|
|
||||||
|
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.threaded.TestTableData;
|
||||||
|
import ghidra.util.task.TaskMonitor;
|
||||||
|
|
||||||
|
public class CoalescingAddRemoveStrategyTest {
|
||||||
|
|
||||||
|
private CoalescingAddRemoveStrategy<TestRowObject> strategy;
|
||||||
|
private SpyTableData spyTableData;
|
||||||
|
private List<TestRowObject> modelData;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws Exception {
|
||||||
|
strategy = new CoalescingAddRemoveStrategy<>();
|
||||||
|
modelData = createModelData();
|
||||||
|
spyTableData = createTableData();
|
||||||
|
}
|
||||||
|
|
||||||
|
private SpyTableData createTableData() {
|
||||||
|
Comparator<TestRowObject> comparator = (s1, s2) -> {
|
||||||
|
return s1.getName().compareTo(s2.getName());
|
||||||
|
};
|
||||||
|
TableSortState sortState = TableSortState.createDefaultSortState(0);
|
||||||
|
TableSortingContext<TestRowObject> sortContext =
|
||||||
|
new TableSortingContext<>(sortState, comparator);
|
||||||
|
|
||||||
|
modelData.sort(comparator);
|
||||||
|
|
||||||
|
return new SpyTableData(modelData, sortContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<TestRowObject> createModelData() {
|
||||||
|
List<TestRowObject> data = new ArrayList<>();
|
||||||
|
data.add(new TestRowObject(1));
|
||||||
|
data.add(new TestRowObject(2));
|
||||||
|
data.add(new TestRowObject(3));
|
||||||
|
data.add(new TestRowObject(4));
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRemove_DifferentInstance_SameId() throws Exception {
|
||||||
|
|
||||||
|
List<AddRemoveListItem<TestRowObject>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
TestRowObject ro = new TestRowObject(1);
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(REMOVE, ro));
|
||||||
|
|
||||||
|
strategy.process(addRemoves, spyTableData, TaskMonitor.DUMMY);
|
||||||
|
|
||||||
|
assertEquals(1, spyTableData.getRemoveCount());
|
||||||
|
assertEquals(0, spyTableData.getInsertCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testInsert_NewSymbol() throws Exception {
|
||||||
|
|
||||||
|
List<AddRemoveListItem<TestRowObject>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
TestRowObject newSymbol = new TestRowObject(10);
|
||||||
|
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<TestRowObject>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
TestRowObject newSymbol = new TestRowObject(10);
|
||||||
|
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<TestRowObject>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
TestRowObject newSymbol = new TestRowObject(10);
|
||||||
|
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<TestRowObject>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
TestRowObject ro = modelData.get(0);
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(CHANGE, ro));
|
||||||
|
|
||||||
|
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<TestRowObject>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
TestRowObject newSymbol = new TestRowObject(10);
|
||||||
|
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<TestRowObject>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
TestRowObject ro = modelData.get(0);
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(REMOVE, ro));
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(ADD, ro));
|
||||||
|
|
||||||
|
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<TestRowObject>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
TestRowObject ro = modelData.get(0);
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(CHANGE, ro));
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(ADD, ro));
|
||||||
|
|
||||||
|
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<TestRowObject>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
TestRowObject ro = modelData.get(0);
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(CHANGE, ro));
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(REMOVE, ro));
|
||||||
|
|
||||||
|
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<TestRowObject>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
TestRowObject ro = modelData.get(0);
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(CHANGE, ro));
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(CHANGE, ro));
|
||||||
|
|
||||||
|
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<TestRowObject>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
TestRowObject ro = modelData.get(0);
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(REMOVE, ro));
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(REMOVE, ro));
|
||||||
|
|
||||||
|
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<TestRowObject>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
TestRowObject ro = modelData.get(0);
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(ADD, ro));
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(ADD, ro));
|
||||||
|
|
||||||
|
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<TestRowObject>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
TestRowObject ro = modelData.get(0);
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(ADD, ro));
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(CHANGE, ro));
|
||||||
|
|
||||||
|
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<TestRowObject>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
TestRowObject ro = modelData.get(0);
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(REMOVE, ro));
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(CHANGE, ro));
|
||||||
|
|
||||||
|
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<TestRowObject>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
TestRowObject symbol = modelData.get(0);
|
||||||
|
symbol.setName("UpdatedName");
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(REMOVE, 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<TestRowObject>> addRemoves = new ArrayList<>();
|
||||||
|
|
||||||
|
TestRowObject symbol = modelData.get(0);
|
||||||
|
symbol.setName("UpdatedName");
|
||||||
|
addRemoves.add(new AddRemoveListItem<>(CHANGE, 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<TestRowObject> {
|
||||||
|
|
||||||
|
private int removeCount;
|
||||||
|
private int insertCount;
|
||||||
|
|
||||||
|
SpyTableData(List<TestRowObject> data, TableSortingContext<TestRowObject> sortContext) {
|
||||||
|
super(data, sortContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean remove(TestRowObject t) {
|
||||||
|
removeCount++;
|
||||||
|
return super.remove(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void insert(TestRowObject value) {
|
||||||
|
insertCount++;
|
||||||
|
super.insert(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
int getRemoveCount() {
|
||||||
|
return removeCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
int getInsertCount() {
|
||||||
|
return insertCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestRowObject {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private long id;
|
||||||
|
|
||||||
|
TestRowObject(long id) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = Long.toString(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setName(String newName) {
|
||||||
|
this.name = newName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return (int) id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestRowObject other = (TestRowObject) obj;
|
||||||
|
if (id != other.id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -18,8 +18,7 @@ package ghidra.framework.plugintool.dialog;
|
||||||
import java.awt.Color;
|
import java.awt.Color;
|
||||||
import java.awt.Component;
|
import java.awt.Component;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import docking.widgets.table.*;
|
import docking.widgets.table.*;
|
||||||
import docking.widgets.table.threaded.ThreadedTableModel;
|
import docking.widgets.table.threaded.ThreadedTableModel;
|
||||||
|
@ -52,7 +51,7 @@ import utilities.util.FileUtilities;
|
||||||
* is a checkbox allowing users to install/uninstall a particular extension.
|
* is a checkbox allowing users to install/uninstall a particular extension.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, List<ExtensionDetails>> {
|
class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, Object> {
|
||||||
|
|
||||||
/** We don't care about the ordering of other columns, but the install/uninstall checkbox should be
|
/** We don't care about the ordering of other columns, but the install/uninstall checkbox should be
|
||||||
the first one and the name col is our initial sort column. */
|
the first one and the name col is our initial sort column. */
|
||||||
|
@ -60,7 +59,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, List<Exte
|
||||||
final static int NAME_COL = 1;
|
final static int NAME_COL = 1;
|
||||||
|
|
||||||
/** This is the data source for the model. Whatever is here will be displayed in the table. */
|
/** This is the data source for the model. Whatever is here will be displayed in the table. */
|
||||||
private List<ExtensionDetails> extensions = new ArrayList<>();
|
private Set<ExtensionDetails> extensions;
|
||||||
|
|
||||||
/** Indicates if the model has changed due to an install or uninstall. */
|
/** Indicates if the model has changed due to an install or uninstall. */
|
||||||
private boolean modelChanged = false;
|
private boolean modelChanged = false;
|
||||||
|
@ -78,7 +77,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, List<Exte
|
||||||
protected TableColumnDescriptor<ExtensionDetails> createTableColumnDescriptor() {
|
protected TableColumnDescriptor<ExtensionDetails> createTableColumnDescriptor() {
|
||||||
|
|
||||||
TableColumnDescriptor<ExtensionDetails> descriptor =
|
TableColumnDescriptor<ExtensionDetails> descriptor =
|
||||||
new TableColumnDescriptor<ExtensionDetails>();
|
new TableColumnDescriptor<>();
|
||||||
|
|
||||||
descriptor.addVisibleColumn(new ExtensionInstalledColumn(), INSTALLED_COL, true);
|
descriptor.addVisibleColumn(new ExtensionInstalledColumn(), INSTALLED_COL, true);
|
||||||
descriptor.addVisibleColumn(new ExtensionNameColumn(), NAME_COL, true);
|
descriptor.addVisibleColumn(new ExtensionNameColumn(), NAME_COL, true);
|
||||||
|
@ -178,20 +177,33 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, List<Exte
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ExtensionDetails> getDataSource() {
|
public Object getDataSource() {
|
||||||
return extensions;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doLoad(Accumulator<ExtensionDetails> accumulator, TaskMonitor monitor)
|
protected void doLoad(Accumulator<ExtensionDetails> accumulator, TaskMonitor monitor)
|
||||||
throws CancelledException {
|
throws CancelledException {
|
||||||
|
if (extensions != null) {
|
||||||
|
accumulator.addAll(extensions);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
extensions = ExtensionUtils.getExtensions();
|
||||||
|
}
|
||||||
|
catch (ExtensionException e) {
|
||||||
|
Msg.error(this, "Error loading extensions", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
accumulator.addAll(extensions);
|
accumulator.addAll(extensions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the model has changed as a result of installing or uninstalling an extension.
|
* Returns true if the model has changed as a result of installing or uninstalling an extension
|
||||||
*
|
*
|
||||||
* @return true if the model has changed as a result of installing or uninstalling an extension.
|
* @return true if the model has changed as a result of installing or uninstalling an extension
|
||||||
*/
|
*/
|
||||||
public boolean hasModelChanged() {
|
public boolean hasModelChanged() {
|
||||||
return modelChanged;
|
return modelChanged;
|
||||||
|
@ -203,7 +215,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, List<Exte
|
||||||
* @param model the list to use as the model
|
* @param model the list to use as the model
|
||||||
*/
|
*/
|
||||||
public void setModelData(List<ExtensionDetails> model) {
|
public void setModelData(List<ExtensionDetails> model) {
|
||||||
extensions = model;
|
extensions = new HashSet<>(model);
|
||||||
reload();
|
reload();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,12 +223,8 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, List<Exte
|
||||||
* Gets a new set of extensions and reloads the table.
|
* Gets a new set of extensions and reloads the table.
|
||||||
*/
|
*/
|
||||||
public void refreshTable() {
|
public void refreshTable() {
|
||||||
try {
|
extensions = null;
|
||||||
setModelData(new ArrayList<ExtensionDetails>(ExtensionUtils.getExtensions()));
|
reload();
|
||||||
}
|
|
||||||
catch (ExtensionException e) {
|
|
||||||
Msg.error(this, "Error loading extensions", e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -236,7 +244,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, List<Exte
|
||||||
* Table column for displaying the extension name.
|
* Table column for displaying the extension name.
|
||||||
*/
|
*/
|
||||||
private class ExtensionNameColumn
|
private class ExtensionNameColumn
|
||||||
extends AbstractDynamicTableColumn<ExtensionDetails, String, List<ExtensionDetails>> {
|
extends AbstractDynamicTableColumn<ExtensionDetails, String, Object> {
|
||||||
|
|
||||||
private ExtVersionRenderer renderer = new ExtVersionRenderer();
|
private ExtVersionRenderer renderer = new ExtVersionRenderer();
|
||||||
|
|
||||||
|
@ -252,7 +260,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, List<Exte
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getValue(ExtensionDetails rowObject, Settings settings,
|
public String getValue(ExtensionDetails rowObject, Settings settings,
|
||||||
List<ExtensionDetails> data, ServiceProvider sp) throws IllegalArgumentException {
|
Object data, ServiceProvider sp) throws IllegalArgumentException {
|
||||||
return rowObject.getName();
|
return rowObject.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,7 +274,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, List<Exte
|
||||||
* Table column for displaying the extension description.
|
* Table column for displaying the extension description.
|
||||||
*/
|
*/
|
||||||
private class ExtensionDescriptionColumn
|
private class ExtensionDescriptionColumn
|
||||||
extends AbstractDynamicTableColumn<ExtensionDetails, String, List<ExtensionDetails>> {
|
extends AbstractDynamicTableColumn<ExtensionDetails, String, Object> {
|
||||||
|
|
||||||
private ExtVersionRenderer renderer = new ExtVersionRenderer();
|
private ExtVersionRenderer renderer = new ExtVersionRenderer();
|
||||||
|
|
||||||
|
@ -282,7 +290,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, List<Exte
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getValue(ExtensionDetails rowObject, Settings settings,
|
public String getValue(ExtensionDetails rowObject, Settings settings,
|
||||||
List<ExtensionDetails> data, ServiceProvider sp) throws IllegalArgumentException {
|
Object data, ServiceProvider sp) throws IllegalArgumentException {
|
||||||
return rowObject.getDescription();
|
return rowObject.getDescription();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -296,7 +304,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, List<Exte
|
||||||
* Table column for displaying the extension description.
|
* Table column for displaying the extension description.
|
||||||
*/
|
*/
|
||||||
private class ExtensionVersionColumn
|
private class ExtensionVersionColumn
|
||||||
extends AbstractDynamicTableColumn<ExtensionDetails, String, List<ExtensionDetails>> {
|
extends AbstractDynamicTableColumn<ExtensionDetails, String, Object> {
|
||||||
|
|
||||||
private ExtVersionRenderer renderer = new ExtVersionRenderer();
|
private ExtVersionRenderer renderer = new ExtVersionRenderer();
|
||||||
|
|
||||||
|
@ -312,7 +320,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, List<Exte
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getValue(ExtensionDetails rowObject, Settings settings,
|
public String getValue(ExtensionDetails rowObject, Settings settings,
|
||||||
List<ExtensionDetails> data, ServiceProvider sp) throws IllegalArgumentException {
|
Object data, ServiceProvider sp) throws IllegalArgumentException {
|
||||||
|
|
||||||
String version = rowObject.getVersion();
|
String version = rowObject.getVersion();
|
||||||
|
|
||||||
|
@ -335,7 +343,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, List<Exte
|
||||||
* Table column for displaying the extension installation status.
|
* Table column for displaying the extension installation status.
|
||||||
*/
|
*/
|
||||||
private class ExtensionInstalledColumn
|
private class ExtensionInstalledColumn
|
||||||
extends AbstractDynamicTableColumn<ExtensionDetails, Boolean, List<ExtensionDetails>> {
|
extends AbstractDynamicTableColumn<ExtensionDetails, Boolean, Object> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getColumnName() {
|
public String getColumnName() {
|
||||||
|
@ -349,7 +357,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, List<Exte
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Boolean getValue(ExtensionDetails rowObject, Settings settings,
|
public Boolean getValue(ExtensionDetails rowObject, Settings settings,
|
||||||
List<ExtensionDetails> data, ServiceProvider sp) throws IllegalArgumentException {
|
Object data, ServiceProvider sp) throws IllegalArgumentException {
|
||||||
return rowObject.isInstalled();
|
return rowObject.isInstalled();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -358,7 +366,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, List<Exte
|
||||||
* Table column for displaying the extension installation directory.
|
* Table column for displaying the extension installation directory.
|
||||||
*/
|
*/
|
||||||
private class ExtensionInstallationDirColumn
|
private class ExtensionInstallationDirColumn
|
||||||
extends AbstractDynamicTableColumn<ExtensionDetails, String, List<ExtensionDetails>> {
|
extends AbstractDynamicTableColumn<ExtensionDetails, String, Object> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getColumnName() {
|
public String getColumnName() {
|
||||||
|
@ -372,7 +380,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, List<Exte
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getValue(ExtensionDetails rowObject, Settings settings,
|
public String getValue(ExtensionDetails rowObject, Settings settings,
|
||||||
List<ExtensionDetails> data, ServiceProvider sp) throws IllegalArgumentException {
|
Object data, ServiceProvider sp) throws IllegalArgumentException {
|
||||||
return rowObject.getInstallPath();
|
return rowObject.getInstallPath();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -381,7 +389,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, List<Exte
|
||||||
* Table column for displaying the extension archive file.
|
* Table column for displaying the extension archive file.
|
||||||
*/
|
*/
|
||||||
private class ExtensionArchiveFileColumn
|
private class ExtensionArchiveFileColumn
|
||||||
extends AbstractDynamicTableColumn<ExtensionDetails, String, List<ExtensionDetails>> {
|
extends AbstractDynamicTableColumn<ExtensionDetails, String, Object> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getColumnName() {
|
public String getColumnName() {
|
||||||
|
@ -395,7 +403,7 @@ class ExtensionTableModel extends ThreadedTableModel<ExtensionDetails, List<Exte
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getValue(ExtensionDetails rowObject, Settings settings,
|
public String getValue(ExtensionDetails rowObject, Settings settings,
|
||||||
List<ExtensionDetails> data, ServiceProvider sp) throws IllegalArgumentException {
|
Object data, ServiceProvider sp) throws IllegalArgumentException {
|
||||||
return rowObject.getArchivePath();
|
return rowObject.getArchivePath();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue