Merge remote-tracking branch 'origin/GP-6732_ryanmkurtz_batch--SQUASHED'

(Closes #1934)
This commit is contained in:
Ryan Kurtz
2026-06-22 13:30:03 -04:00
7 changed files with 67 additions and 102 deletions

View File

@@ -135,7 +135,7 @@ public class ImporterLanguageDialog extends DialogComponentProvider {
} }
} }
LanguageCompilerSpecPair getSelectedLanguage() { public LanguageCompilerSpecPair getSelectedLanguage() {
if (wasDialogCancelled) { if (wasDialogCancelled) {
return null; return null;
} }

View File

@@ -59,12 +59,10 @@ public class BatchGroup {
} }
public LoadSpec getLoadSpec(BatchGroupLoadSpec batchGroupLoadSpec) { public LoadSpec getLoadSpec(BatchGroupLoadSpec batchGroupLoadSpec) {
for (LoadSpec loadSpec : loadSpecs) { return loadSpecs.stream()
if (batchGroupLoadSpec.matches(loadSpec)) { .filter(batchGroupLoadSpec::matches)
return loadSpec; .findFirst()
} .orElse(new LoadSpec(loader, 0, batchGroupLoadSpec.lcsPair(), false));
}
return null;
} }
public UserAddedSourceInfo getUasi() { public UserAddedSourceInfo getUasi() {

View File

@@ -24,14 +24,15 @@ import ghidra.program.model.lang.LanguageCompilerSpecPair;
* <p> * <p>
* This has the same information as a {@link LoadSpec}, but for all the members of a * This has the same information as a {@link LoadSpec}, but for all the members of a
* {@link BatchGroup}. * {@link BatchGroup}.
*
* @param lcsPair The language/compiler spec ID
* @param preferred true if this {@link BatchGroupLoadSpec} is preferred; otherwise, false
*/ */
public class BatchGroupLoadSpec implements Comparable<BatchGroupLoadSpec> { public record BatchGroupLoadSpec(LanguageCompilerSpecPair lcsPair, boolean preferred)
public LanguageCompilerSpecPair lcsPair; implements Comparable<BatchGroupLoadSpec> {
public boolean preferred;
public BatchGroupLoadSpec(LoadSpec loadSpec) { public BatchGroupLoadSpec(LoadSpec loadSpec) {
lcsPair = loadSpec.getLanguageCompilerSpec(); this(loadSpec.getLanguageCompilerSpec(), loadSpec.isPreferred());
preferred = loadSpec.isPreferred();
} }
@Override @Override
@@ -45,41 +46,6 @@ public class BatchGroupLoadSpec implements Comparable<BatchGroupLoadSpec> {
loadSpec.getLanguageCompilerSpec().equals(lcsPair)); loadSpec.getLanguageCompilerSpec().equals(lcsPair));
} }
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((lcsPair == null) ? 0 : lcsPair.hashCode());
result = prime * result + (preferred ? 1231 : 1237);
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (!(obj instanceof BatchGroupLoadSpec)) {
return false;
}
BatchGroupLoadSpec other = (BatchGroupLoadSpec) obj;
if (lcsPair == null) {
if (other.lcsPair != null) {
return false;
}
}
else if (!lcsPair.equals(other.lcsPair)) {
return false;
}
if (preferred != other.preferred) {
return false;
}
return true;
}
@Override @Override
public int compareTo(BatchGroupLoadSpec o) { public int compareTo(BatchGroupLoadSpec o) {
String s1 = this.toString(); String s1 = this.toString();

View File

@@ -40,15 +40,18 @@ import docking.widgets.label.GDLabel;
import docking.widgets.table.*; import docking.widgets.table.*;
import generic.theme.GThemeDefaults.Colors.Messages; import generic.theme.GThemeDefaults.Colors.Messages;
import ghidra.app.services.ProgramManager; import ghidra.app.services.ProgramManager;
import ghidra.app.util.opinion.LoadSpec;
import ghidra.formats.gfilesystem.FSRL; import ghidra.formats.gfilesystem.FSRL;
import ghidra.formats.gfilesystem.FileSystemService; import ghidra.formats.gfilesystem.FileSystemService;
import ghidra.framework.main.AppInfo; import ghidra.framework.main.AppInfo;
import ghidra.framework.model.DomainFolder; import ghidra.framework.model.DomainFolder;
import ghidra.framework.plugintool.PluginTool; import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.preferences.Preferences; import ghidra.framework.preferences.Preferences;
import ghidra.plugin.importer.ImporterLanguageDialog;
import ghidra.plugin.importer.ImporterUtilities; import ghidra.plugin.importer.ImporterUtilities;
import ghidra.plugins.importer.batch.BatchGroup.BatchLoadConfig; import ghidra.plugins.importer.batch.BatchGroup.BatchLoadConfig;
import ghidra.plugins.importer.tasks.ImportBatchTask; import ghidra.plugins.importer.tasks.ImportBatchTask;
import ghidra.program.model.lang.LanguageCompilerSpecPair;
import ghidra.util.*; import ghidra.util.*;
import ghidra.util.filechooser.GhidraFileFilter; import ghidra.util.filechooser.GhidraFileFilter;
import ghidra.util.task.TaskLauncher; import ghidra.util.task.TaskLauncher;
@@ -76,7 +79,8 @@ public class BatchImportDialog extends DialogComponentProvider {
*/ */
public static void showAndImport(PluginTool tool, BatchInfo batchInfo, List<FSRL> initialFiles, public static void showAndImport(PluginTool tool, BatchInfo batchInfo, List<FSRL> initialFiles,
DomainFolder defaultFolder, ProgramManager programManager) { DomainFolder defaultFolder, ProgramManager programManager) {
BatchImportDialog dialog = new BatchImportDialog(batchInfo, defaultFolder, programManager); BatchImportDialog dialog =
new BatchImportDialog(batchInfo, defaultFolder, programManager, tool);
Swing.runLater(() -> { Swing.runLater(() -> {
if (initialFiles != null && !initialFiles.isEmpty()) { if (initialFiles != null && !initialFiles.isEmpty()) {
dialog.addSources(initialFiles); dialog.addSources(initialFiles);
@@ -91,6 +95,7 @@ public class BatchImportDialog extends DialogComponentProvider {
private BatchInfo batchInfo; private BatchInfo batchInfo;
private DomainFolder destinationFolder; private DomainFolder destinationFolder;
private ProgramManager programManager; private ProgramManager programManager;
private PluginTool tool;
private boolean stripLeading = getBooleanPref(PREF_STRIPLEADING, true); private boolean stripLeading = getBooleanPref(PREF_STRIPLEADING, true);
private boolean stripContainer = getBooleanPref(PREF_STRIPCONTAINER, false); private boolean stripContainer = getBooleanPref(PREF_STRIPCONTAINER, false);
private boolean mirrorFs = getBooleanPref(PREF_MIRRORFS, false); private boolean mirrorFs = getBooleanPref(PREF_MIRRORFS, false);
@@ -109,13 +114,14 @@ public class BatchImportDialog extends DialogComponentProvider {
private SourcesListModel sourceListModel; private SourcesListModel sourceListModel;
private BatchImportDialog(BatchInfo batchInfo, DomainFolder defaultFolder, private BatchImportDialog(BatchInfo batchInfo, DomainFolder defaultFolder,
ProgramManager programManager) { ProgramManager programManager, PluginTool tool) {
super("Batch Import", true); super("Batch Import", true);
this.batchInfo = (batchInfo != null) ? batchInfo : new BatchInfo(); this.batchInfo = (batchInfo != null) ? batchInfo : new BatchInfo();
this.destinationFolder = defaultFolder != null ? defaultFolder this.destinationFolder = defaultFolder != null ? defaultFolder
: AppInfo.getActiveProject().getProjectData().getRootFolder(); : AppInfo.getActiveProject().getProjectData().getRootFolder();
this.programManager = programManager; this.programManager = programManager;
this.tool = tool;
setHelpLocation(new HelpLocation("ImporterPlugin", "Batch_Import_Dialog")); setHelpLocation(new HelpLocation("ImporterPlugin", "Batch_Import_Dialog"));
@@ -150,6 +156,9 @@ public class BatchImportDialog extends DialogComponentProvider {
if (modelIndex == BatchImportTableModel.COLS.FILES.ordinal()) { if (modelIndex == BatchImportTableModel.COLS.FILES.ordinal()) {
showFiles(row); showFiles(row);
} }
else if (modelIndex == BatchImportTableModel.COLS.LANG.ordinal()) {
showLanguages(row);
}
} }
}); });
@@ -171,7 +180,6 @@ public class BatchImportDialog extends DialogComponentProvider {
TableColumn langColumn = TableColumn langColumn =
table.getColumnModel().getColumn(BatchImportTableModel.COLS.LANG.ordinal()); table.getColumnModel().getColumn(BatchImportTableModel.COLS.LANG.ordinal());
langColumn.setCellEditor(createLangColumnCellEditor());
langColumn.setCellRenderer(createLangColumnCellRenderer()); langColumn.setCellRenderer(createLangColumnCellRenderer());
JScrollPane scrollPane = new JScrollPane(table); JScrollPane scrollPane = new JScrollPane(table);
@@ -377,13 +385,10 @@ public class BatchImportDialog extends DialogComponentProvider {
BatchGroup group = tableModel.getRowObject(row); BatchGroup group = tableModel.getRowObject(row);
List<BatchLoadConfig> batchLoadConfigs = group.getBatchLoadConfig(); List<BatchLoadConfig> batchLoadConfigs = group.getBatchLoadConfig();
//@formatter:off
List<String> names = batchLoadConfigs.stream() List<String> names = batchLoadConfigs.stream()
.map(batchLoadConfig -> batchLoadConfig.getPreferredFileName()) .map(batchLoadConfig -> batchLoadConfig.getPreferredFileName())
.sorted() .sorted()
.collect(Collectors.toList()) .collect(Collectors.toList());
;
//@formatter:on
ListSelectionTableDialog<String> dialog = ListSelectionTableDialog<String> dialog =
new ListSelectionTableDialog<>("Application Files", names); new ListSelectionTableDialog<>("Application Files", names);
@@ -391,6 +396,36 @@ public class BatchImportDialog extends DialogComponentProvider {
dialog.showSelectMultiple(table); dialog.showSelectMultiple(table);
} }
private void showLanguages(int row) {
BatchGroup group = tableModel.getRowObject(row);
List<BatchLoadConfig> batchLoadConfigs = group.getBatchLoadConfig();
List<LoadSpec> loadSpecs = batchLoadConfigs.stream()
.flatMap(entry -> entry.getLoadSpecs().stream())
.distinct()
.collect(Collectors.toList());
BatchGroupLoadSpec selectedLoadSpec = group.getSelectedBatchGroupLoadSpec();
ImporterLanguageDialog dialog =
new ImporterLanguageDialog(loadSpecs, tool, selectedLoadSpec.lcsPair());
dialog.show(getComponent());
LanguageCompilerSpecPair dialogResult = dialog.getSelectedLanguage();
if (dialogResult != null) {
for (BatchLoadConfig loadConfig : batchLoadConfigs) {
for (LoadSpec loadSpec : loadConfig.getLoadSpecs()) {
if (dialogResult.equals(loadSpec.getLanguageCompilerSpec())) {
tableModel.setValueAt(new BatchGroupLoadSpec(loadSpec), row,
BatchImportTableModel.COLS.LANG.ordinal());
return;
}
}
}
tableModel.setValueAt(new BatchGroupLoadSpec(dialogResult, false), row,
BatchImportTableModel.COLS.LANG.ordinal());
}
}
private void setOpenAfterImporting(boolean b) { private void setOpenAfterImporting(boolean b) {
this.openAfterImporting = b; this.openAfterImporting = b;
} }
@@ -534,30 +569,6 @@ public class BatchImportDialog extends DialogComponentProvider {
return cellRenderer; return cellRenderer;
} }
private TableCellEditor createLangColumnCellEditor() {
JComboBox<Object> comboBox = new GComboBox<>();
DefaultCellEditor cellEditor = new DefaultCellEditor(comboBox) {
@Override
public boolean shouldSelectCell(EventObject anEvent) {
return false;
}
@Override
public Component getTableCellEditorComponent(JTable jtable, Object value,
boolean isSelected, int row, int column) {
comboBox.removeAllItems();
BatchGroup batchGroup = tableModel.getRowObject(row);
for (BatchGroupLoadSpec bo : batchGroup.getCriteria().getBatchGroupLoadSpecs()) {
comboBox.addItem(bo);
}
return super.getTableCellEditorComponent(jtable, value, isSelected, row, column);
}
};
return cellEditor;
}
private TableCellRenderer createLangColumnCellRenderer() { private TableCellRenderer createLangColumnCellRenderer() {
TableCellRenderer cellRenderer = new GTableCellRenderer() { TableCellRenderer cellRenderer = new GTableCellRenderer() {
{ {
@@ -579,7 +590,6 @@ public class BatchImportDialog extends DialogComponentProvider {
"\">Click to set language</font>"; "\">Click to set language</font>";
} }
}; };
return cellRenderer; return cellRenderer;
} }

View File

@@ -15,13 +15,10 @@
*/ */
package ghidra.plugins.importer.batch; package ghidra.plugins.importer.batch;
import java.util.Comparator; import java.util.*;
import java.util.List;
import javax.swing.table.TableModel; import javax.swing.table.TableModel;
import org.apache.commons.lang3.StringUtils;
import docking.widgets.table.AbstractSortedTableModel; import docking.widgets.table.AbstractSortedTableModel;
import ghidra.util.Msg; import ghidra.util.Msg;
@@ -33,7 +30,7 @@ class BatchImportTableModel extends AbstractSortedTableModel<BatchGroup> {
SELECTED("Selected", true), SELECTED("Selected", true),
FILETYPE("File Type", false), FILETYPE("File Type", false),
LOADER("Loader", false), LOADER("Loader", false),
LANG("Language", true), LANG("Language", false),
FILES("Files", false); FILES("Files", false);
public final String columnLabel; public final String columnLabel;
@@ -165,7 +162,7 @@ class BatchImportTableModel extends AbstractSortedTableModel<BatchGroup> {
case SELECTED: case SELECTED:
return Boolean.valueOf(row.isEnabled()); return Boolean.valueOf(row.isEnabled());
case FILETYPE: case FILETYPE:
return StringUtils.defaultString(row.getCriteria().getFileExt(), "<no ext>"); return Objects.toString(row.getCriteria().getFileExt(), "<no ext>");
case LOADER: case LOADER:
return row.getCriteria().getLoader(); return row.getCriteria().getLoader();
case LANG: case LANG:

View File

@@ -70,7 +70,7 @@ public class BatchSegregatingCriteria {
public BatchGroupLoadSpec getFirstPreferredLoadSpec() { public BatchGroupLoadSpec getFirstPreferredLoadSpec() {
for (BatchGroupLoadSpec groupLoadSpec : groupLoadSpecs) { for (BatchGroupLoadSpec groupLoadSpec : groupLoadSpecs) {
if (groupLoadSpec.preferred) { if (groupLoadSpec.preferred()) {
return groupLoadSpec; return groupLoadSpec;
} }
} }

View File

@@ -141,12 +141,6 @@ public class ImportBatchTask extends Task {
try (ByteProvider byteProvider = FileSystemService.getInstance() try (ByteProvider byteProvider = FileSystemService.getInstance()
.getByteProvider(batchLoadConfig.getFSRL(), true, monitor)) { .getByteProvider(batchLoadConfig.getFSRL(), true, monitor)) {
LoadSpec loadSpec = batchLoadConfig.getLoadSpec(selectedBatchGroupLoadSpec); LoadSpec loadSpec = batchLoadConfig.getLoadSpec(selectedBatchGroupLoadSpec);
if (loadSpec == null) {
Msg.error(this,
"Failed to get load spec from application that matches chosen batch load spec " +
selectedBatchGroupLoadSpec);
return;
}
Pair<DomainFolder, String> destInfo = getDestinationInfo(batchLoadConfig, destFolder); Pair<DomainFolder, String> destInfo = getDestinationInfo(batchLoadConfig, destFolder);
try { try {