Candidate release of source code.

This commit is contained in:
Dan 2019-03-26 13:45:32 -04:00
parent db81e6b3b0
commit 79d8f164f8
12449 changed files with 2800756 additions and 16 deletions

View file

@ -0,0 +1 @@
MODULE FILE LICENSE: lib/commons-compress-1.18.jar Apache License 2.0

View file

@ -0,0 +1,14 @@
apply plugin: 'eclipse'
eclipse.project.name = 'Framework Project'
dependencies {
compile project(':Generic')
compile project(':DB')
compile project(':Docking')
compile project(':FileSystem')
compile project(':Utility')
testCompile project(':Generic').sourceSets.test.output
compile "org.apache.commons:commons-compress:1.18"
}

View file

@ -0,0 +1,63 @@
##VERSION: 2.0
##MODULE IP: Apache License 2.0
##MODULE IP: Modified Nuvola Icons - LGPL 2.1
##MODULE IP: Tango Icons - Public Domain
##MODULE IP: FAMFAMFAM Icons - CC 2.5
##MODULE IP: Oxygen Icons - LGPL 3.0
##MODULE IP: Crystal Clear Icons - LGPL 2.1
.classpath||GHIDRA||||END|
.project||GHIDRA||||END|
Module.manifest||GHIDRA||||END|
build.gradle||GHIDRA||||END|
data/ExtensionPoint.manifest||GHIDRA||||END|
src/main/java/ghidra/framework/cmd/package.html||GHIDRA||reviewed||END|
src/main/java/ghidra/framework/data/package.html||GHIDRA||reviewed||END|
src/main/java/ghidra/framework/model/package.html||GHIDRA||||END|
src/main/java/ghidra/framework/plugintool/dialog/package.html||GHIDRA||||END|
src/main/java/ghidra/framework/plugintool/mgr/package.html||GHIDRA||||END|
src/main/java/ghidra/framework/plugintool/package.html||GHIDRA||||END|
src/main/java/ghidra/framework/plugintool/util/package.html||GHIDRA||||END|
src/main/java/ghidra/framework/project/package.html||GHIDRA||||END|
src/main/java/ghidra/framework/project/tool/package.html||GHIDRA||||END|
src/main/resources/images/Plus.png||GHIDRA||||END|
src/main/resources/images/allInTool.gif||GHIDRA||reviewed||END|
src/main/resources/images/applications-science.png||Oxygen Icons - LGPL 3.0||||END|
src/main/resources/images/bug.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
src/main/resources/images/check.png||GHIDRA||reviewed||END|
src/main/resources/images/checkNotLatest.gif||GHIDRA||reviewed||END|
src/main/resources/images/checkex.png||GHIDRA||reviewed||END|
src/main/resources/images/checkmark_green.gif||GHIDRA||reviewed||END|
src/main/resources/images/closedSmallFolder.png||Modified Nuvola Icons - LGPL 2.1||||END|
src/main/resources/images/connected.gif||GHIDRA||reviewed||END|
src/main/resources/images/cut_red.png||FAMFAMFAM Icons - CC 2.5||||END|
src/main/resources/images/defaultIndicator.png||GHIDRA||reviewed||END|
src/main/resources/images/dialog-cancel.png||Oxygen Icons - LGPL 3.0||||END|
src/main/resources/images/disconnected.gif||GHIDRA||reviewed||END|
src/main/resources/images/disk.png||FAMFAMFAM Icons - CC 2.5||||END|
src/main/resources/images/disk_save_as.png||FAMFAMFAM Icons - CC 2.5|||modified silk icon disk_multiple.png|END|
src/main/resources/images/edit-cut.png||Tango Icons - Public Domain|||tango icon set|END|
src/main/resources/images/face-glasses.png||Tango Icons - Public Domain|||tango icon set|END|
src/main/resources/images/folder_add.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
src/main/resources/images/monitor.png||FAMFAMFAM Icons - CC 2.5|||silk|END|
src/main/resources/images/noneInTool.gif||GHIDRA||reviewed||END|
src/main/resources/images/page_add.png||FAMFAMFAM Icons - CC 2.5||||END|
src/main/resources/images/page_copy.png||FAMFAMFAM Icons - CC 2.5||||END|
src/main/resources/images/page_delete.png||FAMFAMFAM Icons - CC 2.5||||END|
src/main/resources/images/page_edit.png||FAMFAMFAM Icons - CC 2.5||||END|
src/main/resources/images/page_paste.png||FAMFAMFAM Icons - CC 2.5||||END|
src/main/resources/images/plugin.png||GHIDRA||reviewed||END|
src/main/resources/images/small_hijack.gif||GHIDRA||reviewed||END|
src/main/resources/images/someInTool.gif||GHIDRA||reviewed||END|
src/main/resources/images/undo_hijack.png||GHIDRA||reviewed||END|
src/main/resources/images/unknownFile.gif||GHIDRA||reviewed||END|
src/main/resources/images/user-busy.png||Oxygen Icons - LGPL 3.0|||Oxygen icon theme (dual license; LGPL or CC-SA-3.0)|END|
src/main/resources/images/user.png||FAMFAMFAM Icons - CC 2.5||||END|
src/main/resources/images/vcAdd.png||GHIDRA||reviewed||END|
src/main/resources/images/vcCheckIn.png||GHIDRA||reviewed||END|
src/main/resources/images/vcCheckOut.png||GHIDRA||reviewed||END|
src/main/resources/images/vcMerge.png||GHIDRA||reviewed||END|
src/main/resources/images/vcUndoCheckOut.png||GHIDRA||reviewed||END|
src/main/resources/images/view-refresh.png||Tango Icons - Public Domain||||END|
src/main/resources/images/view_detailed.png||Crystal Clear Icons - LGPL 2.1||||END|
src/main/resources/images/wand.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|
src/main/resources/images/warning.png||FAMFAMFAM Icons - CC 2.5|||famfamfam silk icon set|END|

View file

@ -0,0 +1,6 @@
ContentHandler
Plugin
PluginPackage
ToolAssociation
ProjectDataColumn
GhidraProtocolHandler

View file

@ -0,0 +1,29 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra;
import ghidra.framework.plugintool.util.PluginPackage;
import resources.ResourceManager;
public class MiscellaneousPluginPackage extends PluginPackage {
public static final String NAME = "Miscellaneous";
public MiscellaneousPluginPackage() {
super(NAME, ResourceManager.loadImage("images/plasma.png"),
"These plugins do not belong to a package", MISCELLANIOUS_PRIORITY);
}
}

View file

@ -0,0 +1,33 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra;
import ghidra.framework.ModuleInitializer;
public class ProjectInitializer implements ModuleInitializer {
public void run() {
// Register "ghidra" URL protocol Handler
ghidra.framework.protocol.ghidra.Handler.registerHandler();
}
@Override
public String getName() {
return "Project Module";
}
}

View file

@ -0,0 +1,61 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.merge;
public interface MergeProgressModifier {
/**
* Updates the current phase progress area in the default merge panel.
* @param currentProgressPercentage the progress percentage completed for the current phase.
* This should be a value from 0 to 100.
* @param progressMessage a message indicating what is currently occurring in this phase.
* Null indicates to use the default message.
*/
public void updateProgress(final String description);
/**
* Updates the current phase progress area in the default merge panel.
* @param currentProgressPercentage the progress percentage completed for the current phase.
* This should be a value from 0 to 100.
*/
public void updateProgress(final int currentProgressPercentage);
/**
* Updates the current phase progress area in the default merge panel.
* @param currentProgressPercentage the progress percentage completed for the current phase.
* This should be a value from 0 to 100.
* @param progressMessage a message indicating what is currently occurring in this phase.
*/
public void updateProgress(final int currentProgressPercentage, final String progressMessage);
/**
* The manager (MergeResolver) for a particular merge phase should call this when its phase or sub-phase begins.
* The string array should match one that the returned by MergeResolver.getPhases().
* @param mergePhase identifier for the merge phase to change to in progress status.
* @see MergeResolver
*/
public void setInProgress(String[] mergePhase);
/**
* The manager (MergeResolver) for a particular merge phase should call this when its phase or sub-phase completes.
* The string array should match one that the returned by MergeResolver.getPhases().
* @param mergePhase identifier for the merge phase to change to completed status.
* @see MergeResolver
*/
public void setCompleted(String[] mergePhase);
}

View file

@ -0,0 +1,25 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.plugin;
public class GenericPluginCategoryNames {
public static final String COMMON = "Common";
public static final String SUPPORT = "Support";
public static final String TESTING = "Testing";
public static final String MISC = "Miscellaneous";
public static final String EXAMPLES = "Examples";
}

View file

@ -0,0 +1,39 @@
/* ###
* 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;
public interface PluginCategoryNames {
String COMMON = GenericPluginCategoryNames.COMMON;
String SUPPORT = GenericPluginCategoryNames.SUPPORT;
String CODE_VIEWER = "Code Viewer";
String BYTE_VIEWER = "Byte Viewer";
String GRAPH = "Graph";
String ANALYSIS = "Analysis";
String NAVIGATION = "Navigation";
String SEARCH = "Search";
String TREE = "Program Tree";
String TESTING = GenericPluginCategoryNames.TESTING;
String DIFF = "Code Difference";
String MISC = GenericPluginCategoryNames.MISC;
String USER_ANNOTATION = "User Annotation";
String EXAMPLES = GenericPluginCategoryNames.EXAMPLES;
String SELECTION = "Selection";
String INTERPRETERS = "Interpreters";
String DEBUGGER = "Debugger";
String PATCHING = "Patching";
String DECOMPILER = "Decompiler";
String UNMANAGED = "Unmanaged";
}

View file

@ -0,0 +1,237 @@
/* ###
* 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.help;
import java.awt.*;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.Transferable;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import javax.swing.text.JTextComponent;
import docking.DialogComponentProvider;
import docking.DockingUtils;
import docking.dnd.GClipboard;
import docking.dnd.StringTransferable;
import docking.widgets.OptionDialog;
import ghidra.framework.model.DomainFile;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.layout.PairLayout;
public class AboutDomainObjectUtils {
private static final MouseListener COPY_MOUSE_LISTENER = new PopupMouseListener();
/**
* Displays an informational dialog about the specified domain object
*
* @param tool plugin tool
* @param domainObject domain object to display information about
* @param title title to use for the dialog
* @param additionalInfo additional custom user information to append to
* the bottom of the dialog
* @param shouldParent true means that about dialog will be parented
* to <code>parent</code>, false means that only
* the location will be set relative to <code>parent</code>
*/
public static void displayInformation(PluginTool tool, DomainFile domainFile,
Map<String, String> metadata, String title, String additionalInfo,
HelpLocation helpLocation) {
JComponent aboutComp = getAboutPanel(domainFile, metadata, additionalInfo);
if (aboutComp == null) {
return;
}
Dialog dialog = new Dialog(title, aboutComp);
if (helpLocation != null) {
dialog.setHelpLocation(helpLocation);
}
tool.showDialog(dialog, (Component) null);
}
private static void addInfo(JPanel panel, String name, String value) {
if (value == null) {
value = "?????";
}
JTextField nameField = new JTextField(name);
nameField.setBorder(null);
DockingUtils.setTransparent(nameField);
nameField.setEditable(false);
nameField.addMouseListener(COPY_MOUSE_LISTENER);
JTextField valueField = new JTextField(value);
valueField.setBorder(null);
DockingUtils.setTransparent(valueField);
valueField.setEditable(false);
valueField.addMouseListener(COPY_MOUSE_LISTENER);
panel.add(nameField);
panel.add(valueField);
}
private static JComponent getAboutPanel(DomainFile domainFile, Map<String, String> metadata,
String additionalInfo) {
Font font = new Font("Monospaced", Font.PLAIN, 12);
JPanel aboutPanel = new JPanel(new PairLayout());
JScrollPane propertyScroll = new JScrollPane(aboutPanel);
JPanel contentPanel = new JPanel(new BorderLayout(5, 5));
contentPanel.add(propertyScroll, BorderLayout.CENTER);
addInfo(aboutPanel, "Project File Name: ", domainFile.getName());
long lastModified = domainFile.getLastModifiedTime();
if (lastModified != 0) {
addInfo(aboutPanel, "Last Modified:", (new Date(lastModified)).toString());
}
addInfo(aboutPanel, "Readonly:", Boolean.toString(domainFile.isReadOnly()));
Iterator<String> it = metadata.keySet().iterator();
while (it.hasNext()) {
String key = it.next();
String value = metadata.get(key);
addInfo(aboutPanel, key + ":", value);
}
if (additionalInfo != null && additionalInfo.length() > 0) {
JTextArea auxArea = new JTextArea(additionalInfo);
auxArea.setFont(font);
DockingUtils.setTransparent(auxArea);
auxArea.setEditable(false);
auxArea.setCaretPosition(0); // move cursor to BOF...
JScrollPane sp = new JScrollPane(auxArea);
sp.setBorder(BorderFactory.createTitledBorder(
BorderFactory.createLineBorder(Color.black), "Additional Information"));
sp.setPreferredSize(new Dimension(1, 175)); //width is ignored by border layout...
JScrollBar sb = sp.getVerticalScrollBar();
sb.setBorder(BorderFactory.createEtchedBorder());
contentPanel.add(sp, BorderLayout.SOUTH);
}
JLabel infoLabel =
new JLabel(OptionDialog.getIconForMessageType(OptionDialog.INFORMATION_MESSAGE));
JPanel infoPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 5, 10));
infoPanel.add(infoLabel);
JPanel panel = new JPanel(new BorderLayout(5, 5));
panel.add(infoPanel, BorderLayout.WEST);
panel.add(contentPanel, BorderLayout.CENTER);
Component[] comps = aboutPanel.getComponents();
for (Component comp : comps) {
comp.setFont(font);
}
aboutPanel.invalidate();
panel.setPreferredSize(new Dimension(800, 800));
return panel;
}
private static class Dialog extends DialogComponentProvider {
Dialog(String title, JComponent workPanel) {
super(title, false, false, true, false);
init(workPanel);
}
private void init(JComponent workPanel) {
addWorkPanel(workPanel);
addOKButton();
setRememberSize(true);
}
@Override
protected void okCallback() {
close();
}
}
private static class PopupMouseListener extends MouseAdapter {
@Override
public void mousePressed(MouseEvent e) {
processPopupMouseEvent(e);
}
@Override
public void mouseClicked(MouseEvent e) {
processPopupMouseEvent(e);
}
@Override
public void mouseReleased(MouseEvent e) {
processPopupMouseEvent(e);
}
private void processPopupMouseEvent(MouseEvent e) {
final Component component = e.getComponent();
if (component == null) {
return;
}
// get the bounds to see if the clicked point is over the component
Rectangle bounds = component.getBounds(); // get bounds to get width and height
if (component instanceof JComponent) {
((JComponent) component).computeVisibleRect(bounds);
}
Point point = e.getPoint();
boolean withinBounds = bounds.contains(point);
if (e.isPopupTrigger() && withinBounds) {
JPopupMenu popupMenu = new JPopupMenu();
JMenuItem item = new JMenuItem("Copy");
item.addActionListener(event -> writeDataToClipboard(component));
popupMenu.add(item);
popupMenu.show(component, e.getX(), e.getY());
}
}
private static void writeDataToClipboard(Component component) {
Clipboard systemClipboard = GClipboard.getSystemClipboard();
try {
systemClipboard.setContents(createContents(component), null);
}
catch (IllegalStateException e) {
Msg.showInfo(AboutDomainObjectUtils.class, null, "Cannot Copy Data",
"Unable to copy information to clipboard. Please try again.");
}
}
private static Transferable createContents(Component component) {
//
// Structure based upon what is created in getAboutPanel()
//
Container parent = component.getParent();
Component[] components = parent.getComponents();
StringBuilder buffy = new StringBuilder();
for (int i = 0; i < components.length; i++) {
buffy.append(((JTextComponent) components[i]).getText());
if (i != 0 && i % 2 != 0) {
buffy.append('\n');
}
else {
buffy.append('\t');
}
}
return new StringTransferable(buffy.toString());
}
}
}

View file

@ -0,0 +1,26 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util;
import ghidra.framework.plugintool.*;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
public interface FileOpenDataFlavorHandler {
public void handle(PluginTool tool, Object obj, DropTargetDropEvent e, DataFlavor f);
}

View file

@ -0,0 +1,37 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util;
import ghidra.framework.PluggableServiceRegistry;
import ghidra.framework.main.datatree.DataTreeDragNDropHandler;
import ghidra.framework.main.datatree.VersionInfoTransferable;
public class FileOpenDataFlavorHandlerService {
static {
PluggableServiceRegistry.registerPluggableService(FileOpenDataFlavorHandlerService.class, new FileOpenDataFlavorHandlerService());
}
public static void registerDataFlavorHandlers() {
FileOpenDataFlavorHandlerService factory = PluggableServiceRegistry.getPluggableService(FileOpenDataFlavorHandlerService.class);
factory.doRegisterDataFlavorHandlers();
}
protected void doRegisterDataFlavorHandlers() {
FileOpenDropHandler.addDataFlavorHandler(DataTreeDragNDropHandler.localDomainFileFlavor, new LocalTreeNodeFlavorHandler());
FileOpenDropHandler.addDataFlavorHandler(VersionInfoTransferable.localVersionInfoFlavor, new LocalTreeNodeFlavorHandler());
}
}

View file

@ -0,0 +1,159 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.CascadedDropTarget;
import java.awt.Component;
import java.awt.Container;
import java.awt.datatransfer.DataFlavor;
import java.awt.dnd.*;
import java.awt.event.ContainerEvent;
import java.awt.event.ContainerListener;
import java.util.HashMap;
import java.util.Set;
import javax.swing.CellRendererPane;
import docking.DropTargetHandler;
import docking.dnd.DropTgtAdapter;
import docking.dnd.Droppable;
/**
* Handles drag/drop events on a given component such that a file
* dropped on the component from the front end tool will cause
* that file to be opened. Properly handles drop events with
* child components and listens for components being added/removed
* in order to properly support drag/drop with all components.
*/
public class FileOpenDropHandler implements DropTargetHandler, Droppable, ContainerListener {
private static HashMap<DataFlavor, FileOpenDataFlavorHandler> handlers =
new HashMap<DataFlavor, FileOpenDataFlavorHandler>();
static {
FileOpenDataFlavorHandlerService.registerDataFlavorHandlers();
}
private DropTgtAdapter dropTargetAdapter;
private DropTarget globalDropTarget;
private PluginTool tool;
private Component component;
/**
* Construct a new FileOpenDropHandler.
* @param tool plugin tool
* @param component component that is the drop target
*/
public FileOpenDropHandler(PluginTool tool, Component component) {
this.tool = tool;
this.component = component;
Set<DataFlavor> keySet = handlers.keySet();
DataFlavor[] handlersFlavorArray = keySet.toArray(new DataFlavor[keySet.size()]);
dropTargetAdapter =
new DropTgtAdapter(this, DnDConstants.ACTION_COPY_OR_MOVE, handlersFlavorArray);
globalDropTarget =
new DropTarget(component, DnDConstants.ACTION_COPY_OR_MOVE, dropTargetAdapter, true);
initializeComponents(component);
}
/**
* Dispose this drop handler.
*/
public void dispose() {
deinitializeComponents(component);
globalDropTarget.removeDropTargetListener(dropTargetAdapter);
}
public boolean isDropOk(DropTargetDragEvent e) {
Set<DataFlavor> flavors = handlers.keySet();
for (DataFlavor dataFlavor : flavors) {
if (e.isDataFlavorSupported(dataFlavor)) {
return true;
}
}
return false;
}
public void add(Object obj, DropTargetDropEvent e, DataFlavor f) {
FileOpenDataFlavorHandler handler = handlers.get(f);
if (handler != null) {
handler.handle(tool, obj, e, f);
}
}
public void dragUnderFeedback(boolean ok, DropTargetDragEvent e) {
// nothing to display or do
}
public void undoDragUnderFeedback() {
// nothing to display or do
}
private void initializeComponents(Component comp) {
if (comp instanceof CellRendererPane)
return;
if (comp instanceof Container) {
Container c = (Container) comp;
c.addContainerListener(this);
Component comps[] = c.getComponents();
for (Component element : comps)
initializeComponents(element);
}
DropTarget primaryDropTarget = comp.getDropTarget();
if (primaryDropTarget != null) {
new CascadedDropTarget(comp, primaryDropTarget, globalDropTarget);
}
}
private void deinitializeComponents(Component comp) {
if (comp instanceof CellRendererPane)
return;
if (comp instanceof Container) {
Container c = (Container) comp;
c.removeContainerListener(this);
Component comps[] = c.getComponents();
for (Component element : comps)
deinitializeComponents(element);
}
DropTarget dt = comp.getDropTarget();
if (dt instanceof CascadedDropTarget) {
CascadedDropTarget target = (CascadedDropTarget) dt;
DropTarget newTarget = target.removeDropTarget(globalDropTarget);
comp.setDropTarget(newTarget);
}
}
public void componentAdded(ContainerEvent e) {
initializeComponents(e.getChild());
}
public void componentRemoved(ContainerEvent e) {
deinitializeComponents(e.getChild());
}
public static void addDataFlavorHandler(DataFlavor dataFlavor, FileOpenDataFlavorHandler handler) {
handlers.put(dataFlavor, handler);
}
public static FileOpenDataFlavorHandler removeDataFlavorHandler(DataFlavor dataFlavor) {
return handlers.remove(dataFlavor);
}
}

View file

@ -0,0 +1,50 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.app.util;
public class GenericHelpTopics {
/**
* Help Topic for "About."
*/
public final static String ABOUT = "About";
/**
* Name of options for the help topic for the front end
* (Project Window).
*/
public final static String FRONT_END = "FrontEndPlugin";
/**
* Help Topic for the glossary.
*/
public final static String GLOSSARY = "Glossary";
/**
* Help for Intro topics.
*/
public final static String INTRO = "Intro";
/**
* Help Topic for the project repository.
*/
public final static String REPOSITORY = "Repository";
/**
* Help Topic for tools.
*/
public final static String TOOL = "Tool";
}

View file

@ -0,0 +1,72 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
*
*/
package ghidra.app.util;
import ghidra.framework.main.GetVersionedObjectTask;
import ghidra.framework.main.datatree.*;
import ghidra.framework.model.*;
import ghidra.framework.plugintool.PluginTool;
import java.awt.datatransfer.DataFlavor;
import java.awt.dnd.DropTargetDropEvent;
import java.util.List;
final class LocalTreeNodeFlavorHandler implements FileOpenDataFlavorHandler {
public void handle(PluginTool tool, Object obj, DropTargetDropEvent e, DataFlavor f) {
if (f.equals(DataTreeDragNDropHandler.localDomainFileFlavor)) {
List<?> files = (List<?>) obj;
DomainFile[] domainFiles = new DomainFile[files.size()];
for (int i = 0; i < files.size(); i++) {
domainFiles[i] = (DomainFile) files.get(i);
}
tool.acceptDomainFiles(domainFiles);
}
else if (f.equals(DataTreeDragNDropHandler.localDomainFileTreeFlavor)) {
List<?> files = (List<?>) obj;
DomainFile[] domainFiles = new DomainFile[files.size()];
for (int i = 0; i < files.size(); i++) {
DomainFileNode node = (DomainFileNode) files.get(i);
domainFiles[i] = node.getDomainFile();
}
tool.acceptDomainFiles(domainFiles);
}
else if (f.equals(VersionInfoTransferable.localVersionInfoFlavor)) {
VersionInfo info = (VersionInfo) obj;
Project project = tool.getProject();
ProjectData projectData = project.getProjectData();
DomainFile file = projectData.getFile(info.getDomainFilePath());
DomainObject versionedObj = getVersionedObject(tool, file, info.getVersionNumber());
if (versionedObj != null) {
DomainFile domainFile = versionedObj.getDomainFile();
if (domainFile != null) {
tool.acceptDomainFiles(new DomainFile[] { domainFile });
}
versionedObj.release(this);
}
}
}
private DomainObject getVersionedObject(PluginTool tool, DomainFile file, int versionNumber) {
GetVersionedObjectTask task = new GetVersionedObjectTask(this, file, versionNumber);
tool.execute(task, 250);
return task.getVersionedObject();
}
}

View file

@ -0,0 +1,47 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
*
*/
package ghidra.app.util;
import ghidra.framework.main.*;
import ghidra.framework.main.datatree.*;
import ghidra.framework.model.*;
import ghidra.framework.plugintool.*;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
final class LocalVersionInfoFlavorHandler implements
FileOpenDataFlavorHandler {
public void handle(PluginTool tool, Object obj, DropTargetDropEvent e, DataFlavor f) {
VersionInfo info = (VersionInfo) obj;
DomainFile file = tool.getProject().getProjectData().getFile(info.getDomainFilePath());
GetVersionedObjectTask task = new GetVersionedObjectTask(this, file,
info.getVersionNumber());
tool.execute(task, 250);
DomainObject versionedObj = task.getVersionedObject();
if (versionedObj != null) {
DomainFile vfile = versionedObj.getDomainFile();
tool.acceptDomainFiles(new DomainFile[] {vfile});
versionedObj.release(this);
}
}
}

View file

@ -0,0 +1,355 @@
/* ###
* 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.framework;
import java.io.*;
import java.util.*;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jdom.*;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;
import ghidra.framework.model.ProjectManager;
import ghidra.framework.model.ToolTemplate;
import ghidra.framework.project.tool.GhidraToolTemplate;
import ghidra.util.*;
import ghidra.util.exception.AssertException;
import ghidra.util.xml.GenericXMLOutputter;
import ghidra.util.xml.XmlUtilities;
import resources.ResourceManager;
public class ToolUtils {
private static final Logger LOGGER = LogManager.getLogger(ToolUtils.class);
private static final File USER_TOOLS_DIR = new File(getApplicationToolDirPath());
private static Set<ToolTemplate> allTools;
private static Set<ToolTemplate> defaultTools;
private static Set<ToolTemplate> extraTools;
// this can be changed reflectively
private static boolean allowTestTools = SystemUtilities.isInTestingMode();
private ToolUtils() {
// utils class
}
public static File getUserToolsDirectory() {
return USER_TOOLS_DIR;
}
/**
* Returns all tools found in the classpath that live under a root
* 'defaultTools' directory
*
* @return the default tools
*/
// synchronized to protect loading of static set
public static synchronized Set<ToolTemplate> getDefaultApplicationTools() {
if (defaultTools != null) {
return defaultTools;
}
Set<ToolTemplate> set = new HashSet<>();
Set<String> toolNames = ResourceManager.getResourceNames("defaultTools", ".tool");
for (String toolName : toolNames) {
ToolTemplate tool = readToolTemplate(toolName);
if (tool != null) {
set.add(tool);
}
}
defaultTools = Collections.unmodifiableSet(set);
return defaultTools;
}
/**
* Returns all tools found in the classpath that live under a root
* 'extraTools' directory
*
* @return the extra tools
*/
// synchronized to protect loading of static set
public static synchronized Set<ToolTemplate> getExtraApplicationTools() {
if (extraTools != null) {
return extraTools;
}
Set<ToolTemplate> set = new HashSet<>();
Set<String> extraToolsList = ResourceManager.getResourceNames("extraTools", ".tool");
for (String toolName : extraToolsList) {
ToolTemplate tool = readToolTemplate(toolName);
if (tool != null) {
set.add(tool);
}
}
extraTools = Collections.unmodifiableSet(set);
return extraTools;
}
/**
* Returns all tools found in the classpath that live under a root
* 'defaultTools' directory or a root 'extraTools' directory
*
* @return the tools
*/
// synchronized to protect loading of static set
public static synchronized Set<ToolTemplate> getAllApplicationTools() {
if (allTools != null) {
return allTools;
}
Set<ToolTemplate> set = new HashSet<>();
set.addAll(getDefaultApplicationTools());
set.addAll(getExtraApplicationTools());
allTools = Collections.unmodifiableSet(set);
return allTools;
}
public static Map<String, ToolTemplate> loadUserTools() {
FilenameFilter filter =
(dir, name) -> name.endsWith(ProjectManager.APPLICATION_TOOL_EXTENSION);
// we want sorting by tool name, so use a sorted map
Map<String, ToolTemplate> map = new TreeMap<>();
File[] toolFiles = USER_TOOLS_DIR.listFiles(filter);
if (toolFiles != null) {
for (File toolFile : toolFiles) {
ToolTemplate template = ToolUtils.readToolTemplate(toolFile);
if (template != null) {
map.put(template.getName(), template);
}
}
}
return map;
}
public static void removeInvalidPlugins(ToolTemplate template) {
// parse the XML to see what plugins are loaded
Element xmlRoot = template.saveToXml();
// get out the tool element of the high-level tool
Element toolElement = xmlRoot.getChild("TOOL");
// the content of the tool xml consists of:
// -option manager content
// -plugin manager content
// -window manager content
// plugins are stored by adding content from SaveState objects, one per
// plugin
List<?> children = toolElement.getChildren("PLUGIN");
Object[] childArray = children.toArray(); // we may modify this list,
// so we need an array
for (Object object : childArray) {
Element pluginElement = (Element) object;
Attribute classAttribute = pluginElement.getAttribute("CLASS");
String value = classAttribute.getValue();
// check to see if we can still find the plugin class (it may have
// been removed)
try {
Class.forName(value);
}
catch (Throwable t) {
// oh well, leave it out
// TOOL: should we inform the user about these at some point?
LOGGER.info("Removing invalid plugin " + pluginElement.getAttributeValue("CLASS") +
" from tool: " + template.getName());
toolElement.removeContent(pluginElement);
}
}
// force the changes
template.restoreFromXml(xmlRoot);
}
public static void deleteTool(ToolTemplate template) {
USER_TOOLS_DIR.mkdirs();
String name = template.getName();
File toolFile = getToolFile(name);
if (toolFile == null) {
return;
}
toolFile.delete();
}
public static void renameToolTemplate(ToolTemplate toolTemplate, String newName) {
ToolUtils.deleteTool(toolTemplate);
toolTemplate.setName(newName);
ToolUtils.writeToolTemplate(toolTemplate);
}
public static boolean writeToolTemplate(ToolTemplate template) {
USER_TOOLS_DIR.mkdirs();
String toolName = template.getName();
File toolFile = getToolFile(toolName);
boolean status = false;
try (OutputStream os = new FileOutputStream(toolFile)) {
Element element = template.saveToXml();
Document doc = new Document(element);
XMLOutputter xmlout = new GenericXMLOutputter();
xmlout.output(doc, os);
os.close();
status = true;
}
catch (Exception e) {
Msg.error(LOGGER, "Error saving tool: " + toolName, e);
}
return status;
}
public static ToolTemplate readToolTemplate(File toolFile) {
GhidraToolTemplate toolTemplate = null;
try (FileInputStream is = new FileInputStream(toolFile)) {
SAXBuilder sax = XmlUtilities.createSecureSAXBuilder(false, false);
Document doc = sax.build(is);
Element root = doc.getRootElement();
toolTemplate = new GhidraToolTemplate(root, toolFile.getAbsolutePath());
}
catch (FileNotFoundException e) {
throw new AssertException(
"We should only be passed valid files. Cannot find: " + toolFile.getAbsolutePath());
}
catch (JDOMException e) {
Msg.error(LOGGER, "Error reading XML for " + toolFile, e);
}
catch (Exception e) {
Msg.error(LOGGER, "Can't read tool template for " + toolFile, e);
}
updateFilenameToMatchToolName(toolFile, toolTemplate);
return toolTemplate;
}
private static void updateFilenameToMatchToolName(File toolFile,
GhidraToolTemplate toolTemplate) {
if (toolTemplate == null) {
return; // there must have been a problem creating the template
}
File correctToolFile = getToolFile(toolTemplate.getName());
if (correctToolFile.equals(toolFile)) {
return; // nothing to update
}
if (removeLastExtension(correctToolFile.getName()).equals(
NamingUtilities.mangle(toolTemplate.getName()))) {
return; // nothing to update
}
// If we get here, then we have two differently named files (the new one needs to replace
// the outdated old one). Make sure the files live in the same directory (otherwise,
// we can't delete the old one (this implies it is a default tool)).
if (correctToolFile.getParentFile().equals(toolFile.getParentFile())) {
// same parent directory, but different filename
toolFile.delete();
}
writeToolTemplate(toolTemplate);
}
private static String removeLastExtension(String filename) {
int period = filename.lastIndexOf('.');
if (period == -1) {
return filename;
}
return filename.substring(0, period);
}
public static ToolTemplate readToolTemplate(String resourceFileName) {
if (skipTool(resourceFileName)) {
return null;
}
try (InputStream is = ResourceManager.getResourceAsStream(resourceFileName)) {
if (is == null) {
return null;
}
SAXBuilder sax = XmlUtilities.createSecureSAXBuilder(false, false);
Document doc = sax.build(is);
Element root = doc.getRootElement();
return new GhidraToolTemplate(root, resourceFileName);
}
catch (JDOMException e) {
Msg.error(LOGGER, "Error reading XML for resource " + resourceFileName, e);
}
catch (Exception e) {
Msg.error(LOGGER, "Can't read tool template for resource " + resourceFileName, e);
}
return null;
}
private static boolean skipTool(String toolName) {
if (allowTestTools) {
return false;
}
if (StringUtils.containsIgnoreCase(toolName, "test")) {
LOGGER.trace("Not adding default 'test' tool: " + toolName);
return true;
}
return false;
}
public static String getUniqueToolName(ToolTemplate template) {
String name = template.getName();
int n = 1;
while (ToolUtils.getToolFile(name).exists()) {
name = name + "_" + n++;
}
return name;
}
private static File getToolFile(File dir, String toolName) {
return new File(dir,
NamingUtilities.mangle(toolName) + ProjectManager.APPLICATION_TOOL_EXTENSION);
}
public static File getToolFile(String name) {
return getToolFile(USER_TOOLS_DIR, name);
}
/**
* Returns the user's personal tool chest directory path.
*/
public static String getApplicationToolDirPath() {
String userSettingsPath = Application.getUserSettingsDirectory().getAbsolutePath();
return userSettingsPath + File.separatorChar + ProjectManager.APPLICATION_TOOLS_DIR_NAME;
}
}

View file

@ -0,0 +1,139 @@
/* ###
* 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.framework.cmd;
import ghidra.framework.model.DomainObject;
import ghidra.util.task.TaskMonitor;
import ghidra.util.task.TaskMonitorAdapter;
/**
* Abstract command that will be run in a thread (in the background)
* other than the AWT(GUI) thread. Use this to apply a long running
* command that is interruptable.
*
* The monitor allows the command to display status information as it
* executes.
*
* This allows commands to make changes in the background so that the
* GUI is not frozen and the user can still interact with the GUI.
*
*
*/
public abstract class BackgroundCommand implements Command {
private String name;
private boolean hasProgress;
private boolean canCancel;
private boolean isModal;
private String statusMsg;
public BackgroundCommand() {
this("no-name", false, false, false);
}
public BackgroundCommand(String name, boolean hasProgress, boolean canCancel, boolean isModal) {
this.name = name;
this.hasProgress = hasProgress;
this.canCancel = canCancel;
this.isModal = isModal;
}
/*
* @see ghidra.framework.cmd.Command#applyTo(ghidra.framework.model.DomainObject)
*/
@Override
public final boolean applyTo(DomainObject obj) {
return applyTo(obj, TaskMonitorAdapter.DUMMY_MONITOR);
}
/**
* Method called when this command is to apply changes to the
* given domain object. A monitor is provided to display status
* information about the command as it executes in the background.
*
* @param obj domain object that will be affected by the command
* @param monitor monitor to show progress of the command
*
* @return true if the command applied successfully
*/
public abstract boolean applyTo(DomainObject obj, TaskMonitor monitor);
// TODO: This should really throw CancelledException when canceled
@Override
public String getName() {
return name;
}
/**
* Check if the command provides progress information.
*
* @return true if the command shows progress information
*/
public boolean hasProgress() {
return hasProgress;
}
/**
* Check if the command can be canceled.
*
* @return true if this command can be canceled
*/
public boolean canCancel() {
return canCancel;
}
/**
* Check if the command requires the monitor to be modal. No other
* command should be allowed, and the GUI will be locked.
*
* @return true if no other operation should be going on while this
* command is in progress.
*/
public boolean isModal() {
return isModal;
}
/**
* Called when this command is going to be removed/canceled without
* running it. This gives the command the opportunity to free any
* temporary resources it has hold of.
*/
public void dispose() {
// do nothing by default
}
/**
* Called when the task monitor is completely done with indicating progress.
*/
public void taskCompleted() {
// do nothing by default
}
@Override
public String getStatusMsg() {
return statusMsg;
}
protected void setStatusMsg(String statusMsg) {
this.statusMsg = statusMsg;
}
@Override
public String toString() {
return getName();
}
}

View file

@ -0,0 +1,29 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.cmd;
/**
* Listener that is notified when a BackgroundCommand completes.
*/
public interface BackgroundCommandListener {
/**
* Notification that the given BackgroundCommand has completed.
* @param cmd background command that has completed
*/
public void commandCompleted(BackgroundCommand cmd);
}

View file

@ -0,0 +1,50 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.cmd;
import ghidra.framework.model.DomainObject;
/**
* Interface to define a change made to a domain object.
*
*/
public interface Command {
/**
* Applies the command to the given domain object.
*
* @param obj domain object that this command is to be applied.
*
* @return true if the command applied successfully
*/
public boolean applyTo(DomainObject obj);
/**
* Returns the status message indicating the status of the command.
*
* @return reason for failure, or null if the status of the command
* was successful
*/
public String getStatusMsg();
/**
* Returns the name of this command.
*
* @return the name of this command
*/
public String getName();
}

View file

@ -0,0 +1,100 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.cmd;
import ghidra.framework.model.DomainObject;
import ghidra.util.task.TaskMonitor;
import java.util.ArrayList;
/**
* Compound command to handle multiple background commands.
*/
public class CompoundBackgroundCommand extends BackgroundCommand {
private ArrayList<BackgroundCommand> bkgroundCmdList;
private ArrayList<Command> cmdList;
/**
* Constructor
* @param name name of the command
* @param modal true means the monitor dialog is modal and the command has to
* complete or be canceled before any other action can occur
* @param canCancel true means the command can be canceled
*/
public CompoundBackgroundCommand(String name, boolean modal, boolean canCancel) {
super(name, false, canCancel, modal);
bkgroundCmdList = new ArrayList<BackgroundCommand>();
cmdList = new ArrayList<Command>();
}
/* (non-Javadoc)
* @see ghidra.framework.cmd.BackgroundCommand#applyTo(ghidra.framework.model.DomainObject, ghidra.util.task.TaskMonitor)
*/
@Override
public boolean applyTo(DomainObject obj, TaskMonitor monitor) {
for (int i = 0; i < bkgroundCmdList.size(); i++) {
BackgroundCommand cmd = bkgroundCmdList.get(i);
if (!cmd.applyTo(obj, monitor)) {
setStatusMsg(cmd.getStatusMsg());
return false;
}
}
for (int i = 0; i < cmdList.size(); i++) {
if (monitor.isCancelled()) {
setStatusMsg("Cancelled");
return false;
}
Command cmd = cmdList.get(i);
if (!cmd.applyTo(obj)) {
setStatusMsg(cmd.getStatusMsg());
return false;
}
}
return true;
}
/**
* Add a background command to this compound background command.
*/
public void add(BackgroundCommand cmd) {
bkgroundCmdList.add(cmd);
}
/**
* Add a command to this compound background command.
*/
public void add(Command cmd) {
cmdList.add(cmd);
}
/**
* Get the number of background commands in this compound background
* command.
*/
public int size() {
return bkgroundCmdList.size();
}
/**
* @return true if no sub-commands have been added
*/
public boolean isEmpty() {
return bkgroundCmdList.isEmpty();
}
}

View file

@ -0,0 +1,94 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.cmd;
import ghidra.framework.model.DomainObject;
import java.util.ArrayList;
/**
* Implementation for multiple commands that are done as a unit.
*
* Multiple commands may be added to this one so that multiple changes can be
* applied to the domain object as unit.
*
*
*/
public class CompoundCmd implements Command {
private ArrayList<Command> cmds;
private String statusMsg;
private String name;
/**
* Constructor for CompoundCmd.
*
* @param name the name of the command
*/
public CompoundCmd(String name) {
cmds = new ArrayList<Command>();
this.name = name;
}
/*
* @see ghidra.framework.cmd.Command#applyTo(ghidra.framework.model.DomainObject)
*/
public boolean applyTo(DomainObject obj) {
for(int i = 0;i<cmds.size();i++) {
Command cmd = cmds.get(i);
if (!cmd.applyTo(obj)) {
statusMsg = cmd.getStatusMsg();
return false;
}
}
return true;
}
/*
* @see ghidra.framework.cmd.Command#getStatusMsg()
*/
public String getStatusMsg() {
return statusMsg;
}
/*
* @see ghidra.framework.cmd.Command#getName()
*/
public String getName() {
return name;
}
/**
* Add the given command to this command.
*
* @param cmd command to add to this command
*/
public void add(Command cmd) {
cmds.add(cmd);
}
/**
* Return the number of commands that are part of this compound command.
*
* @return the number of commands that have been added to this one.
*/
public int size() {
return cmds.size();
}
}

View file

@ -0,0 +1,27 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.cmd;
public abstract class MergeableBackgroundCommand extends BackgroundCommand {
public MergeableBackgroundCommand(String name, boolean hasProgress, boolean canCancel,
boolean isModal) {
super(name, hasProgress, canCancel, isModal);
}
/** Merges the properties of the two commands */
public abstract MergeableBackgroundCommand mergeCommands(MergeableBackgroundCommand command);
}

View file

@ -0,0 +1,10 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title></title>
</head>
<body>
Provides classes and interfaces for managing commands to be executed from
a PluginTool. <br>
</body>
</html>

View file

@ -0,0 +1,271 @@
/* ###
* 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.framework.data;
import java.io.IOException;
import db.TerminatedTransactionException;
import ghidra.framework.model.*;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
import ghidra.util.exception.AssertException;
abstract class AbstractTransactionManager {
protected static final int NUM_UNDOS = 50;
private volatile LockingTaskMonitor lockingTaskMonitor;
protected int lockCount = 0;
protected String lockReason;
boolean transactionTerminated;
abstract DomainObjectAdapterDB[] getDomainObjects();
abstract void addTransactionListener(DomainObjectAdapterDB domainObj,
TransactionListener listener);
abstract void removeTransactionListener(DomainObjectAdapterDB domainObj,
TransactionListener listener);
abstract void clearTransactions();
synchronized boolean isLocked() {
return lockCount != 0;
}
final boolean lock(String reason) {
checkLockingTask();
synchronized (this) {
if (getCurrentTransaction() != null && !transactionTerminated) {
return false;
}
if (lockCount == 0) {
for (DomainObjectAdapterDB domainObj : getDomainObjects()) {
if (domainObj.isChanged()) {
domainObj.prepareToSave();
}
}
}
lockReason = reason;
++lockCount;
return true;
}
}
/**
* Attempt to obtain a modification lock on the domain object when generating a
* background snapshot.
* @param domainObj domain object corresponding to snapshot
* @param hasProgress true if monitor has progress indicator
* @param title title to be used for monitor
* @return monitor object if lock obtained successfully, else null which indicates that a
* modification is in process.
*/
final synchronized LockingTaskMonitor lockForSnapshot(DomainObjectAdapterDB domainObj,
boolean hasProgress, String title) {
if (SystemUtilities.isInHeadlessMode()) {
Msg.warn(this, "Snapshot not supported in headless mode");
return null;
}
checkDomainObject(domainObj);
if (lockCount != 0 || getCurrentTransaction() != null || lockingTaskMonitor != null) {
return null;
}
++lockCount; // prevent prepareToSave
try {
if (lock("snapshot")) {
lockingTaskMonitor = new LockingTaskMonitor(domainObj, hasProgress, title);
return lockingTaskMonitor;
}
}
finally {
--lockCount;
}
return null;
}
final void forceLock(boolean rollback, String reason) {
synchronized (this) {
if (lockingTaskMonitor != null) {
lockingTaskMonitor.cancel();
}
}
checkLockingTask();
synchronized (this) {
lockReason = reason;
++lockCount;
}
terminateTransaction(rollback, true);
}
abstract void terminateTransaction(boolean rollback, boolean notify);
final synchronized void unlock() {
if (lockCount == 0)
throw new AssertException();
--lockCount;
}
/**
* Release the modification lock which is associated with the specified LockingTaskHandler.
*/
final synchronized void unlock(LockingTaskMonitor handler) {
if (handler == null) {
throw new IllegalArgumentException("null handler");
}
if (lockCount != 1 || handler != lockingTaskMonitor) {
throw new AssertException();
}
unlock();
lockingTaskMonitor = null;
}
/**
* Block on active locking task.
* Do not invoke this method from within a synchronized block.
*/
final void checkLockingTask() {
synchronized (this) {
if (!isLocked() || lockingTaskMonitor == null) {
return;
}
}
lockingTaskMonitor.waitForTaskCompletion();
}
/**
* Throw lock exception if currently locked
* @throws DomainObjectLockedException if currently locked
*/
final void verifyNoLock() throws DomainObjectLockedException {
if (lockCount != 0) {
throw new DomainObjectLockedException(lockReason);
}
}
void checkDomainObject(DomainObjectAdapterDB object) {
boolean found = false;
for (DomainObjectAdapterDB obj : getDomainObjects()) {
if (obj == object) {
found = true;
break;
}
}
if (!found) {
throw new IllegalArgumentException("invalid domain object");
}
}
final int startTransaction(DomainObjectAdapterDB object, String description,
AbortedTransactionListener listener, boolean notify) {
checkLockingTask();
synchronized (this) {
checkDomainObject(object);
if (getCurrentTransaction() != null && transactionTerminated) {
throw new TerminatedTransactionException();
}
return startTransaction(object, description, listener, false, notify);
}
}
abstract int startTransaction(DomainObjectAdapterDB object, String description,
AbortedTransactionListener listener, boolean force, boolean notify);
abstract Transaction endTransaction(DomainObjectAdapterDB object, int transactionID,
boolean commit, boolean notify);
/**
* Returns the undo stack depth.
* (The number of items on the undo stack)
* This method is for JUnits.
* @return the undo stack depth
*/
abstract int getUndoStackDepth();
abstract boolean canRedo();
abstract boolean canUndo();
abstract String getRedoName();
abstract String getUndoName();
abstract Transaction getCurrentTransaction();
final void redo() throws IOException {
checkLockingTask();
synchronized (this) {
if (getCurrentTransaction() != null) {
throw new IllegalStateException("Can not redo while transaction is open");
}
verifyNoLock();
doRedo(true);
}
}
abstract void doRedo(boolean notify) throws IOException;
final void undo() throws IOException {
checkLockingTask();
synchronized (this) {
if (getCurrentTransaction() != null) {
throw new IllegalStateException("Can not undo while transaction is open: " +
getCurrentTransaction().getDescription());
}
verifyNoLock();
doUndo(true);
}
}
abstract void doUndo(boolean notify) throws IOException;
abstract void clearUndo(boolean notifyListeners);
final synchronized boolean hasTerminatedTransaction() {
return transactionTerminated;
}
final synchronized void close(DomainObjectAdapterDB object) {
checkDomainObject(object);
if (lockingTaskMonitor != null && lockingTaskMonitor.getDomainObject() == object) {
// TODO: Should we wait for lock release after cancel?
lockingTaskMonitor.cancel();
}
verifyNoLock();
doClose(object);
}
abstract void doClose(DomainObjectAdapterDB object);
}

View file

@ -0,0 +1,45 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.data;
import ghidra.util.exception.CancelledException;
/**
* <code>CheckinHandler</code> facilitates application callbacks during
* the check-in of a DomainFile.
*/
public interface CheckinHandler {
/**
* Returns the check-in comment.
* @return the check-in comment
* @throws CancelledException thrown if user cancels the check-in
*/
String getComment() throws CancelledException;
/**
* Returns true if check-out state should be retained.
* @return true if check-out state should be retained
* @throws CancelledException thrown if user cancels the check-in
*/
boolean keepCheckedOut() throws CancelledException;
/**
* Returns true if the system should create a keep file copy of the user's check-in file.
* @throws CancelledException thrown if user cancels the check-in
*/
boolean createKeepFile() throws CancelledException;
}

View file

@ -0,0 +1,214 @@
/* ###
* 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.framework.data;
import java.io.IOException;
import javax.swing.Icon;
import db.DBHandle;
import ghidra.framework.model.ChangeSet;
import ghidra.framework.model.DomainObject;
import ghidra.framework.store.FileSystem;
import ghidra.framework.store.FolderItem;
import ghidra.util.InvalidNameException;
import ghidra.util.classfinder.ExtensionPoint;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
/**
* NOTE: ALL ContentHandler CLASSES MUST END IN "ContentHandler". If not,
* the ClassSearcher will not find them.
*
* <code>ContentHandler</code> defines an application interface for converting
* between a specific domain object implementation and folder item storage.
* This interface also defines a method which provides an appropriate icon
* corresponding to the content.
*/
public interface ContentHandler extends ExtensionPoint {
public static final String UNKNOWN_CONTENT = "Unknown-File";
public static final String MISSING_CONTENT = "Missing-File";
/**
* Creates a new folder item within a specified file-system.
* If fs is versioned, the resulting item is marked as checked-out
* within the versioned file-system. The specified domainObj
* will become associated with the newly created database.
* @param fs the file system in which to create the folder item
* @param userfs file system which contains associated user data
* @param path the path of the folder item
* @param name the name of the new folder item
* @param domainObject the domain object to store in the newly created folder item
* @param monitor the monitor that allows the user to cancel
* @return checkout ID for new item
* @throws IOException if an i/o error occurs
* @throws InvalidNameException if the specified name contains invalid characters
* @throws CancelledException if the user cancels
*/
long createFile(FileSystem fs, FileSystem userfs, String path, String name,
DomainObject domainObject, TaskMonitor monitor) throws IOException,
InvalidNameException, CancelledException;
/**
* Open a folder item for immutable use. If any changes are attempted on the
* returned object, an IllegalStateException state exception may be thrown.
* @param item stored folder item
* @param consumer consumer of the returned object
* @param version version of the stored folder item to be opened.
* DomainFile.DEFAULT_VERSION (-1) should be specified when not opening a specific
* file version.
* @param minChangeVersion the minimum version which should be included in the
* change set for the returned object. A value of -1 indicates the default change
* set.
* @param monitor the monitor that allows the user to cancel
* @return immutable domain object
* @throws IOException if a folder item access error occurs
* @throws CancelledException if operation is cancelled by user
* @throws VersionException if unable to handle file content due to version
* difference which could not be handled.
*/
DomainObjectAdapter getImmutableObject(FolderItem item, Object consumer, int version,
int minChangeVersion, TaskMonitor monitor) throws IOException, CancelledException,
VersionException;
/**
* Open a folder item for read-only use. While changes are permitted on the
* returned object, the original folder item may not be overwritten / updated.
* @param item stored folder item
* @param version version of the stored folder item to be opened.
* DomainFile.DEFAULT_VERSION should be specified when not opening a specific
* file version.
* @param okToUpgrade if true a version upgrade to the content will be done
* if necessary.
* @param consumer consumer of the returned object
* @param monitor the monitor that allows the user to cancel
* @return read-only domain object
* @throws IOException if a folder item access error occurs
* @throws CancelledException if operation is cancelled by user
* @throws VersionException if unable to handle file content due to version
* difference which could not be handled.
*/
DomainObjectAdapter getReadOnlyObject(FolderItem item, int version, boolean okToUpgrade,
Object consumer, TaskMonitor monitor) throws IOException, VersionException,
CancelledException;
/**
* Open a folder item for update. Changes made to the returned object may be
* saved to the original folder item.
* @param item stored folder item
* @param userfs file system which contains associated user data
* @param checkoutId an appropriate checout ID required to update the specified
* folder item.
* @param okToUpgrade if true a version upgrade to the content will be done
* if necessary.
* @param okToRecover if true an attempt to recover any unsaved changes resulting from
* a crash will be attempted.
* @param consumer consumer of the returned object
* @param monitor cancelable task monitor
* @return updateable domain object
* @throws IOException if a folder item access error occurs
* @throws CancelledException if operation is cancelled by user
* @throws VersionException if unable to handle file content due to version
* difference which could not be handled.
*/
DomainObjectAdapter getDomainObject(FolderItem item, FileSystem userfs, long checkoutId,
boolean okToUpgrade, boolean okToRecover, Object consumer, TaskMonitor monitor)
throws IOException, CancelledException, VersionException;
/**
* Returns the object change data which includes changes made to the specified
* olderVersion through to the specified newerVersion.
* @param versionedFolderItem versioned folder item
* @param olderVersion the older version number
* @param newerVersion the newer version number
* @return the set of changes that were made
* @throws VersionException if a database version change prevents reading of data.
* @throws IOException if a folder item access error occurs
*/
ChangeSet getChangeSet(FolderItem versionedFolderItem, int olderVersion, int newerVersion)
throws VersionException, IOException;
/**
* Get an instance of a suitable merge manager to be used during the merge of a Versioned
* object which has been modified by another user since it was last merged
* or checked-out.
* @param resultsObj object to which merge results should be written
* @param sourceObj object which contains user's changes to be merged
* @param originalObj object which corresponds to checked-out version state
* @param latestObj object which corresponds to latest version with which
* the sourceObj must be merged.
* @return merge manager
*/
DomainObjectMergeManager getMergeManager(DomainObject resultsObj, DomainObject sourceObj,
DomainObject originalObj, DomainObject latestObj);
/**
* Returns true if the content type is always private
* (i.e., can not be added to the versioned filesystem).
*/
boolean isPrivateContentType();
/**
* Returns list of unique content-types supported.
* A minimum of one content-type will be returned. If more than one
* is returned, these are considered equivalent aliases.
*/
String getContentType();
/**
* A string that is meant to be presented to the user.
*/
String getContentTypeDisplayString();
/**
* Returns the Icon associated with this handlers content type.
*/
Icon getIcon();
/**
* Returns the name of the default tool that should be used to open this content type
*/
String getDefaultToolName();
/**
* Returns domain object implementation class supported.
*/
Class<? extends DomainObject> getDomainObjectClass();
/**
* Create user data file associated with existing content.
* This facilitates the lazy creation of the user data file.
* @param associatedDomainObj associated domain object corresponding to this content handler
* @param userDbh user data handle
* @param userfs private user data filesystem
* @param monitor task monitor
* @throws IOException if an access error occurs
* @throws CancelledException if operation is cancelled by user
*/
void saveUserDataFile(DomainObject associatedDomainObj, DBHandle userDbh, FileSystem userfs,
TaskMonitor monitor) throws CancelledException, IOException;
/**
* Remove user data file associated with an existing folder item.
* @param item folder item
* @param userFilesystem
* @throws IOException if an access error occurs
*/
void removeUserDataFile(FolderItem item, FileSystem userFilesystem) throws IOException;
}

View file

@ -0,0 +1,201 @@
/* ###
* 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.framework.data;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import ghidra.GhidraApplicationLayout;
import ghidra.GhidraLaunchable;
import ghidra.framework.store.local.*;
public class ConvertFileSystem implements GhidraLaunchable {
@Override
public void launch(GhidraApplicationLayout layout, String[] args) {
MessageListener msgListener = string -> System.out.println(string);
try {
File dir = getDir(args.length == 0 ? null : args[0]);
if (dir.getName().endsWith(".rep")) {
convertProject(dir, msgListener);
}
else {
convertRepositories(dir, msgListener);
}
}
catch (ConvertFileSystemException e) {
System.err.println(e.getMessage());
}
}
public static interface MessageListener {
public void println(String string);
}
public static class ConvertFileSystemException extends IOException {
public ConvertFileSystemException() {
super();
}
public ConvertFileSystemException(String message) {
super(message);
}
public ConvertFileSystemException(String message, Throwable cause) {
super(message, cause);
}
}
private static File getDir(String path) throws ConvertFileSystemException {
if (path == null) {
throw new ConvertFileSystemException(
"Must specify a project (*.rep) or server repositories directory");
}
File dir = new File(path);
if (!dir.isDirectory()) {
throw new ConvertFileSystemException(
"Invalid project or repositories directory specified: " + dir);
}
return dir;
}
private static void convertRepositories(File dir, MessageListener msgListener)
throws ConvertFileSystemException {
File file = new File(dir, "~admin");
if (!file.isDirectory()) {
throw new ConvertFileSystemException(
"Invalid repositories directory specified (~admin not found): " + dir);
}
file = new File(dir, "users");
if (!file.isFile()) {
throw new ConvertFileSystemException(
"Invalid repositories directory specified (users not found): " + dir);
}
file = new File(dir, "server.log");
if (!file.isFile()) {
throw new ConvertFileSystemException(
"Invalid repositories directory specified (server.log not found): " + dir);
}
List<File> repoDirs = new ArrayList<>();
for (File f : dir.listFiles()) {
// look for repository directories
if (!f.isDirectory() || LocalFileSystem.isHiddenDirName(f.getName())) {
continue;
}
repoDirs.add(f);
}
msgListener.println("Converting " + repoDirs.size() + " repositories...");
for (File repoDir : repoDirs) {
convertRepo(repoDir, msgListener);
}
}
private static void convertRepo(File repoDir, MessageListener msgListener)
throws ConvertFileSystemException {
try {
LocalFileSystem fs = LocalFileSystem.getLocalFileSystem(repoDir.getAbsolutePath(),
false, true, false, false);
if (fs instanceof MangledLocalFileSystem) {
MangledLocalFileSystem mfs = (MangledLocalFileSystem) fs;
msgListener.println("Converting repository directory: " + repoDir);
mfs.convertToIndexedLocalFileSystem();
}
else if ((fs instanceof IndexedLocalFileSystem) &&
((IndexedLocalFileSystem) fs).getIndexImplementationVersion() < IndexedLocalFileSystem.LATEST_INDEX_VERSION) {
msgListener.println("Rebuilding Index for repository directory (" +
fs.getItemCount() + " files): " + repoDir);
fs.dispose();
IndexedV1LocalFileSystem.rebuild(repoDir);
}
else {
msgListener.println(
"Repository directory has previously been converted: " + repoDir);
}
}
catch (IOException e) {
if (e instanceof ConvertFileSystemException) {
throw (ConvertFileSystemException) e;
}
throw new ConvertFileSystemException(
"Error converting repository directory (" + repoDir + "): " + e.getMessage(), e);
}
}
private static void convertProjectDir(File dir, String dirType, MessageListener msgListener)
throws ConvertFileSystemException {
try {
LocalFileSystem fs = LocalFileSystem.getLocalFileSystem(dir.getAbsolutePath(), false,
false, false, false);
if (fs instanceof MangledLocalFileSystem) {
MangledLocalFileSystem mfs = (MangledLocalFileSystem) fs;
msgListener.println("Converting " + dirType + " directory: " + dir);
mfs.convertToIndexedLocalFileSystem();
}
else if ((fs instanceof IndexedLocalFileSystem) &&
((IndexedLocalFileSystem) fs).getIndexImplementationVersion() < IndexedLocalFileSystem.LATEST_INDEX_VERSION) {
msgListener.println("Rebuilding Index for " + dirType + " directory (" +
fs.getItemCount() + " files): " + dir);
fs.dispose();
IndexedV1LocalFileSystem.rebuild(dir);
}
else if (fs instanceof IndexedLocalFileSystem) {
msgListener.println(
"Project " + dirType + " directory has previously been converted: " + dir);
}
}
catch (IOException e) {
if (e instanceof ConvertFileSystemException) {
throw (ConvertFileSystemException) e;
}
throw new ConvertFileSystemException("Error converting project " + dirType +
" directory (" + dir + "): " + e.getMessage(), e);
}
}
public static void convertProject(File dir, MessageListener msgListener)
throws ConvertFileSystemException {
File projectPropertiesFile = new File(dir, "project.prp");
if (!projectPropertiesFile.isFile()) {
throw new ConvertFileSystemException(
"Invalid project directory specified (project.prp not found): " + dir);
}
File dataDir = new File(dir, "data");
if (!dataDir.isDirectory()) {
dataDir = new File(dir, "idata"); // allow index upgrade
}
if (!dataDir.isDirectory()) {
throw new ConvertFileSystemException(
"Invalid project directory specified (project data not found): " + dir);
}
convertProjectDir(dataDir, "data", msgListener);
File versionedDir = new File(dir, "versioned");
if (versionedDir.isDirectory()) {
convertProjectDir(versionedDir, "versioned data", msgListener);
}
File userDir = new File(dir, "user");
if (userDir.isDirectory()) {
convertProjectDir(userDir, "user data", msgListener);
}
}
}

View file

@ -0,0 +1,189 @@
/* ###
* 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.framework.data;
import java.io.IOException;
import db.DBHandle;
import db.buffers.BufferFile;
import db.buffers.ManagedBufferFile;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainObject;
import ghidra.framework.store.*;
import ghidra.util.*;
import ghidra.util.exception.AssertException;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
/**
* <code>DBContentHandler</code> provides an abstract ContentHandler for
* domain object content which is stored within a database file.
* This class provides helper methods for working with database files.
*/
public abstract class DBContentHandler implements ContentHandler {
/**
* Create a new database file from an open database handle.
* If fs is versioned, the resulting item is marked as checked-out
* within the versioned file-system. The specified domainObj
* will become associated with the newly created database.
* @param domainObj domain object
* @param contentType the content type
* @param fs the domain object file system
* @param path the path of the new file
* @param name the name of the new file
* @param monitor the monitor that allows the user to cancel
* @return checkout ID for new file
* @throws InvalidNameException if the name contains invalid characters
* @throws CancelledException if the user cancels the operation
* @throws IOException if an i/o error occurs
*/
protected final long createFile(DomainObjectAdapterDB domainObj, String contentType,
FileSystem fs, String path, String name, TaskMonitor monitor)
throws InvalidNameException, CancelledException, IOException {
DBHandle dbh = domainObj.getDBHandle();
ManagedBufferFile bf =
fs.createDatabase(path, name, FileIDFactory.createFileID(), contentType,
dbh.getBufferSize(), SystemUtilities.getUserName(), null);
long checkoutId = bf.getCheckinID(); // item remains checked-out after saveAs
boolean success = false;
try {
dbh.saveAs(bf, true, monitor);
success = true;
}
finally {
if (!success) {
try {
bf.delete();
}
catch (IOException e) {
}
abortCreate(fs, path, name, checkoutId);
}
}
return checkoutId;
}
private void abortCreate(FileSystem fs, String path, String name, long checkoutId) {
try {
FolderItem item = fs.getItem(path, name);
if (item != null) {
if (checkoutId != FolderItem.DEFAULT_CHECKOUT_ID) {
item.terminateCheckout(checkoutId, false);
}
item.delete(-1, SystemUtilities.getUserName());
}
}
catch (IOException e) {
// Cleanup failed
}
}
/**
* Return user data content type corresponding to associatedContentType.
*/
private static String getUserDataContentType(String associatedContentType) {
return associatedContentType + "UserData";
}
/**
* @see ghidra.framework.data.ContentHandler#saveUserDataFile(ghidra.framework.model.DomainObject, db.DBHandle, ghidra.framework.store.FileSystem, ghidra.util.task.TaskMonitor)
*/
@Override
public final void saveUserDataFile(DomainObject domainObj, DBHandle userDbh, FileSystem userfs,
TaskMonitor monitor) throws CancelledException, IOException {
if (userfs.isVersioned()) {
throw new IllegalArgumentException("User data file-system may not be versioned");
}
String associatedContentType = getContentType();
DomainFile associatedDf = domainObj.getDomainFile();
if (associatedDf == null) {
throw new IllegalStateException("associated " + associatedContentType +
" file must be saved before user data can be saved");
}
String associatedFileID = associatedDf.getFileID();
if (associatedFileID == null) {
Msg.error(this, associatedContentType + " '" + associatedDf.getName() +
"' has not been assigned a file ID, user settings can not be saved!");
return;
}
String path = "/";
String name = ProjectFileManager.getUserDataFilename(associatedFileID);
BufferFile bf = null;
boolean success = false;
try {
bf =
userfs.createDatabase(path, name, FileIDFactory.createFileID(),
getUserDataContentType(associatedContentType), userDbh.getBufferSize(),
SystemUtilities.getUserName(), null);
userDbh.saveAs(bf, true, monitor);
success = true;
}
catch (InvalidNameException e) {
throw new AssertException("Unexpected Error", e);
}
finally {
if (bf != null && !success) {
try {
bf.delete();
}
catch (IOException e) {
}
abortCreate(userfs, path, name, FolderItem.DEFAULT_CHECKOUT_ID);
}
}
}
/**
* @see ghidra.framework.data.ContentHandler#removeUserDataFile(ghidra.framework.store.FolderItem, ghidra.framework.store.FileSystem)
*/
@Override
public final void removeUserDataFile(FolderItem associatedItem, FileSystem userfs)
throws IOException {
String path = "/";
String name = ProjectFileManager.getUserDataFilename(associatedItem.getFileID());
FolderItem item = userfs.getItem(path, name);
if (item != null) {
item.delete(-1, null);
}
}
/**
* Open user data file associatedDbh
* @param associatedFileID
* @param associatedContentType
* @param userfs
* @param monitor
* @return user data file database handle
* @throws IOException
* @throws CancelledException
*/
protected final DBHandle openAssociatedUserFile(String associatedFileID,
String associatedContentType, FileSystem userfs, TaskMonitor monitor)
throws IOException, CancelledException {
String path = "/";
String name = ProjectFileManager.getUserDataFilename(associatedFileID);
FolderItem item = userfs.getItem(path, name);
if (item == null || !(item instanceof DatabaseItem) ||
!getUserDataContentType(associatedContentType).equals(item.getContentType())) {
return null;
}
DatabaseItem dbItem = (DatabaseItem) item;
BufferFile bf = dbItem.openForUpdate(FolderItem.DEFAULT_CHECKOUT_ID);
return new DBHandle(bf, false, monitor);
}
}

View file

@ -0,0 +1,53 @@
/* ###
* 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.framework.data;
import ghidra.framework.model.DomainFile;
import ghidra.util.exception.CancelledException;
/**
* <code>DefaultCheckinHandler</code> provides a simple
* check-in handler for use with
* {@link DomainFile#checkin(CheckinHandler, boolean, ghidra.util.task.TaskMonitor)}
*/
public class DefaultCheckinHandler implements CheckinHandler {
private final String comment;
private final boolean keepCheckedOut;
private final boolean createKeepFile;
public DefaultCheckinHandler(String comment, boolean keepCheckedOut, boolean createKeepFile) {
this.comment = comment;
this.keepCheckedOut = keepCheckedOut;
this.createKeepFile = createKeepFile;
}
@Override
public String getComment() throws CancelledException {
return comment;
}
@Override
public boolean keepCheckedOut() throws CancelledException {
return keepCheckedOut;
}
@Override
public boolean createKeepFile() throws CancelledException {
return createKeepFile;
}
}

View file

@ -0,0 +1,238 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.data;
import ghidra.framework.model.*;
import ghidra.framework.store.FolderItem;
import ghidra.util.Msg;
import ghidra.util.task.TaskMonitor;
import java.io.IOException;
import java.util.HashMap;
/**
* Helper class to maintain mapping of fileID's to DomainFile's.
*/
class DomainFileIndex implements DomainFolderChangeListener {
private ProjectFileManager projectData;
private HashMap<String, String> fileIdToPathIndex = new HashMap<String, String>();
DomainFileIndex(ProjectFileManager projectData) {
this.projectData = projectData;
}
// NOTE: file index map will generally be incomplete since it will only be a partial map
// based upon "visited" domain folders. If an ID is not found within the map, a scan of the
// domain folders may be required
private void updateFileEntry(GhidraFile df) {
updateFileEntry(df, df.getFileID(), df.getPathname());
}
void updateFileEntry(GhidraFileData dfd) {
updateFileEntry(dfd.getDomainFile(), dfd.getFileID(), dfd.getPathname());
}
void removeFileEntry(String fileID) {
fileIdToPathIndex.remove(fileID);
}
private void updateFileEntry(GhidraFile df, String id, String newPath) {
if (id != null) {
String oldPath = fileIdToPathIndex.get(id);
if (oldPath == null) {
fileIdToPathIndex.put(id, newPath);
}
else if (oldPath.equals(newPath)) {
return;
}
else {
GhidraFile oldDf = (GhidraFile) projectData.getFile(oldPath);
if (oldDf == null) {
fileIdToPathIndex.put(id, newPath);
}
else {
reconcileFileIDConflict(df, oldDf);
}
}
}
}
private void reconcileFileIDConflict(GhidraFile df1, GhidraFile df2) {
try {
String path1 = df1.getPathname();
String path2 = df2.getPathname();
if (!df1.isCheckedOut() && !df1.isVersioned()) {
Msg.warn(this, "WARNING! changing file-ID for " + path1);
df1.resetFileID();
}
else if (!df2.isCheckedOut() && !df2.isVersioned()) {
Msg.warn(this, "WARNING! changing file-ID for " + path2);
df2.resetFileID();
}
else {
// Unable to resolve conflict
Msg.error(this, "The following project files have conflicting file-IDs!\n" + path1 +
"\n" + path2);
}
fileIdToPathIndex.put(df1.getFileID(), path1);
fileIdToPathIndex.put(df2.getFileID(), path2);
}
catch (IOException e) {
Msg.error(this, "Error while resolving file IDs", e);
e.printStackTrace();
}
}
DomainFile getFileByID(String fileID) {
TaskMonitor monitor = projectData.getProjectDisposalMonitor();
String filePath = fileIdToPathIndex.get(fileID);
if (filePath != null) {
return projectData.getFile(filePath);
}
boolean unsupportedOperation = false;
IOException exc = null;
try {
FolderItem item = projectData.getPrivateFileSystem().getItem(fileID);
if (item != null) {
return projectData.getFile(item.getPathName());
}
}
catch (UnsupportedOperationException e) {
unsupportedOperation = true;
}
catch (IOException e) {
exc = e;
}
try {
FolderItem item = projectData.getVersionedFileSystem().getItem(fileID);
if (item != null) {
return projectData.getFile(item.getPathName());
}
return null;
}
catch (UnsupportedOperationException e) {
unsupportedOperation = true;
}
catch (IOException e) {
exc = e;
}
if (unsupportedOperation) {
// if file-system get item by File-ID unsupported use brute force search
try {
return findFileByID(projectData.getRootFolderData(), fileID, monitor);
}
catch (IOException e) {
exc = e;
}
}
if (exc != null) {
Msg.error(this, "File index lookup failed due to error: " + exc.getMessage());
}
return null;
}
private DomainFile findFileByID(GhidraFolderData folderData, String fileID, TaskMonitor monitor)
throws IOException {
if (!folderData.visited()) {
// force files to be added to index and check index map
folderData.refresh(false, true, monitor);
String filePath = fileIdToPathIndex.get(fileID);
if (filePath != null) {
return projectData.getFile(filePath);
}
}
for (String name : folderData.getFolderNames()) {
if (monitor.isCancelled()) {
return null;
}
GhidraFolderData subfolderData = folderData.getFolderData(name, true);
if (subfolderData != null) {
DomainFile df = findFileByID(subfolderData, fileID, monitor);
if (df != null) {
return df;
}
}
}
// perform extra check to handle potential race condition
String filePath = fileIdToPathIndex.get(fileID);
if (filePath != null) {
return projectData.getFile(filePath);
}
return null;
}
public void domainFileAdded(DomainFile file) {
updateFileEntry((GhidraFile) file);
}
public void domainFileMoved(DomainFile file, DomainFolder oldParent, String oldName) {
updateFileEntry((GhidraFile) file);
}
public void domainFileObjectClosed(DomainFile file, DomainObject object) {
// no-op
}
public void domainFileObjectOpenedForUpdate(DomainFile file, DomainObject object) {
// no-op
}
public void domainFileObjectReplaced(DomainFile file, DomainObject oldObject) {
// no-op
}
public void domainFileRemoved(DomainFolder parent, String name, String fileID) {
fileIdToPathIndex.remove(fileID);
}
public void domainFileRenamed(DomainFile file, String oldName) {
updateFileEntry((GhidraFile) file);
}
public void domainFileStatusChanged(DomainFile file, boolean fileIDset) {
if (fileIDset) {
updateFileEntry((GhidraFile) file);
}
}
public void domainFolderAdded(DomainFolder folder) {
// no-op
}
public void domainFolderMoved(DomainFolder folder, DomainFolder oldParent) {
// no-op
}
public void domainFolderRemoved(DomainFolder parent, String name) {
// no-op
}
public void domainFolderRenamed(DomainFolder folder, String oldName) {
// no-op
}
public void domainFolderSetActive(DomainFolder folder) {
// no-op
}
}

View file

@ -0,0 +1,495 @@
/* ###
* 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.framework.data;
import java.io.File;
import java.io.IOException;
import java.util.*;
import javax.swing.Icon;
import ghidra.framework.model.*;
import ghidra.framework.store.ItemCheckoutStatus;
import ghidra.framework.store.Version;
import ghidra.framework.store.db.PackedDatabase;
import ghidra.util.InvalidNameException;
import ghidra.util.ReadOnlyException;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
/**
* Implements the DomainFile interface for DomainObjects that are not currently
* associated with any real DomainFile. This class enforces the sharing of
* objects between tools. After the first tool gets the implementation, all
* other gets() just get the same instance. This class also keeps track of
* which tools are using a its domain object.
*/
public class DomainFileProxy implements DomainFile {
private DomainObjectAdapter domainObj;
private ProjectLocator projectLocation;
private String name;
private int version;
private String parentPath;
private long lastModified = 0;
private String fileID;
public DomainFileProxy(String name, DomainObjectAdapter doa) {
domainObj = doa;
this.name = name;
doa.setDomainFile(this);
TransientDataManager.addTransient(this);
version = DomainFile.DEFAULT_VERSION;
}
DomainFileProxy(String name, String parentPath, DomainObjectAdapter doa, int version,
String fileID, ProjectLocator projectLocation) {
this(name, doa);
this.parentPath = parentPath;
this.version = version;
this.fileID = fileID;
this.projectLocation = projectLocation;
}
@Override
public boolean exists() {
return false;
}
@Override
public synchronized DomainFile setName(String newName) {
// synchronization employed to ensure thread visibility when name changed
this.name = newName;
return this;
}
@Override
public ProjectLocator getProjectLocator() {
return projectLocation;
}
@Override
public long length() throws IOException {
// TODO not sure what we should report here
return 0;
}
@Override
public boolean isReadOnly() {
return true;
}
@Override
public void setReadOnly(boolean state) {
throw new UnsupportedOperationException("setReadOnly() not suppported on DomainFileProxy");
}
@Override
public boolean isInWritableProject() {
return false;
}
@Override
public String getPathname() {
if (parentPath == null || parentPath.equals(DomainFolder.SEPARATOR)) {
return DomainFolder.SEPARATOR + getName();
}
return parentPath + DomainFolder.SEPARATOR + getName();
}
@Override
public int compareTo(DomainFile df) {
return getName().compareToIgnoreCase(df.getName());
}
@Override
public String toString() {
String s = getPathname();
if (projectLocation != null) {
s = projectLocation.getName() + ":" + s;
}
if (version != DomainFile.DEFAULT_VERSION) {
s += "@" + version;
}
return s;
}
@Override
public synchronized String getName() {
return name;
}
@Override
public String getFileID() {
return fileID;
}
@Override
public String getContentType() {
DomainObjectAdapter dobj = getDomainObject();
if (dobj != null) {
try {
ContentHandler ch = DomainObjectAdapter.getContentHandler(dobj);
return ch.getContentType();
}
catch (IOException e) {
// ignore missing content handler
}
}
return "Unknown File";
}
@Override
public Class<? extends DomainObject> getDomainObjectClass() {
DomainObjectAdapter dobj = getDomainObject();
return dobj != null ? dobj.getClass() : null;
}
@Override
public DomainFolder getParent() {
return null;
}
synchronized void setLastModified(long time) {
// TODO: this method should never be called and should throw an exception
lastModified = time;
}
@Override
public synchronized long getLastModifiedTime() {
// TODO: this method should return 0
return lastModified;
}
@Override
public void save(TaskMonitor monitor) throws IOException {
throw new ReadOnlyException("Location does not exist for a save operation!");
}
@Override
public boolean canSave() {
return false;
}
@Override
public boolean canRecover() {
return false;
}
@Override
public boolean takeRecoverySnapshot() {
throw new UnsupportedOperationException("Recovery snapshot not supported for proxy file");
}
public boolean isInUse() {
return true;
}
public boolean isUsedExclusivelyBy(Object consumer) {
DomainObjectAdapter dobj = getDomainObject();
return dobj != null ? dobj.isUsedExclusivelyBy(consumer) : false;
}
@Override
public ArrayList<?> getConsumers() {
DomainObjectAdapter dobj = getDomainObject();
if (dobj != null) {
return dobj.getConsumerList();
}
return new ArrayList<>();
}
void clearDomainObj() {
synchronized (this) {
// synchronization employed to ensure thread visibility when domainObj cleared
domainObj = null;
}
TransientDataManager.removeTransient(this);
}
void release(Object consumer) {
DomainObjectAdapter dobj = getDomainObject();
if (dobj != null) {
try {
dobj.release(consumer);
}
catch (IllegalArgumentException e) {
}
}
}
@Override
public int hashCode() {
return super.hashCode();
}
@Override
public boolean equals(Object obj) {
return obj == this;
}
public boolean isUsedBy(Object consumer) {
DomainObjectAdapter dobj = getDomainObject();
if (dobj != null) {
return dobj.isUsedBy(consumer);
}
return false;
}
@Override
public void addToVersionControl(String comment, boolean keepCheckedOut, TaskMonitor monitor)
throws IOException, CancelledException {
throw new UnsupportedOperationException("Repository operations not supported");
}
@Override
public boolean isVersionControlSupported() {
return false;
}
@Override
public boolean canAddToRepository() {
return false;
}
@Override
public boolean isBusy() {
DomainObjectAdapter dobj = getDomainObject();
return dobj != null && !dobj.canLock();
}
@Override
public boolean canCheckout() {
return false;
}
@Override
public boolean canCheckin() {
return false;
}
@Override
public boolean canMerge() {
return false;
}
@Override
public boolean checkout(boolean exclusive, TaskMonitor monitor) {
throw new UnsupportedOperationException("Repository operations not supported");
}
@Override
public void checkin(CheckinHandler checkinHandler, boolean okToUpgrade, TaskMonitor monitor)
throws IOException, VersionException, CancelledException {
throw new UnsupportedOperationException("Repository operations not supported");
}
@Override
public void merge(boolean okToUpgrade, TaskMonitor monitor) {
throw new UnsupportedOperationException("Repository operations not supported");
}
@Override
public DomainFile copyTo(DomainFolder newParent, TaskMonitor monitor)
throws IOException, CancelledException {
DomainObjectAdapter dobj = getDomainObject();
if (dobj == null) {
throw new ClosedException();
}
try {
return newParent.createFile(getName(), dobj, monitor);
}
catch (InvalidNameException e) {
throw new AssertException("Unexpected error", e);
}
}
/**
* @see ghidra.framework.model.DomainFile#copyVersionTo(int, ghidra.framework.model.DomainFolder, ghidra.util.task.TaskMonitor)
*/
@Override
public DomainFile copyVersionTo(int version, DomainFolder destFolder, TaskMonitor monitor)
throws IOException, CancelledException {
throw new UnsupportedOperationException("copyVersionTo unsupported for DomainFileProxy");
}
@Override
public synchronized void delete() throws IOException {
if (domainObj != null) {
throw new FileInUseException("Proxy file for " + name + " is in use");
}
}
@Override
public void delete(int fileVersion) throws IOException {
throw new UnsupportedOperationException("delete(version) unsupported for DomainFileProxy");
}
@Override
public int getLatestVersion() {
return 0;
}
@Override
public boolean isLatestVersion() {
return version == DEFAULT_VERSION;
}
@Override
public int getVersion() {
return version;
}
@Override
public Version[] getVersionHistory() throws IOException {
return new Version[0];
}
@Override
public boolean isCheckedOut() {
return false;
}
@Override
public boolean isCheckedOutExclusive() {
return false;
}
@Override
public DomainFile moveTo(DomainFolder newParent) throws IOException {
throw new UnsupportedOperationException("Cannot move a proxy file - must call copyTo()");
}
@Override
public void undoCheckout(boolean keep) throws IOException {
throw new UnsupportedOperationException("undoCheckout() unsupported for DomainFileProxy");
}
@Override
public ChangeSet getChangesByOthersSinceCheckout() throws IOException {
return null;
}
private synchronized DomainObjectAdapter getDomainObject() {
return domainObj;
}
@Override
public DomainObject getDomainObject(Object consumer, boolean okToUpgrade, boolean okToRecover,
TaskMonitor monitor) throws VersionException, IOException {
return getOpenedDomainObject(consumer);
}
@Override
public DomainObject getOpenedDomainObject(Object consumer) {
DomainObjectAdapter dobj = getDomainObject();
if (dobj != null) {
dobj.addConsumer(consumer);
}
return dobj;
}
@Override
public boolean isVersioned() {
return false;
}
@Override
public synchronized void packFile(File file, TaskMonitor monitor)
throws IOException, CancelledException {
if (!(domainObj instanceof DomainObjectAdapterDB)) {
throw new UnsupportedOperationException("packFile() only valid for Database files");
}
DomainObjectAdapterDB dbObj = (DomainObjectAdapterDB) domainObj;
ContentHandler ch = DomainObjectAdapter.getContentHandler(domainObj);
PackedDatabase.packDatabase(dbObj.getDBHandle(), dbObj.getName(), ch.getContentType(), file,
monitor);
}
@Override
public Icon getIcon(boolean disabled) {
return null;
}
@Override
public DomainObject getImmutableDomainObject(Object consumer, int fileVersion,
TaskMonitor monitor) throws VersionException, IOException, CancelledException {
throw new UnsupportedOperationException(
"getImmutableDomainObject unsupported for DomainFileProxy");
}
@Override
public DomainObject getReadOnlyDomainObject(Object consumer, int fileVersion,
TaskMonitor monitor) throws VersionException, IOException, CancelledException {
if (fileVersion != DEFAULT_VERSION && fileVersion != this.version) {
throw new AssertException("Version mismatch on DomainFileProxy");
}
return getOpenedDomainObject(consumer);
}
@Override
public boolean isHijacked() {
return false;
}
@Override
public boolean modifiedSinceCheckout() {
return false;
}
@Override
public boolean isChanged() {
DomainObjectAdapter dobj = getDomainObject();
if (dobj != null) {
return dobj.isChanged();
}
return false;
}
@Override
public boolean isOpen() {
DomainObjectAdapter dobj = getDomainObject();
return dobj != null && !dobj.isClosed();
}
@Override
public void terminateCheckout(long checkoutId) throws IOException {
throw new UnsupportedOperationException(
"terminateCheckout() unsupported for DomainFileProxy");
}
@Override
public ItemCheckoutStatus[] getCheckouts() throws IOException {
throw new UnsupportedOperationException("getCheckouts() unsupported for DomainFileProxy");
}
@Override
public ItemCheckoutStatus getCheckoutStatus() throws IOException {
throw new UnsupportedOperationException(
"getCheckoutStatus() unsupported for DomainFileProxy");
}
@Override
public Map<String, String> getMetadata() {
DomainObjectAdapter dobj = getDomainObject();
if (dobj != null) {
dobj.getMetadata();
}
return new HashMap<>();
}
}

View file

@ -0,0 +1,219 @@
/* ###
* 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.framework.data;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import ghidra.framework.model.*;
import ghidra.util.SystemUtilities;
class DomainFolderChangeListenerList implements DomainFolderChangeListener {
private DomainFileIndex fileIndex;
/** CopyOnWriteArrayList prevents the need for synchronization */
private List<DomainFolderChangeListener> list =
new CopyOnWriteArrayList<>();
DomainFolderChangeListenerList(DomainFileIndex fileIndex) {
this.fileIndex = fileIndex;
}
void addListener(DomainFolderChangeListener listener) {
list.add(listener);
}
void removeListener(DomainFolderChangeListener listener) {
list.remove(listener);
}
@Override
public void domainFolderAdded(final DomainFolder folder) {
fileIndex.domainFolderAdded(folder);
if (list.isEmpty()) {
return;
}
SystemUtilities.runSwingLater(() -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFolderAdded(folder);
}
});
}
@Override
public void domainFileAdded(final DomainFile file) {
fileIndex.domainFileAdded(file);
if (list.isEmpty()) {
return;
}
SystemUtilities.runSwingLater(() -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFileAdded(file);
}
});
}
@Override
public void domainFolderRemoved(final DomainFolder parent, final String name) {
fileIndex.domainFolderRemoved(parent, name);
if (list.isEmpty()) {
return;
}
SystemUtilities.runSwingLater(() -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFolderRemoved(parent, name);
}
});
}
@Override
public void domainFileRemoved(final DomainFolder parent, final String name,
final String fileID) {
fileIndex.domainFileRemoved(parent, name, fileID);
if (list.isEmpty()) {
return;
}
SystemUtilities.runSwingLater(() -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFileRemoved(parent, name, fileID);
}
});
}
@Override
public void domainFolderRenamed(final DomainFolder folder, final String oldName) {
fileIndex.domainFolderRenamed(folder, oldName);
if (list.isEmpty()) {
return;
}
SystemUtilities.runSwingLater(() -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFolderRenamed(folder, oldName);
}
});
}
@Override
public void domainFileRenamed(final DomainFile file, final String oldName) {
fileIndex.domainFileRenamed(file, oldName);
if (list.isEmpty()) {
return;
}
SystemUtilities.runSwingLater(() -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFileRenamed(file, oldName);
}
});
}
@Override
public void domainFolderMoved(final DomainFolder folder, final DomainFolder oldParent) {
fileIndex.domainFolderMoved(folder, oldParent);
if (list.isEmpty()) {
return;
}
SystemUtilities.runSwingLater(() -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFolderMoved(folder, oldParent);
}
});
}
@Override
public void domainFileMoved(final DomainFile file, final DomainFolder oldParent,
final String oldName) {
fileIndex.domainFileMoved(file, oldParent, oldName);
if (list.isEmpty()) {
return;
}
SystemUtilities.runSwingLater(() -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFileMoved(file, oldParent, oldName);
}
});
}
@Override
public void domainFolderSetActive(final DomainFolder folder) {
fileIndex.domainFolderSetActive(folder);
if (list.isEmpty()) {
return;
}
SystemUtilities.runSwingLater(() -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFolderSetActive(folder);
}
});
}
@Override
public void domainFileStatusChanged(final DomainFile file, final boolean fileIDset) {
fileIndex.domainFileStatusChanged(file, fileIDset);
if (list.isEmpty()) {
return;
}
SystemUtilities.runSwingLater(() -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFileStatusChanged(file, fileIDset);
}
});
}
@Override
public void domainFileObjectOpenedForUpdate(final DomainFile file, final DomainObject object) {
fileIndex.domainFileObjectOpenedForUpdate(file, object);
if (list.isEmpty()) {
return;
}
SystemUtilities.runSwingLater(() -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFileObjectOpenedForUpdate(file, object);
}
});
}
@Override
public void domainFileObjectClosed(final DomainFile file, final DomainObject object) {
fileIndex.domainFileObjectClosed(file, object);
if (list.isEmpty()) {
return;
}
SystemUtilities.runSwingLater(() -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFileObjectClosed(file, object);
}
});
}
@Override
public void domainFileObjectReplaced(final DomainFile file, final DomainObject oldObject) {
fileIndex.domainFileObjectReplaced(file, oldObject);
if (list.isEmpty()) {
return;
}
Runnable r = () -> {
for (DomainFolderChangeListener listener : list) {
listener.domainFileObjectReplaced(file, oldObject);
}
};
SystemUtilities.runSwingNow(r);
}
public void clearAll() {
list.clear();
}
}

View file

@ -0,0 +1,523 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.data;
import ghidra.framework.model.*;
import ghidra.framework.store.FileSystem;
import ghidra.framework.store.LockException;
import ghidra.util.Lock;
import ghidra.util.classfinder.ClassSearcher;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/**
* An abstract class that provides default behavior for
* DomainObject(s), specifically it handles listeners and
* change status; the derived class must provide the
* getDescription() method.
*/
public abstract class DomainObjectAdapter implements DomainObject {
protected final static String DEFAULT_NAME = "untitled";
private static Class<?> defaultDomainObjClass; // Domain object implementation mapped to unknown content type
private static HashMap<String, ContentHandler> contentHandlerTypeMap; // maps content-type string to handler
private static HashMap<Class<?>, ContentHandler> contentHandlerClassMap; // maps domain object class to handler
private static ChangeListener contentHandlerUpdateListener = new ChangeListener() {
@Override
public void stateChanged(ChangeEvent e) {
getContentHandlers();
}
};
protected String name;
private DomainFile domainFile;
private DomainObjectChangeSupport docs;
protected Map<EventQueueID, DomainObjectChangeSupport> changeSupportMap =
new ConcurrentHashMap<EventQueueID, DomainObjectChangeSupport>();
private volatile boolean eventsEnabled = true;
private Set<DomainObjectClosedListener> closeListeners =
new HashSet<DomainObjectClosedListener>();
private ArrayList<Object> consumers;
protected Map<String, String> metadata = new LinkedHashMap<String, String>();
// a flag indicating whether the domain object has changed
// any methods of this domain object which cause its state to
// to change must set this flag to true
protected boolean changed = false;
// a flag indicating that this object is temporary
protected boolean temporary = false;
protected Lock lock = new Lock("Domain Object");
private long modificationNumber = 1;
/**
* Construct a new DomainObjectAdapter.
* If construction of this object fails, be sure to release with consumer.
* @param name name of the object
* @param timeInterval the time (in milliseconds) to wait before the
* event queue is flushed. If a new event comes in before the time expires,
* the timer is reset.
* @param bufsize initial size of event buffer
* @param consumer the object that created this domain object
*/
protected DomainObjectAdapter(String name, int timeInterval, int bufsize, Object consumer) {
if (consumer == null) {
throw new IllegalArgumentException("Consumer must not be null");
}
this.name = name;
docs = new DomainObjectChangeSupport(this, timeInterval, bufsize, lock);
consumers = new ArrayList<Object>();
consumers.add(consumer);
if (!UserData.class.isAssignableFrom(getClass())) {
// UserData instances do not utilize DomainFile storage
domainFile = new DomainFileProxy(name, this);
}
}
/**
* @see ghidra.framework.model.DomainObject#release(java.lang.Object)
*/
@Override
public void release(Object consumer) {
synchronized (consumers) {
if (!consumers.remove(consumer)) {
throw new IllegalArgumentException(
"Attempted to release domain object with unknown consumer: " + consumer);
}
if (consumers.size() != 0) {
return;
}
}
close();
}
public Lock getLock() {
return lock;
}
/**
* @see ghidra.framework.model.DomainObject#getDomainFile()
*/
@Override
public DomainFile getDomainFile() {
return domainFile;
}
/**
* Returns the hidden user-filesystem associated with
* this objects domain file, or null if unknown.
* @return user data file system
*/
protected FileSystem getAssociatedUserFilesystem() {
if (domainFile instanceof GhidraFile) {
return ((GhidraFile) domainFile).getUserFileSystem();
}
return null;
}
/**
* @see ghidra.framework.model.DomainObject#getName()
*/
@Override
public String getName() {
return name;
}
/**
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
String classname = getClass().getName();
classname = classname.substring(classname.lastIndexOf('.'));
return name + " - " + classname;
}
/**
* @see ghidra.framework.model.DomainObject#setName(java.lang.String)
*/
@Override
public void setName(String newName) {
synchronized (this) {
if (name.equals(newName)) {
return;
}
name = newName;
changed = true;
}
fireEvent(new DomainObjectChangeRecord(DO_OBJECT_RENAMED));
}
private void clearDomainObj() {
if (domainFile instanceof GhidraFile) {
GhidraFile file = (GhidraFile) domainFile;
file.clearDomainObj();
}
else if (domainFile instanceof DomainFileProxy) {
DomainFileProxy df = (DomainFileProxy) domainFile;
df.clearDomainObj();
}
}
/**
* @see ghidra.framework.model.DomainObject#isChanged()
*/
@Override
public boolean isChanged() {
return changed && !temporary;
}
/**
* @see ghidra.framework.model.DomainObject#setTemporary(boolean)
*/
@Override
public void setTemporary(boolean state) {
temporary = state;
}
/**
* @see ghidra.framework.model.DomainObject#isTemporary()
*/
@Override
public boolean isTemporary() {
return temporary;
}
protected void setDomainFile(DomainFile df) {
if (df == null) {
throw new IllegalArgumentException("DomainFile must not be null");
}
clearDomainObj();
DomainFile oldDf = domainFile;
domainFile = df;
fireEvent(new DomainObjectChangeRecord(DO_DOMAIN_FILE_CHANGED, oldDf, df));
}
protected void close() {
synchronized (this) {
clearDomainObj();
}
// TODO: This does not work since change manager is disposed before event ever gets sent out
// fireEvent(new DomainObjectChangeRecord(DO_OBJECT_CLOSED));
docs.dispose(); // clear out any unsent events to prevent listeners from trying
for (DomainObjectChangeSupport queue : changeSupportMap.values()) {
queue.dispose();
}
notifyCloseListeners();
}
private void notifyCloseListeners() {
for (DomainObjectClosedListener listener : closeListeners) {
listener.domainObjectClosed();
}
closeListeners.clear();
}
/**
* @see ghidra.framework.model.DomainObject#flushEvents()
*/
@Override
public void flushEvents() {
docs.flush();
for (DomainObjectChangeSupport queue : changeSupportMap.values()) {
queue.flush();
}
}
/**
* Return "changed" status
* @return true if this object has changed
*/
public boolean getChangeStatus() {
return changed;
}
/**
* @see ghidra.framework.model.DomainObject#addListener(ghidra.framework.model.DomainObjectListener)
*/
@Override
public synchronized void addListener(DomainObjectListener l) {
docs.addListener(l);
}
/**
* @see ghidra.framework.model.DomainObject#removeListener(ghidra.framework.model.DomainObjectListener)
*/
@Override
public synchronized void removeListener(DomainObjectListener l) {
docs.removeListener(l);
}
@Override
public void addCloseListener(DomainObjectClosedListener listener) {
closeListeners.add(listener);
}
@Override
public void removeCloseListener(DomainObjectClosedListener listener) {
closeListeners.remove(listener);
}
@Override
public EventQueueID createPrivateEventQueue(DomainObjectListener listener, int maxDelay) {
EventQueueID eventQueueID = new EventQueueID();
DomainObjectChangeSupport queue = new DomainObjectChangeSupport(this, maxDelay, 1000, lock);
queue.addListener(listener);
changeSupportMap.put(eventQueueID, queue);
return eventQueueID;
}
@Override
public boolean removePrivateEventQueue(EventQueueID id) {
DomainObjectChangeSupport queue = changeSupportMap.remove(id);
if (queue == null) {
return false;
}
queue.dispose();
return true;
}
@Override
public void flushPrivateEventQueue(EventQueueID id) {
DomainObjectChangeSupport queue = changeSupportMap.get(id);
if (queue != null) {
queue.flush();
}
}
/**
* @see ghidra.framework.model.DomainObject#getDescription()
*/
@Override
public abstract String getDescription();
/**
* Fires the specified event.
* @param ev event to fire
*/
public void fireEvent(DomainObjectChangeRecord ev) {
modificationNumber++;
if (eventsEnabled) {
docs.fireEvent(ev);
for (DomainObjectChangeSupport queue : changeSupportMap.values()) {
queue.fireEvent(ev);
}
}
}
/**
* @see ghidra.framework.model.DomainObject#setEventsEnabled(boolean)
*/
@Override
public void setEventsEnabled(boolean v) {
if (eventsEnabled != v) {
eventsEnabled = v;
if (eventsEnabled) {
DomainObjectChangeRecord docr = new DomainObjectChangeRecord(DO_OBJECT_RESTORED);
docs.fireEvent(docr);
for (DomainObjectChangeSupport queue : changeSupportMap.values()) {
queue.fireEvent(docr);
}
}
}
}
@Override
public boolean isSendingEvents() {
return eventsEnabled;
}
/**
* @see ghidra.framework.model.DomainObject#hasExclusiveAccess()
*/
@Override
public boolean hasExclusiveAccess() {
return domainFile == null || !domainFile.isCheckedOut() ||
domainFile.isCheckedOutExclusive();
}
public void checkExclusiveAccess() throws LockException {
if (!hasExclusiveAccess()) {
throw new LockException();
}
}
protected void setChanged(boolean state) {
changed = state;
}
/**
* @see ghidra.framework.model.DomainObject#addConsumer(java.lang.Object)
*/
@Override
public boolean addConsumer(Object consumer) {
if (consumer == null) {
throw new IllegalArgumentException("Consumer must not be null");
}
synchronized (consumers) {
if (isClosed()) {
return false;
}
if (consumers.contains(consumer)) {
throw new IllegalArgumentException("Attempted to acquire the " +
"domain object more than once by the same consumer: " + consumer);
}
consumers.add(consumer);
}
return true;
}
boolean hasConsumers() {
synchronized (consumers) {
return consumers.size() > 0;
}
}
/**
* Returns true if the this file is used only by the given tool
*/
boolean isUsedExclusivelyBy(Object consumer) {
synchronized (consumers) {
return (consumers.size() == 1) && (consumers.contains(consumer));
}
}
/**
* Returns true if the given tool is using this object.
*/
@Override
public boolean isUsedBy(Object consumer) {
synchronized (consumers) {
return consumers.contains(consumer);
}
}
@Override
public ArrayList<Object> getConsumerList() {
synchronized (consumers) {
return new ArrayList<Object>(consumers);
}
}
/**
* Set default content type
* @param doClass default domain object implementation
*/
public static synchronized void setDefaultContentClass(Class<?> doClass) {
defaultDomainObjClass = doClass;
if (contentHandlerTypeMap != null) {
if (doClass == null) {
contentHandlerTypeMap.remove(null);
}
else {
ContentHandler ch = contentHandlerClassMap.get(doClass);
if (ch != null) {
contentHandlerTypeMap.put(null, ch);
}
}
}
}
/**
* Get the ContentHandler associated with the specified content-type.
* @param contentType domain object content type
* @return content handler
*/
static synchronized ContentHandler getContentHandler(String contentType) throws IOException {
checkContentHandlerMaps();
ContentHandler ch = contentHandlerTypeMap.get(contentType);
if (ch == null) {
throw new IOException("Content handler not found for " + contentType);
}
return ch;
}
/**
* Get the ContentHandler associated with the specified domain object
* @param dobj domain object
* @return content handler
*/
public static synchronized ContentHandler getContentHandler(DomainObject dobj)
throws IOException {
checkContentHandlerMaps();
ContentHandler ch = contentHandlerClassMap.get(dobj.getClass());
if (ch == null) {
throw new IOException("Content handler not found for " + dobj.getClass().getName());
}
return ch;
}
private static void checkContentHandlerMaps() {
if (contentHandlerTypeMap != null) {
return;
}
getContentHandlers();
ClassSearcher.addChangeListener(contentHandlerUpdateListener);
}
private synchronized static void getContentHandlers() {
contentHandlerClassMap = new HashMap<Class<?>, ContentHandler>();
contentHandlerTypeMap = new HashMap<String, ContentHandler>();
Set<ContentHandler> handlers = ClassSearcher.getInstances(ContentHandler.class);
for (ContentHandler ch : handlers) {
String type = ch.getContentType();
Class<?> DOClass = ch.getDomainObjectClass();
if (type != null && DOClass != null) {
contentHandlerClassMap.put(DOClass, ch);
contentHandlerTypeMap.put(type, ch);
continue;
}
}
setDefaultContentClass(defaultDomainObjClass);
}
@Override
public Map<String, String> getMetadata() {
return metadata;
}
@Override
public long getModificationNumber() {
return modificationNumber;
}
protected void fatalErrorOccurred(Exception e) {
docs.fatalErrorOccurred(e);
for (DomainObjectChangeSupport queue : changeSupportMap.values()) {
queue.fatalErrorOccurred(e);
}
throw new DomainObjectException(e);
}
}

View file

@ -0,0 +1,646 @@
/* ###
* 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.framework.data;
import java.io.File;
import java.io.IOException;
import java.util.*;
import db.DBConstants;
import db.DBHandle;
import db.util.ErrorHandler;
import ghidra.framework.model.*;
import ghidra.framework.options.Options;
import ghidra.framework.options.SubOptions;
import ghidra.framework.store.LockException;
import ghidra.framework.store.db.PackedDatabase;
import ghidra.util.Msg;
import ghidra.util.ReadOnlyException;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import ghidra.util.task.TaskMonitorAdapter;
/**
* Database version of the DomainObjectAdapter; this version adds the
* concept of starting a transaction before a change is made to the
* domain object and ending the transaction. The transaction allows for
* undo/redo changes.
*
* The implementation class must also satisfy the following requirements:
* <pre>
*
* 1. The following constructor signature must be implemented:
*
* **
* * Constructs new Domain Object
* * @param dbh a handle to an open domain object database.
* * @param openMode one of:
* * READ_ONLY: the original database will not be modified
* * UPDATE: the database can be written to.
* * UPGRADE: the database is upgraded to the latest schema as it is opened.
* * @param monitor TaskMonitor that allows the open to be cancelled.
* * @param consumer the object that keeping the program open.
* *
* * @throws IOException if an error accessing the database occurs.
* * @throws VersionException if database version does not match implementation. UPGRADE may be possible.
* **
* public DomainObjectAdapterDB(DBHandle dbh, int openMode, TaskMonitor monitor, Object consumer) throws IOException, VersionException
*
* 2. The following static field must be provided:
*
* public static final String CONTENT_TYPE
*
* </pre>
*/
public abstract class DomainObjectAdapterDB extends DomainObjectAdapter
implements UndoableDomainObject, ErrorHandler, DBConstants {
protected static final int NUM_UNDOS = 50;
protected DBHandle dbh;
protected DomainObjectDBChangeSet changeSet;
protected OptionsDB options;
volatile boolean closed = false;
private volatile boolean fatalErrorOccurred = false;
private AbstractTransactionManager transactionMgr;
/**
* Construct a new DomainObjectAdapterDB object.
* If construction of this object fails, be sure to release with consumer
* @param dbh database handle
* @param name name of the domain object
* @param timeInterval the time (in milliseconds) to wait before the
* event queue is flushed. If a new event comes in before the time expires,
* the timer is reset.
* @param bufSize initial size of event buffer
* @param consumer the object that created this domain object
*/
protected DomainObjectAdapterDB(DBHandle dbh, String name, int timeInterval, int bufSize,
Object consumer) {
super(name, timeInterval, bufSize, consumer);
this.dbh = dbh;
options = new OptionsDB(this);
transactionMgr = new DomainObjectTransactionManager(this);
}
void setTransactionManager(AbstractTransactionManager transactionMgr) {
this.transactionMgr = transactionMgr;
}
AbstractTransactionManager getTransactionManager() {
return transactionMgr;
}
/**
* Flush any pending database changes.
* This method will be invoked by the transaction manager
* prior to closing a transaction.
*/
public void flushWriteCache() {
}
/**
* Invalidate (i.e., clear) any pending database changes not yet written.
* This method will be invoked by the transaction manager
* prior to aborting a transaction.
*/
public void invalidateWriteCache() {
}
/**
* Return array of all domain objects synchronized with a
* shared transaction manager.
* @return returns array of synchronized domain objects or
* null if this domain object is not synchronized with others.
*/
@Override
public DomainObject[] getSynchronizedDomainObjects() {
if (transactionMgr instanceof SynchronizedTransactionManager) {
return transactionMgr.getDomainObjects();
}
return null;
}
/**
* Synchronize the specified domain object with this domain object
* using a shared transaction manager. If either or both is already shared,
* a transition to a single shared transaction manager will be
* performed.
* @param domainObj
* @throws LockException if lock or open transaction is active on either
* this or the specified domain object
*/
@Override
public void addSynchronizedDomainObject(DomainObject domainObj) throws LockException {
if (!(domainObj instanceof DomainObjectAdapterDB)) {
Msg.debug(this,
"Attempted to synchronize to a domainObject that is not a domainObjectDB: " +
domainObj.getClass());
return;
}
DomainObjectAdapterDB other = (DomainObjectAdapterDB) domainObj;
SynchronizedTransactionManager manager;
if (transactionMgr instanceof SynchronizedTransactionManager) {
if (!(other.transactionMgr instanceof DomainObjectTransactionManager)) {
throw new IllegalStateException();
}
manager = (SynchronizedTransactionManager) transactionMgr;
manager.addDomainObject(other);
}
else if (other.transactionMgr instanceof SynchronizedTransactionManager) {
if (!(transactionMgr instanceof DomainObjectTransactionManager)) {
throw new IllegalStateException();
}
manager = (SynchronizedTransactionManager) other.transactionMgr;
manager.addDomainObject(this);
}
else {
manager = new SynchronizedTransactionManager();
manager.addDomainObject(this);
manager.addDomainObject(other);
}
}
/**
* Release this domain object from a shared transaction manager. If
* this object has not been synchronized with others via a shared
* transaction manager, this method will have no affect.
* @throws LockException if lock or open transaction is active
*/
@Override
public void releaseSynchronizedDomainObject() throws LockException {
if (!(transactionMgr instanceof SynchronizedTransactionManager)) {
return;
}
((SynchronizedTransactionManager) transactionMgr).removeDomainObject(this);
}
/**
* Returns the open handle to the underlying database.
*/
public DBHandle getDBHandle() {
return dbh;
}
/**
* Returns the user data object or null if not supported by this domain object.
*/
protected DomainObjectAdapterDB getUserData() {
return null;
}
/**
* Returns the change set corresponding to all unsaved changes in this domain object.
* @return the change set corresponding to all unsaved changes in this domain object
*/
public DomainObjectDBChangeSet getChangeSet() {
return changeSet;
}
/**
* @see db.util.ErrorHandler#dbError(java.io.IOException)
*/
@Override
public void dbError(IOException e) {
fatalErrorOccurred = true;
fatalErrorOccurred(e);
}
/**
* Returns all properties lists contained by this domain object.
*
* @return all property lists contained by this domain object.
*/
@Override
public List<String> getOptionsNames() {
List<Options> childOptions = options.getChildOptions();
List<String> names = new ArrayList<>(childOptions.size());
for (Options options : childOptions) {
names.add(options.getName());
}
return names;
}
/**
* @see ghidra.framework.model.DomainObject#getOptions(java.lang.String)
*/
@Override
public Options getOptions(String propertyListName) {
return new SubOptions(options, propertyListName, propertyListName + Options.DELIMITER);
}
/**
* This method can be used to perform property list alterations resulting from renamed or obsolete
* property paths. This should only be invoked during an upgrade.
* WARNING! Should only be called during construction of domain object
* @see OptionsDB#performAlterations(Map)
*/
protected void performPropertyListAlterations(Map<String, String> propertyAlterations,
TaskMonitor monitor) throws IOException {
monitor.setProgress(0);
monitor.setMessage("Fixing Properties...");
options.performAlterations(propertyAlterations);
}
/**
* @see ghidra.framework.model.DomainObject#canLock()
*/
@Override
public boolean canLock() {
return transactionMgr.getCurrentTransaction() == null && !closed;
}
/**
* @see ghidra.framework.model.DomainObject#isLocked()
*/
@Override
public boolean isLocked() {
return transactionMgr.isLocked();
}
/**
* @see ghidra.framework.model.DomainObject#lock(String)
*/
@Override
public boolean lock(String reason) {
return transactionMgr.lock(reason);
}
void prepareToSave() {
int txId = transactionMgr.startTransaction(this, "Update Metadata", null, true, true);
try {
updateMetadata();
}
catch (IOException e) {
dbError(e);
}
finally {
transactionMgr.endTransaction(this, txId, true, true);
}
}
/**
* Attempt to obtain a modification lock on the domain object when generating a
* background snapshot.
* @param hasProgress true if monitor has progress indicator
* @param title title to be used for monitor
* @return monitor object if lock obtained successfully, else null which indicates that a
* modification is in process.
*/
LockingTaskMonitor lockForSnapshot(boolean hasProgress, String title) {
return transactionMgr.lockForSnapshot(this, hasProgress, title);
}
/**
* @see ghidra.framework.model.DomainObject#forceLock(boolean, String)
*/
@Override
public void forceLock(boolean rollback, String reason) {
transactionMgr.forceLock(rollback, reason);
}
/**
* @see ghidra.framework.model.DomainObject#unlock()
*/
@Override
public void unlock() {
transactionMgr.unlock();
}
/**
* Release the modification lock which is associated with the specified LockingTaskHandler.
*/
void unlock(LockingTaskMonitor handler) {
transactionMgr.unlock(handler);
}
@Override
public int startTransaction(String description) {
return startTransaction(description, null);
}
/**
* @see ghidra.framework.model.UndoableDomainObject#startTransaction(java.lang.String)
*/
@Override
public int startTransaction(String description, AbortedTransactionListener listener) {
int id = -1;
while (id == -1) {
try {
id = transactionMgr.startTransaction(this, description, listener, true);
}
catch (DomainObjectLockedException e) {
// wait for lock to be removed (e.g., Save operation)
try {
Thread.sleep(100);
}
catch (InterruptedException e1) {
Msg.debug(this, "Unexpected thread interrupt", e1);
}
}
}
return id;
}
/**
* @see ghidra.framework.model.UndoableDomainObject#endTransaction(int, boolean)
*/
@Override
public void endTransaction(int transactionID, boolean commit) {
transactionMgr.endTransaction(this, transactionID, commit, true);
}
/**
* Adds the given transaction listener to this domain object
* @param listener the new transaction listener to add
*/
@Override
public void addTransactionListener(TransactionListener listener) {
transactionMgr.addTransactionListener(this, listener);
}
/**
* Removes the given transaction listener from this domain object.
* @param listener the transaction listener to remove
*/
@Override
public void removeTransactionListener(TransactionListener listener) {
transactionMgr.removeTransactionListener(this, listener);
}
/**
* Returns the undo stack depth.
* (The number of items on the undo stack)
* This method is for JUnits.
* @return the undo stack depth
*/
public int getUndoStackDepth() {
return transactionMgr.getUndoStackDepth();
}
/**
* @see ghidra.framework.model.Undoable#canRedo()
*/
@Override
public boolean canRedo() {
return transactionMgr.canRedo();
}
/**
* @see ghidra.framework.model.Undoable#canUndo()
*/
@Override
public boolean canUndo() {
return transactionMgr.canUndo();
}
/**
* @see ghidra.framework.model.Undoable#getRedoName()
*/
@Override
public String getRedoName() {
return transactionMgr.getRedoName();
}
/**
* @see ghidra.framework.model.Undoable#getUndoName()
*/
@Override
public String getUndoName() {
return transactionMgr.getUndoName();
}
/**
* @see ghidra.framework.model.UndoableDomainObject#getCurrentTransaction()
*/
@Override
public Transaction getCurrentTransaction() {
return transactionMgr.getCurrentTransaction();
}
/**
* @see ghidra.framework.model.Undoable#redo()
*/
@Override
public void redo() throws IOException {
transactionMgr.redo();
}
/**
* @see ghidra.framework.model.Undoable#undo()
*/
@Override
public void undo() throws IOException {
transactionMgr.undo();
}
/**
* @see ghidra.framework.model.DomainObject#isChanged()
*/
@Override
public boolean isChanged() {
if (dbh == null) {
return false;
}
return super.isChanged() && dbh.isChanged();
}
@Override
protected void setChanged(boolean b) {
super.setChanged(b);
if (!b) {
clearUndo(true);
}
}
/**
* Notification of property change
* @param propertyName
* @param oldValue
* @param newValue
* @return true if change is OK, false value should be reverted
*/
protected boolean propertyChanged(String propertyName, Object oldValue, Object newValue) {
setChanged(true);
fireEvent(new DomainObjectChangeRecord(DomainObject.DO_PROPERTY_CHANGED, name, name));
return true;
}
/**
* @see ghidra.framework.model.Undoable#clearUndo()
*/
@Override
public void clearUndo() {
clearUndo(true);
}
protected void clearUndo(boolean notifyListeners) {
transactionMgr.clearUndo(notifyListeners);
}
protected void clearCache(boolean all) {
options.clearCache();
}
/**
* @see ghidra.framework.model.DomainObject#canSave()
*/
@Override
public synchronized boolean canSave() {
DomainFile df = getDomainFile();
if (df instanceof GhidraFile) {
return df.isInWritableProject() && dbh.canUpdate() && !df.isReadOnly();
}
return dbh.canUpdate();
}
/**
* @see ghidra.framework.model.DomainObject#save(java.lang.String, ghidra.util.task.TaskMonitor)
*/
@Override
public void save(String comment, TaskMonitor monitor) throws IOException, CancelledException {
if (!canSave()) {
throw new ReadOnlyException("File is read-only");
}
boolean wasSaved = false;
if (!lock("save")) {
throw new IOException("Unable to lock due to active transaction");
}
try {
synchronized (this) {
if (changed) {
dbh.save(comment, getChangeSet(), monitor);
setChanged(false);
wasSaved = true;
}
}
DomainObjectAdapterDB userData = getUserData();
if (userData != null && userData.isChanged()) {
userData.save(comment, monitor);
}
}
finally {
unlock();
}
if (wasSaved) {
fireEvent(new DomainObjectChangeRecord(DomainObject.DO_OBJECT_SAVED));
DomainFile df = getDomainFile();
if (df instanceof GhidraFile) {
((GhidraFile) df).fileChanged();
}
}
}
/**
* @see ghidra.framework.model.DomainObject#saveToPackedFile(java.io.File, ghidra.util.task.TaskMonitor)
*/
@Override
public void saveToPackedFile(File outputFile, TaskMonitor monitor)
throws IOException, CancelledException {
transactionMgr.checkLockingTask();
if (!lock("saveToPackedFile")) {
throw new IOException("Unable to lock due to active transaction");
}
try {
ContentHandler ch = DomainObjectAdapter.getContentHandler(this);
PackedDatabase.packDatabase(dbh, name, ch.getContentType(), outputFile, monitor);
// TODO :( output method will cause Redo-able transactions to be cleared
// and may cause older Undo-able transactions to be cleared.
// Should implement transaction listener to properly maintain domain object
// transaction sychronization
}
finally {
unlock();
}
}
/**
* This method is called before a save, saveAs, or saveToPackedFile
* to update common meta data
* @throws IOException
*/
protected void updateMetadata() throws IOException {
saveMetadata();
}
@Override
protected void close() {
synchronized (transactionMgr) {
transactionMgr.close(this);
closed = true;
}
DomainObjectAdapterDB userData = getUserData();
if (userData != null && userData.isChanged() && (getDomainFile() instanceof GhidraFile)) {
try {
userData.save(null, TaskMonitorAdapter.DUMMY_MONITOR);
}
catch (CancelledException e) {
}
catch (IOException e) {
Msg.warn(this, "Failed to save user data for: " + getDomainFile().getName());
}
}
super.close();
dbh.close(fatalErrorOccurred);
if (userData != null) {
userData.close();
}
}
/**
* @see ghidra.framework.model.DomainObject#isClosed()
*/
@Override
public boolean isClosed() {
return closed;
}
/**
* @see ghidra.framework.model.UndoableDomainObject#hasTerminatedTransaction()
*/
@Override
public boolean hasTerminatedTransaction() {
return transactionMgr.hasTerminatedTransaction();
}
protected void loadMetadata() throws IOException {
MetadataManager.loadData(this, metadata);
}
protected void saveMetadata() throws IOException {
MetadataManager.saveData(this, metadata);
}
}

View file

@ -0,0 +1,238 @@
/* ###
* 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.framework.data;
import java.util.*;
import java.util.concurrent.Callable;
import generic.timer.GhidraTimer;
import generic.timer.GhidraTimerFactory;
import ghidra.framework.model.*;
import ghidra.util.*;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
class DomainObjectChangeSupport {
private WeakSet<DomainObjectListener> listeners;
private DomainObject src;
private List<DomainObjectChangeRecord> changesQueue;
private GhidraTimer timer;
private Lock domainObjectLock;
private Lock writeLock = new Lock("DOCS Change Records Queue Lock");
private volatile boolean isDisposed;
/**
* Constructs a new DomainObjectChangeSupport object.
* @param src The object to be put as the src for all events generated.
* @param timeInterval The time (in milliseconds) this object will wait before
* flushing its event buffer. If a new event comes in before the time expires,
* the timer is reset.
* @param lock the lock used to verify that calls to {@link #flush()} are not performed
* while a lock is held; this is the lock to guard the DB
*/
DomainObjectChangeSupport(DomainObject src, int timeInterval, int bufsize, Lock lock) {
this.src = src;
this.domainObjectLock = lock;
changesQueue = new ArrayList<>(bufsize);
listeners = WeakDataStructureFactory.createCopyOnWriteWeakSet();
timer = GhidraTimerFactory.getGhidraTimer(timeInterval, timeInterval, () -> sendEventNow());
timer.setInitialDelay(25);
timer.setDelay(500);
timer.setRepeats(true);
}
void addListener(DomainObjectListener listener) {
// Capture the pending event to send to the existing listeners. This prevents the new
// listener from getting events registered before the listener was added.
DomainObjectChangedEvent pendingEvent = convertEventQueueRecordsToEvent();
List<DomainObjectListener> previousListeners = atomicAddListener(listener);
/*
* Do later so that we do not get this deadlock:
* Thread1 has Domain Lock -> wants AWT lock
* Swing has AWT lock -> wants Domain lock
*/
SystemUtilities.runIfSwingOrPostSwingLater(
() -> notifyEvent(previousListeners, pendingEvent));
}
void removeListener(DomainObjectListener listener) {
listeners.remove(listener);
}
private void sendEventNow() {
DomainObjectChangedEvent ev = convertEventQueueRecordsToEvent();
notifyEvent(listeners, ev);
}
private DomainObjectChangedEvent convertEventQueueRecordsToEvent() {
DomainObjectChangedEvent event = lockQueue(() -> {
if (changesQueue.isEmpty()) {
timer.stop();
return null;
}
DomainObjectChangedEvent e = new DomainObjectChangedEvent(src, changesQueue);
changesQueue = new ArrayList<>();
return e;
});
return event;
}
// This version of notify takes in the listeners to notify so that we can send events to
// some listeners, but not all of them (like flushing when adding new listeners)
private void notifyEvent(Iterable<DomainObjectListener> listenersToNotify,
DomainObjectChangedEvent ev) {
if (ev == null) {
return; // this implies there we no changes when the timer expired
}
if (isDisposed) {
return;
}
for (DomainObjectListener dol : listenersToNotify) {
try {
dol.domainObjectChanged(ev);
}
catch (Exception exc) {
Msg.showError(this, null, "Error", "Error in Domain Object listener", exc);
}
}
}
void flush() {
Thread lockOwner = domainObjectLock.getOwner();
if (domainObjectLock != null && lockOwner == Thread.currentThread()) {
/*
* We have decided that flushing events with a lock can lead to deadlocks. There
* should be no reason to flush events while holding a lock. This is the
* potential deadlock:
* Thread1 has Domain Lock -> wants AWT lock
* Swing has AWT lock -> wants Domain lock
*/
throw new IllegalStateException("Cannot call flush() with locks!");
}
SystemUtilities.runSwingNow(() -> sendEventNow());
}
void fireEvent(DomainObjectChangeRecord docr) {
if (isDisposed) {
return;
}
lockQueue(() -> {
changesQueue.add(docr);
timer.start();
});
}
void fatalErrorOccurred(final Throwable t) {
List<DomainObjectListener> listenersCopy = new ArrayList<>(listeners.values());
dispose();
Runnable errorTask = () -> {
List<DomainObjectChangeRecord> records =
Arrays.asList(new DomainObjectChangeRecord(DomainObject.DO_OBJECT_ERROR, null, t));
DomainObjectChangedEvent ev = new DomainObjectChangedEvent(src, records);
for (DomainObjectListener l : listenersCopy) {
try {
l.domainObjectChanged(ev);
}
catch (Throwable t2) {
// I guess we don't care (probably because some other fatal error has
// already happened)
}
}
};
SystemUtilities.runSwingLater(errorTask);
}
void dispose() {
lockQueue(() -> {
isDisposed = true;
timer.stop();
changesQueue.clear();
});
listeners.clear();
}
private List<DomainObjectListener> atomicAddListener(DomainObjectListener l) {
List<DomainObjectListener> previousLisetners = new ArrayList<>();
for (DomainObjectListener listener : listeners) {
previousLisetners.add(listener);
}
listeners.add(l);
return previousLisetners;
}
//==================================================================================================
// Lock Methods
//==================================================================================================
private void lockQueue(Runnable r) {
try {
writeLock.acquire();
r.run();
}
finally {
writeLock.release();
}
}
private <T> T lockQueue(Callable<T> c) {
try {
writeLock.acquire();
T result;
try {
result = c.call();
return result;
}
catch (Exception e) {
// sholudn't happen
Msg.error(this, "Exception while updating change records", e);
return null;
}
}
finally {
writeLock.release();
}
}
}

View file

@ -0,0 +1,62 @@
/* ###
* 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.framework.data;
import db.DBChangeSet;
/**
* <code>DomainObjectDBChangeSet</code> extends <code>DBChangeSet</code>
* providing methods which facilitate transaction synchronization with the domain object's DBHandle.
*/
public interface DomainObjectDBChangeSet extends DBChangeSet {
/**
* Resets the change sets after a save.
*/
void clearUndo(boolean isCheckedOut);
/**
* Undo the last change data transaction
*/
void undo();
/**
* Redo the change data transaction associated the last Undo.
*/
void redo();
/**
* Set the undo/redo stack depth
* @param maxUndos the maximum numbder of undo
*/
void setMaxUndos(int maxUndos);
/**
* Clears the undo/redo stack.
*/
void clearUndo();
/**
* Start change data transaction.
*/
void startTransaction();
/**
* End change data transaction.
* @param commit if true transaction data is committed,
* otherwise transaction data is discarded
*/
void endTransaction(boolean commit);
}

View file

@ -0,0 +1,266 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.data;
import ghidra.framework.model.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
import java.util.*;
/**
* <code>DomainObjectDBTransaction</code> represents an atomic undoable operation performed
* on a single domain object.
*/
class DomainObjectDBTransaction implements Transaction {
private static int nextBaseId = 1234;
private ArrayList<TransactionEntry> list;
private HashMap<PluginTool, ToolState> toolStates;
private int activeEntries = 0;
private int status = NOT_DONE;
private boolean hasDBTransaction = false;
private final long id;
private WeakSet<AbortedTransactionListener> abortedTransactionListeners =
WeakDataStructureFactory.createCopyOnWriteWeakSet();
// baseId used to improve differentiation between application level transaction id's
// nextBaseId is used to prime the base and should also be incremented each time
// a sub-transaction is added. This approach is based upon the fact that
// only a single Transaction object is pending at any given time for a
// specific database.
private final int baseId;
private final DomainObject domainObject;
DomainObjectDBTransaction(long id, DomainObject domainObject) {
this.domainObject = domainObject;
this.id = id;
baseId = getNextBaseId();
list = new ArrayList<TransactionEntry>();
toolStates = new HashMap<PluginTool, ToolState>();
getToolStates();
}
private void getToolStates() {
if (SystemUtilities.isInHeadlessMode()) {
return;
}
for (Object consumer : domainObject.getConsumerList()) {
if (consumer instanceof PluginTool) {
PluginTool tool = (PluginTool) consumer;
try {
ToolState toolState = ToolStateFactory.createToolState(tool, domainObject);
toolStates.put(tool, toolState);
}
catch (Throwable t) {
Msg.error(this, "Unexpected Exception: " + t.getMessage(), t);
}
}
}
}
void restoreToolStates(final boolean beforeState) {
if (toolStates.isEmpty()) {
return;
}
SystemUtilities.runSwingLater(new Runnable() {
@Override
public void run() {
// flush events blocks so that current tool state and domain object are
// consistent prior to restore tool state
domainObject.flushEvents();
if (beforeState) {
restoreToolStatesAfterUndo(domainObject);
}
else {
restoreToolStatesAfterRedo(domainObject);
}
}
});
}
static synchronized int getNextBaseId() {
return nextBaseId++;
}
/**
* Mark this fully committed transaction as having a corresponding
* database transaction/checkpoint.
*/
void setHasCommittedDBTransaction() {
if (getStatus() != COMMITTED) {
throw new IllegalStateException("transaction was not committed");
}
hasDBTransaction = true;
}
/**
* Returns true if this fully committed transaction has a corresponding
* database transaction/checkpoint.
*/
@Override
public boolean hasCommittedDBTransaction() {
return hasDBTransaction;
}
/* (non-Javadoc)
* @see ghidra.framework.data.XTransaction#getID()
*/
@Override
public long getID() {
return id;
}
int addEntry(String description, AbortedTransactionListener listener) {
if (listener != null) {
abortedTransactionListeners.add(listener);
}
list.add(new TransactionEntry(description));
activeEntries++;
getNextBaseId();
return list.size() + baseId - 1;
}
void endEntry(int transactionID, boolean commit) {
TransactionEntry entry = null;
try {
entry = list.get(transactionID - baseId);
}
catch (ArrayIndexOutOfBoundsException e) {
throw new IllegalStateException("Transaction not found");
}
if (entry.status != NOT_DONE) {
throw new IllegalStateException("Attempted to end Transaction " + "more that once: " +
entry.description);
}
entry.status = commit ? COMMITTED : ABORTED;
if (!commit) {
status = ABORTED;
}
if (--activeEntries == 0 && status == NOT_DONE) {
status = COMMITTED;
}
}
@Override
public int getStatus() {
if (status == ABORTED && activeEntries > 0) {
return NOT_DONE_BUT_ABORTED;
}
return status;
}
private void restoreToolStatesAfterUndo(DomainObject object) {
List<Object> consumers = object.getConsumerList();
for (int i = 0; i < consumers.size(); i++) {
Object obj = consumers.get(i);
if (obj instanceof PluginTool) {
PluginTool tool = (PluginTool) obj;
ToolState toolState = toolStates.get(tool);
if (toolState != null) {
toolState.restoreAfterUndo(object);
}
}
}
}
private void restoreToolStatesAfterRedo(DomainObject object) {
List<Object> consumers = object.getConsumerList();
for (int i = 0; i < consumers.size(); i++) {
Object obj = consumers.get(i);
if (obj instanceof PluginTool) {
PluginTool tool = (PluginTool) obj;
ToolState toolState = toolStates.get(tool);
if (toolState != null) {
toolState.restoreAfterRedo(object);
}
}
}
}
/* (non-Javadoc)
* @see ghidra.framework.data.XTransaction#getDescription()
*/
@Override
public String getDescription() {
if (list.isEmpty()) {
return "";
}
String description = "";
for (TransactionEntry entry : list) {
description = entry.description;
if (description != null && description.length() != 0) {
description = domainObject.getDomainFile().getName() + ": " + description;
break;
}
}
return description;
}
/* (non-Javadoc)
* @see ghidra.framework.data.XTransaction#getOpenSubTransactions()
*/
@Override
public ArrayList<String> getOpenSubTransactions() {
ArrayList<String> subTxList = new ArrayList<String>();
Iterator<TransactionEntry> iter = list.iterator();
while (iter.hasNext()) {
TransactionEntry entry = iter.next();
if (entry.status == NOT_DONE) {
subTxList.add(entry.description);
}
}
return subTxList;
}
private static class TransactionEntry {
String description;
int status;
TransactionEntry(String description) {
this.description = description;
status = NOT_DONE;
}
}
void initAfterState(DomainObject object) {
List<Object> consumers = object.getConsumerList();
for (int i = 0; i < consumers.size(); i++) {
Object obj = consumers.get(i);
if (obj instanceof PluginTool) {
PluginTool tool = (PluginTool) obj;
ToolState toolState = toolStates.get(tool);
if (toolState != null) {
toolState.getAfterState(object);
}
}
}
}
void abort() {
status = ABORTED;
for (AbortedTransactionListener listener : abortedTransactionListeners) {
listener.transactionAborted(id);
}
abortedTransactionListeners.clear();
}
}

View file

@ -0,0 +1,77 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.data;
import ghidra.app.merge.MergeProgressModifier;
import ghidra.util.HelpLocation;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import javax.swing.JComponent;
/**
* An interface to allow merging of domain objects.
*/
public interface DomainObjectMergeManager extends MergeProgressModifier {
/**
* Merge domain objects and resolve any conflicts.
* @return true if the merge process completed successfully
* @throws CancelledException if the user canceled the merge process
*/
boolean merge(TaskMonitor monitor) throws CancelledException;
/**
* Sets the resolve information object for the indicated standardized name.
* This is how information is passed between merge managers.
* @param infoType the string indicating the type of resolve information
* @param infoObject the object for the named string. This information is
* determined by the merge manager that creates it.
* @see getResolveInformation(String)
*/
public void setResolveInformation(String infoType, Object infoObject);
/**
* Show the component that is used to resolve conflicts. This method
* is called by the MergeResolvers when user input is required. If the
* component is not null, this method blocks until the user either
* cancels the merge process or resolves a conflict. If comp is null,
* then the default component is displayed, and the method does not
* wait for user input.
* @param comp component to show; if component is null, show the
* default component and do not block
* @param componentID id or name for the component
*/
public void showComponent(final JComponent comp, final String componentID,
HelpLocation helpLoc);
/**
* Enable the apply button according to the "enabled" parameter.
*/
public void setApplyEnabled(final boolean enabled);
/**
* Clear the status text on the merge dialog.
*
*/
public void clearStatusText();
/**
* Set the status text on the merge dialog.
*/
public void setStatusText(String msg);
}

View file

@ -0,0 +1,356 @@
/* ###
* 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.framework.data;
import java.io.IOException;
import java.util.LinkedList;
import ghidra.framework.model.*;
import ghidra.util.Msg;
import ghidra.util.SystemUtilities;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
class DomainObjectTransactionManager extends AbstractTransactionManager {
private LinkedList<DomainObjectDBTransaction> undoList =
new LinkedList<>();
private LinkedList<DomainObjectDBTransaction> redoList =
new LinkedList<>();
private WeakSet<TransactionListener> transactionListeners =
WeakDataStructureFactory.createCopyOnWriteWeakSet();
private DomainObjectAdapterDB domainObj;
private DomainObjectAdapterDB[] domainObjAsArray;
private DomainObjectDBTransaction transaction;
DomainObjectTransactionManager(DomainObjectAdapterDB domainObj) {
super();
this.domainObj = domainObj;
domainObj.dbh.setMaxUndos(NUM_UNDOS);
domainObjAsArray = new DomainObjectAdapterDB[] { domainObj };
}
DomainObjectAdapterDB getDomainObject() {
return domainObj;
}
@Override
DomainObjectAdapterDB[] getDomainObjects() {
return domainObjAsArray;
}
@Override
void checkDomainObject(DomainObjectAdapterDB object) {
if (object != domainObj) {
throw new IllegalArgumentException("invalid domain object");
}
}
@Override
void clearTransactions() {
domainObj.dbh.setMaxUndos(0);
domainObj.dbh.setMaxUndos(NUM_UNDOS);
if (domainObj.changeSet != null) {
domainObj.changeSet.clearUndo();
}
undoList.clear();
redoList.clear();
}
@Override
void terminateTransaction(boolean rollback, boolean notify) {
synchronized (this) {
if (transaction == null || transactionTerminated) {
return;
}
try {
domainObj.dbh.terminateTransaction(transaction.getID(), !rollback);
}
catch (IOException e) {
domainObj.dbError(e);
}
transaction.abort();
transactionTerminated = true;
if (domainObj.changeSet != null) {
domainObj.changeSet.endTransaction(!rollback);
}
domainObj.clearCache(false);
}
domainObj.fireEvent(new DomainObjectChangeRecord(DomainObject.DO_OBJECT_RESTORED));
if (notify) {
notifyEndTransaction();
}
}
@Override
synchronized int startTransaction(DomainObjectAdapterDB object, String description,
AbortedTransactionListener listener, boolean force, boolean notify) {
if (object != domainObj) {
throw new IllegalArgumentException("invalid domain object");
}
if (!force) {
verifyNoLock();
}
if (transaction == null) {
transactionTerminated = false;
transaction =
new DomainObjectDBTransaction(domainObj.dbh.startTransaction(), domainObj);
if (domainObj.changeSet != null) {
domainObj.changeSet.startTransaction();
}
int id = transaction.addEntry(description, listener);
if (notify) {
notifyStartTransaction(transaction);
}
return id;
}
if (transactionTerminated) {
Msg.warn(this,
"Aborted transaction still pending, new transaction will also be aborted: " +
description);
}
return transaction.addEntry(description, listener);
}
private void flushDomainObjectEvents() {
// In headless mode this method will block
SystemUtilities.runSwingLater(() -> domainObj.flushEvents());
}
@Override
synchronized Transaction endTransaction(DomainObjectAdapterDB object, int transactionID,
boolean commit, boolean notify) {
if (object != domainObj) {
throw new IllegalArgumentException("invalid domain object");
}
if (transaction == null) {
throw new IllegalStateException("No transaction is open");
}
DomainObjectDBTransaction returnedTransaction = transaction;
try {
transaction.endEntry(transactionID, commit && !transactionTerminated);
int status = transaction.getStatus();
if (status == Transaction.COMMITTED) {
object.flushWriteCache();
boolean committed = domainObj.dbh.endTransaction(transaction.getID(), true);
if (committed) {
returnedTransaction.setHasCommittedDBTransaction();
domainObj.changed = true;
redoList.clear();
undoList.addLast(transaction);
if (undoList.size() > NUM_UNDOS) {
undoList.removeFirst();
}
flushDomainObjectEvents();
}
if (domainObj.changeSet != null) {
domainObj.changeSet.endTransaction(committed);
}
if (notify) {
notifyEndTransaction();
}
transaction = null;
}
else if (status == Transaction.ABORTED) {
object.invalidateWriteCache();
if (!transactionTerminated) {
domainObj.dbh.endTransaction(transaction.getID(), false);
if (domainObj.changeSet != null) {
domainObj.changeSet.endTransaction(false);
}
}
domainObj.clearCache(false);
if (notify) {
notifyEndTransaction();
}
domainObj.fireEvent(new DomainObjectChangeRecord(DomainObject.DO_OBJECT_RESTORED));
transaction.restoreToolStates(true);
transaction = null;
}
}
catch (IOException e) {
transaction = null;
domainObj.dbError(e);
}
return returnedTransaction;
}
/**
* Returns the undo stack depth.
* (The number of items on the undo stack)
* This method is for JUnits.
* @return the undo stack depth
*/
@Override
int getUndoStackDepth() {
return undoList.size();
}
@Override
synchronized boolean canRedo() {
if (redoList.size() > 0) {
return domainObj.dbh.canRedo();
}
return false;
}
@Override
synchronized boolean canUndo() {
if (undoList.size() > 0) {
return domainObj.dbh.canUndo();
}
return false;
}
@Override
synchronized String getRedoName() {
if (redoList.size() > 0) {
Transaction t = redoList.getLast();
return t.getDescription();
}
return "";
}
@Override
synchronized String getUndoName() {
if (undoList.size() > 0) {
Transaction t = undoList.getLast();
return t.getDescription();
}
return "";
}
@Override
Transaction getCurrentTransaction() {
return transaction;
}
@Override
void doRedo(boolean notify) throws IOException {
if (canRedo()) {
DomainObjectDBTransaction t = redoList.removeLast();
domainObj.dbh.redo();
domainObj.clearCache(false);
if (domainObj.changeSet != null) {
domainObj.changeSet.redo();
}
domainObj.fireEvent(new DomainObjectChangeRecord(DomainObject.DO_OBJECT_RESTORED));
undoList.addLast(t);
t.restoreToolStates(false);
if (notify) {
notifyUndoRedo();
}
}
}
@Override
void doUndo(boolean notify) throws IOException {
if (canUndo()) {
DomainObjectDBTransaction t = undoList.removeLast();
t.initAfterState(domainObj);
domainObj.dbh.undo();
if (domainObj.changeSet != null) {
domainObj.changeSet.undo();
}
domainObj.clearCache(false);
domainObj.fireEvent(new DomainObjectChangeRecord(DomainObject.DO_OBJECT_RESTORED));
redoList.addLast(t);
t.restoreToolStates(true);
if (notify) {
notifyUndoRedo();
}
}
}
@Override
synchronized void clearUndo(boolean notifyListeners) {
if (!undoList.isEmpty() || !redoList.isEmpty()) {
undoList.clear();
redoList.clear();
DomainFile df = domainObj.getDomainFile();
if (domainObj.changeSet != null) {
domainObj.changeSet.clearUndo(df != null && df.isCheckedOut());
}
if (notifyListeners) {
notifyUndoStackChanged();
}
}
}
@Override
void doClose(DomainObjectAdapterDB object) {
// don't care
}
@Override
void addTransactionListener(DomainObjectAdapterDB object, TransactionListener listener) {
if (object != domainObj) {
throw new IllegalArgumentException("invalid domain object");
}
transactionListeners.add(listener);
}
@Override
void removeTransactionListener(DomainObjectAdapterDB object, TransactionListener listener) {
if (object != domainObj) {
throw new IllegalArgumentException("invalid domain object");
}
transactionListeners.remove(listener);
}
void notifyStartTransaction(Transaction tx) {
SystemUtilities.runSwingLater(() -> {
for (TransactionListener listener : transactionListeners) {
listener.transactionStarted(domainObj, tx);
listener.undoStackChanged(domainObj);
}
});
}
void notifyEndTransaction() {
SystemUtilities.runSwingLater(() -> {
for (TransactionListener listener : transactionListeners) {
listener.transactionEnded(domainObj);
listener.undoStackChanged(domainObj);
}
});
}
void notifyUndoStackChanged() {
SystemUtilities.runSwingLater(() -> {
for (TransactionListener listener : transactionListeners) {
listener.undoStackChanged(domainObj);
}
});
}
void notifyUndoRedo() {
SystemUtilities.runSwingLater(() -> {
for (TransactionListener listener : transactionListeners) {
listener.undoRedoOccurred(domainObj);
listener.undoStackChanged(domainObj);
}
});
}
}

View file

@ -0,0 +1,596 @@
/* ###
* 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.framework.data;
import java.io.File;
import java.io.IOException;
import java.util.*;
import javax.swing.Icon;
import ghidra.framework.model.*;
import ghidra.framework.store.ItemCheckoutStatus;
import ghidra.framework.store.Version;
import ghidra.framework.store.local.LocalFileSystem;
import ghidra.util.InvalidNameException;
import ghidra.util.ReadOnlyException;
import ghidra.util.exception.*;
import ghidra.util.task.TaskMonitor;
import ghidra.util.task.TaskMonitorAdapter;
public class GhidraFile implements DomainFile {
// FIXME: This implementation assumes a single implementation of the DomainFile and DomainFolder interfaces
protected ProjectFileManager fileManager;
private LocalFileSystem fileSystem;
private DomainFolderChangeListener listener;
private GhidraFolder parent;
private String name;
GhidraFile(GhidraFolder parent, String name) {
this.parent = parent;
this.name = name;
this.fileManager = parent.getProjectFileManager();
this.fileSystem = parent.getLocalFileSystem();
this.listener = parent.getChangeListener();
}
public LocalFileSystem getUserFileSystem() {
return fileManager.getUserFileSystem();
}
private GhidraFileData getFileData() throws IOException {
return parent.getFileData(name);
}
private void fileError(IOException e) {
// can easily occur during server disconnect
//Msg.error(this, "IO Error on file " + getPathname() + ": " + e.getMessage());
}
@Override
public boolean exists() {
try {
getFileData();
return true;
}
catch (IOException e) {
// All IO exceptions treated as missing file
return false;
}
}
@Override
public String getFileID() {
try {
return getFileData().getFileID();
}
catch (IOException e) {
fileError(e);
}
return null;
}
/**
* Reassign a new file-ID to resolve file-ID conflict.
* Conflicts can occur as a result of a cancelled check-out.
*/
void resetFileID() throws IOException {
getFileData().resetFileID();
}
void clearDomainObj() {
String path = getPathname();
DomainObjectAdapter doa = fileManager.getOpenedDomainObject(path);
if (doa != null && fileManager.clearDomainObject(getPathname())) {
listener.domainFileObjectClosed(this, doa);
}
}
@Override
public GhidraFile setName(String newName) throws InvalidNameException, IOException {
return getFileData().setName(newName);
}
@Override
public String getName() {
return name;
}
@Override
public String getPathname() {
return parent.getPathname(name);
}
@Override
public ProjectLocator getProjectLocator() {
return fileManager.getProjectLocator();
}
@Override
public String getContentType() {
try {
return getFileData().getContentType();
}
catch (IOException e) {
fileError(e);
}
return ContentHandler.UNKNOWN_CONTENT;
}
@Override
public Class<? extends DomainObject> getDomainObjectClass() {
try {
return getFileData().getDomainObjectClass();
}
catch (IOException e) {
fileError(e);
}
return DomainObject.class;
}
@Override
public DomainFolder getParent() {
return parent;
}
@Override
public int compareTo(DomainFile df) {
return name.compareToIgnoreCase(df.getName());
}
@Override
public ChangeSet getChangesByOthersSinceCheckout() throws VersionException, IOException {
return getFileData().getChangesByOthersSinceCheckout();
}
@Override
public DomainObject getOpenedDomainObject(Object consumer) {
DomainObjectAdapter domainObj = fileManager.getOpenedDomainObject(getPathname());
if (domainObj != null) {
if (!domainObj.addConsumer(consumer)) {
fileManager.clearDomainObject(getPathname());
throw new IllegalStateException("Domain Object is closed: " + domainObj.getName());
}
}
return domainObj;
}
@Override
public DomainObject getDomainObject(Object consumer, boolean okToUpgrade, boolean okToRecover,
TaskMonitor monitor) throws VersionException, IOException, CancelledException {
return getFileData().getDomainObject(consumer, okToUpgrade, okToRecover,
monitor != null ? monitor : TaskMonitorAdapter.DUMMY_MONITOR);
}
@Override
public DomainObject getReadOnlyDomainObject(Object consumer, int version, TaskMonitor monitor)
throws VersionException, IOException, CancelledException {
return getFileData().getReadOnlyDomainObject(consumer, version,
monitor != null ? monitor : TaskMonitorAdapter.DUMMY_MONITOR);
}
@Override
public DomainObject getImmutableDomainObject(Object consumer, int version, TaskMonitor monitor)
throws VersionException, IOException, CancelledException {
return getFileData().getImmutableDomainObject(consumer, version,
monitor != null ? monitor : TaskMonitorAdapter.DUMMY_MONITOR);
}
@Override
public void save(TaskMonitor monitor) throws IOException, CancelledException {
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname());
if (dobj == null) {
throw new AssertException("Cannot save, domainObj not open");
}
if (fileSystem.isReadOnly()) {
throw new ReadOnlyException("Cannot save to read-only project");
}
if (isReadOnly()) {
throw new ReadOnlyException("Cannot save to read-only file");
}
dobj.save(null, monitor != null ? monitor : TaskMonitorAdapter.DUMMY_MONITOR);
}
@Override
public boolean canSave() {
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname());
if (dobj == null) {
return false;
}
return dobj.canSave();
}
@Override
public boolean canRecover() {
try {
return getFileData().canRecover();
}
catch (IOException e) {
fileError(e);
}
return false;
}
@Override
public boolean takeRecoverySnapshot() throws IOException {
return getFileData().takeRecoverySnapshot();
}
@Override
public boolean isInWritableProject() {
return !fileSystem.isReadOnly();
}
@Override
public long getLastModifiedTime() {
try {
return getFileData().getLastModifiedTime();
}
catch (IOException e) {
fileError(e);
}
return 0;
}
@Override
public Icon getIcon(boolean disabled) {
try {
return getFileData().getIcon(disabled);
}
catch (IOException e) {
fileError(e);
}
return GhidraFileData.UNSUPPORTED_FILE_ICON;
}
@Override
public boolean isCheckedOut() {
try {
return getFileData().isCheckedOut();
}
catch (IOException e) {
fileError(e);
}
return false;
}
@Override
public boolean isCheckedOutExclusive() {
try {
return getFileData().isCheckedOutExclusive();
}
catch (IOException e) {
fileError(e);
}
return false;
}
@Override
public boolean modifiedSinceCheckout() {
try {
return getFileData().modifiedSinceCheckout();
}
catch (IOException e) {
fileError(e);
}
return false;
}
@Override
public boolean canCheckout() {
try {
return getFileData().canCheckout();
}
catch (IOException e) {
fileError(e);
}
return false;
}
@Override
public boolean canCheckin() {
try {
return getFileData().canCheckin();
}
catch (IOException e) {
fileError(e);
}
return false;
}
@Override
public boolean canMerge() {
try {
return getFileData().canMerge();
}
catch (IOException e) {
fileError(e);
}
return false;
}
@Override
public boolean canAddToRepository() {
try {
return getFileData().canAddToRepository();
}
catch (IOException e) {
fileError(e);
}
return false;
}
@Override
public void setReadOnly(boolean state) throws IOException {
getFileData().setReadOnly(state);
}
@Override
public boolean isReadOnly() {
try {
return getFileData().isReadOnly();
}
catch (IOException e) {
fileError(e);
}
return true;
}
@Override
public boolean isVersionControlSupported() {
try {
return getFileData().isVersionControlSupported();
}
catch (IOException e) {
fileError(e);
}
return false;
}
@Override
public boolean isVersioned() {
try {
return getFileData().isVersioned();
}
catch (IOException e) {
fileError(e);
}
return false;
}
@Override
public boolean isHijacked() {
try {
return getFileData().isHijacked();
}
catch (IOException e) {
fileError(e);
}
return false;
}
@Override
public int getLatestVersion() {
try {
return getFileData().getLatestVersion();
}
catch (IOException e) {
fileError(e);
}
return 0;
}
@Override
public boolean isLatestVersion() {
return true;
}
@Override
public int getVersion() {
try {
return getFileData().getVersion();
}
catch (IOException e) {
fileError(e);
}
return -1;
}
@Override
public Version[] getVersionHistory() throws IOException {
return getFileData().getVersionHistory();
}
@Override
public void addToVersionControl(String comment, boolean keepCheckedOut, TaskMonitor monitor)
throws IOException, CancelledException {
getFileData().addToVersionControl(comment, keepCheckedOut, monitor);
}
@Override
public boolean checkout(boolean exclusive, TaskMonitor monitor) throws IOException,
CancelledException {
return getFileData().checkout(exclusive,
monitor != null ? monitor : TaskMonitorAdapter.DUMMY_MONITOR);
}
@Override
public void checkin(CheckinHandler checkinHandler, boolean okToUpgrade, TaskMonitor monitor)
throws IOException, VersionException, CancelledException {
getFileData().checkin(checkinHandler, okToUpgrade,
monitor != null ? monitor : TaskMonitorAdapter.DUMMY_MONITOR);
}
@Override
public void merge(boolean okToUpgrade, TaskMonitor monitor) throws IOException,
VersionException, CancelledException {
getFileData().merge(okToUpgrade,
monitor != null ? monitor : TaskMonitorAdapter.DUMMY_MONITOR);
}
@Override
public void undoCheckout(boolean keep) throws IOException {
getFileData().undoCheckout(keep, false);
}
@Override
public void terminateCheckout(long checkoutId) throws IOException {
getFileData().terminateCheckout(checkoutId);
}
@Override
public ItemCheckoutStatus[] getCheckouts() throws IOException {
return getFileData().getCheckouts();
}
@Override
public ItemCheckoutStatus getCheckoutStatus() throws IOException {
return getFileData().getCheckoutStatus();
}
@Override
public void delete() throws IOException {
getFileData().delete();
}
@Override
public void delete(int version) throws IOException {
getFileData().delete(version);
}
@Override
public GhidraFile moveTo(DomainFolder newParent) throws IOException {
GhidraFolder newGhidraParent = (GhidraFolder) newParent;
return getFileData().moveTo(newGhidraParent.getFolderData());
}
@Override
public DomainFile copyTo(DomainFolder newParent, TaskMonitor monitor) throws IOException,
CancelledException {
GhidraFolder newGhidraParent = (GhidraFolder) newParent; // assumes single implementation
return getFileData().copyTo(newGhidraParent.getFolderData(),
monitor != null ? monitor : TaskMonitorAdapter.DUMMY_MONITOR);
}
@Override
public DomainFile copyVersionTo(int version, DomainFolder destFolder, TaskMonitor monitor)
throws IOException, CancelledException {
GhidraFolder destGhidraFolder = (GhidraFolder) destFolder; // assumes single implementation
return getFileData().copyVersionTo(version, destGhidraFolder.getFolderData(),
monitor != null ? monitor : TaskMonitorAdapter.DUMMY_MONITOR);
}
/**
* Copy this file to make a private file if it is versioned. This method should be called
* only when a non shared project is being converted to a shared project.
* @throws IOException
*/
void convertToPrivateFile(TaskMonitor monitor) throws IOException, CancelledException {
getFileData().convertToPrivateFile(
monitor != null ? monitor : TaskMonitorAdapter.DUMMY_MONITOR);
}
@Override
public ArrayList<?> getConsumers() {
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname());
if (dobj == null) {
return new ArrayList<Object>();
}
return dobj.getConsumerList();
}
@Override
public boolean isChanged() {
DomainObjectAdapter dobj = fileManager.getOpenedDomainObject(getPathname());
return dobj != null && dobj.isChanged();
}
@Override
public boolean isOpen() {
return fileManager.getOpenedDomainObject(getPathname()) != null;
}
@Override
public boolean isBusy() {
synchronized (fileSystem) {
try {
return getFileData().isBusy();
}
catch (IOException e) {
fileError(e);
}
}
return false;
}
@Override
public void packFile(File file, TaskMonitor monitor) throws IOException, CancelledException {
getFileData().packFile(file, monitor != null ? monitor : TaskMonitorAdapter.DUMMY_MONITOR);
}
@Override
public Map<String, String> getMetadata() {
try {
return getFileData().getMetadata();
}
catch (IOException e) {
fileError(e);
}
return new HashMap<String, String>();
}
void fileChanged() {
try {
getFileData().getParent().fileChanged(name);
}
catch (IOException e) {
fileError(e);
}
}
@Override
public long length() throws IOException {
return getFileData().length();
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof GhidraFile)) {
return false;
}
GhidraFile other = (GhidraFile) obj;
if (fileManager != other.fileManager) {
return false;
}
return getPathname().equals(other.getPathname());
}
@Override
public int hashCode() {
return getPathname().hashCode();
}
@Override
public String toString() {
return fileManager.getProjectLocator().getName() + ":" + getPathname();
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,396 @@
/* ###
* 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.framework.data;
import java.io.*;
import java.util.List;
import ghidra.framework.model.*;
import ghidra.framework.store.FileSystem;
import ghidra.framework.store.local.LocalFileSystem;
import ghidra.util.InvalidNameException;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.TaskMonitor;
import ghidra.util.task.TaskMonitorAdapter;
public class GhidraFolder implements DomainFolder {
private ProjectFileManager fileManager;
private LocalFileSystem fileSystem;
private FileSystem versionedFileSystem;
private DomainFolderChangeListener listener;
private GhidraFolder parent;
private String name;
GhidraFolder(ProjectFileManager fileManager, DomainFolderChangeListener listener) {
this.fileManager = fileManager;
this.fileSystem = fileManager.getLocalFileSystem();
this.versionedFileSystem = fileManager.getVersionedFileSystem();
this.listener = listener;
this.name = FileSystem.SEPARATOR;
}
GhidraFolder(GhidraFolder parent, String name) {
this.parent = parent;
this.name = name;
this.fileManager = parent.getProjectFileManager();
this.fileSystem = parent.getLocalFileSystem();
this.versionedFileSystem = parent.getVersionedFileSystem();
this.listener = parent.getChangeListener();
}
LocalFileSystem getLocalFileSystem() {
return fileSystem;
}
FileSystem getVersionedFileSystem() {
return versionedFileSystem;
}
LocalFileSystem getUserFileSystem() {
return fileManager.getUserFileSystem();
}
DomainFolderChangeListener getChangeListener() {
return listener;
}
ProjectFileManager getProjectFileManager() {
return fileManager;
}
GhidraFileData getFileData(String fileName) throws FileNotFoundException, IOException {
GhidraFileData fileData = getFolderData().getFileData(fileName, false);
if (fileData == null) {
throw new FileNotFoundException("file " + getPathname(fileName) + " not found");
}
return fileData;
}
GhidraFolderData getFolderPathData(String folderPath) throws FileNotFoundException {
GhidraFolderData parentData = (folderPath.startsWith(FileSystem.SEPARATOR))
? fileManager.getRootFolderData() : getFolderData();
GhidraFolderData folderData = parentData.getFolderPathData(folderPath, false);
if (folderData == null) {
String path = (folderPath.startsWith(FileSystem.SEPARATOR)) ? folderPath
: getPathname(folderPath);
throw new FileNotFoundException("folder " + path + " not found");
}
return folderData;
}
GhidraFolderData getFolderData() throws FileNotFoundException {
if (parent == null) {
return fileManager.getRootFolderData();
}
GhidraFolderData folderData = parent.getFolderData().getFolderData(name, false);
if (folderData == null) {
throw new FileNotFoundException("folder " + getPathname() + " not found");
}
return folderData;
}
/**
* Create folder hierarchy in local filesystem if it does not already exist
* @param folderName
* @return folder data
* @throws IOException error while creating folder
*/
private GhidraFolderData createFolderData(String folderName) throws IOException {
synchronized (fileSystem) {
GhidraFolderData parentData =
parent == null ? fileManager.getRootFolderData() : createFolderData();
GhidraFolderData folderData = parentData.getFolderData(folderName, false);
if (folderData == null) {
try {
folderData = parentData.createFolder(folderName);
}
catch (InvalidNameException e) {
throw new IOException(e);
}
}
return folderData;
}
}
private GhidraFolderData createFolderData() throws IOException {
GhidraFolderData rootFolderData = fileManager.getRootFolderData();
if (parent == null) {
return rootFolderData;
}
return parent.createFolderData(name);
}
/**
* Refresh folder data - used for testing only
* @throws IOException
*/
void refreshFolderData() throws IOException {
getFolderData().refresh(false, true, TaskMonitorAdapter.DUMMY_MONITOR);
}
@Override
public int compareTo(DomainFolder df) {
return name.compareToIgnoreCase(df.getName());
}
@Override
public String getName() {
return name;
}
@Override
public GhidraFolder setName(String newName) throws InvalidNameException, IOException {
return getFolderData().setName(newName);
}
@Override
public ProjectLocator getProjectLocator() {
return fileManager.getProjectLocator();
}
@Override
public ProjectFileManager getProjectData() {
return fileManager;
}
String getPathname(String childName) {
String path = getPathname();
if (path.length() != FileSystem.SEPARATOR.length()) {
path += FileSystem.SEPARATOR;
}
path += childName;
return path;
}
@Override
public String getPathname() {
if (parent == null) {
return FileSystem.SEPARATOR;
}
String path = parent.getPathname();
if (path.length() != FileSystem.SEPARATOR.length()) {
path += FileSystem.SEPARATOR;
}
path += name;
return path;
}
@Override
public boolean isInWritableProject() {
return !getProjectData().getLocalFileSystem().isReadOnly();
}
@Override
public DomainFolder getParent() {
return parent;
}
@Override
public GhidraFolder[] getFolders() {
synchronized (fileSystem) {
try {
GhidraFolderData folderData = getFolderData();
List<String> folderNames = folderData.getFolderNames();
int count = folderNames.size();
GhidraFolder[] folders = new GhidraFolder[count];
for (int i = 0; i < count; i++) {
folders[i] = new GhidraFolder(this, folderNames.get(i));
}
return folders;
}
catch (FileNotFoundException e) {
return new GhidraFolder[0];
}
}
}
@Override
public GhidraFolder getFolder(String folderName) {
synchronized (fileSystem) {
try {
GhidraFolderData folderData = getFolderData();
return folderData.getDomainFolder(folderName);
}
catch (FileNotFoundException e) {
// ignore
}
return null;
}
}
@Override
public boolean isEmpty() {
synchronized (fileSystem) {
try {
GhidraFolderData folderData = getFolderData();
return folderData.isEmpty();
}
catch (FileNotFoundException e) {
return false; // TODO: what should we return if folder not found
}
}
}
@Override
public GhidraFile[] getFiles() {
synchronized (fileSystem) {
try {
GhidraFolderData folderData = getFolderData();
List<String> fileNames = folderData.getFileNames();
int count = fileNames.size();
GhidraFile[] files = new GhidraFile[count];
for (int i = 0; i < count; i++) {
files[i] = new GhidraFile(this, fileNames.get(i));
}
return files;
}
catch (FileNotFoundException e) {
return new GhidraFile[0];
}
}
}
@Override
public GhidraFile getFile(String fileName) {
synchronized (fileSystem) {
GhidraFolderData folderData;
try {
folderData = getFolderData();
}
catch (FileNotFoundException e) {
return null; // exception occurs if this folder has been deleted.
}
try {
if (folderData.containsFile(fileName)) {
return new GhidraFile(this, fileName);
}
}
catch (IOException e) {
Msg.error(this, "file error for " + parent.getPathname(fileName), e);
}
return null;
}
}
@Override
public DomainFile createFile(String fileName, DomainObject obj, TaskMonitor monitor)
throws InvalidNameException, IOException, CancelledException {
return createFolderData().createFile(fileName, obj,
monitor != null ? monitor : TaskMonitorAdapter.DUMMY_MONITOR);
}
@Override
public DomainFile createFile(String fileName, File packFile, TaskMonitor monitor)
throws InvalidNameException, IOException, CancelledException {
return createFolderData().createFile(fileName, packFile,
monitor != null ? monitor : TaskMonitorAdapter.DUMMY_MONITOR);
}
@Override
public GhidraFolder createFolder(String folderName) throws InvalidNameException, IOException {
return createFolderData().createFolder(folderName).getDomainFolder();
}
@Override
public void delete() throws IOException {
try {
getFolderData().delete();
}
catch (FileNotFoundException e) {
// ignore
}
}
@Override
public GhidraFolder moveTo(DomainFolder newParent) throws IOException {
if (parent == null) {
throw new UnsupportedOperationException("root folder may not be moved");
}
GhidraFolderData folderData = getFolderData();
GhidraFolder newGhidraParent = (GhidraFolder) newParent; // assumes single implementation
return folderData.moveTo(newGhidraParent.getFolderData());
}
@Override
public GhidraFolder copyTo(DomainFolder newParent, TaskMonitor monitor)
throws IOException, CancelledException {
GhidraFolderData folderData = getFolderData();
GhidraFolder newGhidraParent = (GhidraFolder) newParent; // assumes single implementation
return folderData.copyTo(newGhidraParent.getFolderData(),
monitor != null ? monitor : TaskMonitorAdapter.DUMMY_MONITOR);
}
/**
* used for testing
*/
boolean privateExists() {
try {
return getFolderData().privateExists();
}
catch (FileNotFoundException e) {
return false;
}
}
/**
* used for testing
*/
boolean sharedExists() {
try {
return getFolderData().sharedExists();
}
catch (FileNotFoundException e) {
return false;
}
}
@Override
public void setActive() {
listener.domainFolderSetActive(this);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof GhidraFolder)) {
return false;
}
GhidraFolder other = (GhidraFolder) obj;
if (fileManager != other.fileManager) {
return false;
}
return getPathname().equals(other.getPathname());
}
@Override
public int hashCode() {
return getPathname().hashCode();
}
@Override
public String toString() {
ProjectLocator projectLocator = fileManager.getProjectLocator();
if (projectLocator.isTransient()) {
return fileManager.getProjectLocator().getName() + getPathname();
}
return fileManager.getProjectLocator().getName() + ":" + getPathname();
}
}

View file

@ -0,0 +1,290 @@
/* ###
* 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.framework.data;
import ghidra.util.Issue;
import ghidra.util.SystemUtilities;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.*;
class LockingTaskMonitor implements TaskMonitor {
private DomainObjectAdapterDB dobj;
private final String title;
private boolean isCanceled = false;
private boolean cancelEnabled = true;
private final boolean hasProgress;
private long maxProgress;
private long curProgress;
private boolean indeterminate;
private boolean showProgressValue = true;
private String msg;
private MyTaskDialog taskDialog;
private WeakSet<IssueListener> issueListeners;
/**
* Constructs a locking task handler for a locked dobj. The setCompleted() method must be
* invoked to dispose this object and release the lock. This should
* be done in a try/finally block to avoid accidentally locking the
* domain object indefinitely.
* @param dobj domain object
* @param hasProgress
* @param title task title
*/
LockingTaskMonitor(DomainObjectAdapterDB dobj, boolean hasProgress, String title) {
this.dobj = dobj;
this.hasProgress = hasProgress;
this.title = title;
}
DomainObjectAdapterDB getDomainObject() {
return dobj;
}
/**
* Display a modal task dialog associated with this locking task.
* This method will not return until the task has completed and the
* lock has been released.
*/
void waitForTaskCompletion() {
synchronized (this) {
if (dobj == null) {
return;
}
if (taskDialog == null) {
taskDialog = new MyTaskDialog();
}
else {
try {
// dialog already displayed - wait for releaseLock to occur
wait();
}
catch (InterruptedException e) {
// ignore
}
return;
}
}
// show dialog without synchronization
MyTaskDialog dialog = taskDialog;
if (dialog != null) {
SystemUtilities.runSwingNow(() -> dialog.show(0));
}
}
/*
* @see ghidra.util.task.TaskMonitor#isCancelled()
*/
@Override
public synchronized boolean isCancelled() {
return taskDialog != null ? taskDialog.isCancelled() : isCanceled;
}
/**
* Release associated domain object lock and close dialog.
* All blocked waits will be notified.
*/
synchronized void releaseLock() {
if (dobj != null) {
dobj.unlock(this);
dobj = null;
}
if (taskDialog != null) {
taskDialog.taskProcessed();
taskDialog = null;
}
notifyAll();
}
/*
* @see ghidra.util.task.TaskMonitor#setMessage(java.lang.String)
*/
@Override
public synchronized void setMessage(String msg) {
this.msg = msg;
if (taskDialog != null) {
taskDialog.setMessage(msg);
}
}
/*
* @see ghidra.util.task.TaskMonitor#setProgress(int)
*/
@Override
public synchronized void setProgress(long value) {
this.curProgress = value;
if (taskDialog != null) {
taskDialog.setProgress(value);
}
}
@Override
public synchronized void initialize(long max) {
setMaximum(max);
setProgress(0);
}
@Override
public synchronized void setMaximum(long max) {
this.maxProgress = max;
if (taskDialog != null) {
taskDialog.setMaximum(max);
}
}
@Override
public long getMaximum() {
return maxProgress;
}
@Override
public void setShowProgressValue(boolean showProgressValue) {
this.showProgressValue = showProgressValue;
if (taskDialog != null) {
taskDialog.setShowProgressValue(showProgressValue);
}
}
@Override
public void setIndeterminate(boolean indeterminate) {
this.indeterminate = indeterminate;
if (taskDialog != null) {
taskDialog.setIndeterminate(indeterminate);
}
}
/*
* @see ghidra.util.task.TaskMonitor#setCancelEnabled(boolean)
*/
@Override
public synchronized void setCancelEnabled(boolean enable) {
this.cancelEnabled = enable;
if (taskDialog != null) {
taskDialog.setCancelEnabled(enable);
}
}
/**
* @see ghidra.util.task.TaskMonitor#isCancelEnabled()
*/
@Override
public synchronized boolean isCancelEnabled() {
return taskDialog != null ? taskDialog.isCancelEnabled() : cancelEnabled;
}
/*
* @see ghidra.util.task.TaskMonitor#cancel()
*/
@Override
public synchronized void cancel() {
this.isCanceled = true;
if (taskDialog != null) {
taskDialog.cancel();
}
}
/*
* @see ghidra.util.task.TaskMonitor#clearCanceled()
*/
@Override
public void clearCanceled() {
this.isCanceled = false;
if (taskDialog != null) {
taskDialog.clearCanceled();
}
}
/*
* @see ghidra.util.task.TaskMonitor#checkCanceled()
*/
@Override
public void checkCanceled() throws CancelledException {
if (isCancelled()) {
throw new CancelledException();
}
}
/**
* @see ghidra.util.task.TaskMonitor#incrementProgress(int)
*/
@Override
public void incrementProgress(long incrementAmount) {
setProgress(curProgress + incrementAmount);
}
/**
* @see ghidra.util.task.TaskMonitor#getProgress()
*/
@Override
public long getProgress() {
return curProgress;
}
private class MyTaskDialog extends TaskDialog {
MyTaskDialog() {
super(title, true, true, hasProgress);
setCancelEnabled(cancelEnabled);
if (hasProgress) {
initialize(maxProgress);
setProgress(curProgress);
setIndeterminate(indeterminate);
setShowProgressValue(showProgressValue);
}
if (msg != null) {
setMessage(msg);
}
}
}
@Override
public void addCancelledListener(CancelledListener listener) {
throw new UnsupportedOperationException();
}
@Override
public void removeCancelledListener(CancelledListener listener) {
throw new UnsupportedOperationException();
}
@Override
public void addIssueListener(IssueListener listener) {
if (issueListeners == null) {
issueListeners = WeakDataStructureFactory.createCopyOnWriteWeakSet();
}
}
@Override
public void removeIssueListener(IssueListener listener) {
if (issueListeners != null) {
issueListeners.remove(listener);
}
}
@Override
public void reportIssue(Issue issue) {
if (issueListeners != null) {
for (IssueListener listener : issueListeners) {
listener.issueReported(issue);
}
}
}
}

View file

@ -0,0 +1,72 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.data;
import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import db.*;
class MetadataManager {
private final static String TABLE_NAME = "Metadata";
private final static Schema SCHEMA = new Schema(0,"ID",
new Class[] {StringField.class, StringField.class},
new String[] {"Key", "Value"});
static void loadData(DomainObjectAdapterDB dobj, Map<String, String> metadata) throws IOException {
metadata.clear();
Table table = dobj.getDBHandle().getTable(TABLE_NAME);
if (table != null) {
RecordIterator iterator = table.iterator();
while(iterator.hasNext()) {
Record record = iterator.next();
String key = record.getString(0);
String value = record.getString(1);
metadata.put(key, value);
}
}
}
static void saveData(DomainObjectAdapterDB dobj, Map<String, String> metadata) throws IOException {
int transactionID = dobj.startTransaction("Update Metadata");
try {
Table table = dobj.getDBHandle().getTable(TABLE_NAME);
if (table == null) {
table = dobj.getDBHandle().createTable(TABLE_NAME, SCHEMA);
}
else {
table.deleteAll();
}
Iterator<String> keyIterator = metadata.keySet().iterator();
long id = 1;
while(keyIterator.hasNext()) {
String key = keyIterator.next();
String value = metadata.get(key);
Record record = SCHEMA.createRecord(id++);
record.setString(0, key);
record.setString(1, value);
table.putRecord(record);
}
}
finally {
dobj.endTransaction(transactionID, true);
}
}
}

View file

@ -0,0 +1,347 @@
/* ###
* 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.framework.data;
import java.beans.PropertyEditor;
import java.io.IOException;
import java.util.*;
import db.*;
import ghidra.framework.options.*;
import ghidra.util.HelpLocation;
import ghidra.util.SystemUtilities;
import ghidra.util.exception.ClosedException;
/**
*
*
*/
class OptionsDB extends AbstractOptions {
private static final String PROPERTY_TABLE_NAME = "Property Table";
private final static Schema PROPERTY_SCHEMA = new Schema(0, StringField.class, "Property Name",
new Class[] { StringField.class, ByteField.class }, new String[] { "Value", "Type" });
private static final int VALUE_COL = 0;
private static final int TYPE_COL = 1;
private Table propertyTable;
private DomainObjectAdapterDB domainObj;
/**
*/
OptionsDB(DomainObjectAdapterDB domainObj) {
super("");
this.domainObj = domainObj;
propertyTable = domainObj.getDBHandle().getTable(PROPERTY_TABLE_NAME);
}
/**
* Perform property alterations as specified by the map provided. This must be called
* immediately following construction before any other instance method is invoked
* (with the exception of checkAlterations)
* @param propertyAlterations oldPath-to-newPath property mappings. Paths must not end
* with the '.' path separator. If the newPath is null or conflicts with an existing stored property,
* the corresponding oldPath properties will be removed.
* @throws IllegalStateException if list has been manipulated since construction
* @throws IllegalArgumentException if invalid property alterations are provided
* @throws IOException
*/
synchronized void performAlterations(Map<String, String> propertyAlterations)
throws IOException {
if (propertyAlterations == null) {
return;
}
if (!super.getOptionNames().isEmpty()) {
throw new IllegalStateException("property list alterations not permitted");
}
for (String oldPath : propertyAlterations.keySet()) {
checkAlterationPath(oldPath, false);
String newPath = propertyAlterations.get(oldPath);
checkAlterationPath(newPath, true);
if (newPath == null || !moveProperties(oldPath, newPath)) {
removeProperties(oldPath);
}
}
}
private void checkAlterationPath(String path, boolean nullIsOK) {
if (!nullIsOK && path == null) {
throw new IllegalArgumentException("property alteration old-path may not be null");
}
if (path != null && path.endsWith(DELIMITER_STRING)) {
throw new IllegalArgumentException("property alteration paths must not end with '" +
DELIMITER + "': " + path);
}
}
private synchronized boolean moveProperties(String oldPath, String newPath) throws IOException {
String oldSubListPath = oldPath + ".";
String newSubListPath = newPath + ".";
// check for move conflict
if (propertyTable.getRecord(new StringField(newPath)) != null) {
return false;
}
RecordIterator iterator = propertyTable.iterator(new StringField(newSubListPath));
Record rec = iterator.next();
if (rec != null) {
String keyName = ((StringField) rec.getKeyField()).getString();
if (keyName.startsWith(newSubListPath)) {
return false;
}
}
// move records
ArrayList<Record> list = new ArrayList<Record>();
rec = propertyTable.getRecord(new StringField(oldPath));
if (rec != null) {
propertyTable.deleteRecord(new StringField(oldPath));
rec.setKey(new StringField(newPath));
list.add(rec);
}
iterator = propertyTable.iterator(new StringField(oldSubListPath));
while (iterator.hasNext()) {
rec = iterator.next();
String keyName = ((StringField) rec.getKeyField()).getString();
if (keyName.startsWith(oldSubListPath)) {
iterator.delete();
rec.setKey(new StringField(newSubListPath +
keyName.substring(oldSubListPath.length())));
list.add(rec);
}
else {
break;
}
}
for (Record updatedRec : list) {
propertyTable.putRecord(updatedRec);
}
return true;
}
private synchronized void removeProperties(String path) throws IOException {
String subListPath = path + ".";
// remove records
RecordIterator iterator = propertyTable.iterator(new StringField(path));
while (iterator.hasNext()) {
Record rec = iterator.next();
String keyName = ((StringField) rec.getKeyField()).getString();
if (keyName.equals(path)) {
iterator.delete();
}
else if (keyName.startsWith(subListPath)) {
iterator.delete();
}
}
}
@Override
public synchronized void removeOption(String propertyName) {
super.removeOption(propertyName);
removePropertyFromDB(propertyName);
}
private void removePropertyFromDB(String propertyName) {
try {
StringField key = new StringField(propertyName);
if (propertyTable.hasRecord(key)) {
propertyTable.deleteRecord(key);
}
}
catch (IOException e) {
domainObj.dbError(e);
}
}
synchronized void clearCache() {
for (Option option : valueMap.values()) {
DBOption dbOption = (DBOption) option;
dbOption.clearCache();
}
}
@Override
public synchronized List<String> getOptionNames() {
Set<String> names = new HashSet<String>(valueMap.keySet());
names.addAll(aliasMap.keySet());
try {
if (propertyTable != null) {
RecordIterator recIt = propertyTable.iterator();
while (recIt.hasNext()) {
Record rec = recIt.next();
names.add(rec.getKeyField().getString());
}
}
}
catch (IOException e) {
domainObj.dbError(e);
}
List<String> optionNames = new ArrayList<String>(names);
Collections.sort(optionNames);
return optionNames;
}
@Override
public synchronized boolean contains(String optionName) {
if (super.contains(optionName)) {
return true;
}
try {
if (propertyTable != null) {
RecordIterator recIt = propertyTable.iterator();
while (recIt.hasNext()) {
Record rec = recIt.next();
String key = rec.getKeyField().getString();
if (optionName.equals(key)) {
return true;
}
}
}
}
catch (IOException e) {
domainObj.dbError(e);
}
return false;
}
private Record getPropertyRecord(String propertyName) {
if (propertyTable == null) {
return null;
}
try {
return propertyTable.getRecord(new StringField(propertyName));
}
catch (ClosedException e) {
return null; // ignore closed file
}
catch (IOException e) {
domainObj.dbError(e);
}
return null;
}
private void putRecord(Record rec) {
try {
if (propertyTable == null) {
propertyTable =
domainObj.getDBHandle().createTable(PROPERTY_TABLE_NAME, PROPERTY_SCHEMA);
}
propertyTable.putRecord(rec);
}
catch (IOException e) {
domainObj.dbError(e);
}
}
// Property getProperty(String propertyName) {
// Record rec = getPropertyRecord(propertyName);
// if (rec == null) {
// return null;
// }
// int propertyOrdinal = rec.getByteValue(TYPE_COL);
// OptionType propertyType = OptionType.values()[propertyOrdinal];
// return createProperty(propertyName, propertyType, null);
// }
class DBOption extends Option {
private Object value = null;
private boolean isCached = false;
protected DBOption(String name, OptionType type, String description, HelpLocation help,
Object defaultValue, boolean isRegistered, PropertyEditor editor) {
super(name, type, description, help, defaultValue, isRegistered, editor);
}
@Override
public Object getCurrentValue() {
if (!isCached) {
Record rec = getPropertyRecord(getName());
if (rec == null) {
value = getDefaultValue();
}
else {
OptionType optionType = OptionType.values()[rec.getByteValue(TYPE_COL)];
// Make sure the optionType in the database matches the current
// registered type. If not, it implies the option type has been changed
// in the code. In that case, ignore the old value.
if (optionType == getOptionType()) {
value = optionType.convertStringToObject(rec.getString(VALUE_COL));
}
}
}
isCached = true;
return value;
}
@Override
public void doSetCurrentValue(Object newValue) {
if (SystemUtilities.isEqual(getCurrentValue(), newValue)) {
return;
}
this.value = newValue;
this.isCached = true;
if (SystemUtilities.isEqual(newValue, getDefaultValue())) { // changing back to default value
removePropertyFromDB(getName());
}
else {
Record rec = PROPERTY_SCHEMA.createRecord(new StringField(getName()));
OptionType optionType = getOptionType();
rec.setByteValue(TYPE_COL, (byte) (optionType.ordinal()));
rec.setString(VALUE_COL, optionType.convertObjectToString(newValue));
putRecord(rec);
}
}
void clearCache() {
value = null;
isCached = false;
}
}
@Override
protected Option createRegisteredOption(String optionName, OptionType type, String description,
HelpLocation help, Object defaultValue, PropertyEditor editor) {
return new DBOption(optionName, type, description, help, defaultValue, true, editor);
}
@Override
protected Option createUnregisteredOption(String optionName, OptionType type,
Object defaultValue) {
if (type == OptionType.NO_TYPE) {
Record record = getPropertyRecord(optionName);
if (record != null) {
type = OptionType.values()[record.getByteValue(TYPE_COL)];
}
}
return new DBOption(optionName, type, null, null, defaultValue, false, null);
}
@Override
protected boolean notifyOptionChanged(String optionName, Object oldValue, Object newValue) {
return domainObj.propertyChanged(name, oldValue, newValue);
}
}

View file

@ -0,0 +1,27 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.data;
import ghidra.framework.model.DomainFolderChangeListener;
public class RootGhidraFolder extends GhidraFolder {
RootGhidraFolder(ProjectFileManager fileManager, DomainFolderChangeListener listener) {
super(fileManager, listener);
}
}

View file

@ -0,0 +1,54 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.data;
import ghidra.framework.model.DomainFolderChangeListener;
import ghidra.framework.store.FileSystem;
public class RootGhidraFolderData extends GhidraFolderData {
RootGhidraFolderData(ProjectFileManager fileManager, DomainFolderChangeListener listener) {
super(fileManager, listener);
}
@Override
GhidraFolder getDomainFolder() {
return new RootGhidraFolder(getProjectFileManager(), getChangeListener());
}
/**
* Provided for testing use only
* @param fs
*/
void setVersionedFileSystem(FileSystem fs) {
versionedFileSystem = fs;
}
@Override
boolean privateExists() {
return true;
}
/**
* used for testing
*/
@Override
boolean sharedExists() {
return true;
}
}

View file

@ -0,0 +1,195 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.data;
import ghidra.framework.model.AbortedTransactionListener;
import ghidra.framework.model.Transaction;
import java.io.IOException;
import java.util.ArrayList;
/**
* <code>SynchronizedTransaction</code> represents an atomic undoable operation performed
* on a synchronized set of domain objects.
*/
class SynchronizedTransaction implements Transaction {
private DomainObjectTransactionManager[] managers;
private int[] holdTransactionIds;
private boolean[] hasChanges;
private String[] descriptions;
private int[] activeCounts;
private int status = NOT_DONE;
private final long id;
SynchronizedTransaction(DomainObjectTransactionManager[] managers) {
this.managers = managers;
holdTransactionIds = new int[managers.length];
hasChanges = new boolean[managers.length];
descriptions = new String[managers.length];
activeCounts = new int[managers.length];
id = DomainObjectDBTransaction.getNextBaseId();
for (int i = 0; i < managers.length; i++) {
DomainObjectTransactionManager mgr = managers[i];
holdTransactionIds[i] = mgr.startTransaction(mgr.getDomainObject(), "", null, false,
false);
}
}
@Override
public String getDescription() {
StringBuffer buf = new StringBuffer();
for (int i = 0; i < managers.length; i++) {
if (descriptions[i] != null) {
if (buf.length() != 0) {
buf.append('\n');
}
buf.append(getDomainObjectName(managers[i]));
buf.append(": ");
buf.append(descriptions[i]);
}
}
return buf.toString();
}
private String getDomainObjectName(DomainObjectTransactionManager manager) {
DomainObjectAdapterDB domainObject = manager.getDomainObject();
return domainObject.getDomainFile().getName();
}
@Override
public long getID() {
return id;
}
@Override
public ArrayList<String> getOpenSubTransactions() {
ArrayList<String> list = new ArrayList<String>();
int status = getStatus();
if (status == ABORTED || status == COMMITTED) {
return list;
}
for (int i = 0; i < managers.length; i++) {
String name = getDomainObjectName(managers[i]);
for (String str : managers[i].getCurrentTransaction().getOpenSubTransactions()) {
list.add(name + ": " + str);
}
}
return list;
}
private boolean isActive() {
for (int activeCount : activeCounts) {
if (activeCount != 0) {
return true;
}
}
return false;
}
@Override
public int getStatus() {
if (status == ABORTED && isActive()) {
return NOT_DONE_BUT_ABORTED;
}
return status;
}
int addEntry(DomainObjectAdapterDB domainObj, String description,
AbortedTransactionListener listener) {
int index = findDomainObject(domainObj);
int txId = managers[index].startTransaction(domainObj, description, listener, false, false);
++activeCounts[index];
if (descriptions[index] == null && description != null && description.length() != 0) {
descriptions[index] = description;
}
return txId;
}
void endEntry(DomainObjectAdapterDB domainObj, int transactionID, boolean commit) {
int index = findDomainObject(domainObj);
managers[index].endTransaction(domainObj, transactionID, commit, false);
if (!commit) {
status = ABORTED;
}
--activeCounts[index];
if (!isActive() && status == NOT_DONE) {
status = COMMITTED;
}
}
private int findDomainObject(DomainObjectAdapterDB domainObj) {
for (int i = 0; i < managers.length; i++) {
if (managers[i].getDomainObject() == domainObj) {
return i;
}
}
throw new IllegalStateException("unknown domain object");
}
/**
* End all domain object transactions and keep track as to which ones
* resulted in a low-level transaction.
* @param commit indicates if all domain object hold transactions
* should be committed or rolled-back
* @return true if this transaction produced any low-level transaction
*/
boolean endAll(boolean commit) {
boolean hasChange = false;
for (int i = 0; i < managers.length; i++) {
Transaction transaction = managers[i].endTransaction(managers[i].getDomainObject(),
holdTransactionIds[i], commit, false);
if (commit && transaction.hasCommittedDBTransaction()) {
hasChanges[i] = true;
hasChange = true;
}
else {
descriptions[i] = null;
}
}
return hasChange;
}
void redo() throws IOException {
for (int i = 0; i < managers.length; i++) {
if (hasChanges[i]) {
managers[i].doRedo(false);
}
}
}
void undo() throws IOException {
for (int i = 0; i < managers.length; i++) {
if (hasChanges[i]) {
managers[i].doUndo(false);
}
}
}
@Override
public boolean hasCommittedDBTransaction() {
for (int i = 0; i < managers.length; i++) {
if (hasChanges[i]) {
return true;
}
}
return false;
}
}

View file

@ -0,0 +1,365 @@
/* ###
* 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.framework.data;
import java.io.IOException;
import java.util.LinkedList;
import ghidra.framework.model.*;
import ghidra.framework.store.LockException;
import ghidra.util.Msg;
class SynchronizedTransactionManager extends AbstractTransactionManager {
private LinkedList<SynchronizedTransaction> undoList = new LinkedList<>();
private LinkedList<SynchronizedTransaction> redoList = new LinkedList<>();
//private Map<DomainObjectAdapterDB, DomainObjectTransactionManager> domainObjects = new HashMap<DomainObjectAdapterDB, DomainObjectTransactionManager>();
private DomainObjectAdapterDB[] domainObjects = new DomainObjectAdapterDB[0];
private DomainObjectTransactionManager[] domainObjectTransactionManagers =
new DomainObjectTransactionManager[0];
private SynchronizedTransaction transaction;
SynchronizedTransactionManager() {
super();
}
@Override
DomainObjectAdapterDB[] getDomainObjects() {
return domainObjects;
}
@Override
synchronized void clearTransactions() {
for (DomainObjectTransactionManager mgr : domainObjectTransactionManagers) {
mgr.clearTransactions();
}
undoList.clear();
redoList.clear();
}
void addDomainObject(DomainObjectAdapterDB domainObj) throws LockException {
synchronized (this) {
AbstractTransactionManager mgr = domainObj.getTransactionManager();
if (!(mgr instanceof DomainObjectTransactionManager)) {
throw new IllegalArgumentException("domain object has invalid transaction manager");
}
if (isLocked() || mgr.isLocked() || getCurrentTransaction() != null ||
mgr.getCurrentTransaction() != null) {
throw new LockException("domain object(s) are busy/locked");
}
if (!mgr.lock("Transaction manager join")) {
throw new LockException("domain object is busy");
}
clearTransactions();
int count = domainObjects.length + 1;
DomainObjectAdapterDB[] updatedDomainObjects = new DomainObjectAdapterDB[count];
DomainObjectTransactionManager[] updatedManagers =
new DomainObjectTransactionManager[count];
System.arraycopy(domainObjects, 0, updatedDomainObjects, 0, domainObjects.length);
updatedDomainObjects[count - 1] = domainObj;
System.arraycopy(domainObjectTransactionManagers, 0, updatedManagers, 0,
domainObjectTransactionManagers.length);
updatedManagers[count - 1] =
(DomainObjectTransactionManager) domainObj.getTransactionManager();
domainObjects = updatedDomainObjects;
domainObjectTransactionManagers = updatedManagers;
domainObj.setTransactionManager(this);
updatedManagers[count - 1].lockCount = 0;
}
notifyUndoableListeners();
}
synchronized void removeDomainObject(DomainObjectAdapterDB domainObj) throws LockException {
if (getCurrentTransaction() != null) {
throw new LockException(
"domain object has open transaction: " + getCurrentTransaction().getDescription());
}
if (isLocked()) {
throw new LockException("domain object is locked!");
}
if (domainObj.getTransactionManager() != this) {
throw new IllegalArgumentException("domain object has different transaction manager");
}
int index = -1;
for (int i = 0; i < domainObjects.length; i++) {
if (domainObjects[i] == domainObj) {
index = i;
break;
}
}
if (index < 0) {
throw new IllegalArgumentException("invalid domain object");
}
clearTransactions();
DomainObjectTransactionManager restoredMgr = domainObjectTransactionManagers[index];
int count = domainObjects.length - 1;
DomainObjectAdapterDB[] updatedDomainObjects = new DomainObjectAdapterDB[count];
DomainObjectTransactionManager[] updatedManagers =
new DomainObjectTransactionManager[count];
System.arraycopy(domainObjects, 0, updatedDomainObjects, 0, index);
System.arraycopy(domainObjectTransactionManagers, 0, updatedManagers, 0, index);
if (index < count) {
System.arraycopy(domainObjects, index + 1, updatedDomainObjects, index, count - index);
System.arraycopy(domainObjectTransactionManagers, index + 1, updatedManagers, index,
count - index);
}
domainObjects = updatedDomainObjects;
domainObjectTransactionManagers = updatedManagers;
domainObj.setTransactionManager(restoredMgr);
restoredMgr.notifyUndoStackChanged();
if (count == 1) {
removeDomainObject(domainObjects[0]);
}
else {
notifyUndoableListeners();
}
}
@Override
void terminateTransaction(boolean rollback, boolean notify) {
if (transaction == null || transactionTerminated) {
return;
}
for (AbstractTransactionManager mgr : domainObjectTransactionManagers) {
mgr.terminateTransaction(rollback, false);
}
transactionTerminated = true;
if (notify) {
notifyEndTransaction();
}
}
@Override
synchronized int startTransaction(DomainObjectAdapterDB object, String description,
AbortedTransactionListener listener, boolean force, boolean notify) {
if (!force) {
verifyNoLock();
}
if (transaction == null) {
transactionTerminated = false;
transaction = new SynchronizedTransaction(domainObjectTransactionManagers);
int txId = transaction.addEntry(object, description, listener);
if (notify) {
notifyStartTransaction();
}
return txId;
}
if (transactionTerminated) {
Msg.warn(this,
"Aborted transaction still pending, new transaction will also be aborted: " +
description);
}
int txId = transaction.addEntry(object, description, listener);
if (notify) {
notifyStartTransaction();
}
return txId;
}
@Override
synchronized Transaction endTransaction(DomainObjectAdapterDB object, int transactionID,
boolean commit, boolean notify) {
if (transaction == null) {
throw new IllegalStateException("No transaction is open");
}
Transaction returnedTransaction = transaction;
transaction.endEntry(object, transactionID, commit && !transactionTerminated);
int status = transaction.getStatus();
if (status == Transaction.COMMITTED) {
boolean committed = transaction.endAll(true);
if (committed) {
redoList.clear();
undoList.addLast(transaction);
if (undoList.size() > NUM_UNDOS) {
undoList.removeFirst();
}
}
transaction = null;
if (notify) {
notifyEndTransaction();
}
}
else if (status == Transaction.ABORTED) {
if (!transactionTerminated) {
transaction.endAll(false);
}
transaction = null;
if (notify) {
notifyEndTransaction();
}
}
return returnedTransaction;
}
/**
* Returns the undo stack depth.
* (The number of items on the undo stack)
* This method is for JUnits.
* @return the undo stack depth
*/
@Override
int getUndoStackDepth() {
return undoList.size();
}
@Override
synchronized boolean canRedo() {
if (redoList.size() > 0) {
for (DomainObjectTransactionManager mgr : domainObjectTransactionManagers) {
if (mgr.canRedo()) {
return true;
}
}
}
return false;
}
@Override
synchronized boolean canUndo() {
if (undoList.size() > 0) {
for (DomainObjectTransactionManager mgr : domainObjectTransactionManagers) {
if (mgr.canUndo()) {
return true;
}
}
}
return false;
}
@Override
synchronized String getRedoName() {
if (redoList.size() > 0) {
Transaction t = redoList.getLast();
return t.getDescription();
}
return "";
}
@Override
synchronized String getUndoName() {
if (undoList.size() > 0) {
Transaction t = undoList.getLast();
return t.getDescription();
}
return "";
}
@Override
Transaction getCurrentTransaction() {
return transaction;
}
@Override
void doRedo(boolean notify) throws IOException {
if (canRedo()) {
SynchronizedTransaction t = redoList.removeLast();
undoList.addLast(t);
t.redo();
if (notify) {
notifyUndoableListeners();
}
}
}
@Override
void doUndo(boolean notify) throws IOException {
if (canUndo()) {
SynchronizedTransaction t = undoList.removeLast();
redoList.addLast(t);
t.undo();
if (notify) {
notifyUndoableListeners();
}
}
}
@Override
synchronized void clearUndo(boolean notifyListeners) {
if (!undoList.isEmpty() || !redoList.isEmpty()) {
undoList.clear();
redoList.clear();
for (DomainObjectTransactionManager mgr : domainObjectTransactionManagers) {
mgr.clearUndo(false);
}
if (notifyListeners) {
notifyUndoableListeners();
}
}
}
@Override
void doClose(DomainObjectAdapterDB object) {
try {
removeDomainObject(object);
}
catch (LockException e) {
throw new IllegalStateException(e);
}
object.getTransactionManager().close(object);
}
@Override
synchronized void addTransactionListener(DomainObjectAdapterDB object,
TransactionListener listener) {
for (DomainObjectTransactionManager mgr : domainObjectTransactionManagers) {
if (mgr.getDomainObject() == object) {
mgr.addTransactionListener(object, listener);
return;
}
}
throw new IllegalArgumentException("invalid domain object");
}
@Override
synchronized void removeTransactionListener(DomainObjectAdapterDB object,
TransactionListener listener) {
for (DomainObjectTransactionManager mgr : domainObjectTransactionManagers) {
if (mgr.getDomainObject() == object) {
mgr.removeTransactionListener(object, listener);
return;
}
}
throw new IllegalArgumentException("invalid domain object");
}
private void notifyUndoableListeners() {
for (DomainObjectTransactionManager mgr : domainObjectTransactionManagers) {
mgr.notifyUndoStackChanged();
}
}
private void notifyStartTransaction() {
for (DomainObjectTransactionManager mgr : domainObjectTransactionManagers) {
mgr.notifyStartTransaction(transaction);
}
}
private void notifyEndTransaction() {
for (DomainObjectTransactionManager mgr : domainObjectTransactionManagers) {
mgr.notifyEndTransaction();
}
}
}

View file

@ -0,0 +1,59 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.data;
import ghidra.framework.model.*;
import ghidra.framework.plugintool.*;
import ghidra.framework.plugintool.util.*;
/**
* Container object for the state of the tool to hold an XML element.
*/
public class ToolState {
protected PluginTool tool;
private UndoRedoToolState beforeState;
private UndoRedoToolState afterState;
/**
* Construct a new tool state.
* @param tool tool's state to save
* @param element element containing tool state
*/
public ToolState(PluginTool tool, DomainObject domainObject) {
this.tool = tool;
beforeState = tool.getUndoRedoToolState(domainObject);
}
/**
* Restore the tool's state after an undo
*/
public void restoreAfterUndo(DomainObject domainObject) {
beforeState.restoreTool(domainObject);
}
/**
* Restore the tool's state after an undo
*/
public void restoreAfterRedo(DomainObject domainObject) {
afterState.restoreTool(domainObject);
}
public void getAfterState(DomainObject domainObject) {
afterState = tool.getUndoRedoToolState(domainObject);
}
}

View file

@ -0,0 +1,36 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.data;
import ghidra.framework.PluggableServiceRegistry;
import ghidra.framework.model.DomainObject;
import ghidra.framework.plugintool.PluginTool;
public class ToolStateFactory {
static {
PluggableServiceRegistry.registerPluggableService(ToolStateFactory.class, new ToolStateFactory());
}
public static ToolState createToolState(PluginTool tool, DomainObject domainObject) {
ToolStateFactory toolStateFactory = PluggableServiceRegistry.getPluggableService(ToolStateFactory.class);
return toolStateFactory.doCreateToolState(tool, domainObject);
}
protected ToolState doCreateToolState(PluginTool tool, DomainObject domainObject) {
return new ToolState(tool, domainObject);
}
}

View file

@ -0,0 +1,80 @@
/* ###
* 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.framework.data;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;
import ghidra.framework.model.DomainFile;
/**
* Simple static class to keep track of transient domain file/domain objects.
* When new domain objects are created, they may not have an associated DomainFile.
* In this case, a DomainFileProxy is created to contain it. DomainFileProxy objects
* will add themselves to this Manager whenever a tool is using the associated
* DomainObject and will remove itself all the tools have released the domainObject.
*/
public class TransientDataManager {
private TransientDataManager() {
}
// Set implementation must be thread safe and not sensitive to the file's name changing
private static CopyOnWriteArraySet<DomainFileProxy> set = new CopyOnWriteArraySet<>();
/**
* Adds the given transient domain file to the list.
* @param domainFile the transient domain file to add to the list
*/
public static void addTransient(DomainFileProxy domainFile) {
// TODO This previously had a change to exclude files which do not need saving.
// Can't do this since versioned files may get changed later.
// Find out why files would need to be excluded here.
set.add(domainFile);
}
/**
* Removes the given transient domain file from the list.
* @param domainFile the transient domain file to remove
*/
public static void removeTransient(DomainFileProxy domainFile) {
set.remove(domainFile);
}
/**
* Removes all transients from the list.
*/
public static void clearAll() {
set.clear();
}
/**
* Populates the given array list with all the transients.
* @param l the list populate with the transients
*/
public static void getTransients(List<DomainFile> l) {
l.addAll(set);
}
/**
* Releases all files for the given consumer.
* @param consumer the domain file consumer
*/
public static void releaseFiles(Object consumer) {
for (DomainFileProxy df : set) {
df.release(consumer);
}
}
}

View file

@ -0,0 +1,9 @@
<!doctype html public "-//w3c//dtd html 4.0 transitional//en">
<html>
<head>
<title>package ghidra.framework.data</title>
</head>
<body>
Provides classes for defining domain objects and events related to domain objects.
</body>
</html>

View file

@ -0,0 +1,58 @@
/* ###
* 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.framework.main;
import ghidra.framework.model.Project;
import ghidra.util.exception.AssertException;
/**
* Class with static methods to maintain application info, e.g., a handle to the
* tool that is the Ghidra Project Window, the user's name, etc.
*/
public class AppInfo {
private static FrontEndTool tool;
private static Project activeProject;
static void setFrontEndTool(FrontEndTool t) {
tool = t;
}
static void setActiveProject(Project p) {
activeProject = p;
}
public static FrontEndTool getFrontEndTool() {
assertFrontEndRunning();
return tool;
}
public static Project getActiveProject() {
return activeProject;
}
public static void exitGhidra() {
assertFrontEndRunning();
tool.exit();
}
private static void assertFrontEndRunning() {
if (tool == null) {
throw new AssertException(
"Cannot use " + AppInfo.class.getSimpleName() + " without a Front End running");
}
}
}

View file

@ -0,0 +1,205 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.main;
import java.awt.BorderLayout;
import java.awt.event.*;
import java.io.File;
import javax.swing.*;
import docking.DockingUtils;
import docking.options.editor.ButtonPanelFactory;
import docking.widgets.filechooser.GhidraFileChooser;
/**
* Helper class that restricts the width of the textField to the size of the
* scrolled paths list; also provides the listener for the textfield if user
* presses Enter or Tab in a textfield.
*
*/
class BrowsePathPanel extends JPanel {
private boolean changed;
private GhidraFileChooser fileChooser;
private JTextField pathTextField;
private EditPluginPathDialog dialog;
private JButton browseButton;
/**
* Construct a new BrowsePathPanel.
* @param editDialog parent dialog
* @param sizeComp component to use for size in creating text field
* @param button browse button
* @param dirOnly
* @param textFieldLabel
* @param fieldName name of text field component
*/
BrowsePathPanel(EditPluginPathDialog editDialog, ActionListener buttonListener, String fieldName) {
super();
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
dialog = editDialog;
create(fieldName);
addListeners(buttonListener);
}
/**
* Create the components
* @param sizeComp component to use when creating the text field to get the
* size
* @param textFieldLabel label for the field
*/
private void create(String fieldName) {
pathTextField = new JTextField();
pathTextField.setName(fieldName);
pathTextField.setEditable(false);
pathTextField.setBackground(getBackground());
browseButton = ButtonPanelFactory.createButton(ButtonPanelFactory.BROWSE_TYPE);
browseButton.setToolTipText("Choose Directory");
// construct the panel with text field and browse button
JPanel browsePathPanel = new JPanel(new BorderLayout(5, 5));
browsePathPanel.add(pathTextField, BorderLayout.CENTER);
browsePathPanel.add(browseButton, BorderLayout.EAST);
add(browsePathPanel);
}
private void createFileChooser() {
// create the fileChooser this panel will use based on its input criteria
fileChooser = new GhidraFileChooser(dialog.getComponent());
fileChooser.setCurrentDirectory(new File(System.getProperty("user.home")));
fileChooser.setFileSelectionMode(GhidraFileChooser.DIRECTORIES_ONLY);
fileChooser.setApproveButtonToolTipText("Choose Directory With Plugin JAR Files");
fileChooser.setApproveButtonText("Choose JAR Directory");
}
/**
* Add listeners.
* @param listener listener for the browse button
*/
private void addListeners(ActionListener listener) {
browseButton.addActionListener(listener);
pathTextField.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
// when Esc or Ctrl-C is pressed, reset the plugin
// jar directory to what is saved in preferences
if (keyCode == KeyEvent.VK_ESCAPE ||
(DockingUtils.isControlModifier(e) && keyCode == KeyEvent.VK_C)) {
dialog.initJarDirectory();
}
else {
dialog.setApplyEnabled(true);
}
}
});
}
String getPath() {
return pathTextField.getText().trim();
}
boolean isChanged() {
return changed;
}
@Override
public boolean hasFocus() {
return pathTextField.hasFocus();
}
@Override
public void requestFocus() {
pathTextField.requestFocus();
pathTextField.selectAll();
}
/**
* Pop up the file chooser.
*/
void showFileChooser() {
if (fileChooser == null) {
createFileChooser();
}
// reset the status message
dialog.setStatusMessage(EditPluginPathDialog.EMPTY_STATUS);
File pluginFile = fileChooser.getSelectedFile();
if (pluginFile != null) {
setPath(pluginFile);
}
else {
pathTextField.requestFocus();
pathTextField.selectAll();
}
}
/**
* Set whether something has changed.
* @param changed true if something changed
*/
void setChanged(boolean changed) {
this.changed = changed;
}
/**
* Set the path field.
* @param path filename for the path field
* @return boolean true if the path is valid
*/
private boolean setPath(File path) {
boolean pathOK = false;
dialog.setStatusMessage(EditPluginPathDialog.EMPTY_STATUS);
if (!path.canRead()) {
pathTextField.selectAll();
dialog.setStatusMessage("Cannot read path: " + path.toString());
}
else {
pathTextField.setText(path.getAbsolutePath());
pathOK = (pathTextField.getText().trim().length() > 0);
}
if (pathOK) {
dialog.setStatusMessage("Press Apply or OK to set JAR directory.");
}
changed = changed || pathOK;
dialog.enableApply();
return pathOK;
}
/**
* sets the text of the text field of the panel without
* any error checking
*/
void setText(String text) {
pathTextField.setText(text);
}
}

View file

@ -0,0 +1,193 @@
/* ###
* 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.framework.main;
import java.io.File;
import java.io.IOException;
import docking.ActionContext;
import docking.action.DockingAction;
import docking.action.MenuData;
import docking.widgets.OptionDialog;
import docking.widgets.filechooser.GhidraFileChooser;
import ghidra.framework.plugintool.util.ToolConstants;
import ghidra.net.ApplicationKeyManagerFactory;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
/**
* Helper class to manage the actions on the Edit menu.
*/
class EditActionManager {
private FrontEndPlugin plugin;
private FrontEndTool tool;
private DockingAction editPluginPathAction;
private DockingAction editCertPathAction;
private DockingAction clearCertPathAction;
private EditPluginPathDialog pluginPathDialog;
private GhidraFileChooser certFileChooser;
EditActionManager(FrontEndPlugin plugin) {
this.plugin = plugin;
tool = (FrontEndTool) plugin.getTool();
createActions();
}
/**
* Create the menu items.
*/
private void createActions() {
// window.addSeparator(Ghidra.MENU_FILE);
editPluginPathAction = new DockingAction("Edit Plugin Path", plugin.getName()) {
@Override
public void actionPerformed(ActionContext context) {
editPluginPath();
}
};
// ACTIONS - auto generated
editPluginPathAction.setEnabled(true);
editPluginPathAction.setMenuBarData(new MenuData(new String[] { ToolConstants.MENU_EDIT,
"Plugin Path..." }, "GEdit"));
editCertPathAction = new DockingAction("Set PKI Certificate", plugin.getName()) {
@Override
public void actionPerformed(ActionContext context) {
editCertPath();
}
};
// ACTIONS - auto generated
editCertPathAction.setEnabled(true);
editCertPathAction.setMenuBarData(new MenuData(new String[] { ToolConstants.MENU_EDIT,
"Set PKI Certificate..." }, "PKI"));
clearCertPathAction = new DockingAction("Clear PKI Certificate", plugin.getName()) {
@Override
public void actionPerformed(ActionContext context) {
clearCertPath();
}
};
// ACTIONS - auto generated
clearCertPathAction.setEnabled(ApplicationKeyManagerFactory.getKeyStore() != null);
clearCertPathAction.setMenuBarData(new MenuData(new String[] { ToolConstants.MENU_EDIT,
"Clear PKI Certificate..." }, "PKI"));
clearCertPathAction.setHelpLocation(new HelpLocation("FrontEndPlugin",
"Set_PKI_Certificate"));
tool.addAction(editCertPathAction);
tool.addAction(clearCertPathAction);
tool.addAction(editPluginPathAction);
}
/**
* Pop up the edit plugin path dialog.
*/
private void editPluginPath() {
if (pluginPathDialog == null) {
pluginPathDialog = new EditPluginPathDialog();
}
pluginPathDialog.show(tool);
}
private void clearCertPath() {
String path = ApplicationKeyManagerFactory.getKeyStore();
if (path == null) {
// unexpected
clearCertPathAction.setEnabled(false);
return;
}
if (OptionDialog.YES_OPTION != OptionDialog.showYesNoDialog(tool.getToolFrame(),
"Clear PKI Certificate", "Clear PKI certificate setting?\n(" + path + ")")) {
return;
}
try {
ApplicationKeyManagerFactory.setKeyStore(null, true);
clearCertPathAction.setEnabled(false);
}
catch (IOException e) {
Msg.error(this,
"Error occured while clearing PKI certificate setting: " + e.getMessage());
}
}
private void editCertPath() {
if (certFileChooser == null) {
certFileChooser = createCertFileChooser();
}
File dir = null;
File oldFile = null;
String path = ApplicationKeyManagerFactory.getKeyStore();
if (path != null) {
oldFile = new File(path);
dir = oldFile.getParentFile();
if (!oldFile.isFile()) {
oldFile = null;
if (!dir.isDirectory()) {
dir = null;
}
}
}
if (dir == null) {
dir = new File(System.getProperty("user.home"));
}
if (oldFile != null) {
certFileChooser.setSelectedFile(oldFile);
}
else {
certFileChooser.setCurrentDirectory(dir);
}
boolean validInput = false;
while (!validInput) {
// display the file chooser and handle the action, Select or Create
File file = certFileChooser.getSelectedFile();
if (file == null) {
return; // cancelled
}
try {
ApplicationKeyManagerFactory.setKeyStore(file.getAbsolutePath(), true);
clearCertPathAction.setEnabled(true);
validInput = true;
}
catch (IOException e) {
Msg.showError(this, tool.getToolFrame(), "Certificate Failure",
"Failed to initialize key manager.\n" + e.getMessage(), e);
file = null;
}
}
}
private GhidraFileChooser createCertFileChooser() {
GhidraFileChooser fileChooser = new GhidraFileChooser(tool.getToolFrame());
fileChooser.setTitle("Select Certificate (req'd for PKI authentication only)");
fileChooser.setApproveButtonText("Set Certificate");
fileChooser.setFileFilter(ApplicationKeyManagerFactory.CERTIFICATE_FILE_FILTER);
fileChooser.setFileSelectionMode(GhidraFileChooser.FILES_ONLY);
fileChooser.setHelpLocation(new HelpLocation(plugin.getName(), "Set_PKI_Certificate"));
return fileChooser;
}
}

View file

@ -0,0 +1,648 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/* Generated by Together */
package ghidra.framework.main;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.preferences.Preferences;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.filechooser.ExtensionFileFilter;
import ghidra.util.filechooser.GhidraFileFilter;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.border.TitledBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import docking.DialogComponentProvider;
import docking.ToolTipManager;
import docking.options.editor.ButtonPanelFactory;
import docking.widgets.filechooser.GhidraFileChooser;
/**
* Dialog for editing the Plugin path and Jar directory path preferences.
* <p>The Plugin Path and Jar directory path are locations where Ghidra searches
* for plugins to load. The Plugin Path is specified exactly as a Java Classpath
* is specified. The Jar directory is searched only for Jar files containing
* Plugins. When changes are made to these fields in the dialog, the
* preferences file is updated and written to disk. The preferences file is
* located in the .ghidra directory in the user's home directory.
* </P>
* <p> The preferences file also contains the last project that was opened,
* and the positions of the Ghidra Project Window and other tools that were
* running when the user last exited Ghidra.
* </P>
*/
class EditPluginPathDialog extends DialogComponentProvider {
private final static int SIDE_MARGIN = 5;
private final static Color INVALID_PATH_COLOR = Color.red.brighter();
private final static Color INVALID_SELECTED_PATH_COLOR = Color.pink;
private final static Color STATUS_MESSAGE_COLOR = Color.blue.brighter();
final static String EMPTY_STATUS = " ";
private ExtensionFileFilter JAR_FILTER = new ExtensionFileFilter(new String[] { "jar", "zip" },
"Plugin Jar Files");
// codes used when handling actions
private final static byte UP = (byte) 0;
private final static byte DOWN = (byte) 1;
private final static byte REMOVE = (byte) 2;
// state data
private DefaultListModel<String> listModel; // paths to search for finding plugins
private boolean pluginPathsChanged = false;
// gui members needed for dis/enabling and other state-dependent actions
private JScrollPane scrollPane; // need for preferred size when resizing
private JList<String> pluginPathsList;
private BrowsePathPanel jarPathPanel;
private GhidraFileChooser fileChooser;
private JButton upButton;
private JButton downButton;
private JButton removeButton;
private List<String> selectedInList;
private JLabel statusMessage;
private JPanel mainPanel;
private String errorMsg;
/**
* Creates a non-modal dialog with OK, Apply, Cancel buttons.
* The OK and Apply buttons will be enabled when user makes unapplied
* changes to the UserPluginPath or UserPluginJarDirectory property values.
* @param parent parent to this dialog
*/
EditPluginPathDialog() {
super("Edit Plugin Path", true, false, true, false);
setHelpLocation(new HelpLocation("FrontEndPlugin", "Edit_Plugin_Path"));
addWorkPanel(buildMainPanel());
addOKButton();
addApplyButton();
addCancelButton();
// set model after the pack() so the dialog is sized properly
pluginPathsList.setModel(listModel);
}
/**
* Define the Main panel for the dialog here.
* @return JPanel the completed <CODE>Main Panel<\CODE>
*/
protected JPanel buildMainPanel() {
// give base class the panel it needs to complete its construction
// and then finish building after base class is done
mainPanel = new JPanel();
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
listModel = new DefaultListModel<String>();
setPluginPathsListData(Preferences.getPluginPaths());
// construct the bottom error message panel
JPanel statusMessagePanel = new JPanel();
statusMessage = new JLabel("Ready to set User Plugin Paths");
statusMessage.setName("statusLabel");
statusMessage.setForeground(STATUS_MESSAGE_COLOR);
statusMessagePanel.add(statusMessage);
// put the main panel together
// make sure to construct the plugin paths panel, since that
// creates the scroll pane we use for sizing the text fields on
// subsequent panels
mainPanel.add(buildPluginPathsPanel());
mainPanel.add(Box.createVerticalStrut(10));
mainPanel.add(buildJarDirectoryPanel());
mainPanel.add(Box.createVerticalStrut(10));
mainPanel.add(Box.createVerticalGlue());
mainPanel.add(statusMessagePanel);
mainPanel.invalidate();
// dialog sensitivity setup
enableButtons(false);
setApplyEnabled(false);
return mainPanel;
}
/**
* Gets called when the user selects Apply
*/
@Override
protected void applyCallback() {
// validate the jar path before applying changes, since the user
// is pressing the Apply button to save this setting
String jarPathname = jarPathPanel.getPath();
if (jarPathname.length() > 0) {
File jarPath = new File(jarPathname);
if (!jarPath.isDirectory() || !jarPath.canRead()) {
setStatusMessage("Bad Jar Directory: " + jarPathname);
jarPathPanel.requestFocus();
return;
}
}
// do the things we need to do to handle the applied changes
handleApply();
}
/**
* Gets called when the user selects Cancel
*/
@Override
protected void cancelCallback() {
close();
// reset original state of dialog for next display of dialog
enableButtons(false);
setStatusMessage(EMPTY_STATUS);
setApplyEnabled(false);
errorMsg = null;
}
/**
* if the jar directory field has focus, don't let the base dialog
* handle it.
*/
@Override
protected void escapeCallback() {
if (!jarPathPanel.hasFocus()) {
super.escapeCallback();
}
}
/**
* Gets called when the user selects Ok
*/
@Override
protected void okCallback() {
if (isApplyEnabled()) {
applyCallback();
}
if (errorMsg == null) {
cancelCallback();
}
}
/**
* re-set the list of paths each time the dialog is shown
*/
public void show(PluginTool tool) {
setPluginPathsListData(Preferences.getPluginPaths());
initJarDirectory();
// setting the path enables the apply, but we know we haven't
// made any changes yet, so disable
setApplyEnabled(false);
tool.showDialog(this);
}
/**
* Method enableApply.
*/
void enableApply() {
setApplyEnabled(pluginPathsChanged || jarPathPanel.isChanged());
}
void initJarDirectory() {
setApplyEnabled(pluginPathsChanged);
setStatusMessage(EMPTY_STATUS);
}
void setStatusMessage(String msg) {
if (msg == null || msg.length() == 0) {
msg = EMPTY_STATUS;
}
statusMessage.setText(msg);
statusMessage.invalidate();
}
/**
* @see ghidra.util.bean.GhidraDialog#setApplyEnabled(boolean)
*/
@Override
protected void setApplyEnabled(boolean state) {
super.setApplyEnabled(state);
}
void addJarCallback() {
setStatusMessage(EditPluginPathDialog.EMPTY_STATUS);
if (fileChooser == null) {
fileChooser = new GhidraFileChooser(getComponent());
fileChooser.setCurrentDirectory(new File(System.getProperty("user.home")));
}
fileChooser.setFileSelectionMode(GhidraFileChooser.FILES_ONLY);
fileChooser.setFileFilter(JAR_FILTER);
fileChooser.setApproveButtonToolTipText("Choose Plugin Jar File");
fileChooser.setApproveButtonText("Add Jar File");
File dir = fileChooser.getSelectedFile();
if (dir != null) {
try {
String dirPath = dir.getCanonicalPath();
if (!listModel.contains(dirPath)) {
listModel.addElement(dirPath);
pluginPathsChanged = true;
setApplyEnabled(true);
}
else {
setStatusMessage(dirPath + " is already in the list.");
}
}
catch (IOException e) {
setStatusMessage(e.getMessage());
}
}
}
void addDirCallback() {
setStatusMessage(EditPluginPathDialog.EMPTY_STATUS);
if (fileChooser == null) {
fileChooser = new GhidraFileChooser(getComponent());
fileChooser.setCurrentDirectory(new File(System.getProperty("user.home")));
}
fileChooser.setFileSelectionMode(GhidraFileChooser.DIRECTORIES_ONLY);
fileChooser.setFileFilter(GhidraFileFilter.ALL);
fileChooser.setApproveButtonToolTipText("Choose Directory with Plugin class Files");
fileChooser.setApproveButtonText("Add Directory");
File dir = fileChooser.getSelectedFile();
if (dir != null) {
try {
String dirPath = dir.getCanonicalPath();
if (!listModel.contains(dirPath)) {
listModel.addElement(dirPath);
pluginPathsChanged = true;
setApplyEnabled(true);
}
else {
setStatusMessage(dirPath + " is already in the list.");
}
}
catch (IOException e) {
setStatusMessage(e.getMessage());
Msg.error(this, "Unexpected Exception: " + e.getMessage(), e);
}
}
}
/**
* Returns an array of pathnames where plugins can be found; used by custom
* class loader when searching for plugins.
*/
private String[] getUserPluginPaths() {
String[] pluginsArray = new String[listModel.size()];
listModel.copyInto(pluginsArray);
return pluginsArray;
}
/**
* construct the plugin paths button panel
*/
private JPanel buildPluginPathsPanel() {
// create the UP and DOWN arrows panel
upButton = ButtonPanelFactory.createButton(ButtonPanelFactory.ARROW_UP_TYPE);
upButton.setName("UpArrow");
upButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
handleSelection(UP);
}
});
downButton = ButtonPanelFactory.createButton(ButtonPanelFactory.ARROW_DOWN_TYPE);
downButton.setName("DownArrow");
downButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
handleSelection(DOWN);
}
});
JPanel arrowButtonsPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 10));
arrowButtonsPanel.add(upButton);
arrowButtonsPanel.add(downButton);
// create the Add and Remove panel
JButton addJarButton = ButtonPanelFactory.createButton("Add Jar...");
addJarButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
addJarCallback();
}
});
JButton addDirButton = ButtonPanelFactory.createButton("Add Dir...");
addDirButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
addDirCallback();
}
});
removeButton = ButtonPanelFactory.createButton("Remove");
removeButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
handleSelection(REMOVE);
}
});
Dimension d = addJarButton.getPreferredSize();
addDirButton.setPreferredSize(d);
removeButton.setPreferredSize(d);
JPanel otherButtonsPanel =
ButtonPanelFactory.createButtonPanel(new JButton[] { addJarButton, addDirButton,
removeButton }, SIDE_MARGIN);
// put the right-side buttons panel together
JPanel listButtonPanel = new JPanel(new BorderLayout(0, 0));
listButtonPanel.add(arrowButtonsPanel, BorderLayout.NORTH);
listButtonPanel.add(otherButtonsPanel, BorderLayout.CENTER);
//
// construct the plugin paths list
//
JPanel scrollListPanel = new JPanel(new BorderLayout(10, 15));
pluginPathsList = new JList<String>();
pluginPathsList.addListSelectionListener(new PathListSelectionListener());
pluginPathsList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
// give the list a custom cell renderer that shows invalid paths
// in red (may be preference paths set previously that are no longer
// available
pluginPathsList.setCellRenderer(new PluginPathRenderer());
// component used for sizing all the text fields on the main panel
scrollPane = new JScrollPane(pluginPathsList);
scrollPane.setPreferredSize(new Dimension(250, 150));
scrollListPanel.add(scrollPane, BorderLayout.CENTER);
//
// construct the plugin text panel
//
JPanel pluginPathListPanel = new JPanel(new BorderLayout(0, 0));
pluginPathListPanel.add(scrollListPanel, BorderLayout.CENTER);
pluginPathListPanel.add(listButtonPanel, BorderLayout.EAST);
pluginPathListPanel.setBorder(new TitledBorder("User Plugin Paths"));
// set tooltips after adding all components to get around swing
// tooltip text problem where the text is obscured by a component
// added after tooltip has been added
//
ToolTipManager.setToolTipText(upButton, "Changes the order of search for plugins");
ToolTipManager.setToolTipText(downButton, "Changes the order of search for plugins");
pluginPathListPanel.validate();
return pluginPathListPanel;
}
/**
* construct the jar directory panel
*/
private JPanel buildJarDirectoryPanel() {
ActionListener listener = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
jarPathPanel.showFileChooser();
enableApply();
}
};
jarPathPanel = new BrowsePathPanel(this, listener, "UserPluginJarDirectory");
jarPathPanel.setText(Preferences.getProperty(Preferences.USER_PLUGIN_JAR_DIRECTORY));
jarPathPanel.setBorder(new TitledBorder("User Plugin Jar Directory"));
return jarPathPanel;
}
private void enableButtons(boolean enabled) {
upButton.setEnabled(enabled);
downButton.setEnabled(enabled);
removeButton.setEnabled(enabled);
}
/**
* done here so can be handled in a separate thread
*/
private void handleApply() {
// get the data model as an array of strings
String[] userPluginPaths = getUserPluginPaths();
// update Ghidra Preferences with new paths
Preferences.setPluginPaths(userPluginPaths);
// Get user Jar directory
String jarDirectoryName = jarPathPanel.getPath();
if (jarDirectoryName.trim().length() == 0) {
jarDirectoryName = null;
}
// update Ghidra Preferences with new Jar path
Preferences.setProperty(Preferences.USER_PLUGIN_JAR_DIRECTORY, jarDirectoryName);
errorMsg = null;
// save the new values
if (Preferences.store()) {
setStatusMessage("Saved plugin paths successfully!");
// indicate to user all changes have been applied
setApplyEnabled(false);
jarPathPanel.setChanged(false);
Msg.showInfo(getClass(), rootPanel, "Restart Ghidra",
"You must restart Ghidra in order\n" + "for path changes to take effect.");
}
else {
setStatusMessage("");
Msg.showError(this, rootPanel, "Error Saving Plugin Paths",
"Failed to update user preferences (see log for details)");
}
}
/**
* dispatched method for handling button actions on the
* dialog
*/
private void handleSelection(byte whichAction) {
// if nothing selected, nothing to do
if (selectedInList == null) {
enableButtons(false);
return;
}
// confirm removal of plugin path entries, since this may cause
// previously saved tools to not be able to load their plugins
if (whichAction == REMOVE) {
// for each selected entry, do the specified action
List<String> tempList = new ArrayList<>(selectedInList);
for (String pathName : tempList) {
int index = listModel.indexOf(pathName);
if (index >= 0) {
listModel.remove(index);
}
}
}
else {
int newIndex = -1;
int selIndex = pluginPathsList.getSelectedIndex();
int size = listModel.size();
String path = listModel.remove(selIndex);
if (selIndex == 0) {
// if UP, then place this index last,
if (whichAction == UP) {
listModel.add(listModel.size(), path);
newIndex = size - 1;
}
else {
// else place it 2nd
listModel.add(1, path);
newIndex = 1;
}
}
else {
// if UP, place selectedIndex at previous.
if (whichAction == UP) {
listModel.add(selIndex - 1, path);
newIndex = selIndex - 1;
}
else {
if (selIndex == size - 1) {
// place this item first
listModel.add(0, path);
newIndex = 0;
}
else {
// else place selected Index at next
listModel.add(selIndex + 1, path);
newIndex = selIndex + 1;
}
}
}
if (newIndex >= 0) {
pluginPathsList.setSelectedIndex(newIndex);
}
}
// alert user that something changed
setApplyEnabled(true);
if (whichAction == REMOVE) {
enableButtons(false);
}
}
/**
* special class that renders the path values in the list,
* coloring paths that are no longer readable in red.
*/
private class PluginPathRenderer extends JLabel implements ListCellRenderer<String> {
public PluginPathRenderer() {
super();
setOpaque(true);
}
@Override
public Component getListCellRendererComponent(JList<? extends String> list, String value,
int index, boolean isSelected, boolean cellHasFocus) {
// validate the paths, setting invalid paths to red in the list
// Invalid paths are defined as:
// Jar files or directories that are no longer accessible;
//
String pathName = listModel.get(index);
setText(pathName);
setFont(list.getFont());
boolean pathOK = new File(pathName).canRead();
if (isSelected) {
if (!pathOK) {
setForeground(INVALID_SELECTED_PATH_COLOR);
}
else {
setForeground(list.getSelectionForeground());
}
setBackground(list.getSelectionBackground());
}
else {
// set color to red if no longer accessible
if (!pathOK) {
setForeground(INVALID_PATH_COLOR);
}
else {
setForeground(list.getForeground());
}
setBackground(list.getBackground());
}
return this;
}
}
private void setPluginPathsListData(String[] pluginPathNames) {
listModel.clear();
for (int p = 0; p < pluginPathNames.length; p++) {
listModel.addElement(pluginPathNames[p]);
}
}
private class PathListSelectionListener implements ListSelectionListener {
@Override
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) {
return;
}
// reset state initially
selectedInList = null;
enableButtons(false);
List<String> selectedValues = pluginPathsList.getSelectedValuesList();
if (selectedValues.isEmpty()) {
String path = pluginPathsList.getSelectedValue();
if (path != null) {
selectedValues = Collections.singletonList(path);
}
}
removeButton.setEnabled(false);
if (selectedValues != null) {
int numSelected = selectedValues.size();
selectedInList = new ArrayList<>(selectedValues);
if (numSelected == 1) {
// only if there are more than 1 paths in list
// do we enable the UP and DOWN arrows
if (listModel.size() > 1) {
enableButtons(true);
}
else {
removeButton.setEnabled(true);
}
}
else if (numSelected > 0) {
removeButton.setEnabled(true);
}
}
}
}
}

View file

@ -0,0 +1,749 @@
/* ###
* 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.framework.main;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.util.*;
import javax.swing.Icon;
import javax.swing.KeyStroke;
import docking.ActionContext;
import docking.action.*;
import docking.widgets.OptionDialog;
import docking.widgets.filechooser.GhidraFileChooser;
import docking.wizard.WizardManager;
import ghidra.framework.client.ClientUtil;
import ghidra.framework.client.RepositoryAdapter;
import ghidra.framework.model.*;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.util.ToolConstants;
import ghidra.framework.store.LockException;
import ghidra.util.*;
import ghidra.util.exception.NotFoundException;
import ghidra.util.task.InvokeInSwingTask;
import ghidra.util.task.TaskLauncher;
import resources.ResourceManager;
/**
* Helper class to manage actions on the File menu.
*/
class FileActionManager {
private final static int NEW_ACCELERATOR = KeyEvent.VK_N;
private final static int OPEN_ACCELERATOR = KeyEvent.VK_O;
private final static int CLOSE_ACCELERATOR = KeyEvent.VK_W;
private final static int SAVE_ACCELERATOR = KeyEvent.VK_S;
private final static Icon NEW_PROJECT_ICON = ResourceManager.loadImage("images/folder_add.png");
private final static String LAST_SELECTED_PROJECT_DIRECTORY = "LastSelectedProjectDirectory";
private static final String DISPLAY_DATA = "DISPLAY_DATA";
private FrontEndTool tool;
private FrontEndPlugin plugin;
private DockingAction newAction;
private DockingAction openAction;
private DockingAction closeProjectAction;
private DockingAction deleteAction;
private DockingAction saveAction;
private List<ViewInfo> reopenList;
private GhidraFileChooser fileChooser;
private boolean firingProjectOpened;
FileActionManager(FrontEndPlugin plugin) {
this.plugin = plugin;
tool = (FrontEndTool) plugin.getTool();
reopenList = new ArrayList<>();
createActions();
}
/**
* creates all the menu items for the File menu
*/
private void createActions() {
// create the menu items and their listeners
newAction = new DockingAction("New Project", plugin.getName()) {
@Override
public void actionPerformed(ActionContext context) {
newProject();
}
};
newAction.setEnabled(true);
newAction.setKeyBindingData(
new KeyBindingData(KeyStroke.getKeyStroke(NEW_ACCELERATOR, ActionEvent.CTRL_MASK)));
newAction.setMenuBarData(
new MenuData(new String[] { ToolConstants.MENU_FILE, "New Project..." }, "AProject"));
tool.addAction(newAction);
openAction = new DockingAction("Open Project", plugin.getName()) {
@Override
public void actionPerformed(ActionContext context) {
openProject();
}
};
openAction.setEnabled(true);
openAction.setKeyBindingData(
new KeyBindingData(KeyStroke.getKeyStroke(OPEN_ACCELERATOR, ActionEvent.CTRL_MASK)));
openAction.setMenuBarData(
new MenuData(new String[] { ToolConstants.MENU_FILE, "Open Project..." }, "AProject"));
tool.addAction(openAction);
saveAction = new DockingAction("Save Project", plugin.getName()) {
@Override
public void actionPerformed(ActionContext context) {
saveProject();
tool.saveToolConfigurationToDisk();
}
};
saveAction.setEnabled(false);
saveAction.setKeyBindingData(
new KeyBindingData(KeyStroke.getKeyStroke(SAVE_ACCELERATOR, ActionEvent.CTRL_MASK)));
saveAction.setMenuBarData(
new MenuData(new String[] { ToolConstants.MENU_FILE, "Save Project" }, "BProject"));
tool.addAction(saveAction);
closeProjectAction = new DockingAction("Close Project", plugin.getName()) {
@Override
public void actionPerformed(ActionContext context) {
closeProject(false); //not exiting
}
};
closeProjectAction.setEnabled(false);
closeProjectAction.setKeyBindingData(
new KeyBindingData(KeyStroke.getKeyStroke(CLOSE_ACCELERATOR, ActionEvent.CTRL_MASK)));
closeProjectAction.setMenuBarData(
new MenuData(new String[] { ToolConstants.MENU_FILE, "Close Project" }, "BProject"));
tool.addAction(closeProjectAction);
deleteAction = new DockingAction("Delete Project", plugin.getName()) {
@Override
public void actionPerformed(ActionContext context) {
deleteProject();
}
};
deleteAction.setEnabled(true);
deleteAction.setMenuBarData(new MenuData(
new String[] { ToolConstants.MENU_FILE, "Delete Project..." }, "CProject"));
tool.addAction(deleteAction);
}
/**
* creates the recent projects menu
*/
void buildRecentProjectsMenu() {
for (ViewInfo info : reopenList) {
tool.removeAction(info.getAction());
}
reopenList.clear();
ProjectLocator[] recentProjects = plugin.getRecentProjects();
// the project manager maintains the order of the projects
// with the most recent being first in the list
for (ProjectLocator projectLocator : recentProjects) {
String filename = projectLocator.toString();
DockingAction action = new ReopenProjectAction(projectLocator, filename);
reopenList.add(new ViewInfo(action, projectLocator.getURL()));
tool.addAction(action);
}
}
/**
* Create a new project using a wizard to get the project information.
*/
void newProject() {
NewProjectPanelManager panelManager = new NewProjectPanelManager(tool);
WizardManager wm = new WizardManager("New Project", true, panelManager, NEW_PROJECT_ICON);
wm.showWizard(tool.getToolFrame());
ProjectLocator newProjectLocator = panelManager.getNewProjectLocation();
RepositoryAdapter newRepo = panelManager.getProjectRepository();
if (newProjectLocator == null) {
return; // user canceled
}
Project newProject = null;
try {
// if all is well and we already have an active project, close it
Project activeProject = plugin.getActiveProject();
if (activeProject != null) {
if (!closeProject(false)) { // false -->not exiting
return; // user canceled
}
}
if (newRepo != null) {
try {
if (newRepo.getServer().isConnected()) {
newRepo.connect();
}
}
catch (IOException e) {
ClientUtil.handleException(newRepo, e, "Repository Connection",
tool.getToolFrame());
}
}
newProject = tool.getProjectManager().createProject(newProjectLocator, newRepo, true);
}
catch (Exception e) {
String msg = e.getMessage();
if (msg == null) {
msg = e.toString();
}
Msg.showError(this, tool.getToolFrame(), "Create Project Failed",
"Failed to create new project '" + newProjectLocator.getName() + "': " + msg, e);
}
finally {
if (newProject == null && newRepo != null) {
newRepo.disconnect();
}
}
// make the new project the active one
tool.setActiveProject(newProject);
// update our list of recent projects
plugin.rebuildRecentMenus();
if (newProject != null) {
openProjectAndNotify(newProject);
}
}
private void openProject() {
ProjectLocator currentProjectLocator = null;
Project activeProject = plugin.getActiveProject();
if (activeProject != null) {
currentProjectLocator = activeProject.getProjectLocator();
}
if (fileChooser == null) {
fileChooser = plugin.createFileChooser(LAST_SELECTED_PROJECT_DIRECTORY);
}
ProjectLocator projectLocator =
plugin.chooseProject(fileChooser, "Open", LAST_SELECTED_PROJECT_DIRECTORY);
if (projectLocator != null) {
if (!doOpenProject(projectLocator) && currentProjectLocator != null) {
doOpenProject(currentProjectLocator);
}
}
}
private class OpenTaskRunnable implements Runnable {
private final ProjectLocator newProjectLocator;
private boolean result = false;
OpenTaskRunnable(ProjectLocator newProjectLocator) {
this.newProjectLocator = newProjectLocator;
}
@Override
public void run() {
result = doOpenProject(newProjectLocator);
}
boolean getResult() {
return result;
}
}
/**
* Opens the given project in a task that will show a dialog to block input while opening
* the project in the swing thread.
*/
final boolean openProject(ProjectLocator projectLocator) {
OpenTaskRunnable openRunnable = new OpenTaskRunnable(projectLocator);
InvokeInSwingTask task = new InvokeInSwingTask("Opening Project", openRunnable);
new TaskLauncher(task, tool.getToolFrame(), 0);
return openRunnable.getResult();
}
/**
* Open an existing project, using a file chooser to specify where the
* existing project folder is stored.
*/
final boolean doOpenProject(ProjectLocator projectLocator) {
String status = "Opened project: " + projectLocator.getName();
Project project = null;
boolean openStatus = false;
try {
// first close the active project (if there is one)
// but if user cancels operation, don't continue
if (!closeProject(false)) {
return true;
}
ProjectManager pm = plugin.getProjectManager();
project = pm.openProject(projectLocator, true, false);
if (project == null) {
status = "Error opening project: " + projectLocator.toString();
}
else {
firingProjectOpened = true;
tool.setActiveProject(project);
openProjectAndNotify(project);
openStatus = true;
firingProjectOpened = false;
}
}
catch (NotFoundException nfe) {
status = "Project not found for " + projectLocator.toString();
Msg.showInfo(getClass(), tool.getToolFrame(), "Error Opening Project", status);
}
catch (NotOwnerException e) {
status = "Cannot open project: " + e.getMessage();
Msg.showError(this, null, "Not Project Owner", "Cannot open project " + projectLocator +
"\n" + e.getMessage() +
"\n \nEach user must create their own project. If needed, another user's project may be viewed\n" +
"and files copied, using the View Other action from your own open project. Alternatively, \n" +
"creating a \"Shared Project\" will allow a group of users to use a shared server-based repository.");
}
catch (LockException e) {
status = "Project is already open for update: " + projectLocator.toString();
Msg.showError(this, null, "Open Project Failed", status);
}
catch (Exception e) {
status = "Error opening project: " + projectLocator.toString();
Msg.showError(this, null, "Open Project Failed", status, e);
}
finally {
// update our list of recent projects
plugin.rebuildRecentMenus();
}
if (!openStatus) {
Msg.error(this, status);
}
else {
Msg.info(this, status);
}
return openStatus;
}
/**
* Obtain domain objects from files and lock. If unable to lock
* one or more of the files, none are locked and null is returned.
* @param files
* @return locked domain objects, or null if unable to lock
* all domain objects.
*/
private DomainObject[] lockDomainObjects(List<DomainFile> files) {
DomainObject[] objs = new DomainObject[files.size()];
int lastIndex = 0;
boolean locked = true;
while (lastIndex < files.size()) {
try {
objs[lastIndex] = files.get(lastIndex).getDomainObject(this, false, false, null);
}
catch (Throwable t) {
Msg.error(this, "Failed to aqcuire domain object instance", t);
locked = false;
break;
}
if (!objs[lastIndex].lock(null)) {
String title = "Exit Ghidra";
StringBuffer buf = new StringBuffer();
UndoableDomainObject udo = (UndoableDomainObject) objs[lastIndex];
buf.append("The File " + files.get(lastIndex).getPathname() +
" is currently being modified by the\n");
buf.append("the following actions:\n \n");
Transaction t = udo.getCurrentTransaction();
List<String> list = t.getOpenSubTransactions();
Iterator<String> it = list.iterator();
while (it.hasNext()) {
buf.append("\n ");
buf.append(it.next());
}
buf.append("\n \n");
buf.append(
"You may exit Ghidra, but the above action(s) will be aborted and all\n");
buf.append("changes made by those actions (and all changes made since those\n");
buf.append("actions started),will be lost! You will still have the option of \n");
buf.append("saving any changes made before those actions began.\n \n");
buf.append("Do you want to abort the action(s) and exit Ghidra?");
int result = OptionDialog.showOptionDialog(tool.getToolFrame(), title,
buf.toString(), "Exit Ghidra", OptionDialog.WARNING_MESSAGE);
if (result == OptionDialog.CANCEL_OPTION) {
locked = false;
objs[lastIndex].release(this);
break;
}
udo.forceLock(true, null);
}
++lastIndex;
}
if (!locked) {
//skip the last one that could not be locked...
for (int i = 0; i < lastIndex; i++) {
objs[i].unlock();
objs[i].release(this);
}
return null;
}
return objs;
}
/**
* menu listener for File | Close Project...
* <p>
* This method will always save the FrontEndTool and project, but not the data unless
* <tt>confirmClose</tt> is called.
*
* @param confirmClose true if the confirmation dialog should be
* displayed
* @param isExiting true if we are closing the project because
* Ghidra is exiting
* @return false if user cancels the close operation
*/
boolean closeProject(boolean isExiting) {
// if there is no active project currently, ignore request
Project activeProject = plugin.getActiveProject();
if (activeProject == null) {
return true;
}
// check for any changes since last saved
Tool[] runningTools = activeProject.getToolManager().getRunningTools();
for (int i = 0; i < runningTools.length; i++) {
if (!runningTools[i].canClose(isExiting)) {
return false;
}
}
boolean saveSuccessful = saveChangedData(activeProject);
if (!saveSuccessful) {
return false;
}
if (!activeProject.saveSessionTools()) {
return false;
}
doSaveProject(activeProject);
// close the project
String name = activeProject.getName();
ProjectLocator projectLocator = activeProject.getProjectLocator();
activeProject.close();
// TODO: This should be done by tool.setActiveProject which should always be invoked
fireProjectClosed(activeProject);
if (!isExiting) {
// update the gui now that active project is closed
tool.setActiveProject(null);
Msg.info(this, "Closed project: " + name);
// update the list of project views to include the "active"
// project that is no longer active
plugin.rebuildRecentMenus();
plugin.getProjectManager().setLastOpenedProject(null);
}
else {
plugin.getProjectManager().setLastOpenedProject(projectLocator);
}
if (tool.getManagePluginsDialog() != null) {
tool.getManagePluginsDialog().close();
}
return true;
}
private void doSaveProject(Project project) {
project.setSaveableData(DISPLAY_DATA, tool.getSaveableDisplayData());
project.save();
}
private void openProjectAndNotify(Project project) {
doRestoreProject(project);
fireProjectOpened(project);
}
private void doRestoreProject(Project project) {
SaveState saveState = project.getSaveableData(DISPLAY_DATA);
if (saveState == null) {
return;
}
tool.setSaveableDisplayData(saveState);
}
private boolean saveChangedData(Project activeProject) {
List<DomainFile> data = activeProject.getOpenData();
if (data.isEmpty()) {
return true;
}
DomainObject[] lockedObjects = lockDomainObjects(data);
if (lockedObjects == null) {
return false;
}
List<DomainFile> changedFiles = getChangedFiles(data);
try {
if (!checkReadOnlyFiles(lockedObjects)) {
return false;
}
// pop up dialog to save the data
SaveDataDialog saveDialog = new SaveDataDialog(tool);
if (!saveDialog.showDialog(changedFiles)) {
// user hit the cancel button on the "Save" dialog
// so cancel closing the project
return false;
}
}
finally {
for (DomainObject lockedObject : lockedObjects) {
lockedObject.unlock();
lockedObject.release(this);
}
}
return true;
}
private List<DomainFile> getChangedFiles(List<DomainFile> data) {
List<DomainFile> changedFiles = new ArrayList<>();
for (DomainFile domainFile : data) {
if (domainFile.isChanged()) {
changedFiles.add(domainFile);
}
}
return changedFiles;
}
void setActiveProject(Project activeProject) {
plugin.rebuildRecentMenus();
if (!firingProjectOpened && activeProject != null) {
openProjectAndNotify(activeProject);
}
}
/**
* menu listener for File | Save Project
*/
void saveProject() {
Project project = plugin.getActiveProject();
if (project == null) {
return;
}
doSaveProject(project);
Msg.info(this, "Saved project: " + project.getName());
}
private boolean allowDelete(Project activeProject) {
if (activeProject != null) {
Msg.showWarn(getClass(), tool.getToolFrame(), "Cannot Delete Active Project",
"You must close your project to delete it.");
return false;
}
return true;
}
/**
* menu listener for File | Delete Project...
*/
private void deleteProject() {
if (fileChooser == null) {
fileChooser = plugin.createFileChooser(LAST_SELECTED_PROJECT_DIRECTORY);
}
ProjectLocator projectLocator =
plugin.chooseProject(fileChooser, "Delete", LAST_SELECTED_PROJECT_DIRECTORY);
if (projectLocator == null) {
return; // user canceled
}
ProjectManager pm = plugin.getProjectManager();
if (!pm.projectExists(projectLocator)) {
Msg.showInfo(getClass(), tool.getToolFrame(), "Project Does Not Exist",
"Project " + projectLocator.getName() + " was not found.");
return;
}
// confirm delete before continuing
Project activeProject = plugin.getActiveProject();
// give a special confirm message if user is about to
// remove the active project
StringBuffer confirmMsg = new StringBuffer("Project: ");
confirmMsg.append(projectLocator.toString());
confirmMsg.append(" ?\n");
boolean isActiveProject =
(activeProject != null && activeProject.getProjectLocator().equals(projectLocator));
// also give special warning if we open this project as read-only voew
boolean isOpenProjectView = isOpenProjectView(projectLocator);
if (!allowDelete(isActiveProject ? activeProject : null)) {
return;
}
confirmMsg.append(" \n");
confirmMsg.append("WARNING: Delete CANNOT be undone!");
if (!plugin.confirmDelete(confirmMsg.toString())) {
return;
}
String projectName = projectLocator.getName();
try {
if (!pm.deleteProject(projectLocator)) {
Msg.showInfo(getClass(), tool.getToolFrame(), "Error Deleting Project",
"All files from project " + projectName + " were not deleted.");
}
}
catch (Exception e) {
Msg.error(this, "Error deleting project: " + projectName + ", " + e.getMessage(), e);
return;
}
if (isActiveProject) {
activeProject.close();
fireProjectClosed(activeProject);
tool.setActiveProject(null);
}
else if (isOpenProjectView) {
// update the read-only project views if affected
plugin.getProjectActionManager().closeView(projectLocator.getURL());
}
// update our list of recent projects
plugin.rebuildRecentMenus();
Msg.info(this, "Deleted project: " + projectName);
}
private boolean isOpenProjectView(ProjectLocator projectLocator) {
boolean isOpenView = false;
ProjectLocator[] openViews = plugin.getProjectDataPanel().getProjectViews();
for (int v = 0; !isOpenView && v < openViews.length; v++) {
isOpenView = openViews[v].equals(projectLocator);
}
return isOpenView;
}
final void enableActions(boolean enabled) {
// renameAction.setEnabled(enabled);
closeProjectAction.setEnabled(enabled);
saveAction.setEnabled(enabled);
}
/**
* Checks the list for read-only files; if any are found, pops up
* a dialog for whether to save now or lose changes.
* @param files list of files which correspond to modified
* domain objects.
* @return true if there are no read only files OR if the user
* wants to lose his changes; false if the user wants to save the
* files now, so don't continue.
*/
private boolean checkReadOnlyFiles(DomainObject[] objs) {
ArrayList<DomainObject> list = new ArrayList<>(10);
for (DomainObject domainObject : objs) {
try {
if (domainObject.isChanged() && !domainObject.getDomainFile().canSave()) {
list.add(domainObject);
}
}
catch (Exception e) {
Msg.showError(this, null, null, null, e);
}
}
if (list.size() == 0) {
return true;
}
StringBuffer sb = new StringBuffer();
sb.append("The following files are Read-Only and cannot be\n" +
" saved 'As Is.' You must do a manual 'Save As' for these\n" + " files: \n \n");
for (int i = 0; i < list.size(); i++) {
DomainObject obj = list.get(i);
sb.append(obj.getDomainFile().getPathname());
sb.append("\n");
}
// note: put the extra space in or else OptionDialog will not show
// the new line char
sb.append(" \nChoose 'Cancel' to cancel Close Project, or \n");
sb.append("'Lose Changes' to continue.");
if (OptionDialog.showOptionDialog(tool.getToolFrame(), "Read-Only Files", sb.toString(),
"Lose Changes", OptionDialog.QUESTION_MESSAGE) == OptionDialog.OPTION_ONE) {
return true; // Lose changes, so close the project
}
return false;
}
/**
* Fire the project opened event
* @param project project being opened
*/
private void fireProjectOpened(Project project) {
for (ProjectListener listener : tool.getListeners()) {
listener.projectOpened(project);
}
}
/**
* Fire the project closed event.
* @param project project being closed
*/
private void fireProjectClosed(Project project) {
for (ProjectListener listener : tool.getListeners()) {
listener.projectClosed(project);
}
}
/**
* Action for a recently opened project.
*
*/
private class ReopenProjectAction extends DockingAction {
private ProjectLocator projectLocator;
private ReopenProjectAction(ProjectLocator projectLocator, String filename) {
super(filename, plugin.getName(), false);
this.projectLocator = projectLocator;
// ACTIONS - auto generated
setMenuBarData(new MenuData(
new String[] { ToolConstants.MENU_FILE, "Reopen", filename }, null, "AProject"));
tool.setMenuGroup(new String[] { ToolConstants.MENU_FILE, "Reopen" }, "AProject");
setEnabled(true);
setHelpLocation(new HelpLocation(plugin.getName(), "Reopen_Project"));
}
/* (non Javadoc)
* @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
*/
@Override
public void actionPerformed(ActionContext context) {
doOpenProject(projectLocator);
}
}
}

View file

@ -0,0 +1,25 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.main;
/**
* Marker interface to indicate plugin is for Front-end use only.
*/
public interface FrontEndOnly extends FrontEndable {
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,38 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.main;
import ghidra.framework.model.ProjectListener;
/**
* Interface for accessing front-end functionality.
*
*
*/
public interface FrontEndService {
/**
* Adds the specified listener to the front-end tool.
* @param l the project listener
*/
public void addProjectListener(ProjectListener l);
/**
* Removes the specified listener from the front-end tool.
* @param l the project listener
*/
public void removeProjectListener(ProjectListener l);
}

View file

@ -0,0 +1,930 @@
/* ###
* 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.framework.main;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.swing.JFrame;
import javax.swing.JPanel;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;
import db.buffers.DataBuffer;
import docking.*;
import docking.action.DockingAction;
import docking.action.MenuData;
import docking.help.Help;
import docking.help.HelpService;
import docking.util.AnimationUtils;
import docking.widgets.OptionDialog;
import generic.jar.ResourceFile;
import generic.util.WindowUtilities;
import ghidra.app.plugin.GenericPluginCategoryNames;
import ghidra.app.util.GenericHelpTopics;
import ghidra.framework.Application;
import ghidra.framework.LoggingInitialization;
import ghidra.framework.client.*;
import ghidra.framework.main.datatree.ChangedFilesDialog;
import ghidra.framework.main.datatree.CheckInTask;
import ghidra.framework.main.logviewer.event.FVEvent;
import ghidra.framework.main.logviewer.event.FVEvent.EventType;
import ghidra.framework.main.logviewer.event.FVEventListener;
import ghidra.framework.main.logviewer.model.ChunkModel;
import ghidra.framework.main.logviewer.model.ChunkReader;
import ghidra.framework.main.logviewer.ui.FileViewer;
import ghidra.framework.main.logviewer.ui.FileWatcher;
import ghidra.framework.model.*;
import ghidra.framework.options.*;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.util.*;
import ghidra.framework.preferences.Preferences;
import ghidra.framework.project.tool.*;
import ghidra.util.*;
import ghidra.util.bean.GGlassPane;
import ghidra.util.classfinder.ClassSearcher;
import ghidra.util.datastruct.WeakDataStructureFactory;
import ghidra.util.datastruct.WeakSet;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.*;
import ghidra.util.xml.GenericXMLOutputter;
import ghidra.util.xml.XmlUtilities;
/**
* Tool that serves as the the Ghidra Project Window. Only those plugins that
* implement the FrontEndable interface may be <i>directly</i> added to this
* tool by the user. Other plugins that are not marked as FrontEndable may get
* pulled in because the FrontEndable plugins depend on them. These plugins are
* aware of what tool they live in so that they can behave in the appropriate
* manner.
*/
public class FrontEndTool extends PluginTool implements OptionsChangeListener {
public static final String AUTOMATICALLY_SAVE_TOOLS = "Automatically Save Tools";
private static final String USE_ALERT_ANIMATION_OPTION_NAME = "Use Notification Animation";
// TODO: Experimental Option !!
private static final String ENABLE_COMPRESSED_DATABUFFER_OUTPUT =
"Use DataBuffer Output Compression";
private static final int MIN_HEIGHT = 600;
/**
* Preference name for whether to show the "What's New" help page when the
* Ghidra Project Window is displayed.
*/
private final static String GHIDRA_SHOW_WHATS_NEW = "GhidraShowWhatsNew";
/**
* Window state preference for the location of the divider for the split
* pane in the Ghidra Project Window. The divider is visible when another
* project view is opened.
*/
private final static String GHIDRA_MAIN_PANEL_DIVIDER_LOC = "GhidraMainPanelDividerLocation";
private static final String FRONT_END_TOOL_XML_NAME = "FRONTEND";
private static final String FRONT_END_FILE_NAME = "FrontEndTool.xml";
private WeakSet<ProjectListener> listeners;
private FrontEndPlugin plugin;
private ComponentProvider compProvider;
private LogComponentProvider logProvider;
private WindowListener windowListener;
private DockingAction configureToolAction;
private PluginClassManager pluginClassManager;
/**
* Construct a new Ghidra Project Window.
*
* @param pm project manager
*/
public FrontEndTool(ProjectManager pm) {
super(null, pm, null, null /*tool template*/, false, false, false);
setToolName("Project Window");
listeners = WeakDataStructureFactory.createCopyOnWriteWeakSet();
addFrontEndPlugin();
createActions();
loadToolConfigurationFromDisk();
ensureSize();
windowListener = new WindowAdapter() {
@Override
public void windowOpened(WindowEvent e) {
setDividerLocation();
getToolFrame().removeWindowListener(windowListener);
}
};
JFrame toolFrame = getToolFrame();
toolFrame.addWindowListener(windowListener);
AppInfo.setFrontEndTool(this);
AppInfo.setActiveProject(getProject());
}
private void ensureSize() {
JFrame frame = getToolFrame();
Dimension size = frame.getSize();
if (size.height < MIN_HEIGHT) {
size.height = MIN_HEIGHT;
Point center = WindowUtilities.centerOnScreen(size);
frame.setBounds(center.x, center.y, size.width, size.height);
}
}
@Override
public PluginClassManager getPluginClassManager() {
if (pluginClassManager == null) {
pluginClassManager = new PluginClassManager(FrontEndable.class, null);
}
return pluginClassManager;
}
public void selectFiles(Set<DomainFile> files) {
plugin.selectFiles(files);
}
private void loadToolConfigurationFromDisk() {
File saveFile = new File(Application.getUserSettingsDirectory(), FRONT_END_FILE_NAME);
if (!saveFile.exists()) {
addFrontEndablePlugins();
return;
}
try {
InputStream is = new FileInputStream(saveFile);
SAXBuilder sax = XmlUtilities.createSecureSAXBuilder(false, false);
Element root = sax.build(is).getRootElement();
GhidraToolTemplate template = new GhidraToolTemplate(
(Element) root.getChildren().get(0), saveFile.getAbsolutePath());
refresh(template);
}
catch (JDOMException e) {
Msg.showError(this, null, "Error", "Error in XML reading front end configuration", e);
}
catch (IOException e) {
Msg.showError(this, null, "Error", "Error reading front end configuration", e);
}
}
void saveToolConfigurationToDisk() {
ToolTemplate template = saveToolToToolTemplate();
Element root = new Element(FRONT_END_TOOL_XML_NAME);
root.addContent(template.saveToXml());
File saveFile = new File(Application.getUserSettingsDirectory(), FRONT_END_FILE_NAME);
try {
OutputStream os = new FileOutputStream(saveFile);
org.jdom.Document doc = new org.jdom.Document(root);
XMLOutputter xmlOut = new GenericXMLOutputter();
xmlOut.output(doc, os);
os.close();
}
catch (IOException e) {
Msg.showError(this, null, "Error", "Error saving front end configuration", e);
}
}
private void addFrontEndPlugin() {
plugin = new FrontEndPlugin(this);
plugin.setProjectManager(getProjectManager());
try {
addPlugin(plugin);
}
catch (PluginException e) {
// should not happen
Msg.showError(this, getToolFrame(), "Can't Create Project Window", e.getMessage(), e);
}
compProvider = plugin.getFrontEndProvider();
showComponentHeader(compProvider, false);
}
private void initFrontEndOptions() {
ToolOptions options = getOptions("Tool");
HelpLocation help = new HelpLocation("Tool", "Save_Tool");
options.registerOption(AUTOMATICALLY_SAVE_TOOLS, true, help,
"When enabled tools will be saved " + "when they are closed");
options.registerOption(USE_ALERT_ANIMATION_OPTION_NAME, true, help,
"Signals that user notifications " +
"should be animated. This makes notifications more distinguishable.");
options.registerOption(ENABLE_COMPRESSED_DATABUFFER_OUTPUT, Boolean.FALSE, help,
"When enabled data buffers sent to Ghidra Server are compressed (see server configuration for other direction)");
boolean autoSave = options.getBoolean(AUTOMATICALLY_SAVE_TOOLS, true);
GhidraTool.autoSave = autoSave;
boolean animationEnabled = options.getBoolean(USE_ALERT_ANIMATION_OPTION_NAME, true);
AnimationUtils.setAnimationEnabled(animationEnabled);
boolean compressDataBuffers =
options.getBoolean(ENABLE_COMPRESSED_DATABUFFER_OUTPUT, false);
DataBuffer.enableCompressedSerializationOutput(compressDataBuffers);
options.addOptionsChangeListener(this);
}
// a place to clear options that are specific to the FrontEndTool and should be reset between
// opening projects
private void clearFrontEndOptions() {
// TODO: just for the record, it seems odd to me that you would want to the FrontEndTool
// to have the 'auto save' setting be different for different projects--no sir, I don' like it
ToolOptions options = getOptions("Tool");
options.removeOptionsChangeListener(this);
options.removeOption(AUTOMATICALLY_SAVE_TOOLS);
}
@Override
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
Object newValue) {
if (AUTOMATICALLY_SAVE_TOOLS.equals(optionName)) {
GhidraTool.autoSave = (Boolean) newValue;
}
else if (USE_ALERT_ANIMATION_OPTION_NAME.equals(optionName)) {
AnimationUtils.setAnimationEnabled((Boolean) newValue);
}
else if (ENABLE_COMPRESSED_DATABUFFER_OUTPUT.equals(optionName)) {
DataBuffer.enableCompressedSerializationOutput((Boolean) newValue);
}
}
@Override
public void exit() {
saveToolConfigurationToDisk();
plugin.exitGhidra();
}
@Override
public void close() {
exit();
}
/**
* Set the active project.
*
* @param project may be null if there is no active project
*/
public void setActiveProject(Project project) {
if (isDisposed) {
return;
}
clearFrontEndOptions();
configureToolAction.setEnabled(true);
setProject(project);
AppInfo.setActiveProject(project);
plugin.setActiveProject(project);
initFrontEndOptions();
}
/**
* Add the given project listener.
*
* @param l listener to add
*/
public void addProjectListener(ProjectListener l) {
listeners.add(l);
}
/**
* Remove the given project listener.
*
* @param l listener to remove
*/
public void removeProjectListener(ProjectListener l) {
listeners.remove(l);
}
/**
* NOTE: do not call this from a non-Swing thread.
*
* @return true if the repository is null or is connected.
*/
boolean checkRepositoryConnected(PluginTool tool) {
RepositoryAdapter repository = tool.getProject().getRepository();
if (repository != null) {
if (!repository.verifyConnection()) {
if (OptionDialog.showYesNoDialog(tool.getToolFrame(), "Lost Connection to Server",
"The connection to the Ghidra Server has been lost.\n" +
"Do you want to reconnect now?") == OptionDialog.OPTION_ONE) {
try {
repository.connect();
return true;
}
catch (NotConnectedException e) {
// message displayed by repository server adapter
return false;
}
catch (IOException e) {
ClientUtil.handleException(repository, e, "Repository Connection",
tool.getToolFrame());
return false;
}
}
return false;
}
}
return true;
}
/**
* Check in the given domain file.
*
* @param tool tool that has the domain file opened
* @param domainFile domain file to check in
* @param taskListener listener that is notified when task completes
*/
public void checkIn(PluginTool tool, DomainFile domainFile, TaskListener taskListener) {
ArrayList<DomainFile> list = new ArrayList<>();
list.add(domainFile);
checkIn(tool, list, taskListener, tool.getToolFrame());
}
/**
* Check in the list of domain files.
*
* @param tool tool that has the domain files opened
* @param fileList list of DomainFile objects
* @param taskListener listener that is notified when task completes
* @param parent parent of dialog if an error occurs during checkin
*/
public void checkIn(PluginTool tool, List<DomainFile> fileList, TaskListener taskListener,
Component parent) {
if (!checkRepositoryConnected(tool)) {
return;
}
ArrayList<DomainFile> changedList = new ArrayList<>();
ArrayList<DomainFile> list = new ArrayList<>();
for (int i = 0; i < fileList.size(); i++) {
DomainFile df = fileList.get(i);
if (df != null && df.canCheckin()) {
if (!canCloseDomainFile(df)) {
continue;
}
list.add(df);
if (df.isChanged()) {
changedList.add(df);
}
}
}
if (changedList.size() > 0) {
ChangedFilesDialog dialog = new ChangedFilesDialog(tool, changedList);
dialog.setCancelToolTipText("Cancel Check In");
if (!dialog.showDialog()) {// blocks until the user hits Save or Cancel
Msg.info(this, "Checkin canceled");
return;
}
for (int i = 0; i < changedList.size(); i++) {
DomainFile df = changedList.get(i);
if (df.isChanged()) {
list.remove(df);
}
}
}
if (list.size() > 0) {
tool.execute(new CheckInTask(tool, list, parent));
}
else {
Msg.showError(this, tool.getToolFrame(), "Checkin Failed", "Unable to checkin file(s)");
}
}
/**
* Merge the latest version in the repository with the given checked out
* domain file. Upon completion of the merge, the domain file appears as
* though the latest version was checked out.
*
* @param tool tool that has the domain file opened
* @param domainFile domain file where latest version will be merged into
* @param taskListener listener that is notified when the merge task
* completes
*/
public void merge(PluginTool tool, DomainFile domainFile, TaskListener taskListener) {
ArrayList<DomainFile> list = new ArrayList<>();
list.add(domainFile);
merge(tool, list, taskListener);
}
/**
* Merge the latest version (in the repository) of each checked out file in
* fileList. Upon completion of the merge, the domain file appears as though
* the latest version was checked out.
*
* @param tool tool that has the domain files opened
* @param fileList list of files that are checked out and are to be merged
* @param taskListener listener that is notified when the merge task
* completes
*/
public void merge(PluginTool tool, List<DomainFile> fileList, TaskListener taskListener) {
if (!checkRepositoryConnected(tool)) {
return;
}
ArrayList<DomainFile> list = new ArrayList<>();
ArrayList<DomainFile> changedList = new ArrayList<>();
for (int i = 0; i < fileList.size(); i++) {
DomainFile df = fileList.get(i);
if (df != null && df.canMerge()) {
if (!canCloseDomainFile(df)) {
continue;
}
list.add(df);
if (df.isChanged()) {
changedList.add(df);
}
}
}
if (changedList.size() > 0) {
ChangedFilesDialog dialog = new ChangedFilesDialog(tool, changedList);
dialog.setCancelToolTipText("Cancel Merge");
if (!dialog.showDialog()) {// blocks until the user hits Save or Cancel
Msg.info(this, "Merge canceled");
return;
}
for (int i = 0; i < changedList.size(); i++) {
DomainFile df = changedList.get(i);
if (df.isChanged()) {
list.remove(df);
}
}
}
if (list.size() > 0) {
execute(new MergeTask(tool, list, taskListener));
}
else {
Msg.showError(this, tool.getToolFrame(), "Update Failed", "Unable to update file(s)");
}
}
@Override
public void setVisible(boolean visibility) {
if (visibility) {
super.setVisible(visibility);
plugin.rebuildRecentMenus();
checkWhatsNewPreference();
}
else {
super.setVisible(visibility);
// Treat setVisible(false) as a dispose, as this is the only time we should be hidden
AppInfo.setFrontEndTool(null);
AppInfo.setActiveProject(null);
dispose();
}
}
public void setBusy(boolean busy) {
JFrame rootFrame = winMgr.getRootFrame();
Component glassPane = rootFrame.getGlassPane();
if (!(glassPane instanceof GGlassPane)) {
Msg.debug(this, "Found root frame without a GhidraGlassPane registered!");
return;
}
GGlassPane dockingGlassPane = (GGlassPane) glassPane;
dockingGlassPane.setBusy(busy);
}
private void addManageExtensionsAction() {
DockingAction installExtensionsAction = new DockingAction("Extensions", "Project Window") {
@Override
public void actionPerformed(ActionContext context) {
showExtensions();
extensionTableProvider.setHelpLocation(
new HelpLocation(GenericHelpTopics.FRONT_END, "Extensions"));
}
@Override
public boolean isEnabledForContext(ActionContext context) {
return isConfigurable();
}
};
installExtensionsAction.setMenuBarData(new MenuData(
new String[] { ToolConstants.MENU_FILE, "Install Extensions..." }, null, "Extensions"));
installExtensionsAction.setHelpLocation(
new HelpLocation(GenericHelpTopics.FRONT_END, "Extensions"));
installExtensionsAction.setEnabled(true);
addAction(installExtensionsAction);
}
private void addManagePluginsAction() {
configureToolAction = new DockingAction("Configure Tool", "Project Window") {
@Override
public void actionPerformed(ActionContext context) {
showConfig(false, false);
manageDialog.setHelpLocation(
new HelpLocation(GenericHelpTopics.FRONT_END, "Configure"));
}
@Override
public boolean isEnabledForContext(ActionContext context) {
return isConfigurable();
}
};
configureToolAction.setMenuBarData(new MenuData(
new String[] { ToolConstants.MENU_FILE, "Configure..." }, null, "Configure"));
configureToolAction.setHelpLocation(
new HelpLocation(GenericHelpTopics.FRONT_END, "Configure"));
configureToolAction.setEnabled(true);
addAction(configureToolAction);
}
@Override
public ToolTemplate getToolTemplate(boolean includeConfigState) {
ToolTemplate toolTemplate = new FrontEndToolTemplate(getIconURL(),
saveToXml(includeConfigState), getSupportedDataTypes());
return toolTemplate;
}
//////////////////////////////////////////////////////////////////////
/**
* Get project listeners.
*
* @return ProjectListener[]
*/
Iterable<ProjectListener> getListeners() {
return listeners;
}
// access for Junit tests
ComponentProvider getProvider() {
return compProvider;
}
SaveState getSaveableDisplayData() {
SaveState saveState = new SaveState();
plugin.writeDataState(saveState);
return saveState;
}
void setSaveableDisplayData(SaveState saveState) {
plugin.readDataState(saveState);
}
////////////////////////////////////////////////////////////////////
/**
* Add those plugins that implement the FrontEndable interface and have a
* RELEASED status and not (example || testing) category.
*/
private void addFrontEndablePlugins() {
List<String> classNames = new ArrayList<>();
for (Class<? extends Plugin> pluginClass : ClassSearcher.getClasses(Plugin.class,
c -> FrontEndable.class.isAssignableFrom(c))) {
PluginDescription pd = PluginDescription.getPluginDescription(pluginClass);
String category = pd.getCategory();
boolean isBadCategory = category.equals(GenericPluginCategoryNames.EXAMPLES) ||
category.equals(GenericPluginCategoryNames.TESTING);
if (pd.getStatus() == PluginStatus.RELEASED && !isBadCategory) {
classNames.add(pluginClass.getName());
}
}
try {
addPlugins(classNames.toArray(new String[classNames.size()]));
}
catch (PluginException e) {
Msg.showError(this, getToolFrame(), "Plugin Error", "Error restoring front-end plugins",
e);
}
}
/**
* Refresh the plugins in the Ghidra Project Window based on what is
* contained in the given XML Element.
*
* @param tc object that contains an entry for each plugin and its
* configuration state
*/
private void refresh(ToolTemplate tc) {
listeners = WeakDataStructureFactory.createCopyOnWriteWeakSet();
List<Plugin> list = getManagedPlugins();
list.remove(plugin);
Plugin[] plugins = new Plugin[list.size()];
plugins = list.toArray(plugins);
removePlugins(plugins);
Element root = tc.saveToXml();
Element elem = root.getChild("TOOL");
restoreOptionsFromXml(elem);
try {
restorePluginsFromXml(elem);
}
catch (PluginException e) {
Msg.showError(this, getToolFrame(), "Error Restoring Front-end Plugins", e.getMessage(),
e);
}
winMgr.restoreFromXML(tc.getToolElement());
setConfigChanged(false);
}
private void createActions() {
addExitAction();
addManagePluginsAction();
addManageExtensionsAction();
addOptionsAction();
addHelpActions();
// our log file action
DockingAction action = new DockingAction("Show Log", "Tool") {
@Override
public void actionPerformed(ActionContext context) {
showGhidraUserLogFile();
}
};
action.setMenuBarData(
new MenuData(new String[] { ToolConstants.MENU_HELP, "Show Log" }, null, "BBB"));
action.setEnabled(true);
addAction(action);
}
private void setDividerLocation() {
String dividerLocStr = Preferences.getProperty(GHIDRA_MAIN_PANEL_DIVIDER_LOC);
if (dividerLocStr != null) {
int dividerLoc = parse(dividerLocStr, -1);
ProjectDataPanel pdp = plugin.getProjectDataPanel();
pdp.setDividerLocation(dividerLoc);
pdp.invalidate();
getToolFrame().validate();
}
}
/**
* Get the int value for the given string.
*
* @param value
* @param defaultValue return this value if a NumberFormatException is
* thrown during the parseInt() method
*/
private int parse(String value, int defaultValue) {
if (value != null) {
try {
return Integer.parseInt(value);
}
catch (NumberFormatException e) {
// don't care
}
}
return defaultValue;
}
/**
* Check the "What's New" preference; if it has not been set, then show the
* "What's New" help page. This should only happen if the preference was
* never set.
*/
private void checkWhatsNewPreference() {
if (SystemUtilities.isInDevelopmentMode() || SystemUtilities.isInTestingMode()) {
return; // don't show help for dev mode
}
HelpService help = Help.getHelpService();
// if this is the first time Ghidra is being run, pop up
// the What's New help page
String showWhatsNewStribng = Preferences.getProperty(GHIDRA_SHOW_WHATS_NEW, "true");
boolean showWhatsNew = Boolean.parseBoolean(showWhatsNewStribng);
if (!showWhatsNew) {
return;
}
Preferences.setProperty(GHIDRA_SHOW_WHATS_NEW, "false");
Preferences.store();
ResourceFile installDir = Application.getInstallationDirectory();
ResourceFile whatsNewFile = new ResourceFile(installDir, "docs/WhatsNew.html");
try {
URL url = whatsNewFile.toURL();
help.showHelp(url);
}
catch (MalformedURLException e) {
Msg.debug(this, "Unable to show the What's New help page", e);
}
}
@Override
public boolean canCloseDomainFile(DomainFile df) {
Tool[] tools = getProject().getToolManager().getRunningTools();
for (Tool tool : tools) {
DomainFile[] files = tool.getDomainFiles();
for (DomainFile domainFile : files) {
if (df == domainFile) {
return tool.canCloseDomainFile(df);
}
}
}
return true;
}
void showGhidraUserLogFile() {
File logFile = LoggingInitialization.getApplicationLogFile();
if (logFile == null) {
return;// something odd is going on; can't find log file
}
if (logProvider == null) {
logProvider = new LogComponentProvider(this, logFile);
showDialog(logProvider, getToolFrame());
return;
}
if (logProvider.isShowing()) {
logProvider.toFront();
}
else {
showDialog(logProvider, getToolFrame());
}
}
//==================================================================================================
// Inner Classes
//==================================================================================================
private static class LogComponentProvider extends DialogComponentProvider {
private final File logFile;
private Dimension defaultSize = new Dimension(600, 400);
private FileWatcher watcher;
LogComponentProvider(PluginTool tool, File logFile) {
super("Ghidra User Log", false, false, false, false);
this.logFile = logFile;
addWorkPanel(buildWorkPanel());
}
/**
* Need to override this method so we can stop the file watcher when the
* dialog is closed.
*/
@Override
protected void dialogClosed() {
if (watcher != null) {
watcher.stop();
}
}
/**
* Need to override this method so we can stop the file watcher when the
* dialog is closed.
*/
@Override
protected void dialogShown() {
if (watcher != null) {
watcher.start();
}
}
private JPanel buildWorkPanel() {
JPanel panel = new JPanel(new BorderLayout()) {
@Override
public Dimension getPreferredSize() {
return defaultSize;
}
};
try {
FVEventListener eventListener = new FVEventListener();
ChunkModel model = new ChunkModel();
ChunkReader reader = new ChunkReader(logFile, model);
FileViewer viewer = new FileViewer(reader, model, eventListener);
panel.add(viewer);
panel.setVisible(true);
// Turn on the file watcher so events will be fired off whenever the log file
// changes.
watcher = new FileWatcher(logFile, eventListener);
watcher.start();
// Now tell subscribers that the file needs to be read-in. Have it view the bottom
// of the file on startup.
FVEvent loadEvt = new FVEvent(EventType.SCROLL_END, null);
eventListener.send(loadEvt);
}
catch (IOException e) {
Msg.error(this, "Exception reading log file", e);
}
return panel;
}
}
/**
* Task to merge latest version of a domain file into the checked out
* version.
*/
private class MergeTask extends Task {
private List<DomainFile> list;
private PluginTool tool;
private TaskListener taskListener;
private boolean wasCanceled;
/**
* Construct a new MergeTask.
*
* @param tool tool that has the domain files open
* @param list list of DomainFiles to be merged
* @param taskListener listener that is notified when this task
* completes
*/
MergeTask(PluginTool tool, List<DomainFile> list, TaskListener taskListener) {
super("Merge", true, true, true);
this.tool = tool;
this.list = list;
this.taskListener = taskListener;
}
@Override
public void run(TaskMonitor monitor) {
String currentName = null;
try {
for (int i = 0; i < list.size() && !monitor.isCancelled(); i++) {
DomainFile df = list.get(i);
currentName = df.getName();
monitor.setMessage("Initiating Merging for " + currentName);
df.merge(true, monitor);
}
}
catch (VersionException e) {
Msg.showError(this, tool.getToolFrame(), "Error During Merge Process",
"Versioned file was created with newer version of Ghidra: " + currentName);
}
catch (CancelledException e) {
wasCanceled = true;
Msg.info(this, "Merge Process was canceled");
}
catch (IOException e) {
ClientUtil.handleException(getProject().getRepository(), e, "Merge Process",
tool.getToolFrame());
}
notifyTaskListener();
}
private void notifyTaskListener() {
SystemUtilities.runSwingNow(() -> {
if (wasCanceled) {
taskListener.taskCancelled(MergeTask.this);
}
else {
taskListener.taskCompleted(MergeTask.this);
}
});
}
}
private static class FrontEndToolTemplate extends GhidraToolTemplate {
FrontEndToolTemplate(ToolIconURL iconURL, Element element, Class<?>[] supportedDataTypes) {
super(iconURL, element, supportedDataTypes);
}
}
}

View file

@ -0,0 +1,26 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.main;
/**
* Marker that allows a plugin to be added <i>directly</i> to the Ghidra Project
* Window by the user.
* Some plugins may get added due to dependencies by FrontEndable
* plugins.
*/
public interface FrontEndable {
}

View file

@ -0,0 +1,104 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.main;
import java.io.IOException;
import ghidra.framework.client.ClientUtil;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainObject;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.Task;
import ghidra.util.task.TaskMonitor;
/**
* A modal task that gets a domain object for a specific version.
*
*
*/
public class GetVersionedObjectTask extends Task {
private Object consumer;
private DomainFile domainFile;
private int versionNumber;
private DomainObject versionedObj;
/**
* Constructor; task will get a read only domain object
* @param consumer consumer of the domain object
* @param domainFile domain file
* @param versionNumber version
*/
public GetVersionedObjectTask(Object consumer, DomainFile domainFile,
int versionNumber) {
this(consumer, domainFile, versionNumber, true);
}
/**
* Constructor
* @param consumer consumer of the domain object
* @param domainFile domain file
* @param versionNumber version
* @param readOnly true if the object should be read only versus
* immutable
*/
public GetVersionedObjectTask(Object consumer, DomainFile domainFile,
int versionNumber, boolean readOnly) {
super("Get Versioned Domain Object", true, false, true);
this.consumer = consumer;
this.domainFile = domainFile;
this.versionNumber = versionNumber;
}
/* (non-Javadoc)
* @see ghidra.util.task.Task#run(ghidra.util.task.TaskMonitor)
*/
@Override
public void run(TaskMonitor monitor) {
try {
monitor.setMessage("Getting Version " + versionNumber +
" for " + domainFile.getName());
versionedObj =
domainFile.getReadOnlyDomainObject(consumer, versionNumber,
monitor);
}catch (CancelledException e) {
}catch (IOException e) {
if (domainFile.isInWritableProject()) {
ClientUtil.handleException(AppInfo.getActiveProject().getRepository(), e,
"Get Versioned Object", null);
}
else {
Msg.showError(this, null,
"Error Getting Versioned Object", "Could not get version " + versionNumber +
" for " + domainFile.getName() + ": " + e, e);
}
} catch (VersionException e) {
Msg.showError(this,
null,
"Error Getting Versioned Object", "Could not get version " + versionNumber +
" for " + domainFile.getName() + ": " + e);
}
}
/**
* Return the versioned domain object.
*/
public DomainObject getVersionedObject() {
return versionedObj;
}
}

View file

@ -0,0 +1,261 @@
/* ###
* 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.framework.main;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.border.TitledBorder;
import com.google.common.collect.Iterables;
import docking.DialogComponentProvider;
import docking.options.editor.ButtonPanelFactory;
import docking.widgets.list.ListPanel;
import ghidra.framework.ToolUtils;
import ghidra.framework.model.ToolTemplate;
import ghidra.util.HelpLocation;
class ImportGhidraToolsDialog extends DialogComponentProvider {
private final static String SELECT_ALL = "Select All";
private final static String DESELECT_ALL = "Select None";
private ListPanel listPanel;
private JPanel mainPanel;
private JCheckBox[] checkboxes;
private String[] tools;
private JButton selectAllButton;
private JButton deselectAllButton;
private FrontEndTool tool;
private boolean cancelled = false;
/**
* Construct new SaveDataDiaog
* @param tool front end tool
*/
ImportGhidraToolsDialog(FrontEndTool tool) {
super("Import Ghidra Tools", true);
setHelpLocation(new HelpLocation("Tool", "Import Ghidra Tools"));
this.tool = tool;
mainPanel = createPanel();
addWorkPanel(mainPanel);
addOKButton();
addCancelButton();
addListeners();
}
void showDialog() {
clearStatusText();
loadListData();
tool.showDialog(this);
}
/**
* Gets called when the user clicks on the OK Action for the dialog.
*/
@Override
protected void okCallback() {
close();
}
/**
* Gets called when the user clicks on the Cancel Action for the dialog.
*/
@Override
protected void cancelCallback() {
cancelled = true;
close();
}
/**
* Create the panel for this dialog.
*/
private JPanel createPanel() {
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
JPanel availableToolsPanel = new JPanel(new BorderLayout());
//
// Create Button Panel
//
selectAllButton = new JButton(SELECT_ALL);
selectAllButton.setMnemonic('A');
deselectAllButton = new JButton(DESELECT_ALL);
deselectAllButton.setMnemonic('N');
JPanel buttonPanel = ButtonPanelFactory.createButtonPanel(
new JButton[] { selectAllButton, deselectAllButton });
//
// List Panel
//
listPanel = new ListPanel();
listPanel.setCellRenderer(new DataCellRenderer());
listPanel.setMouseListener(new ListMouseListener());
// Layout Main Panel
availableToolsPanel.add(buttonPanel, BorderLayout.EAST);
availableToolsPanel.add(listPanel, BorderLayout.CENTER);
availableToolsPanel.setBorder(new TitledBorder("Available Tools"));
panel.add(availableToolsPanel, BorderLayout.CENTER);
return panel;
}
/**
* Add listeners to the buttons.
*/
private void addListeners() {
selectAllButton.addActionListener(e -> selectAll());
deselectAllButton.addActionListener(e -> deselectAll());
}
/**
* Select all files to be saved.
*/
private void selectAll() {
for (JCheckBox checkboxe : checkboxes) {
checkboxe.setSelected(true);
}
listPanel.repaint();
}
/**
* Clear selected checkboxes.
*/
private void deselectAll() {
for (JCheckBox checkboxe : checkboxes) {
checkboxe.setSelected(false);
}
listPanel.repaint();
}
private void loadListData() {
Set<ToolTemplate> defaultTools = ToolUtils.getDefaultApplicationTools();
Set<ToolTemplate> extraTools = ToolUtils.getExtraApplicationTools();
Iterable<String> defaultToolNames =
Iterables.transform(defaultTools, ToolTemplate::getPath);
Iterable<String> extraToolNames = Iterables.transform(extraTools, ToolTemplate::getPath);
int elementCount = defaultTools.size() + extraTools.size();
tools = new String[elementCount];
checkboxes = new JCheckBox[elementCount];
Iterator<String> itr = defaultToolNames.iterator();
int count = 0;
while (itr.hasNext()) {
tools[count] = itr.next();
checkboxes[count] = new JCheckBox(tools[count], false);
checkboxes[count].setBackground(Color.WHITE);
count++;
}
itr = extraToolNames.iterator();
while (itr.hasNext()) {
tools[count] = itr.next();
checkboxes[count] = new JCheckBox(tools[count], false);
checkboxes[count].setBackground(Color.LIGHT_GRAY);
count++;
}
listPanel.refreshList(checkboxes);
}
public List<String> getSelectedList() {
//return selectedList;
ArrayList<String> ret = new ArrayList<>();
for (JCheckBox checkboxe : checkboxes) {
if (checkboxe.isSelected()) {
ret.add(checkboxe.getText());
}
}
return ret;
}
public boolean isCancelled() {
return cancelled;
}
//==================================================================================================
// Inner Classes
//==================================================================================================
/**
* Cell renderer to show the checkboxes for the changed data files.
*/
private class DataCellRenderer implements ListCellRenderer<JCheckBox> {
private Font boldFont;
@Override
public Component getListCellRendererComponent(JList<? extends JCheckBox> list,
JCheckBox value, int index, boolean isSelected, boolean cellHasFocus) {
if (boldFont == null) {
Font font = list.getFont();
boldFont = new Font(font.getName(), font.getStyle() | Font.BOLD, font.getSize());
}
if (index == -1) {
int selected = list.getSelectedIndex();
if (selected == -1) {
return null;
}
index = selected;
}
return checkboxes[index];
}
}
/**
* Mouse listener to get the selected cell in the list.
*/
private class ListMouseListener extends MouseAdapter {
@Override
public void mouseClicked(MouseEvent e) {
clearStatusText();
@SuppressWarnings("unchecked")
JList<JCheckBox> list = (JList<JCheckBox>) e.getSource();
int index = list.locationToIndex(e.getPoint());
if (index < 0) {
return;
}
boolean selected = checkboxes[index].isSelected();
checkboxes[index].setSelected(!selected);
listPanel.repaint();
}
}
}

View file

@ -0,0 +1,118 @@
/* ###
* 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.framework.main;
import java.awt.*;
import javax.swing.*;
import javax.swing.border.Border;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import docking.StatusBarSpacer;
import docking.ToolTipManager;
import docking.help.Help;
import docking.help.HelpService;
import docking.widgets.EmptyBorderButton;
import ghidra.util.*;
import ghidra.util.layout.HorizontalLayout;
import log.LogListener;
import log.LogPanelAppender;
import resources.ResourceManager;
/**
* A JPanel that contains a label to show the last message displayed. It also has a button to
* show the Console.
*/
public class LogPanel extends JPanel implements LogListener {
private JButton button;
private JLabel label;
private Color defaultColor;
LogPanel(final FrontEndPlugin plugin) {
super(new BorderLayout());
JPanel panel = new JPanel(new BorderLayout());
panel.setBorder(BorderFactory.createEmptyBorder(8, 4, 4, 2));
button = new EmptyBorderButton(ResourceManager.loadImage("images/monitor.png"));
label = new JLabel();
label.setName("Details");
defaultColor = label.getForeground();
panel.add(label, BorderLayout.CENTER);
JPanel eastPanel = new JPanel(new HorizontalLayout(0));
eastPanel.add(button);
eastPanel.add(new StatusBarSpacer());
panel.add(eastPanel, BorderLayout.EAST);
Border b = BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(0, 0, 0, 5),
BorderFactory.createLoweredBevelBorder());
label.setBorder(b);
button.setPreferredSize(new Dimension(24, 24));
button.setFocusable(false);
ToolTipManager.setToolTipText(button, "Show Console (Refresh Open Console)");
button.addActionListener(e -> {
FrontEndTool tool = (FrontEndTool) plugin.getTool();
tool.showGhidraUserLogFile();
});
addLogAppender();
add(panel, BorderLayout.NORTH);
}
/**
* Set the help location for the components in the LogPanel.
* @param helpLocation help location for this LogPanel
*
*/
public void setHelpLocation(HelpLocation helpLocation) {
HelpService help = Help.getHelpService();
help.registerHelp(button, helpLocation);
button.setFocusable(true);
}
@Override
public void messageLogged(String message, boolean isError) {
SystemUtilities.runIfSwingOrPostSwingLater(() -> {
label.setForeground(defaultColor);
if (isError) {
label.setForeground(Color.RED);
}
label.setText(message);
ToolTipManager.setToolTipText(label, message);
});
}
/**
* Extracts the {@link LogPanelAppender} from the root logger configuration
* and hands an instance of this panel to it.
*/
private void addLogAppender() {
LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
Configuration config = ctx.getConfiguration();
LogPanelAppender logAppender = config.getAppender("logPanel");
if (logAppender == null) {
Msg.error(this, "Couldn't find LogPanelAppender instance in the Log4j context; " +
"nothing will be logged to the application's Front-end panel.");
return;
}
logAppender.setLogListener(this);
}
}

View file

@ -0,0 +1,81 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.main;
import ghidra.util.bean.GGlassPane;
import java.awt.Rectangle;
import org.jdesktop.animation.timing.Animator;
import org.jdesktop.animation.timing.TimingTargetAdapter;
import org.jdesktop.animation.timing.interpolation.PropertySetter;
/**
* Changes the 'containerBounds' field on the {@link ZoomedImagePainter} via the
* setters/getters in order to move where the painter paints.
*/
class MoveImageRunner {
private Animator animator;
private final GGlassPane dockingGlassPane;
public MoveImageRunner( GGlassPane ghidraGlassPane, Rectangle startBounds,
Rectangle endBounds, ZoomedImagePainter painter ) {
this( ghidraGlassPane, startBounds, endBounds, painter, false );
}
/**
* Changes the bounds of the given painter over a period of time
*
* @param ghidraGlassPane The glass pane we are using to paint
* @param startBounds The start position and size
* @param endBounds The end position and size
* @param painter The painter upon which we will update bounds
* @param repaint true signals to repaint as the changes are made. This can lead to
* choppiness when using other animators in conjunction with the one used by this
* class.
*/
public MoveImageRunner( GGlassPane ghidraGlassPane, Rectangle startBounds,
Rectangle endBounds, ZoomedImagePainter painter, boolean repaint ) {
this.dockingGlassPane = ghidraGlassPane;
// changes the 'containerBounds' field on the painter via the setters/getters
// note: a smaller duration here allows more location changing to be painted
animator = PropertySetter.createAnimator( 200, painter,
"targetBounds", startBounds, endBounds );
animator.setAcceleration( 0.2f );
animator.setDeceleration( 0.4f );
if ( repaint ) {
animator.addTarget( new TimingTargetAdapter() {
@Override
public void end() {
dockingGlassPane.repaint();
}
@Override
public void timingEvent( float fraction ) {
dockingGlassPane.repaint();
}
});
}
}
public void run() {
animator.start();
}
}

View file

@ -0,0 +1,409 @@
/* ###
* 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.framework.main;
import java.awt.Dimension;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.ArrayList;
import javax.swing.BorderFactory;
import javax.swing.border.Border;
import docking.wizard.*;
import ghidra.framework.client.*;
import ghidra.framework.model.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.preferences.Preferences;
import ghidra.framework.remote.User;
import ghidra.util.Msg;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.UserAccessException;
/**
* Manage the panels for the "New Project" wizard. The wizard handles
* creating a local project and a "shared" project.
* If the project is shared, the panel order is
* (1) Server Info
* (2) Repository panel
* (3) Project access panel (if user has admin privileges AND user is
* creating a new repository)
* (4) Specify Project Location panel.
* If the project is not shared, the only other panel to show is the
* Specify Project Location panel.
*
*/
class NewProjectPanelManager implements PanelManager {
private WizardManager wizardMgr;
private String[] knownUsers;
private ProjectTypePanel projectTypePanel;
private SelectProjectPanel selectProjectPanel;
private ServerInfoPanel serverPanel;
private RepositoryPanel repositoryPanel;
private ProjectAccessPanel projectAccessPanel;
private WizardPanel currentWizardPanel;
private boolean includeAnonymousAccessControl = false;
private ProjectManager projectMgr;
private RepositoryServerAdapter server;
private RepositoryAdapter repository;
private ServerInfo serverInfo;
private ProjectLocator newProjectLocator;
private String statusMessage;
private PluginTool tool;
final static Border EMPTY_BORDER = BorderFactory.createEmptyBorder(80, 120, 0, 120);
NewProjectPanelManager(FrontEndTool tool) {
projectTypePanel = new ProjectTypePanel(this);
selectProjectPanel = new SelectProjectPanel(this);
serverPanel = new ServerInfoPanel(this);
projectMgr = tool.getProjectManager();
this.tool = tool;
}
@Override
public boolean canFinish() {
if (!projectTypePanel.isValidInformation()) {
return false;
}
if (!projectTypePanel.isSharedProject() && selectProjectPanel.isValidInformation()) {
return true;
}
if (repositoryPanel == null) {
return false;
}
if (repositoryPanel.isValidInformation() &&
(projectAccessPanel == null ||
projectAccessPanel != null && projectAccessPanel.isValidInformation()) &&
selectProjectPanel.isValidInformation()) {
return true;
}
return false;
}
@Override
public boolean hasNextPanel() {
if (currentWizardPanel == selectProjectPanel) {
if (selectProjectPanel.isValidInformation() && projectTypePanel.isValidInformation() &&
!projectTypePanel.isSharedProject()) {
return false;
}
}
return currentWizardPanel != selectProjectPanel;
}
@Override
public boolean hasPreviousPanel() {
return currentWizardPanel != projectTypePanel;
}
@Override
public WizardPanel getInitialPanel() {
currentWizardPanel = projectTypePanel;
return currentWizardPanel;
}
@Override
public WizardPanel getNextPanel() {
if (currentWizardPanel == null) {
currentWizardPanel = projectTypePanel;
}
else if (currentWizardPanel == projectTypePanel) {
if (projectTypePanel.isSharedProject()) {
currentWizardPanel = serverPanel;
serverPanel.setServerInfo(projectMgr.getMostRecentServerInfo());
}
else {
server = null;
serverInfo = null;
currentWizardPanel = selectProjectPanel;
}
}
else if (currentWizardPanel == serverPanel) {
String serverName = serverPanel.getServerName();
int portNumber = serverPanel.getPortNumber();
if (!isServerInfoValid(serverName, portNumber)) {
return serverPanel;
}
try {
knownUsers = server.getAllUsers();
String[] repositoryNames = server.getRepositoryNames();
includeAnonymousAccessControl = server.anonymousAccessAllowed();
if (repositoryPanel == null) {
repositoryPanel =
new RepositoryPanel(this, serverName, repositoryNames, server.isReadOnly());
}
currentWizardPanel = repositoryPanel;
}
catch (RemoteException e) {
statusMessage = "Error accessing remote server on " + serverName;
}
catch (NotConnectedException e) {
statusMessage = e.getMessage();
if (statusMessage == null) {
statusMessage = "Not Connected to server " + serverName;
}
}
catch (IOException e) {
statusMessage = "IOException: could not access remote server on " + serverName;
}
}
else if (currentWizardPanel == repositoryPanel) {
if (repository != null) {
repository.disconnect();
repository = null;
}
String repositoryName = repositoryPanel.getRepositoryName();
selectProjectPanel.setProjectName(repositoryName);
if (!repositoryPanel.createRepository()) {
currentWizardPanel = selectProjectPanel;
selectProjectPanel.setProjectName(repositoryName);
repository = server.getRepository(repositoryName);
statusMessage = selectProjectPanel.getStatusMessage();
return currentWizardPanel;
}
checkNewRepositoryAccessPanel();
currentWizardPanel = projectAccessPanel;
}
else if (currentWizardPanel == projectAccessPanel) {
currentWizardPanel = selectProjectPanel;
statusMessage = selectProjectPanel.getStatusMessage();
}
else {
currentWizardPanel = null;
}
return currentWizardPanel;
}
/**
* Build repository access panel for new repository only.
* @throws IOException
*/
private void checkNewRepositoryAccessPanel() {
String repositoryName = repositoryPanel.getRepositoryName();
if (projectAccessPanel != null &&
projectAccessPanel.getRepositoryName().equals(repositoryName)) {
return;
}
ArrayList<User> userList = new ArrayList<>();
userList.add(new User(server.getUser(), User.ADMIN));
try {
projectAccessPanel = new ProjectAccessPanel(knownUsers, server.getUser(), userList,
repositoryName, server.anonymousAccessAllowed(), false, tool);
}
catch (IOException e) {
Msg.error(this, "Error creating project access panel");
}
}
@Override
public WizardPanel getPreviousPanel() {
if (currentWizardPanel == selectProjectPanel) {
if (projectTypePanel.isSharedProject()) {
if (repositoryPanel.createRepository()) {
currentWizardPanel = projectAccessPanel;
}
else {
currentWizardPanel = repositoryPanel;
}
}
else {
currentWizardPanel = projectTypePanel;
}
}
else if (currentWizardPanel == projectAccessPanel) {
currentWizardPanel = repositoryPanel;
}
else if (currentWizardPanel == repositoryPanel) {
currentWizardPanel = serverPanel;
}
else if (currentWizardPanel == serverPanel) {
currentWizardPanel = projectTypePanel;
}
else {
currentWizardPanel = null;
}
return currentWizardPanel;
}
@Override
public String getStatusMessage() {
String msg = statusMessage;
statusMessage = null;
return msg;
}
@Override
public void finish() {
ProjectLocator projectLocator = selectProjectPanel.getProjectLocator();
if (server != null) {
boolean createNewRepository = repositoryPanel.createRepository();
if (!createNewRepository) {
if (repository == null) {
repository = server.getRepository(repositoryPanel.getRepositoryName());
}
}
else {
try {
repository = server.createRepository(repositoryPanel.getRepositoryName());
repository.setUserList(projectAccessPanel.getProjectUsers(),
projectAccessPanel.allowAnonymousAccess());
}
catch (DuplicateNameException e) {
statusMessage = "Repository " + repositoryPanel.getRepositoryName() + " exists";
}
catch (UserAccessException exc) {
statusMessage = "Could not update the user list: " + exc.getMessage();
return;
}
catch (NotConnectedException e) {
statusMessage = e.getMessage();
if (statusMessage == null) {
statusMessage = "Not connected to server " + serverInfo.getServerName();
}
return;
}
catch (IOException exc) {
String msg = exc.getMessage();
if (msg == null) {
msg = exc.toString();
}
statusMessage = "Error occurred while updating the user list: " + msg;
return;
}
}
}
Preferences.setProperty(Preferences.LAST_NEW_PROJECT_DIRECTORY,
projectLocator.getLocation());
Preferences.store();
newProjectLocator = projectLocator;
wizardMgr.close();
}
@Override
public void cancel() {
currentWizardPanel = null;
repositoryPanel = null;
projectAccessPanel = null;
server = null;
if (repository != null) {
repository.disconnect();
repository = null;
}
}
@Override
public void initialize() {
currentWizardPanel = null;
selectProjectPanel.initialize();
serverPanel.initialize();
// serverPanel.setServerInfo(serverInfo);
if (repositoryPanel != null) {
repositoryPanel.initialize();
}
if (projectAccessPanel != null) {
projectAccessPanel.initialize();
}
}
@Override
public Dimension getPanelSize() {
return getMyPanelSize();
}
@Override
public void setWizardManager(WizardManager wm) {
wizardMgr = wm;
}
@Override
public WizardManager getWizardManager() {
return wizardMgr;
}
/**
* Get the project that was created.
* @return null if no project was created
*/
ProjectLocator getNewProjectLocation() {
return newProjectLocator;
}
/**
* Get the repository adapter associated with the new project.
* After displaying this panel, this method should be invoked to obtain the
* repository which will be opended for shared projects. If the repository is
* not used to create a new project, its disconnect method should be invoked.
* @return null if project is not shared
*/
RepositoryAdapter getProjectRepository() {
return repository;
}
String getProjectRepositoryName() {
return repositoryPanel.getRepositoryName();
}
boolean isSharedProject() {
return projectTypePanel.isSharedProject();
}
/**
* Return true if a connection could be established using the given
* server name and port number.
*/
private boolean isServerInfoValid(String serverName, int portNumber) {
if (server != null && serverInfo != null && serverInfo.getServerName().equals(serverName) &&
serverInfo.getPortNumber() == portNumber && server.isConnected()) {
return true;
}
repositoryPanel = null;
server = projectMgr.getRepositoryServerAdapter(serverName, portNumber, true);
if (server.isConnected()) {
serverInfo = projectMgr.getMostRecentServerInfo();
return true;
}
server = null;
serverInfo = null;
statusMessage = "Could not connect to server " + serverName + ", port " + portNumber;
return false;
}
private Dimension getMyPanelSize() {
ProjectAccessPanel panel1 = new ProjectAccessPanel(new String[] { "nobody" }, "user",
new ArrayList<User>(), "MyRepository", true, false, tool);
RepositoryPanel panel2 = new RepositoryPanel(this, "ServerOne",
new String[] { "MyRepository", "NewStuff", "Repository_A", "Repository_B" }, false);
Dimension d1 = panel1.getPreferredSize();
Dimension d2 = panel2.getPreferredSize();
return new Dimension(Math.max(d1.width, d2.width), Math.max(d1.height, d2.height));
}
}

View file

@ -0,0 +1,200 @@
/* ###
* 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.framework.main;
import java.awt.BorderLayout;
import java.awt.Component;
import java.util.*;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import docking.DialogComponentProvider;
import docking.widgets.table.*;
import ghidra.framework.model.*;
import ghidra.framework.project.tool.GhidraToolTemplate;
import ghidra.util.HelpLocation;
public class PickToolDialog extends DialogComponentProvider {
private final FrontEndTool tool;
private ToolTableModel model;
private GTable table;
private ToolTemplate selectedTemplate;
private final Class<? extends DomainObject> domainClass;
protected PickToolDialog(FrontEndTool tool, Class<? extends DomainObject> domainClass) {
super("Pick Tool", true);
this.tool = tool;
this.domainClass = domainClass;
setHelpLocation(new HelpLocation("Tool", "Set Tool Associations"));
addWorkPanel(createWorkPanel());
addOKButton();
addCancelButton();
setPreferredSize(300, 400);
setRememberLocation(false);
}
private JComponent createWorkPanel() {
JPanel mainPanel = new JPanel(new BorderLayout());
model = new ToolTableModel();
table = new GTable(model);
table.setRowHeight(28); // make big enough for tool icons
table.setColumnHeaderPopupEnabled(false); // don't allow column configuration
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.setDefaultRenderer(GhidraToolTemplate.class, new ToolTemplateRenderer());
table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) {
return;
}
int selectedRow = table.getSelectedRow();
ToolTemplate template = model.getRowObject(selectedRow);
okButton.setEnabled(template != null);
}
});
loadList();
mainPanel.add(new JScrollPane(table), BorderLayout.CENTER);
return mainPanel;
}
private void loadList() {
Project project = tool.getProject();
ToolServices toolServices = project.getToolServices();
Set<ToolTemplate> compatibleTools = toolServices.getCompatibleTools(domainClass);
model.setData(new ArrayList<>(compatibleTools));
}
void showDialog() {
clearStatusText();
tool.showDialog(this);
}
@Override
protected void okCallback() {
int selectedRow = table.getSelectedRow();
selectedTemplate = model.getRowObject(selectedRow);
close();
}
ToolTemplate getSelectedToolTemplate() {
return selectedTemplate;
}
//==================================================================================================
// Inner Classes
//==================================================================================================
private class ToolTableModel extends AbstractSortedTableModel<ToolTemplate> {
private List<ToolTemplate> data;
ToolTableModel() {
super(0);
this.data = Collections.emptyList();
}
void setData(List<ToolTemplate> data) {
this.data = data;
fireTableDataChanged();
}
@Override
public String getName() {
return "Tool Picker";
}
@Override
public Object getColumnValueForRow(ToolTemplate t, int column) {
return t;
}
@Override
public String getColumnName(int column) {
return "Tool";
}
@Override
public Class<?> getColumnClass(int column) {
return GhidraToolTemplate.class;
}
@Override
public List<ToolTemplate> getModelData() {
return data;
}
@Override
public boolean isSortable(int columnIndex) {
return columnIndex == 0;
}
@Override
public int getColumnCount() {
return 1;
}
@Override
public int getRowCount() {
return data.size();
}
@Override
protected Comparator<ToolTemplate> createSortComparator(int column) {
return new ToolTemplateComparator();
}
}
private class ToolTemplateComparator implements Comparator<ToolTemplate> {
@Override
public int compare(ToolTemplate o1, ToolTemplate o2) {
return o1.getName().compareTo(o2.getName());
}
}
private class ToolTemplateRenderer extends GTableCellRenderer {
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
JLabel renderer = (JLabel) super.getTableCellRendererComponent(data);
Object value = data.getValue();
ToolTemplate template = (ToolTemplate) value;
if (template == null) {
return renderer;
}
renderer.setIcon(template.getIcon());
renderer.setText(template.getName());
return renderer;
}
}
}

View file

@ -0,0 +1,25 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.main;
/**
* Marker interface for plugins that only get constructed programatically for specific purposes.
* Plugins that implement this interface should never be added via the config GUIs.
*/
public interface ProgramaticUseOnly {
}

View file

@ -0,0 +1,106 @@
/* ###
* 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.framework.main;
import java.io.IOException;
import docking.DialogComponentProvider;
import ghidra.framework.client.*;
import ghidra.framework.plugintool.Plugin;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.exception.UserAccessException;
/**
* Dialog showing all users associated with a repository and those with
* access to the current shared project. Users with admin rights can use
* this dialog to edit user permissions.
*
*/
class ProjectAccessDialog extends DialogComponentProvider {
private RepositoryAdapter repository;
private ProjectAccessPanel projectAccessPanel;
/**
* Creates a new dialog.
*
* @param plugin the currrent plugin
* @param repHandle the name of the repository
* @param knownUsers list of all users in the repository
* @param allowEditing if true, widgets for adding/removing users will be available
* @throws UserAccessException
* @throws IOException
* @throws NotConnectedException
*/
ProjectAccessDialog(Plugin plugin, RepositoryAdapter repHandle, String[] knownUsers, boolean allowEditing)
throws UserAccessException, IOException, NotConnectedException {
super("Project Access List for " + repHandle.getName(), true);
this.repository = repHandle;
setHelpLocation(new HelpLocation(plugin.getName(), "Edit_Project_Access_List"));
if (allowEditing) {
projectAccessPanel = new ProjectAccessPanel(knownUsers, repository, plugin.getTool());
}
else {
projectAccessPanel = new ViewProjectAccessPanel(repository, plugin.getTool());
}
addWorkPanel(projectAccessPanel);
if (allowEditing) {
addOKButton();
setOkEnabled(true);
addCancelButton();
}
else {
addCancelButton();
setCancelButtonText("Close");
}
}
@Override
protected void cancelCallback() {
close();
}
@Override
protected void okCallback() {
String statusMessage = null;
try {
repository.connect();
repository.setUserList(projectAccessPanel.getProjectUsers(),
projectAccessPanel.allowAnonymousAccess());
close();
Msg.info(this, "Successfully updated project access list.");
}
catch (UserAccessException exc) {
statusMessage = "Could not update the user list: " + exc.getMessage();
}
catch (NotConnectedException e) {
statusMessage = "Server connection is down: " + e.getMessage();
}
catch (IOException exc) {
ClientUtil.handleException(repository, exc, "Update User List", getComponent());
}
if (statusMessage != null) {
setStatusText(statusMessage);
}
}
}

View file

@ -0,0 +1,597 @@
/* ###
* 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.framework.main;
import java.awt.*;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.swing.*;
import docking.options.editor.ButtonPanelFactory;
import docking.widgets.table.GTable;
import docking.wizard.AbstractWizardJPanel;
import ghidra.app.util.GenericHelpTopics;
import ghidra.framework.client.RepositoryAdapter;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.remote.User;
import ghidra.util.HelpLocation;
import resources.ResourceManager;
import util.CollectionUtils;
/**
* Panel that shows the users for a given repository and the users associated with the current
* shared project. There are 3 main sub-panels:
* <p>
* <li>Known Users Panel: Displays all users in the repository</li>
* <li>Button Panel: Provides buttons for adding/removing users from the project</li>
* <li>User Access Panel: Displays all users on the project, and their access permissions</li>
* <p>
* If the current user is an admin, he may change user permissions and add/remove them
* from the project. If not, only the User Access Panel will be visible and it will
* be read-only.
*
*/
public class ProjectAccessPanel extends AbstractWizardJPanel {
protected KnownUsersPanel knownUsersPanel;
protected UserAccessPanel userAccessPanel;
protected ButtonPanel addRemoveButtonPanel;
protected JCheckBox anonymousAccessCB;
protected String currentUser;
protected List<User> origProjectUserList;
protected boolean origAnonymousAccessEnabled;
protected String repositoryName;
protected HelpLocation helpLoc;
protected final Color SELECTION_BG_COLOR = new Color(204, 204, 255);
protected final Color SELECTION_FG_COLOR = Color.BLACK;
protected PluginTool tool;
/**
* Construct a new panel from a {@link RepositoryAdapter} instance.
*
* @param knownUser names of the users that are known to the remote server
* @param repository the repository adapter instance
* @param tool the current tool
* @throws IOException if there's an error processing the repository user list
*/
public ProjectAccessPanel(String[] knownUsers, RepositoryAdapter repository, PluginTool tool)
throws IOException {
this(knownUsers, repository.getServer().getUser(), Arrays.asList(repository.getUserList()),
repository.getName(), repository.getServer().anonymousAccessAllowed(),
repository.anonymousAccessAllowed(), tool);
}
/**
* Constructs a new panel from the given arguments.
*
* @param knownUsers names of the users that are known to the remote server
* @param currentUser the current user
* @param allUsers all users known to the repository
* @param repositoryName the name of the repository
* @param anonymousServerAccessAllowed true if the server allows anonymous access
* @param anonymousAccessEnabled true if the repository allows anonymous access
* (ignored if anonymousServerAccessAllowed is false)
* @param tool the current tool
*/
public ProjectAccessPanel(String[] knownUsers, String currentUser, List<User> allUsers,
String repositoryName, boolean anonymousServerAccessAllowed,
boolean anonymousAccessEnabled, PluginTool tool) {
super(new BorderLayout());
this.currentUser = currentUser;
this.origProjectUserList = allUsers;
this.origAnonymousAccessEnabled = anonymousAccessEnabled;
this.repositoryName = repositoryName;
this.tool = tool;
createMainPanel(knownUsers, anonymousServerAccessAllowed);
}
@Override
public boolean isValidInformation() {
return true;
}
@Override
public String getTitle() {
return "Specify Users for Repository " + repositoryName;
}
@Override
public void initialize() {
userAccessPanel.resetUserList();
if (anonymousAccessCB != null) {
anonymousAccessCB.setSelected(origAnonymousAccessEnabled);
}
}
@Override
public HelpLocation getHelpLocation() {
if (helpLoc != null) {
return helpLoc;
}
return new HelpLocation(GenericHelpTopics.FRONT_END, "UserAccessList");
}
/**
* Sets the help location.
*
* @param helpLoc the help location
*/
void setHelpLocation(HelpLocation helpLoc) {
this.helpLoc = helpLoc;
}
/**
* Returns a list of all users with permission to access the project.
*
* @return the list of users
*/
User[] getProjectUsers() {
return userAccessPanel.getProjectUsers();
}
/**
* Returns true if anonymous access is allowed by the repository.
*
* @return true if allowed
*/
boolean allowAnonymousAccess() {
return anonymousAccessCB != null && anonymousAccessCB.isSelected();
}
/**
* Returns the repository name.
*
* @return the repository name
*/
String getRepositoryName() {
return repositoryName;
}
/**
* Creates the main gui panel, containing the known users, button, and user access
* panels.
*/
protected void createMainPanel(String[] knownUsers, boolean anonymousServerAccessAllowed) {
JPanel mainPanel = new JPanel();
mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.X_AXIS));
knownUsersPanel = new KnownUsersPanel(Arrays.asList(knownUsers));
userAccessPanel = new UserAccessPanel(currentUser);
addRemoveButtonPanel = new ButtonPanel();
mainPanel.add(knownUsersPanel);
mainPanel.add(addRemoveButtonPanel);
mainPanel.add(userAccessPanel);
add(mainPanel, BorderLayout.CENTER);
if (anonymousServerAccessAllowed) {
anonymousAccessCB = new JCheckBox("Allow Anonymous Access");
anonymousAccessCB.setSelected(origAnonymousAccessEnabled);
anonymousAccessCB.setBorder(BorderFactory.createEmptyBorder(10, 10, 0, 0));
add(anonymousAccessCB, BorderLayout.SOUTH);
}
}
/**
* Panel containing the buttons for adding/removing users from the current project.
*/
class ButtonPanel extends JPanel {
private JButton addButton;
private JButton addAllButton;
private JButton removeButton;
private JButton removeAllButton;
public ButtonPanel() {
addButton = new JButton("Add >>");
addButton.setEnabled(false);
addButton.addActionListener(e -> {
userAccessPanel.addUsers(knownUsersPanel.getSelectedUsers());
});
addAllButton = new JButton("Add All");
addAllButton.addActionListener(e -> {
userAccessPanel.addUsers(knownUsersPanel.getAllUsers());
knownUsersPanel.clearSelection();
});
addAllButton.setEnabled(true);
removeButton = new JButton("<< Remove");
removeButton.setEnabled(false);
removeButton.addActionListener(e -> userAccessPanel.removeSelectedUsers());
removeAllButton = new JButton("Remove All");
removeAllButton.addActionListener(e -> {
userAccessPanel.removeAllUsers();
knownUsersPanel.clearSelection();
});
removeAllButton.setEnabled(true);
JPanel panel = ButtonPanelFactory.createButtonPanel(
new JButton[] { addButton, addAllButton, removeButton, removeAllButton }, 5);
panel.setMinimumSize(panel.getPreferredSize());
// Set up a listener so this panel can update its state when something in the user
// permissions list has been selected.
userAccessPanel.getTable().getSelectionModel().addListSelectionListener(e -> {
if (e.getValueIsAdjusting()) {
return;
}
updateState();
});
// Need to update the known users panel whenever a user is added/removed from the
// access panel (the icon showing whether they're in the project or not needs
// to be updated).
userAccessPanel.tableModel.addTableModelListener(e -> {
knownUsersPanel.repaint();
});
// Set up a listener so this panel can update its state when something in the known
// users list has been selected.
knownUsersPanel.getList().getSelectionModel().addListSelectionListener(e -> {
if (e.getValueIsAdjusting()) {
return;
}
updateState();
});
add(panel);
}
/**
* Ensures that all buttons are enabled/disabled appropriately based on the current
* selections.
* <p>
* Note that the "add all" and "remove all" buttons are always enabled so they aren't addressed
* here.
*/
public void updateState() {
updateAddButtonState();
updateRemoveButtonState();
}
/**
* Updates the 'remove' button state based on the selections in the user access panel.
*/
private void updateRemoveButtonState() {
boolean enabled = false;
List<String> selectedUserNames = userAccessPanel.getSelectedUsers();
if (selectedUserNames.isEmpty()) {
enabled = false;
}
else if (selectedUserNames.size() == 1) {
if (selectedUserNames.get(0).equals(currentUser)) {
enabled = false;
}
else {
enabled = true;
}
}
else {
enabled = true;
}
removeButton.setEnabled(enabled);
}
/**
* Updates the 'add' button state based on the selections in the known users panel.
*/
private void updateAddButtonState() {
boolean enabled = false;
List<String> selectedUserNames = knownUsersPanel.getSelectedUsers();
for (String user : selectedUserNames) {
if (!userAccessPanel.isInProjectAccessList(user)) {
enabled = true;
break;
}
}
addButton.setEnabled(enabled);
}
}
/**
* Panel for displaying project users and their access permissions. Users with admin rights
* can edit the permissions of other users.
*/
class UserAccessPanel extends JPanel {
private GTable table;
private UserAccessTableModel tableModel;
/**
* Creates a new user access panel.
*
* @param user the current user
* @param userList the list of users to display in the table
*/
UserAccessPanel(String user) {
setLayout(new BorderLayout());
tableModel = new UserAccessTableModel(user, origProjectUserList, tool);
table = new GTable(tableModel);
table.setShowGrid(false);
table.setSelectionBackground(SELECTION_BG_COLOR);
table.setSelectionForeground(SELECTION_FG_COLOR);
table.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
table.setBorder(BorderFactory.createEmptyBorder());
JScrollPane sp = new JScrollPane(table);
sp.setBorder(BorderFactory.createTitledBorder("Project Users"));
sp.setBackground(getBackground());
add(sp, BorderLayout.CENTER);
setPreferredSize(new Dimension(400, 200));
}
/**
* Returns the user table.
*
* @return the user table
*/
GTable getTable() {
return table;
}
/**
* Reset user list with the original set of users and permissions
*/
void resetUserList() {
tableModel.setUserList(origProjectUserList);
}
/**
* Returns a list of all selected users in the table.
*
* @return list of user names
*/
List<String> getSelectedUsers() {
List<String> users = new ArrayList<>();
int[] selectedRows = table.getSelectedRows();
for (int i = 0; i < selectedRows.length; i++) {
User user = tableModel.getRowObject(selectedRows[i]);
users.add(user.getName());
}
return users;
}
/**
* Returns true if the given user is in the project access list.
*
* @param name the user name
* @return true if already has project access
*/
boolean isInProjectAccessList(String name) {
List<User> usersInProject = tableModel.getDataSource();
for (User user : usersInProject) {
if (user.getName().equals(name)) {
return true;
}
}
return false;
}
/**
* Returns a list of all users who have project access.
*
* @return list of users
*/
User[] getProjectUsers() {
User[] users = new User[tableModel.getModelData().size()];
return tableModel.getModelData().toArray(users);
}
/**
* Removes all users from the table.
*/
private void removeAllUsers() {
ArrayList<User> list = new ArrayList<>();
// Remove all users, except the user entry that represents the one
// doing the removing.
for (User user : tableModel.getModelData()) {
if (user.getName().equals(currentUser)) {
continue;
}
list.add(user);
}
tableModel.removeUsers(list);
}
/**
* Removes only the selected users from the table.
*/
private void removeSelectedUsers() {
ArrayList<User> users = new ArrayList<>();
// Remove all selected users, except the user entry that represents the one
// doing the removing.
for (int selectedRow : table.getSelectedRows()) {
User user = tableModel.getRowObject(selectedRow);
if (user.getName().equals(currentUser)) {
continue;
}
users.add(user);
}
tableModel.removeUsers(users);
}
/**
* Adds the give list of users to the table.
*
* @param users the users to add
*/
private void addUsers(List<String> users) {
ArrayList<User> list = new ArrayList<>();
// Only add the user if they don't already have access.
for (String user : users) {
if (!isInProjectAccessList(user)) {
list.add(new User(user, User.WRITE));
}
}
tableModel.addUsers(list);
}
}
/**
* Panel for displaying the list of users with repository access.
*/
class KnownUsersPanel extends JPanel {
private JList<String> userList;
private DefaultListModel<String> listModel;
/**
* Creates a new users panel.
*
* @param users list of users to display
*/
KnownUsersPanel(List<String> users) {
setLayout(new BorderLayout());
users.sort(String::compareToIgnoreCase);
listModel = new DefaultListModel<>();
for (String user : users) {
listModel.addElement(user);
}
userList = new JList<>(listModel);
userList.setSelectionBackground(SELECTION_BG_COLOR);
userList.setSelectionForeground(SELECTION_FG_COLOR);
userList.setCellRenderer(new UserListCellRenderer());
JScrollPane sp = new JScrollPane(userList);
sp.setBorder(BorderFactory.createTitledBorder("Known Users"));
sp.setOpaque(false);
// Set the minimum dimensions of the scroll pane so we can't collapse it.
Dimension d = userList.getPreferredSize();
d.width = 100;
sp.setPreferredSize(d);
sp.setMinimumSize(new Dimension(100, 200));
add(sp, BorderLayout.CENTER);
}
/**
* Returns a list of selected users
*
* @return list of user names
*/
List<String> getSelectedUsers() {
return userList.getSelectedValuesList();
}
/**
* Returns the user list.
*
* @return the user list
*/
JList<String> getList() {
return userList;
}
/**
* Returns a list of all users in the panel
*
* @return list of user names
*/
List<String> getAllUsers() {
List<String> allUsers = CollectionUtils.asList(listModel.elements());
return allUsers;
}
/**
* Clears any user selection in the panel.
*/
void clearSelection() {
userList.getSelectionModel().clearSelection();
}
/**
* Renderer for the {@link KnownUsersPanel}. This is to ensure that we render the
* correct icon for each user in the list, and that the text is colored properly.
*/
private class UserListCellRenderer extends JLabel implements ListCellRenderer {
private Icon icon;
private Icon inProjectIcon;
UserListCellRenderer() {
icon = ResourceManager.loadImage("images/EmptyIcon.gif");
inProjectIcon = ResourceManager.loadImage("images/user.png");
icon = ResourceManager.getScaledIcon(icon, inProjectIcon.getIconWidth(),
inProjectIcon.getIconHeight());
setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
setOpaque(true);
}
@Override
public Component getListCellRendererComponent(JList list, Object value, int index,
boolean isSelected, boolean cellHasFocus) {
String name = (String) value;
if (userAccessPanel != null) {
setIcon(userAccessPanel.isInProjectAccessList(name) ? inProjectIcon : icon);
}
setText(name);
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
}
else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
return this;
}
}
}
}

View file

@ -0,0 +1,668 @@
/* ###
* 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.framework.main;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
import docking.ActionContext;
import docking.ComponentProvider;
import docking.action.DockingAction;
import docking.action.MenuData;
import docking.widgets.OptionDialog;
import docking.widgets.PasswordChangeDialog;
import docking.widgets.filechooser.GhidraFileChooser;
import ghidra.framework.client.ClientUtil;
import ghidra.framework.client.RepositoryAdapter;
import ghidra.framework.model.*;
import ghidra.framework.plugintool.util.ToolConstants;
import ghidra.framework.preferences.Preferences;
import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.framework.remote.User;
import ghidra.util.*;
class ProjectActionManager {
private final static String CLOSE_ALL_OPEN_VIEWS = "Close All Read-Only Views";
private final static String LAST_VIEWED_PROJECT_DIRECTORY = "LastViewedProjectDirectory";
private final static String LAST_VIEWED_REPOSITORY_URL = "LastViewedRepositoryURL";
private FrontEndTool tool;
private FrontEndPlugin plugin;
private List<ViewInfo> openViewsList;
private List<ViewInfo> reopenViewsList;
private Project activeProject;
private GhidraFileChooser fileChooser;
private RepositoryChooser repositoryChooser;
private DockingAction openProjectViewAction;
private DockingAction openRepositoryViewAction;
private DockingAction addWSAction;
private DockingAction removeWSAction;
private DockingAction renameWSAction;
private DockingAction switchWSAction;
private DockingAction editAccessAction;
private DockingAction viewAccessAction;
private DockingAction setPasswordAction;
private DockingAction viewInfoAction;
private ProjectInfoDialog infoDialog;
ProjectActionManager(FrontEndPlugin plugin) {
this.plugin = plugin;
tool = plugin.getFrontEndTool();
openViewsList = new ArrayList<>();
reopenViewsList = new ArrayList<>();
createActions();
createSwitchWorkspaceAction();
}
private void openRecentView(String urlPath) {
URL url = GhidraURL.toURL(urlPath);
openView(url);
}
private void createActions() {
String owner = plugin.getName();
// create the listeners for the menuitems
openProjectViewAction = new DockingAction("View Project", owner) {
@Override
public void actionPerformed(ActionContext context) {
openProjectView();
}
};
openProjectViewAction.setEnabled(false);
openProjectViewAction.setMenuBarData(
new MenuData(new String[] { ToolConstants.MENU_PROJECT, "View Project..." }, "AView"));
openProjectViewAction.getMenuBarData().setMenuSubGroup("1");
tool.addAction(openProjectViewAction);
openRepositoryViewAction = new DockingAction("View Repository", owner) {
@Override
public void actionPerformed(ActionContext context) {
openRepositoryView();
}
};
openRepositoryViewAction.setEnabled(false);
openRepositoryViewAction.setMenuBarData(new MenuData(
new String[] { ToolConstants.MENU_PROJECT, "View Repository..." }, "AView"));
openRepositoryViewAction.getMenuBarData().setMenuSubGroup("2");
tool.addAction(openRepositoryViewAction);
addWSAction = new DockingAction("Add Workspace", owner) {
@Override
public void actionPerformed(ActionContext context) {
plugin.getWorkspacePanel().addWorkspace();
}
};
addWSAction.setEnabled(false);
addWSAction.setMenuBarData(new MenuData(
new String[] { ToolConstants.MENU_PROJECT, "Workspace", "Add..." }, "zProject"));
tool.addAction(addWSAction);
renameWSAction = new DockingAction("Rename Workspace", owner) {
@Override
public void actionPerformed(ActionContext context) {
plugin.getWorkspacePanel().renameWorkspace();
}
};
renameWSAction.setEnabled(false);
renameWSAction.setMenuBarData(new MenuData(
new String[] { ToolConstants.MENU_PROJECT, "Workspace", "Rename..." }, "zProject"));
tool.addAction(renameWSAction);
removeWSAction = new DockingAction("Delete Workspace", owner) {
@Override
public void actionPerformed(ActionContext context) {
plugin.getWorkspacePanel().removeWorkspace();
}
};
removeWSAction.setEnabled(false);
removeWSAction.setMenuBarData(new MenuData(
new String[] { ToolConstants.MENU_PROJECT, "Workspace", "Delete..." }, "zProject"));
tool.addAction(removeWSAction);
tool.setMenuGroup(new String[] { ToolConstants.MENU_PROJECT, "Workspace" }, "zProject");
editAccessAction = new DockingAction("Edit Project Access List", owner) {
@Override
public void actionPerformed(ActionContext context) {
editProjectAccess();
}
};
editAccessAction.setMenuBarData(
new MenuData(new String[] { "Project", "Edit Project Access List..." }, "zzProject"));
viewAccessAction = new DockingAction("View Project Access List", owner) {
@Override
public void actionPerformed(ActionContext context) {
viewProjectAccess();
}
};
viewAccessAction.setMenuBarData(
new MenuData(new String[] { "Project", "View Project Access List..." }, "zzProject"));
setPasswordAction = new DockingAction("Change Password", owner) {
@Override
public void actionPerformed(ActionContext context) {
changePassword();
}
};
setPasswordAction.setMenuBarData(
new MenuData(new String[] { "Project", "Change Password..." }, "zzProject"));
viewInfoAction = new DockingAction("View Project Info", owner) {
@Override
public void actionPerformed(ActionContext context) {
showProjectInfo();
}
};
viewInfoAction.setEnabled(false);
viewInfoAction.setMenuBarData(
new MenuData(new String[] { "Project", "View Project Info..." }, "zzzProject"));
tool.addAction(viewInfoAction);
}
private void createSwitchWorkspaceAction() {
String owner = plugin.getName();
switchWSAction = new DockingAction("Switch Workspace", owner) {
@Override
public void actionPerformed(ActionContext context) {
ToolManager toolManager = activeProject.getToolManager();
Workspace[] workspaces = toolManager.getWorkspaces();
if (workspaces.length <= 1) {
Msg.info("FrontEnd", "Unable to switch workspace, only 1 exists.");
return;//can't switch, there is only 1
}
Workspace activeWorkspace = plugin.getWorkspacePanel().getActiveWorkspace();
int index = 0;
for (Workspace workspace : workspaces) {
++index;
if (workspace.equals(activeWorkspace)) {
break;
}
}
if (index >= workspaces.length) {
index = 0;//at the end, so loop back around
}
plugin.getWorkspacePanel().setActiveWorkspace(workspaces[index]);
}
};
switchWSAction.setEnabled(false);
switchWSAction.setMenuBarData(new MenuData(
new String[] { ToolConstants.MENU_PROJECT, "Workspace", "Switch..." }, "zProject"));
tool.addAction(switchWSAction);
}
/**
* creates the recent projects menu
*/
private void buildCloseViewsActions() {
for (ViewInfo info : openViewsList) {
tool.removeAction(info.getAction());
}
openViewsList.clear();
ProjectDataPanel pdp = plugin.getProjectDataPanel();
if (pdp == null) {
return;
}
tool.setMenuGroup(new String[] { ToolConstants.MENU_PROJECT, "Close View" }, "AView", "4");
ProjectLocator[] projectViews = pdp.getProjectViews();
for (ProjectLocator view : projectViews) {
DockingAction action =
new CloseViewPluginAction(GhidraURL.getDisplayString(view.getURL()));
openViewsList.add(new ViewInfo(action, view.getURL()));
tool.addAction(action);
}
if (projectViews.length > 1) {
DockingAction action =
new DockingAction(CLOSE_ALL_OPEN_VIEWS, plugin.getName(), false) {
@Override
public void actionPerformed(ActionContext context) {
closeView(CLOSE_ALL_OPEN_VIEWS);
}
};
action.setMenuBarData(new MenuData(
new String[] { ToolConstants.MENU_PROJECT, "Close View", CLOSE_ALL_OPEN_VIEWS },
"AView"));
openViewsList.add(new ViewInfo(action, null));
tool.addAction(action);
}
else if (projectViews.length == 0) {
DockingAction action = new DockingAction("Close View", plugin.getName()) {
@Override
public void actionPerformed(ActionContext context) {
// do nothing - place holder menu item only
}
};
action.setEnabled(false);
action.setMenuBarData(
new MenuData(new String[] { ToolConstants.MENU_PROJECT, "Close View" }, "AView"));
action.getMenuBarData().setMenuSubGroup("4");
openViewsList.add(new ViewInfo(action, null));
tool.addAction(action);
}
}
/**
* creates the recent projects menu
*/
void buildRecentViewsActions() {
for (ViewInfo info : reopenViewsList) {
tool.removeAction(info.getAction());
}
// remove these actions
reopenViewsList.clear();
if (activeProject == null) {
return;
}
// don't include the active project in the list of views
URL[] recentViews = plugin.getRecentViewedProjects();
tool.setMenuGroup(new String[] { ToolConstants.MENU_PROJECT, "View Recent" }, "AView", "3");
// the project manager maintains the order of the projects
// with the most recent being first in the list
for (URL projectView : recentViews) {
String urlPath = GhidraURL.getDisplayString(projectView);
DockingAction action = new RecentViewPluginAction(urlPath);
reopenViewsList.add(new ViewInfo(action, projectView));
tool.addAction(action);
}
// disable the menu if no recent project views
if (reopenViewsList.size() == 0) {
DockingAction action = new DockingAction("View Recent", plugin.getName(), false) {
@Override
public void actionPerformed(ActionContext context) {
// no-op; disabled action placeholder
}
};
action.setEnabled(false);
action.setMenuBarData(
new MenuData(new String[] { ToolConstants.MENU_PROJECT, "View Recent" }, "AView"));
action.getMenuBarData().setMenuSubGroup("3");
reopenViewsList.add(new ViewInfo(action, null));
tool.addAction(action);
}
}
void showProjectInfo() {
if (infoDialog != null && !infoDialog.isVisible()) {
infoDialog = null;
}
if (infoDialog == null) {
infoDialog = new ProjectInfoDialog(plugin);
tool.showDialog(infoDialog, (ComponentProvider) null);
}
else {
infoDialog.toFront();
}
}
void enableActions(boolean enabled) {
openProjectViewAction.setEnabled(enabled);
openRepositoryViewAction.setEnabled(enabled);
addWSAction.setEnabled(enabled);
removeWSAction.setEnabled(enabled);
renameWSAction.setEnabled(enabled);
switchWSAction.setEnabled(enabled);
viewInfoAction.setEnabled(enabled);
}
void setActiveProject(Project activeProject) {
if (infoDialog != null) {
infoDialog.close();
infoDialog = null;
}
// Remove all the view/edit access-related actions so we always start
// with a clean slate. If we don't do this we could eventually end up with
// both edit and view options available at the same time (open a project with
// admin rights, then open one without).
//
// Note that overriding the isValidContext method in the actions themselves will
// have no effect; that only works for context menus.
tool.removeAction(viewAccessAction);
tool.removeAction(editAccessAction);
tool.removeAction(setPasswordAction);
viewAccessAction.setEnabled(false);
editAccessAction.setEnabled(false);
setPasswordAction.setEnabled(false);
this.activeProject = activeProject;
plugin.rebuildRecentMenus();
buildCloseViewsActions();
boolean hasActiveProject = activeProject != null;
enableActions(hasActiveProject);
if (hasActiveProject) {
RepositoryAdapter repository = activeProject.getRepository();
if (repository != null) {
if (isUserAdmin(repository)) {
tool.addAction(editAccessAction);
editAccessAction.setEnabled(true);
}
else if (!isAnonymousUserOrNotConnected(repository)) {
tool.addAction(viewAccessAction);
viewAccessAction.setEnabled(true);
}
if (repository.isConnected() && repository.getServer().canSetPassword()) {
tool.addAction(setPasswordAction);
setPasswordAction.setEnabled(true);
}
}
}
}
/**
* Notification that the connection state has changed;
* enable or disable the edit Project access action.
*/
void connectionStateChanged(RepositoryAdapter repository) {
if (repository.isConnected()) {
editAccessAction.setEnabled(isUserAdmin(repository));
if (repository.getServer().canSetPassword()) {
tool.addAction(setPasswordAction);
}
}
else {
editAccessAction.setEnabled(false);
tool.removeAction(setPasswordAction);
}
if (infoDialog != null && infoDialog.isVisible()) {
infoDialog.updateConnectionStatus();
}
}
/**
* en/disable operations on views depending on whether
* any are opened
*/
void setViewsVisible(boolean visible) {
buildCloseViewsActions();
}
private boolean isUserAdmin(RepositoryAdapter rep) {
try {
User user = rep.getUser();
return user.isAdmin();
}
catch (IOException e) {
// ignore
}
return false;
}
private boolean isAnonymousUserOrNotConnected(RepositoryAdapter rep) {
try {
User user = rep.getUser();
if (User.ANONYMOUS_USERNAME.equals(user.getName())) {
return true;
}
// work around when user authenticates with their SID
for (User u : rep.getUserList()) {
if (u.equals(user)) {
return false;
}
}
}
catch (IOException e) {
// ignore
}
return true;
}
/**
* closes all the open views
*/
private void closeAllViews() {
ProjectDataPanel pdp = plugin.getProjectDataPanel();
ProjectLocator[] openViews = pdp.getProjectViews();
for (ProjectLocator openView : openViews) {
URL view = openView.getURL();
pdp.closeView(view);
}
buildCloseViewsActions();
}
/**
* closes a view for Project | Close View
* @throws IllegalArgumentException if urlPath is invalid
*/
private void closeView(String urlPath) {
if (urlPath.equals(CLOSE_ALL_OPEN_VIEWS)) {
closeAllViews();
return;
}
// close the named view
URL url = GhidraURL.toURL(urlPath);
closeView(url);
}
void closeView(URL view) {
ProjectDataPanel pdp = plugin.getProjectDataPanel();
pdp.closeView(view);
buildCloseViewsActions();
}
/**
* Notification that a view was closed; called when the user
* right mouse clicks on the project tab and hits the "close" option.
*/
void viewClosed() {
buildCloseViewsActions();
}
/**
* menu listener for Project | Add View...
*/
private void openProjectView() {
if (fileChooser == null) {
fileChooser = plugin.createFileChooser(LAST_VIEWED_PROJECT_DIRECTORY);
}
ProjectLocator projectView =
plugin.chooseProject(fileChooser, "Select", LAST_VIEWED_PROJECT_DIRECTORY);
if (projectView != null) {
openView(projectView.getURL());
}
}
private void openRepositoryView() {
if (repositoryChooser == null) {
repositoryChooser = new RepositoryChooser("View Server Repository");
repositoryChooser.setHelpLocation(
new HelpLocation("FrontEndPlugin", "View_Repository"));
}
String urlStr = Preferences.getProperty(LAST_VIEWED_REPOSITORY_URL);
URL lastURL = null;
if (urlStr != null) {
try {
lastURL = new URL(urlStr);
}
catch (MalformedURLException e) {
// ignore
}
}
URL repositoryURL = repositoryChooser.getSelectedRepository(tool, lastURL);
if (repositoryURL != null) {
openView(repositoryURL);
Preferences.setProperty(LAST_VIEWED_REPOSITORY_URL, repositoryURL.toExternalForm());
Preferences.store();
}
}
private void openView(URL view) {
// don't allow opening the active project as a read-only view
if (activeProject != null && activeProject.getProjectLocator().getURL().equals(view)) {
Msg.showError(getClass(), tool.getToolFrame(), "Error Opening as Read-Only",
"Cannot open active project as Read-Only view!");
return;
}
ProjectDataPanel pdp = plugin.getProjectDataPanel();
pdp.openView(view);
// also update the recent views menu
plugin.rebuildRecentMenus();
}
private void editProjectAccess() {
RepositoryAdapter repository = activeProject.getRepository();
try {
ProjectAccessDialog dialog =
new ProjectAccessDialog(plugin, repository, repository.getServerUserList(), true);
tool.showDialog(dialog);
}
catch (IOException e) {
ClientUtil.handleException(repository, e, "Edit Project Access List",
tool.getToolFrame());
}
}
private void viewProjectAccess() {
RepositoryAdapter repository = activeProject.getRepository();
try {
ProjectAccessDialog dialog =
new ProjectAccessDialog(plugin, repository, repository.getServerUserList(), false);
tool.showDialog(dialog);
}
catch (IOException e) {
ClientUtil.handleException(repository, e, "View Project Access List",
tool.getToolFrame());
}
}
private void changePassword() {
RepositoryAdapter repository = activeProject.getRepository();
if (repository == null) {
return;
}
PasswordChangeDialog dlg = null;
char[] pwd = null;
try {
repository.connect();
ServerInfo info = repository.getServerInfo();
if (OptionDialog.OPTION_ONE != OptionDialog.showOptionDialog(tool.getToolFrame(),
"Confirm Password Change",
"You are about to change your repository server password for:\n" + info +
"\n \nThis password is used when connecting to project\n" +
"repositories associated with this server",
"Continue", OptionDialog.WARNING_MESSAGE)) {
return;
}
dlg = new PasswordChangeDialog("Change Password", "Repository Server",
repository.getServerInfo().getServerName(), repository.getServer().getUser());
tool.showDialog(dlg);
pwd = dlg.getPassword();
if (pwd != null) {
repository.getServer().setPassword(
HashUtilities.getSaltedHash(HashUtilities.SHA256_ALGORITHM, pwd));
Msg.showInfo(getClass(), tool.getToolFrame(), "Password Changed",
"Password was changed successfully");
}
}
catch (IOException e) {
ClientUtil.handleException(repository, e, "Password Change", tool.getToolFrame());
}
finally {
if (pwd != null) {
Arrays.fill(pwd, ' ');
}
if (dlg != null) {
dlg.dispose();
}
}
}
/**
* Class for recent view actions; subclass to set the help ID.
*/
private class RecentViewPluginAction extends DockingAction {
private final String urlPath;
private RecentViewPluginAction(String urlPath) {
super("View " + urlPath, plugin.getName(), false);
this.urlPath = urlPath;
setMenuBarData(new MenuData(
new String[] { ToolConstants.MENU_PROJECT, "View Recent", urlPath }, "AView"));
setHelpLocation(new HelpLocation(plugin.getName(), "View_Recent"));
}
@Override
public void actionPerformed(ActionContext context) {
openRecentView(urlPath);
}
}
private class CloseViewPluginAction extends DockingAction {
private final String urlPath;
private CloseViewPluginAction(String urlPath) {
super("Close View " + urlPath, plugin.getName(), false);
this.urlPath = urlPath;
setMenuBarData(new MenuData(
new String[] { ToolConstants.MENU_PROJECT, "Close View", urlPath }, "AView"));
setHelpLocation(new HelpLocation(plugin.getName(), "Close_View"));
}
@Override
public void actionPerformed(ActionContext context) {
closeView(urlPath);
}
}
}

View file

@ -0,0 +1,371 @@
/* ###
* 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.framework.main;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.event.MouseEvent;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import javax.swing.*;
import docking.ActionContext;
import docking.ComponentProvider;
import docking.help.Help;
import docking.help.HelpService;
import docking.widgets.tabbedpane.DockingTabRenderer;
import ghidra.framework.main.datatable.ProjectDataTablePanel;
import ghidra.framework.main.datatree.ProjectDataTreePanel;
import ghidra.framework.model.*;
import ghidra.framework.options.SaveState;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
/**
* Manages the data tree for the active project, and the trees for the
* project views.
*/
class ProjectDataPanel extends JSplitPane {
private final static String BORDER_PREFIX = "Active Project: ";
private final static String READ_ONLY_BORDER = "READ-ONLY Project Data";
private final static int TYPICAL_NUM_VIEWS = 2;
private final static int DIVIDER_SIZE = 2;
private final static double DIVIDER_LOCATION = 0.50d;
private static final String EXPANDED_PATHS = "EXPANDED_PATHS";
private FrontEndPlugin frontEndPlugin;
private JTabbedPane projectTabPanel;
private JTabbedPane readOnlyTab;
private Map<ProjectLocator, ProjectDataTreePanel> readOnlyViews;
private FrontEndTool tool;
private ProjectDataTreePanel treePanel;
private ProjectDataTablePanel tablePanel;
private JPanel bugFixPanel;
ProjectDataPanel(FrontEndPlugin plugin, ProjectDataTreePanel activePanel,
ProjectDataTablePanel tablePanel, String projectName) {
super();
this.frontEndPlugin = plugin;
this.tablePanel = tablePanel;
tool = ((FrontEndTool) plugin.getTool());
this.treePanel = activePanel;
// initialize the table of views being managed
readOnlyViews = new HashMap<>(TYPICAL_NUM_VIEWS);
projectTabPanel = new JTabbedPane(SwingConstants.BOTTOM);
projectTabPanel.setBorder(BorderFactory.createTitledBorder(BORDER_PREFIX));
projectTabPanel.addChangeListener(e -> frontEndPlugin.getTool().contextChanged(null));
projectTabPanel.addTab("Tree View", activePanel);
projectTabPanel.addTab("Table View", tablePanel);
// setup the active data tree panel
this.add(projectTabPanel, JSplitPane.LEFT);
projectTabPanel.setBorder(BorderFactory.createTitledBorder(BORDER_PREFIX));
// initialize the read-only project view tabbed pane
// create a container panel just to have a title border because of a bug in
// the JTabbedPane when you add custom tab renderers (which we will later)
//
bugFixPanel = new JPanel(new BorderLayout());
readOnlyTab = new JTabbedPane(SwingConstants.BOTTOM);
bugFixPanel.add(readOnlyTab, BorderLayout.CENTER);
bugFixPanel.setBorder(BorderFactory.createTitledBorder(READ_ONLY_BORDER));
setHelpOnReadOnlyTab();
this.add(bugFixPanel, JSplitPane.RIGHT);
//setBorder(projectName);
setViewsVisible(false);
}
private void setHelpOnReadOnlyTab() {
HelpService help = Help.getHelpService();
help.registerHelp(readOnlyTab,
new HelpLocation(frontEndPlugin.getName(), "ReadOnlyProjectDataPanel"));
}
/**
* Populates the project views data tree panel(s) whenever a project is
* made active.
* If no project views are open, the tabbed pane is not visible.
*/
private void populateReadOnlyViews(Project project) {
// readOnlyTab.setBorder(BorderFactory.createTitledBorder(READ_ONLY_BORDER));
if (project == null) {
setViewsVisible(false);
return;
}
ProjectLocator[] views = project.getProjectViews();
HelpLocation helpLocation =
new HelpLocation(frontEndPlugin.getName(), "ReadOnlyProjectDataPanel");
for (ProjectLocator view : views) {
try {
ProjectData projectData = project.getProjectData(view);
ProjectLocator projectLocator = projectData.getProjectLocator();
String viewName = projectLocator.getName();
final ProjectDataTreePanel dtp =
new ProjectDataTreePanel(viewName, false, frontEndPlugin, null); //not active, no filter
dtp.setProjectData(viewName, projectData);
dtp.setHelpLocation(helpLocation);
readOnlyTab.addTab(viewName, dtp);
int index = readOnlyTab.indexOfComponent(dtp);
readOnlyTab.setTabComponentAt(index, new DockingTabRenderer(readOnlyTab, viewName,
viewName, e -> viewRemoved(dtp, getProjectURL(dtp), true)));
readOnlyViews.put(view, dtp);
}
catch (Exception e) {
Msg.showError(this, null, "Error", "Cannot restore project view", e);
}
}
// update the close views menu and set the views pane visible
// if we have open views
setViewsVisible(views.length > 0);
}
private void clearReadOnlyViews() {
readOnlyTab.removeAll();
readOnlyViews.clear();
setViewsVisible(false);
}
private void setViewsVisible(boolean visible) {
bugFixPanel.setVisible(visible);
this.setDividerSize(visible ? DIVIDER_SIZE : 0);
this.setDividerLocation(visible ? DIVIDER_LOCATION : 1.0);
}
void openView(URL projectView) {
ProjectManager projectManager = tool.getProjectManager();
Project activeProject = tool.getProject();
ProjectDataTreePanel dtp = getViewPanel(projectView);
if (dtp != null) {
readOnlyTab.setSelectedComponent(dtp);
try {
activeProject.addProjectView(projectView);
projectManager.rememberViewedProject(projectView);
}
catch (Exception e) {
projectManager.forgetViewedProject(projectView);
Msg.showError(getClass(), tool.getToolFrame(), "Error Adding View", e.toString());
}
return;
}
try {
// TODO: addProjectView should be done in a model task
ProjectData projectData = activeProject.addProjectView(projectView);
projectManager.rememberViewedProject(projectView);
String viewName = projectData.getProjectLocator().getName();
final ProjectDataTreePanel newPanel =
new ProjectDataTreePanel(viewName, false /*isActiveProject*/, frontEndPlugin, null); // no filter
newPanel.setProjectData(viewName, projectData);
newPanel.setHelpLocation(
new HelpLocation(frontEndPlugin.getName(), "ReadOnlyProjectDataPanel"));
readOnlyTab.insertTab(viewName, null, newPanel, null, 0);
int index = readOnlyTab.indexOfComponent(newPanel);
readOnlyTab.setTabComponentAt(index, new DockingTabRenderer(readOnlyTab, viewName,
viewName, e -> viewRemoved(newPanel, getProjectURL(newPanel), true)));
readOnlyTab.setSelectedIndex(0);
readOnlyViews.put(projectData.getProjectLocator(), newPanel);
setViewsVisible(true);
}
catch (Exception e) {
projectManager.forgetViewedProject(projectView);
Msg.showError(getClass(), tool.getToolFrame(), "Error Adding View",
"Failed to view project/repository: " + e.getMessage());
}
validate();
}
ProjectLocator[] getProjectViews() {
int numViews = readOnlyViews.size();
ProjectLocator[] projViews = new ProjectLocator[numViews];
readOnlyViews.keySet().toArray(projViews);
return projViews;
}
/**
* Get the project data for the given project view.
* @return null if project view was not found
*/
ProjectData getProjectData(ProjectLocator projectView) {
ProjectDataTreePanel dtp = readOnlyViews.get(projectView);
if (dtp != null) {
return dtp.getProjectData();
}
return null;
}
/**
* remove (close) the specified project view
*/
void closeView(URL projectView) {
Project activeProject = tool.getProject();
if (activeProject == null) {
Msg.showError(getClass(), tool.getToolFrame(), "Views Only Allowed With Active Project",
"Cannot remove project view: " + projectView);
return;
}
ProjectDataTreePanel dtp = getViewPanel(projectView);
if (dtp == null) {
Msg.showError(getClass(), tool.getToolFrame(), "Cannot Remove Project Not In View",
"Project view: " + projectView + " not found.");
return;
}
viewRemoved(dtp, projectView, false);
}
private ProjectDataTreePanel getViewPanel(URL projectView) {
for (ProjectLocator locator : readOnlyViews.keySet()) {
if (projectView.equals(locator.getURL())) {
return readOnlyViews.get(locator);
}
}
return null;
}
private void removeViewPanel(URL projectView) {
for (ProjectLocator locator : readOnlyViews.keySet()) {
if (projectView.equals(locator.getURL())) {
readOnlyViews.remove(locator);
break;
}
}
}
/**
* returns the ProjectURL for the current active view; null if no views open
*/
URL getCurrentView() {
return getProjectURL(treePanel);
}
private URL getProjectURL(ProjectDataTreePanel panel) {
return panel.getProjectData().getProjectLocator().getURL();
}
private void viewRemoved(Component view, URL url, boolean notify) {
removeViewPanel(url);
// remove the component from the tabbed pane
readOnlyTab.remove(view);
((ProjectDataTreePanel) view).dispose();
// if we have no more views, hide the read-only tabbed pane
if (readOnlyViews.size() == 0) {
setViewsVisible(false);
}
tool.getProject().removeProjectView(url);
validate();
}
void setActiveProject(Project project) {
// close the current active data tree
treePanel.closeRootFolder();
// clear previous project views
clearReadOnlyViews();
// if we have a new active project, display its data tree
if (project != null) {
treePanel.setProjectData(project.getName(), project.getProjectData());
tablePanel.setProjectData(project.getName(), project.getProjectData());
populateReadOnlyViews(project);
}
else {
tablePanel.setProjectData("No Active Project", null);
}
validate();
}
void setBorder(String projectName) {
projectTabPanel.setBorder(BorderFactory.createTitledBorder(BORDER_PREFIX + projectName));
treePanel.updateProjectName(projectName);
}
ActionContext getActionContext(ComponentProvider provider, MouseEvent e) {
Component comp = e == null ? projectTabPanel.getSelectedComponent() : e.getComponent();
while (comp != null) {
if (comp instanceof JTabbedPane) {
return new ActionContext(provider, comp);
}
if (comp instanceof ProjectDataTreePanel) {
ProjectDataTreePanel panel = (ProjectDataTreePanel) comp;
return panel.getActionContext(provider, e);
}
if (comp instanceof ProjectDataTablePanel) {
ProjectDataTablePanel panel = (ProjectDataTablePanel) comp;
return panel.getActionContext(provider, e);
}
comp = comp.getParent();
}
// the clicked component is not a child of a ProjectDataTreePanel--no context
return null;
}
void writeDataState(SaveState saveState) {
String[] expandedPaths = treePanel.getExpandedPathsByNodeName();
if (expandedPaths == null || expandedPaths.length == 0) {
return;
}
saveState.putStrings(EXPANDED_PATHS, expandedPaths);
saveState.putBoolean("SHOW_TABLE", isTableShowing());
}
void readDataState(SaveState saveState) {
String[] expandedPaths = saveState.getStrings(EXPANDED_PATHS, null);
if (expandedPaths == null) {
return;
}
treePanel.setExpandedPathsByNodeName(expandedPaths);
boolean showTable = saveState.getBoolean("SHOW_TABLE", false);
if (showTable) {
showTable();
}
}
private void showTable() {
projectTabPanel.setSelectedIndex(1);
}
private boolean isTableShowing() {
return projectTabPanel.getSelectedIndex() == 1;
}
}

View file

@ -0,0 +1,579 @@
/* ###
* 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.framework.main;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.io.File;
import java.io.IOException;
import javax.swing.*;
import javax.swing.border.BevelBorder;
import docking.DialogComponentProvider;
import docking.help.Help;
import docking.help.HelpService;
import docking.widgets.OptionDialog;
import docking.wizard.WizardManager;
import ghidra.app.util.GenericHelpTopics;
import ghidra.framework.client.*;
import ghidra.framework.data.ConvertFileSystem;
import ghidra.framework.model.*;
import ghidra.framework.remote.User;
import ghidra.framework.store.local.*;
import ghidra.util.*;
import ghidra.util.exception.CancelledException;
import ghidra.util.layout.PairLayout;
import ghidra.util.layout.VerticalLayout;
import ghidra.util.task.*;
import resources.ResourceManager;
/**
* Dialog to show project information. Allows the user to convert a local project to a shared project,
* OR to specify a different server or port, or repository for a shared project.
*
*/
public class ProjectInfoDialog extends DialogComponentProvider {
private final static Icon CONVERT_ICON = ResourceManager.loadImage("images/wand.png");
public final static String CHANGE = "Change Shared Project Info...";
final static String CONVERT = "Convert to Shared...";
private FrontEndPlugin plugin;
private Project project;
private RepositoryAdapter repository;
private JButton connectionButton;
private JLabel userAccessLabel;
private JButton changeConvertButton;
private JButton convertStorageButton;
private JLabel projectDirLabel;
private JLabel serverLabel;
private JLabel portLabel;
private JLabel repNameLabel;
ProjectInfoDialog(FrontEndPlugin plugin) {
super("Project Information", false, true, true, false);
this.plugin = plugin;
project = plugin.getActiveProject();
repository = project.getRepository();
addWorkPanel(buildMainPanel());
addDismissButton();
setHelpLocation(new HelpLocation(GenericHelpTopics.FRONT_END,
repository != null ? "View_Project_Info" : "Convert_to_Shared"));
setFocusComponent(dismissButton);
setRememberSize(false);
}
/**
* Called from the project action manager when the connection state changes on the
* repository.
*/
void updateConnectionStatus() {
boolean isConnected = repository.isConnected();
connectionButton.setIcon(
isConnected ? FrontEndPlugin.CONNECTED_ICON : FrontEndPlugin.DISCONNECTED_ICON);
connectionButton.setContentAreaFilled(false);
connectionButton.setSelected(isConnected);
connectionButton.setBorder(
isConnected ? BorderFactory.createBevelBorder(BevelBorder.LOWERED)
: BorderFactory.createBevelBorder(BevelBorder.RAISED));
updateConnectButtonToolTip();
if (isConnected) {
try {
User user = repository.getUser();
userAccessLabel.setText(getAccessString(user));
}
catch (IOException e) {
Msg.error(this, "Exception obtaining user", e);
}
}
}
private JPanel buildMainPanel() {
JPanel mainPanel = new JPanel(new VerticalLayout(20));
mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 5));
mainPanel.add(buildInfoPanel());
mainPanel.add(buildRepositoryInfoPanel());
mainPanel.add(buildButtonPanel());
return mainPanel;
}
private JPanel buildInfoPanel() {
File dir = project.getProjectLocator().getProjectDir();
JPanel outerPanel = new JPanel(new BorderLayout());
outerPanel.setBorder(BorderFactory.createTitledBorder("Project Location"));
JPanel infoPanel = new JPanel(new PairLayout(5, 10));
infoPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
JLabel dirLabel = new JLabel("Directory Location:", SwingConstants.RIGHT);
dirLabel.setToolTipText("Directory where your project files reside.");
infoPanel.add(dirLabel);
projectDirLabel = new JLabel(dir.getAbsolutePath());
infoPanel.add(projectDirLabel);
infoPanel.add(new JLabel("Project Storage Type:", SwingConstants.RIGHT));
Class<? extends LocalFileSystem> fsClass = project.getProjectData().getLocalStorageClass();
String fsClassName = "<UNKNOWN>";
if (IndexedV1LocalFileSystem.class.equals(fsClass)) {
fsClassName = "Indexed Filesystem (V1)";
}
else if (IndexedLocalFileSystem.class.equals(fsClass)) {
fsClassName = "Indexed Filesystem (V0)";
}
else if (MangledLocalFileSystem.class.equals(fsClass)) {
fsClassName = "Mangled Filesystem";
}
JLabel label = new JLabel(fsClassName);
label.setName("Project Storage Type");
infoPanel.add(label);
infoPanel.add(new JLabel("Project Name:", SwingConstants.RIGHT));
label = new JLabel(project.getName());
label.setName("Project Name");
infoPanel.add(label);
outerPanel.add(infoPanel);
return outerPanel;
}
private JPanel buildButtonPanel() {
JPanel buttonPanel = new JPanel(new BorderLayout());
changeConvertButton = new JButton(repository != null ? CHANGE : CONVERT);
changeConvertButton.addActionListener(e -> {
if (changeConvertButton.getText().equals(CONVERT)) {
convertToShared();
}
else {
updateSharedProjectInfo();
}
});
HelpService help = Help.getHelpService();
String tag = repository != null ? "Change_Shared_Project_Info" : "Convert_to_Shared";
help.registerHelp(changeConvertButton, new HelpLocation(GenericHelpTopics.FRONT_END, tag));
String toolTipForChange = "Change server information or specify another repository.";
String toolTipForConvert = "Convert project to be a shared project.";
changeConvertButton.setToolTipText(
repository != null ? toolTipForChange : toolTipForConvert);
Class<? extends LocalFileSystem> fsClass = project.getProjectData().getLocalStorageClass();
String convertStorageButtonLabel = null;
if (IndexedLocalFileSystem.class.equals(fsClass)) {
convertStorageButtonLabel = "Upgrade Project Storage Index...";
}
else if (MangledLocalFileSystem.class.equals(fsClass)) {
convertStorageButtonLabel = "Convert Project Storage to Indexed...";
}
if (convertStorageButtonLabel != null) {
convertStorageButton = new JButton(convertStorageButtonLabel);
convertStorageButton.addActionListener(e -> convertToIndexedFilesystem());
help.registerHelp(changeConvertButton,
new HelpLocation(GenericHelpTopics.FRONT_END, "Convert_Project_Storage"));
convertStorageButton.setToolTipText(
"Convert/Upgrade project storage to latest Indexed Filesystem");
}
JPanel p = new JPanel(new FlowLayout());
p.add(changeConvertButton);
if (convertStorageButton != null) {
p.add(convertStorageButton);
}
buttonPanel.add(p);
return buttonPanel;
}
private JPanel buildRepositoryInfoPanel() {
String serverName = "";
ServerInfo info = null;
String repositoryName = "";
String portNumberStr = "";
boolean isConnected = false;
if (repository != null) {
info = repository.getServerInfo();
serverName = info.getServerName();
repositoryName = repository.getName();
portNumberStr = Integer.toString(info.getPortNumber());
isConnected = repository.isConnected();
}
JPanel outerPanel = new JPanel(new BorderLayout());
outerPanel.setBorder(BorderFactory.createTitledBorder("Repository Info"));
JPanel panel = new JPanel(new PairLayout(5, 10));
panel.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
JLabel sLabel = new JLabel("Server Name:", SwingConstants.RIGHT);
panel.add(sLabel);
serverLabel = new JLabel(serverName);
serverLabel.setName("Server Name");
panel.add(serverLabel);
JLabel pLabel = new JLabel("Port Number:", SwingConstants.RIGHT);
panel.add(pLabel);
portLabel = new JLabel(portNumberStr);
portLabel.setName("Port Number");
panel.add(portLabel);
JLabel repLabel = new JLabel("Repository Name:", SwingConstants.RIGHT);
panel.add(repLabel);
repNameLabel = new JLabel(repositoryName);
repNameLabel.setName("Repository Name");
panel.add(repNameLabel);
JLabel connectLabel = new JLabel("Connection Status:", SwingConstants.RIGHT);
panel.add(connectLabel);
connectionButton = new JButton(
isConnected ? FrontEndPlugin.CONNECTED_ICON : FrontEndPlugin.DISCONNECTED_ICON);
connectionButton.addActionListener(e -> connect());
connectionButton.setName("Connect Button");
connectionButton.setContentAreaFilled(false);
connectionButton.setSelected(isConnected);
connectionButton.setBorder(
isConnected ? BorderFactory.createBevelBorder(BevelBorder.LOWERED)
: BorderFactory.createBevelBorder(BevelBorder.RAISED));
updateConnectButtonToolTip();
HelpService help = Help.getHelpService();
help.registerHelp(connectionButton,
new HelpLocation(GenericHelpTopics.FRONT_END, "ConnectToServer"));
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
buttonPanel.setBorder(BorderFactory.createEmptyBorder());
buttonPanel.add(connectionButton);
panel.add(buttonPanel);
JLabel userLabel = new JLabel("User Access Level:", SwingConstants.RIGHT);
userLabel.setToolTipText("Indicates your privileges in the shared repository");
panel.add(userLabel);
User user = null;
if (isConnected) {
try {
user = repository.getUser();
}
catch (IOException e) {
Msg.error(this, "Unable to get the current user", e);
}
}
userAccessLabel = new JLabel(getAccessString(user));
userAccessLabel.setName("User Access Level");
panel.add(userLabel);
panel.add(userAccessLabel);
outerPanel.add(panel);
if (repository == null) {
sLabel.setEnabled(false);
pLabel.setEnabled(false);
repLabel.setEnabled(false);
connectLabel.setEnabled(false);
connectionButton.setEnabled(false);
userLabel.setEnabled(false);
}
return outerPanel;
}
private void updateConnectButtonToolTip() {
if (repository != null) {
ServerInfo info = repository.getServerInfo();
String serverName = info.getServerName();
String notConnectedToolTip = HTMLUtilities.toHTML(
"Disconnected from " + serverName + ".\n" + "Activate this button to connect.");
connectionButton.setToolTipText(
repository.isConnected() ? "Connected to " + serverName : notConnectedToolTip);
}
}
private void connect() {
try {
repository.connect();
}
catch (NotConnectedException e) {
// message displayed by repository server adapter
}
catch (IOException e) {
ClientUtil.handleException(repository, e, "Repository Connection", rootPanel);
}
}
private String getAccessString(User user) {
if (user == null) {
return "";
}
if (user.isAdmin()) {
return "Administrator";
}
if (user.isReadOnly()) {
return "Read Only";
}
return "Read/Write";
}
private void updateSharedProjectInfo() {
if (filesAreOpen()) {
Msg.showInfo(getClass(), getComponent(), "Cannot Change Project Info with Open Files",
"Before your project info can be updated, you must close\n" +
"files in running tools and make sure you have no files\n" + "checked out.");
return;
}
SetupProjectPanelManager panelManager =
new SetupProjectPanelManager(plugin.getTool(), project.getRepository().getServerInfo());
WizardManager wm = new WizardManager("Change Shared Project Information", true,
panelManager, CONVERT_ICON);
wm.showWizard(getComponent());
RepositoryAdapter rep = panelManager.getProjectRepository();
if (rep != null) {
RepositoryAdapter currentRepository = project.getRepository();
if (currentRepository.getServerInfo().equals(rep.getServerInfo()) &&
currentRepository.getName().equals(rep.getName())) {
Msg.showInfo(getClass(), getComponent(), "No Changes Made",
"No changes were made to the shared project information.");
}
else if (OptionDialog.showOptionDialog(getComponent(), "Update Shared Project Info",
"Are you sure you want to update your shared project information?", "Update",
OptionDialog.QUESTION_MESSAGE) == OptionDialog.OPTION_ONE) {
UpdateInfoTask task = new UpdateInfoTask(rep);
new TaskLauncher(task, getComponent(), 500);
// block until task completes
if (task.getStatus()) {
FileActionManager actionMgr = plugin.getFileActionManager();
close();
actionMgr.closeProject(false);
actionMgr.openProject(project.getProjectLocator());
plugin.getProjectActionManager().showProjectInfo();
}
}
}
}
private void convertToIndexedFilesystem() {
if (filesAreOpen()) {
Msg.showInfo(getClass(), getComponent(),
"Cannot Convert/Upgrade Project Storage with Open Files",
"Before your project can be converted, you must close\n" +
"files in running tools.");
return;
}
RepositoryAdapter rep = project.getRepository();
if (rep != null) {
rep.disconnect();
}
if (OptionDialog.showOptionDialog(getComponent(), "Confirm Convert/Upgrade Project Storage",
"Convert/Upgrade Project Storage to latest Indexed Filesystem ?\n \n" +
"WARNING! Once converted a project may no longer be opened by\n" +
"any version of Ghidra older than version 6.1.",
"Convert", OptionDialog.WARNING_MESSAGE) == OptionDialog.OPTION_ONE) {
ProjectLocator projectLocator = project.getProjectLocator();
FileActionManager actionMgr = plugin.getFileActionManager();
actionMgr.closeProject(false);
// put the conversion in a task
ConvertProjectStorageTask task = new ConvertProjectStorageTask(projectLocator);
new TaskLauncher(task, getComponent(), 500);
// block until task completes
if (task.getStatus()) {
close();
actionMgr.openProject(projectLocator);
plugin.getProjectActionManager().showProjectInfo();
}
}
}
private void convertToShared() {
if (filesAreOpen()) {
Msg.showInfo(getClass(), getComponent(), "Cannot Convert Project with Open Files",
"Before your project can be converted, you must close\n" +
"files in running tools and make sure you have no files\n" + "checked out.");
return;
}
SetupProjectPanelManager panelManager =
new SetupProjectPanelManager(plugin.getTool(), null);
WizardManager wm = new WizardManager("Convert Project", true, panelManager, CONVERT_ICON);
wm.showWizard(getComponent());
RepositoryAdapter rep = panelManager.getProjectRepository();
if (rep != null) {
StringBuffer confirmMsg = new StringBuffer();
confirmMsg.append("All version history on your files will be\n" +
"lost after your project is converted.\n" +
"Do you want to convert your project?\n");
confirmMsg.append(" \n");
confirmMsg.append("WARNING: Convert CANNOT be undone!");
if (OptionDialog.showOptionDialog(getComponent(), "Confirm Convert Project",
confirmMsg.toString(), "Convert",
OptionDialog.WARNING_MESSAGE) == OptionDialog.OPTION_ONE) {
// put the conversion in a task
ConvertProjectTask task = new ConvertProjectTask(rep);
new TaskLauncher(task, getComponent(), 500);
// block until task completes
if (task.getStatus()) {
close();
FileActionManager actionMgr = plugin.getFileActionManager();
actionMgr.closeProject(false);
actionMgr.openProject(project.getProjectLocator());
plugin.getProjectActionManager().showProjectInfo();
}
else {
Msg.trace(this, "Convert project task failed");
}
}
}
}
private boolean filesAreOpen() {
Tool[] tools = project.getToolManager().getRunningTools();
if (tools.length > 0) {
for (Tool tool : tools) {
if (tool.getDomainFiles().length > 0) {
return true;
}
}
}
return false;
}
private class ConvertProjectTask extends Task {
private RepositoryAdapter taskRepository;
private boolean status;
ConvertProjectTask(RepositoryAdapter repository) {
super("Convert Project to Shared", true, false, true);
this.taskRepository = repository;
}
/* (non-Javadoc)
* @see ghidra.util.task.Task#run(ghidra.util.task.TaskMonitor)
*/
@Override
public void run(TaskMonitor monitor) {
try {
project.getProjectData().convertProjectToShared(taskRepository, monitor);
status = true;
}
catch (IOException e) {
String msg = e.getMessage();
if (msg == null) {
msg = e.toString();
}
Msg.showError(this, getComponent(), "Failed to Convert Project",
"Update to shared project info failed:\n" + msg);
}
catch (CancelledException e) {
Msg.info(this, "Update shared project info was canceled.");
}
}
boolean getStatus() {
return status;
}
}
private class ConvertProjectStorageTask extends Task {
private ProjectLocator projectLocator;
private boolean status;
ConvertProjectStorageTask(ProjectLocator projectLocator) {
super("Convert Project Storage", false, false, true);
this.projectLocator = projectLocator;
}
/* (non-Javadoc)
* @see ghidra.util.task.Task#run(ghidra.util.task.TaskMonitor)
*/
@Override
public void run(TaskMonitor monitor) {
try {
monitor.setMessage("Converting storage...");
File projectDir = projectLocator.getProjectDir();
ConvertFileSystem.convertProject(projectDir,
new ConvertFileSystem.MessageListener() {
@Override
public void println(String string) {
Msg.info(this, string);
}
});
status = true;
}
catch (ConvertFileSystem.ConvertFileSystemException e) {
Msg.showError(this, getComponent(), "Failed to Convert Project Storage",
e.getMessage());
}
}
boolean getStatus() {
return status;
}
}
private class UpdateInfoTask extends Task {
private RepositoryAdapter taskRepository;
private boolean status;
UpdateInfoTask(RepositoryAdapter repository) {
super("Update Shared Project Info", true, false, true);
this.taskRepository = repository;
}
/* (non-Javadoc)
* @see ghidra.util.task.Task#run(ghidra.util.task.TaskMonitor)
*/
@Override
public void run(TaskMonitor monitor) {
try {
project.getProjectData().updateRepositoryInfo(taskRepository, monitor);
status = true;
}
catch (IOException e) {
String msg = e.getMessage();
if (msg == null) {
msg = e.toString();
}
Msg.showError(this, getComponent(), "Failed to Update Shared Project Info",
"Conversion to shared project failed:\n" + msg);
}
catch (CancelledException e) {
Msg.info(this, "Convert project was canceled.");
}
}
boolean getStatus() {
return status;
}
}
}

View file

@ -0,0 +1,135 @@
/* ###
* 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.framework.main;
import java.util.*;
import javax.swing.BorderFactory;
import javax.swing.JToolBar;
import ghidra.framework.model.*;
/**
* Toolbar that shows icons for the tools in the user's tool chest.
*/
class ProjectToolBar extends JToolBar implements ToolChestChangeListener {
private final static int TYPICAL_NUM_TOOLS = 5;
private Map<String, ToolButton> toolButtonMap;
private FrontEndPlugin plugin;
private FrontEndTool tool;
ProjectToolBar(FrontEndPlugin plugin) {
super();
this.plugin = plugin;
tool = ((FrontEndTool) plugin.getTool());
toolButtonMap = new HashMap<>(TYPICAL_NUM_TOOLS);
// remove the default etched border
setBorder(BorderFactory.createTitledBorder("Tool Chest"));
setActiveProject(plugin.getActiveProject());
// let the default coloring shine through this toolbar; leaving this opaque causes
// odd display coloring issues in some LookAndFeels, like Metal
setOpaque(false);
setFloatable(false); // it is odd to allow the user to undock the tool buttons
}
@Override
public void toolTemplateAdded(ToolTemplate toolConfig) {
// rebuild the tool bar so that the tools are shown in
// alphabetical order
populateToolBar();
}
/**
* ToolSet was added to the project toolchest
*/
@Override
public void toolSetAdded(ToolSet toolset) {
ToolChest toolChest = tool.getProject().getLocalToolChest();
toolTemplateAdded(toolChest.getToolTemplate(toolset.getName()));
}
@Override
public void toolRemoved(String toolName) {
if (!toolButtonMap.containsKey(toolName)) {
return;
}
ToolButton button = toolButtonMap.get(toolName);
this.remove(button);
toolButtonMap.remove(toolName);
button.dispose();
tool.getToolFrame().validate();
repaint();
}
void setActiveProject(Project project) {
// first clear state from previous project
clear();
if (project == null) {
return;
}
populateToolBar();
}
private void clear() {
this.removeAll();
Iterator<ToolButton> it = toolButtonMap.values().iterator();
while (it.hasNext()) {
ToolButton tb = it.next();
tb.dispose();
}
toolButtonMap.clear();
if (tool.isVisible()) {
tool.getToolFrame().validate();
}
}
/**
* Redo the tool bar.
*/
private void populateToolBar() {
removeAll();
toolButtonMap.clear();
ToolChest tc = plugin.getActiveProject().getLocalToolChest();
ToolTemplate[] templates = tc.getToolTemplates();
for (ToolTemplate element : templates) {
addConfig(element);
}
invalidate();
tool.getToolFrame().validate();
repaint();
}
/**
* Add a button for the tool template to the tool bar.
*/
private void addConfig(ToolTemplate toolConfig) {
ToolButton button = new ToolButton(plugin, toolConfig);
this.add(button);
toolButtonMap.put(toolConfig.getName(), button);
}
public ToolButton getToolButtonForToolConfig(ToolTemplate toolTemplate) {
return toolButtonMap.get(toolTemplate.getName());
}
}

View file

@ -0,0 +1,118 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.main;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import javax.swing.*;
import docking.wizard.AbstractWizardJPanel;
import docking.wizard.PanelManager;
import ghidra.app.util.*;
import ghidra.util.HelpLocation;
import ghidra.util.layout.VerticalLayout;
/**
* First panel shown in the New Project Wizard to get user input for what
* type of project to create: Shared, or not shared.
*
*
*/
class ProjectTypePanel extends AbstractWizardJPanel {
private JRadioButton sharedRB;
private JRadioButton nonSharedRB;
private ButtonGroup buttonGroup;
private PanelManager panelManager;
ProjectTypePanel(PanelManager panelManager) {
super();
this.panelManager = panelManager;
buildPanel();
setBorder(NewProjectPanelManager.EMPTY_BORDER);
}
private void buildPanel() {
JPanel innerPanel = new JPanel(new VerticalLayout(10));
innerPanel.setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
ItemListener listener = new ItemListener() {
public void itemStateChanged(ItemEvent e) {
panelManager.getWizardManager().validityChanged();
}
};
nonSharedRB = new JRadioButton("Non-Shared Project", true);
nonSharedRB.addItemListener(listener);
nonSharedRB.setToolTipText("Create a project that is not shared with others");
sharedRB = new JRadioButton("Shared Project");
sharedRB.addItemListener(listener);
sharedRB.setToolTipText("Create a project that can be shared with others");
buttonGroup = new ButtonGroup();
buttonGroup.add(nonSharedRB);
buttonGroup.add(sharedRB);
innerPanel.add(nonSharedRB);
innerPanel.add(sharedRB);
JPanel outerPanel = new JPanel();
outerPanel.setBorder(BorderFactory.createEmptyBorder());
outerPanel.add(innerPanel);
add(outerPanel);
}
/* (non-Javadoc)
* @see ghidra.util.bean.wizard.WizardPanel#getTitle()
*/
public String getTitle() {
return "Select Project Type";
}
/* (non-Javadoc)
* @see ghidra.util.bean.wizard.WizardPanel#initialize()
*/
public void initialize() {
buttonGroup.remove(sharedRB);
buttonGroup.remove(nonSharedRB);
sharedRB.setSelected(false);
sharedRB.setSelected(false);
buttonGroup.add(nonSharedRB);
buttonGroup.add(sharedRB);
}
/**
* Return true if the user has entered a valid project file
*/
public boolean isValidInformation() {
return sharedRB.isSelected() || nonSharedRB.isSelected();
}
/* (non-Javadoc)
* @see ghidra.util.bean.wizard.WizardPanel#getHelpLocation()
*/
@Override
public HelpLocation getHelpLocation() {
return new HelpLocation(GenericHelpTopics.FRONT_END, "SelectProjectType");
}
boolean isSharedProject() {
return sharedRB.isSelected();
}
}

View file

@ -0,0 +1,358 @@
/* ###
* 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.framework.main;
import ghidra.framework.client.*;
import ghidra.framework.model.ServerInfo;
import ghidra.framework.protocol.ghidra.GhidraURL;
import ghidra.framework.remote.GhidraServerHandle;
import ghidra.util.MessageType;
import ghidra.util.Msg;
import ghidra.util.layout.MiddleLayout;
import ghidra.util.layout.PairLayout;
import java.awt.BorderLayout;
import java.awt.CardLayout;
import java.awt.event.*;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import javax.swing.*;
import javax.swing.event.*;
import resources.ResourceManager;
import docking.DialogComponentProvider;
class RepositoryChooser extends DialogComponentProvider {
static final Icon REFRESH_ICON = ResourceManager.loadImage("images/view-refresh.png");
private static final String SERVER_INFO = "ServerInfo";
private static final String GHIDRA_URL = "GhidraURL";
private JRadioButton serverInfoChoice;
private JRadioButton urlChoice;
private JPanel cardPanel;
private CardLayout cardLayout;
private ServerInfoComponent serverInfoComponent;
private JButton queryButton;
private JList<String> nameList;
private DefaultListModel<String> listModel;
private JTextField urlTextField;
private boolean okPressed;
RepositoryChooser(String title) {
super(title);
setRememberLocation(false);
buildMainPanel();
}
private JPanel buildServerInfoPanel() {
JPanel serverInfoPanel = new JPanel(new BorderLayout(10, 10));
JPanel topPanel = new JPanel(new BorderLayout(10, 10));
serverInfoComponent = new ServerInfoComponent();
serverInfoComponent.setStatusListener(this);
serverInfoComponent.setChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
serverInfoChanged();
}
});
topPanel.add(serverInfoComponent, BorderLayout.CENTER);
queryButton = new JButton(REFRESH_ICON);
queryButton.setToolTipText("Refresh Repository Names List");
setDefaultButton(queryButton);
queryButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
queryServer();
}
});
JPanel buttonPanel = new JPanel(new MiddleLayout());
buttonPanel.add(queryButton);
topPanel.add(buttonPanel, BorderLayout.EAST);
serverInfoPanel.add(topPanel, BorderLayout.NORTH);
JPanel lowerPanel = new JPanel(new BorderLayout());
JLabel label = new JLabel("Repository Names", SwingConstants.LEFT);
label.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 5));
lowerPanel.add(label, BorderLayout.NORTH);
listModel = new DefaultListModel<String>();
nameList = new JList<String>(listModel);
nameList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
nameList.addListSelectionListener(new ListSelectionListener() {
public void valueChanged(ListSelectionEvent e) {
selectionChanged();
}
});
nameList.addMouseListener(new MouseInputAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getButton() != MouseEvent.BUTTON1 || e.getClickCount() != 2) {
return;
}
if (nameList.getSelectedValue() != null) {
e.consume();
okCallback();
}
}
});
JScrollPane sp = new JScrollPane(nameList);
lowerPanel.add(sp);
serverInfoPanel.add(lowerPanel, BorderLayout.CENTER);
return serverInfoPanel;
}
private JPanel buildURLPanel() {
JPanel urlPanel = new JPanel(new BorderLayout(10, 10));
urlTextField = new JTextField("ghidra:");
JPanel panel = new JPanel(new PairLayout());
panel.add(new JLabel("URL:"));
panel.add(urlTextField);
urlPanel.add(panel, BorderLayout.NORTH);
return urlPanel;
}
private void choiceActivated(JRadioButton choiceButton) {
if (choiceButton == urlChoice) {
cardLayout.show(cardPanel, GHIDRA_URL);
}
else {
cardLayout.show(cardPanel, SERVER_INFO);
}
cardPanel.requestFocus();
choiceChanged();
}
private void choiceChanged() {
setStatusText("");
if (urlChoice.isSelected()) {
urlInfoChanged();
}
else {
serverInfoChanged();
}
}
private void buildMainPanel() {
JPanel panel = new JPanel(new BorderLayout(10, 10));
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
JPanel radioButtonPanel = new JPanel(new PairLayout(5, 5));
radioButtonPanel.setBorder(BorderFactory.createTitledBorder("Repository Specification"));
ChangeListener choiceListener = new ChangeListener() {
public void stateChanged(ChangeEvent e) {
Object src = e.getSource();
if (src instanceof JRadioButton) {
JRadioButton choiceButton = (JRadioButton) src;
if (choiceButton.isSelected()) {
choiceActivated(choiceButton);
}
}
}
};
serverInfoChoice = new JRadioButton("Ghidra Server");
serverInfoChoice.setSelected(true);
serverInfoChoice.addChangeListener(choiceListener);
radioButtonPanel.add(serverInfoChoice);
urlChoice = new JRadioButton("Ghidra URL");
urlChoice.addChangeListener(choiceListener);
radioButtonPanel.add(urlChoice);
ButtonGroup panelChoices = new ButtonGroup();
panelChoices.add(serverInfoChoice);
panelChoices.add(urlChoice);
panel.add(radioButtonPanel, BorderLayout.NORTH);
cardLayout = new CardLayout();
cardPanel = new JPanel(cardLayout);
cardPanel.add(buildServerInfoPanel(), SERVER_INFO);
cardPanel.add(buildURLPanel(), GHIDRA_URL);
panel.add(cardPanel, BorderLayout.CENTER);
cardLayout.show(cardPanel, SERVER_INFO);
addWorkPanel(panel);
addCancelButton();
addOKButton();
setOkButtonText("Select Repository");
setOkEnabled(false);
}
private void selectionChanged() {
String name = nameList.getSelectedValue();
setOkEnabled(name != null);
}
private void queryServer() {
listModel.clear();
RepositoryServerAdapter repositoryServer =
ClientUtil.getRepositoryServer(serverInfoComponent.getServerName(),
serverInfoComponent.getPortNumber(), true);
if (repositoryServer == null) {
return;
}
try {
for (String name : repositoryServer.getRepositoryNames()) {
listModel.addElement(name);
}
}
catch (NotConnectedException e) {
return;
}
catch (IOException e) {
Msg.showError(this, null, "Server Error",
"Failed to query list of repositories: " + e.getMessage());
}
if (listModel.size() == 0) {
setStatusText("No repositories found");
}
}
private void urlInfoChanged() {
setStatusText("");
setOkEnabled(false);
try {
URL url = new URL(urlTextField.getText());
if (!GhidraURL.PROTOCOL.equals(url.getProtocol())) {
setStatusText("URL must specify 'ghidra:' protocol", MessageType.ERROR);
}
else {
setOkEnabled(true);
}
}
catch (MalformedURLException e) {
setStatusText(e.getMessage(), MessageType.ERROR);
}
}
private void serverInfoChanged() {
setStatusText("");
setOkEnabled(false);
listModel.clear();
queryButton.setEnabled(serverInfoComponent.isValidInformation());
}
URL getSelectedRepository(FrontEndTool tool, URL initURL) {
init(initURL);
tool.showDialog(this, tool.getToolFrame());
if (!okPressed) {
return null;
}
if (serverInfoChoice.isSelected()) {
return GhidraURL.makeURL(serverInfoComponent.getServerName(),
serverInfoComponent.getPortNumber(), nameList.getSelectedValue());
}
// TODO: How do we restrict URL to repository only - not sure we can
try {
return new URL(urlTextField.getText());
}
catch (MalformedURLException e) {
Msg.error(this, e.getMessage());
}
return null;
}
@Override
protected void okCallback() {
if (serverInfoChoice.isSelected()) {
// Server info specified
if (nameList.getSelectedValue() != null) {
okPressed = true;
close();
}
}
else {
// URL specified
okPressed = true;
close();
}
}
private void init(URL initURL) {
okPressed = false;
if (initURL != null) {
String url = initURL.toExternalForm();
String ghidraProtocol = GhidraURL.PROTOCOL + ":";
if (url.startsWith(ghidraProtocol)) {
if (!url.startsWith(ghidraProtocol + "//")) {
// non-standard URL
urlTextField.setText(url);
urlChoice.setSelected(true);
return;
}
}
else {
initURL = null; // ignore
}
}
ServerInfo serverInfo = null;
if (initURL != null) {
String host = initURL.getHost();
int port = initURL.getPort();
if (port <= 0) {
port = GhidraServerHandle.DEFAULT_PORT;
}
serverInfo = new ServerInfo(host, port);
}
serverInfoComponent.setServerInfo(serverInfo);
// TODO: serverInfoChanged should get called.
serverInfoChoice.setSelected(true);
}
}

View file

@ -0,0 +1,260 @@
/* ###
* 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.framework.main;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.event.*;
import docking.wizard.*;
import ghidra.app.util.GenericHelpTopics;
import ghidra.util.HelpLocation;
import ghidra.util.NamingUtilities;
import ghidra.util.layout.VerticalLayout;
/**
* Panel that shows a list of existing repositories, or allows the user
* to enter the name of a new repository to be created.
*
*/
public class RepositoryPanel extends AbstractWizardJPanel {
private String serverName;
private JRadioButton existingRepButton;
private JRadioButton createRepButton;
private ButtonGroup buttonGroup;
private JList<String> nameList;
private DefaultListModel<String> listModel;
private JTextField nameField;
private JLabel nameLabel;
private PanelManager panelManager;
private HelpLocation helpLoc;
public RepositoryPanel(PanelManager panelManager, String serverName, String[] repositoryNames,
boolean readOnlyServerAccess) {
super(new BorderLayout(5, 10));
setBorder(BorderFactory.createEmptyBorder(20, 20, 20, 20));
this.panelManager = panelManager;
this.serverName = serverName;
buildMainPanel(repositoryNames, readOnlyServerAccess);
}
/* (non Javadoc)
* @see ghidra.util.bean.wizard.WizardPanel#getTitle()
*/
public String getTitle() {
return "Specify Repository Name on " + serverName;
}
/* (non Javadoc)
* @see ghidra.util.bean.wizard.WizardPanel#initialize()
*/
public void initialize() {
existingRepButton.setSelected(true);
nameList.clearSelection();
nameField.setText("");
}
/**
* Return whether the user entry is valid
*/
public boolean isValidInformation() {
if (createRepButton.isSelected()) {
String name = nameField.getText();
if (name.length() == 0) {
return false;
}
if (!NamingUtilities.isValidName(name)) {
panelManager.getWizardManager().setStatusMessage(
name + " contains invalid characters");
return false;
}
//
return !listModel.contains(name);
}
if (nameList.getSelectedValue() != null) {
return true;
}
return false;
}
/* (non-Javadoc)
* @see ghidra.util.bean.wizard.WizardPanel#getHelpLocation()
*/
@Override
public HelpLocation getHelpLocation() {
if (helpLoc != null) {
return helpLoc;
}
return new HelpLocation(GenericHelpTopics.FRONT_END, "SelectRepository");
}
void setHelpLocation(HelpLocation helpLoc) {
this.helpLoc = helpLoc;
}
boolean createRepository() {
return createRepButton.isSelected();
}
/**
* Get the name of the repository; it either one selected from the list,
* or the name that the user entered to create a new repository.
*/
String getRepositoryName() {
if (createRepButton.isSelected()) {
return nameField.getText();
}
return nameList.getSelectedValue();
}
private void buildMainPanel(String[] repositoryNames, boolean readOnlyServerAccess) {
buttonGroup = new ButtonGroup();
add(createListPanel(repositoryNames), BorderLayout.CENTER);
add(createNamePanel(), BorderLayout.SOUTH);
addListeners();
if (readOnlyServerAccess) {
createRepButton.setEnabled(false);
createRepButton.setSelected(false);
nameField.setEnabled(false);
nameLabel.setEnabled(false);
}
}
private JPanel createListPanel(String[] repositoryNames) {
JPanel panel = new JPanel(new VerticalLayout(5));
panel.setBorder(BorderFactory.createTitledBorder("Choose Existing Repository"));
existingRepButton = new JRadioButton("Existing Repository", (repositoryNames.length > 0));
existingRepButton.setEnabled(repositoryNames.length > 0);
buttonGroup.add(existingRepButton);
JPanel innerPanel = new JPanel(new BorderLayout());
JLabel label = new JLabel("Repository Names", SwingConstants.LEFT);
label.setBorder(BorderFactory.createEmptyBorder(0, 2, 0, 5));
innerPanel.add(label, BorderLayout.NORTH);
listModel = new DefaultListModel<>();
for (String repositoryName : repositoryNames) {
listModel.addElement(repositoryName);
}
nameList = new JList<>(listModel);
nameList.setEnabled(existingRepButton.isSelected());
JScrollPane sp = new JScrollPane(nameList);
innerPanel.add(sp);
panel.add(existingRepButton);
panel.add(innerPanel);
return panel;
}
private JPanel createNamePanel() {
JPanel namePanel = new JPanel();
namePanel.setLayout(new VerticalLayout(5));
namePanel.setBorder(BorderFactory.createTitledBorder("Create Repository"));
createRepButton = new JRadioButton("Create Repository", !existingRepButton.isSelected());
buttonGroup.add(createRepButton);
nameLabel = new JLabel("Repository Name:", SwingConstants.RIGHT);
nameLabel.setEnabled(createRepButton.isSelected());
nameField = new JTextField(20);
DocumentListener dl = new DocumentListener() {
public void insertUpdate(DocumentEvent e) {
validateName();
}
public void removeUpdate(DocumentEvent e) {
validateName();
}
public void changedUpdate(DocumentEvent e) {
validateName();
}
};
nameField.getDocument().addDocumentListener(dl);
nameField.setEnabled(createRepButton.isSelected());
JPanel innerPanel = new JPanel();
innerPanel.add(nameLabel);
innerPanel.add(nameField);
namePanel.add(createRepButton);
namePanel.add(innerPanel);
return namePanel;
}
private void validateName() {
WizardManager wm = panelManager.getWizardManager();
String msg = null;
if (createRepButton.isSelected()) {
String name = nameField.getText();
if (name.length() != 0) {
if (!NamingUtilities.isValidName(name)) {
msg = name + " contains invalid characters";
}
else if (listModel.contains(name)) {
msg = name + " already exists";
}
}
}
wm.validityChanged();
if (msg != null) {
wm.setStatusMessage(msg);
}
}
private void addListeners() {
ActionListener listener = new ActionListener() {
public void actionPerformed(ActionEvent e) {
boolean existingRepSelected = existingRepButton.isSelected();
nameList.setEnabled(existingRepSelected);
if (!existingRepSelected) {
nameList.clearSelection();
}
boolean createRepSelected = createRepButton.isSelected();
nameField.setEnabled(createRepSelected);
nameLabel.setEnabled(createRepSelected);
if (!createRepSelected) {
nameField.setText("");
}
validateName();
}
};
existingRepButton.addActionListener(listener);
createRepButton.addActionListener(listener);
ListSelectionModel selModel = nameList.getSelectionModel();
selModel.addListSelectionListener(new ListSelectionListener() {
/* (non Javadoc)
* @see javax.swing.event.ListSelectionListener#valueChanged(javax.swing.event.ListSelectionEvent)
*/
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) {
return;
}
panelManager.getWizardManager().validityChanged();
}
});
}
}

View file

@ -0,0 +1,115 @@
/* ###
* 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.framework.main;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.util.HashMap;
import java.util.Map;
import javax.swing.*;
import docking.DockingUtils;
import ghidra.framework.model.*;
class RunningToolsPanel extends JPanel {
private JToolBar runningToolbar;
private FrontEndPlugin plugin;
private Map<Tool, ToolButton> runningTools;
RunningToolsPanel(FrontEndPlugin plugin, Workspace ws) {
super(new BorderLayout(0, 0));
this.plugin = plugin;
runningToolbar = new JToolBar(SwingConstants.HORIZONTAL) {
// don't let the user accidentally drag the tools out
// of the workspace panel
@Override
public boolean isFloatable() {
return false;
}
@Override
public boolean isBorderPainted() {
return false;
}
};
// let the default coloring shine through this toolbar; leaving this opaque causes
// odd display coloring issues in some LookAndFeels, like Metal
DockingUtils.setTransparent(runningToolbar);
// remove the default etched border
add(runningToolbar, BorderLayout.CENTER);
runningTools = new HashMap<Tool, ToolButton>(WorkspacePanel.TYPICAL_NUM_RUNNING_TOOLS);
// populate the toolbar if the workspace has running tools
if (ws != null) {
Tool[] tools = ws.getTools();
for (Tool element : tools) {
addTool(element);
}
}
validate();
}
@Override
public Dimension getPreferredSize() {
return runningToolbar.getPreferredSize();
}
void addTool(Tool runningTool) {
ToolButton toolButton =
new ToolButton(plugin, runningTool, runningTool.getToolTemplate(true));
runningToolbar.add(toolButton);
runningTools.put(runningTool, toolButton);
runningToolbar.invalidate();
validate();
repaint();
}
void removeTool(Tool tool) {
ToolButton button = runningTools.get(tool);
if (button == null) {
return;
}
runningToolbar.remove(button);
runningTools.remove(tool);
runningToolbar.invalidate();
button.dispose();
validate();
repaint();
}
// parameter not used
void toolNameChanged(Tool changedTool) {
}
/**
* Update the tool template for the tool button.
*/
void updateToolButton(Tool tool, ToolTemplate template, Icon icon) {
ToolButton button = runningTools.get(tool);
if (button != null) {
button.setToolTemplate(template, icon);
}
validate();
repaint();
}
}

View file

@ -0,0 +1,421 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.main;
import ghidra.framework.model.DomainFile;
import ghidra.framework.model.ProjectLocator;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.exception.CancelledException;
import ghidra.util.task.*;
import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
import javax.swing.border.TitledBorder;
import docking.DialogComponentProvider;
import docking.DockingWindowManager;
import docking.options.editor.ButtonPanelFactory;
import docking.widgets.list.ListPanel;
/**
* Modal dialog to display a list of domain objects that have changed.
* The user can mark the ones to save, or pop up another dialog to save
* the files to a different location and/or name.
* Read-only files are rendered in red and the checkboxes for these files
* cannot be selected.
* If the project has changed, then the first checkbox displayed will be
* for saving the project configuration.
*/
public class SaveDataDialog extends DialogComponentProvider {
private final static String SELECT_ALL = "Select All";
private final static String DESELECT_ALL = "Select None";
private ListPanel listPanel;
private JPanel mainPanel;
private JCheckBox[] checkboxes;
private List<DomainFile> files;
private boolean[] saveable;
private JButton selectAllButton;
private JButton deselectAllButton;
private JButton yesButton;
private JButton noButton;
private PluginTool tool;
/** This class gets run as a task and this flag signals a user cancel */
private boolean operationCompleted;
/**
* Construct new SaveDataDiaog
* @param tool front end tool
*/
public SaveDataDialog(PluginTool tool) {
super("Save Modified Files", true);
setHelpLocation(new HelpLocation("FrontEndPlugin", "SaveDataDialog"));
this.tool = tool;
mainPanel = createPanel();
addWorkPanel(mainPanel);
yesButton = new JButton("Save");
yesButton.setMnemonic('S');
addButton(yesButton);
yesButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
okCallback();
}
});
noButton = new JButton("Don't Save");
noButton.setMnemonic('n');
noButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
operationCompleted = true;
close();
}
});
addButton(noButton);
addCancelButton();
addListeners();
}
/**
* Shows the save dialog with the given domain files, but no options to save
* the project. The dialog will not appear if there is no data that needs
* saving.
*
* @param domainFiles The files that may need saving.
* @return true if the user hit the 'Save' or 'Don't Save' option; return false if the
* user cancelled the operation
*/
public boolean showDialog(List<DomainFile> domainFiles) {
clearStatusText();
operationCompleted = false;
files = domainFiles;
initList();
if (!files.isEmpty()) {
tool.showDialog(this, DockingWindowManager.getActiveInstance().getActiveComponent());
}
else {
operationCompleted = true;
}
return operationCompleted;
}
/**
* Gets called when the user clicks on the OK Action for the dialog.
*/
@Override
protected void okCallback() {
List<DomainFile> list = new ArrayList<DomainFile>();
for (int i = 0; i < checkboxes.length; i++) {
if (checkboxes[i].isSelected()) {
list.add(files.get(i));
}
}
if (list.size() > 0) {
DomainFile[] deleteFiles = new DomainFile[list.size()];
SaveTask task = new SaveTask(list.toArray(deleteFiles));
new TaskLauncher(task, getComponent());
}
else {
operationCompleted = true;
close();
}
}
/**
* Gets called when the user clicks on the Cancel Action for the dialog.
*/
@Override
protected void cancelCallback() {
close();
}
/**
* Create the panel for this dialog.
*/
private JPanel createPanel() {
JPanel panel = new JPanel();
panel.setLayout(new BorderLayout());
JPanel parentPanel = new JPanel(new BorderLayout());
//
// Create Button Panel
//
selectAllButton = new JButton(SELECT_ALL);
selectAllButton.setMnemonic('A');
deselectAllButton = new JButton(DESELECT_ALL);
deselectAllButton.setMnemonic('N');
JPanel buttonPanel =
ButtonPanelFactory.createButtonPanel(new JButton[] { selectAllButton, deselectAllButton });
//
// List Panel
//
listPanel = new ListPanel();
listPanel.setCellRenderer(new DataCellRenderer());
listPanel.setMouseListener(new ListMouseListener());
// Layout Main Panel
parentPanel.add(buttonPanel, BorderLayout.EAST);
parentPanel.add(listPanel, BorderLayout.CENTER);
parentPanel.setBorder(new TitledBorder("Data"));
panel.add(parentPanel, BorderLayout.CENTER);
return panel;
}
/**
* Add listeners to the buttons.
*/
private void addListeners() {
selectAllButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
selectAll();
}
});
deselectAllButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
deselectAll();
}
});
}
/**
* Select all files to be saved.
*/
private void selectAll() {
clearStatusText();
for (int i = 0; i < checkboxes.length; i++) {
if (saveable[i]) {
checkboxes[i].setSelected(true);
}
}
listPanel.repaint();
}
/**
* Clear selected checkboxes.
*/
private void deselectAll() {
clearStatusText();
for (int i = 0; i < checkboxes.length; i++) {
checkboxes[i].setSelected(false);
}
listPanel.repaint();
}
private List<DomainFile> checkForUnsavedFiles(List<DomainFile> domainFiles) {
List<DomainFile> unsavedFiles = new ArrayList<DomainFile>();
for (DomainFile domainFile : domainFiles) {
if (domainFile.isChanged()) {
unsavedFiles.add(domainFile);
}
}
return unsavedFiles;
}
private void initList() {
// initList() may be called multiple times within one dialog showing,
// and some files may have been changed, so we need to update the list
files = checkForUnsavedFiles(files);
checkboxes = new JCheckBox[files.size()];
saveable = new boolean[files.size()];
String readOnlyString = " (Read-Only)";
yesButton.setEnabled(false);
for (int i = 0; i < files.size(); i++) {
checkboxes[i] = new JCheckBox(files.get(i).getName());
checkboxes[i].setBackground(Color.white);
saveable[i] = files.get(i).canSave();
if (!saveable[i]) {
String text = files.get(i).getName() + readOnlyString;
if (!files.get(i).isInWritableProject()) {
ProjectLocator projectLocator = files.get(i).getProjectLocator();
if (projectLocator != null) {
text =
files.get(i).getName() + " (Read-Only from " +
files.get(i).getProjectLocator().getName() + ")";
}
}
checkboxes[i].setText(text);
}
else {
checkboxes[i].setSelected(true);
yesButton.setEnabled(true);
}
}
listPanel.refreshList(checkboxes);
setFocusComponent(yesButton);//.requestFocusInWindow();
}
/////////////////////////////////////////////////////////////////////////
/**
* Cell renderer to show the checkboxes for the changed data files.
*/
private class DataCellRenderer implements ListCellRenderer {
private Font boldFont;
@Override
public Component getListCellRendererComponent(JList list, Object value, int index,
boolean isSelected, boolean cellHasFocus) {
if (boldFont == null) {
Font font = list.getFont();
boldFont = new Font(font.getName(), font.getStyle() | Font.BOLD, font.getSize());
}
// set color to red if file cannot be saved 'as is'
if (!saveable[index]) {
checkboxes[index].setForeground(Color.red);
checkboxes[index].setFont(boldFont);
}
return (checkboxes[index]);
}
}
/**
* Mouse listener to get the selected cell in the list.
*/
private class ListMouseListener extends MouseAdapter {
@Override
public void mouseClicked(MouseEvent e) {
clearStatusText();
JList list = (JList) e.getSource();
int index = list.locationToIndex(e.getPoint());
if (index < 0) {
return;
}
if (!saveable[index]) {
setStatusText(files.get(index).getPathname() +
" cannot be saved to current location");
return;
}
boolean selected = checkboxes[index].isSelected();
checkboxes[index].setSelected(!selected);
listPanel.repaint();
}
}
/////////////////////////////////////////////////////////////////////////
/**
* Task to save files.
*/
private class SaveTask extends Task {
private DomainFile[] domainFiles;
SaveTask(DomainFile[] files) {
super(files.length > 1 ? "Saving Files..." : "Saving File", true, true, true);
this.domainFiles = files;
}
/**
* @see ghidra.util.task.Task#run(TaskMonitor)
*/
@Override
public void run(TaskMonitor monitor) {
try {
for (int i = 0; i < domainFiles.length; i++) {
if (monitor.isCancelled()) {
break;
}
monitor.setProgress(0);
monitor.setMessage("Saving " + domainFiles[i].getName());
domainFiles[i].save(monitor);
}
operationCompleted = !monitor.isCancelled();
}
catch (CancelledException ce) {
// this is OK, it will be handled below
}
catch (Throwable t) {
Msg.showError(this, null, "Error Saving Data", "Unexpected exception saving data!",
t);
}
if (operationCompleted) {
try {
SwingUtilities.invokeAndWait(new Runnable() {
@Override
public void run() {
close();
}
});
}
catch (InterruptedException e) {
// don't care?
}
catch (InvocationTargetException e) {
// don't care?
}
}
else if (monitor.isCancelled()) {
updateList();
}
}
/**
* Refresh the list of files that need saving.
*/
private void updateList() {
Runnable r = new Runnable() {
@Override
public void run() {
initList();
}
};
try {
SwingUtilities.invokeAndWait(r);
}
catch (Exception e) {
// don't care?
}
}
}
}

View file

@ -0,0 +1,350 @@
/* ###
* 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.framework.main;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import javax.swing.*;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import javax.swing.text.Document;
import docking.options.editor.ButtonPanelFactory;
import docking.widgets.filechooser.GhidraFileChooser;
import docking.wizard.AbstractWizardJPanel;
import docking.wizard.WizardManager;
import ghidra.app.util.GenericHelpTopics;
import ghidra.framework.GenericRunInfo;
import ghidra.framework.model.ProjectLocator;
import ghidra.framework.preferences.Preferences;
import ghidra.util.HelpLocation;
import ghidra.util.NamingUtilities;
import ghidra.util.filechooser.GhidraFileChooserModel;
import ghidra.util.filechooser.GhidraFileFilter;
import ghidra.util.layout.VerticalLayout;
/**
* Panel that allows the project directory and name to be specified for a
* new project. A checkbox indicates whether the project should be created
* as a shared project.
*
*/
class SelectProjectPanel extends AbstractWizardJPanel {
//remove the "." from the extension
private static String PROJECT_EXTENSION = ProjectLocator.getProjectExtension().substring(1);
private JTextField projectNameField;
private JTextField directoryField;
private JButton browseButton;
private GhidraFileChooser fileChooser;
private ProjectLocator projectLocator;
private NewProjectPanelManager panelManager;
private DocumentListener docListener;
/**
* Construct a new panel.
* @param panelManager manager for the "new project" set of panels
*/
public SelectProjectPanel(NewProjectPanelManager panelManager) {
super(new BorderLayout());
this.panelManager = panelManager;
buildMainPanel();
setBorder(BorderFactory.createEmptyBorder(80, 80, 0, 80));
}
/* (non Javadoc)
* @see ghidra.util.bean.wizard.WizardPanel#getTitle()
*/
public String getTitle() {
if (panelManager.isSharedProject()) {
return "Select Local Project Location for Repository " +
panelManager.getProjectRepositoryName();
}
return "Select Project Location";
}
/* (non Javadoc)
* @see ghidra.util.bean.wizard.WizardPanel#initialize()
*/
public void initialize() {
projectLocator = null;
Document doc = projectNameField.getDocument();
doc.removeDocumentListener(docListener);
projectNameField.setText("");
doc.addDocumentListener(docListener);
}
/**
* Return true if the user has entered a valid project file
*/
public boolean isValidInformation() {
return projectLocator != null;
}
/* (non-Javadoc)
* @see ghidra.util.bean.wizard.WizardPanel#getHelpLocation()
*/
@Override
public HelpLocation getHelpLocation() {
if (panelManager.isSharedProject()) {
return new HelpLocation(GenericHelpTopics.FRONT_END, "SelectProjectLocation");
}
return new HelpLocation(GenericHelpTopics.FRONT_END, "CreateNonSharedProject");
}
ProjectLocator getProjectLocator() {
return projectLocator;
}
void setProjectName(String projectName) {
projectNameField.setText(projectName);
}
String getStatusMessage() {
if (projectLocator == null) {
return checkProjectFile(false);
}
return "";
}
private void buildMainPanel() {
JPanel outerPanel = new JPanel();
GridBagLayout gbl = new GridBagLayout();
outerPanel.setLayout(gbl);
JLabel dirLabel = new JLabel("Project Directory:", SwingConstants.RIGHT);
directoryField = new JTextField(25);
directoryField.setName("Project Directory");
String lastDirSelected = Preferences.getProperty(Preferences.LAST_NEW_PROJECT_DIRECTORY);
if (lastDirSelected != null) {
directoryField.setText(lastDirSelected);
}
else {
File projectDirectory = new File(GenericRunInfo.getProjectsDirPath());
directoryField.setText(projectDirectory.getAbsolutePath());
}
directoryField.setCaretPosition(directoryField.getText().length() - 1);
JLabel projectNameLabel = new JLabel("Project Name:", SwingConstants.RIGHT);
projectNameField = new JTextField(25);
projectNameField.setName("Project Name");
projectNameField.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
setProjectFile();
}
});
docListener = new DocumentListener() {
public void insertUpdate(DocumentEvent e) {
setProjectFile();
}
public void removeUpdate(DocumentEvent e) {
setProjectFile();
}
public void changedUpdate(DocumentEvent e) {
setProjectFile();
}
};
projectNameField.getDocument().addDocumentListener(docListener);
directoryField.getDocument().addDocumentListener(docListener);
browseButton = ButtonPanelFactory.createButton(ButtonPanelFactory.BROWSE_TYPE);
browseButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
displayFileChooser();
}
});
// sharedProjectCB = new JCheckBox("Project can be Shared with Others");
// sharedProjectCB.addItemListener(new ItemListener() {
// public void itemStateChanged(ItemEvent e) {
// panelManager.getWizardManager().validityChanged();
// checkProjectFile(false); // cause message to be displayed
// // if project name is invalid
// }
// });
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.anchor = GridBagConstraints.EAST;
gbl.setConstraints(dirLabel, gbc);
outerPanel.add(dirLabel);
gbc = new GridBagConstraints();
gbc.gridx = 1;
gbc.insets.left = 5;
gbc.insets.bottom = 5;
gbc.weightx = 1.0;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbl.setConstraints(directoryField, gbc);
outerPanel.add(directoryField);
gbc = new GridBagConstraints();
gbc.gridx = 2;
gbc.insets.left = 5;
gbc.insets.bottom = 5;
gbc.anchor = GridBagConstraints.EAST;
gbl.setConstraints(browseButton, gbc);
outerPanel.add(browseButton);
gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 1;
gbc.insets.left = 5;
gbc.insets.bottom = 5;
gbc.weightx = 1.0;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbl.setConstraints(projectNameLabel, gbc);
outerPanel.add(projectNameLabel);
gbc = new GridBagConstraints();
gbc.gridx = 1;
gbc.gridy = 1;
gbc.insets.left = 5;
gbc.insets.bottom = 5;
gbc.weightx = 1.0;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbl.setConstraints(projectNameField, gbc);
outerPanel.add(projectNameField);
JPanel p = new JPanel(new VerticalLayout(5));
p.add(outerPanel);
add(p, BorderLayout.CENTER);
}
private void setProjectFile() {
checkProjectFile(true);
}
/**
* Check the validity of the project file name.
*/
private String checkProjectFile(boolean showMessage) {
WizardManager wm = panelManager.getWizardManager();
if (showMessage) {
wm.setStatusMessage("");
}
projectLocator = null;
ProjectLocator projectLocator = null;
String msg = null;
String dir = directoryField.getText().trim();
if (dir.length() == 0) {
msg = "Please specify project directory";
}
else if (!new File(dir).isDirectory()) {
msg = "Project directory does not exist.";
}
else {
String projectName = projectNameField.getText().trim();
if (projectName.endsWith(PROJECT_EXTENSION)) {
projectName =
projectName.substring(0, projectName.length() - PROJECT_EXTENSION.length());
}
if (projectName.length() == 0 || !NamingUtilities.isValidName(projectName)) {
msg = "Please specify valid project name";
}
else {
try {
projectLocator = new ProjectLocator(dir, projectName);
}
catch (IllegalArgumentException e) {
msg = e.getMessage();
}
}
}
if (projectLocator != null) {
File parentDir = new File(dir);
if (!parentDir.isDirectory()) {
msg = "Please specify a Project Directory";
}
else if (projectLocator.getMarkerFile().exists() ||
projectLocator.getProjectDir().exists()) {
msg =
getProjectName("A project named " + projectLocator.getName() +
" already exists in " + parentDir.getAbsolutePath());
}
else {
this.projectLocator = projectLocator;
}
}
wm.validityChanged();
if (showMessage) {
wm.setStatusMessage(msg);
}
return msg;
}
private void displayFileChooser() {
if (fileChooser == null) {
createFileChooser();
}
fileChooser.setTitle("Select a Ghidra Project Directory");
fileChooser.setApproveButtonText("Select Project Directory");
fileChooser.setApproveButtonToolTipText("Select a Ghidra Project Directory");
File file = fileChooser.getSelectedFile();
if (file != null) {
directoryField.setText(file.getAbsolutePath());
WizardManager wm = panelManager.getWizardManager();
wm.setStatusMessage("");
wm.validityChanged();
checkProjectFile(true);
}
}
private String getProjectName(String name) {
if (name.endsWith(PROJECT_EXTENSION)) {
name = name.substring(0, name.indexOf(PROJECT_EXTENSION) - 1);
}
return name;
}
private void createFileChooser() {
WizardManager wm = panelManager.getWizardManager();
fileChooser = new GhidraFileChooser(wm.getComponent());
File projectDirectory = new File(GenericRunInfo.getProjectsDirPath());
String lastDirSelected =
Preferences.getProperty(Preferences.LAST_NEW_PROJECT_DIRECTORY, null, true);
if (lastDirSelected != null) {
projectDirectory = new File(lastDirSelected);
}
fileChooser.setFileSelectionMode(GhidraFileChooser.DIRECTORIES_ONLY);
fileChooser.setFileFilter(new GhidraFileFilter() {
public String getDescription() {
return "All Directories";
}
public boolean accept(File f, GhidraFileChooserModel model) {
return model.isDirectory(f) &&
!f.getName().endsWith(ProjectLocator.getProjectDirExtension());
}
});
fileChooser.setCurrentDirectory(projectDirectory);//start the browsing in the user's preferred project directory
}
}

View file

@ -0,0 +1,209 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.main;
import ghidra.framework.model.ServerInfo;
import ghidra.framework.remote.GhidraServerHandle;
import ghidra.util.MessageType;
import ghidra.util.StatusListener;
import ghidra.util.layout.PairLayout;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.event.*;
import docking.ToolTipManager;
/**
* Component that allows the user to specify the host name and port
* number for the remote repository server.
*/
public class ServerInfoComponent extends JPanel {
private JTextField nameField;
private JTextField portNumberField;
private int portNumber = -1;
private DocumentListener portDocListener;
private DocumentListener nameDocListener;
private StatusListener statusListener;
private ChangeListener listener;
public ServerInfoComponent() {
super(new BorderLayout(10, 10));
buildMainPanel();
}
/**
* Set the status listener
* @param statusListener
*/
public void setStatusListener(StatusListener statusListener) {
this.statusListener = statusListener;
}
/**
* Set the change listener for this component
* @param listener
*/
public void setChangeListener(ChangeListener listener) {
this.listener = listener;
}
/**
* Get the server name.
*/
public String getServerName() {
return nameField.getText();
}
/**
* Get the port number.
*/
public int getPortNumber() {
return portNumber;
}
/**
* Set the field values using the given server info.
*/
public void setServerInfo(ServerInfo info) {
if (info != null) {
nameField.setText(info.getServerName());
portNumberField.setText(Integer.toString(info.getPortNumber()));
}
else {
nameField.setText("");
portNumberField.setText(Integer.toString(GhidraServerHandle.DEFAULT_PORT));
}
}
private void buildMainPanel() {
JLabel nameLabel = new JLabel("Server Name:", SwingConstants.RIGHT);
nameField = new JTextField(20);
nameField.setName("Server Name");
nameField.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
nameField.transferFocus();
}
});
nameDocListener = new DocumentListener() {
public void changedUpdate(DocumentEvent e) {
notifyChange();
}
public void insertUpdate(DocumentEvent e) {
notifyChange();
}
public void removeUpdate(DocumentEvent e) {
notifyChange();
}
};
nameField.getDocument().addDocumentListener(nameDocListener);
JLabel portLabel = new JLabel("Port Number:", SwingConstants.RIGHT);
portNumberField = new JTextField(20);
portNumberField.setName("Port Number");
portNumberField.setText(Integer.toString(GhidraServerHandle.DEFAULT_PORT));
portNumberField.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
portNumberField.transferFocus();
}
});
portDocListener = new DocumentListener() {
public void insertUpdate(DocumentEvent e) {
notifyChange();
}
public void removeUpdate(DocumentEvent e) {
notifyChange();
}
public void changedUpdate(DocumentEvent e) {
notifyChange();
}
};
portNumberField.getDocument().addDocumentListener(portDocListener);
ToolTipManager.setToolTipText(portNumberField, "Enter port number");
JPanel panel = new JPanel(new PairLayout(5, 10));
panel.add(nameLabel);
panel.add(nameField);
panel.add(portLabel);
panel.add(portNumberField);
add(panel, BorderLayout.CENTER);
}
private void setStatus(String text) {
if (statusListener == null) {
return;
}
if (text == null || text.length() == 0) {
statusListener.clearStatusText();
}
else {
statusListener.setStatusText(text, MessageType.ERROR);
}
}
private void notifyChange() {
if (listener != null) {
listener.stateChanged(new ChangeEvent(this));
}
}
private boolean checkPortNumber() {
portNumber = -1;
String portStr = portNumberField.getText();
String msg = null;
try {
portNumber = Integer.parseInt(portStr);
if (portNumber < 0 || portNumber > 65536) {
portNumber = -1;
msg = "Port number must in range of 0 to 65536";
}
}
catch (NumberFormatException e) {
msg = "Invalid port number entered";
}
setStatus(msg);
return msg == null;
}
private boolean checkServerName() {
String name = nameField.getText();
String msg = null;
if (name.length() == 0) {
msg = "Enter the server name";
}
setStatus(msg);
return msg == null;
}
/**
* Return whether the fields on this panel have valid information.
*/
public boolean isValidInformation() {
return checkServerName() && checkPortNumber();
}
}

View file

@ -0,0 +1,116 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.main;
import ghidra.app.util.GenericHelpTopics;
import ghidra.framework.model.ServerInfo;
import ghidra.util.HelpLocation;
import java.awt.BorderLayout;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import docking.wizard.*;
/**
* Wizard panel that allows the user to specify the host name and port
* number for the remote repository server.
*/
public class ServerInfoPanel extends AbstractWizardJPanel {
private ServerInfoComponent serverInfoComponent;
private PanelManager panelManager;
private HelpLocation helpLoc;
public ServerInfoPanel(PanelManager panelManager) {
super(new BorderLayout(10, 10));
this.panelManager = panelManager;
setBorder(NewProjectPanelManager.EMPTY_BORDER);
buildMainPanel();
}
/* (non Javadoc)
* @see ghidra.util.bean.wizard.WizardPanel#getTitle()
*/
public String getTitle() {
return "Specify Server Information";
}
/* (non-Javadoc)
* @see ghidra.util.bean.wizard.WizardPanel#getHelpLocation()
*/
@Override
public HelpLocation getHelpLocation() {
if (helpLoc != null) {
return helpLoc;
}
return new HelpLocation(GenericHelpTopics.FRONT_END, "ServerInfo");
}
/* (non Javadoc)
* @see ghidra.util.bean.wizard.WizardPanel#initialize()
*/
public void initialize() {
serverInfoComponent.setStatusListener(panelManager.getWizardManager());
}
/**
* Return whether the fields on this panel have valid information.
*/
public boolean isValidInformation() {
return serverInfoComponent.isValidInformation();
}
/**
* Get the server name.
*/
String getServerName() {
return serverInfoComponent.getServerName();
}
/**
* Get the port number.
*/
int getPortNumber() {
return serverInfoComponent.getPortNumber();
}
/**
* Set the field values using the given server info.
*/
public void setServerInfo(ServerInfo info) {
serverInfoComponent.setServerInfo(info);
}
void setHelpLocation(HelpLocation helpLoc) {
this.helpLoc = helpLoc;
}
private void buildMainPanel() {
serverInfoComponent = new ServerInfoComponent();
serverInfoComponent.setChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent e) {
WizardManager wm = panelManager.getWizardManager();
if (wm.getCurrentWizardPanel() != null) {
wm.validityChanged();
}
}
});
add(serverInfoComponent, BorderLayout.CENTER);
}
}

View file

@ -0,0 +1,329 @@
/* ###
* 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.framework.main;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.*;
import java.util.List;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import docking.DialogComponentProvider;
import docking.widgets.table.*;
import ghidra.framework.data.ContentHandler;
import ghidra.framework.model.*;
import ghidra.framework.project.tool.GhidraToolTemplate;
import ghidra.util.HelpLocation;
import resources.ResourceManager;
class SetToolAssociationsDialog extends DialogComponentProvider {
private final FrontEndTool tool;
private ToolAssociationTableModel model;
private GTable table;
SetToolAssociationsDialog(FrontEndTool tool) {
super("Set Tool Associations", true);
this.tool = tool;
setHelpLocation(new HelpLocation("Tool", "Set Tool Associations"));
addWorkPanel(createWorkPanel());
addOKButton();
addCancelButton();
setPreferredSize(400, 400);
setRememberLocation(false);
}
private JComponent createWorkPanel() {
JPanel mainPanel = new JPanel(new BorderLayout());
model = new ToolAssociationTableModel();
table = new GTable(model);
final JButton editButton = new JButton("Edit");
editButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int selectedRow = table.getSelectedRow();
ToolAssociationInfo info = model.getRowObject(selectedRow);
if (info == null) {
return;
}
ContentHandler contentHandler = info.getContentHandler();
Class<? extends DomainObject> domainClass = contentHandler.getDomainObjectClass();
PickToolDialog dialog = new PickToolDialog(tool, domainClass);
dialog.showDialog();
ToolTemplate template = dialog.getSelectedToolTemplate();
if (template != null) {
info.setCurrentTool(template);
model.fireTableDataChanged();
}
}
});
editButton.setEnabled(false);
final JButton resetButton = new JButton("Restore Default");
resetButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
int selectedRow = table.getSelectedRow();
ToolAssociationInfo info = model.getRowObject(selectedRow);
if (info != null) {
info.restoreDefaultAssociation();
table.repaint();
}
}
});
resetButton.setEnabled(false);
table.setRowHeight(28); // make big enough for tool icons
table.setColumnHeaderPopupEnabled(false); // don't allow column configuration
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
table.setDefaultRenderer(ContentHandler.class, new ContentHandlerRenderer());
table.setDefaultRenderer(GhidraToolTemplate.class, new ToolTemplateRenderer());
table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
@Override
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) {
return;
}
int selectedRow = table.getSelectedRow();
ToolAssociationInfo info = model.getRowObject(selectedRow);
if (info == null) {
editButton.setEnabled(false);
resetButton.setEnabled(false);
return;
}
editButton.setEnabled(true);
resetButton.setEnabled(!info.isDefault());
}
});
loadList();
JPanel buttonPanel = new JPanel();
buttonPanel.add(editButton);
buttonPanel.add(Box.createHorizontalStrut(5));
buttonPanel.add(resetButton);
mainPanel.add(new JScrollPane(table), BorderLayout.CENTER);
mainPanel.add(buttonPanel, BorderLayout.SOUTH);
return mainPanel;
}
private void loadList() {
Project project = tool.getProject();
ToolServices toolServices = project.getToolServices();
Set<ToolAssociationInfo> infos = toolServices.getContentTypeToolAssociations();
model.setData(new ArrayList<>(infos));
}
void showDialog() {
clearStatusText();
tool.showDialog(this);
}
@Override
protected void okCallback() {
applyUserChoices();
close();
}
private void applyUserChoices() {
Set<ToolAssociationInfo> set = new HashSet<>(model.getModelData());
tool.getToolServices().setContentTypeToolAssociations(set);
}
//==================================================================================================
// Inner Classes
//==================================================================================================
private class ToolAssociationTableModel extends AbstractSortedTableModel<ToolAssociationInfo> {
private List<ToolAssociationInfo> data;
ToolAssociationTableModel() {
super(0);
this.data = Collections.emptyList();
}
void setData(List<ToolAssociationInfo> data) {
this.data = data;
fireTableDataChanged();
}
@Override
public String getName() {
return "Set Tool Association";
}
@Override
public Object getColumnValueForRow(ToolAssociationInfo t, int column) {
switch (column) {
case 0:
return t.getContentHandler();
case 1:
return t.getCurrentTemplate();
}
return null;
}
@Override
public String getColumnName(int column) {
switch (column) {
case 0:
return "Content Type";
case 1:
return "Tool";
}
return null;
}
@Override
public Class<?> getColumnClass(int column) {
switch (column) {
case 0:
return ContentHandler.class;
case 1:
return GhidraToolTemplate.class;
}
return null;
}
@Override
public List<ToolAssociationInfo> getModelData() {
return data;
}
@Override
public boolean isSortable(int columnIndex) {
return columnIndex == 0;
}
@Override
public int getColumnCount() {
return 2;
}
@Override
public int getRowCount() {
return data.size();
}
@Override
protected Comparator<ToolAssociationInfo> createSortComparator(int column) {
switch (column) {
case 0:
return new ContentHandlerComparator();
case 1:
return new ToolTemplateComparator();
}
return super.createSortComparator(column);
}
}
private class ContentHandlerComparator implements Comparator<ToolAssociationInfo> {
@Override
public int compare(ToolAssociationInfo o1, ToolAssociationInfo o2) {
return o1.getContentHandler().getContentType().compareTo(
o2.getContentHandler().getContentType());
}
}
private class ToolTemplateComparator implements Comparator<ToolAssociationInfo> {
@Override
public int compare(ToolAssociationInfo o1, ToolAssociationInfo o2) {
return o1.getCurrentTemplate().getName().compareTo(o2.getCurrentTemplate().getName());
}
}
private class ContentHandlerRenderer extends GTableCellRenderer {
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
JLabel renderer = (JLabel) super.getTableCellRendererComponent(data);
Object value = data.getValue();
//
// Content Type: icon - name
//
ContentHandler handler = (ContentHandler) value;
renderer.setIcon(handler.getIcon());
renderer.setText(handler.getContentType());
return renderer;
}
}
private class ToolTemplateRenderer extends GTableCellRenderer {
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
JLabel renderer = (JLabel) super.getTableCellRendererComponent(data);
Object value = data.getValue();
int row = data.getRowViewIndex();
ToolTemplate template = (ToolTemplate) value;
if (template == null) {
renderDefaultTool(renderer, row);
return renderer;
}
renderer.setIcon(template.getIcon());
renderer.setText(template.getName());
return renderer;
}
private void renderDefaultTool(JLabel renderer, int row) {
ToolAssociationInfo info = model.getRowObject(row);
ToolTemplate template = info.getDefaultTemplate();
if (template == null) {
return;
}
renderer.setForeground(Color.LIGHT_GRAY);
Icon icon = null;
if (template.getName().equals(info.getAssociatedToolName())) {
icon = ResourceManager.getDisabledIcon(template.getIcon());
}
else {
icon = ResourceManager.getDisabledIcon(ResourceManager.getScaledIcon(
ResourceManager.loadImage("images/EmptyIcon.gif"), 24, 24));
}
renderer.setText(info.getAssociatedToolName());
renderer.setIcon(icon);
}
}
}

View file

@ -0,0 +1,354 @@
/* ###
* 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.framework.main;
import java.awt.Dimension;
import java.io.IOException;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.border.Border;
import docking.wizard.*;
import ghidra.app.util.GenericHelpTopics;
import ghidra.framework.client.*;
import ghidra.framework.model.ProjectManager;
import ghidra.framework.model.ServerInfo;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.remote.User;
import ghidra.util.HelpLocation;
import ghidra.util.exception.DuplicateNameException;
import ghidra.util.exception.UserAccessException;
/**
* Manage the panels for the wizard that shows server info and repository panels.
* The panel order is
* (1) Server Info
* (2) Repository panel
* (3) Project access panel (if user is creating a new repository)
* This panel manager is used when the project is being converted to a shared project and
* when a shared project's information is to change.
*/
class SetupProjectPanelManager implements PanelManager {
private WizardManager wizardMgr;
private String[] knownUsers;
private ServerInfoPanel serverPanel;
private RepositoryPanel repositoryPanel;
private ProjectAccessPanel projectAccessPanel;
private WizardPanel currentWizardPanel;
private boolean includeAnonymousAccessControl = false;
private ProjectManager projectMgr;
private RepositoryServerAdapter server;
private RepositoryAdapter repository;
private ServerInfo serverInfo;
private ServerInfo currentServerInfo;
private String statusMessage;
private PluginTool tool;
final static Border EMPTY_BORDER = BorderFactory.createEmptyBorder(80, 120, 0, 120);
SetupProjectPanelManager(PluginTool tool, ServerInfo serverInfo) {
serverPanel = new ServerInfoPanel(this);
serverPanel.setHelpLocation(
new HelpLocation(GenericHelpTopics.FRONT_END, "SetupServerInfo"));
projectMgr = tool.getProjectManager();
currentServerInfo = serverInfo;
this.tool = tool;
}
@Override
public boolean canFinish() {
if (repositoryPanel == null) {
return false;
}
if (repositoryPanel.isValidInformation()) {
if (repositoryPanel.createRepository()) {
return projectAccessPanel == null || projectAccessPanel.isValidInformation();
}
return true;
}
return false;
}
@Override
public boolean hasNextPanel() {
if (currentWizardPanel == serverPanel) {
return true;
}
if (currentWizardPanel == repositoryPanel && repositoryPanel.createRepository()) {
return true;
}
return false;
}
@Override
public boolean hasPreviousPanel() {
return currentWizardPanel != serverPanel;
}
@Override
public WizardPanel getInitialPanel() {
currentWizardPanel = serverPanel;
return currentWizardPanel;
}
@Override
public WizardPanel getNextPanel() {
if (currentWizardPanel == null) {
currentWizardPanel = serverPanel;
if (currentServerInfo != null) {
serverPanel.setServerInfo(currentServerInfo);
}
else {
serverPanel.setServerInfo(projectMgr.getMostRecentServerInfo());
}
}
else if (currentWizardPanel == serverPanel) {
String serverName = serverPanel.getServerName();
int portNumber = serverPanel.getPortNumber();
if (!isServerInfoValid(serverName, portNumber)) {
return serverPanel;
}
try {
knownUsers = server.getAllUsers();
String[] repositoryNames = server.getRepositoryNames();
includeAnonymousAccessControl = server.anonymousAccessAllowed();
if (repositoryPanel == null) {
repositoryPanel =
new RepositoryPanel(this, serverName, repositoryNames, server.isReadOnly());
repositoryPanel.setHelpLocation(
new HelpLocation(GenericHelpTopics.FRONT_END, "ChangeRepository"));
}
currentWizardPanel = repositoryPanel;
}
catch (RemoteException e) {
statusMessage = "Error accessing remote server on " + serverName;
}
catch (NotConnectedException e) {
statusMessage = e.getMessage();
if (statusMessage == null) {
statusMessage = "Not connected to server " + serverName + ": " + e;
}
}
catch (IOException e) {
statusMessage = "IOException: could not access remote server on " + serverName;
}
}
else if (currentWizardPanel == repositoryPanel) {
String repositoryName = repositoryPanel.getRepositoryName();
if (!repositoryPanel.createRepository()) {
currentWizardPanel = null;
repository = server.getRepository(repositoryName);
return currentWizardPanel;
}
checkNewRepositoryAccessPanel();
currentWizardPanel = projectAccessPanel;
}
else if (currentWizardPanel == projectAccessPanel) {
currentWizardPanel = null;
}
return currentWizardPanel;
}
@Override
public WizardPanel getPreviousPanel() {
if (currentWizardPanel == projectAccessPanel) {
currentWizardPanel = repositoryPanel;
}
else if (currentWizardPanel == repositoryPanel) {
currentWizardPanel = serverPanel;
}
else {
currentWizardPanel = null;
}
return currentWizardPanel;
}
@Override
public String getStatusMessage() {
String msg = statusMessage;
statusMessage = null;
return msg;
}
@Override
public void finish() {
if (server != null) {
boolean createNewRepository = repositoryPanel.createRepository();
if (!createNewRepository) {
if (repository == null) {
repository = server.getRepository(repositoryPanel.getRepositoryName());
}
}
else {
try {
String repositoryName = repositoryPanel.getRepositoryName();
boolean allowAnonymousAccess;
User[] accessList;
if (projectAccessPanel != null &&
projectAccessPanel.getRepositoryName().equals(repositoryName)) {
accessList = projectAccessPanel.getProjectUsers();
allowAnonymousAccess = projectAccessPanel.allowAnonymousAccess();
}
else {
accessList = new User[] { new User(server.getUser(), User.ADMIN) };
allowAnonymousAccess = false;
}
repository = server.createRepository(repositoryName);
repository.setUserList(accessList, allowAnonymousAccess);
}
catch (DuplicateNameException e) {
statusMessage = "Repository " + repositoryPanel.getRepositoryName() + " exists";
}
catch (UserAccessException exc) {
statusMessage = "Could not update the user list: " + exc.getMessage();
return;
}
catch (NotConnectedException e) {
statusMessage = e.getMessage();
if (statusMessage == null) {
statusMessage =
"Not connected to server " + serverInfo.getServerName() + ": " + e;
}
return;
}
catch (IOException exc) {
String msg = exc.getMessage();
if (msg == null) {
msg = exc.toString();
}
statusMessage = "Error occurred while updating the user list: " + msg;
return;
}
}
}
wizardMgr.close();
}
@Override
public void cancel() {
currentWizardPanel = null;
repositoryPanel = null;
projectAccessPanel = null;
server = null;
if (repository != null) {
repository.disconnect();
repository = null;
}
}
@Override
public void initialize() {
currentWizardPanel = null;
if (repositoryPanel != null) {
repositoryPanel.initialize();
}
if (projectAccessPanel != null) {
projectAccessPanel.initialize();
}
}
@Override
public Dimension getPanelSize() {
return getMyPanelSize();
}
@Override
public void setWizardManager(WizardManager wm) {
wizardMgr = wm;
}
@Override
public WizardManager getWizardManager() {
return wizardMgr;
}
/**
* Get the repository adapter associated with the new project.
* After displaying this panel, this method should be invoked to obtain the
* repository which will be opened for shared projects. If the repository is
* not used to create a new project, its disconnect method should be invoked.
* @return null if project is not shared
*/
RepositoryAdapter getProjectRepository() {
return repository;
}
String getProjectRepositoryName() {
return repositoryPanel.getRepositoryName();
}
private void checkNewRepositoryAccessPanel() {
String repositoryName = repositoryPanel.getRepositoryName();
if (projectAccessPanel != null &&
projectAccessPanel.getRepositoryName().equals(repositoryName)) {
return;
}
List<User> userList = new ArrayList<>();
userList.add(new User(server.getUser(), User.ADMIN));
projectAccessPanel = new ProjectAccessPanel(knownUsers, server.getUser(), userList,
repositoryName, includeAnonymousAccessControl, false, tool);
projectAccessPanel.setHelpLocation(
new HelpLocation(GenericHelpTopics.FRONT_END, "SetupUsers"));
}
/**
* Return true if a connection could be established using the given
* server name and port number.
*/
private boolean isServerInfoValid(String serverName, int portNumber) {
if (server != null && serverInfo != null && serverInfo.getServerName().equals(serverName) &&
serverInfo.getPortNumber() == portNumber && server.isConnected()) {
return true;
}
server = null;
serverInfo = null;
repositoryPanel = null;
server = projectMgr.getRepositoryServerAdapter(serverName, portNumber, true);
if (server.isConnected()) {
serverInfo = projectMgr.getMostRecentServerInfo();
return true;
}
statusMessage = "Could not connect to server " + serverName + ", port " + portNumber;
return false;
}
private Dimension getMyPanelSize() {
ProjectAccessPanel panel1 = new ProjectAccessPanel(new String[] { "nobody" }, "user",
new ArrayList<User>(), "MyRepository", true, false, tool);
RepositoryPanel panel2 = new RepositoryPanel(this, "ServerOne",
new String[] { "MyRepository", "NewStuff", "Repository_A", "Repository_B" }, false);
Dimension d1 = panel1.getPreferredSize();
Dimension d2 = panel2.getPreferredSize();
return new Dimension(Math.max(d1.width, d2.width), Math.max(d1.height, d2.height));
}
}

View file

@ -0,0 +1,33 @@
/* ###
* 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.framework.main;
import ghidra.framework.model.ProjectManager;
/**
* A test version of the {@link FrontEndTool} that disables some functionality
*/
public class TestFrontEndTool extends FrontEndTool {
public TestFrontEndTool(ProjectManager pm) {
super(pm);
}
@Override
public void close() {
setVisible(false);
}
}

View file

@ -0,0 +1,581 @@
/* ###
* 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.framework.main;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.io.*;
import java.util.*;
import javax.swing.JMenuItem;
import org.jdom.Element;
import org.jdom.input.SAXBuilder;
import docking.ActionContext;
import docking.action.*;
import docking.widgets.filechooser.GhidraFileChooser;
import ghidra.framework.model.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.plugintool.util.ToolConstants;
import ghidra.framework.preferences.Preferences;
import ghidra.framework.project.tool.GhidraToolTemplate;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.filechooser.ExtensionFileFilter;
import ghidra.util.xml.XmlUtilities;
import resources.ResourceManager;
/**
* Helper class to manage actions on the Tool menu.
*/
class ToolActionManager implements ToolChestChangeListener {
private final static int TYPICAL_NUM_TOOLS_IN_TOOLCHEST = 5;
private final static int NEWTOOL_ACCELERATOR = KeyEvent.VK_T;
private static final String MENU_ITEM_CREATE_TOOL = "&Create Tool..."; // Group: ATools
private static final String MENU_ITEM_RUN_TOOL = "&Run Tool"; // Group: BTools
private static final String MENU_ITEM_DELETE_TOOL = "Delete Tool"; // Group: CTools
private static final String MENU_ITEM_IMPORT_TOOL = "&Import Tool..."; // Group: DTools
private static final String MENU_ITEM_IMPORT_DEFTOOLS = "Import &Default Tools...";
private static final String MENU_ITEM_EXPORT_TOOL = "&Export Tool";
private static final String MENU_ITEM_CONNECT_TOOLS = "Connect &Tools..."; // Group: ETools
private static final String MENU_ITEM_SET_DEFAULT_TOOL = "&Set As Default"; // Group: FTools
private FrontEndPlugin plugin;
private FrontEndTool tool;
private ToolConnectionDialog toolConnectionDialog;
private DockingAction createToolAction;
private DockingAction connectToolsAction;
private DockingAction importAction;
private DockingAction importDefaultToolsAction;
private DockingAction setToolAssociationsAction;
private Map<String, DockingAction> runToolActionMap;
private Map<String, DockingAction> delToolActionMap;
private Map<String, DockingAction> exportToolActionMap;
private GhidraFileChooser fileChooser;
ToolActionManager(FrontEndPlugin fePlugin) {
plugin = fePlugin;
tool = (FrontEndTool) plugin.getTool();
// initialize the table of tool menu items
runToolActionMap = new HashMap<>(TYPICAL_NUM_TOOLS_IN_TOOLCHEST);
delToolActionMap = new HashMap<>(TYPICAL_NUM_TOOLS_IN_TOOLCHEST);
exportToolActionMap = new HashMap<>(TYPICAL_NUM_TOOLS_IN_TOOLCHEST);
createActions();
}
/**
* Enable actions according to enabled param.
*/
void enableActions(boolean enabled) {
createToolAction.setEnabled(enabled);
enableConnectTools();
enableActions(runToolActionMap, enabled);
enableActions(delToolActionMap, enabled);
enableActions(exportToolActionMap, enabled);
importAction.setEnabled(enabled);
importDefaultToolsAction.setEnabled(enabled);
setToolAssociationsAction.setEnabled(enabled);
}
/**
* Update the tool connection dialog.
*/
void updateConnectionDialog() {
if (toolConnectionDialog != null) {
toolConnectionDialog.updateDisplay();
}
enableConnectTools();
}
/**
* Set the active project; update tool menus.
*/
void setActiveProject(Project activeProject) {
if (toolConnectionDialog != null) {
if (activeProject != null) {
toolConnectionDialog.setToolManager(activeProject.getToolManager());
}
else if (toolConnectionDialog.isVisible()) {
toolConnectionDialog.setVisible(false);
}
}
populateToolMenus(activeProject);
}
private void createActions() {
// create the menu items and listeners
createToolAction = new DockingAction("Create Tool", plugin.getName()) {
@Override
public void actionPerformed(ActionContext context) {
createNewTool();
}
};
createToolAction.setKeyBindingData(
new KeyBindingData(NEWTOOL_ACCELERATOR, ActionEvent.CTRL_MASK));
createToolAction.setMenuBarData(new MenuData(
new String[] { ToolConstants.MENU_TOOLS, MENU_ITEM_CREATE_TOOL }, null, "ATools"));
createToolAction.setEnabled(false);
createToolAction.setHelpLocation(new HelpLocation("Tool", "Create_Tool"));
tool.addAction(createToolAction);
importAction = new DockingAction("Import Tool", plugin.getName()) {
@Override
public void actionPerformed(ActionContext e) {
importTool();
}
};
importAction.setMenuBarData(new MenuData(
new String[] { ToolConstants.MENU_TOOLS, MENU_ITEM_IMPORT_TOOL }, null, "DTools"));
importAction.setHelpLocation(new HelpLocation("Tool", "Import Tool"));
importAction.setEnabled(false);
tool.addAction(importAction);
importDefaultToolsAction = new DockingAction("Import Ghidra Tools", plugin.getName()) {
@Override
public void actionPerformed(ActionContext e) {
addDefaultTools();
}
};
importDefaultToolsAction.setMenuBarData(new MenuData(
new String[] { ToolConstants.MENU_TOOLS, MENU_ITEM_IMPORT_DEFTOOLS }, null, "DTools"));
importDefaultToolsAction.setHelpLocation(new HelpLocation("Tool", "Import Ghidra Tools"));
importDefaultToolsAction.setEnabled(false);
tool.addAction(importDefaultToolsAction);
connectToolsAction = new DockingAction("Connect Tools", plugin.getName()) {
@Override
public void actionPerformed(ActionContext e) {
connectTools();
}
};
connectToolsAction.setMenuBarData(new MenuData(
new String[] { ToolConstants.MENU_TOOLS, MENU_ITEM_CONNECT_TOOLS }, null, "ETools"));
connectToolsAction.setEnabled(false);
tool.addAction(connectToolsAction);
setToolAssociationsAction = new DockingAction("Set Tool Associations", plugin.getName()) {
@Override
public void actionPerformed(ActionContext context) {
showToolAssociationsDialog();
}
};
setToolAssociationsAction.setEnabled(false);
setToolAssociationsAction.setMenuBarData(new MenuData(
new String[] { ToolConstants.MENU_TOOLS, "Set Tool Associations..." }, null, "FTools"));
tool.addAction(setToolAssociationsAction);
setToolAssociationsAction.setHelpLocation(
new HelpLocation("Tool", "Set Tool Associations"));
tool.setMenuGroup(new String[] { ToolConstants.MENU_TOOLS, MENU_ITEM_RUN_TOOL }, "BTools");
tool.setMenuGroup(new String[] { ToolConstants.MENU_TOOLS, MENU_ITEM_DELETE_TOOL },
"CTools");
tool.setMenuGroup(new String[] { ToolConstants.MENU_TOOLS, MENU_ITEM_EXPORT_TOOL },
"DTools");
tool.setMenuGroup(new String[] { ToolConstants.MENU_TOOLS, MENU_ITEM_SET_DEFAULT_TOOL },
"FTools");
// populate the menu items corresponding to the tool templates
// in the project's toolchest
populateToolMenus(plugin.getActiveProject());
}
private void showToolAssociationsDialog() {
SetToolAssociationsDialog dialog = new SetToolAssociationsDialog(tool);
dialog.showDialog();
}
/**
* Get the default tools from the defaultTools location.
*/
private void addDefaultTools() {
ImportGhidraToolsDialog dialog = new ImportGhidraToolsDialog(tool);
dialog.showDialog();
if (dialog.isCancelled()) {
return;
}
List<String> list = dialog.getSelectedList();
for (int i = 0; i < list.size(); i++) {
String filename = list.get(i);
addDefaultTool(filename);
}
}
/**
* Add default tool to the project tool chest.
*
* @param filename tool template filename
* @param toolName tool name
*/
private void addDefaultTool(String filename) {
try {
InputStream is = ResourceManager.getResourceAsStream(filename);
addToolTemplate(is, filename);
}
catch (Exception e) {
Msg.showError(this, null, "Error", "Error loading default tool: " + filename, e);
}
}
/**
* Enable/disable actions in the given map.
*
* @param map
* @param enabled
*/
private void enableActions(Map<String, DockingAction> map, boolean enabled) {
Iterator<String> iter = map.keySet().iterator();
while (iter.hasNext()) {
String name = iter.next();
DockingAction action = map.get(name);
action.setEnabled(enabled);
}
}
/**
* Rebuild the tool menus.
*
* @param activeProject
*/
private void populateToolMenus(Project activeProject) {
removeActions(runToolActionMap);
removeActions(delToolActionMap);
removeActions(exportToolActionMap);
// get the active workspace to host the running tool
if (activeProject == null) {
createPlaceHolderActions();
return;
}
ToolTemplate[] templates = activeProject.getLocalToolChest().getToolTemplates();
for (ToolTemplate template : templates) {
addConfig(template);
}
// if there are no tools in the toolchest, disable menus
if (templates.length == 0) {
createPlaceHolderActions();
}
}
/**
* Remove actions in the given map.
*/
private void removeActions(Map<String, DockingAction> map) {
Iterator<String> iter = map.keySet().iterator();
while (iter.hasNext()) {
String toolName = iter.next();
DockingAction action = map.get(toolName);
tool.removeAction(action);
}
map.clear();
}
/**
* Remove the action named toolName.
*
* @param map map to search for the action
* @param toolName name of the action (happens to be the name of the tool)
*/
private void removeDefaultAction(Map<String, DockingAction> map, String toolName) {
DockingAction action = map.get(toolName);
if (action != null) {
tool.removeAction(action);
map.remove(toolName);
}
}
/**
* Pop up the connect tools dialog.
*/
private void connectTools() {
ToolManager tm = plugin.getActiveProject().getToolManager();
if (toolConnectionDialog == null) {
toolConnectionDialog = new ToolConnectionDialog(tool, tm);
}
else {
toolConnectionDialog.setToolManager(tm);
}
toolConnectionDialog.setVisible(true); // dialog handles the connections
}
/**
* Disable the connect tools if 1 or less than 1 tool is running.
*/
void enableConnectTools() {
Project project = plugin.getActiveProject();
if (project == null) {
connectToolsAction.setEnabled(false);
return;
}
// only enable if project has more than 1 running tool
ToolManager tm = project.getToolManager();
Tool[] runningTools = tm.getRunningTools();
connectToolsAction.setEnabled(runningTools.length > 1);
}
/**
* Create a new tool; pop up the manage plugins dialog.
*/
private void createNewTool() {
// get the active workspace to host the running tool
Workspace ws = plugin.getActiveWorkspace();
// create the running tool
PluginTool runningTool = (PluginTool) ws.createTool();
// whenever we create a new tool, the first thing the
// user will want to do is configure it, so automatically
// bring up the manage plugins dialog as well
// ((PluginTool)runningTool).managePlugins(true);
// TODO replace old manage plugins with new component
runningTool.showConfig(true, true);
}
/**
* ToolConfig was added to the project toolchest
*/
@Override
public void toolTemplateAdded(ToolTemplate tc) {
populateToolMenus(plugin.getActiveProject());
}
/**
* ToolSet was added to the project toolchest
*/
@Override
public void toolSetAdded(ToolSet toolset) {
ToolChest toolChest = plugin.getActiveProject().getLocalToolChest();
toolTemplateAdded(toolChest.getToolTemplate(toolset.getName()));
}
/**
* ToolConfig was removed from the project toolchest
*/
@Override
public void toolRemoved(String toolName) {
removeDefaultAction(runToolActionMap, toolName);
removeDefaultAction(delToolActionMap, toolName);
removeDefaultAction(exportToolActionMap, toolName);
// create default disable menu items if the number of tools
// in toolchest is now zero
if (runToolActionMap.size() == 0) {
createPlaceHolderActions();
}
}
/**
* Create default actions as Place holders in the menu.
*/
private void createPlaceHolderActions() {
String owner = plugin.getName();
DockingAction action = new DockingAction("Run Tool", owner) {
@Override
public void actionPerformed(ActionContext context) {
// no-op; placeholder action
}
};
action.setMenuBarData(new MenuData(
new String[] { ToolConstants.MENU_TOOLS, MENU_ITEM_RUN_TOOL }, null, "BTools"));
action.setHelpLocation(new HelpLocation("Tool", "Run Tool"));
action.setEnabled(false);
tool.addAction(action);
runToolActionMap.put(action.getName(), action);
action = new DockingAction("Delete Tool", owner) {
@Override
public void actionPerformed(ActionContext context) {
// no-op; placeholder action
}
};
action.setMenuBarData(new MenuData(
new String[] { ToolConstants.MENU_TOOLS, MENU_ITEM_DELETE_TOOL }, null, "CTools"));
action.setHelpLocation(new HelpLocation("Tool", "Delete Tool"));
action.setEnabled(false);
tool.addAction(action);
delToolActionMap.put(action.getName(), action);
action = new DockingAction("Export Tool", owner) {
@Override
public void actionPerformed(ActionContext context) {
// no-op; placeholder action
}
};
action.setMenuBarData(new MenuData(
new String[] { ToolConstants.MENU_TOOLS, MENU_ITEM_EXPORT_TOOL }, null, "DTools"));
action.setHelpLocation(new HelpLocation("Tool", "Export Tool"));
action.setEnabled(false);
exportToolActionMap.put(action.getName(), action);
tool.addAction(action);
}
/**
* Pop up a file chooser dialog for the user to find the file to import as a
* tool.
*/
private void importTool() {
if (fileChooser == null) {
fileChooser = new GhidraFileChooser(tool.getToolFrame());
fileChooser.setFileFilter(
new ExtensionFileFilter(new String[] { "tool", "tcd" }, "Tools"));
fileChooser.setTitle("Import Tool");
fileChooser.setApproveButtonText("Import");
String importDir = Preferences.getProperty(Preferences.LAST_TOOL_IMPORT_DIRECTORY);
if (importDir != null) {
fileChooser.setCurrentDirectory(new File(importDir));
}
}
fileChooser.rescanCurrentDirectory();
File selectedFile = fileChooser.getSelectedFile(true);
if (selectedFile == null) {
return;
}
if (!selectedFile.exists()) {
Msg.showError(this, null, "Error",
"Tool " + selectedFile.getName() + " doesn't exist!");
}
Preferences.setProperty(Preferences.LAST_TOOL_IMPORT_DIRECTORY, selectedFile.getParent());
try {
addToolTemplate(new FileInputStream(selectedFile.getAbsolutePath()),
selectedFile.getAbsolutePath());
}
catch (Exception e) {
Msg.showError(this, tool.getToolFrame(), "Error Creating Input Stream",
"Error creating input stream for\n" + selectedFile.getAbsolutePath() + ": " + e, e);
}
}
/**
* Create the Template object and add it to the tool chest.
*/
private void addToolTemplate(InputStream instream, String path) {
try {
SAXBuilder sax = XmlUtilities.createSecureSAXBuilder(false, false);
Element root = sax.build(instream).getRootElement();
ToolTemplate template = new GhidraToolTemplate(root, path);
if (plugin.getActiveProject().getLocalToolChest().addToolTemplate(template)) {
Msg.info(this,
"Successfully added " + template.getName() + " to project tool chest.");
}
else {
Msg.warn(this, "Could not add " + template.getName() + " to project tool chest.");
}
}
catch (Exception e) {
Msg.showError(getClass(), tool.getToolFrame(), "Error Reading Tool",
"Could not read tool: " + e, e);
}
}
/**
* Add a menu for the given tool template.
*/
private void addConfig(ToolTemplate template) {
String toolName = template.getName();
ToolAction runAction = new ToolAction(toolName, "Run_Tool") {
@Override
public void actionPerformed(ActionContext context) {
String name = ((JMenuItem) context.getSourceObject()).getText();
Workspace ws = plugin.getActiveWorkspace();
ToolChest toolChest = plugin.getActiveProject().getLocalToolChest();
ws.runTool(toolChest.getToolTemplate(name));
}
};
runAction.setEnabled(true);
runAction.setMenuBarData(
new MenuData(new String[] { ToolConstants.MENU_TOOLS, MENU_ITEM_RUN_TOOL, toolName },
null, "BTools"));
runAction.setHelpLocation(new HelpLocation("Tool", "Run_Tool"));
runToolActionMap.put(toolName, runAction);
tool.addAction(runAction);
ToolAction deleteAction = new ToolAction(toolName, "Delete_Tool") {
@Override
public void actionPerformed(ActionContext context) {
String name = ((JMenuItem) context.getSourceObject()).getText();
if (!plugin.confirmDelete(name + " from the project tool chest?")) {
return;
}
ToolChest toolChest = plugin.getActiveProject().getLocalToolChest();
toolChest.remove(name);
}
};
deleteAction.setEnabled(true);
deleteAction.setMenuBarData(
new MenuData(new String[] { ToolConstants.MENU_TOOLS, MENU_ITEM_DELETE_TOOL, toolName },
null, "CTools"));
deleteAction.setHelpLocation(new HelpLocation("Tool", "Delete_Tool"));
delToolActionMap.put(toolName, deleteAction);
tool.addAction(deleteAction);
ToolAction exportAction = new ToolAction(toolName, "Export_Tool") {
@Override
public void actionPerformed(ActionContext context) {
String name = ((JMenuItem) context.getSourceObject()).getText();
ToolChest toolChest = plugin.getActiveProject().getLocalToolChest();
plugin.exportToolConfig(toolChest.getToolTemplate(name), "Tool Menu");
}
};
exportAction.setEnabled(true);
exportAction.setMenuBarData(
new MenuData(new String[] { ToolConstants.MENU_TOOLS, MENU_ITEM_EXPORT_TOOL, toolName },
null, "DTools"));
exportAction.setHelpLocation(new HelpLocation("Tool", "Export_Tool"));
exportToolActionMap.put(toolName, exportAction);
tool.addAction(exportAction);
}
/////////////////////////////////////////////////////////////////////
/**
* Subclass to set the help ID for the tool actions whose names are the same
* as the tool name for run, delete, and export.
*
*/
private abstract class ToolAction extends DockingAction {
private ToolAction(String toolName, String helpStr) {
super(toolName, plugin.getName(), false);
setHelpLocation(new HelpLocation("FrontEndPlugin", helpStr));
}
}
}

View file

@ -0,0 +1,738 @@
/* ###
* 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.framework.main;
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.dnd.*;
import java.awt.event.*;
import java.io.IOException;
import java.util.List;
import javax.swing.*;
import org.jdesktop.animation.timing.TimingTarget;
import org.jdesktop.animation.timing.TimingTargetAdapter;
import docking.DockingWindowManager;
import docking.dnd.*;
import docking.help.Help;
import docking.help.HelpService;
import docking.widgets.EmptyBorderButton;
import ghidra.framework.main.datatree.*;
import ghidra.framework.model.*;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.project.tool.ToolIconURL;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.bean.GGlassPane;
import ghidra.util.exception.AssertException;
/**
* Component that is a drop target for a DataTreeTransferable object.
* If the object contains a domain file that is supported by a tool of
* this tool template, then a tool is launched with the data in it.
* <p>
* This button can be used in one of two ways: to launch new instances of an associated tool
* template, or to represent a running tool.
*/
class ToolButton extends EmptyBorderButton implements Draggable, Droppable {
private static final Runnable DUMMY_CALLBACK_RUNNABLE = () -> {
// dummy
};
private DropTarget dropTarget;
private DropTgtAdapter dropTargetAdapter;
private DataFlavor[] acceptableFlavors; // data flavors that this component can support
private DragSource dragSource;
private DragGestureAdapter dragGestureAdapter;
private DragSrcAdapter dragSourceAdapter;
private int dragAction = DnDConstants.ACTION_MOVE;
private FrontEndPlugin plugin;
private ToolTemplate template;
private Tool associatedRunningTool;
private DefaultToolChangeListener toolChangeListener;
private ToolServices toolServices;
/**
* Construct a tool button that does not represent a running tool, using
* the default tool icon.
*/
ToolButton(FrontEndPlugin plugin, ToolTemplate template) {
this(plugin, null, template, template.getIconURL());
setHelpLocation("Run_Tool");
}
/**
* Construct a tool label that represents a running tool, using the
* default RUNNING_TOOL icon.
*/
ToolButton(FrontEndPlugin plugin, Tool tool, ToolTemplate template) {
this(plugin, tool, template, tool.getIconURL());
setHelpLocation("Run_Tool");
}
/**
* Construct a tool label that represents a running tool.
*/
private ToolButton(FrontEndPlugin plugin, Tool tool, ToolTemplate template,
ToolIconURL iconURL) {
super(iconURL.getIcon());
this.plugin = plugin;
associatedRunningTool = tool;
this.template = template;
setUpDragDrop();
// configure the look and feel of the button
setVerticalTextPosition(SwingConstants.BOTTOM);
setHorizontalTextPosition(SwingConstants.CENTER);
setMargin(new Insets(0, 0, 0, 0));
addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (menuShowing()) {
// assume we are on a Mac and do not wish to process the button pressed too
return;
}
activateTool();
}
private boolean menuShowing() {
MenuSelectionManager manager = MenuSelectionManager.defaultManager();
MenuElement[] selectedPath = manager.getSelectedPath();
return selectedPath != null && selectedPath.length != 0;
}
});
toolServices = plugin.getTool().getToolServices();
if (toolServices == null) {
throw new AssertException("ToolButton requires ToolServices to run.");
}
if (!isRunningTool()) {
toolChangeListener = new ToolChangeListener(template);
toolServices.addDefaultToolChangeListener(toolChangeListener);
setIcon(generateIcon());
}
}
/**
* Get the tool tip text.
*/
@Override
public String getToolTipText(MouseEvent event) {
if (associatedRunningTool != null) {
if (associatedRunningTool instanceof PluginTool) {
return ((PluginTool) associatedRunningTool).getToolFrame().getTitle();
}
return associatedRunningTool.getName();
}
return template.getName();
}
public void launchTool(DomainFile domainFile) {
doLaunchTool(new DomainFile[] { domainFile });
}
//==================================================================================================
// Droppable interface
//==================================================================================================
/**
* Set drag feedback according to the OK parameter.
* @param ok true means the drop action is OK
* @param e event that has current state of drag and drop operation
*/
@Override
public void dragUnderFeedback(boolean ok, DropTargetDragEvent e) {
// nothing to do
}
/**
* Return true if is OK to drop the transferable at the location
* specified the event.
* @param e event that has current state of drag and drop operation
*/
@Override
@SuppressWarnings("unchecked")
// our data; cast is OK
public boolean isDropOk(DropTargetDragEvent e) {
DataFlavor[] flavors = e.getCurrentDataFlavors();
try {
for (DataFlavor element : flavors) {
if (element.equals(DataTreeDragNDropHandler.localDomainFileFlavor)) {
Object draggedData = e.getTransferable().getTransferData(
DataTreeDragNDropHandler.localDomainFileFlavor);
return containsSupportedDataTypes((List<DomainFile>) draggedData);
}
else if (element.equals(ToolButtonTransferable.localToolButtonFlavor)) {
Object draggedData = e.getTransferable().getTransferData(
ToolButtonTransferable.localToolButtonFlavor);
ToolButton draggedButton = (ToolButton) draggedData;
if (draggedButton != null) {
if (draggedButton.associatedRunningTool == associatedRunningTool) {
// tool chest -> tool chest is not allowed (both runningTools are null).
// runningTool -> same runningTool is not allowed.
return false;
}
return true;
}
}
else if (element.equals(VersionInfoTransferable.localVersionInfoFlavor)) {
return true;
}
}
}
catch (UnsupportedFlavorException e1) {
// don't care; return false
}
catch (IOException e1) {
// don't care; return false
}
return false;
}
/**
* The given list must contain only valid domain files (i.e., no folders or null items)
* @param nodeList The list of DataTreeNode objects to validate
* @return true if <b>all</b> items in the list are supported
*/
private boolean containsSupportedDataTypes(List<DomainFile> fileList) {
for (DomainFile file : fileList) {
if (!isSupportedDataType(file)) {
return false;
}
}
// if we get here then no invalid items were found, so as long as there is data, it is valid
return fileList.size() > 0;
}
/**
* Revert back to normal if any drag feedback was set.
*/
@Override
public void undoDragUnderFeedback() {
// nothing to do
}
/**
* Add the object to the droppable component. The DropTgtAdapter
* calls this method from its drop() method.
*
* @param obj Transferable object that is to be dropped.
* @param e has current state of drop operation
* @param f represents the opaque concept of a data format as
* would appear on a clipboard, during drag and drop.
*/
@Override
public void add(Object obj, DropTargetDropEvent event, DataFlavor f) {
if (f.equals(DataTreeDragNDropHandler.localDomainFileFlavor)) {
resetButtonAfterDrag(this);
@SuppressWarnings("unchecked")
// we put the data in
List<DomainFile> list = (List<DomainFile>) obj;
DomainFile[] domainFiles = list.toArray(new DomainFile[list.size()]);
openFilesAndOpenToolAsNecessary(domainFiles);
}
else if (f.equals(VersionInfoTransferable.localVersionInfoFlavor)) {
VersionInfo info = (VersionInfo) obj;
PluginTool tool = plugin.getTool();
Project project = tool.getProject();
ProjectData projectData = project.getProjectData();
DomainFile file = projectData.getFile(info.getDomainFilePath());
final DomainObject versionedObj = getVersionedObject(file, info.getVersionNumber());
if (versionedObj != null) {
DomainFile domainFile = versionedObj.getDomainFile();
if (isSupportedDataType(domainFile)) {
resetButtonAfterDrag(this);
openFilesAndOpenToolAsNecessary(new DomainFile[] { domainFile },
() -> versionedObj.release(ToolButton.this));
}
else {
versionedObj.release(ToolButton.this);
}
}
}
else {
plugin.setToolButtonTransferable(null);
ToolButton toolButton = (ToolButton) obj;
resetButtonAfterDrag(toolButton);
addFromToolButton(toolButton);
}
}
private void showFilesNotAcceptedMessage(DomainFile[] domainFiles) {
StringBuffer buffer = new StringBuffer("Tool did not accept files: ");
for (int i = 0; i < domainFiles.length; i++) {
buffer.append(domainFiles[i].getName());
if (i != domainFiles.length - 1) {
buffer.append(", ");
}
}
Msg.showError(this, null, "Error", buffer.toString());
}
private void addFromToolButton(ToolButton toolButton) {
plugin.setToolButtonTransferable(null);
Tool tool = null;
if (associatedRunningTool != null && toolButton.associatedRunningTool != null) {
final Tool t2 = toolButton.associatedRunningTool;
SwingUtilities.invokeLater(() -> connectTools(associatedRunningTool, t2));
return;
}
boolean accepted = false;
if (toolButton.associatedRunningTool == null) {
tool = plugin.getActiveWorkspace().runTool(toolButton.template);
accepted = tool.acceptDomainFiles(associatedRunningTool.getDomainFiles());
final Tool t = tool;
SwingUtilities.invokeLater(() -> connectTools(t, associatedRunningTool));
}
else {
tool = plugin.getActiveWorkspace().runTool(template);
accepted = tool.acceptDomainFiles(toolButton.associatedRunningTool.getDomainFiles());
final Tool t = tool;
final Tool t2 = toolButton.associatedRunningTool;
SwingUtilities.invokeLater(() -> connectTools(t, t2));
}
if (!accepted) {
Msg.error(this, tool.getName() + " did not accept data.");
}
}
/**
* Connect the tools in both directions.
*/
private void connectTools(Tool t1, Tool t2) {
ToolManager tm = plugin.getActiveProject().getToolManager();
ToolConnection tc = tm.getConnection(t1, t2);
connectAll(tc);
tc = tm.getConnection(t2, t1);
connectAll(tc);
}
/**
* Connect all events in the connection object.
*/
private void connectAll(ToolConnection tc) {
String[] events = tc.getEvents();
for (String element : events) {
tc.connect(element);
}
plugin.updateToolConnectionDialog();
Msg.info(this, "Connected all events for " + tc.getProducer().getName() + " to " +
tc.getConsumer().getName());
}
/**
* Return true if the domain file's object class is supported by
* this tool.
*/
private boolean isSupportedDataType(DomainFile file) {
if (file == null) {
return false;
}
Class<?> c = file.getDomainObjectClass();
Class<?>[] classes = (associatedRunningTool != null)
? associatedRunningTool.getSupportedDataTypes() : template.getSupportedDataTypes();
for (Class<?> element : classes) {
if (element.isAssignableFrom(c)) {
return true;
}
}
return false;
}
private DomainObject getVersionedObject(DomainFile file, int versionNumber) {
GetVersionedObjectTask task = new GetVersionedObjectTask(this, file, versionNumber);
plugin.getTool().execute(task, 250);
return task.getVersionedObject();
}
//==================================================================================================
// Draggable interface
//==================================================================================================
/** Fix the button state after dragging/dropping, since this is broken in Java */
private void resetButtonAfterDrag(JButton button) {
// HACK: fix for error where the drag and drop system does not properly reset the state of
// JButton. If you drag away from and onto the same button and release, the button thinks
// it is still pressed and armed (yeah, I know, dragging buttons is weird).
ButtonModel buttonModel = button.getModel();
buttonModel.setArmed(false);
buttonModel.setPressed(false);
buttonModel.setRollover(false);
clearBorder();
}
/**
* Method called when the drag operation exits the drop target
* without dropping.
*/
@Override
public void dragCanceled(DragSourceDropEvent event) {
plugin.setToolButtonTransferable(null);
resetButtonAfterDrag(this);
// Unusual Code Alert!
// When dragging, we do not get mouseReleased() events, which we use to launch tools.
// In this case, the drag was cancelled; if we are over ourselves, then simulate
// the Java-eaten mouseReleased() call
Container parent = getParent();
if (parent == null) {
return;
}
Point point = event.getLocation();
if (point == null) {
return;
}
SwingUtilities.convertPointFromScreen(point, parent);
Component componentUnderMouse =
SwingUtilities.getDeepestComponentAt(parent, point.x, point.y);
if (componentUnderMouse == this) {
handleMouseReleased();
}
}
/**
* Return true if the object at the location in the DragGesture
* event is draggable.
*
* @param e event passed to a DragGestureListener via its
* dragGestureRecognized() method when a particular DragGestureRecognizer
* detects a platform dependent Drag and Drop action initiating
* gesture has occurred on the Component it is tracking.
* @see docking.dnd.DragGestureAdapter
*/
@Override
public boolean isStartDragOk(DragGestureEvent e) {
plugin.setToolButtonTransferable(new ToolButtonTransferable(this));
return true;
}
/**
* Called by the DragGestureAdapter to start the drag.
*/
@Override
public DragSourceListener getDragSourceListener() {
return dragSourceAdapter;
}
/**
* Get the object to transfer.
* @param p location of object to transfer
* @return object to transfer
*/
@Override
public Transferable getTransferable(Point p) {
return plugin.getToolButtonTransferable();
}
/**
* Do the move operation; called when the drag and drop operation
* completes.
* @see ghidra.util.bean.dnd.DragSourceAdapter#dragDropEnd
*/
@Override
public void move() {
resetButtonAfterDrag(this);
}
/**
* Get the drag actions supported by this drag source:
* <UL>
* <li>DnDConstants.ACTION_MOVE
* <li>DnDConstants.ACTION_COPY
* <li>DnDConstants.ACTION_COPY_OR_MOVE
* </li>
*
* @return the drag actions
*/
@Override
public int getDragAction() {
return dragAction;
}
//==================================================================================================
// Package methods
//==================================================================================================
/**
* Set the tool template for this button.
*/
void setToolTemplate(ToolTemplate template, Icon icon) {
this.template = template;
setIcon(icon);
}
ToolTemplate getToolTemplate() {
return template;
}
/**
* Return whether this tool button represents a running tool.
*/
boolean isRunningTool() {
return associatedRunningTool != null;
}
/**
* Close the running tool.
*/
void closeTool() {
associatedRunningTool.close();
}
Tool getRunningTool() {
return associatedRunningTool;
}
void dispose() {
toolServices.removeDefaultToolChangeListener(toolChangeListener);
plugin = null;
template = null;
associatedRunningTool = null;
dropTarget = null;
dropTargetAdapter = null;
acceptableFlavors = null;
dragSource = null;
dragGestureAdapter = null;
}
//==================================================================================================
// Private methods(non-drag/drop)
//==================================================================================================
private void activateTool() {
if (associatedRunningTool == null) {
// this is a button on the tool bar, so launch a new tool
doLaunchTool(null);
}
else {
associatedRunningTool.toFront();
}
}
private void doLaunchTool(DomainFile[] domainFiles) {
openFilesAndOpenToolAsNecessary(domainFiles);
}
private void openFilesAndOpenToolAsNecessary(final DomainFile[] domainFiles) {
openFilesAndOpenToolAsNecessary(domainFiles, DUMMY_CALLBACK_RUNNABLE);
}
private void openFilesAndOpenToolAsNecessary(final DomainFile[] domainFiles,
Runnable finishedCallback) {
if (associatedRunningTool != null) {
// this button has a running tool, no need to open one
openDomainFiles(associatedRunningTool, domainFiles);
return;
}
DockingWindowManager manager = DockingWindowManager.getInstance(this);
final JFrame toolFrame = manager.getRootFrame();
Component glassPane = toolFrame.getGlassPane();
if (!(glassPane instanceof GGlassPane)) {
// We cannot perform the tool launching animation, so just do the old fashion way
Msg.debug(this, "Found root frame without a GhidraGlassPane registered!");
// try to recover without animation
Tool newTool = plugin.getActiveWorkspace().runTool(template);
openDomainFiles(newTool, domainFiles);
finishedCallback.run();
return;
}
launchToolWithAnimationAndOpenFiles(domainFiles, toolFrame, (GGlassPane) glassPane,
finishedCallback);
}
private void launchToolWithAnimationAndOpenFiles(final DomainFile[] domainFiles,
final JFrame toolFrame, final GGlassPane toolGlassPane,
final Runnable finishedCallback) {
Icon icon = getIcon();
Point buttonLocation = getLocation();
Insets insets = getInsets();
buttonLocation.x += insets.left;
buttonLocation.y += insets.top;
buttonLocation =
SwingUtilities.convertPoint(getParent(), buttonLocation, toolFrame.getRootPane());
// start the animation over top of this button, so it appears as though the tool is
// launching from that button
Rectangle startBounds =
new Rectangle(buttonLocation, new Dimension(icon.getIconWidth(), icon.getIconHeight()));
Dimension frameSize = toolFrame.getSize();
// the final point over which the image will be painted
Rectangle endBounds = new Rectangle(new Point(0, 0), frameSize);
// Create our animation code: a zooming effect and an effect to move where the image is
// painted. These effects are independent code-wise, but work together in that the
// mover will set the location and size, and the zoomer will will paint the image with
// a transparency and a zoom level, which is affected by the movers bounds changing.
Image image = ZoomedImagePainter.createIconImage(icon);
final ZoomedImagePainter painter = new ZoomedImagePainter(startBounds, image);
final ZoomImageRunner zoomRunner = new ZoomImageRunner(toolGlassPane, painter, icon);
MoveImageRunner moveRunner =
new MoveImageRunner(toolGlassPane, startBounds, endBounds, painter);
// a callback that lets us know when to open the given files and restore the state of
// the GhidraGlassPane
TimingTarget finishedTarget = new TimingTargetAdapter() {
@Override
public void end() {
toolGlassPane.removePainter(painter);
try {
// cleanup any residual painting effects
toolGlassPane.paintImmediately(toolGlassPane.getBounds());
Tool newTool = plugin.getActiveWorkspace().runTool(template);
openDomainFiles(newTool, domainFiles);
}
finally {
// always restore the cursor
GGlassPane.setAllGlassPanesBusy(false);
finishedCallback.run();
}
}
};
zoomRunner.addTimingTargetListener(finishedTarget);
// change to a busy cursor and block input
GGlassPane.setAllGlassPanesBusy(true);
moveRunner.run();
zoomRunner.run();
}
private void openDomainFiles(Tool tool, DomainFile[] domainFiles) {
if (domainFiles == null) {
return;
}
boolean accepted = tool.acceptDomainFiles(domainFiles);
if (!accepted) {
showFilesNotAcceptedMessage(domainFiles);
}
}
/**
* Set up the objects so we can be a drag and drop site.
*/
private void setUpDragDrop() {
acceptableFlavors = new DataFlavor[] { DataTreeDragNDropHandler.localDomainFileFlavor,
ToolButtonTransferable.localToolButtonFlavor,
VersionInfoTransferable.localVersionInfoFlavor };
// set up drop stuff
dropTargetAdapter =
new ToolButtonDropTgtAdapter(DnDConstants.ACTION_COPY_OR_MOVE, acceptableFlavors);
dropTarget =
new DropTarget(this, DnDConstants.ACTION_COPY_OR_MOVE, dropTargetAdapter, true);
dropTarget.setActive(true);
// set up drag stuff
dragSource = DragSource.getDefaultDragSource();
dragGestureAdapter = new DragGestureAdapter(this);
dragSourceAdapter = new DragSrcAdapter(this);
dragSource.createDefaultDragGestureRecognizer(this, dragAction, dragGestureAdapter);
}
private Icon generateIcon() {
Icon icon = template.getIcon();
if (isRunningTool()) {
return icon;
}
return icon;
}
private void setHelpLocation(String anchorTag) {
HelpService help = Help.getHelpService();
help.registerHelp(this, new HelpLocation("Tool", anchorTag));
}
private void handleMouseReleased() {
activateTool();
}
//==================================================================================================
// Inner Classes
//==================================================================================================
private class ToolChangeListener implements DefaultToolChangeListener {
private final ToolTemplate toolTemplate;
public ToolChangeListener(ToolTemplate toolTemplate) {
this.toolTemplate = toolTemplate;
}
@Override
public void defaultToolChanged(String oldName, String newName) {
String myName = toolTemplate.getName();
if (myName.equals(oldName) || myName.equals(newName)) {
setIcon(generateIcon());
}
}
}
private class ToolButtonDropTgtAdapter extends DropTgtAdapter {
private boolean draggingOverValidDropTarget = false;
public ToolButtonDropTgtAdapter(int acceptableDropActions,
DataFlavor[] acceptableDropFlavors) {
super(ToolButton.this, acceptableDropActions, acceptableDropFlavors);
}
@Override
public void dragEnter(DropTargetDragEvent e) {
super.dragEnter(e);
if (super.isDropOk(e)) {
ToolButton.this.setBorder(RAISED_BUTTON_BORDER);
draggingOverValidDropTarget = true;
}
}
@Override
public void dragExit(DropTargetEvent e) {
super.dragExit(e);
if (draggingOverValidDropTarget) {
clearBorder();
draggingOverValidDropTarget = false;
}
}
}
}

View file

@ -0,0 +1,107 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.main;
import ghidra.util.Msg;
import java.awt.datatransfer.*;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import docking.dnd.GenericDataFlavor;
/**
* Defines data that is available for drag/drop and clipboard transfers.
* The data is a ToolButton object.
*/
public class ToolButtonTransferable implements Transferable, ClipboardOwner {
public static DataFlavor localToolButtonFlavor = createLocalToolButtonFlavor();
// create a data flavor that is a tool button
private static DataFlavor createLocalToolButtonFlavor() {
try {
return new GenericDataFlavor(
DataFlavor.javaJVMLocalObjectMimeType+
"; class=ghidra.framework.main.ToolButton",
"Local tool button object");
}catch (Exception e) {
Msg.showError(ToolButtonTransferable.class, null, null, null, e);
}
return null;
}
private static DataFlavor []flavors=
{localToolButtonFlavor};
private static List flavorList = Arrays.asList(flavors);
private ToolButton button;
/**
* Constructor
*/
ToolButtonTransferable(ToolButton button) {
this.button = button;
}
/**
* Return all data flavors that this class supports.
*/
public synchronized DataFlavor []getTransferDataFlavors() {
return flavors;
}
/**
* Return whether the specifed data flavor is supported.
*/
public boolean isDataFlavorSupported(DataFlavor f) {
return flavorList.contains(f);
}
/**
* Return the transfer data with the given data flavor.
*/
public synchronized Object getTransferData(DataFlavor f)
throws UnsupportedFlavorException, IOException {
if (f.equals(localToolButtonFlavor)) {
return button;
}
throw new UnsupportedFlavorException(f);
}
/**
* Get the string representation for this transferable.
*/
@Override
public String toString() {
return "ToolButtonTransferable";
}
/**
* ClipboardOwner interface method.
*/
public void lostOwnership(Clipboard clipboard, Transferable contents) {
}
/**
* Clear the tool button that is being transferred.
*/
void clearTransferData() {
button = null;
}
}

View file

@ -0,0 +1,185 @@
/* ###
* 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.framework.main;
import java.beans.PropertyChangeEvent;
import javax.swing.JButton;
import javax.swing.JPanel;
import docking.DialogComponentProvider;
import ghidra.app.util.GenericHelpTopics;
import ghidra.framework.model.*;
import ghidra.util.HelpLocation;
/**
* Dialog to show existing connections between tools and
* to connect tools.
*/
class ToolConnectionDialog extends DialogComponentProvider implements WorkspaceChangeListener {
private ToolManager toolManager;
private ToolConnectionPanel panel;
private FrontEndTool frontEndTool;
private final static String CONNECTALL = "Connect All";
private final static String DISCONNECTALL = "Disconnect All";
private JButton connectAllButton;
private JButton disconnectAllButton;
/**
* Constructor
*/
ToolConnectionDialog(FrontEndTool tool, ToolManager toolManager) {
super("Connect Tools", false);
this.frontEndTool = tool;
this.toolManager = toolManager;
setHelpLocation(new HelpLocation(GenericHelpTopics.FRONT_END, "Connect_Tools"));
addWorkPanel(buildMainPanel());
connectAllButton = new JButton(CONNECTALL);
connectAllButton.addActionListener(ev -> connectCallback());
addButton(connectAllButton);
disconnectAllButton = new JButton(DISCONNECTALL);
disconnectAllButton.addActionListener(ev -> disconnectCallback());
addButton(disconnectAllButton);
addOKButton();
toolManager.addWorkspaceChangeListener(this);
}
/**
* Set the dialog to be visible according to the v param.
*/
void setVisible(boolean v) {
if (v) {
frontEndTool.showDialog(this);
panel.showData();
setStatusText("Please select an Event Producer");
setConnectAllEnabled(false);
setDisconnectAllEnabled(false);
}
else {
close();
toolManager.removeWorkspaceChangeListener(this);
panel.clear();
}
}
// WorkspaceChangedListener
/**
* Tool was added to the given workspace.
*/
@Override
public void toolAdded(Workspace ws, Tool tool) {
panel.toolAdded(tool);
}
/**
* Tool was removed from the given workspace.
*/
@Override
public void toolRemoved(Workspace ws, Tool tool) {
panel.toolRemoved(tool);
}
/*
* (non-Javadoc)
* @see ghidra.util.bean.GhidraDialog#okCallback()
*/
@Override
protected void okCallback() {
setVisible(false);
}
@Override
public void workspaceAdded(Workspace ws) {
}
@Override
public void workspaceRemoved(Workspace ws) {
}
@Override
public void workspaceSetActive(Workspace ws) {
}
/**
* Property change on the workspace.
*/
@Override
public void propertyChange(PropertyChangeEvent event) {
Object eventSource = event.getSource();
if (eventSource instanceof Tool) {
// tool name might have changed
updateDisplay();
}
}
///////////////////////////////////////////////////////////////////
/**
* Update the tool manager object.
*/
void setToolManager(ToolManager tm) {
toolManager.removeWorkspaceChangeListener(this);
toolManager = tm;
toolManager.addWorkspaceChangeListener(this);
panel.setToolManager(toolManager);
}
/**
* Update the display because tools have been added or removed;
* restore selection if possible.
*/
void updateDisplay() {
panel.updateDisplay();
}
void setConnectAllEnabled(boolean enabled) {
connectAllButton.setEnabled(enabled);
}
void setDisconnectAllEnabled(boolean enabled) {
disconnectAllButton.setEnabled(enabled);
}
/**
* Return the main panel for this dialog.
* The contents of this panel will be created in the constructor.
*
* @return JPanel
*/
protected JPanel buildMainPanel() {
panel = new ToolConnectionPanel(this, toolManager);
return panel;
}
/**
* "Connect All" button
*/
protected void connectCallback() {
panel.connectAll(true);
}
/**
* "Disconnect All" button
*/
protected void disconnectCallback() {
panel.connectAll(false);
}
}

View file

@ -0,0 +1,514 @@
/* ###
* 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.framework.main;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Arrays;
import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import ghidra.framework.model.*;
import ghidra.util.Msg;
/**
* Adds the listeners for the connection panel that shows 3 lists: one
* for producers of event, one for consumers of events, and one
* that shows events that are an intersection of the consumed and
* produced events.
*/
class ToolConnectionPanel extends JPanel implements ListSelectionListener {
private ToolConnectionDialog toolDialog;
private ToolManager toolManager;
private JList<Tool> consumerList; // list of receiver tools
private JList<Tool> producerList; // list of source (of events)
private JList<JCheckBox> eventList; // names of events generated by source
private DefaultListModel<Tool> producerModel;
private DefaultListModel<Tool> consumerModel;
private JCheckBox[] checkboxes;
private String[] eventNames;
private final static String msgSource = "Tool Connection";
/**
* Constructor
* @param myTool plugin tool associated with this connect panel
*/
ToolConnectionPanel(ToolConnectionDialog toolDialog, ToolManager toolManager) {
super();
this.toolDialog = toolDialog;
this.toolManager = toolManager;
initialize();
}
/**
* ListSelectionListener method to process selection events.
*/
@Override
public void valueChanged(ListSelectionEvent e) {
toolDialog.setStatusText("");
toolDialog.setConnectAllEnabled(false);
toolDialog.setDisconnectAllEnabled(false);
if (e.getValueIsAdjusting()) {
return;
}
processSelection();
}
/**
* Set the tool manager; need to do this if another project is opened.
*/
void setToolManager(ToolManager toolManager) {
this.toolManager = toolManager;
updateDisplay();
}
/**
* Update the list of tools. If any selections were made,
* restore them. This method is called because tools were either
* added or removed.
*/
void updateDisplay() {
Tool producer = producerList.getSelectedValue();
Tool consumer = consumerList.getSelectedValue();
showData();
// restore the selection
if (producer != null && consumer != null) {
int index = producerModel.indexOf(producer);
if (index >= 0) {
ListSelectionModel sm = producerList.getSelectionModel();
// add to selection
sm.addSelectionInterval(index, index);
}
index = consumerModel.indexOf(consumer);
if (index >= 0) {
ListSelectionModel sm = consumerList.getSelectionModel();
// add to selection
sm.addSelectionInterval(index, index);
}
}
validate();
}
/**
* Populate the lists according to the type of panel.
*/
void showData() {
// clear the event list
eventList.setModel(new DefaultListModel<>());
clearSelection();
populateConsumerList();
populateProducerList();
}
void clear() {
this.consumerModel.clear();
this.producerModel.clear();
}
/**
* Tool was added to the workspace; update the display.
* @param tool tool added
*/
void toolAdded(Tool tool) {
String[] consumedEvents = tool.getConsumedToolEventNames();
String[] producedEvents = tool.getToolEventNames();
if (consumedEvents.length > 0) {
consumerModel.addElement(tool);
}
if (producedEvents.length > 0) {
producerModel.addElement(tool);
}
validate();
}
/**
* Tool was removed from a workspace; update the display.
* @param tool tool removed
*/
void toolRemoved(Tool tool) {
int index = producerModel.indexOf(tool);
if (index >= 0) {
producerModel.remove(index);
}
index = consumerModel.indexOf(tool);
if (index >= 0) {
consumerModel.remove(index);
}
processSelection();
validate();
}
/**
* Interconnect two producers and consumers
*/
void connectAll(boolean connect) {
Tool producer = producerList.getSelectedValue();
Tool consumer = consumerList.getSelectedValue();
// clear the event list
eventList.setModel(new DefaultListModel<>());
if (consumer == null || producer == null) {
return;
}
if (producer.getName().equals(consumer.getName())) {
return;
}
ToolConnection tc = toolManager.getConnection(producer, consumer);
// connect all producer to consumer
eventNames = tc.getEvents();
for (String eventName : eventNames) {
doConnect(producer, consumer, eventName, connect);
}
// connect all consumer to producer
tc = toolManager.getConnection(consumer, producer);
eventNames = tc.getEvents();
for (String eventName : eventNames) {
doConnect(consumer, producer, eventName, connect);
}
updateDisplay();
}
///////////////////////////////////////////////////////////////
// *** private methods ***
///////////////////////////////////////////////////////////////
private void initialize() {
JPanel panel = createListPanel();
// add sub-panels to the dialog
setLayout(new BorderLayout(10, 10));
add(panel, BorderLayout.CENTER);
producerList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
consumerList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
eventList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
// add selection listeners for the producer and consumer lists
ListSelectionModel sm = producerList.getSelectionModel();
sm.addListSelectionListener(this);
sm = consumerList.getSelectionModel();
sm.addListSelectionListener(this);
eventList.addMouseListener(new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
processMouseClicked(e);
}
});
eventList.setCellRenderer(new DataCellRenderer());
producerList.setCellRenderer(new ToolListCellRenderer());
consumerList.setCellRenderer(new ToolListCellRenderer());
producerModel = (DefaultListModel<Tool>) producerList.getModel();
consumerModel = (DefaultListModel<Tool>) consumerList.getModel();
}
private void processMouseClicked(MouseEvent e) {
if (e.getClickCount() == 1) {
JList<?> list = (JList<?>) e.getSource();
int index = list.locationToIndex(e.getPoint());
if (index < 0) {
return;
}
if (!checkboxes[index].isEnabled()) {
return;
}
boolean selected = checkboxes[index].isSelected();
checkboxes[index].setSelected(!selected);
refreshList(checkboxes);
Tool producer = producerList.getSelectedValue();
Tool consumer = consumerList.getSelectedValue();
doConnect(producer, consumer, eventNames[index], !selected);
int connectedCount = 0;
for (JCheckBox checkboxe : checkboxes) {
if (checkboxe.isSelected()) {
connectedCount++;
}
}
updateButtonEnablement(connectedCount);
}
}
private void doConnect(Tool producer, Tool consumer, String eventName, boolean connect) {
ToolConnection tc = toolManager.getConnection(producer, consumer);
if (tc.isConnected(eventName) == connect) {
// if already connected
return;
}
if (connect) {
tc.connect(eventName);
Msg.info(this, msgSource + ": Connected consumer " + consumer.getName() +
" to producer " + producer.getName() + " for event " + eventName);
}
else {
tc.disconnect(eventName);
Msg.info(this, msgSource + ": Disconnected consumer " + consumer.getName() +
" from producer " + producer.getName() + " for event " + eventName);
}
}
private void populateConsumerList() {
consumerModel.removeAllElements();
Tool[] tools = toolManager.getConsumerTools();
Arrays.sort(tools, (t1, t2) -> {
return t1.getName().compareTo(t2.getName());
});
for (Tool tool : tools) {
consumerModel.addElement(tool);
}
if (tools.length == 0) {
Msg.info(this, msgSource + ": No Tool consumes any events.");
}
}
private void populateProducerList() {
producerModel.removeAllElements();
Tool[] tools = toolManager.getProducerTools();
Arrays.sort(tools, (t1, t2) -> {
return t1.getName().compareTo(t2.getName());
});
for (Tool tool : tools) {
producerModel.addElement(tool);
}
if (tools.length == 0) {
Msg.info(this, msgSource + ": No Tool generates events.");
}
}
private void processSelection() {
// clear the event list
eventList.setModel(new DefaultListModel<>());
Tool producer = producerList.getSelectedValue();
if (producer == null) {
toolDialog.setStatusText("Please select an Event Producer");
return;
}
Tool consumer = consumerList.getSelectedValue();
if (consumer == null) {
toolDialog.setStatusText("Please select an Event Consumer");
return;
}
if (producer.getName().equals(consumer.getName())) {
toolDialog.setStatusText("The selected Event Producer Consumer must be different");
return;
}
ToolConnection tc = toolManager.getConnection(producer, consumer);
eventNames = tc.getEvents();
checkboxes = new JCheckBox[eventNames.length];
int connectedCount = 0;
for (int i = 0; i < checkboxes.length; i++) {
checkboxes[i] = new JCheckBox(eventNames[i]);
checkboxes[i].setBackground(Color.white);
boolean isConnected = tc.isConnected(eventNames[i]);
checkboxes[i].setSelected(isConnected);
checkboxes[i].setEnabled(true);
if (isConnected) {
connectedCount++;
}
}
refreshList(checkboxes);
updateButtonEnablement(connectedCount);
toolDialog.setStatusText("Please select on the events to be connected or disconnected");
}
private void updateButtonEnablement(int connectedCount) {
toolDialog.setConnectAllEnabled(connectedCount < eventNames.length);
toolDialog.setDisconnectAllEnabled(connectedCount > 0);
}
/**
* Clear selection on all lists.
*/
private void clearSelection() {
consumerList.clearSelection();
producerList.clearSelection();
eventList.clearSelection();
}
/**
* replaces the list contents with the new list.
*/
private void refreshList(JCheckBox[] dataList) {
eventList.setListData(dataList);
eventList.clearSelection();
}
private JPanel createListPanel() {
consumerList = new JList<>(new DefaultListModel<>());
consumerList.setName("Consumers");
JScrollPane consumerListScrollPane = new JScrollPane(consumerList);
producerList = new JList<>(new DefaultListModel<>());
producerList.setName("Producers");
JScrollPane producerListScrollPane = new JScrollPane(producerList);
eventList = new JList<>(new DefaultListModel<>());
eventList.setName("Events");
JScrollPane eventListScrollPane = new JScrollPane(eventList);
Dimension minimumSize = new Dimension(150, 150);
consumerListScrollPane.setMinimumSize(minimumSize);
consumerListScrollPane.setPreferredSize(minimumSize);
producerListScrollPane.setMinimumSize(minimumSize);
producerListScrollPane.setPreferredSize(minimumSize);
eventListScrollPane.setMinimumSize(minimumSize);
eventListScrollPane.setPreferredSize(minimumSize);
JPanel panel = new JPanel();
GridBagLayout gbl = new GridBagLayout();
panel.setLayout(gbl);
JComponent[] row1 = null;
JComponent[] row2 = null;
JLabel producerLabel = new JLabel("Event Producer:");
JLabel consumerLabel = new JLabel("Event Consumer:");
JLabel eventLabel = new JLabel("Event Names:");
JComponent[] c1 = { producerLabel, consumerLabel, eventLabel };
JComponent[] c2 = { producerListScrollPane, consumerListScrollPane, eventListScrollPane };
row1 = c1;
row2 = c2;
GridBagConstraints gbc = null;
for (int i = 0; i < row1.length; i++) {
gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.NORTH;
gbc.fill = GridBagConstraints.BOTH;
gbc.gridx = i;
gbc.gridy = 0;
gbc.insets.top = 10;
gbc.insets.left = 10;
gbc.insets.right = 10;
gbc.weightx = 1.0;
gbl.setConstraints(row1[i], gbc);
panel.add(row1[i]);
}
for (int i = 0; i < row2.length; i++) {
gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.NORTH;
gbc.fill = GridBagConstraints.BOTH;
gbc.gridx = i;
gbc.gridy = 1;
gbc.insets.top = 5;
gbc.insets.left = 10;
gbc.insets.right = 10;
gbc.weightx = 1.0;
gbc.weighty = 1.0;
gbl.setConstraints(row2[i], gbc);
panel.add(row2[i]);
}
return (panel);
}
private class DataCellRenderer implements ListCellRenderer<JCheckBox> {
@Override
public Component getListCellRendererComponent(JList<? extends JCheckBox> list,
JCheckBox value, int index, boolean isSelected, boolean cellHasFocus) {
if (index == -1) {
int selected = list.getSelectedIndex();
if (selected == -1) {
return (null);
}
index = selected;
}
return (checkboxes[index]);
}
}
private class ToolListCellRenderer extends JLabel implements ListCellRenderer<Tool> {
ToolListCellRenderer() {
setOpaque(true);
setVerticalAlignment(CENTER);
}
@Override
public Component getListCellRendererComponent(JList<? extends Tool> list, Tool value,
int index, boolean isSelected, boolean cellHasFocus) {
Tool tool = null;
if (list == consumerList) {
tool = consumerModel.elementAt(index);
}
else {
tool = producerModel.elementAt(index);
}
if (isSelected) {
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
}
else {
setBackground(list.getBackground());
setForeground(list.getForeground());
}
setText(tool.getName());
return this;
}
}
}

View file

@ -0,0 +1,261 @@
/* ###
* 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.framework.main;
import java.util.ArrayList;
import java.util.List;
import docking.widgets.table.*;
import ghidra.docking.settings.Settings;
import ghidra.framework.plugintool.ServiceProvider;
import ghidra.framework.remote.User;
/**
* Table model for managing a list of Ghidra users associated with a project, and
* their access permissions. The permissions (read-only, read/write, admin) are rendered
* as checkboxes that can be selected by users, provided they have admin access.
*
*/
class UserAccessTableModel extends GDynamicColumnTableModel<User, List<User>> {
private List<User> users;
private String currentUser;
public static final int USERS_COL = 0;
public static final int READ_ONLY_COL = 1;
public static final int READ_WRITE_COL = 2;
public static final int ADMIN_COL = 3;
/**
* Constructs a new table model.
*
* @param currentUser the name of the current user
* @param userList list of all users associated with the current project
* @param serviceProvider the service provider
*/
public UserAccessTableModel(String currentUser, List<User> userList,
ServiceProvider serviceProvider) {
super(serviceProvider);
this.currentUser = currentUser;
this.users = new ArrayList<>(userList);
}
@Override
public String getName() {
return "User Access";
}
/**
* Invoked when the user has changed one of the access rights checkboxes. When this
* happens we have to update the associated User data.
*/
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
// Shouldn't happen, but do a sanity check.
if (rowIndex < 0 || rowIndex >= users.size()) {
return;
}
User user = users.get(rowIndex);
switch (columnIndex) {
case READ_ONLY_COL:
user = new User(user.getName(),
((Boolean) aValue).booleanValue() ? User.READ_ONLY : User.WRITE);
break;
case READ_WRITE_COL:
user = new User(user.getName(),
((Boolean) aValue).booleanValue() ? User.WRITE : User.READ_ONLY);
break;
case ADMIN_COL:
user = new User(user.getName(),
((Boolean) aValue).booleanValue() ? User.ADMIN : User.WRITE);
break;
}
users.remove(rowIndex);
users.add(rowIndex, user);
refresh();
}
/**
* The permissions columns in the table should be editable as long as the user
* is an admin and is not trying to adjust his/her own permissions.
*/
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
// If the user is not admin, nothing is editable.
if (!getCurrentUser().isAdmin()) {
return false;
}
switch (columnIndex) {
case USERS_COL:
return false;
case READ_ONLY_COL:
case READ_WRITE_COL:
case ADMIN_COL:
User rowUser = users.get(rowIndex);
User currentUser = getCurrentUser();
if (currentUser != null) {
return currentUser.isAdmin() && !rowUser.equals(currentUser);
}
}
return false;
}
@Override
protected TableColumnDescriptor<User> createTableColumnDescriptor() {
TableColumnDescriptor<User> descriptor = new TableColumnDescriptor<>();
descriptor.addVisibleColumn(new UserColumn());
descriptor.addVisibleColumn(new ReadOnlyColumn());
descriptor.addVisibleColumn(new ReadWriteColumn());
descriptor.addVisibleColumn(new AdminColumn());
return descriptor;
}
@Override
public List<User> getDataSource() {
return users;
}
/**
* Replaces the contents of this model with a given list of users.
*
* @param users the user list
*/
void setUserList(List<User> users) {
this.users = users;
refresh();
}
/**
* Remove a list of users from the table.
*
* @param list list of User objects
*/
void removeUsers(ArrayList<User> users) {
this.users.removeAll(users);
refresh();
}
/**
* Add a list of users to the table.
*
* @param users list of User objects
*/
void addUsers(ArrayList<User> users) {
this.users.addAll(users);
refresh();
}
/**
* Returns the {@link User} currently using the dialog.
*
* @return the current user or null if not found
*/
private User getCurrentUser() {
for (User user : users) {
if (user.getName().equals(currentUser)) {
return user;
}
}
return null;
}
/**
* Table column for displaying the user name.
*/
class UserColumn extends AbstractDynamicTableColumn<User, String, List<User>> {
@Override
public String getColumnName() {
return "User";
}
@Override
public String getValue(User rowObject, Settings settings, List<User> data,
ServiceProvider serviceProvider) throws IllegalArgumentException {
return rowObject.getName();
}
}
/**
* Table column for displaying the users read only status.
*/
class ReadOnlyColumn extends AbstractDynamicTableColumn<User, Boolean, List<User>> {
@Override
public String getColumnName() {
return "Read Only";
}
@Override
public Boolean getValue(User rowObject, Settings settings, List<User> data,
ServiceProvider serviceProvider) throws IllegalArgumentException {
return rowObject.isReadOnly();
}
}
/**
* Table column for displaying the users read/write status.
*/
class ReadWriteColumn extends AbstractDynamicTableColumn<User, Boolean, List<User>> {
@Override
public String getColumnName() {
return "Read/Write";
}
@Override
public Boolean getValue(User rowObject, Settings settings, List<User> data,
ServiceProvider serviceProvider) throws IllegalArgumentException {
return rowObject.hasWritePermission() && !rowObject.isAdmin();
}
}
/**
* Table column for displaying if the user has admin status.
*/
class AdminColumn extends AbstractDynamicTableColumn<User, Boolean, List<User>> {
@Override
public String getColumnName() {
return "Admin";
}
@Override
public Boolean getValue(User rowObject, Settings settings, List<User> data,
ServiceProvider serviceProvider) throws IllegalArgumentException {
return rowObject.isAdmin();
}
}
@Override
public List<User> getModelData() {
return users;
}
}

View file

@ -0,0 +1,114 @@
/* ###
* 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.framework.main;
import java.awt.*;
import java.io.InputStream;
import javax.swing.*;
import javax.swing.text.html.HTMLEditorKit;
import docking.DialogComponentProvider;
import docking.DockingWindowManager;
import ghidra.util.HTMLUtilities;
import ghidra.util.Msg;
import ghidra.util.layout.VerticalLayout;
import resources.ResourceManager;
import utilities.util.FileUtilities;
public class UserAgreementDialog extends DialogComponentProvider {
private static final String USER_AGREEMENT_FILENAME = "UserAgreement.html";
private boolean showAgreementChoices;
private boolean exitOnCancel;
public UserAgreementDialog(boolean showAgreementChoices, boolean exitOnCancel) {
super("", true, false, true, false);
this.showAgreementChoices = showAgreementChoices;
this.exitOnCancel = exitOnCancel;
addWorkPanel(buildWorkPanel());
addOKButton();
if (showAgreementChoices) {
setOkButtonText("I Agree");
addCancelButton();
setCancelButtonText("I Don't Agree");
}
else {
setOkButtonText("OK");
}
setPreferredSize(1000, 500);
}
private JComponent buildWorkPanel() {
Font font = new Font("Default", Font.PLAIN, 16);
JPanel panel = new JPanel(new BorderLayout());
JLabel label = new JLabel("Ghidra User Agreement", SwingConstants.CENTER);
label.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
label.setFont(font.deriveFont(Font.ITALIC, 22f));
panel.add(label, BorderLayout.NORTH);
panel.setBorder(BorderFactory.createEmptyBorder(10, 40, 40, 40));
JEditorPane editorPane = new JEditorPane();
editorPane.setEditorKit(new HTMLEditorKit());
editorPane.setMargin(new Insets(10, 10, 10, 10));
editorPane.setText(getUserAgreementText());
editorPane.setCaretPosition(0);
editorPane.setEditable(false);
JScrollPane scrollPane = new JScrollPane(editorPane);
panel.add(scrollPane, BorderLayout.CENTER);
JPanel checkBoxPanel = new JPanel(new VerticalLayout(10));
checkBoxPanel.setBorder(BorderFactory.createEmptyBorder(5, 10, 0, 10));
panel.add(checkBoxPanel, BorderLayout.SOUTH);
return panel;
}
private String getUserAgreementText() {
try (InputStream in = ResourceManager.getResourceAsStream(USER_AGREEMENT_FILENAME)) {
String text = FileUtilities.getText(in);
if (!HTMLUtilities.isHTML(text)) {
// our labels to not render correctly when not using HTML
text = HTMLUtilities.toHTML(text);
}
text = text.replace('\n', ' ');
return text;
}
catch (Exception e) {
// use default splash screen info
Msg.debug(this, "Unable to read user agreement text from: " + USER_AGREEMENT_FILENAME);
return USER_AGREEMENT_FILENAME + " file is missing!";
}
}
@Override
protected void okCallback() {
close();
}
@Override
protected void cancelCallback() {
if (exitOnCancel) {
System.exit(0);
}
close();
}
public static void main(String[] args) {
UserAgreementDialog dialog = new UserAgreementDialog(true, true);
DockingWindowManager.showDialog(null, dialog);
}
}

View file

@ -0,0 +1,40 @@
/* ###
* IP: GHIDRA
* REVIEWED: YES
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package ghidra.framework.main;
import java.net.URL;
import docking.action.DockingAction;
class ViewInfo {
private DockingAction action;
private URL view;
ViewInfo(DockingAction action, URL view) {
this.action = action;
this.view = view;
}
DockingAction getAction() {
return action;
}
URL getView() {
return view;
}
}

View file

@ -0,0 +1,92 @@
/* ###
* 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.framework.main;
import java.awt.BorderLayout;
import java.awt.Font;
import java.io.IOException;
import java.util.List;
import javax.swing.*;
import ghidra.framework.client.RepositoryAdapter;
import ghidra.framework.plugintool.PluginTool;
import ghidra.framework.remote.User;
/**
* Extension of the {@link ProjectAccessPanel} that only shows the user access list.
*
*/
public class ViewProjectAccessPanel extends ProjectAccessPanel {
/**
* Construct a new panel.
*
* @param repository handle to the repository adapter
* @param tool the plugin tool
* @throws IOException if there's an error processing repository information
*/
public ViewProjectAccessPanel(RepositoryAdapter repository, PluginTool tool)
throws IOException {
super(null, repository, tool);
}
/**
* Constructs a new panel.
*
* @param knownUsers names of the users that are known to the remote server
* @param currentUser the current user
* @param allUsers all users known to the repository
* @param repositoryName the name of the repository
* @param anonymousServerAccessAllowed true if the server allows anonymous access
* @param anonymousAccessEnabled true if the repository allows anonymous access
* (ignored if anonymousServerAccessAllowed is false)
* @param tool the current tool
*/
public ViewProjectAccessPanel(String[] knownUsers, String currentUser, List<User> allUsers,
String repositoryName, boolean anonymousServerAccessAllowed,
boolean anonymousAccessEnabled, PluginTool tool) {
super(knownUsers, currentUser, allUsers, repositoryName, anonymousServerAccessAllowed,
anonymousAccessEnabled, tool);
}
/**
* Creates the main gui panel, containing the known users, button, and user access
* panels.
*/
@Override
protected void createMainPanel(String[] knownUsers, boolean anonymousServerAccessAllowed) {
JPanel mainPanel = new JPanel();
mainPanel.setLayout(new BorderLayout());
mainPanel.setBorder(BorderFactory.createEmptyBorder(5, 10, 5, 10));
userAccessPanel = new UserAccessPanel(currentUser);
mainPanel.add(userAccessPanel, BorderLayout.CENTER);
if (anonymousServerAccessAllowed && origAnonymousAccessEnabled) {
JLabel anonymousAccessLabel = new JLabel("Anonymous Read-Only Access Enabled");
anonymousAccessLabel.setBorder(BorderFactory.createEmptyBorder(5, 2, 0, 0));
Font f = anonymousAccessLabel.getFont().deriveFont(Font.ITALIC);
anonymousAccessLabel.setFont(f);
mainPanel.add(anonymousAccessLabel, BorderLayout.SOUTH);
}
add(mainPanel);
}
}

View file

@ -0,0 +1,475 @@
/* ###
* 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.framework.main;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.beans.PropertyChangeEvent;
import java.util.HashMap;
import javax.swing.*;
import javax.swing.border.Border;
import docking.help.Help;
import docking.help.HelpService;
import docking.widgets.dialogs.InputDialog;
import ghidra.framework.model.*;
import ghidra.util.HelpLocation;
import ghidra.util.Msg;
import ghidra.util.exception.DuplicateNameException;
class WorkspacePanel extends JPanel implements WorkspaceChangeListener {
private final static long serialVersionUID = 1L;
private final static String RUNNING_TOOLS_TITLE = "Running Tools";
private final static Border ACTIVE_WORKSPACE_BORDER =
BorderFactory.createTitledBorder(RUNNING_TOOLS_TITLE);
private final static String NO_ACTIVE_WORKSPACE = "INACTIVE";
private final static Border INACTIVE_BORDER =
BorderFactory.createTitledBorder(RUNNING_TOOLS_TITLE + ": " + NO_ACTIVE_WORKSPACE);
final static int TYPICAL_NUM_RUNNING_TOOLS = 3;
private JComboBox<String> workspaceChooser;
private DefaultComboBoxModel<String> workspaceModel;
private CardLayout workspaceManager;
private JPanel inactivePanel;
private JPanel runningToolsCardPanel;
private HashMap<String, RunningToolsPanel> runningToolsMap;
private Workspace activeWorkspace;
private FrontEndPlugin plugin;
private Project activeProject;
private boolean valueIsAdjusting;
WorkspacePanel(FrontEndPlugin plugin) {
super(new BorderLayout(0, 0));
this.plugin = plugin;
// create the plugin only to manage the running tools in the workspace(s)
workspaceManager = new CardLayout();
runningToolsCardPanel = new JPanel(workspaceManager);
runningToolsMap = new HashMap<>(TYPICAL_NUM_RUNNING_TOOLS);
// create the combo box that allows the user to choose which
// workspace becomes active
workspaceModel = new DefaultComboBoxModel<>();
workspaceChooser = new JComboBox<>(workspaceModel);
workspaceChooser.addItemListener(e -> {
if (e.getStateChange() == ItemEvent.SELECTED) {
chooseWorkspace((String) workspaceModel.getSelectedItem());
}
});
workspaceChooser.setPreferredSize(
new Dimension(200, (int) workspaceChooser.getPreferredSize().getHeight()));
setHelpLocation();
JPanel wcPanel = new JPanel();
wcPanel.add(workspaceChooser);
add(wcPanel, BorderLayout.EAST);
add(runningToolsCardPanel, BorderLayout.CENTER);
setBorder(INACTIVE_BORDER);
}
/**
* Tool was removed from the given workspace.
*/
@Override
public void toolRemoved(Workspace ws, Tool tool) {
removeTool(ws.getName(), tool);
plugin.getToolActionManager().enableConnectTools();
}
/**
* Tool was added to the given workspace.
*/
@Override
public void toolAdded(Workspace ws, Tool tool) {
addTool(ws.getName(), tool);
plugin.getToolActionManager().enableConnectTools();
}
/**
* called when a workspace is added by the ToolManager
*/
@Override
public void workspaceAdded(Workspace ws) {
if (ws == null) {
return;
}
String wsName = ws.getName();
RunningToolsPanel rtp = new RunningToolsPanel(plugin, ws);
runningToolsCardPanel.add(rtp, wsName);
runningToolsMap.put(wsName, rtp);
workspaceModel.addElement(wsName);
validate();
}
/**
* called when a workspace is removed by the ToolManager
*/
@Override
public void workspaceRemoved(Workspace ws) {
if (ws == null) {
return;
}
String workspaceName = ws.getName();
workspaceModel.removeElement(workspaceName);
RunningToolsPanel rtp = runningToolsMap.get(workspaceName);
runningToolsMap.remove(workspaceName);
runningToolsCardPanel.remove(rtp);
}
/**
* called when a workspace is setActive() by the ToolManager
*/
@Override
public void workspaceSetActive(Workspace ws) {
if (ws == null) {
throw new IllegalArgumentException("Active Workspace cannot be null");
}
workspaceSetActive(ws, ws.getName());
}
private void workspaceSetActive(Workspace ws, String workspaceName) {
// set the active workspace handle
activeWorkspace = ws;
if (ws == null) {
showInactiveWorkspace();
return;
}
// show the right workspace in the chooser
workspaceModel.setSelectedItem(workspaceName);
// show the running tools in the specified workspace
workspaceManager.show(runningToolsCardPanel, workspaceName);
setPanelEnabled(true);
setBorder(ACTIVE_WORKSPACE_BORDER);
validate();
repaint();
}
@Override
public void propertyChange(PropertyChangeEvent event) {
if (activeProject == null || activeWorkspace == null) {
return;
}
// get the active workspace and type of property being changed
String eventPropertyName = event.getPropertyName();
// if this is a workspace name change, update our running
// tools map so we can identify the runningToolsPanel properly
if (eventPropertyName.equals(ToolManager.WORKSPACE_NAME_PROPERTY)) {
String oldName = (String) event.getOldValue();
String newName = (String) event.getNewValue();
renameWorkspace(activeWorkspace, oldName, newName);
return;
}
// if this is a change in a tool, update the running tools panel
// containing the tool
Object eventSource = event.getSource();
if (eventSource instanceof Tool) {
Tool tool = (Tool) eventSource;
ToolTemplate template = tool.getToolTemplate(true);
Icon icon = tool.getIconURL().getIcon();
String workspaceName = activeWorkspace.getName();
RunningToolsPanel rtp = runningToolsMap.get(workspaceName);
if (eventPropertyName.equals(Tool.TOOL_NAME_PROPERTY)) {
rtp.toolNameChanged(tool);
}
else {
rtp.updateToolButton(tool, template, icon);
}
}
}
/**
* called whenever the active project changes or is being set for
* the first time
*/
void setActiveProject(Project project) {
// clear state from previous project
clearAll();
// if no active project, provide default workspace panel
if (project == null) {
showInactiveWorkspace();
activeProject = null;
return;
}
// set the workspace up to know about changes made to it in the framework
// first remove ourselves from the previous project's tool manager's listeners
if (activeProject != null) {
ToolManager tm = activeProject.getToolManager();
tm.removeWorkspaceChangeListener(this);
}
// now add ourselves to the new active project's tool manager's listeners
ToolManager toolManager = project.getToolManager();
toolManager.addWorkspaceChangeListener(this);
Tool[] tools = toolManager.getRunningTools();
for (Tool tool : tools) {
tool.addPropertyChangeListener(this);
}
setPanelEnabled(true);
this.activeProject = project;
// because of timing of state being restored on projects
// being opened and created by the ProjectManager, we initialize
// our workspace state manually whenever a project is opened
initProjectState(activeProject);
}
/**
* add a running tool to the panel
*/
void addTool(String workspaceName, Tool runningTool) {
RunningToolsPanel rtp = runningToolsMap.get(workspaceName);
if (rtp != null) {
rtp.addTool(runningTool);
runningTool.addPropertyChangeListener(this);
}
validate();
repaint();
}
/**
* adds a new empty workspace to the project with the name of the
* workspace set by the user; called from the Workspace menu.
*/
void addWorkspace() {
// query the user for the name of the workspace
InputDialog nameDialog = new InputDialog("Create New Workspace", "Workspace Name",
ToolManager.DEFAULT_WORKSPACE_NAME);
plugin.getTool().showDialog(nameDialog, (Component) null);
if (nameDialog.isCanceled()) {
return; // user canceled
}
String workspaceName = nameDialog.getValue();
try {
ToolManager tm = activeProject.getToolManager();
tm.createWorkspace(workspaceName);
}
catch (DuplicateNameException e) {
String msg = "Workspace named: " + workspaceName + " already exists.";
Msg.showError(getClass(), plugin.getTool().getToolFrame(), "Workspace Name Exists",
msg);
}
}
/**
* used by the action listener on the combo-box workspace chooser
*/
private void chooseWorkspace(String workspaceName) {
if (valueIsAdjusting) {
return;
}
ToolManager tm = activeProject.getToolManager();
Workspace[] workspaces = tm.getWorkspaces();
Workspace ws = null;
for (int w = 0; ws == null && w < workspaces.length; w++) {
if (workspaces[w].getName().equals(workspaceName)) {
ws = workspaces[w];
}
}
if (ws != null) {
ws.setActive();
}
else {
// must have been a rename that set this and not the action
// listener, so don't do anything
}
}
/**
* removes the active workspace
*/
void removeWorkspace() {
if (activeWorkspace == null) {
return;
}
String workspaceName = activeWorkspace.getName();
if (!plugin.confirmDelete("Workspace: " + workspaceName)) {
return; // user canceled
}
// remove the workspace from the framework model
ToolManager tm = activeProject.getToolManager();
tm.removeWorkspace(activeWorkspace);
}
/**
* renames the active workspace
*/
void renameWorkspace() {
if (activeWorkspace == null) {
return;
}
boolean done = false;
while (!done) {
// query the user for the name of the workspace
String workspaceName = activeWorkspace.getName();
InputDialog nameDialog =
new InputDialog("Rename Workspace", "Workspace Name", workspaceName);
plugin.getTool().showDialog(nameDialog, (Component) null);
if (nameDialog.isCanceled()) {
return;
}
String newName = nameDialog.getValue();
if (newName.equals(workspaceName)) {
return;
}
if (newName.length() > 0) {
try {
activeWorkspace.setName(newName);
// ToolManager will send a propertyChange event that we will
// handle there when the name changes
break;
}
catch (DuplicateNameException e) {
Msg.showError(getClass(), plugin.getTool().getToolFrame(),
"Error Renaming Workspace",
"Workspace named: " + newName + " already exists.");
}
}
}
}
private void renameWorkspace(Workspace ws, String oldName, String newName) {
RunningToolsPanel rtp = runningToolsMap.get(oldName);
runningToolsMap.remove(oldName);
runningToolsMap.put(newName, rtp);
// update the workspace model
valueIsAdjusting = true;
workspaceModel.removeElement(oldName);
workspaceModel.addElement(newName);
valueIsAdjusting = false;
// remove the panel from our layout, and add it back in with the new name
runningToolsCardPanel.remove(rtp);
runningToolsCardPanel.add(rtp, newName);
workspaceSetActive(ws, newName);
}
/**
* because of timing of state being restored on projects
* being opened and created by the ProjectManager, we initialize
* our workspace state manually whenever a project is opened
* This should ONLY be called by setActiveProject()!
*/
private void initProjectState(Project project) {
// set this value so the workspaceAdded() routine doesn't set selected item
// in the workspace chooser
valueIsAdjusting = true;
ToolManager tm = project.getToolManager();
Workspace[] workspaces = tm.getWorkspaces();
for (Workspace workspace : workspaces) {
workspaceAdded(workspace);
}
valueIsAdjusting = false;
workspaceSetActive(tm.getActiveWorkspace());
}
/**
* @return the active workspace for the project
*/
Workspace getActiveWorkspace() {
return activeWorkspace;
}
/**
* cause the specified workspace to be the active one
* NOTE: this workspace must already be known to the ToolManager
*/
void setActiveWorkspace(Workspace ws) {
chooseWorkspace(ws.getName());
}
private void clearAll() {
runningToolsMap.clear();
workspaceModel.removeAllElements();
runningToolsCardPanel.removeAll();
validate();
}
private void showInactiveWorkspace() {
// if this is the first time we're showing it, create it
if (inactivePanel == null) {
inactivePanel = new JPanel();
runningToolsCardPanel.add(inactivePanel, NO_ACTIVE_WORKSPACE);
}
if (runningToolsCardPanel.getComponentCount() == 0) {
runningToolsCardPanel.add(inactivePanel, NO_ACTIVE_WORKSPACE);
}
activeWorkspace = null;
setPanelEnabled(false);
workspaceManager.show(runningToolsCardPanel, NO_ACTIVE_WORKSPACE);
setBorder(INACTIVE_BORDER);
}
private void removeTool(String workspaceName, Tool tool) {
RunningToolsPanel rtp = runningToolsMap.get(workspaceName);
if (rtp != null) {
rtp.removeTool(tool);
tool.removePropertyChangeListener(this);
}
validate();
repaint();
}
private void setPanelEnabled(boolean enabled) {
workspaceChooser.setEnabled(enabled);
runningToolsCardPanel.setEnabled(enabled);
validate();
repaint();
}
private void setHelpLocation() {
HelpService help = Help.getHelpService();
help.registerHelp(workspaceChooser, new HelpLocation(plugin.getName(), "Workspace"));
}
}

Some files were not shown because too many files have changed in this diff Show more