GP-6539 - Accumulator API - Fixed thread visibility issues; simplified the API to be write focused

This commit is contained in:
dragonmacher
2026-03-06 17:06:02 -05:00
parent a7a795b335
commit 09e44ef460
32 changed files with 245 additions and 487 deletions
@@ -90,7 +90,7 @@ public class AddressRangeTableModel extends GhidraProgramTableModel<AddressRange
range.getMaxAddress(), range.getLength(), isSameByte, numRefsTo, numRefsFrom);
accumulator.add(info);
if (accumulator.size() >= resultsLimit) {
if (accumulator.getProgress() >= resultsLimit) {
Msg.showWarn(this, null, "Results Truncated",
"Results are limited to " + resultsLimit + " address ranges.\n" +
"This limit can be changed by the tool option \"" +
@@ -99,7 +99,7 @@ public class AddressRangeTableModel extends GhidraProgramTableModel<AddressRange
break;
}
}
if (accumulator.isEmpty()) {
if (accumulator.getProgress() == 0) {
Msg.showWarn(this, null, "No Ranges to Display",
"No ranges to display - consider adjusting \"" +
CodeBrowserSelectionPlugin.OPTION_CATEGORY_NAME + " -> " +
@@ -233,7 +233,7 @@ public class CodeBrowserSelectionPlugin extends Plugin {
monitor.initialize(size);
while (iterator.hasNext()) {
if (accumulator.size() >= resultsLimit) {
if (accumulator.getProgress() >= resultsLimit) {
Msg.showWarn(this, null, "Results Truncated",
"Results are limited to " + resultsLimit + " code units.\n" +
"This limit can be changed by the tool option \"" +
@@ -31,6 +31,7 @@ import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.Reference;
import ghidra.program.util.*;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.datastruct.SetAccumulatorWrapper;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@@ -343,16 +344,22 @@ public abstract class LocationDescriptor {
private void getReferenceAddressSet(Accumulator<LocationReference> accumulator,
TaskMonitor monitor, boolean reload) throws CancelledException {
if (referenceAddressList == null || reload) {
doGetReferences(accumulator, monitor);
// put into list so that we can later perform fast lookups of Addresses
referenceAddressList = new ArrayList<>(accumulator.get());
Collections.sort(referenceAddressList);
if (referenceAddressList != null && !reload) {
accumulator.addAll(referenceAddressList);
return;
}
accumulator.addAll(referenceAddressList);
// We do not want duplicates. Use a known set accumulator here, which also allows us to get
// the results when the loading is finished.
SetAccumulatorWrapper<LocationReference> setAccumulator =
new SetAccumulatorWrapper<>(accumulator);
doGetReferences(setAccumulator, monitor);
// Save results so we can later perform fast lookups of Addresses using a binary search
referenceAddressList = new ArrayList<>(setAccumulator.asSet());
Collections.sort(referenceAddressList);
}
/**
@@ -16,7 +16,6 @@
package ghidra.app.plugin.core.navigation.locationreferences;
import java.util.*;
import java.util.Stack;
import java.util.function.Consumer;
import java.util.function.Predicate;
@@ -24,14 +23,14 @@ import docking.widgets.search.SearchLocationContext;
import ghidra.app.services.*;
import ghidra.program.model.address.*;
import ghidra.program.model.data.*;
import ghidra.program.model.data.Array;
import ghidra.program.model.data.Enum;
import ghidra.program.model.listing.*;
import ghidra.program.model.symbol.*;
import ghidra.program.util.*;
import ghidra.util.*;
import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.datastruct.*;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.datastruct.SetAccumulator;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
@@ -74,16 +73,14 @@ public final class ReferenceUtils {
public static void getReferences(Accumulator<LocationReference> accumulator,
ProgramLocation location, TaskMonitor monitor) throws CancelledException {
Accumulator<LocationReference> asSet = asSet(accumulator);
Program program = location.getProgram();
Address address = location.getAddress();
Consumer<LocationReference> consumer = ref -> accumulator.add(ref);
accumulateDirectReferences(consumer, program, address);
accumulateThunkReferences(asSet, program, address, monitor);
accumulateThunkReferences(accumulator, program, address, monitor);
if (isMemoryAddress(location)) {
accumulateOffcutReferencesToCodeUnitAt(asSet, location, monitor);
accumulateOffcutReferencesToCodeUnitAt(accumulator, location, monitor);
}
}
@@ -291,23 +288,20 @@ public final class ReferenceUtils {
monitor.initialize(totalCount);
// Mimic a set in case the client passes in an accumulator that allows duplicates. This
// seems a bit cleaner than adding checks for 'accumulator.contains(ref)' throughout
// the code.
Accumulator<LocationReference> asSet = asSet(accumulator);
if (fieldMatcher.isIgnored()) {
//
// It only makes sense to search here when we do not have a field to match
//
boolean localsOnly = discoverTypes;
FunctionIterator iterator = listing.getFunctions(false);
findDataTypeMatchesInFunctionHeaders(asSet, iterator, dataType, localsOnly, monitor);
findDataTypeMatchesInFunctionHeaders(accumulator, iterator, dataType, localsOnly,
monitor);
// external functions don't get searched by type discovery
localsOnly = false;
iterator = listing.getExternalFunctions();
findDataTypeMatchesInFunctionHeaders(asSet, iterator, dataType, localsOnly, monitor);
findDataTypeMatchesInFunctionHeaders(accumulator, iterator, dataType, localsOnly,
monitor);
}
Predicate<Data> dataMatcher = data -> {
@@ -316,25 +310,16 @@ public final class ReferenceUtils {
return matches;
};
findDataTypeMatchesInDefinedData(asSet, program, dataMatcher, fieldMatcher, monitor);
findDataTypeMatchesInDefinedData(accumulator, program, dataMatcher, fieldMatcher, monitor);
if (discoverTypes) {
findDataTypeMatchesOutsideOfListing(asSet, program, dataType, fieldMatcher, monitor);
findDataTypeMatchesOutsideOfListing(accumulator, program, dataType, fieldMatcher,
monitor);
}
monitor.checkCancelled();
}
private static Accumulator<LocationReference> asSet(
Accumulator<LocationReference> accumulator) {
if (accumulator instanceof SetAccumulator) {
return accumulator;
}
return new FilteringAccumulatorWrapper<>(accumulator, ref -> !accumulator.contains(ref));
}
private static void findDataTypeMatchesOutsideOfListing(
Accumulator<LocationReference> accumulator, Program program, DataType dataType,
FieldMatcher fieldMatcher, TaskMonitor monitor) throws CancelledException {
@@ -1173,9 +1158,7 @@ public final class ReferenceUtils {
return;
}
if (!accumulator.contains(ref)) {
accumulator.add(ref);
}
accumulator.add(ref);
// this address will either be the data, or the field's, if it exists
Address dataAddress = ref.getLocationOfUse();
@@ -247,11 +247,6 @@ public class FunctionReachabilityTableModel
this.accumulator = accumulator;
}
@Override
public Iterator<List<FRVertex>> iterator() {
throw new UnsupportedOperationException();
}
@Override
public void add(List<FRVertex> t) {
accumulator.add(new FunctionReachabilityResult(fromFunction, toFunction, t));
@@ -265,18 +260,8 @@ public class FunctionReachabilityTableModel
}
@Override
public boolean contains(List<FRVertex> t) {
throw new UnsupportedOperationException();
}
@Override
public Collection<List<FRVertex>> get() {
throw new UnsupportedOperationException();
}
@Override
public int size() {
return accumulator.size();
public int getProgress() {
return accumulator.getProgress();
}
}
@@ -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.
@@ -34,7 +34,6 @@ import ghidra.program.model.symbol.Reference;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.datastruct.SizeLimitedAccumulatorWrapper;
import ghidra.util.exception.CancelledException;
import ghidra.util.table.AddressBasedTableModel;
import ghidra.util.table.column.AbstractGColumnRenderer;
@@ -50,7 +49,7 @@ public class ScalarSearchModel extends AddressBasedTableModel<ScalarRowObject> {
static final int PREVIEW_COLUMN = 1;
static final int HEX_COLUMN = 2;
static final int TEMP_MAX_RESULTS = 1_000_000;
static final int MAX_RESULTS = 100_000;
private Listing listing;
@@ -58,7 +57,7 @@ public class ScalarSearchModel extends AddressBasedTableModel<ScalarRowObject> {
private long minValue;
private long maxValue;
private SizeLimitedAccumulatorWrapper<ScalarRowObject> sizedAccumulator;
private Accumulator<ScalarRowObject> currentAccumulator;
ScalarSearchModel(ScalarSearchPlugin plugin, ProgramSelection currentSelection) {
super("Scalars", plugin.getTool(), null, null);
@@ -92,7 +91,7 @@ public class ScalarSearchModel extends AddressBasedTableModel<ScalarRowObject> {
return;
}
sizedAccumulator = new SizeLimitedAccumulatorWrapper<>(accumulator, TEMP_MAX_RESULTS);
currentAccumulator = accumulator;
if (currentSelection != null) {
loadTableFromSelection(monitor);
@@ -106,11 +105,7 @@ public class ScalarSearchModel extends AddressBasedTableModel<ScalarRowObject> {
iterateOverInstructions(monitor, instructions);
iterateOverData(monitor, dataIterator);
sizedAccumulator = null;
}
private boolean tooManyResults() {
return sizedAccumulator.hasReachedSizeLimit();
currentAccumulator = null;
}
void initialize(Program p, long newMinValue, long newMaxValue) {
@@ -153,12 +148,11 @@ public class ScalarSearchModel extends AddressBasedTableModel<ScalarRowObject> {
monitor.checkCancelled();
monitor.incrementProgress(1);
if (tooManyResults()) {
if (currentAccumulator.getProgress() > MAX_RESULTS) {
return;
}
int numOperands = instruction.getNumOperands();
for (int opIndex = 0; opIndex <= numOperands; opIndex++) {
monitor.checkCancelled();
@@ -181,14 +175,12 @@ public class ScalarSearchModel extends AddressBasedTableModel<ScalarRowObject> {
monitor.checkCancelled();
monitor.incrementProgress(1);
if (tooManyResults()) {
if (currentAccumulator.getProgress() > MAX_RESULTS) {
return;
}
Data data = dataIterator.next();
int numComponents = data.getNumComponents();
if (numComponents > 0) {
findScalarsInCompositeData(data, numComponents, monitor);
}
@@ -223,7 +215,7 @@ public class ScalarSearchModel extends AddressBasedTableModel<ScalarRowObject> {
return;
}
sizedAccumulator.add(rowObject);
currentAccumulator.add(rowObject);
}
private void findScalarsInCompositeData(Data data, int numComponents, TaskMonitor monitor)
@@ -59,7 +59,7 @@ public abstract class AbstractSearchTableModel extends ProgramLocationPreviewTab
Searcher searcher = getSearcher(tool, monitor);
monitor.checkCancelled();
TextSearchResult result = searcher.search();
while (result != null && accumulator.size() < searchLimit) {
while (result != null && accumulator.getProgress() < searchLimit) {
accumulator.add(result.programLocation());
monitor.checkCancelled();
result = searcher.search();
@@ -64,11 +64,6 @@ public class CombinedMatchTableLoader implements MemoryMatchTableLoader {
previousResults = null;
}
@Override
public MemoryMatch getFirstMatch() {
return firstMatch;
}
@Override
public boolean hasResults() {
return firstMatch != null;
@@ -40,11 +40,6 @@ public class EmptyMemoryMatchTableLoader implements MemoryMatchTableLoader {
return false;
}
@Override
public MemoryMatch<SearchData> getFirstMatch() {
return null;
}
@Override
public boolean hasResults() {
return false;
@@ -78,11 +78,6 @@ public class FindOnceTableLoader implements MemoryMatchTableLoader {
return false;
}
@Override
public MemoryMatch getFirstMatch() {
return match;
}
@Override
public void dispose() {
previousResults = null;
@@ -47,12 +47,6 @@ public interface MemoryMatchTableLoader {
*/
public void dispose();
/**
* Returns the first match found. Typically used to navigate the associated navigatable.
* @return the first match found
*/
public MemoryMatch getFirstMatch();
/**
* Returns true if at least one match was found.
* @return true if at least one match was found
@@ -15,8 +15,6 @@
*/
package ghidra.features.base.memsearch.gui;
import java.util.Iterator;
import ghidra.features.base.memsearch.matcher.SearchData;
import ghidra.features.base.memsearch.searcher.MemoryMatch;
import ghidra.features.base.memsearch.searcher.MemorySearcher;
@@ -30,7 +28,7 @@ public class NewSearchTableLoader implements MemoryMatchTableLoader {
private MemorySearcher<SearchData> memSearcher;
private boolean completedSearch;
private MemoryMatch<SearchData> firstMatch;
private boolean hasResults;
NewSearchTableLoader(MemorySearcher<SearchData> memSearcher) {
this.memSearcher = memSearcher;
@@ -39,10 +37,7 @@ public class NewSearchTableLoader implements MemoryMatchTableLoader {
@Override
public void loadResults(Accumulator<MemoryMatch<SearchData>> accumulator, TaskMonitor monitor) {
completedSearch = memSearcher.findAll(accumulator, monitor);
Iterator<MemoryMatch<SearchData>> iterator = accumulator.iterator();
if (iterator.hasNext()) {
firstMatch = iterator.next();
}
hasResults = accumulator.getProgress() > 0;
}
@Override
@@ -55,14 +50,9 @@ public class NewSearchTableLoader implements MemoryMatchTableLoader {
// nothing to do
}
@Override
public MemoryMatch getFirstMatch() {
return firstMatch;
}
@Override
public boolean hasResults() {
return firstMatch != null;
return hasResults;
}
}
@@ -38,7 +38,7 @@ public class RefreshResultsTableLoader implements MemoryMatchTableLoader {
@Override
public void loadResults(Accumulator<MemoryMatch<SearchData>> accumulator, TaskMonitor monitor) {
accumulator.addAll(matches);
hasResults = !accumulator.isEmpty();
hasResults = accumulator.getProgress() > 0;
}
@Override
@@ -52,11 +52,6 @@ public class RefreshResultsTableLoader implements MemoryMatchTableLoader {
return false;
}
@Override
public MemoryMatch getFirstMatch() {
return null;
}
@Override
public boolean hasResults() {
return hasResults;
@@ -327,7 +327,7 @@ public class MemorySearcher<T> {
byte[] bytes = searchSequence.getBytes((int) match.getStart(), match.getLength());
MemoryMatch<T> memMatch = new MemoryMatch<>(address, bytes, match.getPattern());
if (filter.test(memMatch)) {
if (accumulator.size() >= searchLimit) {
if (accumulator.getProgress() >= searchLimit) {
return false;
}
accumulator.add(memMatch);
@@ -50,7 +50,7 @@ public class SearchAndReplaceQuckFixTableLoader implements TableDataLoader<Quick
searchLimitExceeded = true;
}
finally {
hasResults = !accumulator.isEmpty();
hasResults = accumulator.getProgress() != 0;
}
}
@@ -56,9 +56,10 @@ public class DatatypeCategorySearchAndReplaceHandler extends SearchAndReplaceHan
Matcher matcher = pattern.matcher(category.getName());
if (matcher.find()) {
String newName = matcher.replaceAll(query.getReplacementText());
RenameCategoryQuickFix item = new RenameCategoryQuickFix(program, category, newName);
RenameCategoryQuickFix item =
new RenameCategoryQuickFix(program, category, newName);
accumulator.add(item);
if (accumulator.size() >= query.getSearchLimit()) {
if (accumulator.getProgress() >= query.getSearchLimit()) {
return;
}
}
@@ -35,8 +35,7 @@ import ghidra.app.script.GhidraScript;
import ghidra.features.base.memsearch.bytesource.AddressableByteSource;
import ghidra.features.base.memsearch.bytesource.ProgramByteSource;
import ghidra.features.base.memsearch.gui.SearchSettings;
import ghidra.features.base.memsearch.matcher.ByteMatcher;
import ghidra.features.base.memsearch.matcher.RegExByteMatcher;
import ghidra.features.base.memsearch.matcher.*;
import ghidra.features.base.memsearch.searcher.MemoryMatch;
import ghidra.features.base.memsearch.searcher.MemorySearcher;
import ghidra.framework.main.AppInfo;
@@ -54,7 +53,6 @@ import ghidra.program.model.util.CodeUnitInsertionException;
import ghidra.program.util.AddressEvaluator;
import ghidra.program.util.string.*;
import ghidra.util.ascii.AsciiCharSetRecognizer;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.datastruct.ListAccumulator;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
@@ -815,13 +813,14 @@ public class FlatProgramAPI {
}
SearchSettings settings = new SearchSettings().withAlignment(alignment);
ByteMatcher matcher = new RegExByteMatcher(byteString, settings);
ByteMatcher<SearchData> matcher = new RegExByteMatcher(byteString, settings);
AddressableByteSource byteSource = new ProgramByteSource(currentProgram);
Memory memory = currentProgram.getMemory();
AddressSet intersection = memory.getLoadedAndInitializedAddressSet().intersect(set);
MemorySearcher searcher = new MemorySearcher(byteSource, matcher, intersection, matchLimit);
Accumulator<MemoryMatch> accumulator = new ListAccumulator<>();
MemorySearcher<SearchData> searcher =
new MemorySearcher<>(byteSource, matcher, intersection, matchLimit);
ListAccumulator<MemoryMatch<SearchData>> accumulator = new ListAccumulator<>();
searcher.findAll(accumulator, monitor);
//@formatter:off
@@ -264,7 +264,7 @@ public class ProgramMemoryUtil {
public static void loadDirectReferenceList(Program program, int alignment, Address toAddress,
AddressSetView toAddressSet, List<ReferenceAddressPair> directReferenceList,
TaskMonitor monitor) throws CancelledException {
Accumulator<ReferenceAddressPair> accumulator = new ListAccumulator<>();
ListAccumulator<ReferenceAddressPair> accumulator = new ListAccumulator<>();
loadDirectReferenceList(program, alignment, toAddress, toAddressSet, accumulator, monitor);
directReferenceList.addAll(accumulator.get());
}
@@ -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.
@@ -45,8 +45,7 @@ import ghidra.program.util.FieldNameFieldLocation;
import ghidra.test.AbstractProgramBasedTest;
import ghidra.test.ClassicSampleX86ProgramBuilder;
import ghidra.util.Msg;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.datastruct.ListAccumulator;
import ghidra.util.datastruct.SetAccumulator;
import ghidra.util.exception.CancelledException;
import ghidra.util.table.GhidraTable;
import ghidra.util.task.TaskMonitor;
@@ -248,7 +247,7 @@ public abstract class AbstractLocationReferencesTest extends AbstractProgramBase
waitForTable();
Accumulator<LocationReference> accumulator = new ListAccumulator<>();
SetAccumulator<LocationReference> accumulator = new SetAccumulator<>();
runSwing(() -> {
try {
locationDescriptor.getReferences(accumulator, TaskMonitor.DUMMY, false);
@@ -27,7 +27,6 @@ import ghidra.features.base.memsearch.bytesource.SearchRegion;
import ghidra.features.base.memsearch.matcher.*;
import ghidra.program.model.address.*;
import ghidra.program.util.ProgramLocation;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.datastruct.ListAccumulator;
import ghidra.util.task.TaskMonitor;
@@ -38,7 +37,7 @@ public class MemSearcherTest {
private AddressSpace space;
private TaskMonitor monitor = TaskMonitor.DUMMY;
private ByteMatcher<SearchData> bobMatcher = new RegExByteMatcher("bob", null);
private Accumulator<MemoryMatch<SearchData>> accumulator = new ListAccumulator<>();
private ListAccumulator<MemoryMatch<SearchData>> accumulator = new ListAccumulator<>();
@Before
public void setUp() {
@@ -340,7 +339,7 @@ public class MemSearcherTest {
public void invalidate() {
// ignore
}
@Override
public ProgramLocation getCanonicalLocation(Address address) {
return AddressableByteSource.generateProgramLocation(null, address);
@@ -43,7 +43,7 @@ import ghidra.program.util.ProgramMemoryUtil;
import ghidra.util.InvalidNameException;
import ghidra.util.Msg;
import ghidra.util.bytesearch.*;
import ghidra.util.datastruct.ListAccumulator;
import ghidra.util.datastruct.SetAccumulator;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
@@ -4287,14 +4287,13 @@ public class RecoveredClassHelper {
monitor.checkCancelled();
ListAccumulator<LocationReference> accumulator = new ListAccumulator<>();
boolean discoverTypes = true;
ReferenceUtils.findDataTypeReferences(accumulator, badStructure, program, discoverTypes,
monitor);
List<LocationReference> referenceList = accumulator.asList();
if (referenceList.isEmpty()) {
SetAccumulator<LocationReference> accumulator = new SetAccumulator<>();
ReferenceUtils.findDataTypeReferences(accumulator, badStructure, program,
discoverTypes, monitor);
if (accumulator.size() == 0) {
// delete empty class data type and empty parent folders
removeEmptyStructure(badStructure.getDataTypePath().getCategoryPath(),
badStructure.getName());
@@ -17,12 +17,12 @@ package docking.widgets.table.threaded;
import static org.apache.commons.lang3.exception.ExceptionUtils.*;
import java.util.Collection;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import ghidra.util.Msg;
import ghidra.util.Swing;
import ghidra.util.datastruct.SynchronizedListAccumulator;
import ghidra.util.datastruct.Accumulator;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.SwingUpdateManager;
import ghidra.util.task.TaskMonitor;
@@ -218,7 +218,9 @@ public class IncrementalLoadJob<ROW_OBJECT> extends Job implements ThreadedTable
* An accumulator that will essentially periodically update the table with the data that
* is being provided to the accumulator.
*/
private class IncrementalUpdatingAccumulator extends SynchronizedListAccumulator<ROW_OBJECT> {
private class IncrementalUpdatingAccumulator implements Accumulator<ROW_OBJECT> {
private List<ROW_OBJECT> list = new ArrayList<>();
private volatile boolean isDone;
private Runnable runnable = () -> {
@@ -245,12 +247,6 @@ public class IncrementalLoadJob<ROW_OBJECT> extends Job implements ThreadedTable
new SwingUpdateManager((int) threadedModel.getMinDelay(),
(int) threadedModel.getMaxDelay(), "Incremental Table Load Update", runnable);
@Override
public synchronized void add(ROW_OBJECT t) {
super.add(t);
swingUpdateManager.update();
}
private boolean isCancelledOrDone() {
return isCancelled || isDone;
}
@@ -259,18 +255,37 @@ public class IncrementalLoadJob<ROW_OBJECT> extends Job implements ThreadedTable
swingUpdateManager.dispose();
}
@Override
public synchronized void addAll(Collection<ROW_OBJECT> collection) {
super.addAll(collection);
if (collection.size() > 0) {
swingUpdateManager.update();
}
}
void flushData() {
isDone = true;
swingUpdateManager.dispose();
updateManager.reloadSpecificData(asList());
}
@Override
public synchronized void add(ROW_OBJECT t) {
list.add(t);
swingUpdateManager.update();
}
@Override
public synchronized void addAll(Collection<ROW_OBJECT> collection) {
list.addAll(collection);
if (collection.size() > 0) {
swingUpdateManager.update();
}
}
private synchronized List<ROW_OBJECT> asList() {
return new ArrayList<>(list);
}
@Override
public synchronized int getProgress() {
return list.size();
}
private synchronized void clear() {
list.clear();
}
}
}
@@ -17,8 +17,6 @@ package ghidra.util.datastruct;
import java.util.Collection;
import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
/**
* The interface provides a mechanism for clients to pass around an object that is effectively
@@ -30,27 +28,23 @@ import java.util.stream.StreamSupport;
* to be returned by it) so that the client can make use of data as it is discovered. This
* allows for long searching processes to report data as they work.
*
* <P>
* Using this class implies that data will be added asynchronously. Implementations of this
* interface should properly synchronize storage so that the data written is visible to the client
* thread.
*
* @param <T> the type
*/
public interface Accumulator<T> extends Iterable<T>, Consumer<T> {
public interface Accumulator<T> extends Consumer<T> {
public void add(T t);
public void addAll(Collection<T> collection);
public boolean contains(T t);
public Collection<T> get();
public int size();
default boolean isEmpty() {
return size() == 0;
}
default Stream<T> stream() {
return StreamSupport.stream(spliterator(), false);
}
/**
* {@return the number of items that have been added to this accumulator}
*/
public int getProgress();
@Override
default void accept(T t) {
@@ -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.
@@ -15,23 +15,20 @@
*/
package ghidra.util.datastruct;
import java.util.*;
import java.util.Collection;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
/**
* An implementation of {@link Accumulator} that allows clients to easily process items as
* they arrive.
* they arrive.
*
* <P>This class is different than normal accumulators in that the values are <b>not</b>
* stored internally. As such, calls to {@link #get()}, {@link #iterator()} and
* {@link #size()} will reflect having no data.
*
* @param <T> the type of the item being accumulated
*/
public class CallbackAccumulator<T> implements Accumulator<T> {
private final Collection<T> NULL_COLLECTION = Collections.emptyList();
private AtomicInteger counter;
private Consumer<T> consumer;
/**
@@ -46,33 +43,19 @@ public class CallbackAccumulator<T> implements Accumulator<T> {
@Override
public void add(T t) {
consumer.accept(t);
counter.incrementAndGet();
}
@Override
public void addAll(Collection<T> collection) {
for (T t : collection) {
consumer.accept(t);
add(t);
}
}
@Override
public boolean contains(T t) {
return false;
}
@Override
public Collection<T> get() {
return NULL_COLLECTION;
}
@Override
public Iterator<T> iterator() {
return NULL_COLLECTION.iterator();
}
@Override
public int size() {
return 0;
public int getProgress() {
return counter.intValue();
}
}
@@ -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.
@@ -20,6 +20,8 @@ import org.apache.commons.lang3.mutable.MutableInt;
/**
* Simple class used to avoid immutable objects and autoboxing when storing changing integer
* primitives in a collection.
* <P>
* @apiNote This class is not thread-safe.
*/
public class Counter extends MutableInt {
/**
@@ -1,85 +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.util.datastruct;
import java.util.*;
import java.util.function.Predicate;
/**
* A class that allows clients to wrap a given accumulator, only adding elements that pass the
* given filter.
*
* @param <T> the type of the accumulator
*/
public class FilteringAccumulatorWrapper<T> implements Accumulator<T> {
private Accumulator<T> accumulator;
private Predicate<T> predicate;
/**
* Constructor.
*
* @param accumulator the accumulator to pass items to
* @param passesFilterPredicate the predicate that will return true for items that should be
* allowed to pass
*/
public FilteringAccumulatorWrapper(Accumulator<T> accumulator,
Predicate<T> passesFilterPredicate) {
this.predicate = passesFilterPredicate;
this.accumulator = Objects.requireNonNull(accumulator);
}
private boolean passesFilter(T t) {
return predicate.test(t);
}
@Override
public Iterator<T> iterator() {
return accumulator.iterator();
}
@Override
public void add(T t) {
if (passesFilter(t)) {
accumulator.add(t);
}
}
@Override
public void addAll(Collection<T> collection) {
collection.forEach(t -> {
if (passesFilter(t)) {
accumulator.add(t);
}
});
}
@Override
public boolean contains(T t) {
return accumulator.contains(t);
}
@Override
public Collection<T> get() {
return accumulator.get();
}
@Override
public int size() {
return accumulator.size();
}
}
@@ -1,13 +1,12 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -17,18 +16,22 @@
package ghidra.util.datastruct;
import java.util.*;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public class ListAccumulator<T> implements Accumulator<T> {
/**
* An accumulator backed by a thread safe list. This class has methods to retrieve the data once
* all loading has finished.
*
* <P>
* API uses of the accumulator are inherently multi-threaded. The list in this class is
* synchronized so that the data in the accumulator will be visible to the client thread.
*
* @param <T> the type
*/
public class ListAccumulator<T> implements Accumulator<T>, Iterable<T> {
private List<T> list;
public ListAccumulator() {
this.list = new ArrayList<T>();
}
public ListAccumulator(List<T> list) {
this.list = list;
}
private List<T> list = Collections.synchronizedList(new ArrayList<>());
@Override
public void add(T t) {
@@ -41,11 +44,14 @@ public class ListAccumulator<T> implements Accumulator<T> {
}
@Override
public int getProgress() {
return list.size();
}
public boolean contains(T t) {
return list.contains(t);
}
@Override
public Collection<T> get() {
return list;
}
@@ -54,7 +60,6 @@ public class ListAccumulator<T> implements Accumulator<T> {
return list;
}
@Override
public int size() {
return list.size();
}
@@ -64,6 +69,10 @@ public class ListAccumulator<T> implements Accumulator<T> {
return list.iterator();
}
public Stream<T> stream() {
return StreamSupport.stream(spliterator(), false);
}
@Override
public String toString() {
return list.toString();
@@ -1,13 +1,12 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@@ -17,18 +16,23 @@
package ghidra.util.datastruct;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
public class SetAccumulator<T> implements Accumulator<T> {
/**
* An accumulator backed by a thread safe set. This class has methods to retrieve the data once all
* loading has finished.
*
* <P>
* API uses of the accumulator are inherently multi-threaded. The set in this class is
* synchronized so that the data in the accumulator will be visible to the client thread.
*
* @param <T> the type
*/
public class SetAccumulator<T> implements Accumulator<T>, Iterable<T> {
private Set<T> set;
public SetAccumulator() {
this.set = new HashSet<T>();
}
public SetAccumulator(Set<T> set) {
this.set = set;
}
private Set<T> set = ConcurrentHashMap.newKeySet();
@Override
public void add(T t) {
@@ -40,12 +44,10 @@ public class SetAccumulator<T> implements Accumulator<T> {
set.addAll(collection);
}
@Override
public boolean contains(T t) {
return set.contains(t);
}
@Override
public Collection<T> get() {
return set;
}
@@ -55,6 +57,10 @@ public class SetAccumulator<T> implements Accumulator<T> {
}
@Override
public int getProgress() {
return set.size();
}
public int size() {
return set.size();
}
@@ -64,6 +70,10 @@ public class SetAccumulator<T> implements Accumulator<T> {
return set.iterator();
}
public Stream<T> stream() {
return StreamSupport.stream(spliterator(), false);
}
@Override
public String toString() {
return set.toString();
@@ -0,0 +1,65 @@
/* ###
* 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.util.datastruct;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* An accumulator that will only pass on unique items to the wrapped accumulator. This class uses
* a concurrent set, which means that the data will be copied in this accumulator while it is being
* used.
*
* @param <T> the type
*/
public class SetAccumulatorWrapper<T> implements Accumulator<T> {
private Set<T> set = ConcurrentHashMap.newKeySet();
private Accumulator<T> accumulator;
public SetAccumulatorWrapper(Accumulator<T> accumulator) {
this.accumulator = accumulator;
}
@Override
public void add(T t) {
if (set.add(t)) {
accumulator.add(t);
}
}
@Override
public void addAll(Collection<T> collection) {
for (T t : collection) {
add(t);
}
}
@Override
public int getProgress() {
return set.size();
}
/**
* Returns the internal Set used by this class. This should only be called when the data is
* finished loading.
* @return the set
*/
public Set<T> asSet() {
return set;
}
}
@@ -1,75 +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.util.datastruct;
import java.util.*;
public class SizeLimitedAccumulatorWrapper<T> implements Accumulator<T> {
private Accumulator<T> accumulator;
private int maxSize;
/**
* Constructor.
*
* @param accumulator the accumulator to pass items to
* @param maxSize the maximum number of items this accumulator will hold
*/
public SizeLimitedAccumulatorWrapper(Accumulator<T> accumulator, int maxSize) {
this.accumulator = Objects.requireNonNull(accumulator);
this.maxSize = maxSize;
}
@Override
public Iterator<T> iterator() {
return accumulator.iterator();
}
@Override
public void add(T t) {
accumulator.add(t);
}
@Override
public void addAll(Collection<T> collection) {
accumulator.addAll(collection);
}
@Override
public boolean contains(T t) {
return accumulator.contains(t);
}
@Override
public Collection<T> get() {
return accumulator.get();
}
@Override
public int size() {
return accumulator.size();
}
/**
* Returns true if this size of this accumulator is greater than or equal to the given
* maximum size
*
* @return true if the max size has been reachged
*/
public boolean hasReachedSizeLimit() {
return accumulator.size() >= maxSize;
}
}
@@ -15,7 +15,8 @@
*/
package ghidra.util.datastruct;
import java.util.*;
import java.util.Collection;
import java.util.Objects;
public class SizeRestrictedAccumulatorWrapper<T> implements Accumulator<T> {
@@ -33,14 +34,9 @@ public class SizeRestrictedAccumulatorWrapper<T> implements Accumulator<T> {
this.maxSize = maxSize;
}
@Override
public Iterator<T> iterator() {
return accumulator.iterator();
}
@Override
public void add(T t) {
if (accumulator.size() >= maxSize) {
if (accumulator.getProgress() >= maxSize) {
throw new AccumulatorSizeException(maxSize);
}
accumulator.add(t);
@@ -49,23 +45,13 @@ public class SizeRestrictedAccumulatorWrapper<T> implements Accumulator<T> {
@Override
public void addAll(Collection<T> collection) {
for (T t : collection) {
accumulator.add(t);
add(t);
}
}
@Override
public boolean contains(T t) {
return accumulator.contains(t);
}
@Override
public Collection<T> get() {
return accumulator.get();
}
@Override
public int size() {
return accumulator.size();
public int getProgress() {
return accumulator.getProgress();
}
}
@@ -1,74 +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.util.datastruct;
import java.util.*;
public class SynchronizedListAccumulator<T> implements Accumulator<T> {
private List<T> list;
public SynchronizedListAccumulator() {
this.list = new ArrayList<T>();
}
public SynchronizedListAccumulator(List<T> list) {
this.list = new ArrayList<T>(list);
}
@Override
public synchronized void add(T t) {
list.add(t);
}
@Override
public synchronized void addAll(Collection<T> collection) {
list.addAll(collection);
}
@Override
public synchronized boolean contains(T t) {
return list.contains(t);
}
@Override
public synchronized Collection<T> get() {
return new ArrayList<T>(list);
}
public synchronized List<T> asList() {
return new ArrayList<T>(list);
}
@Override
public synchronized int size() {
return list.size();
}
public void clear() {
list.clear();
}
@Override
public synchronized Iterator<T> iterator() {
return asList().iterator();
}
@Override
public synchronized String toString() {
return list.toString();
}
}