GP-71: Prepping for source release.

This commit is contained in:
Dan
2020-12-10 09:39:41 -05:00
parent ddbfbfe198
commit 8201baef2b
2705 changed files with 305722 additions and 53 deletions
+24
View File
@@ -0,0 +1,24 @@
apply from: "${rootProject.projectDir}/gradle/javaProject.gradle"
apply from: "${rootProject.projectDir}/gradle/jacocoProject.gradle"
apply from: "${rootProject.projectDir}/gradle/javaTestProject.gradle"
apply from: "${rootProject.projectDir}/gradle/distributableGhidraModule.gradle"
apply plugin: 'eclipse'
eclipse.project.name = 'Debug ProposedUtils'
// val autoServiceVersion = "1.0-rc5"
dependencies {
compile project(':DB')
compile project(':Project')
compile project(':SoftwareModeling')
compile project(':Utility')
compile project(':Base') // Boo!: (Where to put DefaultEnumeratedColumnProgramTableModel?)
// TODO: Evaluate these dependencies
// compile("com.google.auto.service:auto-service-annotations:$autoServiceVersion")
// annotationProcessor("com.google.auto.service:auto-service:$autoServiceVersion")
testCompile project(':Base')
}
@@ -0,0 +1,5 @@
##VERSION: 2.0
.classpath||NONE||reviewed||END|
.project||NONE||reviewed||END|
Module.manifest||GHIDRA||||END|
build.gradle||GHIDRA||||END|
@@ -0,0 +1,27 @@
/* ###
* 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 docking.widgets;
public interface ExpanderArrowExpansionListener {
/**
* @throws ExpanderArrowExpansionVetoException
*/
default void changing(boolean expanding) throws ExpanderArrowExpansionVetoException {
// Nothing
}
void changed(boolean expanded);
}
@@ -0,0 +1,20 @@
/* ###
* 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 docking.widgets;
public class ExpanderArrowExpansionVetoException extends Exception {
}
@@ -0,0 +1,155 @@
/* ###
* 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 docking.widgets;
import java.awt.*;
import java.awt.event.*;
import java.util.concurrent.CompletableFuture;
import javax.swing.JPanel;
import ghidra.util.datastruct.ListenerSet;
public class ExpanderArrowPanel extends JPanel {
// TODO: Can I make this consistent with the UI LaF
protected final static Polygon ARROW =
new Polygon(new int[] { 5, -5, -5 }, new int[] { 0, -5, 5 }, 3);
protected final static Dimension SIZE = new Dimension(16, 16);
protected final static int ANIM_MILLIS = 80;
protected final static int FRAME_MILLIS = 30; // Approx 30 fps
private final ListenerSet<ExpanderArrowExpansionListener> listeners =
new ListenerSet<>(ExpanderArrowExpansionListener.class);
private boolean expanded = false;
private double animTheta;
private boolean animActive = false;
private long animTimeEnd;
private double animThetaEnd;
private double animThetaOverTimeRate;
private final MouseListener mouseListener = new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
toggle();
}
};
{
addMouseListener(mouseListener);
}
public void addExpansionListener(ExpanderArrowExpansionListener listener) {
listeners.add(listener);
}
public void removeExpansionListener(ExpanderArrowExpansionListener listener) {
listeners.remove(listener);
}
protected synchronized void animateTheta(double destTheta) {
animTimeEnd = System.currentTimeMillis() + ANIM_MILLIS;
animThetaEnd = destTheta;
animThetaOverTimeRate = (destTheta - animTheta) / ANIM_MILLIS;
animActive = true;
scheduleNextFrame();
}
public void toggle() {
setExpanded(!expanded);
}
protected boolean fireChanging(boolean newExpanded) {
try {
listeners.fire.changing(newExpanded);
}
catch (ExpanderArrowExpansionVetoException e) {
return false;
}
return true;
}
protected void fireChanged() {
listeners.fire.changed(expanded);
}
public void setExpanded(boolean expanded) {
if (this.expanded == expanded) {
return;
}
if (!fireChanging(expanded)) {
return;
}
double destTheta = expanded ? Math.PI / 2 : 0;
animateTheta(destTheta);
this.expanded = expanded;
fireChanged();
}
public boolean isExpanded() {
return expanded;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.translate((double) SIZE.width / 2, (double) SIZE.height / 2);
g2.rotate(animTheta);
g2.fillPolygon(ARROW);
if (!animActive) {
return;
}
long time = System.currentTimeMillis();
double timeDiff = Math.max(0, animTimeEnd - time);
if (timeDiff != 0) {
double thetaDiff = timeDiff * animThetaOverTimeRate;
animTheta = animThetaEnd - thetaDiff;
scheduleNextFrame();
return;
}
animActive = false;
if (animTheta != animThetaEnd) {
animTheta = animThetaEnd;
scheduleNextFrame();
}
}
@Override
public Dimension getPreferredSize() {
return SIZE;
}
@Override
public Dimension getMinimumSize() {
return SIZE;
}
protected void scheduleNextFrame() {
CompletableFuture.runAsync(() -> {
try {
Thread.sleep(FRAME_MILLIS);
}
catch (InterruptedException e) {
// Whatever. Render early.
}
repaint();
});
}
}
@@ -0,0 +1,202 @@
/* ###
* 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 docking.widgets;
import java.awt.*;
import java.awt.event.ActionEvent;
import javax.swing.*;
import javax.swing.border.BevelBorder;
import javax.swing.event.ChangeEvent;
// TODO: I'd like "close" buttons on the tabs, optionally.
// For now, client must use a popup menu.
@SuppressWarnings("serial")
public class HorizontalTabPanel<T> extends JPanel {
public static Color copyColor(Color c) {
return new Color(c.getRGB());
}
public static class TabListCellRenderer<T> implements ListCellRenderer<T> {
protected final Box hBox = Box.createHorizontalBox();
protected final JLabel label = new JLabel();
{
hBox.setBorder(new BevelBorder(BevelBorder.RAISED));
hBox.setOpaque(true);
hBox.add(label);
}
protected String getText(T value) {
return value.toString();
}
protected Icon getIcon(T value) {
return null;
}
@Override
public Component getListCellRendererComponent(JList<? extends T> list,
T value, int index, boolean isSelected, boolean cellHasFocus) {
label.setText(getText(value));
label.setIcon(getIcon(value));
if (isSelected) {
//label.setForeground(list.getSelectionForeground());
label.setForeground(copyColor(list.getSelectionForeground()));
hBox.setBackground(list.getSelectionBackground());
}
else {
label.setForeground(list.getForeground());
hBox.setBackground(list.getBackground());
}
hBox.validate();
return hBox;
}
}
private final JList<T> list = new JList<>();
private final JScrollPane scroll = new JScrollPane(list);
private final JViewport viewport = scroll.getViewport();
private final DefaultListModel<T> model = new DefaultListModel<>();
private final JButton left = new JButton("<");
private final JButton right = new JButton(">");
{
list.setModel(model);
// TODO: Experiment with multiple traces in one timeline
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
list.setLayoutOrientation(JList.HORIZONTAL_WRAP);
list.setVisibleRowCount(1);
list.setCellRenderer(new TabListCellRenderer<>());
list.setOpaque(false);
scroll.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
scroll.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scroll.setBorder(null);
viewport.addChangeListener(this::viewportChanged);
left.setBorder(null);
right.setBorder(null);
left.setContentAreaFilled(false);
right.setContentAreaFilled(false);
left.setOpaque(true);
right.setOpaque(true);
left.addActionListener(this::leftActivated);
right.addActionListener(this::rightActivated);
}
public HorizontalTabPanel() {
super();
setLayout(new BorderLayout());
list.setBackground(getBackground());
add(scroll, BorderLayout.CENTER);
add(left, BorderLayout.WEST);
add(right, BorderLayout.EAST);
}
private void viewportChanged(ChangeEvent e) {
Dimension paneSize = getSize();
Dimension listSize = list.getSize();
boolean buttonsVisible = paneSize.getWidth() < listSize.getWidth();
left.setVisible(buttonsVisible);
right.setVisible(buttonsVisible);
}
/**
* Find the first cell which is even partially visible
*
* @param reverse true to search from right to left
* @return the cell index
*/
private int findFirstVisible(boolean reverse) {
int n = model.getSize();
Rectangle vis = list.getVisibleRect();
for (int i = reverse ? n - 1 : 0; reverse ? i >= 0 : i < n; i += reverse ? -1 : 1) {
Rectangle b = list.getCellBounds(i, i);
if (vis.intersects(b)) {
return i;
}
}
return -1;
}
/**
* Find the first cell <em>after</em> a given start which is even partially occluded
*
* @param start the starting cell index
* @param reverse true to search from right to left
* @return the cell index
*/
private int findNextOccluded(int start, boolean reverse) {
if (start == -1) {
return -1;
}
int n = model.getSize();
Rectangle vis = list.getVisibleRect();
for (int i = reverse ? start - 1 : start + 1; reverse ? i >= 0 : i < n; i +=
reverse ? -1 : 1) {
Rectangle b = list.getCellBounds(i, i);
if (!vis.contains(b)) {
return i;
}
}
return -1;
}
private void leftActivated(ActionEvent e) {
list.ensureIndexIsVisible(findNextOccluded(findFirstVisible(true), true));
}
private void rightActivated(ActionEvent e) {
list.ensureIndexIsVisible(findNextOccluded(findFirstVisible(false), false));
}
public JList<T> getList() {
return list;
}
public void addItem(T item) {
model.addElement(item);
revalidate();
}
public void removeItem(T item) {
model.removeElement(item);
revalidate();
}
public T getSelectedItem() {
int index = list.getSelectedIndex();
return index < 0 ? null : list.getModel().getElementAt(index);
}
public void setSelectedItem(T item) {
// NOTE: For large lists, this could get slow
int index = model.indexOf(item);
if (index < 0) {
list.clearSelection();
}
else {
list.setSelectedIndex(index);
}
}
public T getItem(int index) {
return list.getModel().getElementAt(index);
}
}
@@ -0,0 +1,227 @@
/* ###
* 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 docking.widgets;
import java.awt.*;
import java.awt.event.*;
import javax.swing.JPanel;
import com.google.common.collect.Range;
import ghidra.util.datastruct.ListenerSet;
public class RangeCursorPanel extends JPanel {
protected final static int MIN_SIZE = 16;
protected final static Dimension MIN_BOX = new Dimension(MIN_SIZE, MIN_SIZE);
protected final static Polygon ARROW =
new Polygon(new int[] { 0, -MIN_SIZE, -MIN_SIZE }, new int[] { 0, MIN_SIZE, -MIN_SIZE }, 3);
protected static double clamp(Range<Double> range, double value) {
return Math.max(range.lowerEndpoint(), Math.min(value, range.upperEndpoint()));
}
protected enum Orientation {
HORIZONTAL {
@Override
void transform(Component component, Graphics2D g, Range<Double> range, double value) {
int offset = valueToOffset(component.getWidth(), range, value);
g.translate(offset, 0);
}
@Override
double getValue(Component component, MouseEvent e, Range<Double> range) {
return offsetToValue(component.getWidth(), range, e.getX());
}
},
VERTICAL {
@Override
void transform(Component component, Graphics2D g, Range<Double> range, double value) {
g.translate(0, valueToOffset(component.getHeight(), range, value));
}
@Override
double getValue(Component component, MouseEvent e, Range<Double> range) {
return offsetToValue(component.getHeight(), range, e.getY());
}
};
protected static int valueToOffset(int size, Range<Double> range, double value) {
double lower = range.lowerEndpoint();
double length = range.upperEndpoint() - lower;
double diff = value - lower;
return (int) (diff * size / length);
}
protected static double offsetToValue(int size, Range<Double> range, int offset) {
double lower = range.lowerEndpoint();
double length = range.upperEndpoint() - lower;
double diff = length * offset / size;
return lower + diff;
}
abstract void transform(Component component, Graphics2D g, Range<Double> range,
double value);
abstract double getValue(Component component, MouseEvent e, Range<Double> range);
}
public enum Direction {
EAST(Orientation.VERTICAL) {
@Override
void transform(Component component, Graphics2D g) {
g.translate(component.getWidth(), 0);
}
},
NORTH(Orientation.HORIZONTAL) {
@Override
void transform(Component component, Graphics2D g) {
g.rotate(-Math.PI / 2);
}
},
WEST(Orientation.VERTICAL) {
@Override
void transform(Component component, Graphics2D g) {
g.rotate(Math.PI);
}
},
SOUTH(Orientation.HORIZONTAL) {
@Override
void transform(Component component, Graphics2D g) {
g.translate(0, component.getHeight());
g.rotate(Math.PI / 2);
}
};
protected final Orientation orientation;
Direction(Orientation orientation) {
this.orientation = orientation;
}
abstract void transform(Component component, Graphics2D g);
}
protected final ListenerSet<RangeCursorValueListener> listeners =
new ListenerSet<>(RangeCursorValueListener.class);
protected final MouseListener mouseListener = new MouseAdapter() {
@Override
public void mouseClicked(MouseEvent e) {
if (e.getButton() != MouseEvent.BUTTON1) {
return;
}
doSeek(e);
}
};
protected final MouseMotionListener mouseMotionListener = new MouseMotionAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
if ((e.getModifiersEx() & MouseEvent.BUTTON1_DOWN_MASK) == 0) {
return;
}
doSeek(e);
}
};
{
addMouseListener(mouseListener);
addMouseMotionListener(mouseMotionListener);
}
protected Direction direction;
protected Range<Double> range = Range.closed(-1.0, 1.0);
protected double value;
public RangeCursorPanel(Direction direction) {
this.direction = direction;
this.setFocusable(true);
}
protected void doSeek(MouseEvent e) {
double requested = direction.orientation.getValue(RangeCursorPanel.this, e, range);
requestValue(requested, EventTrigger.GUI_ACTION);
}
public void addValueListener(RangeCursorValueListener listener) {
listeners.add(listener);
}
public void removeValueListener(RangeCursorValueListener listener) {
listeners.remove(listener);
}
public void setDirection(Direction direction) {
this.direction = direction;
invalidate();
}
public void setRange(Range<Double> range) {
this.range = range;
repaint();
}
public void requestValue(double requested) {
requestValue(requested, EventTrigger.API_CALL);
}
public void requestValue(double requested, EventTrigger trigger) {
double val = adjustRequestedValue(requested);
if (this.value == val) {
return;
}
this.value = val;
listeners.fire.valueChanged(val, trigger);
repaint();
}
public double getValue() {
return value;
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
//g2.setColor(Color.GREEN);
//g2.fillPolygon(ARROW);
direction.orientation.transform(this, g2, range, value);
//g2.setColor(Color.RED);
//g2.fillPolygon(ARROW);
direction.transform(this, g2);
g2.setColor(getForeground());
g2.fillPolygon(ARROW);
}
protected double adjustRequestedValue(double requested) {
// Extension point
return clamp(range, requested);
}
@Override
public Dimension getPreferredSize() {
return MIN_BOX;
}
@Override
public Dimension getMinimumSize() {
return MIN_BOX;
}
}
@@ -0,0 +1,20 @@
/* ###
* 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 docking.widgets;
public interface RangeCursorValueListener {
void valueChanged(double position, EventTrigger trigger);
}
@@ -0,0 +1,46 @@
/* ###
* 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 docking.widgets.table;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.util.function.Consumer;
import javax.swing.*;
import javax.swing.table.TableColumn;
public enum CellEditorUtils {
;
public static void onOneFocus(JComponent editorComponent, Runnable action) {
FocusAdapter l = new FocusAdapter() {
@Override
public void focusGained(FocusEvent e) {
action.run();
editorComponent.removeFocusListener(this);
}
};
editorComponent.addFocusListener(l);
}
public static <R> void installButton(JTable table, GTableFilterPanel<R> filterPanel,
TableColumn column, Icon icon, int size, Consumer<R> action) {
table.setRowHeight(size);
column.setMaxWidth(size);
column.setMinWidth(size);
column.setCellRenderer(new IconButtonTableCellRenderer(icon, size));
column.setCellEditor(new IconButtonTableCellEditor<>(filterPanel, icon, action));
}
}
@@ -0,0 +1,88 @@
/* ###
* 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 docking.widgets.table;
import java.awt.Component;
import java.awt.Font;
import java.util.function.BiFunction;
import javax.swing.JTable;
import javax.swing.table.TableModel;
import ghidra.docking.settings.Settings;
import ghidra.util.table.column.AbstractGColumnRenderer;
public class CustomToStringCellRenderer<T> extends AbstractGColumnRenderer<T> {
public enum CustomFont {
DEFAULT, MONOSPACED, BOLD;
}
public static final CustomToStringCellRenderer<Object> MONO_OBJECT =
new CustomToStringCellRenderer<>(CustomFont.MONOSPACED, Object.class,
(v, s) -> v == null ? "<null>" : v.toString());
public static final CustomToStringCellRenderer<Long> MONO_LONG_HEX =
new CustomToStringCellRenderer<>(CustomFont.MONOSPACED, Long.class,
(v, s) -> v == null ? "<null>" : "0x" + Long.toString(v, 16));
public static final CustomToStringCellRenderer<Long> MONO_ULONG_HEX =
new CustomToStringCellRenderer<>(CustomFont.MONOSPACED, Long.class,
(v, s) -> v == null ? "<null>" : "0x" + Long.toUnsignedString(v, 16));
private final CustomFont customFont;
private final Class<T> cls;
private final BiFunction<T, Settings, String> toString;
public CustomToStringCellRenderer(Class<T> cls, BiFunction<T, Settings, String> toString) {
this(null, cls, toString);
}
public CustomToStringCellRenderer(CustomFont font, Class<T> cls,
BiFunction<T, Settings, String> toString) {
this.customFont = font;
this.cls = cls;
this.toString = toString;
}
@Override
protected void configureFont(JTable table, TableModel model, int column) {
setFont(getCustomFont());
}
protected Font getCustomFont() {
switch (customFont) {
default:
case DEFAULT:
return defaultFont;
case MONOSPACED:
return fixedWidthFont;
case BOLD:
return boldFont;
}
}
// TODO: Seems the filter model does not heed this....
@Override
public String getFilterString(T t, Settings settings) {
return toString.apply(t, settings);
}
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
super.getTableCellRendererComponent(data);
setText(toString.apply(cls.cast(data.getValue()), data.getColumnSettings()));
return this;
}
}
@@ -0,0 +1,96 @@
/* ###
* 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 docking.widgets.table;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
import ghidra.program.model.address.*;
import ghidra.program.model.listing.Program;
import ghidra.program.util.ProgramLocation;
import ghidra.program.util.ProgramSelection;
public class DefaultEnumeratedColumnProgramTableModel<C extends Enum<C> & EnumeratedTableColumn<C, ? super R>, R>
extends DefaultEnumeratedColumnTableModel<C, R>
implements EnumeratedColumnProgramTableModel<R> {
protected final C selColumn;
private Program program;
public DefaultEnumeratedColumnProgramTableModel(String name, Class<C> colType, C selColumn) {
super(name, colType);
if (selColumn != null) {
Class<?> valueClass = selColumn.getValueClass();
if (!Address.class.isAssignableFrom(valueClass) &&
!AddressRange.class.isAssignableFrom(valueClass) &&
!AddressSetView.class.isAssignableFrom(valueClass)) {
throw new IllegalArgumentException(
"Address-selection column must have Address, AddressRange, " +
"or AddressSetView type");
}
}
this.selColumn = selColumn;
}
@Override
public ProgramLocation getProgramLocation(int row, int column) {
Class<?> columnClass = getColumnClass(column);
if (!Address.class.isAssignableFrom(columnClass) &&
!ProgramLocation.class.isAssignableFrom(columnClass)) {
return null;
}
Object value = getValueAt(row, column);
if (value instanceof Address) {
return new ProgramLocation(program, (Address) value);
}
if (value instanceof ProgramLocation) {
return (ProgramLocation) value;
}
throw new AssertionError();
}
@Override
public ProgramSelection getProgramSelection(int[] rows) {
if (selColumn == null) {
return null;
}
AddressSet sel = new AddressSet();
for (int r : rows) {
Object value = selColumn.getValueOf(getRowObject(r));
if (value instanceof Address) {
sel.add((Address) value);
}
else if (value instanceof AddressRange) {
sel.add((AddressRange) value);
}
else if (value instanceof AddressSetView) {
sel.add((AddressSetView) value);
}
else {
throw new AssertionError();
}
}
return new ProgramSelection(sel);
}
@Override
public Program getProgram() {
return program;
}
@Override
public void setProgram(Program program) {
this.program = program;
}
}
@@ -0,0 +1,298 @@
/* ###
* 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 docking.widgets.table;
import java.util.*;
import java.util.function.Predicate;
import docking.widgets.table.ColumnSortState.SortDirection;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
public class DefaultEnumeratedColumnTableModel<C extends Enum<C> & EnumeratedTableColumn<C, ? super R>, R>
extends AbstractSortedTableModel<R> implements EnumeratedColumnTableModel<R> {
// NOTE: If I need to track indices, addSortListener
public static interface EnumeratedTableColumn<C extends Enum<C>, R> {
public Class<?> getValueClass();
public Object getValueOf(R row);
public String getHeader();
default public void setValueOf(R row, Object value) {
throw new UnsupportedOperationException("Cell is not editable");
}
default public boolean isEditable(R row) {
return false;
}
default public boolean isSortable() {
return true;
}
default public SortDirection defaultSortDirection() {
return SortDirection.ASCENDING;
}
}
public class TableRowIterator implements RowIterator<R> {
protected final ListIterator<R> it = modelData.listIterator();
protected int index;
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public R next() {
index = it.nextIndex();
return it.next();
}
@Override
public boolean hasPrevious() {
return it.hasPrevious();
}
@Override
public R previous() {
index = it.previousIndex();
return it.previous();
}
@Override
public int nextIndex() {
return it.nextIndex();
}
@Override
public int previousIndex() {
return it.previousIndex();
}
@Override
public void remove() {
it.remove();
fireTableRowsDeleted(index, index);
}
@Override
public void set(R e) {
it.set(e);
fireTableRowsUpdated(index, index);
}
@Override
public void notifyUpdated() {
fireTableRowsUpdated(index, index);
}
@Override
public void add(R e) {
it.add(e);
int nextIndex = it.nextIndex();
fireTableRowsInserted(nextIndex, nextIndex);
}
}
private final List<R> modelData = new ArrayList<>();
private final String name;
private final C[] cols;
public DefaultEnumeratedColumnTableModel(String name, Class<C> colType) {
this.name = name;
this.cols = colType.getEnumConstants();
setupDefaultSortOrder();
}
@Override
public String getName() {
return name;
}
@Override
public List<R> getModelData() {
return modelData;
}
protected void setupDefaultSortOrder() {
List<C> defaultOrder = defaultSortOrder();
if (defaultOrder.isEmpty()) {
return;
}
TableSortStateEditor editor = new TableSortStateEditor();
for (C col : defaultOrder) {
editor.addSortedColumn(col.ordinal(), col.defaultSortDirection());
}
setDefaultTableSortState(editor.createTableSortState());
}
public List<C> defaultSortOrder() {
return Collections.emptyList();
}
/*@Override
public int getRowCount() {
return modelData.size();
}*/
@Override
public int getColumnCount() {
return cols.length;
}
/*@Override
public Object getValueAt(int rowIndex, int columnIndex) {
return getColumnValueForRow(modelData.get(rowIndex), columnIndex);
}*/
@Override
public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
R row = modelData.get(rowIndex);
C col = cols[columnIndex];
Class<?> cls = col.getValueClass();
col.setValueOf(row, cls.cast(aValue));
fireTableCellUpdated(rowIndex, columnIndex);
}
@Override
public boolean isCellEditable(int rowIndex, int columnIndex) {
R row = modelData.get(rowIndex);
C col = cols[columnIndex];
return col.isEditable(row);
}
@Override
public boolean isSortable(int columnIndex) {
C col = cols[columnIndex];
return col.isSortable();
}
@Override
public Class<?> getColumnClass(int columnIndex) {
return cols[columnIndex].getValueClass();
}
@Override
public String getColumnName(int column) {
return cols[column].getHeader();
}
@Override
public Object getColumnValueForRow(R t, int columnIndex) {
return cols[columnIndex].getValueOf(t);
}
@Override
public void add(R row) {
int rowIndex = modelData.size();
modelData.add(row);
fireTableRowsInserted(rowIndex, rowIndex);
}
@Override
public void addAll(Collection<R> c) {
int startIndex = modelData.size();
modelData.addAll(c);
int endIndex = modelData.size() - 1;
fireTableRowsInserted(startIndex, endIndex);
}
@Override
public RowIterator<R> rowIterator() {
return new TableRowIterator();
}
@Override
public void notifyUpdated(R row) {
int rowIndex = modelData.indexOf(row);
fireTableRowsUpdated(rowIndex, rowIndex);
}
@Override
public List<R> notifyUpdatedWith(Predicate<R> predicate) {
int lastIndexUpdated = 0;
ListIterator<R> rit = modelData.listIterator();
List<R> updated = new ArrayList<>();
while (rit.hasNext()) {
R row = rit.next();
if (predicate.test(row)) {
lastIndexUpdated = rit.previousIndex();
updated.add(row);
}
}
int size = updated.size();
if (size == 0) {
}
else if (size == 1) {
fireTableRowsUpdated(lastIndexUpdated, lastIndexUpdated);
}
else {
fireTableDataChanged();
}
return updated;
}
@Override
public void delete(R row) {
int rowIndex = modelData.indexOf(row);
modelData.remove(rowIndex);
fireTableRowsDeleted(rowIndex, rowIndex);
}
@Override
public List<R> deleteWith(Predicate<R> predicate) {
int lastIndexRemoved = 0;
ListIterator<R> rit = modelData.listIterator();
List<R> removed = new ArrayList<>();
while (rit.hasNext()) {
R row = rit.next();
if (predicate.test(row)) {
lastIndexRemoved = rit.previousIndex();
rit.remove();
removed.add(row);
}
}
int size = removed.size();
if (size == 0) {
}
else if (size == 1) {
fireTableRowsDeleted(lastIndexRemoved, lastIndexRemoved);
}
else {
fireTableDataChanged();
}
return removed;
}
@Override
public R findFirst(Predicate<R> predicate) {
for (R row : modelData) {
if (predicate.test(row)) {
return row;
}
}
return null;
}
@Override
public void clear() {
modelData.clear();
fireTableDataChanged();
}
}
@@ -0,0 +1,24 @@
/* ###
* 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 docking.widgets.table;
import ghidra.program.model.listing.Program;
import ghidra.util.table.ProgramTableModel;
public interface EnumeratedColumnProgramTableModel<R>
extends EnumeratedColumnTableModel<R>, ProgramTableModel {
void setProgram(Program program);
}
@@ -0,0 +1,43 @@
/* ###
* 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 docking.widgets.table;
import java.util.*;
import java.util.function.Predicate;
public interface EnumeratedColumnTableModel<R> extends RowObjectTableModel<R> {
interface RowIterator<R> extends ListIterator<R> {
void notifyUpdated();
}
void add(R row);
void addAll(Collection<R> c);
ListIterator<R> rowIterator();
void notifyUpdated(R row);
List<R> notifyUpdatedWith(Predicate<R> predicate);
void delete(R row);
List<R> deleteWith(Predicate<R> predicate);
R findFirst(Predicate<R> predicate);
public void clear();
}
@@ -0,0 +1,63 @@
/* ###
* 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 docking.widgets.table;
import java.awt.Component;
import java.awt.event.MouseEvent;
import java.math.BigInteger;
import java.util.EventObject;
import javax.swing.*;
import javax.swing.table.TableCellEditor;
import docking.widgets.textfield.IntegerTextField;
public class HexBigIntegerTableCellEditor extends AbstractCellEditor implements TableCellEditor {
private IntegerTextField input;
@Override
public BigInteger getCellEditorValue() {
return input.getValue();
}
@Override
public boolean isCellEditable(EventObject e) {
// If mouse event, require double-click
if (e instanceof MouseEvent) {
MouseEvent evt = (MouseEvent) e;
return evt.getClickCount() >= 2 && super.isCellEditable(e);
}
return super.isCellEditable(e);
}
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected,
int row, int column) {
input = new IntegerTextField();
input.getComponent().setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
input.setAllowNegativeValues(true);
input.setHexMode();
input.setAllowsHexPrefix(false);
input.setShowNumberMode(true);
if (value != null) {
input.setValue((BigInteger) value);
CellEditorUtils.onOneFocus(input.getComponent(), () -> input.selectAll());
}
input.addActionListener(e -> stopCellEditing());
return input.getComponent();
}
}
@@ -0,0 +1,49 @@
/* ###
* 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 docking.widgets.table;
import java.awt.Component;
import java.math.BigInteger;
import javax.swing.JTable;
import javax.swing.table.TableModel;
import ghidra.docking.settings.Settings;
import ghidra.util.table.column.AbstractGColumnRenderer;
public class HexBigIntegerTableCellRenderer extends AbstractGColumnRenderer<BigInteger> {
@Override
protected void configureFont(JTable table, TableModel model, int column) {
setFont(fixedWidthFont);
}
protected String formatBigInteger(BigInteger value) {
return value == null ? "??" : value.toString(16);
}
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
super.getTableCellRendererComponent(data);
setText(formatBigInteger((BigInteger) data.getValue()));
return this;
}
// TODO: Seems the filter model does not heed this....
@Override
public String getFilterString(BigInteger t, Settings settings) {
return formatBigInteger(t);
}
}
@@ -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 docking.widgets.table;
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.function.Consumer;
import javax.swing.*;
import javax.swing.table.TableCellEditor;
public class IconButtonTableCellEditor<R> extends AbstractCellEditor
implements TableCellEditor, ActionListener {
protected static final String BLANK = "";
protected JButton button = new JButton(BLANK);
private final GTableFilterPanel<R> filterPanel;
private final Consumer<R> action;
protected R row;
public IconButtonTableCellEditor(GTableFilterPanel<R> filterPanel, Icon icon,
Consumer<R> action) {
this.filterPanel = filterPanel;
button.setIcon(icon);
this.action = action;
button.addActionListener(this);
}
@Override
public Object getCellEditorValue() {
return BLANK;
}
@Override
@SuppressWarnings("hiding")
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected,
int row, int column) {
this.row = filterPanel.getRowObject(row);
return button;
}
@Override
public void actionPerformed(ActionEvent e) {
fireEditingStopped();
action.accept(row);
}
}
@@ -0,0 +1,45 @@
/* ###
* 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 docking.widgets.table;
import java.awt.Component;
import java.awt.Dimension;
import javax.swing.Icon;
import javax.swing.JButton;
import ghidra.docking.settings.Settings;
import ghidra.util.table.column.AbstractGhidraColumnRenderer;
public class IconButtonTableCellRenderer
extends AbstractGhidraColumnRenderer<String> {
protected final JButton button = new JButton("");
public IconButtonTableCellRenderer(Icon icon, int buttonSize) {
button.setIcon(icon);
button.setMinimumSize(new Dimension(buttonSize, buttonSize));
}
@Override
public Component getTableCellRendererComponent(GTableCellRenderingData data) {
return button;
}
@Override
public String getFilterString(String t, Settings settings) {
return t;
}
}
@@ -0,0 +1,75 @@
/* ###
* 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 docking.widgets.table;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import docking.widgets.table.DefaultEnumeratedColumnTableModel.EnumeratedTableColumn;
public class RowWrappedEnumeratedColumnTableModel<C extends Enum<C> & EnumeratedTableColumn<C, R>, R, T>
extends DefaultEnumeratedColumnTableModel<C, R> {
private final Function<T, R> wrapper;
private final Map<T, R> map = new HashMap<>();
public RowWrappedEnumeratedColumnTableModel(String name, Class<C> colType,
Function<T, R> wrapper) {
super(name, colType);
this.wrapper = wrapper;
}
protected synchronized R rowFor(T t) {
return map.computeIfAbsent(t, wrapper);
}
protected synchronized R delFor(T t) {
return map.remove(t);
}
protected synchronized List<R> rowsFor(Collection<? extends T> c) {
return c.stream().map(this::rowFor).collect(Collectors.toList());
}
public void addItem(T t) {
add(rowFor(t));
}
public void addAllItems(Collection<? extends T> c) {
addAll(rowsFor(c));
}
public void updateItem(T t) {
notifyUpdated(rowFor(t));
}
public void updateAllItems(Collection<T> c) {
notifyUpdatedWith(rowsFor(c)::contains);
}
public void deleteItem(T t) {
delete(delFor(t));
}
public synchronized void deleteAllItems(Collection<T> c) {
deleteWith(rowsFor(c)::contains);
map.keySet().removeAll(c);
}
public synchronized Map<T, R> getMap() {
return Map.copyOf(map);
}
}
@@ -0,0 +1,22 @@
/* ###
* 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 docking.widgets.timeline;
import com.google.common.collect.Range;
public interface TimelineViewRangeListener {
void viewRangeChanged(Range<Double> viewRange);
}
@@ -0,0 +1,43 @@
/* ###
* 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 docking.widgets.tree;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
public interface AnyChangeTreeModelListener extends TreeModelListener {
void treeChanged(TreeModelEvent e);
@Override
default void treeNodesChanged(TreeModelEvent e) {
treeChanged(e);
}
@Override
default void treeNodesInserted(TreeModelEvent e) {
treeChanged(e);
}
@Override
default void treeNodesRemoved(TreeModelEvent e) {
treeChanged(e);
}
@Override
default void treeStructureChanged(TreeModelEvent e) {
treeChanged(e);
}
}
@@ -0,0 +1,46 @@
/* ###
* 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 docking.widgets.tree;
import java.util.Map;
public interface SearchableByObjectGTreeNode {
Map<? extends Object, ? extends GTreeNode> getObjectNodeMap();
default GTreeNode findNodeForObject(Object obj) {
Map<? extends Object, ? extends GTreeNode> index = getObjectNodeMap();
synchronized (index) {
GTreeNode node = index.get(obj);
if (node != null) {
return node;
}
for (GTreeNode sub : index.values()) {
if (sub.isLeaf()) {
continue;
}
if (!(sub instanceof SearchableByObjectGTreeNode)) {
continue;
}
SearchableByObjectGTreeNode toSearch = (SearchableByObjectGTreeNode) sub;
node = toSearch.findNodeForObject(obj);
if (node != null) {
return node;
}
}
return null;
}
}
}
@@ -0,0 +1,95 @@
/* ###
* 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 generic;
import java.util.*;
public abstract class AbstractUnionedCollection<E> extends AbstractCollection<E> {
protected final Collection<? extends Collection<? extends E>> collections;
public AbstractUnionedCollection(Collection<? extends Collection<? extends E>> collections) {
this.collections = collections;
}
@SafeVarargs
public AbstractUnionedCollection(Collection<? extends E>... collections) {
this.collections = Arrays.asList(collections);
}
@Override
public int size() {
int size = 0;
for (Collection<? extends E> col : collections) {
size += col.size();
}
return size;
}
@Override
public boolean isEmpty() {
for (Collection<? extends E> col : collections) {
if (!col.isEmpty()) {
return false;
}
}
return true;
}
@Override
public boolean contains(Object o) {
for (Collection<? extends E> col : collections) {
if (col.contains(o)) {
return true;
}
}
return false;
}
@Override
public boolean remove(Object o) {
for (Collection<? extends E> col : collections) {
if (col.remove(o)) {
return true;
}
}
return false;
}
@Override
public boolean removeAll(Collection<?> c) {
boolean result = false;
for (Collection<? extends E> col : collections) {
result |= col.removeAll(c);
}
return result;
}
@Override
public boolean retainAll(Collection<?> c) {
boolean result = false;
for (Collection<? extends E> col : collections) {
result |= col.retainAll(c);
}
return result;
}
@Override
public void clear() {
for (Collection<? extends E> col : collections) {
col.clear();
}
}
}
@@ -0,0 +1,50 @@
/* ###
* 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 generic;
import java.util.Collection;
import java.util.Iterator;
import com.google.common.collect.Iterators;
public class CatenatedCollection<E> extends AbstractUnionedCollection<E> {
public CatenatedCollection(Collection<? extends Collection<? extends E>> collections) {
super(collections);
}
@SafeVarargs
public CatenatedCollection(Collection<? extends E>... collections) {
super(collections);
}
@Override
public Iterator<E> iterator() {
return Iterators.concat(new Iterator<Iterator<? extends E>>() {
Iterator<? extends Collection<? extends E>> it = collections.iterator();
@Override
public boolean hasNext() {
return it.hasNext();
}
@Override
public Iterator<? extends E> next() {
return it.next().iterator();
}
});
}
}
@@ -0,0 +1,51 @@
/* ###
* 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 generic;
import java.util.List;
import java.util.function.Function;
public interface ComparableTupleRecord<T extends ComparableTupleRecord<T>>
extends TupleRecord<T>, Comparable<T> {
List<Function<T, ? extends Comparable<?>>> getComparableFieldAccessors();
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
default List<Function<T, ?>> getFieldAccessors() {
return (List) getComparableFieldAccessors();
}
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
default int compareTo(T that) {
if (that == null) {
return 1;
}
int result = this.getClass().hashCode() - that.getClass().hashCode();
if (result != 0) {
return result;
}
for (Function<T, ? extends Comparable<?>> field : getComparableFieldAccessors()) {
Comparable vthis = field.apply((T) this);
Comparable vthat = field.apply(that);
result = vthis.compareTo(vthat);
if (result != 0) {
return result;
}
}
return 0;
}
}
@@ -0,0 +1,47 @@
/* ###
* 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 generic;
public class ID<T> {
public static <T> ID<T> of(T obj) {
return new ID<>(obj);
}
private final T obj;
public ID(T obj) {
this.obj = obj;
}
public T getObject() {
return obj;
}
@Override
public int hashCode() {
return System.identityHashCode(obj);
}
@Override
public boolean equals(Object o) {
if (!(o instanceof ID<?>)) {
return false;
}
ID<?> that = (ID<?>) o;
return this.obj == that.obj;
}
}
@@ -0,0 +1,86 @@
/* ###
* 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 generic;
import java.util.Iterator;
import java.util.function.Function;
/**
* TODO Document me
*
* Note the innerFactory may return null to skip an outer element.
*
* TODO: Test innerFactory returning null.
*
* @param <O>
* @param <I>
*/
public class NestedIterator<O, I> implements Iterator<I> {
public static <O, I> Iterator<I> start(Iterator<O> outer,
Function<O, Iterator<? extends I>> innerFactory) {
return new NestedIterator<>(outer, innerFactory);
}
protected final Iterator<O> outer;
protected final Function<O, Iterator<? extends I>> innerFactory;
protected Iterator<? extends I> inner;
protected Iterator<? extends I> preppedInner;
protected NestedIterator(Iterator<O> outer, Function<O, Iterator<? extends I>> innerFactory) {
this.outer = outer;
this.innerFactory = innerFactory;
}
private Iterator<? extends I> prepNextIterator() {
while (outer.hasNext()) {
Iterator<? extends I> candidate = innerFactory.apply(outer.next());
if (candidate != null && candidate.hasNext()) {
return candidate;
}
}
return null;
}
@Override
public boolean hasNext() {
if (inner != null && inner.hasNext() || preppedInner != null && preppedInner.hasNext()) {
return true;
}
preppedInner = prepNextIterator();
return preppedInner != null;
}
@Override
public I next() {
if (inner == null || !inner.hasNext()) {
if (preppedInner == null) {
preppedInner = prepNextIterator();
}
if (preppedInner == null) { // Still
return null;
}
inner = preppedInner;
preppedInner = null;
}
return inner.next();
}
@Override
public void remove() {
inner.remove();
}
}
@@ -0,0 +1,51 @@
/* ###
* 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 generic;
import java.util.List;
import java.util.Objects;
import java.util.function.Function;
public interface TupleRecord<T extends TupleRecord<T>> {
List<Function<T, ?>> getFieldAccessors();
@SuppressWarnings("unchecked")
default boolean doEquals(Object that) {
if (!this.getClass().equals(that.getClass())) {
return false;
}
for (Function<T, ?> field : getFieldAccessors()) {
if (!Objects.equals(field.apply((T) this), field.apply((T) that))) {
return false;
}
}
return true;
}
@SuppressWarnings("unchecked")
default int doHashCode() {
int hash = 1;
for (Function<T, ?> field : getFieldAccessors()) {
hash *= 31;
Object val = field.apply((T) this);
if (val == null) {
continue;
}
hash += val.hashCode();
}
return hash;
}
}
@@ -0,0 +1,64 @@
/* ###
* 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 generic;
import java.util.Iterator;
/**
* Some utilities for when singleton collections are expected
*/
public interface Unique {
/**
* Assert that exactly one element is in an iterable and get that element
*
* @param <T> the type of element
* @param col the iterable
* @return the element
* @throws AssertionError if no element or many elements exist in the iterable
*/
static <T> T assertOne(Iterable<T> col) {
Iterator<T> it = col.iterator();
if (!it.hasNext()) {
throw new AssertionError("Expected exactly one. Got none.");
}
T result = it.next();
if (it.hasNext()) {
throw new AssertionError("Expected exactly one. Got many.");
}
return result;
}
/**
* Assert that at most one element is in an iterable and get that element or {@code null}
*
* @param <T> the type of element
* @param col the iterable
* @return the element or {@code null} if empty
* @throws AssertionError if many elements exist in the iterable
*/
static <T> T assertAtMostOne(Iterable<T> col) {
Iterator<T> it = col.iterator();
if (!it.hasNext()) {
return null;
}
T result = it.next();
if (it.hasNext()) {
throw new AssertionError("Expected at most one. Got many.");
}
return result;
}
}
@@ -0,0 +1,28 @@
/* ###
* 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 generic.depends;
import java.lang.annotation.*;
@Target({ ElementType.METHOD, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface DependentService {
Class<?> override() default Sentinel.class;
enum Sentinel {
// None
}
}
@@ -0,0 +1,57 @@
/* ###
* 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 generic.depends;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import generic.depends.err.ServiceConstructionException;
class DependentServiceConstructor<T> {
final Class<T> cls;
final Method method;
DependentServiceConstructor(Class<T> cls, Method method) {
if (!cls.isAssignableFrom(method.getReturnType())) {
throw new IllegalArgumentException(
"Constructor method must return type assignable to the class");
}
this.cls = cls;
this.method = method;
}
@SuppressWarnings("unchecked")
T construct(Object obj, Map<Class<?>, Object> dependencies)
throws ServiceConstructionException {
List<Object> params = new ArrayList<>(method.getParameterCount());
for (Class<?> pType : method.getParameterTypes()) {
Object p = dependencies.get(pType);
assert p != null;
params.add(p);
}
try {
return (T) method.invoke(obj, params.toArray());
}
catch (InvocationTargetException e) {
throw new ServiceConstructionException(
"Error constructing dependent service via " + method, e.getCause());
}
catch (IllegalAccessException | IllegalArgumentException e) {
throw new AssertionError(e);
}
}
}
@@ -0,0 +1,153 @@
/* ###
* 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 generic.depends;
import java.lang.reflect.*;
import java.util.*;
import java.util.Map.Entry;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import generic.depends.err.*;
public class DependentServiceResolver<T> {
private static final Map<Class<?>, DependentServiceResolver<?>> CACHED = new HashMap<>();
@SuppressWarnings("unchecked")
public static <T> DependentServiceResolver<T> get(Class<T> cls)
throws UnsatisfiedParameterException, UnsatisfiedFieldsException {
DependentServiceResolver<T> resolver = (DependentServiceResolver<T>) CACHED.get(cls);
if (resolver == null) {
CACHED.put(cls, resolver = new DependentServiceResolver<>(cls));
}
return resolver;
}
@SuppressWarnings("unchecked")
public static <T> void inject(T t) throws ServiceConstructionException,
UnsatisfiedParameterException, UnsatisfiedFieldsException {
get((Class<T>) t.getClass()).injectServices(t);
}
private final Set<Class<?>> classesIncluded = new HashSet<>();
private final Multimap<Class<?>, Field> fieldsByClass = HashMultimap.create();
private final Multimap<Class<?>, Class<?>> depsByDependents = HashMultimap.create();
private final Map<Class<?>, Method> constructors = new HashMap<>();
private final List<DependentServiceConstructor<?>> ordered = new ArrayList<>();
private DependentServiceResolver(Class<T> cls)
throws UnsatisfiedParameterException, UnsatisfiedFieldsException {
addClass(cls);
compile();
}
private void addClass(Class<?> cls) {
if (classesIncluded.contains(cls)) {
return;
}
Class<?> superCls = cls.getSuperclass();
if (superCls == null) {
return;
}
addClass(superCls);
for (Class<?> superIf : cls.getInterfaces()) {
addClass(superIf);
}
for (Method m : cls.getDeclaredMethods()) {
DependentService annot = m.getAnnotation(DependentService.class);
if (annot == null) {
continue;
}
int mods = m.getModifiers();
if (Modifier.isStatic(mods)) {
throw new IllegalArgumentException("Constructor must be a non-static method");
}
Class<?> override = annot.override();
Class<?> rCls = m.getReturnType();
if (override != DependentService.Sentinel.class) {
if (!override.isAssignableFrom(rCls)) {
throw new IllegalArgumentException(
"Overridden constructor must return same or subclass of original");
}
depsByDependents.put(override, rCls);
constructors.put(override, m);
}
constructors.put(rCls, m);
m.setAccessible(true);
for (Class<?> pType : m.getParameterTypes()) {
depsByDependents.put(rCls, pType);
}
}
for (Field f : cls.getDeclaredFields()) {
DependentService annot = f.getAnnotation(DependentService.class);
if (annot == null) {
continue;
}
Class<?> fCls = f.getType();
fieldsByClass.put(fCls, f);
f.setAccessible(true);
}
}
private void compile() throws UnsatisfiedParameterException, UnsatisfiedFieldsException {
Set<Class<?>> missing = new HashSet<>(fieldsByClass.keySet());
missing.removeAll(constructors.keySet());
if (!missing.isEmpty()) {
throw new UnsatisfiedFieldsException(missing);
}
Set<Class<?>> unordered = new HashSet<>(constructors.keySet());
while (!unordered.isEmpty()) {
Set<Class<?>> forRound = new HashSet<>(unordered);
forRound.removeAll(depsByDependents.keySet());
if (forRound.isEmpty()) {
throw new UnsatisfiedParameterException(unordered);
}
for (Class<?> ready : forRound) {
Method m = constructors.get(ready);
unordered.remove(ready);
ordered.add(new DependentServiceConstructor<>(ready, m));
depsByDependents.values().removeAll(Collections.singleton(ready));
}
}
assert ordered.size() == constructors.size();
}
public void injectServices(T obj) throws ServiceConstructionException {
Map<Class<?>, Object> instancesByClass = new HashMap<>();
Map<Method, Object> constructed = new HashMap<>();
for (DependentServiceConstructor<?> cons : ordered) {
Object service = constructed.get(cons.method);
if (service == null) {
service = cons.construct(obj, instancesByClass);
constructed.put(cons.method, service);
}
instancesByClass.put(cons.cls, service);
}
for (Entry<Class<?>, Field> entry : fieldsByClass.entries()) {
try {
entry.getValue().set(obj, instancesByClass.get(entry.getKey()));
}
catch (IllegalArgumentException | IllegalAccessException e) {
throw new AssertionError(e);
}
}
}
}
@@ -0,0 +1,29 @@
/* ###
* 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 generic.depends.err;
public class ServiceConstructionException extends Exception {
public ServiceConstructionException(String message, Throwable cause) {
super(message, cause);
}
public <E extends Throwable> void unwrap(Class<E> cls) throws E {
Throwable cause = getCause();
if (cls.isInstance(cause)) {
throw cls.cast(cause);
}
}
}
@@ -0,0 +1,32 @@
/* ###
* 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 generic.depends.err;
import java.util.Collections;
import java.util.Set;
public class UnsatisfiedFieldsException extends Exception {
private final Set<Class<?>> missing;
public UnsatisfiedFieldsException(Set<Class<?>> missing) {
super("There are fields without suitable constructors: " + missing);
this.missing = Collections.unmodifiableSet(missing);
}
public Set<Class<?>> getMissing() {
return missing;
}
}
@@ -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 generic.depends.err;
import java.util.Collections;
import java.util.Set;
public class UnsatisfiedParameterException extends Exception {
private final Set<Class<?>> left;
public UnsatisfiedParameterException(Set<Class<?>> left) {
super("Could not resolve required parameter for next in: " + left +
". Note: it may be a circular dependency.");
this.left = Collections.unmodifiableSet(left);
}
public Set<Class<?>> getLeft() {
return left;
}
}
@@ -0,0 +1,235 @@
/* ###
* 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.base.widgets.table;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.util.EventObject;
import javax.swing.*;
import javax.swing.event.CellEditorListener;
import javax.swing.event.ChangeEvent;
import javax.swing.table.TableCellEditor;
import docking.widgets.DropDownSelectionTextField;
import docking.widgets.table.CellEditorUtils;
import ghidra.app.services.DataTypeManagerService;
import ghidra.app.util.datatype.DataTypeSelectionEditor;
import ghidra.framework.plugintool.PluginTool;
import ghidra.program.model.data.*;
import ghidra.util.Swing;
import ghidra.util.data.DataTypeParser.AllowedDataTypes;
public class DataTypeTableCellEditor extends AbstractCellEditor
implements TableCellEditor {
private final PluginTool tool;
private DataTypeManagerService service;
private JTable table;
private JPanel editorPanel;
private DataTypeSelectionEditor editor;
private DataType dt;
private CellEditorListener cellEditorListener = new CellEditorListener() {
@Override
public void editingCanceled(ChangeEvent e) {
cancelCellEditing();
}
@Override
public void editingStopped(ChangeEvent e) {
stopCellEditing();
}
};
private JButton dataTypeChooserButton = new JButton("...") {
@Override
public Dimension getPreferredSize() {
Dimension preferredSize = super.getPreferredSize();
preferredSize.width = 15;
return preferredSize;
}
};
{
dataTypeChooserButton.addActionListener(e -> Swing.runLater(() -> stopEdit()));
}
protected DataTypeTableCellEditor(PluginTool tool, DataTypeManagerService service) {
this.tool = tool;
this.service = service;
}
public DataTypeTableCellEditor(DataTypeManagerService service) {
this(null, service);
}
public DataTypeTableCellEditor(PluginTool tool) {
// NOTE: Service will be updated on request
this(tool, null);
}
private DataTypeManagerService updateService() {
if (tool != null) {
service = tool.getService(DataTypeManagerService.class);
}
return service;
}
/**
* Get the maximum allowed size for the given row and column
*
* Defaults to unlimited.
*
* @param row the row being edited
* @param column the column being edited
* @return the maximum size or -1 for unlimited
*/
protected int getMaxLength(int row, int column) {
return -1;
}
protected AllowedDataTypes getAllowed(int row, int column) {
return AllowedDataTypes.ALL;
}
protected DataTypeManager getPreferredDataTypeManager(int row, int column) {
return null;
}
@Override
public Component getTableCellEditorComponent(JTable newTable, Object value, boolean isSelected,
int row, int column) {
this.table = newTable;
init(row, column);
// TODO: Use this to verify lengths if variable-length is to be permitted.
/*DataTypeInstance dti = (DataTypeInstance) value;
if (dti != null) {
dt = dti.getDataType();
}
else {
dt = null;
}*/
dt = (DataType) value;
editor.setCellEditorValue(dt);
return editorPanel;
}
protected void init(int row, int column) {
updateService();
editor = new DataTypeSelectionEditor(service, getMaxLength(row, column),
getAllowed(row, column));
editor.setPreferredDataTypeManager(getPreferredDataTypeManager(row, column));
editor.setTabCommitsEdit(true);
editor.setConsumeEnterKeyPress(false);
final DropDownSelectionTextField<DataType> textField = editor.getDropDownTextField();
textField.setBorder(UIManager.getBorder("Table.focusCellHighlightBorder"));
CellEditorUtils.onOneFocus(textField, () -> textField.selectAll());
editor.addCellEditorListener(cellEditorListener);
editorPanel = new JPanel(new BorderLayout()) {
@Override
public void requestFocus() {
textField.requestFocus();
}
};
editorPanel.add(textField, BorderLayout.CENTER);
editorPanel.add(dataTypeChooserButton, BorderLayout.EAST);
}
protected void stopEdit() {
updateService();
DataType dataType = service.getDataType((String) null); // Why?
if (dataType != null) {
editor.setCellEditorValue(dataType);
editor.stopCellEditing();
}
else {
editor.cancelCellEditing();
}
}
@Override
public DataType getCellEditorValue() {
return dt;
}
protected boolean validateSelection(DataType dataType) {
return true;
}
protected DataType resolveSelection(DataType dataType) {
return dataType;
}
private boolean isEmptyEditorCell() {
return editor.getCellEditorValueAsText().trim().isEmpty();
}
@Override
public boolean stopCellEditing() {
ListSelectionModel columnSelectionModel = table.getColumnModel().getSelectionModel();
columnSelectionModel.setValueIsAdjusting(true);
int editingColumn = table.getEditingColumn();
try {
if (!editor.validateUserSelection()) {
return false;
}
}
catch (InvalidDataTypeException e) {
return false;
}
DataType dataType = resolveSelection(editor.getCellEditorValueAsDataType());
if (!isEmptyEditorCell() && !validateSelection(dataType)) {
return false;
}
if (dataType != null) {
if (dataType.equals(dt)) {
fireEditingCanceled(); // no change
}
else {
dt = dataType;
fireEditingStopped();
}
}
else {
fireEditingCanceled();
}
columnSelectionModel.setAnchorSelectionIndex(editingColumn);
columnSelectionModel.setLeadSelectionIndex(editingColumn);
columnSelectionModel.setValueIsAdjusting(false);
return true;
}
@Override
public boolean isCellEditable(EventObject e) {
// If mouse event, require double-click
if (e instanceof MouseEvent) {
MouseEvent evt = (MouseEvent) e;
return evt.getClickCount() >= 2 && super.isCellEditable(e);
}
return super.isCellEditable(e);
}
}
@@ -0,0 +1,91 @@
/* ###
* 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 generic.depends.DependentServiceResolver;
import generic.depends.err.*;
import ghidra.util.database.DBOpenMode;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
public abstract class DBDomainObjectSupport extends DomainObjectAdapterDB {
private DBOpenMode openMode;
private TaskMonitor monitor;
private VersionException versionExc;
protected static interface ManagerSupplier<T> {
T create(DBOpenMode openMode, TaskMonitor monitor)
throws IOException, VersionException, CancelledException;
}
protected DBDomainObjectSupport(DBHandle dbh, DBOpenMode openMode, TaskMonitor monitor,
String name, int timeInterval, int bufSize, Object consumer) {
super(dbh, name, timeInterval, bufSize, consumer);
this.openMode = openMode;
this.monitor = monitor;
}
public void init()
throws CancelledException, IOException, VersionException, ServiceConstructionException {
this.versionExc = null;
try {
DependentServiceResolver.inject(this);
}
catch (ServiceConstructionException e) {
Throwable cause = e.getCause();
if (cause instanceof VersionException) {
throw (VersionException) cause;
}
if (cause instanceof CancelledException) {
throw (CancelledException) cause;
}
if (cause instanceof IOException) {
throw (IOException) cause;
}
throw e;
}
catch (UnsatisfiedParameterException | UnsatisfiedFieldsException e) {
throw new AssertionError(e);
}
if (versionExc != null) {
throw versionExc;
}
finishedCreatingManagers();
this.monitor = null;
}
protected void finishedCreatingManagers() {
// Extension point
}
protected <T> T createManager(String managerName, ManagerSupplier<T> supplier)
throws CancelledException, IOException {
monitor.checkCanceled();
monitor.setMessage("Creating " + managerName);
try {
return supplier.create(openMode, monitor);
}
catch (VersionException e) {
versionExc = e.combine(versionExc);
return null;
}
}
}
@@ -0,0 +1,57 @@
/* ###
* 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 ghidra.framework.model.DomainFile;
import ghidra.framework.model.DomainObject;
import ghidra.util.exception.CancelledException;
import ghidra.util.exception.VersionException;
import ghidra.util.task.TaskMonitor;
public class OpenedDomainFile<T extends DomainObject> implements AutoCloseable {
public final T content;
public static <T extends DomainObject> OpenedDomainFile<T> open(Class<T> contentType,
DomainFile file, boolean okToUpgrade, boolean okToRecover, TaskMonitor monitor)
throws VersionException, CancelledException, IOException {
return new OpenedDomainFile<>(contentType, file, okToUpgrade, okToRecover, monitor);
}
public static <T extends DomainObject> OpenedDomainFile<T> open(Class<T> contentType,
DomainFile file, TaskMonitor monitor)
throws VersionException, CancelledException, IOException {
return new OpenedDomainFile<>(contentType, file, false, false, monitor);
}
public OpenedDomainFile(Class<T> contentType, DomainFile file, boolean okToUpgrade,
boolean okToRecover, TaskMonitor monitor)
throws VersionException, CancelledException, IOException {
if (!contentType.isAssignableFrom(file.getDomainObjectClass())) {
throw new ClassCastException("file " + file + " does not contain " + contentType +
". got " + file.getDomainObjectClass() + " instead.");
}
content = contentType.cast(file.getDomainObject(this, okToUpgrade, okToRecover, monitor));
}
@Override
public void close() {
if (content != null) {
content.release(this);
}
}
}
@@ -0,0 +1,223 @@
/* ###
* 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.options;
import java.beans.PropertyEditor;
import java.lang.annotation.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.function.Function;
import org.apache.commons.lang3.StringUtils;
import com.google.common.collect.ImmutableList;
import generic.ComparableTupleRecord;
import ghidra.framework.options.annotation.*;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.HelpLocation;
public interface AutoOptions {
static class CategoryAndName implements ComparableTupleRecord<CategoryAndName> {
public static final List<Function<CategoryAndName, ? extends Comparable<?>>> ACCESSORS =
ImmutableList.of(CategoryAndName::getCategory, CategoryAndName::getName);
private final String category;
private final String name;
protected static String getPluginPackageName(Plugin plugin) {
return plugin.getPluginDescription().getPluginPackage().getName();
}
public CategoryAndName(AutoOptionDefined annotation, Plugin plugin) {
String[] categoryNames = annotation.category();
if (categoryNames.length == 0) {
this.category = getPluginPackageName(plugin);
}
else {
this.category = StringUtils.join(categoryNames, ".");
}
this.name = StringUtils.join(annotation.name(), ".");
}
public CategoryAndName(AutoOptionConsumed annotation, Plugin plugin) {
// Same code because annotations cannot extend one another
String[] categoryNames = annotation.category();
if (categoryNames.length == 0) {
this.category = getPluginPackageName(plugin);
}
else {
this.category = StringUtils.join(categoryNames, ".");
}
this.name = StringUtils.join(annotation.name(), ".");
}
public CategoryAndName(String category, String name) {
this.category = category;
this.name = name;
}
@Override
public List<Function<CategoryAndName, ? extends Comparable<?>>> getComparableFieldAccessors() {
return ACCESSORS;
}
public String getCategory() {
return category;
}
public String getName() {
return name;
}
@Override
public int hashCode() {
return doHashCode();
}
@Override
public boolean equals(Object obj) {
return doEquals(obj);
}
@Override
public String toString() {
return category + ":" + name;
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@interface OldValue {
// no attributes
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
@interface NewValue {
// no attributes
}
public interface Wiring {
void dispose();
}
public static class WiringImpl implements Wiring {
@SuppressWarnings("unused") // strong reference
private AutoOptionsListener<?> listener;
public WiringImpl(AutoOptionsListener<?> listener) {
this.listener = listener;
}
@Override
public void dispose() {
this.listener = null;
}
}
public static Wiring wireOptions(Plugin plugin) {
return wireOptions(plugin, plugin);
}
public static Wiring wireOptions(Plugin plugin, Object receiver) {
registerOptionsDefined(plugin, receiver.getClass(), receiver);
return wireOptionsConsumed(plugin, receiver);
}
static void registerOptionsDefined(Plugin plugin, Class<?> cls, Object receiver) {
Class<?> superclass = cls.getSuperclass();
if (superclass != null) {
registerOptionsDefined(plugin, superclass, receiver);
}
for (Field f : cls.getDeclaredFields()) {
AutoOptionDefined annotation = f.getAnnotation(AutoOptionDefined.class);
if (annotation == null) {
continue;
}
CategoryAndName key = new CategoryAndName(annotation, plugin);
ToolOptions options = plugin.getTool().getOptions(key.getCategory());
if (options.isRegistered(key.getName())) {
continue;
}
f.setAccessible(true);
HelpLocation help = getHelpLocation(plugin.getName(), annotation.help());
Object defaultValue;
try {
defaultValue = f.get(receiver);
}
catch (IllegalArgumentException | IllegalAccessException e) {
throw new AssertionError(e);
}
OptionType type = annotation.type();
if (type == OptionType.NO_TYPE) {
type = OptionType.getOptionType(defaultValue);
// TODO: OptionType does have getValueClass, if searching by class is better
}
if (type == OptionType.NO_TYPE) {
throw new IllegalArgumentException(
"Could not determine option type from default value: " + f + " = " +
defaultValue);
}
String description = annotation.description();
Class<? extends PropertyEditor> editorClass = annotation.editor();
final PropertyEditor editor;
if (editorClass == PropertyEditor.class) {
editor = null;
}
else {
try {
editor = editorClass.getConstructor().newInstance();
}
catch (InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException | SecurityException e) {
throw new IllegalArgumentException(
"editor class must have accessible default constructor", e);
}
}
options.registerOption(key.getName(), type, defaultValue, help, description, editor);
// TODO: Wish Ghidra would do this upon any option registration
options.putObject(key.getName(), defaultValue, type);
}
}
public static HelpLocation getHelpLocation(String defaultTopic, HelpInfo annot) {
if (annot.topic().length == 0) {
return null;
}
String anchor = annot.anchor();
if ("".equals(anchor)) {
anchor = null;
}
String topic =
annot.topic().length == 0 ? defaultTopic : StringUtils.join(annot.topic(), ".");
return new HelpLocation(topic, anchor);
}
public static Wiring wireOptionsConsumed(Plugin plugin, Object receiver) {
PluginTool tool = plugin.getTool();
AutoOptionsListener<?> listener = new AutoOptionsListener<>(plugin, receiver);
for (String category : listener.getCategories()) {
ToolOptions options = tool.getOptions(category);
options.addOptionsChangeListener(listener);
}
listener.notifyCurrentValues(tool);
return new WiringImpl(listener);
}
}
@@ -0,0 +1,335 @@
/* ###
* 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.options;
import java.lang.reflect.*;
import java.util.*;
import java.util.function.BiFunction;
import org.apache.commons.lang3.StringUtils;
import ghidra.framework.options.*;
import ghidra.framework.options.AutoOptions.*;
import ghidra.framework.options.annotation.AutoOptionConsumed;
import ghidra.framework.options.annotation.AutoOptionDefined;
import ghidra.framework.plugintool.Plugin;
import ghidra.framework.plugintool.PluginTool;
import ghidra.util.Msg;
public class AutoOptionsListener<R> implements OptionsChangeListener {
protected static final Map<Class<?>, Set<OptionSetter<?>>> SETTERS_BY_RECEIVER_CLASS =
new HashMap<>();
protected static final Map<Class<?>, ReceiverProfile<?>> PROFILES_BY_RECEIVER_CLASS =
new HashMap<>();
protected interface OptionSetter<R> {
public void set(R receiver, Object newValue, Object oldValue);
public CategoryAndName getKey();
}
protected static class FieldOptionSetter<R> implements OptionSetter<R> {
protected final Field field;
protected final CategoryAndName key;
public FieldOptionSetter(Field field, AutoOptionDefined annotation, Plugin plugin) {
this(field, new CategoryAndName(annotation, plugin));
}
public FieldOptionSetter(Field field, AutoOptionConsumed annotation, Plugin plugin) {
this(field, new CategoryAndName(annotation, plugin));
}
public FieldOptionSetter(Field field, String category, String name) {
this(field, new CategoryAndName(category, name));
}
public FieldOptionSetter(Field field, CategoryAndName key) {
this.field = field;
this.key = key;
field.setAccessible(true);
}
@Override
public void set(R receiver, Object newValue, /* unused */ Object oldValue) {
try {
field.set(receiver, newValue);
}
catch (IllegalArgumentException | IllegalAccessException e) {
throw new AssertionError(
"Could not set " + field + " = " + newValue + " for option " + key, e);
}
}
@Override
public CategoryAndName getKey() {
return key;
}
}
protected enum ParamOrder {
NONE((o, n) -> new Object[] {}),
NEW_ONLY((o, n) -> new Object[] { n }),
OLD_ONLY((o, n) -> new Object[] { o }),
NEW_OLD((o, n) -> new Object[] { n, o }),
OLD_NEW((o, n) -> new Object[] { o, n });
private final BiFunction<Object, Object, Object[]> pop;
ParamOrder(BiFunction<Object, Object, Object[]> pop) {
this.pop = pop;
}
public Object[] populate(Object oldVal, Object newVal) {
return this.pop.apply(oldVal, newVal);
}
}
protected static class MethodOptionSetter<R> implements OptionSetter<R> {
protected final Method method;
protected final CategoryAndName key;
protected final ParamOrder order;
public MethodOptionSetter(Method method, AutoOptionConsumed annotation, Plugin plugin) {
this(method, new CategoryAndName(annotation, plugin));
}
public MethodOptionSetter(Method method, String category, String name) {
this(method, new CategoryAndName(category, name));
}
public MethodOptionSetter(Method method, CategoryAndName key) {
this.method = method;
this.key = key;
method.setAccessible(true);
Parameter[] parameters = method.getParameters();
if (parameters.length == 0) {
this.order = ParamOrder.NONE;
}
else if (parameters.length == 1) {
if (parameters[0].getAnnotation(OldValue.class) != null) {
this.order = ParamOrder.OLD_ONLY;
}
else {
this.order = ParamOrder.NEW_ONLY;
}
}
else if (parameters.length == 2) {
if (parameters[0].getAnnotation(NewValue.class) != null) {
if (parameters[1].getAnnotation(NewValue.class) != null) {
throw new IllegalArgumentException("Cannot apply " +
NewValue.class.getName() + " to both parameters of " + method);
}
this.order = ParamOrder.NEW_OLD;
}
else if (parameters[0].getAnnotation(OldValue.class) != null) {
if (parameters[1].getAnnotation(OldValue.class) != null) {
throw new IllegalArgumentException("Cannot apply " +
OldValue.class.getName() + " to both parameters of " + method);
}
this.order = ParamOrder.OLD_NEW;
}
else {
if (parameters[1].getAnnotation(NewValue.class) != null) {
this.order = ParamOrder.OLD_NEW;
}
else {
this.order = ParamOrder.NEW_OLD;
}
}
}
else {
throw new IllegalArgumentException(AutoOptionConsumed.class + "-annotated method " +
method + " cannot have more than two parameters");
}
}
@Override
public void set(R receiver, Object newValue, Object oldValue) {
Object[] args = order.populate(oldValue, newValue);
try {
method.invoke(receiver, args);
}
catch (IllegalArgumentException | IllegalAccessException e) {
String argsStr = StringUtils.join(args, ",");
// Don't throw, so other consumers get updated
Msg.error(this,
"Could not invoke " + method + "(" + argsStr + ") for option " + key, e);
}
catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
}
String argsStr = StringUtils.join(args, ",");
// Don't throw, so other consumers get updated
Msg.error(this,
"Error during invocation of " + method + "(" + argsStr + ") for option " + key,
e.getCause());
}
}
@Override
public CategoryAndName getKey() {
return key;
}
}
protected static class ReceiverProfile<R> {
protected final Map<CategoryAndName, Set<OptionSetter<R>>> settersByOption =
new HashMap<>();
protected final Set<String> categories = new HashSet<>();
protected final Set<String> categoriesView = Collections.unmodifiableSet(categories);
@SuppressWarnings({ "rawtypes", "unchecked" })
public ReceiverProfile(Class<R> receiverCls, Plugin plugin) {
for (OptionSetter<?> setter : collectSettersByReceiver(receiverCls, plugin)) {
CategoryAndName key = setter.getKey();
Set<OptionSetter<R>> settersForReceiver =
settersByOption.computeIfAbsent(key, k -> new HashSet<>());
settersForReceiver.add((OptionSetter) setter);
categories.add(key.getCategory());
}
}
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
Object newValue, R receiver) {
if (oldValue == null) {
// TODO: Wish Ghidra would give default as old value, in case null was actual value
// Maybe Ghidra does not allow null?
oldValue = options.getDefaultValue(optionName);
}
CategoryAndName key = new CategoryAndName(options.getName(), optionName);
Set<OptionSetter<R>> settersForOption = settersByOption.get(key);
if (settersForOption == null) {
return; // Receiver does not consume this option
}
for (OptionSetter<R> setter : settersForOption) {
setter.set(receiver, newValue, oldValue);
}
}
public void notifyCurrentValues(PluginTool tool, R receiver) {
for (Map.Entry<CategoryAndName, Set<OptionSetter<R>>> ent : settersByOption
.entrySet()) {
CategoryAndName key = ent.getKey();
ToolOptions options = tool.getOptions(key.getCategory());
Option opt = options.getOption(key.getName(), OptionType.NO_TYPE, null);
if (!opt.isRegistered()) {
continue;
}
Object newValue = opt.getValue(null);
Object oldValue = opt.getDefaultValue();
for (OptionSetter<R> setter : ent.getValue()) {
setter.set(receiver, newValue, oldValue);
}
}
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
protected static <R> Set<OptionSetter<R>> collectSettersByReceiver(Class<R> cls,
Plugin plugin) {
synchronized (SETTERS_BY_RECEIVER_CLASS) {
if (SETTERS_BY_RECEIVER_CLASS.containsKey(cls)) {
return (Set) SETTERS_BY_RECEIVER_CLASS.get(cls);
}
Set<OptionSetter<?>> result = new HashSet<>();
SETTERS_BY_RECEIVER_CLASS.put(cls, result);
Class<?> superclass = cls.getSuperclass();
if (superclass != null) {
Set<OptionSetter<?>> superResult =
(Set) collectSettersByReceiver(superclass, plugin);
result.addAll(superResult);
}
for (Class<?> superiface : cls.getInterfaces()) {
Set<OptionSetter<?>> superResult =
(Set) collectSettersByReceiver(superiface, plugin);
result.addAll(superResult);
}
for (Field f : cls.getDeclaredFields()) {
AutoOptionDefined defined = f.getAnnotation(AutoOptionDefined.class);
if (defined != null) {
try {
result.add(new FieldOptionSetter(f, defined, plugin));
}
catch (IllegalArgumentException e) {
Msg.error(AutoOptionsListener.class, e.getMessage());
}
}
AutoOptionConsumed consumed = f.getAnnotation(AutoOptionConsumed.class);
if (consumed != null) {
try {
// TODO: Validate type compatibility
// Potential problem: consumed options may be registered yet
result.add(new FieldOptionSetter(f, consumed, plugin));
}
catch (IllegalArgumentException e) {
Msg.error(AutoOptionsListener.class, e.getMessage());
}
}
}
for (Method m : cls.getDeclaredMethods()) {
AutoOptionConsumed consumed = m.getAnnotation(AutoOptionConsumed.class);
if (consumed == null) {
continue;
}
try {
result.add(new MethodOptionSetter(m, consumed, plugin));
}
catch (IllegalArgumentException e) {
Msg.error(AutoOptionsListener.class, e.getMessage());
}
}
return (Set) result;
}
}
protected final R receiver;
protected final ReceiverProfile<R> profile;
@SuppressWarnings("unchecked")
public AutoOptionsListener(Plugin plugin, R receiver) {
this.receiver = receiver;
this.profile = (ReceiverProfile<R>) PROFILES_BY_RECEIVER_CLASS
.computeIfAbsent(receiver.getClass(), cls -> new ReceiverProfile<>(cls, plugin));
}
@Override
public void optionsChanged(ToolOptions options, String optionName, Object oldValue,
Object newValue) {
profile.optionsChanged(options, optionName, oldValue, newValue, receiver);
}
public void notifyCurrentValues(PluginTool tool) {
profile.notifyCurrentValues(tool, receiver);
}
public Set<String> getCategories() {
return profile.categoriesView;
}
}
@@ -0,0 +1,26 @@
/* ###
* 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.options.annotation;
import java.lang.annotation.*;
@Target({ ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoOptionConsumed {
String[] category() default {};
String[] name();
}
@@ -0,0 +1,37 @@
/* ###
* 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.options.annotation;
import java.beans.PropertyEditor;
import java.lang.annotation.*;
import ghidra.framework.options.OptionType;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoOptionDefined {
String[] category() default {};
OptionType type() default OptionType.NO_TYPE;
String[] name();
HelpInfo help() default @HelpInfo(topic = {});
String description();
Class<? extends PropertyEditor> editor() default PropertyEditor.class;
}
@@ -0,0 +1,26 @@
/* ###
* 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.options.annotation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface HelpInfo {
String[] topic() default {};
String anchor() default "";
}
@@ -0,0 +1,493 @@
/* ###
* 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.plugintool;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.*;
import java.util.*;
import ghidra.framework.options.SaveState;
import ghidra.framework.plugintool.annotation.AutoConfigStateField;
import ghidra.framework.plugintool.annotation.AutoConfigStateField.DefaultConfigFieldCodec;
public interface AutoConfigState {
interface ConfigFieldCodec<T> {
T read(SaveState state, String name, T current);
void write(SaveState state, String name, T value);
}
static class BooleanConfigFieldCodec implements ConfigFieldCodec<Boolean> {
public static final BooleanConfigFieldCodec INSTANCE = new BooleanConfigFieldCodec();
@Override
public Boolean read(SaveState state, String name, Boolean current) {
return state.getBoolean(name, false);
}
@Override
public void write(SaveState state, String name, Boolean value) {
state.putBoolean(name, value);
}
}
static class ByteConfigFieldCodec implements ConfigFieldCodec<Byte> {
public static final ByteConfigFieldCodec INSTANCE = new ByteConfigFieldCodec();
@Override
public Byte read(SaveState state, String name, Byte current) {
return state.getByte(name, (byte) 0);
}
@Override
public void write(SaveState state, String name, Byte value) {
state.putByte(name, value);
}
}
static class ShortConfigFieldCodec implements ConfigFieldCodec<Short> {
public static final ShortConfigFieldCodec INSTANCE = new ShortConfigFieldCodec();
@Override
public Short read(SaveState state, String name, Short current) {
return state.getShort(name, (short) 0);
}
@Override
public void write(SaveState state, String name, Short value) {
state.putShort(name, value);
}
}
static class IntConfigFieldCodec implements ConfigFieldCodec<Integer> {
public static final IntConfigFieldCodec INSTANCE = new IntConfigFieldCodec();
@Override
public Integer read(SaveState state, String name, Integer current) {
return state.getInt(name, 0);
}
@Override
public void write(SaveState state, String name, Integer value) {
state.putInt(name, value);
}
}
static class LongConfigFieldCodec implements ConfigFieldCodec<Long> {
public static final LongConfigFieldCodec INSTANCE = new LongConfigFieldCodec();
@Override
public Long read(SaveState state, String name, Long current) {
return state.getLong(name, 0);
}
@Override
public void write(SaveState state, String name, Long value) {
state.putLong(name, value);
}
}
static class FloatConfigFieldCodec implements ConfigFieldCodec<Float> {
public static final FloatConfigFieldCodec INSTANCE = new FloatConfigFieldCodec();
@Override
public Float read(SaveState state, String name, Float current) {
return state.getFloat(name, 0);
}
@Override
public void write(SaveState state, String name, Float value) {
state.putFloat(name, value);
}
}
static class DoubleConfigFieldCodec implements ConfigFieldCodec<Double> {
public static final DoubleConfigFieldCodec INSTANCE = new DoubleConfigFieldCodec();
@Override
public Double read(SaveState state, String name, Double current) {
return state.getDouble(name, 0);
}
@Override
public void write(SaveState state, String name, Double value) {
state.putDouble(name, value);
}
}
static class StringConfigFieldCodec implements ConfigFieldCodec<String> {
public static final StringConfigFieldCodec INSTANCE = new StringConfigFieldCodec();
@Override
public String read(SaveState state, String name, String current) {
return state.getString(name, null);
}
@Override
public void write(SaveState state, String name, String value) {
state.putString(name, value);
}
}
static class BooleanArrayConfigFieldCodec implements ConfigFieldCodec<boolean[]> {
public static final BooleanArrayConfigFieldCodec INSTANCE =
new BooleanArrayConfigFieldCodec();
@Override
public boolean[] read(SaveState state, String name, boolean[] current) {
return state.getBooleans(name, null);
}
@Override
public void write(SaveState state, String name, boolean[] value) {
state.putBooleans(name, value);
}
}
static class ByteArrayConfigFieldCodec implements ConfigFieldCodec<byte[]> {
public static final ByteArrayConfigFieldCodec INSTANCE = new ByteArrayConfigFieldCodec();
@Override
public byte[] read(SaveState state, String name, byte[] current) {
return state.getBytes(name, null);
}
@Override
public void write(SaveState state, String name, byte[] value) {
state.putBytes(name, value);
}
}
static class ShortArrayConfigFieldCodec implements ConfigFieldCodec<short[]> {
public static final ShortArrayConfigFieldCodec INSTANCE = new ShortArrayConfigFieldCodec();
@Override
public short[] read(SaveState state, String name, short[] current) {
return state.getShorts(name, null);
}
@Override
public void write(SaveState state, String name, short[] value) {
state.putShorts(name, value);
}
}
static class IntArrayConfigFieldCodec implements ConfigFieldCodec<int[]> {
public static final IntArrayConfigFieldCodec INSTANCE = new IntArrayConfigFieldCodec();
@Override
public int[] read(SaveState state, String name, int[] current) {
return state.getInts(name, null);
}
@Override
public void write(SaveState state, String name, int[] value) {
state.putInts(name, value);
}
}
static class LongArrayConfigFieldCodec implements ConfigFieldCodec<long[]> {
public static final LongArrayConfigFieldCodec INSTANCE = new LongArrayConfigFieldCodec();
@Override
public long[] read(SaveState state, String name, long[] current) {
return state.getLongs(name, null);
}
@Override
public void write(SaveState state, String name, long[] value) {
state.putLongs(name, value);
}
}
static class FloatArrayConfigFieldCodec implements ConfigFieldCodec<float[]> {
public static final FloatArrayConfigFieldCodec INSTANCE = new FloatArrayConfigFieldCodec();
@Override
public float[] read(SaveState state, String name, float[] current) {
return state.getFloats(name, null);
}
@Override
public void write(SaveState state, String name, float[] value) {
state.putFloats(name, value);
}
}
static class DoubleArrayConfigFieldCodec implements ConfigFieldCodec<double[]> {
public static final DoubleArrayConfigFieldCodec INSTANCE =
new DoubleArrayConfigFieldCodec();
@Override
public double[] read(SaveState state, String name, double[] current) {
return state.getDoubles(name, null);
}
@Override
public void write(SaveState state, String name, double[] value) {
state.putDoubles(name, value);
}
}
static class StringArrayConfigFieldCodec implements ConfigFieldCodec<String[]> {
public static final StringArrayConfigFieldCodec INSTANCE =
new StringArrayConfigFieldCodec();
@Override
public String[] read(SaveState state, String name, String[] current) {
return state.getStrings(name, null);
}
@Override
public void write(SaveState state, String name, String[] value) {
state.putStrings(name, value);
}
}
static class EnumConfigFieldCodec implements ConfigFieldCodec<Enum<?>> {
public static final EnumConfigFieldCodec INSTANCE = new EnumConfigFieldCodec();
@Override
public Enum<?> read(SaveState state, String name, Enum<?> current) {
return state.getEnum(name, null);
}
@Override
public void write(SaveState state, String name, Enum<?> value) {
state.putEnum(name, value);
}
}
class ConfigStateField<T> {
private static final Map<Class<?>, ConfigFieldCodec<?>> CODECS_BY_TYPE = new HashMap<>();
private static final Map<Class<?>, ConfigFieldCodec<?>> CODECS_BY_SPEC = new HashMap<>();
static {
addCodec(boolean.class, BooleanConfigFieldCodec.INSTANCE);
addCodec(Boolean.class, BooleanConfigFieldCodec.INSTANCE);
addCodec(byte.class, ByteConfigFieldCodec.INSTANCE);
addCodec(Byte.class, ByteConfigFieldCodec.INSTANCE);
addCodec(short.class, ShortConfigFieldCodec.INSTANCE);
addCodec(Short.class, ShortConfigFieldCodec.INSTANCE);
addCodec(int.class, IntConfigFieldCodec.INSTANCE);
addCodec(Integer.class, IntConfigFieldCodec.INSTANCE);
addCodec(long.class, LongConfigFieldCodec.INSTANCE);
addCodec(Long.class, LongConfigFieldCodec.INSTANCE);
addCodec(float.class, FloatConfigFieldCodec.INSTANCE);
addCodec(Float.class, FloatConfigFieldCodec.INSTANCE);
addCodec(double.class, DoubleConfigFieldCodec.INSTANCE);
addCodec(Double.class, DoubleConfigFieldCodec.INSTANCE);
addCodec(String.class, StringConfigFieldCodec.INSTANCE);
addCodec(boolean[].class, BooleanArrayConfigFieldCodec.INSTANCE);
addCodec(byte[].class, ByteArrayConfigFieldCodec.INSTANCE);
addCodec(short[].class, ShortArrayConfigFieldCodec.INSTANCE);
addCodec(int[].class, IntArrayConfigFieldCodec.INSTANCE);
addCodec(long[].class, LongArrayConfigFieldCodec.INSTANCE);
addCodec(float[].class, FloatArrayConfigFieldCodec.INSTANCE);
addCodec(double[].class, DoubleArrayConfigFieldCodec.INSTANCE);
addCodec(String[].class, StringArrayConfigFieldCodec.INSTANCE);
}
private static <T> void addCodec(Class<T> cls, ConfigFieldCodec<T> codec) {
CODECS_BY_TYPE.put(cls, codec);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T> ConfigFieldCodec<T> getCodecByType(Class<T> cls) {
if (Enum.class.isAssignableFrom(cls)) {
return (ConfigFieldCodec) EnumConfigFieldCodec.INSTANCE;
}
return (ConfigFieldCodec) CODECS_BY_TYPE.get(cls);
}
private static <T extends ConfigFieldCodec<?>> T getCodecBySpec(Class<T> cls) {
synchronized (CODECS_BY_SPEC) {
@SuppressWarnings("unchecked")
T codec = (T) CODECS_BY_SPEC.get(cls);
if (codec != null) {
return codec;
}
try {
Constructor<T> constructor = cls.getConstructor();
codec = constructor.newInstance();
CODECS_BY_SPEC.put(cls, codec);
return codec;
}
catch (NoSuchMethodException | InstantiationException | IllegalAccessException
| IllegalArgumentException | InvocationTargetException e) {
throw new AssertionError(
"Illegal codec specification. Constructor() cannot be invoked: " + cls, e);
}
}
}
/**
* Put an object into a {@link SaveState} using a known codec
*
* <p>
* This seems like something that should be in SaveState itself, but the object value must
* be one of the supported types.
*
* @param <T> the type of the value
* @param state the state to write into
* @param type the type of the value
* @param name the name of the name-value pair
* @param value the value of the name-value pair
*/
public static <T> void putState(SaveState state, Class<T> type, String name, T value) {
ConfigFieldCodec<T> codec = getCodecByType(type);
if (codec == null) {
throw new IllegalArgumentException("No codec for type " + type);
}
codec.write(state, name, value);
}
/**
* Get an object from a {@link SaveState} using a known codec
*
* <p>
* This seems like something that should be in SaveState itself.
*
* @param <T> the type of the value
* @param state the state to read from
* @param type the expected type of the value
* @param name the name of the name-value pair
* @return the value of the name-value pair
*/
public static <T> T getState(SaveState state, Class<T> type, String name) {
ConfigFieldCodec<T> codec = getCodecByType(type);
if (codec == null) {
throw new IllegalArgumentException("No codec for type " + type);
}
return codec.read(state, name, null);
}
private final MethodHandle getter;
private final MethodHandle setter;
private final ConfigFieldCodec<T> codec;
private final String name;
@SuppressWarnings("unchecked")
private ConfigStateField(AutoConfigStateField annot, Field f, Class<T> type, Lookup lookup)
throws IllegalAccessException {
getter = lookup.unreflectGetter(f);
setter = Modifier.isFinal(f.getModifiers()) ? null : lookup.unreflectSetter(f);
name = f.getName();
@SuppressWarnings("rawtypes")
Class<? extends ConfigFieldCodec> codecCls = annot.codec();
if (codecCls == DefaultConfigFieldCodec.class) {
codec = getCodecByType(type);
}
else {
// TODO: Type check here, or in an annotation processor
codec = getCodecBySpec(codecCls);
}
if (codec == null) {
throw new AssertionError(AutoConfigStateField.class.getSimpleName() +
": Specify a codec for " + f + ".");
}
}
private void save(Object from, SaveState into) {
T val;
try {
val = (T) getter.invoke(from);
}
catch (Throwable e) {
throw new AssertionError(e);
}
assert val != null;
codec.write(into, name, val);
}
private void load(Object into, SaveState from) {
if (!from.hasValue(name)) {
return; // leave the intial value as "default"
}
try {
T current = (T) getter.invoke(into);
T val = codec.read(from, name, current);
if (val == null || val == current) {
return;
}
if (setter == null) {
throw new IllegalAccessException("Codec cannot modify final field: " + name);
}
setter.invoke(into, val);
}
catch (Throwable e) {
throw new AssertionError(e);
}
}
}
class ClassHandler<T> {
private final Set<ConfigStateField<?>> fields = new LinkedHashSet<>();
ClassHandler(Class<T> cls, Lookup lookup) throws IllegalAccessException {
gatherAnnotatedFields(cls, lookup);
}
private void gatherAnnotatedFields(Class<?> cls, Lookup lookup)
throws IllegalAccessException {
for (Field f : cls.getDeclaredFields()) {
AutoConfigStateField annot = f.getAnnotation(AutoConfigStateField.class);
if (annot == null) {
continue;
}
fields.add(new ConfigStateField<>(annot, f, f.getType(), lookup));
}
}
public void writeConfigState(T from, SaveState into) {
for (ConfigStateField<?> f : fields) {
f.save(from, into);
}
}
public void readConfigState(T into, SaveState from) {
for (ConfigStateField<?> f : fields) {
f.load(into, from);
}
}
}
/**
* Wire up a handler for the given class, using the given lookup
*
* <p>
* This does not consider super classes, since the writeConfigState of a class using this and
* the applicable annotations should likely call super.writeConfigState to allow the super class
* to handle its fields, whether or not it also uses the annotations.
*
* @param <T> the type of the class whose fields are annotated by {@link AutoConfigStateField}
* @param cls the class whose fields are annotated
* @param lookup a lookup from within the class, granting access to the annotated fields
* @return the handler
*/
static <T> ClassHandler<T> wireHandler(Class<T> cls, Lookup lookup) {
try {
return new ClassHandler<>(cls, lookup);
}
catch (IllegalAccessException e) {
throw new AssertionError(e);
}
}
}
@@ -0,0 +1,88 @@
/* ###
* 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.plugintool;
import java.lang.reflect.Field;
import ghidra.framework.plugintool.annotation.AutoServiceProvided;
import ghidra.framework.plugintool.util.AutoServiceListener;
import ghidra.util.Msg;
public interface AutoService {
public interface Wiring {
void dispose();
}
static class WiringImpl implements Wiring {
@SuppressWarnings("unused") // strong reference
private AutoServiceListener<?> listener;
public WiringImpl(AutoServiceListener<?> listener) {
this.listener = listener;
}
@Override
public void dispose() {
this.listener = null;
}
}
public static Wiring wireServicesProvidedAndConsumed(Plugin plugin) {
registerServicesProvided(plugin, plugin.getClass(), plugin);
return wireServicesConsumed(plugin, plugin);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
static void registerServicesProvided(Plugin plugin, Class<?> cls, Object provider) {
Class<?> superclass = cls.getSuperclass();
if (superclass != null) {
registerServicesProvided(plugin, superclass, provider);
}
for (Field f : cls.getDeclaredFields()) {
AutoServiceProvided annotation = f.getAnnotation(AutoServiceProvided.class);
if (annotation == null) {
continue;
}
Class<?> iface = annotation.iface();
Class<?> type = f.getType();
if (!iface.isAssignableFrom(type)) {
Msg.error(AutoService.class,
type + " does not implement service interface " + iface);
continue;
}
boolean wasAccessible = f.isAccessible();
f.setAccessible(true);
try {
plugin.registerServiceProvided((Class) iface, f.get(provider));
}
catch (IllegalArgumentException | IllegalAccessException e) {
throw new AssertionError(e);
}
f.setAccessible(wasAccessible);
}
}
public static Wiring wireServicesConsumed(Plugin plugin, Object receiver) {
// TODO: Validate against PluginInfo?
AutoServiceListener<Object> listener = new AutoServiceListener<>(receiver);
PluginTool tool = plugin.getTool();
tool.addServiceListener(listener);
listener.notifyCurrentServices(tool);
return new WiringImpl(listener);
}
}
@@ -0,0 +1,97 @@
/* ###
* 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.plugintool;
import java.util.function.Function;
import docking.DockingWindowManager;
import docking.Tool;
import ghidra.framework.model.DomainFile;
public enum PluginToolUtils {
;
/**
* Attempts the given action in all running tools, starting with the active one, if applicable
*
* <p>
* This will stop once the given action returns a non-null result, and return that result.
*
* @param <T> the type of result
* @param tool the front-end tool, whose running plugin tools to try
* @param action the action to apply to each plugin tool
* @return the first non-null result of the action, or null if all plugin tools were exhausted
*/
public static <T> T inRunningToolsPreferringActive(PluginTool tool,
Function<? super PluginTool, ? extends T> action) {
Tool activeTool = DockingWindowManager.getActiveInstance().getTool();
if (activeTool instanceof PluginTool) {
PluginTool activePluginTool = (PluginTool) activeTool;
T result = action.apply(activePluginTool);
if (result != null) {
return result;
}
}
for (PluginTool pt : tool.getToolServices().getRunningTools()) {
T result = action.apply(pt);
if (result != null) {
return result;
}
}
return null;
}
/**
* Opens the given domain file in the most recent tool which can accept it, or it launches a new
* tool to accept it
*
* <p>
* TODO: This currently fails in the "most-recent" aspect if a non-compatible tool has focus. In
* that case, it'll pick any compatible tool, no matter how recently it had focus.
*
* @param tool the front-end tool
* @param domainFile the domain file to open
* @return the (possibly new) plugin tool which accepted the domain file
*/
public static PluginTool openInMostRecentOrLaunchedCompatibleTool(PluginTool tool,
DomainFile domainFile) {
DomainFile[] data = new DomainFile[] { domainFile };
PluginTool result = inRunningToolsPreferringActive(tool, pt -> {
return pt.acceptDomainFiles(data) ? pt : null;
});
if (result != null) {
return result;
}
return tool.getToolServices().launchDefaultTool(domainFile);
}
/**
* Get the service for the given class from the most recent tool having it
*
* <p>
* TODO: This currently fails in the "most-recent" aspect if a non-compatible tool has focus. In
* that case, it'll pick any compatible tool, no matter how recently it had focus.
*
* @param <T> the type of the service
* @param tool the front-end tool
* @param serviceClass the class of the service
* @return the found service, or {@code null} if no running tool has it
*/
public static <T> T getServiceFromRunningCompatibleTool(PluginTool tool,
Class<T> serviceClass) {
return inRunningToolsPreferringActive(tool, pt -> pt.getService(serviceClass));
}
}
@@ -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.plugintool.annotation;
import java.lang.annotation.*;
import ghidra.framework.plugintool.AutoConfigState.ConfigFieldCodec;
@Target({ ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoConfigStateField {
@SuppressWarnings("rawtypes")
Class<? extends ConfigFieldCodec> codec() default DefaultConfigFieldCodec.class;
static abstract class DefaultConfigFieldCodec<T> implements ConfigFieldCodec<T> {
public DefaultConfigFieldCodec() {
throw new AssertionError();
}
}
}
@@ -0,0 +1,24 @@
/* ###
* 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.plugintool.annotation;
import java.lang.annotation.*;
@Target({ ElementType.FIELD, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoServiceConsumed {
// No attributes
}

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