GP-787: Refactored Regions,Modules/Sections,Mappings,Threads to use RowWrapped...

This commit is contained in:
Dan 2021-05-04 15:10:34 -04:00
parent 84c77bb90c
commit 085daeb39b
17 changed files with 159 additions and 149 deletions

View file

@ -18,7 +18,8 @@ package ghidra.app.plugin.core.debug.gui;
import java.awt.Color;
import java.awt.Component;
import java.awt.event.*;
import java.util.*;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CancellationException;
import java.util.function.Function;
@ -1560,11 +1561,11 @@ public interface DebuggerResources {
};
}
static <V, R> void setSelectedRows(Set<V> sel, Map<V, R> rowMap, GTable table,
static <V, R> void setSelectedRows(Set<V> sel, Function<V, R> rowMapper, GTable table,
RowObjectTableModel<R> model, GTableFilterPanel<R> filterPanel) {
table.clearSelection();
for (V v : sel) {
R row = rowMap.get(v);
R row = rowMapper.apply(v);
if (row == null) {
continue;
}

View file

@ -971,14 +971,12 @@ public class DebuggerBreakpointsProvider extends ComponentProviderAdapter
}
public void setSelectedBreakpoints(Set<LogicalBreakpoint> sel) {
DebuggerResources.setSelectedRows(sel, breakpointTableModel.getMap(), breakpointTable,
DebuggerResources.setSelectedRows(sel, breakpointTableModel::getRow, breakpointTable,
breakpointTableModel, breakpointFilterPanel);
}
public void setSelectedLocations(Set<TraceBreakpoint> sel) {
DebuggerResources.setSelectedRows(
sel.stream().map(b -> b.getObjectKey()).collect(Collectors.toSet()),
locationTableModel.getMap(), locationTable,
DebuggerResources.setSelectedRows(sel, locationTableModel::getRow, locationTable,
locationTableModel, locationFilterPanel);
}
}

View file

@ -31,8 +31,9 @@ import com.google.common.collect.Range;
import docking.ActionContext;
import docking.WindowPosition;
import docking.action.*;
import docking.widgets.table.*;
import docking.widgets.table.CustomToStringCellRenderer;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import docking.widgets.table.RowWrappedEnumeratedColumnTableModel;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
import ghidra.app.plugin.core.debug.gui.DebuggerResources;
import ghidra.app.plugin.core.debug.gui.DebuggerResources.AbstractSelectAddressesAction;
@ -51,6 +52,7 @@ import ghidra.trace.model.Trace.TraceMemoryRegionChangeType;
import ghidra.trace.model.TraceDomainObjectListener;
import ghidra.trace.model.memory.TraceMemoryManager;
import ghidra.trace.model.memory.TraceMemoryRegion;
import ghidra.util.database.ObjectKey;
import ghidra.util.table.GhidraTable;
import ghidra.util.table.GhidraTableFilterPanel;
@ -112,6 +114,16 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
}
}
protected static class RegionTableModel
extends RowWrappedEnumeratedColumnTableModel< //
RegionTableColumns, ObjectKey, RegionRow, TraceMemoryRegion> {
public RegionTableModel() {
super("Regions", RegionTableColumns.class, TraceMemoryRegion::getObjectKey,
RegionRow::new);
}
}
protected static RegionRow getSelectedRegionRow(ActionContext context) {
if (!(context instanceof DebuggerRegionActionContext)) {
return null;
@ -150,26 +162,15 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
}
private void regionAdded(TraceMemoryRegion region) {
regionMap.computeIfAbsent(region, r -> {
RegionRow rr = new RegionRow(r);
regionTableModel.add(rr);
return rr;
});
/**
* NOTE: No need to add sections here. A TraceModule is created empty, so when each
* section is added, we'll get the call.
*/
regionTableModel.addItem(region);
}
private void regionChanged(TraceMemoryRegion region) {
regionTableModel.notifyUpdated(regionMap.get(region));
regionTableModel.updateItem(region);
}
private void regionDeleted(TraceMemoryRegion region) {
RegionRow rr = regionMap.remove(region);
if (rr != null) {
regionTableModel.delete(rr);
}
regionTableModel.deleteItem(region);
}
}
@ -222,9 +223,7 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
private final RegionsListener regionsListener = new RegionsListener();
protected final Map<TraceMemoryRegion, RegionRow> regionMap = new HashMap<>();
protected final EnumeratedColumnTableModel<RegionRow> regionTableModel =
new DefaultEnumeratedColumnTableModel<>("Regions", RegionTableColumns.class);
protected final RegionTableModel regionTableModel = new RegionTableModel();
protected GhidraTable regionTable;
private GhidraTableFilterPanel<RegionRow> regionFilterPanel;
@ -262,16 +261,13 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
}
private void loadRegions() {
regionMap.clear();
regionTableModel.clear();
if (currentTrace == null) {
return;
}
TraceMemoryManager memoryManager = currentTrace.getMemoryManager();
for (TraceMemoryRegion region : memoryManager.getAllRegions()) {
regionTableModel.add(regionMap.computeIfAbsent(region, RegionRow::new));
}
regionTableModel.addAllItems(memoryManager.getAllRegions());
}
protected void buildMainPanel() {
@ -376,8 +372,8 @@ public class DebuggerRegionsProvider extends ComponentProviderAdapter {
}
public void setSelectedRegions(Set<TraceMemoryRegion> sel) {
DebuggerResources.setSelectedRows(sel, regionMap, regionTable, regionTableModel,
regionFilterPanel);
DebuggerResources.setSelectedRows(sel, regionTableModel::getRow, regionTable,
regionTableModel, regionFilterPanel);
}
public Collection<RegionRow> getSelectedRows() {

View file

@ -64,6 +64,7 @@ import ghidra.trace.model.TraceDomainObjectListener;
import ghidra.trace.model.modules.*;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.database.ObjectKey;
import ghidra.util.datastruct.CollectionChangeListener;
import ghidra.util.table.GhidraTable;
import ghidra.util.table.GhidraTableFilterPanel;
@ -198,6 +199,25 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
return sections.iterator().next();
}
protected static class ModuleTableModel
extends RowWrappedEnumeratedColumnTableModel< //
ModuleTableColumns, ObjectKey, ModuleRow, TraceModule> {
public ModuleTableModel() {
super("Modules", ModuleTableColumns.class, TraceModule::getObjectKey, ModuleRow::new);
}
}
protected static class SectionTableModel
extends RowWrappedEnumeratedColumnTableModel< //
SectionTableColumns, ObjectKey, SectionRow, TraceSection> {
public SectionTableModel() {
super("Sections", SectionTableColumns.class, TraceSection::getObjectKey,
SectionRow::new);
}
}
protected static Set<TraceModule> getSelectedModules(ActionContext context) {
if (context instanceof DebuggerModuleActionContext) {
DebuggerModuleActionContext ctx = (DebuggerModuleActionContext) context;
@ -253,54 +273,39 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
}
private void moduleAdded(TraceModule module) {
moduleMap.computeIfAbsent(module, m -> {
ModuleRow mr = new ModuleRow(m);
moduleTableModel.add(mr);
return mr;
});/**
moduleTableModel.addItem(module);
/**
* NOTE: No need to add sections here. A TraceModule is created empty, so when each
* section is added, we'll get the call.
*/
}
private void moduleChanged(TraceModule module) {
moduleTableModel.notifyUpdated(moduleMap.get(module));
moduleTableModel.updateItem(module);
sectionTableModel.fireTableDataChanged(); // Because module name in section row
}
private void moduleDeleted(TraceModule module) {
ModuleRow mr = moduleMap.remove(module);
if (mr != null) {
moduleTableModel.delete(mr);
}
moduleTableModel.deleteItem(module);
// NOTE: module.getSections() will be empty, now
for (Iterator<Entry<TraceSection, SectionRow>> it = sectionMap.entrySet().iterator(); it
.hasNext();) {
Entry<TraceSection, SectionRow> ent = it.next();
if (ent.getKey().getModule() == module) {
it.remove();
sectionTableModel.delete(ent.getValue());
}
}
sectionTableModel.deleteAllItems(sectionTableModel.getMap()
.values()
.stream()
.filter(r -> r.getModule() == module)
.map(r -> r.getSection())
.collect(Collectors.toList()));
}
private void sectionAdded(TraceSection section) {
sectionMap.computeIfAbsent(section, s -> {
SectionRow sr = new SectionRow(s);
sectionTableModel.add(sr);
return sr;
});
sectionTableModel.addItem(section);
}
private void sectionChanged(TraceSection section) {
sectionTableModel.notifyUpdated(sectionMap.get(section));
sectionTableModel.updateItem(section);
}
private void sectionDeleted(TraceSection section) {
SectionRow sr = sectionMap.remove(section);
if (sr != null) {
sectionTableModel.delete(sr);
}
sectionTableModel.deleteItem(section);
}
}
@ -516,15 +521,11 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
private final RecordersChangedListener recordersChangedListener =
new RecordersChangedListener();
protected final Map<TraceModule, ModuleRow> moduleMap = new HashMap<>();
protected final EnumeratedColumnTableModel<ModuleRow> moduleTableModel =
new DefaultEnumeratedColumnTableModel<>("Modules", ModuleTableColumns.class);
protected final ModuleTableModel moduleTableModel = new ModuleTableModel();
protected GhidraTable moduleTable;
private GhidraTableFilterPanel<ModuleRow> moduleFilterPanel;
protected final Map<TraceSection, SectionRow> sectionMap = new HashMap<>();
protected final EnumeratedColumnTableModel<SectionRow> sectionTableModel =
new DefaultEnumeratedColumnTableModel<>("Sections", SectionTableColumns.class);
protected final SectionTableModel sectionTableModel = new SectionTableModel();
protected GhidraTable sectionTable;
protected GhidraTableFilterPanel<SectionRow> sectionFilterPanel;
private final SectionsBySelectedModulesTableFilter filterSectionsBySelectedModules =
@ -598,22 +599,16 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
}
private void loadModules() {
moduleMap.clear();
moduleTableModel.clear();
sectionMap.clear();
sectionTableModel.clear();
if (currentTrace == null) {
return;
}
TraceModuleManager moduleManager = currentTrace.getModuleManager();
for (TraceModule module : moduleManager.getAllModules()) {
moduleTableModel.add(moduleMap.computeIfAbsent(module, ModuleRow::new));
for (TraceSection section : module.getSections()) {
sectionTableModel.add(sectionMap.computeIfAbsent(section, SectionRow::new));
}
}
moduleTableModel.addAllItems(moduleManager.getAllModules());
sectionTableModel.addAllItems(moduleManager.getAllSections());
}
protected void buildMainPanel() {
@ -1092,13 +1087,13 @@ public class DebuggerModulesProvider extends ComponentProviderAdapter {
}
public void setSelectedModules(Set<TraceModule> sel) {
DebuggerResources.setSelectedRows(sel, moduleMap, moduleTable, moduleTableModel,
moduleFilterPanel);
DebuggerResources.setSelectedRows(sel, moduleTableModel::getRow, moduleTable,
moduleTableModel, moduleFilterPanel);
}
public void setSelectedSections(Set<TraceSection> sel) {
DebuggerResources.setSelectedRows(sel, sectionMap, sectionTable, sectionTableModel,
sectionFilterPanel);
DebuggerResources.setSelectedRows(sel, sectionTableModel::getRow, sectionTable,
sectionTableModel, sectionFilterPanel);
}
private DataTreeDialog getProgramChooserDialog() {

View file

@ -19,7 +19,8 @@ import java.awt.BorderLayout;
import java.awt.event.MouseEvent;
import java.math.BigInteger;
import java.net.URL;
import java.util.*;
import java.util.HashSet;
import java.util.Set;
import java.util.function.Function;
import javax.swing.*;
@ -54,6 +55,7 @@ import ghidra.trace.model.modules.TraceStaticMappingManager;
import ghidra.trace.model.program.TraceProgramView;
import ghidra.util.MathUtilities;
import ghidra.util.Msg;
import ghidra.util.database.ObjectKey;
import ghidra.util.database.UndoableTransaction;
import ghidra.util.table.GhidraTableFilterPanel;
@ -95,6 +97,16 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
}
}
protected static class MappingTableModel
extends RowWrappedEnumeratedColumnTableModel< //
StaticMappingTableColumns, ObjectKey, StaticMappingRow, TraceStaticMapping> {
public MappingTableModel() {
super("Mappings", StaticMappingTableColumns.class, TraceStaticMapping::getObjectKey,
StaticMappingRow::new);
}
}
protected class ListenerForStaticMappingDisplay extends TraceDomainObjectListener {
public ListenerForStaticMappingDisplay() {
listenForUntyped(DomainObject.DO_OBJECT_RESTORED, e -> objectRestored());
@ -107,11 +119,11 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
}
private void staticMappingAdded(TraceStaticMapping mapping) {
addMapping(mapping);
mappingTableModel.addItem(mapping);
}
private void staticMappingDeleted(TraceStaticMapping mapping) {
mappingTableModel.deleteWith(rec -> rec.getMapping() == mapping);
mappingTableModel.deleteItem(mapping);
}
}
@ -135,8 +147,7 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
private ListenerForStaticMappingDisplay listener = new ListenerForStaticMappingDisplay();
protected final EnumeratedColumnTableModel<StaticMappingRow> mappingTableModel =
new DefaultEnumeratedColumnTableModel<>("Mappings", StaticMappingTableColumns.class);
protected final MappingTableModel mappingTableModel = new MappingTableModel();
private JPanel mainPanel = new JPanel(new BorderLayout());
protected GTable mappingTable;
@ -187,23 +198,13 @@ public class DebuggerStaticMappingProvider extends ComponentProviderAdapter
return myActionContext;
}
private void addMapping(TraceStaticMapping mapping) {
mappingTableModel.add(new StaticMappingRow(mapping));
}
private void addMappings(Collection<? extends TraceStaticMapping> entries) {
for (TraceStaticMapping ent : entries) {
addMapping(ent);
}
}
private void loadMappings() {
mappingTableModel.clear();
if (currentTrace == null) {
return;
}
TraceStaticMappingManager manager = currentTrace.getStaticMappingManager();
addMappings(manager.getAllEntries());
mappingTableModel.addAllItems(manager.getAllEntries());
}
protected void buildMainPanel() {

View file

@ -214,11 +214,6 @@ public class ObjectEnumeratedColumnTableModel<C extends ObjectsEnumeratedTableCo
fireTableRowsInserted(startIndex, endIndex);
}
@Override
public RowIterator<R> rowIterator() {
return new TableRowIterator();
}
@Override
public void notifyUpdated(R row) {
int rowIndex = modelData.indexOf(row);

View file

@ -30,7 +30,8 @@ import docking.action.*;
import docking.widgets.EventTrigger;
import docking.widgets.HorizontalTabPanel;
import docking.widgets.HorizontalTabPanel.TabListCellRenderer;
import docking.widgets.table.*;
import docking.widgets.table.GTable;
import docking.widgets.table.RowWrappedEnumeratedColumnTableModel;
import docking.widgets.timeline.TimelineListener;
import ghidra.app.plugin.core.debug.DebuggerCoordinates;
import ghidra.app.plugin.core.debug.DebuggerPluginPackage;
@ -55,6 +56,7 @@ import ghidra.trace.model.thread.TraceThreadManager;
import ghidra.trace.model.time.TraceSchedule;
import ghidra.trace.model.time.TraceSnapshot;
import ghidra.util.Swing;
import ghidra.util.database.ObjectKey;
import ghidra.util.datastruct.CollectionChangeListener;
import ghidra.util.table.GhidraTable;
import ghidra.util.table.GhidraTableFilterPanel;
@ -249,6 +251,16 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
}
}
protected static class ThreadTableModel
extends RowWrappedEnumeratedColumnTableModel< //
ThreadTableColumns, ObjectKey, ThreadRow, TraceThread> {
public ThreadTableModel(DebuggerThreadsProvider provider) {
super("Threads", ThreadTableColumns.class, TraceThread::getObjectKey,
t -> new ThreadRow(provider.modelService, t));
}
}
private class ThreadsListener extends TraceDomainObjectListener {
public ThreadsListener() {
listenForUntyped(DomainObject.DO_OBJECT_RESTORED, e -> objectRestored());
@ -267,23 +279,15 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
}
private void threadAdded(TraceThread thread) {
threadMap.computeIfAbsent(thread, t -> {
ThreadRow tr = new ThreadRow(modelService, t);
threadTableModel.add(tr);
doSetThread(thread);
return tr;
});
threadTableModel.addItem(thread);
}
private void threadChanged(TraceThread thread) {
threadTableModel.notifyUpdatedWith(row -> row.getThread() == thread);
threadTableModel.updateItem(thread);
}
private void threadDeleted(TraceThread thread) {
ThreadRow tr = threadMap.remove(thread);
if (tr != null) {
threadTableModel.delete(tr);
}
threadTableModel.deleteItem(thread);
}
private void snapAdded(TraceSnapshot snapshot) {
@ -333,9 +337,7 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
private final CollectionChangeListener<TraceRecorder> recordersListener =
new RecordersChangeListener();
protected final Map<TraceThread, ThreadRow> threadMap = new HashMap<>();
protected final EnumeratedColumnTableModel<ThreadRow> threadTableModel =
new DefaultEnumeratedColumnTableModel<>("Threads", ThreadTableColumns.class);
protected final ThreadTableModel threadTableModel = new ThreadTableModel(this);
private JPanel mainPanel;
private JSplitPane splitPane;
@ -453,7 +455,7 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
}
try (Suppression supp = cbCoordinateActivation.suppress(null)) {
if (thread != null) {
threadFilterPanel.setSelectedItem(threadMap.get(thread));
threadFilterPanel.setSelectedItem(threadTableModel.getRow(thread));
}
else {
threadTable.clearSelection();
@ -495,20 +497,13 @@ public class DebuggerThreadsProvider extends ComponentProviderAdapter {
}
protected void loadThreads() {
threadMap.clear();
threadTableModel.clear();
Trace curTrace = current.getTrace();
if (curTrace == null) {
return;
}
TraceThreadManager manager = curTrace.getThreadManager();
for (TraceThread thread : manager.getAllThreads()) {
threadMap.computeIfAbsent(thread, t -> {
ThreadRow tr = new ThreadRow(modelService, t);
threadTableModel.add(tr);
return tr;
});
}
threadTableModel.addAllItems(manager.getAllThreads());
updateTimelineMax();
}

View file

@ -0,0 +1,27 @@
/* ###
* 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.trace.model;
import ghidra.util.database.ObjectKey;
public interface TraceObject {
/**
* Get an opaque unique id for this object, whose hash is immutable
*
* @return the opaque object id
*/
ObjectKey getObjectKey();
}

View file

@ -23,29 +23,22 @@ import com.google.common.collect.Range;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceObject;
import ghidra.trace.model.thread.TraceThread;
import ghidra.util.database.ObjectKey;
import ghidra.util.exception.DuplicateNameException;
/**
* A breakpoint in a trace
*/
public interface TraceBreakpoint {
public interface TraceBreakpoint extends TraceObject {
/**
* Get the trace containning this breakpoint
* Get the trace containing this breakpoint
*
* @return the trace
*/
Trace getTrace();
/**
* Get an opaque unique id for this object, whose hash is immutable
*
* @return the opaque object id
*/
ObjectKey getObjectKey();
/**
* Get the "full name" of this breakpoint
*

View file

@ -21,12 +21,13 @@ import com.google.common.collect.Range;
import ghidra.program.model.address.*;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceObject;
import ghidra.util.exception.DuplicateNameException;
/**
* A region of mapped target memory in a trace
*/
public interface TraceMemoryRegion {
public interface TraceMemoryRegion extends TraceObject {
/**
* Get the trace containing this region
@ -105,7 +106,8 @@ public interface TraceMemoryRegion {
/**
* @see #setLifespan(Range)
*
* @param destructionSnap the destruction snap, or {@link Long#MAX_VALUE} for "to the end of time"
* @param destructionSnap the destruction snap, or {@link Long#MAX_VALUE} for "to the end of
* time"
*/
void setDestructionSnap(long destructionSnap)
throws DuplicateNameException, TraceOverlappedRegionException;

View file

@ -21,6 +21,7 @@ import com.google.common.collect.Range;
import ghidra.program.model.address.*;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceObject;
import ghidra.util.exception.DuplicateNameException;
/**
@ -29,7 +30,7 @@ import ghidra.util.exception.DuplicateNameException;
* <p>
* This also serves as a namespace for storing the module's sections.
*/
public interface TraceModule {
public interface TraceModule extends TraceObject {
/**
* Get the trace containing this module

View file

@ -18,12 +18,13 @@ package ghidra.trace.model.modules;
import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceObject;
import ghidra.util.exception.DuplicateNameException;
/**
* A section of a module in a trace
*/
public interface TraceSection {
public interface TraceSection extends TraceObject {
/**
* Get the trace containing this section

View file

@ -23,11 +23,12 @@ import ghidra.program.model.address.Address;
import ghidra.program.model.address.AddressRange;
import ghidra.program.model.listing.Program;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceObject;
/**
* A mapped range from this trace to a Ghidra {@link Program}
*/
public interface TraceStaticMapping {
public interface TraceStaticMapping extends TraceObject {
/**
* Get the "from" trace, i.e., the trace containing this mapping

View file

@ -21,12 +21,13 @@ import com.google.common.collect.Range;
import ghidra.program.model.lang.Register;
import ghidra.trace.model.Trace;
import ghidra.trace.model.TraceObject;
import ghidra.util.exception.DuplicateNameException;
/**
* A thread in a trace
*/
public interface TraceThread {
public interface TraceThread extends TraceObject {
/**
* Get the trace containing this thread

View file

@ -213,11 +213,6 @@ public class DefaultEnumeratedColumnTableModel<C extends Enum<C> & EnumeratedTab
fireTableRowsInserted(startIndex, endIndex);
}
@Override
public RowIterator<R> rowIterator() {
return new TableRowIterator();
}
@Override
public void notifyUpdated(R row) {
int rowIndex = modelData.indexOf(row);

View file

@ -27,8 +27,6 @@ public interface EnumeratedColumnTableModel<R> extends RowObjectTableModel<R> {
void addAll(Collection<R> c);
ListIterator<R> rowIterator();
void notifyUpdated(R row);
List<R> notifyUpdatedWith(Predicate<R> predicate);

View file

@ -55,6 +55,10 @@ public class RowWrappedEnumeratedColumnTableModel<C extends Enum<C> & Enumerated
return c.stream().map(this::rowFor).collect(Collectors.toList());
}
public synchronized R getRow(T t) {
return map.get(keyFunc.apply(t));
}
public void addItem(T t) {
add(rowFor(t));
}
@ -83,4 +87,10 @@ public class RowWrappedEnumeratedColumnTableModel<C extends Enum<C> & Enumerated
public synchronized Map<K, R> getMap() {
return Map.copyOf(map);
}
@Override
public synchronized void clear() {
map.clear();
super.clear();
}
}