mirror of
https://github.com/NationalSecurityAgency/ghidra.git
synced 2026-05-27 21:26:02 +08:00
GP-71: Prepping for source release.
This commit is contained in:
@@ -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|
|
||||
+27
@@ -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);
|
||||
}
|
||||
+20
@@ -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;
|
||||
}
|
||||
}
|
||||
+20
@@ -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));
|
||||
}
|
||||
}
|
||||
+88
@@ -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;
|
||||
}
|
||||
}
|
||||
+96
@@ -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;
|
||||
}
|
||||
}
|
||||
+298
@@ -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();
|
||||
}
|
||||
}
|
||||
+24
@@ -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);
|
||||
}
|
||||
+43
@@ -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();
|
||||
}
|
||||
+63
@@ -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();
|
||||
}
|
||||
}
|
||||
+49
@@ -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);
|
||||
}
|
||||
}
|
||||
+62
@@ -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);
|
||||
}
|
||||
}
|
||||
+45
@@ -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;
|
||||
}
|
||||
}
|
||||
+75
@@ -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);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
+22
@@ -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);
|
||||
}
|
||||
+43
@@ -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);
|
||||
}
|
||||
}
|
||||
+46
@@ -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
|
||||
}
|
||||
}
|
||||
+57
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+153
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+32
@@ -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;
|
||||
}
|
||||
}
|
||||
+33
@@ -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;
|
||||
}
|
||||
}
|
||||
+235
@@ -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);
|
||||
}
|
||||
}
|
||||
+91
@@ -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);
|
||||
}
|
||||
}
|
||||
+335
@@ -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;
|
||||
}
|
||||
}
|
||||
+26
@@ -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();
|
||||
}
|
||||
+37
@@ -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;
|
||||
}
|
||||
+26
@@ -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 "";
|
||||
}
|
||||
+493
@@ -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);
|
||||
}
|
||||
}
|
||||
+97
@@ -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));
|
||||
}
|
||||
}
|
||||
+33
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+24
@@ -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
Reference in New Issue
Block a user