GP-1913 - Updated data type synchronization workflow by adding a new action and a home button to the structure editor. Added action to the composite editors and enum editor to show the type being edited in the Data Type Manager's tree.

This commit is contained in:
dragonmacher 2022-05-19 18:33:40 -04:00
parent d7f9cdfe5c
commit d9af59df1a
53 changed files with 955 additions and 453 deletions

View File

@ -15,8 +15,12 @@
*/
package agent.frida.model.invm;
import agent.frida.model.AbstractModelForFridaFactoryTest;
import org.junit.experimental.categories.Category;
import agent.frida.model.AbstractModelForFridaFactoryTest;
import generic.test.category.NightlyCategory;
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
public class InVmModelForFridaFactoryTest extends AbstractModelForFridaFactoryTest {
@Override
public ModelHost modelHost() throws Throwable {

View File

@ -17,11 +17,14 @@ package agent.frida.model.invm;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import agent.frida.model.AbstractModelForFridaInterpreterTest;
import generic.test.category.NightlyCategory;
import ghidra.dbg.test.ProvidesTargetViaLaunchSpecimen;
public class InVmModelForFridaInterpreterTest extends AbstractModelForFridaInterpreterTest
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
public class InVmModelForFridaInterpreterTest extends AbstractModelForFridaInterpreterTest
implements ProvidesTargetViaLaunchSpecimen {
@Override
public ModelHost modelHost() throws Throwable {
@ -61,4 +64,3 @@ public class InVmModelForFridaInterpreterTest extends AbstractModelForFridaInter
}
}

View File

@ -15,8 +15,12 @@
*/
package agent.frida.model.invm;
import agent.frida.model.AbstractModelForFridaMethodsTest;
import org.junit.experimental.categories.Category;
import agent.frida.model.AbstractModelForFridaMethodsTest;
import generic.test.category.NightlyCategory;
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
public class InVmModelForFridaMethodsTest extends AbstractModelForFridaMethodsTest {
@Override
public ModelHost modelHost() throws Throwable {

View File

@ -17,24 +17,27 @@ package agent.frida.model.invm;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import agent.frida.model.AbstractModelForFridaRootAttacherTest;
import generic.test.category.NightlyCategory;
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
public class InVmModelForFridaRootAttacherTest extends AbstractModelForFridaRootAttacherTest {
@Override
public ModelHost modelHost() throws Throwable {
return new InVmFridaModelHost();
}
// NB: These tests need debugger rights, which means either:
// (1) on macos, codesigning the executables
// (2) on linux, "sudo su; echo 0 > /proc/sys/kernel/yama/ptrace_scope"
@Override
@Ignore // test requires ability to attach by object & frida version requires pid
@Test
public void testAttachByObjBogusThrowsException() throws Throwable {
super.testAttachByObjBogusThrowsException();
}
}

View File

@ -15,8 +15,12 @@
*/
package agent.frida.model.invm;
import agent.frida.model.AbstractModelForFridaRootLauncherTest;
import org.junit.experimental.categories.Category;
import agent.frida.model.AbstractModelForFridaRootLauncherTest;
import generic.test.category.NightlyCategory;
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
public class InVmModelForFridaRootLauncherTest extends AbstractModelForFridaRootLauncherTest {
@Override
public ModelHost modelHost() throws Throwable {

View File

@ -15,8 +15,12 @@
*/
package agent.frida.model.invm;
import agent.frida.model.AbstractModelForFridaScenarioStackTest;
import org.junit.experimental.categories.Category;
import agent.frida.model.AbstractModelForFridaScenarioStackTest;
import generic.test.category.NightlyCategory;
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
public class InVmModelForFridaScenarioStackTest extends AbstractModelForFridaScenarioStackTest {
@Override
public ModelHost modelHost() throws Throwable {

View File

@ -15,13 +15,17 @@
*/
package agent.frida.model.invm;
import agent.frida.model.AbstractModelForFridaX64RegistersTest;
import org.junit.experimental.categories.Category;
import agent.frida.model.AbstractModelForFridaX64RegistersTest;
import generic.test.category.NightlyCategory;
@Category(NightlyCategory.class) // this may actually be an @PortSensitive test
public class InVmModelForFridaX64RegistersTest extends AbstractModelForFridaX64RegistersTest {
@Override
public ModelHost modelHost() throws Throwable {
return new InVmFridaModelHost();
}
}

View File

@ -98,6 +98,17 @@
the <i>Data Type Manager</i> display is updated to reflect the new name in the tree.
</p>
</blockquote>
<H2>Show In Data Type Manager</H2>
<BLOCKQUOTE>
<P>Select the <IMG src="images/go-home.png" alt=""> icon in the toolbar to have the editor's
data type be highlighted in the Data Type Manager's tree.
</P>
</BLOCKQUOTE
<h2>Change the Sort Order</h2>
<blockquote>
<p>As with most tables in Ghidra, you can change the sort order of a column by

View File

@ -102,7 +102,17 @@
data items in the program will have changed due to the apply.</P>
</BLOCKQUOTE>
<H2>Closing the Editor</H2>
<H2><A name="Show_In_Data_Type_Manager"></A><A name="Structure_Editor_Show_In_Data_Type_Manager">Show In Data Type Manager</H2>
<BLOCKQUOTE>
<P>Select the <IMG src="images/go-home.png" alt=""> icon in the toolbar to have the editor's
data type be highlighted in the Data Type Manager's tree.
</P>
</BLOCKQUOTE>
<H2>Closing the Editor</H2>
<BLOCKQUOTE>
<P>Select the Close dockable component icon <IMG src="../../shared/close16.gif" alt=""> in

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -23,7 +23,7 @@ import ghidra.program.model.data.InvalidDataTypeException;
import resources.ResourceManager;
/**
* ApplyAction is an action for applying editor changes.
* ApplyAction is an action for applying editor changes.
*/
public class ApplyAction extends CompositeEditorTableAction {
@ -48,11 +48,8 @@ public class ApplyAction extends CompositeEditorTableAction {
try {
model.apply();
}
catch (EmptyCompositeException e1) {
model.setStatus(e1.getMessage(), true);
}
catch (InvalidDataTypeException e1) {
model.setStatus(e1.getMessage(), true);
catch (EmptyCompositeException | InvalidDataTypeException e) {
model.setStatus(e.getMessage(), true);
}
requestTableFocus();
}

View File

@ -45,7 +45,7 @@ abstract public class CompositeEditorTableAction extends DockingAction implement
public static final String EDIT_ACTION_PREFIX = "Editor: ";
public CompositeEditorTableAction(CompositeEditorProvider provider, String name, String group,
String[] popupPath, String[] menuPath, ImageIcon icon) {
String[] popupPath, String[] menuPath, Icon icon) {
super(name, provider.plugin.getName(), KeyBindingType.SHARED);
this.provider = provider;
model = provider.getModel();

View File

@ -15,53 +15,55 @@
*/
package ghidra.app.plugin.core.compositeeditor;
import ghidra.program.model.data.*;
import docking.ComponentProvider;
import ghidra.program.model.data.*;
/**
* Interface implemented by data type editors.
*
*
*/
public interface EditorProvider {
/**
* Get the name of this editor.
* @return the name of this editor
*/
public String getName();
/**
* Get the pathname of the data type being edited.
* @return the pathname of the data type being edited
*/
public DataTypePath getDtPath();
/**
* Get the component provider for this editor.
* @return the component provider for this editor
*/
public ComponentProvider getComponentProvider();
/**
* Get the datatype manager associated with this editor.
* @return the datatype manager associated with this editor
*/
public DataTypeManager getDataTypeManager();
/**
* Notification that the data type manager domain object (program or data type archive) was restored.
* Notification that the data type manager domain object (program or data type archive) was
* restored.
* @param domainObject the program or data type archive that was restored.
*/
public void domainObjectRestored(DataTypeManagerDomainObject domainObject);
/**
* Return whether this editor is editing the data type with the given
* path.
* Return whether this editor is editing the data type with the given path.
* @param dtPath path of a data type
* @return true if the data type for the pathname is being edited
*/
public boolean isEditing(DataTypePath dtPath);
/**
* Add an editor listener that will be notified when the edit window is
* closed.
* Add an editor listener that will be notified when the edit window is closed.
* @param listener the listener
*/
public void addEditorListener(EditorListener listener);
@ -72,6 +74,7 @@ public interface EditorProvider {
/**
* Returns whether changes need to be saved.
* @return whether changes need to be saved
*/
public boolean needsSave();

View File

@ -15,16 +15,13 @@
*/
package ghidra.app.plugin.core.compositeeditor;
import javax.swing.SwingUtilities;
import docking.ActionContext;
import docking.action.MenuData;
import ghidra.app.plugin.core.navigation.FindAppliedDataTypesService;
import ghidra.app.util.HelpTopics;
import ghidra.program.model.data.Composite;
import ghidra.program.model.data.DataTypeComponent;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.*;
/**
* An action to show references to the field in the currently selected editor row
@ -56,8 +53,7 @@ public class FindReferencesToField extends CompositeEditorTableAction {
String fieldName = getFieldName();
Composite composite = model.getOriginalComposite();
SwingUtilities.invokeLater(
() -> service.findAndDisplayAppliedDataTypeAddresses(composite, fieldName));
Swing.runLater(() -> service.findAndDisplayAppliedDataTypeAddresses(composite, fieldName));
}
private String getFieldName() {

View File

@ -0,0 +1,60 @@
/* ###
* 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.app.plugin.core.compositeeditor;
import javax.swing.Icon;
import docking.ActionContext;
import docking.action.ToolBarData;
import ghidra.app.services.DataTypeManagerService;
import ghidra.program.model.data.*;
import resources.ResourceManager;
/**
* Shows the editor's data type in the UI using the {@link DataTypeManagerService}.
*/
public class ShowDataTypeInTreeAction extends CompositeEditorTableAction {
// This action should go after the row-based actions, which have this group:
// 3_COMPONENT_EDITOR_ACTION
private static final String TOOLBAR_GROUP = "4_COMPONENT_EDITOR_ACTION";
private static final Icon ICON = ResourceManager.loadImage("images/go-home.png");
public ShowDataTypeInTreeAction(CompositeEditorProvider provider) {
super(provider, "Show In Data Type Manager", TOOLBAR_GROUP, null /*popupPath*/,
null /*menuPath*/, ICON);
setToolBarData(
new ToolBarData(ResourceManager.loadImage("images/go-home.png"), TOOLBAR_GROUP));
}
@Override
public void actionPerformed(ActionContext context) {
DataTypeManagerService dtmService = tool.getService(DataTypeManagerService.class);
DataTypeManager dtm = provider.getDataTypeManager();
DataTypePath path = provider.getDtPath();
DataType dt = dtm.getDataType(path);
dtmService.setDataTypeSelected(dt);
}
@Override
public void adjustEnablement() {
DataTypeManager dtm = provider.getDataTypeManager();
DataTypePath path = provider.getDtPath();
DataType dt = dtm.getDataType(path);
setEnabled(dt != null);
}
}

View File

@ -55,24 +55,26 @@ public class StructureEditorProvider extends CompositeEditorProvider {
return new CompositeEditorTableAction[] {
new ApplyAction(this),
// new ToggleLockAction(this),
new InsertUndefinedAction(this),
new MoveUpAction(this),
new InsertUndefinedAction(this),
new MoveUpAction(this),
new MoveDownAction(this),
new ClearAction(this),
new DuplicateAction(this),
new ClearAction(this),
new DuplicateAction(this),
new DuplicateMultipleAction(this),
new DeleteAction(this),
new PointerAction(this),
new DeleteAction(this),
new PointerAction(this),
new ArrayAction(this),
new FindReferencesToField(this),
new UnpackageAction(this),
new EditComponentAction(this),
new EditFieldAction(this),
new EditComponentAction(this),
new EditFieldAction(this),
new HexNumbersAction(this),
new CreateInternalStructureAction(this),
new ShowComponentPathAction(this),
new AddBitFieldAction(this),
new EditBitFieldAction(this),
new ShowDataTypeInTreeAction(this),
// new ViewBitFieldAction(this)
};
//@formatter:on

View File

@ -51,21 +51,22 @@ public class UnionEditorProvider extends CompositeEditorProvider {
@Override
protected CompositeEditorTableAction[] createActions() {
//@formatter:off
return new CompositeEditorTableAction[] {
new ApplyAction(this),
return new CompositeEditorTableAction[] {
new ApplyAction(this),
new MoveUpAction(this),
new MoveDownAction(this),
new DuplicateAction(this),
new MoveDownAction(this),
new DuplicateAction(this),
new DuplicateMultipleAction(this),
new DeleteAction(this),
new PointerAction(this),
new DeleteAction(this),
new PointerAction(this),
new ArrayAction(this),
new ShowComponentPathAction(this),
new ShowComponentPathAction(this),
new EditComponentAction(this),
new EditFieldAction(this),
new EditFieldAction(this),
new HexNumbersAction(this),
new AddBitFieldAction(this),
new EditBitFieldAction(this)
new EditBitFieldAction(this),
new ShowDataTypeInTreeAction(this)
};
//@formatter:on
}

View File

@ -38,7 +38,9 @@ import generic.util.Path;
import ghidra.app.CorePluginPackage;
import ghidra.app.plugin.PluginCategoryNames;
import ghidra.app.plugin.ProgramPlugin;
import ghidra.app.plugin.core.datamgr.actions.*;
import ghidra.app.plugin.core.datamgr.actions.RecentlyOpenedArchiveAction;
import ghidra.app.plugin.core.datamgr.actions.UpdateSourceArchiveNamesAction;
import ghidra.app.plugin.core.datamgr.actions.associate.*;
import ghidra.app.plugin.core.datamgr.archive.*;
import ghidra.app.plugin.core.datamgr.editor.DataTypeEditorManager;
import ghidra.app.plugin.core.datamgr.tree.ArchiveNode;
@ -60,8 +62,7 @@ import ghidra.program.model.address.AddressSetView;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.DataTypeArchive;
import ghidra.program.model.listing.Program;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.*;
import ghidra.util.datastruct.LRUMap;
import ghidra.util.task.TaskLauncher;
@ -570,6 +571,10 @@ public class DataTypeManagerPlugin extends ProgramPlugin
return dataTypeManagerHandler.openArchive(archiveName);
}
public List<Archive> getAllArchives() {
return dataTypeManagerHandler.getAllArchives();
}
public void openProjectDataTypeArchive() {
if (openDialog == null) {
ActionListener listener = ev -> {
@ -613,7 +618,9 @@ public class DataTypeManagerPlugin extends ProgramPlugin
@Override
public void setDataTypeSelected(DataType dataType) {
if (provider.isVisible()) {
provider.setDataTypeSelected(dataType);
// this is a service method, ensure it is on the Swing thread, since it interacts with
// Swing components
Swing.runIfSwingOrRunLater(() -> provider.setDataTypeSelected(dataType));
}
}
@ -725,7 +732,7 @@ public class DataTypeManagerPlugin extends ProgramPlugin
return null;
}
DataTypesActionContext dtContext = (DataTypesActionContext) context;
GTreeNode selectedNode = dtContext.getSelectedNode();
GTreeNode selectedNode = dtContext.getClickedNode();
if (!(selectedNode instanceof ArchiveNode)) {
return null;
}

View File

@ -48,9 +48,9 @@ public class DataTypeSynchronizer {
private int localTransactionID;
/**
* Creates a DataTypeSynchronizer to be used for synchronizing data types between a program
* Creates a DataTypeSynchronizer to be used for synchronizing data types between a program
* and an archive.
* @param dataTypeManagerHandler the handler that manages all the open data type managers
* @param dataTypeManagerHandler the handler that manages all the open data type managers
* whether built-in, program, project data type archive or file data type archive.
* @param dataTypeManager the program data type manager.
* @param source the data type source archive information indicating the associated archive for
@ -142,35 +142,38 @@ public class DataTypeSynchronizer {
}
/**
* Commits a single program data type's changes to the associated source data type in the archive.
* @param refDT the program data type
* @return true if the commit succeeds.
* Commits a single program data type's changes to the associated source data type in the
* archive.
* @param dtmHandler the handler that manages data types
* @param dt the program data type
* @return true if the commit succeeds
*/
public static boolean commit(DataTypeManagerHandler dtmHandler, DataType refDT) {
SourceArchive sourceArchive = refDT.getSourceArchive();
public static boolean commit(DataTypeManagerHandler dtmHandler, DataType dt) {
SourceArchive sourceArchive = dt.getSourceArchive();
DataTypeManager sourceDTM = dtmHandler.getDataTypeManager(sourceArchive);
if (sourceDTM == null) {
return false;
}
commit(sourceDTM, refDT);
commit(sourceDTM, dt);
return true;
}
/**
* Updates a single data type in the program to match the associated source data type from the
* archive.
* @param dataType the program data type
* @return true if the update succeeds.
* @param dtmHandler the handler that manages data types
* @param dt the data type
* @return true if the update succeeds
*/
public static boolean update(DataTypeManagerHandler dtmHandler, DataType refDT) {
DataTypeManager dataTypeManager = refDT.getDataTypeManager();
SourceArchive sourceArchive = refDT.getSourceArchive();
DataTypeManager sourceDTM = dtmHandler.getDataTypeManager(sourceArchive);
if (dataTypeManager == null || sourceDTM == null) {
public static boolean update(DataTypeManagerHandler dtmHandler, DataType dt) {
DataTypeManager dataTypeManager = dt.getDataTypeManager();
SourceArchive sourceArchive = dt.getSourceArchive();
DataTypeManager sourceDtm = dtmHandler.getDataTypeManager(sourceArchive);
if (dataTypeManager == null || sourceDtm == null) {
return false;
}
DataType sourceDT = sourceDTM.getDataType(sourceArchive, refDT.getUniversalID());
update(dataTypeManager, sourceDT);
DataType sourceDt = sourceDtm.getDataType(sourceArchive, dt.getUniversalID());
update(dataTypeManager, sourceDt);
return true;
}
@ -216,7 +219,7 @@ public class DataTypeSynchronizer {
}
/**
* If the indicated data type is associated with a source archive, this will remove the
* If the indicated data type is associated with a source archive, this will remove the
* association.
* @param dataType the data type to be disassociated from a source archive.
*/
@ -343,7 +346,7 @@ public class DataTypeSynchronizer {
String htmlContent = diffs[0].getHTMLContentString();
String otherContent = diffs[1].getHTMLContentString();
// this string allows us to force both tables to be the same width, which is
// this string allows us to force both tables to be the same width, which is
// aesthetically pleasing
String spacerString = createHTMLSpacerString(htmlContent, otherContent);
StringBuilder buffy = new StringBuilder();
@ -355,8 +358,9 @@ public class DataTypeSynchronizer {
buffy.append("<TR BORDER=LEFT>");
buffy.append("<TD VALIGN=\"TOP\">");
buffy.append("<B>").append(HTMLUtilities.escapeHTML(dataTypeManager.getName())).append(
"</B><HR NOSHADE>");
buffy.append("<B>")
.append(HTMLUtilities.escapeHTML(dataTypeManager.getName()))
.append("</B><HR NOSHADE>");
buffy.append(htmlContent);
// horizontal spacer below the inner table in order to force a minimum width
@ -368,8 +372,9 @@ public class DataTypeSynchronizer {
buffy.append("</TD>");
buffy.append("<TD VALIGN=\"TOP\">");
buffy.append("<B>").append(HTMLUtilities.escapeHTML(sourceArchive.getName())).append(
"</B><HR NOSHADE>");
buffy.append("<B>")
.append(HTMLUtilities.escapeHTML(sourceArchive.getName()))
.append("</B><HR NOSHADE>");
buffy.append(otherContent);
@ -391,12 +396,12 @@ public class DataTypeSynchronizer {
return ToolTipUtils.getHTMLRepresentation(sourceDT);
}
/**
/**
* Compares the two HTML strings to find the widest *rendered* text and then creates
* an HTML string of spaces that is wide enough to represent that width.
*/
private static String createHTMLSpacerString(String htmlContent, String otherHTMLContent) {
// unfortunately, to get the displayed widths, we have to have rendered content, which
// unfortunately, to get the displayed widths, we have to have rendered content, which
// is what the JLabels below are doing for us
JLabel label1 = new GDHtmlLabel("<HTML>" + htmlContent);
JLabel label2 = new GDHtmlLabel("<HTML>" + otherHTMLContent);
@ -446,9 +451,9 @@ public class DataTypeSynchronizer {
}
/**
* Adjusts the data type and source archive info for an associated source archive if its sync
* state is incorrect. It makes sure that a data type that is the same as the associated
* archive one is in-sync. It also makes sure that a data type that differs from the archive
* Adjusts the data type and source archive info for an associated source archive if its sync
* state is incorrect. It makes sure that a data type that is the same as the associated
* archive one is in-sync. It also makes sure that a data type that differs from the archive
* one can be committed or updated.
*/
public void reSyncDataTypes() {
@ -458,8 +463,8 @@ public class DataTypeSynchronizer {
return;
}
int transactionID = dataTypeManager.startTransaction(
"re-sync '" + sourceArchive.getName() + "' data types");
int transactionID = dataTypeManager
.startTransaction("re-sync '" + sourceArchive.getName() + "' data types");
try {
reSyncOutOfSyncInTimeOnlyDataTypes();
fixSyncForDifferingDataTypes();
@ -498,7 +503,7 @@ public class DataTypeSynchronizer {
/**
* This method is to correct a problem where a data type ends up differing from its associated
* data type in the archive, but its timestamp information indicates that it is in sync.
* It changes the timestamp info on the data type and the info about the source archive so
* It changes the timestamp info on the data type and the info about the source archive so
* the user will be able to commit/update the data type to correctly put it back in sync.
*/
private void fixSyncForDifferingDataTypes() {

View File

@ -20,13 +20,15 @@ import java.util.List;
import javax.swing.tree.TreePath;
import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode;
import ghidra.app.context.ProgramActionContext;
import ghidra.app.plugin.core.datamgr.archive.BuiltInSourceArchive;
import ghidra.app.plugin.core.datamgr.archive.ProjectArchive;
import ghidra.app.plugin.core.datamgr.tree.DataTypeArchiveGTree;
import ghidra.app.plugin.core.datamgr.tree.ProjectArchiveNode;
import ghidra.app.plugin.core.datamgr.tree.*;
import ghidra.framework.main.datatable.DomainFileContext;
import ghidra.framework.model.DomainFile;
import ghidra.program.model.data.*;
import ghidra.program.model.listing.Program;
public class DataTypesActionContext extends ProgramActionContext implements DomainFileContext {
@ -53,7 +55,7 @@ public class DataTypesActionContext extends ProgramActionContext implements Doma
return isToolbarAction;
}
public GTreeNode getSelectedNode() {
public GTreeNode getClickedNode() {
return clickedNode;
}
@ -85,4 +87,49 @@ public class DataTypesActionContext extends ProgramActionContext implements Doma
return true;
}
public List<GTreeNode> getSelectedNodes() {
Object contextObject = getContextObject();
GTree gTree = (GTree) contextObject;
return gTree.getSelectedNodes();
}
public List<DataTypeNode> getDisassociatableNodes() {
Object contextObject = getContextObject();
GTree gTree = (GTree) contextObject;
TreePath[] selectionPaths = gTree.getSelectionPaths();
return getDisassociatableNodes(selectionPaths);
}
private List<DataTypeNode> getDisassociatableNodes(TreePath[] paths) {
List<DataTypeNode> nodes = new ArrayList<>();
for (TreePath treePath : paths) {
DataTypeNode node = getDisassociatableNode(treePath);
if (node != null) {
nodes.add(node);
}
}
return nodes;
}
private DataTypeNode getDisassociatableNode(TreePath path) {
GTreeNode node = (GTreeNode) path.getLastPathComponent();
if (!(node instanceof DataTypeNode)) {
return null;
}
DataTypeNode dataTypeNode = (DataTypeNode) node;
DataType dataType = dataTypeNode.getDataType();
DataTypeManager dataTypeManager = dataType.getDataTypeManager();
SourceArchive sourceArchive = dataType.getSourceArchive();
if (sourceArchive == null || dataTypeManager == null ||
sourceArchive.equals(BuiltInSourceArchive.INSTANCE) ||
sourceArchive.getSourceArchiveID().equals(dataTypeManager.getUniversalID())) {
return null;
}
return dataTypeNode;
}
}

View File

@ -38,6 +38,7 @@ import docking.widgets.textpane.GHtmlTextPane;
import docking.widgets.tree.*;
import docking.widgets.tree.support.GTreeSelectionEvent.EventOrigin;
import ghidra.app.plugin.core.datamgr.actions.*;
import ghidra.app.plugin.core.datamgr.actions.associate.*;
import ghidra.app.plugin.core.datamgr.archive.*;
import ghidra.app.plugin.core.datamgr.tree.*;
import ghidra.app.plugin.core.datamgr.util.DataTypeUtils;
@ -220,6 +221,7 @@ public class DataTypesProvider extends ComponentProviderAdapter {
// key binding only
addLocalAction(new ClearCutAction(plugin)); // Common
addLocalAction(new AssociateDataTypeAction(plugin));
addLocalAction(new CommitSingleDataTypeAction(plugin));
addLocalAction(new UpdateSingleDataTypeAction(plugin));
addLocalAction(new RevertDataTypeAction(plugin));

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.

View File

@ -0,0 +1,322 @@
/* ###
* 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.app.plugin.core.datamgr.actions.associate;
import java.awt.BorderLayout;
import java.awt.Component;
import java.util.List;
import java.util.stream.Collectors;
import javax.swing.*;
import org.apache.commons.lang3.StringUtils;
import docking.*;
import docking.action.DockingAction;
import docking.action.MenuData;
import docking.widgets.OptionDialog;
import docking.widgets.combobox.GhidraComboBox;
import docking.widgets.label.GLabel;
import docking.widgets.tree.GTreeNode;
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
import ghidra.app.plugin.core.datamgr.DataTypesActionContext;
import ghidra.app.plugin.core.datamgr.archive.*;
import ghidra.app.plugin.core.datamgr.tree.ArchiveNode;
import ghidra.app.plugin.core.datamgr.tree.DataTypeNode;
import ghidra.app.plugin.core.datamgr.util.DataTypeTreeCopyMoveTask;
import ghidra.app.plugin.core.datamgr.util.DataTypeTreeCopyMoveTask.ActionType;
import ghidra.program.model.data.*;
import ghidra.util.Msg;
import ghidra.util.layout.PairLayout;
import ghidra.util.task.TaskLauncher;
/**
* Allows the user to associate the selected action with a source archive. An associate data type
* allows users to push changes to the source archive and to pull updates from the source archive.
*/
public class AssociateDataTypeAction extends DockingAction {
private DataTypeManagerPlugin plugin;
public AssociateDataTypeAction(DataTypeManagerPlugin plugin) {
super("Associate With Archive", plugin.getName());
this.plugin = plugin;
setPopupMenuData(new MenuData(new String[] { "Associate With Archive" }, null, "Sync"));
}
@Override
public boolean isEnabledForContext(ActionContext context) {
if (!(context instanceof DataTypesActionContext)) {
return false;
}
return hasOnlyDtNodes(((DataTypesActionContext) context).getSelectedNodes());
}
private boolean hasOnlyDtNodes(List<GTreeNode> nodes) {
if (nodes.isEmpty()) {
return false;
}
for (GTreeNode node : nodes) {
if (!(node instanceof DataTypeNode)) {
return false;
}
}
return true;
}
private boolean isAlreadyAssociated(DataTypesActionContext dtContext) {
List<DataTypeNode> nodes = dtContext.getDisassociatableNodes();
return !nodes.isEmpty();
}
private boolean hasSingleModifiableSourceArchive(List<GTreeNode> nodes) {
Archive sourceArchive = null;
for (GTreeNode node : nodes) {
Archive archive = findArchive(node);
if (sourceArchive == null) {
sourceArchive = archive;
continue;
}
if (sourceArchive != archive) {
return false;
}
}
if (sourceArchive != null && sourceArchive.isModifiable()) {
return true;
}
return false;
}
private static Archive findArchive(GTreeNode node) {
while (node != null) {
if (node instanceof ArchiveNode) {
return ((ArchiveNode) node).getArchive();
}
node = node.getParent();
}
return null;
}
private List<Archive> getDestinationArchives() {
List<Archive> archives = plugin.getAllArchives();
List<Archive> sourceArchives = archives.stream()
.filter(a -> !(a instanceof ProgramArchive))
.filter(a -> !(a instanceof BuiltInArchive))
.sorted((a1, a2) -> a1.getName().compareToIgnoreCase(a2.getName()))
.collect(Collectors.toList());
return sourceArchives;
}
@Override
public void actionPerformed(ActionContext context) {
List<GTreeNode> nodes = ((DataTypesActionContext) context).getSelectedNodes();
if (!hasSingleModifiableSourceArchive(nodes)) {
Msg.showInfo(this, getProviderComponent(), "Multiple Source Archives",
"The currently selected nodes are from multiple archives.\n" +
"Please select only nodes from a single archvie.");
return;
}
if (isAlreadyAssociated((DataTypesActionContext) context)) {
Msg.showInfo(this, getProviderComponent(), "Already Associated",
"One or more of the currently selected nodes are already associated\n" +
"with a source archive.");
return;
}
List<Archive> archives = getDestinationArchives();
if (archives.isEmpty()) {
Msg.showInfo(this, getProviderComponent(), "No Source Archives Open",
"No source archives open. Please open the desired source archive.");
return;
}
ChooseArchiveDialog dialog = new ChooseArchiveDialog(archives);
dialog.show();
if (dialog.isCancelled()) {
return;
}
Archive destinationArchive = dialog.getArchive();
Category destinationCategory = dialog.getCategory();
DataTypeTreeCopyMoveTask task =
new DataTypeTreeCopyMoveTask(destinationArchive, destinationCategory, nodes,
ActionType.COPY, plugin.getProvider().getGTree(), plugin.getConflictHandler());
task.setPromptToAssociateTypes(false); // do not prompt the user; they have already decided
TaskLauncher.launch(task);
}
private JComponent getProviderComponent() {
return plugin.getProvider().getComponent();
}
private class ChooseArchiveDialog extends DialogComponentProvider {
private Category category;
private Archive archive;
// default to true to handle the case the user presses Escape or presses the x button
private boolean isCancelled = true;
private GhidraComboBox<Archive> archivesBox = new GhidraComboBox<>();
private JTextField categoryField = new JTextField(20);
ChooseArchiveDialog(List<Archive> archives) {
super("Choose New Source Archive", true);
addWorkPanel(buildWorkPanel());
archivesBox.addToModel(archives);
categoryField.setText("/");
addOKButton();
addCancelButton();
}
private JComponent buildWorkPanel() {
archivesBox.setRenderer(new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(JList<?> list, Object value,
int index, boolean isSelected, boolean cellHasFocus) {
JLabel renderer = (JLabel) super.getListCellRendererComponent(list, value,
index, isSelected, cellHasFocus);
Archive a = (Archive) value;
renderer.setText(a.getName());
return renderer;
}
});
JPanel panel = new JPanel(new BorderLayout());
JPanel archivePanel = new JPanel(new PairLayout());
archivePanel.add(new GLabel("New Source Archive: "));
archivePanel.add(archivesBox);
JPanel categoryPanel = new JPanel(new PairLayout());
categoryPanel.add(new GLabel("Destination Category: "));
categoryPanel.add(categoryField);
panel.add(archivePanel, BorderLayout.NORTH);
panel.add(categoryPanel, BorderLayout.SOUTH);
return panel;
}
@Override
protected void okCallback() {
clearStatusText();
archive = (Archive) archivesBox.getSelectedItem();
if (archive == null) {
setStatusText("Please choose an archive");
return;
}
if (!archive.isModifiable()) {
setStatusText(
"Archive is not modifiable. You must first open this archive for edit.");
return;
}
if (!updateCategory()) {
return;
}
isCancelled = false;
close();
}
private boolean updateCategory() {
String categoryText = categoryField.getText();
if (StringUtils.isBlank(categoryText)) {
setStatusText("Category must be specified. Use '/' for the root.");
return false;
}
DataTypeManager dtm = archive.getDataTypeManager();
CategoryPath categoryPath = new CategoryPath(categoryText);
category = dtm.getCategory(categoryPath);
if (category != null) {
return true;
}
int choice = OptionDialog.showYesNoDialog(null, "Create Category?",
"Category '" + categoryText + "' does not exist. Create it now?");
if (choice != OptionDialog.YES_OPTION) {
setStatusText("Category does not exist");
return false;
}
boolean noErrors = false;
int tx = dtm.startTransaction("Create Category");
try {
category = dtm.createCategory(categoryPath);
noErrors = true;
}
finally {
dtm.endTransaction(tx, noErrors);
}
if (category == null) {
setStatusText("Unable to create category");
return false;
}
return true;
}
@Override
protected void cancelCallback() {
super.cancelCallback();
}
boolean isCancelled() {
return isCancelled;
}
void show() {
JComponent parent = getProviderComponent();
DockingWindowManager.showDialog(parent, this);
}
Archive getArchive() {
return archive;
}
Category getCategory() {
return category;
}
}
}

View File

@ -13,8 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.datamgr.actions;
package ghidra.app.plugin.core.datamgr.actions.associate;
import java.util.List;
import docking.action.MenuData;
import ghidra.app.plugin.core.datamgr.*;
import ghidra.app.plugin.core.datamgr.archive.DataTypeManagerHandler;
import ghidra.app.plugin.core.datamgr.tree.ArchiveNode;
@ -22,17 +25,13 @@ import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.data.SourceArchive;
import ghidra.util.HelpLocation;
import java.util.List;
import docking.action.MenuData;
public class CommitAction extends SyncAction {
public static final String MENU_NAME = "Commit Datatypes To";
public static final String MENU_NAME = "Commit Data Types To";
public CommitAction(DataTypeManagerPlugin plugin,
DataTypeManagerHandler dataTypeManagerHandler, DataTypeManager dtm,
ArchiveNode archiveNode, SourceArchive sourceArchive, boolean isEnabled) {
public CommitAction(DataTypeManagerPlugin plugin, DataTypeManagerHandler dataTypeManagerHandler,
DataTypeManager dtm, ArchiveNode archiveNode, SourceArchive sourceArchive,
boolean isEnabled) {
super("Commit Changes To Archive", plugin, dataTypeManagerHandler, dtm, archiveNode,
sourceArchive, isEnabled);

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.datamgr.actions;
package ghidra.app.plugin.core.datamgr.actions.associate;
import javax.swing.ImageIcon;
import javax.swing.tree.TreePath;

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.datamgr.actions;
package ghidra.app.plugin.core.datamgr.actions.associate;
import java.util.*;
@ -35,7 +35,7 @@ import ghidra.util.exception.CancelledException;
import ghidra.util.task.*;
public class DisassociateAction extends DockingAction {
public static final String MENU_NAME = "Disassociate Datatypes From";
public static final String MENU_NAME = "Disassociate Data Types From";
private final SourceArchive sourceArchive;
private final DataTypeManager dtm;
@ -96,12 +96,12 @@ public class DisassociateAction extends DockingAction {
}
//@formatter:off
MonitoredRunnable r =
MonitoredRunnable r =
monitor -> doDisassociate(synchronizer, typesToDisassociate, allAssociatedTypes, monitor);
new TaskBuilder("Disassociate From Archive", r)
.setStatusTextAlignment(SwingConstants.LEADING)
.launchModal()
;
;
//@formatter:on
}
@ -110,10 +110,10 @@ public class DisassociateAction extends DockingAction {
TaskMonitor monitor) {
//
// Note: we collapse the node before performing this work because there is a
// Note: we collapse the node before performing this work because there is a
// potential for a large number of events to be generated. Further, if the
// given archive node has many children (like 10s of thousands), then the
// copious events generated herein could lock the UI. By closing the node,
// copious events generated herein could lock the UI. By closing the node,
// the tree is not invalidating/validating its cache as a result of these
// events.
//

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.datamgr.actions;
package ghidra.app.plugin.core.datamgr.actions.associate;
import java.util.*;
import java.util.Map.Entry;
@ -28,7 +28,8 @@ import docking.action.MenuData;
import docking.widgets.OptionDialog;
import docking.widgets.tree.*;
import ghidra.app.plugin.core.datamgr.*;
import ghidra.app.plugin.core.datamgr.archive.*;
import ghidra.app.plugin.core.datamgr.archive.BuiltInSourceArchive;
import ghidra.app.plugin.core.datamgr.archive.DataTypeManagerHandler;
import ghidra.app.plugin.core.datamgr.tree.DataTypeArchiveGTree;
import ghidra.app.plugin.core.datamgr.tree.DataTypeNode;
import ghidra.app.plugin.core.datamgr.util.DataTypeUtils;
@ -56,10 +57,8 @@ public class DisassociateDataTypeAction extends DockingAction {
return false;
}
Object contextObject = context.getContextObject();
GTree gTree = (GTree) contextObject;
TreePath[] selectionPaths = gTree.getSelectionPaths();
List<DataTypeNode> nodes = getDisassociatableNodes(selectionPaths);
DataTypesActionContext dtContext = (DataTypesActionContext) context;
List<DataTypeNode> nodes = dtContext.getDisassociatableNodes();
return !nodes.isEmpty();
}
@ -127,11 +126,11 @@ public class DisassociateDataTypeAction extends DockingAction {
}
//@formatter:off
MonitoredRunnable r =
MonitoredRunnable r =
monitor -> doDisassociate(nodes, monitor);
new TaskBuilder("Disassociate From Archive", r)
.setStatusTextAlignment(SwingConstants.LEADING)
.launchModal();
.launchModal();
//@formatter:on
}
@ -159,10 +158,10 @@ public class DisassociateDataTypeAction extends DockingAction {
nodes.stream().map(node -> node.getDataType()).collect(Collectors.toList());
//
// Note: we collapse the node before performing this work because there is a
// Note: we collapse the node before performing this work because there is a
// potential for a large number of events to be generated. Further, if the
// given archive node has many children (like 10s of thousands), then the
// copious events generated herein could lock the UI. By closing the node,
// copious events generated herein could lock the UI. By closing the node,
// the tree is not invalidating/validating its cache as a result of these
// events.
//
@ -189,7 +188,7 @@ public class DisassociateDataTypeAction extends DockingAction {
monitor.initialize(dataTypes.size());
//@formatter:off
Map<DataTypeManager, List<DataType>> managersToTypes =
Map<DataTypeManager, List<DataType>> managersToTypes =
dataTypes.stream()
.collect(
Collectors.groupingBy(dt -> dt.getDataTypeManager()))
@ -209,7 +208,7 @@ public class DisassociateDataTypeAction extends DockingAction {
// we must process these by their source
//@formatter:off
Map<SourceArchive, List<DataType>> sourceToTypes =
Map<SourceArchive, List<DataType>> sourceToTypes =
dataTypes.stream()
.collect(
Collectors.groupingBy(dt -> dt.getSourceArchive()))

View File

@ -13,8 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.datamgr.actions;
package ghidra.app.plugin.core.datamgr.actions.associate;
import java.util.List;
import docking.action.MenuData;
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
import ghidra.app.plugin.core.datamgr.DataTypeSyncInfo;
import ghidra.app.plugin.core.datamgr.archive.DataTypeManagerHandler;
@ -23,19 +26,15 @@ import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.data.SourceArchive;
import ghidra.util.HelpLocation;
import java.util.List;
import docking.action.MenuData;
public class RevertAction extends SyncAction {
public static final String MENU_NAME = "Revert Datatypes From";
public static final String MENU_NAME = "Revert Data Types From";
public RevertAction(DataTypeManagerPlugin plugin,
DataTypeManagerHandler dataTypeManagerHandler, DataTypeManager dtm,
ArchiveNode archiveNode, SourceArchive sourceArchive, boolean isEnabled) {
public RevertAction(DataTypeManagerPlugin plugin, DataTypeManagerHandler dataTypeManagerHandler,
DataTypeManager dtm, ArchiveNode archiveNode, SourceArchive sourceArchive,
boolean isEnabled) {
super("Revert Datatype Changes", plugin, dataTypeManagerHandler, dtm, archiveNode,
super("Revert Data Type Changes", plugin, dataTypeManagerHandler, dtm, archiveNode,
sourceArchive, isEnabled);
setPopupMenuData(new MenuData(new String[] { MENU_NAME, sourceArchive.getName() }));
setHelpLocation(new HelpLocation(plugin.getName(), getHelpTopic()));

View File

@ -13,14 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.datamgr.actions;
import ghidra.app.plugin.core.datamgr.*;
import ghidra.app.plugin.core.datamgr.archive.DataTypeManagerHandler;
import ghidra.app.plugin.core.datamgr.tree.DataTypeNode;
import ghidra.app.plugin.core.datamgr.util.DataTypeUtils;
import ghidra.program.model.data.*;
import ghidra.util.Msg;
package ghidra.app.plugin.core.datamgr.actions.associate;
import javax.swing.tree.TreePath;
@ -29,6 +22,12 @@ import docking.action.DockingAction;
import docking.action.MenuData;
import docking.widgets.tree.GTree;
import docking.widgets.tree.GTreeNode;
import ghidra.app.plugin.core.datamgr.*;
import ghidra.app.plugin.core.datamgr.archive.DataTypeManagerHandler;
import ghidra.app.plugin.core.datamgr.tree.DataTypeNode;
import ghidra.app.plugin.core.datamgr.util.DataTypeUtils;
import ghidra.program.model.data.*;
import ghidra.util.Msg;
public class RevertDataTypeAction extends DockingAction {
@ -37,7 +36,7 @@ public class RevertDataTypeAction extends DockingAction {
public RevertDataTypeAction(DataTypeManagerPlugin plugin) {
super("Revert Data Type", plugin.getName());
this.plugin = plugin;
setPopupMenuData(new MenuData(new String[] { "Revert" }, "Sync"));
setPopupMenuData(new MenuData(new String[] { "Revert Changes" }, "Sync"));
setEnabled(true);
}
@ -68,8 +67,8 @@ public class RevertDataTypeAction extends DockingAction {
case UNKNOWN:
return false;
case COMMIT:
return true;
case CONFLICT:
return true;
case IN_SYNC:
case ORPHAN:
case UPDATE:
@ -88,29 +87,26 @@ public class RevertDataTypeAction extends DockingAction {
}
GTreeNode node = (GTreeNode) selectionPaths[0].getLastPathComponent();
if (node instanceof DataTypeNode) {
DataTypeNode dataTypeNode = (DataTypeNode) node;
DataType dataType = dataTypeNode.getDataType();
DataTypeManager dtm = dataType.getDataTypeManager();
DataTypeManagerHandler handler = plugin.getDataTypeManagerHandler();
SourceArchive sourceArchive = dataType.getSourceArchive();
if (!dtm.isUpdatable()) {
DataTypeUtils.showUnmodifiableArchiveErrorMessage(gTree, "Revert Failed", dtm);
return;
}
DataTypeManager sourceDTM = handler.getDataTypeManager(sourceArchive);
if (sourceDTM == null) {
Msg.showInfo(getClass(), gTree, "Revert Failed", "Source Archive not open: " +
sourceArchive.getName());
return;
}
plugin.revert(dataType);
// Source archive data type manager was already checked for null above.
DataTypeSynchronizer synchronizer =
new DataTypeSynchronizer(handler, dtm, sourceArchive);
synchronizer.reSyncOutOfSyncInTimeOnlyDataTypes();
DataTypeNode dataTypeNode = (DataTypeNode) node;
DataType dataType = dataTypeNode.getDataType();
DataTypeManager dtm = dataType.getDataTypeManager();
DataTypeManagerHandler handler = plugin.getDataTypeManagerHandler();
SourceArchive sourceArchive = dataType.getSourceArchive();
if (!dtm.isUpdatable()) {
DataTypeUtils.showUnmodifiableArchiveErrorMessage(gTree, "Revert Failed", dtm);
return;
}
DataTypeManager sourceDTM = handler.getDataTypeManager(sourceArchive);
if (sourceDTM == null) {
Msg.showInfo(getClass(), gTree, "Revert Failed",
"Source Archive not open: " + sourceArchive.getName());
return;
}
plugin.revert(dataType);
// Source archive data type manager was already checked for null above.
DataTypeSynchronizer synchronizer = new DataTypeSynchronizer(handler, dtm, sourceArchive);
synchronizer.reSyncOutOfSyncInTimeOnlyDataTypes();
}
}

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.datamgr.actions;
package ghidra.app.plugin.core.datamgr.actions.associate;
import java.util.*;
@ -83,7 +83,6 @@ public abstract class SyncAction extends DockingAction implements Comparable<Syn
@Override
public void actionPerformed(ActionContext context) {
DataTypeSynchronizer synchronizer = new DataTypeSynchronizer(handler, dtm, sourceArchive);
if (!dtm.isUpdatable()) {
showRequiresArchiveOpenMessage(dtm.getName());
@ -103,6 +102,8 @@ public abstract class SyncAction extends DockingAction implements Comparable<Syn
return;
}
DataTypeSynchronizer synchronizer = new DataTypeSynchronizer(handler, dtm, sourceArchive);
//@formatter:off
TaskBuilder.withTask(new SyncTask(synchronizer))
.setStatusTextAlignment(SwingConstants.LEADING)
@ -114,10 +115,10 @@ public abstract class SyncAction extends DockingAction implements Comparable<Syn
private void doSync(DataTypeSynchronizer synchronizer, TaskMonitor monitor) {
//
// Note: we collapse the node before performing this work because there is a
// Note: we collapse the node before performing this work because there is a
// potential for a large number of events to be generated. Further, if the
// given archive node has many children (like 10s of thousands), then the
// copious events generated herein could lock the UI. By closing the node,
// copious events generated herein could lock the UI. By closing the node,
// the tree is not invalidating/validating its cache as a result of these
// events.
//
@ -248,7 +249,7 @@ public abstract class SyncAction extends DockingAction implements Comparable<Syn
Set<DataTypeSyncInfo> outOfSyncInfos) {
String status = getStatusMessage(outOfSyncInfos);
Msg.showInfo(getClass(), plugin.getTool().getToolFrame(), "No Data Type Changes",
"No datatypes found to " + getOperationName() + " for archive \"" + archiveName +
"No data types found to " + getOperationName() + " for archive \"" + archiveName +
"\".\n\n" + status);
}
@ -279,7 +280,7 @@ public abstract class SyncAction extends DockingAction implements Comparable<Syn
case UNKNOWN:
}
}
StringBuffer buf = new StringBuffer();
StringBuilder buf = new StringBuilder();
if (updateCount > 0) {
buf.append("\nNumber of UPDATES remaining: " + updateCount);
}
@ -298,7 +299,7 @@ public abstract class SyncAction extends DockingAction implements Comparable<Syn
private void showNoDataTypesToSyncMessage() {
Msg.showInfo(getClass(), plugin.getTool().getToolFrame(), "No Data Type Changes",
"No out of sync datatypes found. Updating sync time.");
"No out of sync data types found. Updating sync time.");
}
@Override
@ -340,7 +341,7 @@ public abstract class SyncAction extends DockingAction implements Comparable<Syn
private void autoUpdateDataTypesThatHaveNoRealChanges(DataTypeSynchronizer synchronizer,
List<DataTypeSyncInfo> outOfSynchInTimeOnlyList, boolean markArchiveSynchronized) {
int transactionID = dtm.startTransaction("auto sync datatypes");
int transactionID = dtm.startTransaction("Auto-sync data types");
try {
for (DataTypeSyncInfo dataTypeSyncInfo : outOfSynchInTimeOnlyList) {
dataTypeSyncInfo.syncTimes();

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.datamgr.actions;
package ghidra.app.plugin.core.datamgr.actions.associate;
import docking.ActionContext;
import docking.action.DockingAction;

View File

@ -13,8 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.datamgr.actions;
package ghidra.app.plugin.core.datamgr.actions.associate;
import java.util.List;
import docking.action.MenuData;
import ghidra.app.plugin.core.datamgr.*;
import ghidra.app.plugin.core.datamgr.archive.DataTypeManagerHandler;
import ghidra.app.plugin.core.datamgr.tree.ArchiveNode;
@ -22,18 +25,14 @@ import ghidra.program.model.data.DataTypeManager;
import ghidra.program.model.data.SourceArchive;
import ghidra.util.HelpLocation;
import java.util.List;
import docking.action.MenuData;
public class UpdateAction extends SyncAction {
public static final String MENU_NAME = "Update Datatypes From";
public static final String MENU_NAME = "Update Data Types From";
public UpdateAction(DataTypeManagerPlugin plugin,
DataTypeManagerHandler dataTypeManagerHandler, DataTypeManager dtm,
ArchiveNode archiveNode, SourceArchive sourceArchive, boolean isEnabled) {
public UpdateAction(DataTypeManagerPlugin plugin, DataTypeManagerHandler dataTypeManagerHandler,
DataTypeManager dtm, ArchiveNode archiveNode, SourceArchive sourceArchive,
boolean isEnabled) {
super("Update Datatypes From Archive", plugin, dataTypeManagerHandler, dtm, archiveNode,
super("Update Data Types From Archive", plugin, dataTypeManagerHandler, dtm, archiveNode,
sourceArchive, isEnabled);
setPopupMenuData(new MenuData(new String[] { MENU_NAME, sourceArchive.getName() }));

View File

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin.core.datamgr.actions;
package ghidra.app.plugin.core.datamgr.actions.associate;
import javax.swing.ImageIcon;
import javax.swing.tree.TreePath;

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.

View File

@ -34,6 +34,7 @@ import docking.widgets.OptionDialog;
import ghidra.app.plugin.core.compositeeditor.EditorListener;
import ghidra.app.plugin.core.compositeeditor.EditorProvider;
import ghidra.app.plugin.core.datamgr.DataTypeManagerPlugin;
import ghidra.app.services.DataTypeManagerService;
import ghidra.framework.plugintool.ComponentProviderAdapter;
import ghidra.program.model.data.*;
import ghidra.program.model.data.Enum;
@ -81,7 +82,7 @@ public class EnumEditorProvider extends ComponentProviderAdapter
private long originalEnumID = -1;
/**
* Construct a new enum editor provider.
* Construct a new enum editor provider.
* @param plugin owner of this provider
* @param enumDT enum data type
*/
@ -251,7 +252,7 @@ public class EnumEditorProvider extends ComponentProviderAdapter
//==================================================================================================
// Private Methods
//==================================================================================================
//==================================================================================================
private void updateTitle(DataType dataType) {
setTitle(getName() + " - " + getProviderSubTitle(dataType));
@ -285,19 +286,28 @@ public class EnumEditorProvider extends ComponentProviderAdapter
deleteAction =
new EnumPluginAction("Delete Enum Value", e -> editorPanel.deleteSelectedEntries());
deleteAction.setEnabled(false);
deleteAction.setPopupMenuData(
new MenuData(new String[] { "Delete" }, DELETE_ICON, editGroup));
deleteAction
.setPopupMenuData(new MenuData(new String[] { "Delete" }, DELETE_ICON, editGroup));
deleteAction.setToolBarData(new ToolBarData(DELETE_ICON, editGroup));
deleteAction.setDescription("Delete the selected enum entries");
applyAction = new EnumPluginAction("Apply Enum Changes", e -> applyChanges());
applyAction.setEnabled(false);
applyAction.setToolBarData(new ToolBarData(APPLY_ICON, "ApplyChanges"));
String firstGroup = "ApplyChanges";
applyAction.setToolBarData(new ToolBarData(APPLY_ICON, firstGroup));
applyAction.setDescription("Apply changes to Enum");
EnumPluginAction showEnumAction =
new EnumPluginAction("Show In Data Type Manager", e -> showDataEnumInTree());
showEnumAction.setEnabled(true);
String thirdGroup = "FThirdGroup";
showEnumAction.setToolBarData(
new ToolBarData(ResourceManager.loadImage("images/go-home.png"), thirdGroup));
tool.addLocalAction(this, applyAction);
tool.addLocalAction(this, addAction);
tool.addLocalAction(this, deleteAction);
tool.addLocalAction(this, showEnumAction);
}
private boolean applyChanges() {
@ -338,6 +348,11 @@ public class EnumEditorProvider extends ComponentProviderAdapter
return true;
}
private void showDataEnumInTree() {
DataTypeManagerService dtmService = tool.getService(DataTypeManagerService.class);
dtmService.setDataTypeSelected(originalEnum);
}
/**
* Checks to see if the new changes to the enum will affect equates based off of it.
* @param editedEnum the enum to check for conflicts with
@ -467,9 +482,9 @@ public class EnumEditorProvider extends ComponentProviderAdapter
/**
* Prompts the user if the editor has unsaved changes. Saves the changes if
* the user indicates to do so.
* @return CANCEL (0) if the user canceled;
* SAVE (1) if the user saved changes;
* NO_SAVE (2) if the user did not save changes or no save was required;
* @return CANCEL (0) if the user canceled;
* SAVE (1) if the user saved changes;
* NO_SAVE (2) if the user did not save changes or no save was required;
* ERROR (3) if there was an error when the changes were applied.
*/
private int saveChangesForCloseEvent(boolean allowCancel) {
@ -502,7 +517,7 @@ public class EnumEditorProvider extends ComponentProviderAdapter
//==================================================================================================
// Inner Classes
//==================================================================================================
//==================================================================================================
private class MyDataTypeManagerChangeListener extends DataTypeManagerChangeListenerAdapter {
@ -547,7 +562,7 @@ public class EnumEditorProvider extends ComponentProviderAdapter
@Override
public void categoryRemoved(DataTypeManager dtm, CategoryPath path) {
// should never get this callback, as we should first have gotten a
// should never get this callback, as we should first have gotten a
// dataTypeRemoved(), which will dispose this editor
}
@ -665,11 +680,11 @@ public class EnumEditorProvider extends ComponentProviderAdapter
DataType dataType = dtm.getDataType(otherPath);
if (dataType == null) {
//
// Unusual Code Alert!:
//
// Unusual Code Alert!:
// Must have been deleted and we have not yet processed the event...return true
// here to signal that the types are the same so that clients will continue the
// updating process. The types may not really be the same, but the fallout is
// updating process. The types may not really be the same, but the fallout is
// only that there will be more updating than is necessary.
//
return true;

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.

View File

@ -27,12 +27,13 @@ import ghidra.app.plugin.core.datamgr.archive.ProgramArchive;
import ghidra.app.plugin.core.datamgr.tree.*;
import ghidra.program.model.data.*;
import ghidra.util.*;
import ghidra.util.exception.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor;
/**
* Task for handling drop operations.
* Task for copying and moving data type nodes within the Data Types tree.
*/
public class DataTypeTreeCopyMoveTask extends Task {
@ -48,11 +49,11 @@ public class DataTypeTreeCopyMoveTask extends Task {
}
private DataTypeArchiveGTree gTree;
private CategoryNode destinationNode;
private List<GTreeNode> droppedNodes;
private Category destinationCategory;
private List<GTreeNode> copyMoveNodes;
private Archive sourceArchive;
private Archive destinationArchive;
private boolean promptToAssociateTypes = true;
private ActionType actionType;
private DataTypeConflictHandler conflictHandler;
private List<String> errors = new ArrayList<>();
@ -65,19 +66,26 @@ public class DataTypeTreeCopyMoveTask extends Task {
public DataTypeTreeCopyMoveTask(CategoryNode destinationNode, List<GTreeNode> droppedNodeList,
ActionType actionType, DataTypeArchiveGTree gTree,
DataTypeConflictHandler conflictHandler) {
this(findArchive(destinationNode), destinationNode.getCategory(), droppedNodeList,
actionType, gTree, conflictHandler);
}
public DataTypeTreeCopyMoveTask(Archive destinationArchive, Category destinationCategory,
List<GTreeNode> droppedNodeList, ActionType actionType, DataTypeArchiveGTree gTree,
DataTypeConflictHandler conflictHandler) {
super("Drag/Drop", true, true, true);
this.destinationNode = destinationNode;
this.droppedNodes = droppedNodeList;
this.destinationCategory = destinationCategory;
this.copyMoveNodes = droppedNodeList;
this.actionType = actionType;
this.gTree = gTree;
this.conflictHandler = conflictHandler;
this.destinationArchive = findArchive(destinationNode);
this.destinationArchive = destinationArchive;
GTreeNode firstNode = droppedNodes.get(0);
GTreeNode firstNode = copyMoveNodes.get(0);
this.sourceArchive = findArchive(firstNode);
}
private Archive findArchive(GTreeNode node) {
private static Archive findArchive(GTreeNode node) {
while (node != null) {
if (node instanceof ArchiveNode) {
return ((ArchiveNode) node).getArchive();
@ -87,10 +95,22 @@ public class DataTypeTreeCopyMoveTask extends Task {
return null;
}
/**
* Any types being newly copied/moved to a suitable archive are eligible for 'association',
* which means changes between the two archives will be tracked. True, the default, signals to
* prompt before associating types; false signals not to prompt the user, but to always
* associate types.
*
* @param prompt true to prompt; false to not prompt
*/
public void setPromptToAssociateTypes(boolean prompt) {
this.promptToAssociateTypes = prompt;
}
@Override
public void run(TaskMonitor monitor) throws CancelledException {
int nodeCount = droppedNodes.size();
int nodeCount = copyMoveNodes.size();
filterRedundantNodes();
if (checkForDifferentSourceArchives()) {
@ -143,10 +163,10 @@ public class DataTypeTreeCopyMoveTask extends Task {
private boolean checkForDifferentSourceArchives() {
for (GTreeNode node : droppedNodes) {
for (GTreeNode node : copyMoveNodes) {
if (sourceArchive != findArchive(node)) {
Msg.showError(this, gTree, "Copy Failed",
"All dragged data types must be from the same archive!");
"All data types must be from the same archive!");
return true;
}
}
@ -158,7 +178,7 @@ public class DataTypeTreeCopyMoveTask extends Task {
DataTypeManager dtm = destinationArchive.getDataTypeManager();
int txId = dtm.startTransaction("Copy/Move Category/DataType");
try {
dragNodesToCategory(monitor);
copyOrMoveNodesToCategory(monitor);
}
finally {
dtm.endTransaction(txId, true);
@ -187,13 +207,13 @@ public class DataTypeTreeCopyMoveTask extends Task {
return;
}
monitor.initialize(droppedNodes.size());
monitor.initialize(copyMoveNodes.size());
SourceArchive destination = destinationArchive.getDataTypeManager().getLocalSourceArchive();
DataTypeManager dtm = sourceArchive.getDataTypeManager();
int txId = dtm.startTransaction("Associate DataTypes");
int txId = dtm.startTransaction("Associate Data Types");
try {
for (GTreeNode node : droppedNodes) {
for (GTreeNode node : copyMoveNodes) {
monitor.checkCanceled();
if (node instanceof DataTypeNode) {
@ -215,6 +235,10 @@ public class DataTypeTreeCopyMoveTask extends Task {
private boolean promptToAssociateTypes(TaskMonitor monitor) throws CancelledException {
if (!promptToAssociateTypes) {
return true; // do not prompt; always associate
}
if (!containsUnassociatedTypes(monitor)) {
return false; // nothing to associate
}
@ -230,8 +254,8 @@ public class DataTypeTreeCopyMoveTask extends Task {
private boolean containsUnassociatedTypes(TaskMonitor monitor) throws CancelledException {
monitor.setMessage("Checking for types to associate");
monitor.initialize(droppedNodes.size());
for (GTreeNode node : droppedNodes) {
monitor.initialize(copyMoveNodes.size());
for (GTreeNode node : copyMoveNodes) {
monitor.checkCanceled();
if (node instanceof DataTypeNode) {
@ -296,13 +320,13 @@ public class DataTypeTreeCopyMoveTask extends Task {
}
}
private void dragNodesToCategory(TaskMonitor monitor) {
private void copyOrMoveNodesToCategory(TaskMonitor monitor) {
monitor.setMessage("Drag/Drop Categories/Data Types");
monitor.initialize(droppedNodes.size());
monitor.initialize(copyMoveNodes.size());
Category toCategory = getCategory(destinationNode);
for (GTreeNode node : droppedNodes) {
Category toCategory = destinationCategory;
for (GTreeNode node : copyMoveNodes) {
if (monitor.isCancelled()) {
break;
}
@ -360,10 +384,10 @@ public class DataTypeTreeCopyMoveTask extends Task {
}
}
private void renameAsCopy(Category destinationCategory, DataType dataType) {
private void renameAsCopy(Category toCategory, DataType dataType) {
String dtName = dataType.getName();
String baseName = getBaseName(dtName);
String copyName = getNextCopyName(destinationCategory, baseName);
String copyName = getNextCopyName(toCategory, baseName);
try {
dataType.setName(copyName);
}
@ -386,12 +410,12 @@ public class DataTypeTreeCopyMoveTask extends Task {
return baseName;
}
String getNextCopyName(Category destinationCategory, String baseName) {
String getNextCopyName(Category toCategory, String baseName) {
String format = "Copy_%d_of_" + baseName;
for (int i = 1; i < 100; i++) {
String copyName = String.format(format, i);
if (destinationCategory.getDataType(copyName) == null) {
if (toCategory.getDataType(copyName) == null) {
return copyName;
}
}
@ -400,14 +424,14 @@ public class DataTypeTreeCopyMoveTask extends Task {
return String.format(format, System.currentTimeMillis());
}
private void moveNode(Category destinationCategory, GTreeNode node, TaskMonitor monitor) {
private void moveNode(Category toCategory, GTreeNode node, TaskMonitor monitor) {
if (node instanceof DataTypeNode) {
DataType dataType = ((DataTypeNode) node).getDataType();
moveDataType(destinationCategory, dataType);
moveDataType(toCategory, dataType);
}
else if (node instanceof CategoryNode) {
Category category = ((CategoryNode) node).getCategory();
moveCategory(destinationCategory, category, monitor);
moveCategory(toCategory, category, monitor);
}
}
@ -457,17 +481,6 @@ public class DataTypeTreeCopyMoveTask extends Task {
toCategory.copyCategory(category, conflictHandler, monitor);
}
private Category getCategory(GTreeNode node) {
if (node instanceof ArchiveNode) {
return ((ArchiveNode) node).getArchive().getDataTypeManager().getRootCategory();
}
if (node instanceof CategoryNode) {
return ((CategoryNode) node).getCategory();
}
throw new AssertException(
"Expected node to be either an ArchiveNode or CategoryNode but was " + node.getClass());
}
/**
* Returns true if the given data type's source archive is the same as it's current data
* type manager. This is false if copying a new type from the program to an
@ -483,14 +496,14 @@ public class DataTypeTreeCopyMoveTask extends Task {
}
private int askToAssociateDataTypes() {
return OptionDialog.showYesNoCancelDialog(gTree, "Associate DataTypes?",
"Do you want to associate local datatypes with the target archive?");
return OptionDialog.showYesNoCancelDialog(gTree, "Associate Data Types?",
"Do you want to associate local data types with the target archive?");
}
// filters out nodes with categories in their path
private void filterRedundantNodes() {
Set<GTreeNode> nodeSet = new HashSet<>(droppedNodes);
Set<GTreeNode> nodeSet = new HashSet<>(copyMoveNodes);
List<GTreeNode> filteredList = new ArrayList<>();
for (GTreeNode node : nodeSet) {
@ -499,7 +512,7 @@ public class DataTypeTreeCopyMoveTask extends Task {
}
}
droppedNodes = filteredList;
copyMoveNodes = filteredList;
}
private boolean containsAncestor(Set<GTreeNode> nodeSet, GTreeNode node) {

View File

@ -22,7 +22,6 @@ import java.util.List;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import ghidra.app.services.DataTypeManagerService;
import ghidra.app.services.DataTypeQueryService;
import ghidra.program.model.data.*;
import ghidra.program.model.data.Enum;
@ -150,6 +149,7 @@ public class DataTypeUtils {
/**
* Returns the root folder icon.
* @param expanded true to use the expanded icon; false to use the collapsed icon.
* @return the root folder icon.
*/
public static Icon getRootIcon(boolean expanded) {
@ -159,7 +159,7 @@ public class DataTypeUtils {
/**
* Returns the open folder icon.
*
*
* @param disabled True returns a disabled icon; false returns the normal icon.
* @return the open folder icon.
*/
@ -174,7 +174,7 @@ public class DataTypeUtils {
/**
* Returns the closed folder icon.
*
*
* @param disabled True returns a disabled icon; false returns the normal icon.
* @return the closed folder icon.
*/
@ -189,7 +189,7 @@ public class DataTypeUtils {
/**
* Returns the open archive folder icon.
*
*
* @param isLocked True means to return the checked-out open archive folder icon
* @return the open archive folder icon.
*/
@ -204,7 +204,7 @@ public class DataTypeUtils {
/**
* Returns the closed folder icon.
*
*
* @param isLocked True means to return the checked-out closed folder icon
* @return the closed folder icon.
*/
@ -217,39 +217,9 @@ public class DataTypeUtils {
return closedArchiveFolderIcon;
}
// /**
// * Returns the open archive folder icon.
// *
// * @param isLocked True means to return the checked-out open archive folder icon
// * @return the open archive folder icon.
// */
// public static Icon getOpenProjectArchiveFolder( boolean isLocked ) {
// loadImages();
// if ( isLocked ) {
// return lockedOpenProjectArchiveFolderIcon;
// }
//
// return openProjectArchiveFolderIcon;
// }
//
// /**
// * Returns the closed folder icon.
// *
// * @param isLocked True means to return the checked-out closed folder icon
// * @return the closed folder icon.
// */
// public static Icon getClosedProjectArchiveFolder( boolean isLocked ) {
// loadImages();
// if ( isLocked ) {
// return lockedClosedProjectArchiveFolderIcon;
// }
//
// return closedProjectArchiveFolderIcon;
// }
//
/**
* Returns the BuiltIn icon.
*
*
* @param disabled True returns a disabled icon; false returns the normal icon.
* @return the BuiltIn icon.
*/
@ -264,7 +234,7 @@ public class DataTypeUtils {
/**
* Returns the favorites icon.
*
*
* @param disabled True returns a disabled icon; false returns the normal icon.
* @return the favorites icon.
*/
@ -279,7 +249,7 @@ public class DataTypeUtils {
/**
* Finds the icon associated with the provided data type.
*
*
* @param dataType The data type for which to find an icon.
* @param disabled True returns a disabled icon; false returns the normal icon.
* @return the icon associated with the provided data type.
@ -302,7 +272,7 @@ public class DataTypeUtils {
/**
* Returns an icon that adds highlighting to the provided icon.
*
*
* @param baseIcon The icon to highlight.
* @return the highlighted icon.
*/
@ -320,12 +290,12 @@ public class DataTypeUtils {
}
/**
* Returns a sorted list of {@link DataType}s that have names which start with the given
* search string. The list is sorted according to {@link #DATA_TYPE_LOOKUP_COMPARATOR}.
*
@param searchString The name of the DataTypes to match.
* Returns a sorted list of {@link DataType}s that have names which start with the given search
* string. The list is sorted according to {@link #DATA_TYPE_LOOKUP_COMPARATOR}.
*
* @param searchString The name of the DataTypes to match.
* @param dataService The service from which the data types will be taken.
* @return A sorted list of {@link DataType}s that have names which start with the given search
* @return A sorted list of {@link DataType}s that have names which start with the given search
* string.
*/
public static List<DataType> getStartsWithMatchingDataTypes(String searchString,
@ -335,13 +305,13 @@ public class DataTypeUtils {
}
/**
* Returns a sorted list of {@link DataType}s that have names which match the given search
* string. The list is sorted according to {@link #DATA_TYPE_LOOKUP_COMPARATOR}.
*
* Returns a sorted list of {@link DataType}s that have names which match the given search
* string. The list is sorted according to {@link #DATA_TYPE_LOOKUP_COMPARATOR}.
*
* @param searchString The name of the DataTypes to match.
* @param dataService The service from which the data types will be taken.
* @return A sorted list of {@link DataType}s that have names which match the given search
* string.
* @return A sorted list of {@link DataType}s that have names which match the given search
* string.
*/
public static List<DataType> getExactMatchingDataTypes(String searchString,
DataTypeQueryService dataService) {
@ -350,10 +320,13 @@ public class DataTypeUtils {
}
/**
* Changes the give text to prepare it or use in searching for data types. Clients should
* call this method to make sure that the given text is suitable for use when searching
* the data type values returned by {@link #getExactMatchingDataTypes(String, DataTypeManagerService)}
* and {@link #getStartsWithMatchingDataTypes(String, DataTypeManagerService)}.
* Changes the given text to prepare it for use in searching for data types. Clients should
* call this method to make sure that the given text is suitable for use when searching the
* data type values returned by
* {@link #getExactMatchingDataTypes(String, DataTypeQueryService)} and
* {@link #getStartsWithMatchingDataTypes(String, DataTypeQueryService)}.
* @param searchText the search text
* @return the updated text
*/
public static String prepareSearchText(String searchText) {
return searchText.replaceAll(" ", "");
@ -376,12 +349,14 @@ public class DataTypeUtils {
/**
* Get the base data type for the specified data type.
* <br>For example, the base data type for Word*[5] is Word.
* For a pointer, the base data type is the type being pointed to
* or the pointer itself if it is pointing at nothing.
* <br>If "INT" is a typedef on a "dword" then INT[7][3] would have a base data type of dword.
* If you wanted to get the INT from INT[7][3]
* you should call getNamedBasedDataType(DataType) instead.
*
* <p>For example, the base data type for Word*[5] is Word. For a pointer, the base data type
* is the type being pointed to or the pointer itself if it is pointing at nothing.
*
* <p>If "INT" is a typedef on a "dword" then INT[7][3] would have a base data type of dword.
* If you wanted to get the INT from INT[7][3] you should call getNamedBasedDataType(DataType)
* instead.
*
* @param dt the data type whose base data type is to be determined.
* @return the base data type.
*/
@ -409,15 +384,17 @@ public class DataTypeUtils {
}
/**
* Get the named base data type for the specified data type.
* This method intentionally does not drill down into typedefs.
* <br>For example, the named base data type for Word*[5] is Word.
* For a pointer, the named base data type is the type being pointed to
* or the pointer itself if it is pointing at nothing.
* <br>If "INT" is a typedef on a "dword", then INT[7][3] would
* have a named base data type of INT.
* If you wanted to get the dword from INT[7][3]
* you should call getBasedDataType(DataType) instead.
* Get the named base data type for the specified data type. This method intentionally does
* not drill down into typedefs.
*
* <p>For example, the named base data type for Word*[5] is Word. For a pointer, the named
* base data type is the type being pointed to or the pointer itself if it is pointing at
* nothing.
*
* <p>If "INT" is a typedef on a "dword", then INT[7][3] would have a named base data type of
* INT. If you wanted to get the dword from INT[7][3] you should call
* getBasedDataType(DataType) instead.
*
* @param dt the data type whose named base data type is to be determined.
* @return the base data type.
*/
@ -444,10 +421,10 @@ public class DataTypeUtils {
* Create a copy of the chain of data types that eventually lead to a named
* data type.
* <p>
* Returns a {@link DataType#copy(DataTypeManager) copy()} of the first named data
* type found in the pointer / array type chain, and returns an identical chain of
* pointer / arrays up to the copied named type.
* <p>
* Returns a {@link DataType#copy(DataTypeManager) copy()} of the first named data type found
* in the pointer / array type chain, and returns an identical chain of pointer / arrays up to
* the copied named type.
*
* @param dataType data type to be copied
* @param dtm data type manager
* @return deep copy of dataType
@ -475,39 +452,16 @@ public class DataTypeUtils {
msg = "The Program is not modifiable!\n";
}
else if (dtm instanceof FileArchiveBasedDataTypeManager) {
msg =
"The archive file is not modifiable!\nYou must open the archive for editing\n before performing this operation.";
msg = "The archive file is not modifiable!\nYou must open the archive for editing\n" +
"before performing this operation.\n" + dtm.getName();
}
else {
msg =
"The project archive is not modifiable!\nYou must check out the archive\n before performing this operation.";
msg = "The project archive is not modifiable!\nYou must check out the archive\n" +
"before performing this operation.\n" + dtm.getName();
}
Msg.showInfo(DataTypeUtils.class, parent, title, msg);
}
// For testing:
// public static void main( String[] args ) {
// JFrame frame = new JFrame();
// JPanel panel = new JPanel();
//
// JLabel label1 = new GDLabel();
// Icon icon = getOpenFolderIcon( false );
// label1.setIcon( icon );
//
// JLabel label2 = new GDLabel();
// Icon icon2 = ResourceManager.getDisabledIcon( (ImageIcon) icon );
// label2.setIcon( icon2 );
//
// panel.add( label1 );
// panel.add( label2 );
//
// frame.getContentPane().add( panel );
//
// frame.pack();
// frame.setVisible( true );
// frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
// }
}
//==================================================================================================

View File

@ -15,15 +15,14 @@
*/
package ghidra.app.plugin.core.compositeeditor;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import org.junit.Assert;
import org.junit.Test;
import ghidra.program.model.data.*;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.task.TaskMonitorAdapter;
import ghidra.util.task.TaskMonitor;
public class StructureEditorLockedEnablementTest extends AbstractStructureEditorTest {
@ -34,7 +33,7 @@ public class StructureEditorLockedEnablementTest extends AbstractStructureEditor
try {
DataTypeManager dataTypeManager = cat.getDataTypeManager();
if (dt.getDataTypeManager() != dataTypeManager) {
dt = (Structure) dt.clone(dataTypeManager);
dt = dt.clone(dataTypeManager);
}
CategoryPath categoryPath = cat.getCategoryPath();
if (!dt.getCategoryPath().equals(categoryPath)) {
@ -69,7 +68,7 @@ public class StructureEditorLockedEnablementTest extends AbstractStructureEditor
Structure desiredEmptyStructure = emptyStructure;
int txID = program.startTransaction("Removing emptyStruct from DTM.");
try {
programDTM.remove(emptyStructure, TaskMonitorAdapter.DUMMY_MONITOR);
programDTM.remove(emptyStructure, TaskMonitor.DUMMY);
if (emptyStructure.getDataTypeManager() != catDTM) {
desiredEmptyStructure = (Structure) emptyStructure.copy(catDTM);
desiredEmptyStructure.setCategoryPath(pgmTestCat.getCategoryPath());
@ -154,7 +153,8 @@ public class StructureEditorLockedEnablementTest extends AbstractStructureEditor
for (CompositeEditorTableAction action : actions) {
if ((action instanceof EditFieldAction) || (action instanceof AddBitFieldAction) ||
(action instanceof InsertUndefinedAction) || (action instanceof PointerAction) ||
(action instanceof HexNumbersAction)) {
(action instanceof HexNumbersAction) ||
(action instanceof ShowDataTypeInTreeAction)) {
checkEnablement(action, true);
}
else if (action instanceof FavoritesAction) {
@ -203,7 +203,8 @@ public class StructureEditorLockedEnablementTest extends AbstractStructureEditor
(action instanceof DuplicateMultipleAction) || (action instanceof ClearAction) ||
(action instanceof DeleteAction) || (action instanceof ArrayAction) ||
(action instanceof PointerAction) || (action instanceof HexNumbersAction) ||
(action instanceof CreateInternalStructureAction)) {
(action instanceof CreateInternalStructureAction) ||
(action instanceof ShowDataTypeInTreeAction)) {
checkEnablement(action, true);
}
else {
@ -232,7 +233,8 @@ public class StructureEditorLockedEnablementTest extends AbstractStructureEditor
(action instanceof MoveDownAction) || (action instanceof ClearAction) ||
(action instanceof DeleteAction) || (action instanceof ArrayAction) ||
(action instanceof PointerAction) || (action instanceof HexNumbersAction) ||
(action instanceof CreateInternalStructureAction)) {
(action instanceof CreateInternalStructureAction) ||
(action instanceof ShowDataTypeInTreeAction)) {
checkEnablement(action, true);
}
else {
@ -261,7 +263,8 @@ public class StructureEditorLockedEnablementTest extends AbstractStructureEditor
(action instanceof DuplicateAction) ||
(action instanceof DuplicateMultipleAction) || (action instanceof ArrayAction) ||
(action instanceof PointerAction) || (action instanceof HexNumbersAction) ||
(action instanceof CreateInternalStructureAction)) {
(action instanceof CreateInternalStructureAction) ||
(action instanceof ShowDataTypeInTreeAction)) {
checkEnablement(action, true);
}
else {
@ -289,7 +292,8 @@ public class StructureEditorLockedEnablementTest extends AbstractStructureEditor
(action instanceof MoveDownAction) || (action instanceof ClearAction) ||
(action instanceof DeleteAction) || (action instanceof ArrayAction) ||
(action instanceof PointerAction) || (action instanceof HexNumbersAction) ||
(action instanceof CreateInternalStructureAction)) {
(action instanceof CreateInternalStructureAction) ||
(action instanceof ShowDataTypeInTreeAction)) {
checkEnablement(action, true);
}
else {
@ -313,7 +317,8 @@ public class StructureEditorLockedEnablementTest extends AbstractStructureEditor
checkEnablement(action, len <= numBytes);
}
else if ((action instanceof CycleGroupAction) || (action instanceof ClearAction) ||
(action instanceof DeleteAction) || (action instanceof HexNumbersAction)) {
(action instanceof DeleteAction) || (action instanceof HexNumbersAction) ||
(action instanceof ShowDataTypeInTreeAction)) {
checkEnablement(action, true);
}
else {

View File

@ -118,7 +118,8 @@ public class StructureEditorUnlockedEnablementTest extends AbstractStructureEdit
if ((action instanceof FavoritesAction) || (action instanceof CycleGroupAction) ||
(action instanceof EditFieldAction) || (action instanceof InsertUndefinedAction) ||
(action instanceof AddBitFieldAction) || (action instanceof PointerAction) ||
(action instanceof HexNumbersAction)) {
(action instanceof HexNumbersAction) ||
(action instanceof ShowDataTypeInTreeAction)) {
checkEnablement(action, true);
}
else {
@ -151,7 +152,8 @@ public class StructureEditorUnlockedEnablementTest extends AbstractStructureEdit
(action instanceof DuplicateMultipleAction) || (action instanceof DeleteAction) ||
(action instanceof ArrayAction) || (action instanceof PointerAction) ||
(action instanceof HexNumbersAction) ||
(action instanceof CreateInternalStructureAction)) {
(action instanceof CreateInternalStructureAction) ||
(action instanceof ShowDataTypeInTreeAction)) {
checkEnablement(action, true);
}
else if (action instanceof FavoritesAction) {
@ -190,7 +192,8 @@ public class StructureEditorUnlockedEnablementTest extends AbstractStructureEdit
(action instanceof MoveUpAction) || (action instanceof ClearAction) ||
(action instanceof DeleteAction) || (action instanceof ArrayAction) ||
(action instanceof PointerAction) || (action instanceof HexNumbersAction) ||
(action instanceof CreateInternalStructureAction)) {
(action instanceof CreateInternalStructureAction) ||
(action instanceof ShowDataTypeInTreeAction)) {
checkEnablement(action, true);
}
else if (action instanceof FavoritesAction) {
@ -230,7 +233,8 @@ public class StructureEditorUnlockedEnablementTest extends AbstractStructureEdit
(action instanceof DuplicateMultipleAction) || (action instanceof DeleteAction) ||
(action instanceof ArrayAction) || (action instanceof PointerAction) ||
(action instanceof HexNumbersAction) ||
(action instanceof CreateInternalStructureAction)) {
(action instanceof CreateInternalStructureAction) ||
(action instanceof ShowDataTypeInTreeAction)) {
checkEnablement(action, true);
}
else if (action instanceof FavoritesAction) {
@ -269,7 +273,8 @@ public class StructureEditorUnlockedEnablementTest extends AbstractStructureEdit
if ((action instanceof FavoritesAction) || (action instanceof CycleGroupAction) ||
(action instanceof EditFieldAction) || (action instanceof InsertUndefinedAction) ||
(action instanceof AddBitFieldAction) || (action instanceof PointerAction) ||
(action instanceof HexNumbersAction)) {
(action instanceof HexNumbersAction) ||
(action instanceof ShowDataTypeInTreeAction)) {
checkEnablement(action, true);
}
else {

View File

@ -110,9 +110,9 @@ public class DataTypeTestUtils {
public static ArchiveNode openArchive(String archiveName, boolean checkout,
DataTypeManagerPlugin plugin) throws Exception {
ArchiveNode openArchive = openArchive(archiveName, checkout, false, plugin);
ArchiveNode archiveNode = openArchive(archiveName, checkout, false, plugin);
waitForTree(plugin);
return openArchive;
return archiveNode;
}
private static void waitForTree(DataTypeManagerPlugin plugin) {

View File

@ -93,7 +93,7 @@ public class DeveloperDumpAllTypesScript extends GhidraScript {
}
String userChoice =
OptionDialog.showInputChoiceDialog(null, "Choose a Data Type Manager or Cancel",
"Choose", names, initialDtmChoice, OptionDialog.CANCEL_OPTION);
"Choose", names, initialDtmChoice, OptionDialog.PLAIN_MESSAGE);
if (userChoice == null) {
return null;
}

View File

@ -30,24 +30,24 @@ import docking.widgets.GComponent;
/**
* GhidraComboBox adds the following features:
*
*
* <p>
* 1) ActionListeners are only invoked when the &lt;Enter&gt; key is pressed within the text-field
* of the combo-box. In normal JComboBox case, the ActionListeners are notified when an item is
* selected from the list.
*
*
* <p>
* 2) Adds the auto-completion feature. As a user types in the field, the combo box suggest the
* nearest matching entry in the combo box model.
*
*
* <p>
* It also fixes the following bug:
*
*
* <p>
* A normal JComboBox has a problem (feature?) that if you have a dialog with a button and
* JComboBox and you edit the comboText field and then hit the button, the button sometimes does
* not work.
*
*
* <p>
* When the combobox loses focus, and its text has changed, it generates an actionPerformed event
* as though the user pressed &lt;Enter&gt; in the combo text field. This has a bizarre effect if
@ -55,12 +55,12 @@ import docking.widgets.GComponent;
* enablement state of the button that you pressed (which caused the text field to lose focus) in
* that you end up changing the button's internal state(by calling setEnabled(true or false)) in
* the middle of the button press.
*
*
* @param <E> the item type
*/
public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
private ArrayList<ActionListener> listeners = new ArrayList<>();
private ArrayList<DocumentListener> docListeners = new ArrayList<>();
private List<ActionListener> listeners = new ArrayList<>();
private List<DocumentListener> docListeners = new ArrayList<>();
private boolean setSelectedFlag = false;
private boolean forwardEnter;
@ -70,7 +70,6 @@ public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
* Default constructor.
*/
public GhidraComboBox() {
super();
init();
}
@ -164,7 +163,7 @@ public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
* {@link GhidraComboBox} will add an action listener to handle &lt;Enter&gt; actions.
* <p>
* To re-enable the default behavior, set the <code>forwardEnter</code> value to true.
*
*
* @param forwardEnter true to enable default &lt;Enter&gt; key handling.
*/
public void setEnterKeyForwarding(boolean forwardEnter) {
@ -198,7 +197,7 @@ public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
* editor used</b>. By default the editor for combo boxes is a text field. This method is
* a convenience for the user to set the number of columns on that text field, which updates
* the preferred size of the combo box.
*
*
* @param columnCount The number of columns for the text field editor
* @see JTextField#setColumns(int)
*/
@ -216,11 +215,11 @@ public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
* <li>The user deletes the text</li>
* <li>setSelectedItem(Object) method is called with the same item</li>
* </ol>
*
*
* In that above series of steps, the text will still be empty, as the user deleted it *and*
* the call to setSelectedItem(Object) had no effect because the base class assumed that the
* item is already selected.
*
*
* <p>
* This method exists to make sure, in that case, that the text of the field matches the
* selected item.
@ -256,6 +255,13 @@ public class GhidraComboBox<E> extends JComboBox<E> implements GComponent {
model.addElement(obj);
}
public void addToModel(Collection<E> items) {
DefaultComboBoxModel<E> model = (DefaultComboBoxModel<E>) getModel();
for (E e : items) {
model.addElement(e);
}
}
public boolean containsItem(E obj) {
DefaultComboBoxModel<E> model = (DefaultComboBoxModel<E>) getModel();
return model.getIndexOf(obj) != -1;

View File

@ -28,7 +28,6 @@ import docking.widgets.label.GHtmlLabel;
/**
* A dialog that has text fields to get user input.
*
*/
public class InputWithChoicesDialog extends DialogComponentProvider {
@ -37,19 +36,17 @@ public class InputWithChoicesDialog extends DialogComponentProvider {
private boolean allowEdits;
/**
* Creates a provider for a generic input dialog with the specified title,
* a label and a editable comboBox pre-populated with selectable values. The user
* can check the value of {@link #isCanceled()} to know whether or not
* the user canceled the operation. To get the user selected value use the
* {@link #getValue()} value(s) entered by the user. If the user cancelled the operation, then
* null will be returned from <code>getValue()</code>.
* <P>
* Creates a provider for a generic input dialog with the specified title, a label and a
* editable comboBox pre-populated with selectable values. The user can check the value of
* {@link #isCanceled()} to know whether or not the user canceled the operation. To get the
* user selected value use the {@link #getValue()} value(s) entered by the user. If the user
* cancelled the operation, then null will be returned from {@link #getValue()}.
*
* @param dialogTitle used as the name of the dialog's title bar
* @param label value to use for the label of the text field
* @param optionValues values to populate the combo box
* @param initialValue the initial value - can be null
* @param messageIcon the icon to display on the dialog--can be null
* @param initialValue the initial value; may be null
* @param messageIcon the icon to display on the dialog; may be null
*/
public InputWithChoicesDialog(String dialogTitle, String label, String[] optionValues,
String initialValue, Icon messageIcon) {
@ -67,20 +64,18 @@ public class InputWithChoicesDialog extends DialogComponentProvider {
}
/**
* Creates a provider for a generic input dialog with the specified title,
* a label and a editable comboBox pre-populated with selectable values. The user
* can check the value of {@link #isCanceled()} to know whether or not
* the user canceled the operation. To get the user selected value use the
* {@link #getValue()} value(s) entered by the user. If the user cancelled the operation, then
* null will be returned from <code>getValue()</code>.
* <P>
* Creates a provider for a generic input dialog with the specified title, a label and a
* editable comboBox pre-populated with selectable values. The user can check the value of
* {@link #isCanceled()} to know whether or not the user canceled the operation. To get the
* user selected value use the {@link #getValue()} value(s) entered by the user. If the user
* cancelled the operation, then null will be returned from {@link #getValue()}.
*
* @param dialogTitle used as the name of the dialog's title bar
* @param label value to use for the label of the text field
* @param optionValues values to populate the combo box
* @param initialValue the initial value - can be null
* @param allowEdits true allows the user to add custom entries to the combo box by entering text
* @param messageIcon the icon to display on the dialog--can be null
* @param initialValue the initial value; may be null
* @param allowEdits true allows the user to add custom entries by entering text
* @param messageIcon the icon to display on the dialog; may be null
*/
public InputWithChoicesDialog(String dialogTitle, String label, String[] optionValues,
String initialValue, boolean allowEdits, Icon messageIcon) {
@ -103,7 +98,7 @@ public class InputWithChoicesDialog extends DialogComponentProvider {
}
/**
* completes the construction of the gui for this dialog
* Completes the construction of the gui for this dialog
*/
private void buildMainPanel(String labelText, String[] optionValues, String initialValue,
Icon messageIcon) {
@ -168,13 +163,15 @@ public class InputWithChoicesDialog extends DialogComponentProvider {
/**
* Returns if this dialog is canceled.
* @return true if canceled
*/
public boolean isCanceled() {
return isCanceled;
}
/**
* return the value of the first combo box
* Return the value of the first combo box.
* @return the value
*/
public String getValue() {
if (isCanceled) {
@ -192,8 +189,7 @@ public class InputWithChoicesDialog extends DialogComponentProvider {
/**
* Set the current choice to value.
* @param value updated choice
* @throws NoSuchElementException if choice does not permit edits and value is
* not a valid choice.
* @throws NoSuchElementException if edits not permitted and value is not a valid choice
*/
public void setValue(String value) {
combo.setSelectedItem(value);

View File

@ -27,6 +27,7 @@ import java.util.*;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.swing.*;
import javax.swing.Timer;
@ -334,7 +335,7 @@ public class GTree extends JPanel implements BusyListener {
* <p>
* <b>Note: </b>See the usage note at the header of this class concerning how tree state
* is used relative to the <code>equals()</code> method.
*
*
* @param state the state to restore
*
* @see #getTreeState()
@ -582,7 +583,7 @@ public class GTree extends JPanel implements BusyListener {
* been replaced by a new node that is equal, but a different instance. One way this happens
* is if the tree is filtered and therefor the displayed nodes are clones of the model nodes.
* This can also happen if the tree nodes are rebuilt for some reason.
*
*
* @param node the node
* @return the corresponding model node in the tree. If the tree is filtered the viewed node
* will be a clone of the corresponding model node.
@ -596,7 +597,7 @@ public class GTree extends JPanel implements BusyListener {
* been replaced by a new node that is equal, but a different instance. One way this happens
* is if the tree is filtered and therefor the displayed nodes are clones of the model nodes.
* This can also happen if the tree nodes are rebuilt for some reason.
*
*
* @param path the path of the node
* @return the corresponding model node in the tree. If the tree is filtered the viewed node
* will be a clone of the corresponding model node.
@ -609,7 +610,7 @@ public class GTree extends JPanel implements BusyListener {
* Gets the view node for the given node. This is useful to translate to a tree path that is
* valid for the currently displayed tree. (Remember that if the tree is filtered, then the
* displayed nodes are clones of the model nodes.)
*
*
* @param node the node
* @return the current node in the displayed (possibly filtered) tree
*/
@ -621,7 +622,7 @@ public class GTree extends JPanel implements BusyListener {
* Gets the view node for the given path. This is useful to translate to a tree path that is
* valid for the currently displayed tree. (Remember that if the tree is filtered, then the
* displayed nodes are clones of the model nodes.)
*
*
* @param path the path of the node
* @return the current node in the displayed (possibly filtered) tree
*/
@ -760,7 +761,7 @@ public class GTree extends JPanel implements BusyListener {
/**
* Returns true if the given JTree is the actual JTree used by this GTree.
*
*
* @param jTree the tree to test
* @return true if the given JTree is the actual JTree used by this GTree.
*/
@ -773,7 +774,7 @@ public class GTree extends JPanel implements BusyListener {
* <P>
* NOTE: if this method is not called from the Swing thread, then the root node will be set
* later on the Swing thread. That is, this method will return before the work has been done.
*
*
* @param rootNode The node to set as the new root.
*/
public void setRootNode(GTreeNode rootNode) {
@ -916,6 +917,13 @@ public class GTree extends JPanel implements BusyListener {
return paths;
}
public List<GTreeNode> getSelectedNodes() {
TreePath[] paths = getSelectionPaths();
return Arrays.stream(paths)
.map(tp -> (GTreeNode) tp.getLastPathComponent())
.collect(Collectors.toList());
}
public boolean isExpanded(TreePath treePath) {
return tree.isExpanded(treePath);
}
@ -1023,20 +1031,20 @@ public class GTree extends JPanel implements BusyListener {
* becomes available to the model. This method will ensure that the named child passes any
* current filter in order for the child to appear in the tree. This effect is temporary and
* will be undone when next the filter changes.
*
*
* <p>This method is intended to be used by clients using an asynchronous node model, where
* new nodes will get created by application-level events. Such clients may wish to perform
* work when newly created nodes become available. This method simplifies the concurrent
* nature of the GTree, asynchronous nodes and the processing of asynchronous application-level
* events by providing a callback mechanism for clients. <b>This method is non-blocking.</b>
*
*
* <p>Note: this method assumes that the given parent node is in the view and not filtered
* out of the view. This method makes no attempt to ensure the given parent node passes any
* existing filter.
*
*
* <p>Note: this method will not wait forever for the given node to appear. It will eventually
* give up if the node never arrives.
*
*
* @param parent the model's parent node. If the view's parent node is passed, it will
* be translated to the model node.
* @param childName the name of the desired child
@ -1046,17 +1054,17 @@ public class GTree extends JPanel implements BusyListener {
Consumer<GTreeNode> consumer) {
/*
If the GTree were to use Java's CompletableStage API, then the code below
could be written thusly:
tree.getNewNode(modelParent, newName)
.thenCompose(newModelChild -> {
tree.ignoreFilter(newModelChild);
return tree.getNewNode(viewParent, newName);
))
.thenAccept(consumer);
*/
// ensure we operate on the model node which will always have the given child not the view
@ -1075,7 +1083,7 @@ public class GTree extends JPanel implements BusyListener {
* Requests that the node with the given name, in the given parent, be edited. This operation
* is asynchronous. This request will be buffered as needed to wait for the given node to be
* added to the parent, up to a timeout period.
*
*
* @param parent the parent node
* @param childName the name of the child to edit
*/
@ -1101,7 +1109,7 @@ public class GTree extends JPanel implements BusyListener {
/**
* Requests that the node be edited. This operation is asynchronous.
*
*
* @param node the node to edit
*/
public void startEditing(GTreeNode node) {
@ -1269,7 +1277,7 @@ public class GTree extends JPanel implements BusyListener {
/**
* Used to run tree tasks. This method is not meant for general clients of this tree, but
* rather for tasks to tell the tree to perform subtasks.
*
*
* @param task the task to run
*/
public void runTask(GTreeTask task) {

View File

@ -15,8 +15,9 @@
*/
package generic.test;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.StringContains.*;
import static org.junit.Assert.*;
import java.io.File;
import java.util.*;
@ -298,6 +299,15 @@ public abstract class AbstractGTest {
}
}
public static <T> void assertContainsString(String expected, String actual) {
assertThat(actual, containsString(expected));
}
public static <T> void assertContainsStringIgnoringCase(String expected, String actual) {
// newer hamcrest versions have containsStringIgnoringCase()
assertThat(actual.toLowerCase(), containsString(expected.toLowerCase()));
}
private static String printListFailureMessage(String message, List<?> expected,
List<?> actual) {

View File

@ -68,7 +68,7 @@ public interface DataTypeManager {
/**
* Returns a unique name not currently used by any other dataType or category
* with the same baseName
*
*
* @param path the path of the name
* @param baseName the base name to be made unique
* @return a unique name starting with baseName
@ -90,7 +90,7 @@ public interface DataTypeManager {
* Returns a data type after adding it to this data manager.
* The returned dataType will be in a category in this dataTypeManager
* that is equivalent to the category of the passed in dataType.
*
*
* @param dataType the dataType to be resolved.
* @param handler used to resolve conflicts with existing dataTypes.
* @return an equivalent dataType that "belongs" to this dataTypeManager.
@ -120,7 +120,7 @@ public interface DataTypeManager {
/**
* Adds all data types to the specified list.]
*
*
* @param list the result list into which the types will be placed
*/
public void getAllDataTypes(List<DataType> list);
@ -181,7 +181,7 @@ public interface DataTypeManager {
* there is also a category "b" under category "a". A better solution is to use
* the {@link #getDataType(DataTypePath)} method because the DataTypePath keeps the
* category and datatype name separate.
*
*
* @param dataTypePath path
* @return the dataType or null if it isn't found
*/
@ -206,7 +206,7 @@ public interface DataTypeManager {
/**
* Returns the dataTypeId for the given dataType. If the dataType is not
* currently in the dataTypeManger, it will be added
*
*
* @param dt the data type
* @return the ID of the resolved type
*/
@ -215,7 +215,7 @@ public interface DataTypeManager {
/**
* Returns the dataTypeId for the given dataType. If the dataType does not exist,
* a -1 will be returned
*
*
* @param dt the datatype to get an id for
* @return the ID of the type
*/
@ -224,7 +224,7 @@ public interface DataTypeManager {
/**
* Returns the dataType associated with the given dataTypeId or null if the dataTypeId is
* not valid
*
*
* @param dataTypeID the ID
* @return the type
*/
@ -232,7 +232,7 @@ public interface DataTypeManager {
/**
* Returns the Category with the given id
*
*
* @param categoryID id of the desired category
* @return the category
*/
@ -240,7 +240,7 @@ public interface DataTypeManager {
/**
* Get the category that has the given path
*
*
* @param path the path
* @return the category if defined, otherwise null
*/
@ -282,7 +282,7 @@ public interface DataTypeManager {
/**
* Return true if the given dataType exists in this data type manager
*
*
* @param dataType the type
* @return true if the type is in this manager
*/
@ -290,7 +290,7 @@ public interface DataTypeManager {
/**
* Create a category for the given path; returns the current category if it already exits
*
*
* @param path the path
* @return the category
*/
@ -351,7 +351,7 @@ public interface DataTypeManager {
/**
* Returns a default sized pointer to the given datatype. The pointer size is established
* dynamically based upon the data organization established by the compiler specification.
*
*
* @param datatype the pointed to data type
* @return the pointer
*/
@ -361,7 +361,7 @@ public interface DataTypeManager {
* Returns a pointer of the given size to the given datatype.
* Note: It is preferred to use default sized pointers when possible (i.e., size=-1,
* see {@link #getPointer(DataType)}) instead of explicitly specifying the size value.
*
*
* @param datatype the pointed to data type
* @param size the size of the pointer to be created or -1 for a default sized pointer
* @return the pointer
@ -416,6 +416,14 @@ public interface DataTypeManager {
*/
public void findEnumValueNames(long value, Set<String> enumValueNames);
/**
* Finds the data type using the given source archive and id.
*
* @param sourceArchive the optional source archive; required when the type is associated with
* that source archive
* @param datatypeID the type's id
* @return the type or null
*/
public DataType getDataType(SourceArchive sourceArchive, UniversalID datatypeID);
/**
@ -433,7 +441,7 @@ public interface DataTypeManager {
/**
* Returns the source archive for the given ID
*
*
* @param sourceID the ID
* @return the archive; null if the ID is null; null if the archive does not exist
*/
@ -447,7 +455,7 @@ public interface DataTypeManager {
/**
* Returns all data types within this manager that have as their source the given archive
*
*
* @param sourceArchive the archive
* @return the types
*/
@ -461,7 +469,7 @@ public interface DataTypeManager {
/**
* Change the given data type so that its source archive is the given archive
*
*
* @param datatype the type
* @param archive the archive
*/
@ -508,7 +516,7 @@ public interface DataTypeManager {
/**
* Removes the source archive from this manager. This will disassociate all data types in
* this manager from the given archive.
*
*
* @param sourceArchive the archive
*/
public void removeSourceArchive(SourceArchive sourceArchive);
@ -529,17 +537,18 @@ public interface DataTypeManager {
* @deprecated the method {@link DataType#getParents()} should be used instead.
* Use of {@link Set} implementations for containing DataTypes is also inefficient.
*/
@Deprecated
public Set<DataType> getDataTypesContaining(DataType dataType);
/**
* Determine if settings are supported for BuiltIn datatypes within this
* Determine if settings are supported for BuiltIn datatypes within this
* datatype manager.
* @return true if BuiltIn Settings are permitted
*/
public boolean allowsDefaultBuiltInSettings();
/**
* Determine if settings are supported for datatype components within this
* Determine if settings are supported for datatype components within this
* datatype manager (i.e., for structure and union components).
* @return true if BuiltIn Settings are permitted
*/

View File

@ -23,8 +23,8 @@ import ghidra.program.model.listing.*;
/**
* Creates and initializes {@link Structure} objects.
*
*
*
*
*/
public class StructureFactory {
public static final String DEFAULT_STRUCTURE_NAME = "struct";
@ -33,14 +33,14 @@ public class StructureFactory {
* Creates a {@link StructureDataType} instance based upon the information
* provided. The instance will not be placed in memory.
* <p>
* This method is just a pass-through method for
* This method is just a pass-through method for
* {@link #createStructureDataType(Program,Address,int,String,boolean)}
* equivalent to calling:
* <pre>
* Structure newStructure = StructureFactory.createStructureDataType(
* program, address, dataLength, DEFAULT_STRUCTURE_NAME, true );
* </pre>
*
*
* @param program The program to which the structure will belong.
* @param address The address of the structure.
* @param dataLength The number of components to add to the structure.
@ -50,7 +50,7 @@ public class StructureFactory {
* <li>if <code>dataLength</code> is not greater than zero
* <li>if the number of components to add exceeds the available
* address space
* <li>if there are any instructions in the provided
* <li>if there are any instructions in the provided
* address space
* <li>if there are no data components to add to the structure
* </ul>
@ -63,7 +63,7 @@ public class StructureFactory {
/**
* Creates a {@link StructureDataType} instance based upon the information
* provided. The instance will not be placed in memory.
*
*
* @param program The program to which the structure will belong.
* @param address The address of the structure.
* @param dataLength The number of components to add to the structure.
@ -77,7 +77,7 @@ public class StructureFactory {
* <li>if <code>dataLength</code> is not greater than zero
* <li>if the number of components to add exceeds the available
* address space
* <li>if there are any instructions in the provided
* <li>if there are any instructions in the provided
* address space
* <li>if there are no data components to add to the structure
* </ul>
@ -125,18 +125,18 @@ public class StructureFactory {
}
/**
* Creates a {@link StructureDataType} instance, which is inside of
* another structure, based upon the information provided. The instance
* Creates a {@link StructureDataType} instance, which is inside of
* another structure, based upon the information provided. The instance
* will not be placed in memory.
* <p>
* This method is just a pass-through method for
* This method is just a pass-through method for
* {@link #createStructureDataTypeInStrucuture(Program,Address,int[],int[],String,boolean)}
* equivalent to calling:
* <pre>
* Structure newStructure = StructureFactory.createStructureDataTypeInStrucuture(
* program, address, fromPath, toPath, DEFAULT_STRUCTURE_NAME, true );
* </pre>
*
*
* @param program The program to which the structure will belong.
* @param address The address of the structure.
* @param fromPath The path to the first element in the parent structure
@ -160,10 +160,10 @@ public class StructureFactory {
}
/**
* Creates a {@link StructureDataType} instance, which is inside of
* another structure, based upon the information provided. The instance
* Creates a {@link StructureDataType} instance, which is inside of
* another structure, based upon the information provided. The instance
* will not be placed in memory.
*
*
* @param program The program to which the structure will belong.
* @param address The address of the structure.
* @param fromPath The path to the first element in the parent structure
@ -227,7 +227,7 @@ public class StructureFactory {
return newStructure;
}
// uses the provided context to initiailze the provided structure with
// uses the provided context to initialize the provided structure with
// dataLength number of components
private static void initializeStructureFromContext(Structure structure,
DataTypeProviderContext context, int dataLength) {
@ -249,8 +249,8 @@ public class StructureFactory {
}
for (DataTypeComponent dataComp : dataComps) {
structure.add(dataComp.getDataType(), dataComp.getLength(),
dataComp.getFieldName(), dataComp.getComment());
structure.add(dataComp.getDataType(), dataComp.getLength(), dataComp.getFieldName(),
dataComp.getComment());
}
}
}