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) {
return null;
}

View File

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

View File

@@ -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.
@@ -24,14 +24,15 @@ import ghidra.program.model.lang.LanguageCompilerSpecPair;
* <p>
* This has the same information as a {@link LoadSpec}, but for all the members of a
* {@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 LanguageCompilerSpecPair lcsPair;
public boolean preferred;
public record BatchGroupLoadSpec(LanguageCompilerSpecPair lcsPair, boolean preferred)
implements Comparable<BatchGroupLoadSpec> {
public BatchGroupLoadSpec(LoadSpec loadSpec) {
lcsPair = loadSpec.getLanguageCompilerSpec();
preferred = loadSpec.isPreferred();
this(loadSpec.getLanguageCompilerSpec(), loadSpec.isPreferred());
}
@Override
@@ -45,41 +46,6 @@ public class BatchGroupLoadSpec implements Comparable<BatchGroupLoadSpec> {
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
public int compareTo(BatchGroupLoadSpec o) {
String s1 = this.toString();

View File

@@ -40,15 +40,18 @@ import docking.widgets.label.GDLabel;
import docking.widgets.table.*;
import generic.theme.GThemeDefaults.Colors.Messages;
import ghidra.app.services.ProgramManager;
import ghidra.app.util.opinion.LoadSpec;
import ghidra.formats.gfilesystem.FSRL;
import ghidra.formats.gfilesystem.FileSystemService;
import ghidra.framework.main.AppInfo;
import ghidra.framework.model.DomainFolder;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.preferences.Preferences;
import ghidra.plugin.importer.ImporterLanguageDialog;
import ghidra.plugin.importer.ImporterUtilities;
import ghidra.plugins.importer.batch.BatchGroup.BatchLoadConfig;
import ghidra.plugins.importer.tasks.ImportBatchTask;
import ghidra.program.model.lang.LanguageCompilerSpecPair;
import ghidra.util.*;
import ghidra.util.filechooser.GhidraFileFilter;
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,
DomainFolder defaultFolder, ProgramManager programManager) {
BatchImportDialog dialog = new BatchImportDialog(batchInfo, defaultFolder, programManager);
BatchImportDialog dialog =
new BatchImportDialog(batchInfo, defaultFolder, programManager, tool);
Swing.runLater(() -> {
if (initialFiles != null && !initialFiles.isEmpty()) {
dialog.addSources(initialFiles);
@@ -91,6 +95,7 @@ public class BatchImportDialog extends DialogComponentProvider {
private BatchInfo batchInfo;
private DomainFolder destinationFolder;
private ProgramManager programManager;
private PluginTool tool;
private boolean stripLeading = getBooleanPref(PREF_STRIPLEADING, true);
private boolean stripContainer = getBooleanPref(PREF_STRIPCONTAINER, false);
private boolean mirrorFs = getBooleanPref(PREF_MIRRORFS, false);
@@ -109,13 +114,14 @@ public class BatchImportDialog extends DialogComponentProvider {
private SourcesListModel sourceListModel;
private BatchImportDialog(BatchInfo batchInfo, DomainFolder defaultFolder,
ProgramManager programManager) {
ProgramManager programManager, PluginTool tool) {
super("Batch Import", true);
this.batchInfo = (batchInfo != null) ? batchInfo : new BatchInfo();
this.destinationFolder = defaultFolder != null ? defaultFolder
: AppInfo.getActiveProject().getProjectData().getRootFolder();
this.programManager = programManager;
this.tool = tool;
setHelpLocation(new HelpLocation("ImporterPlugin", "Batch_Import_Dialog"));
@@ -150,6 +156,9 @@ public class BatchImportDialog extends DialogComponentProvider {
if (modelIndex == BatchImportTableModel.COLS.FILES.ordinal()) {
showFiles(row);
}
else if (modelIndex == BatchImportTableModel.COLS.LANG.ordinal()) {
showLanguages(row);
}
}
});
@@ -171,7 +180,6 @@ public class BatchImportDialog extends DialogComponentProvider {
TableColumn langColumn =
table.getColumnModel().getColumn(BatchImportTableModel.COLS.LANG.ordinal());
langColumn.setCellEditor(createLangColumnCellEditor());
langColumn.setCellRenderer(createLangColumnCellRenderer());
JScrollPane scrollPane = new JScrollPane(table);
@@ -377,13 +385,10 @@ public class BatchImportDialog extends DialogComponentProvider {
BatchGroup group = tableModel.getRowObject(row);
List<BatchLoadConfig> batchLoadConfigs = group.getBatchLoadConfig();
//@formatter:off
List<String> names = batchLoadConfigs.stream()
.map(batchLoadConfig -> batchLoadConfig.getPreferredFileName())
.sorted()
.collect(Collectors.toList())
;
//@formatter:on
.map(batchLoadConfig -> batchLoadConfig.getPreferredFileName())
.sorted()
.collect(Collectors.toList());
ListSelectionTableDialog<String> dialog =
new ListSelectionTableDialog<>("Application Files", names);
@@ -391,6 +396,36 @@ public class BatchImportDialog extends DialogComponentProvider {
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) {
this.openAfterImporting = b;
}
@@ -534,30 +569,6 @@ public class BatchImportDialog extends DialogComponentProvider {
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() {
TableCellRenderer cellRenderer = new GTableCellRenderer() {
{
@@ -579,7 +590,6 @@ public class BatchImportDialog extends DialogComponentProvider {
"\">Click to set language</font>";
}
};
return cellRenderer;
}

View File

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

View File

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

View File

@@ -141,12 +141,6 @@ public class ImportBatchTask extends Task {
try (ByteProvider byteProvider = FileSystemService.getInstance()
.getByteProvider(batchLoadConfig.getFSRL(), true, monitor)) {
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);
try {