fix(gui): improve code tabs, sync and related code
This commit is contained in:
@@ -899,13 +899,4 @@ public class JadxSettings {
|
||||
public void setSaveOption(SaveOptionEnum saveOption) {
|
||||
settingsData.setSaveOption(saveOption);
|
||||
}
|
||||
|
||||
public boolean isSmaliAreaShowBytecode() {
|
||||
return settingsData.isSmaliAreaShowBytecode();
|
||||
}
|
||||
|
||||
public void setSmaliAreaShowBytecode(boolean smaliAreaShowBytecode) {
|
||||
settingsData.setSmaliAreaShowBytecode(smaliAreaShowBytecode);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -80,7 +80,6 @@ public class JadxSettingsData extends JadxGUIArgs {
|
||||
private int searchResultsPerPage = 50;
|
||||
private boolean useAutoSearch = true;
|
||||
private boolean keepCommonDialogOpen = false;
|
||||
private boolean smaliAreaShowBytecode = false;
|
||||
private LineNumbersMode lineNumbersMode = LineNumbersMode.AUTO;
|
||||
|
||||
private int mainWindowVerticalSplitterLoc = 300;
|
||||
@@ -398,14 +397,6 @@ public class JadxSettingsData extends JadxGUIArgs {
|
||||
this.showHeapUsageBar = showHeapUsageBar;
|
||||
}
|
||||
|
||||
public boolean isSmaliAreaShowBytecode() {
|
||||
return smaliAreaShowBytecode;
|
||||
}
|
||||
|
||||
public void setSmaliAreaShowBytecode(boolean smaliAreaShowBytecode) {
|
||||
this.smaliAreaShowBytecode = smaliAreaShowBytecode;
|
||||
}
|
||||
|
||||
public String getUiFontStr() {
|
||||
return uiFontStr;
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ public abstract class AbstractCodeContentPanel extends ContentPanel {
|
||||
|
||||
public abstract Component getChildrenComponent();
|
||||
|
||||
@Override
|
||||
public void scrollToPos(int pos) {
|
||||
AbstractCodeArea codeArea = getCodeArea();
|
||||
if (codeArea != null) {
|
||||
|
||||
@@ -12,20 +12,27 @@ import javax.swing.JTabbedPane;
|
||||
import javax.swing.JToolBar;
|
||||
import javax.swing.SwingUtilities;
|
||||
import javax.swing.border.EmptyBorder;
|
||||
import javax.swing.event.CaretListener;
|
||||
import javax.swing.text.JTextComponent;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.DecompilationMode;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.ui.codearea.mode.JCodeMode;
|
||||
import jadx.gui.ui.codearea.sync.CodePanelSyncee;
|
||||
import jadx.gui.ui.codearea.sync.CodePanelSyncer;
|
||||
import jadx.gui.ui.codearea.sync.CodePanelSyncerAbstractFactory;
|
||||
import jadx.gui.ui.codearea.sync.CodeAreaSyncee;
|
||||
import jadx.gui.ui.codearea.sync.CodeAreaSyncer;
|
||||
import jadx.gui.ui.codearea.sync.CodeAreaSyncerAbstractFactory;
|
||||
import jadx.gui.ui.codearea.sync.fallback.FallbackSyncer;
|
||||
import jadx.gui.ui.panel.IViewStateSupport;
|
||||
import jadx.gui.ui.tab.TabbedPane;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.ui.ListenersHelper;
|
||||
|
||||
import static com.formdev.flatlaf.FlatClientProperties.TABBED_PANE_TRAILING_COMPONENT;
|
||||
|
||||
@@ -41,152 +48,92 @@ public final class ClassCodeContentPanel extends AbstractCodeContentPanel implem
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ClassCodeContentPanel.class);
|
||||
private static final long serialVersionUID = -7229931102504634591L;
|
||||
|
||||
private final transient CodePanel javaCodePanel;
|
||||
private final transient CodePanel smaliCodePanel;
|
||||
private final transient JTabbedPane areaTabbedPane;
|
||||
private final JClass jCls;
|
||||
private final ListenersHelper<JTextComponent, CaretListener> caretListeners = ListenersHelper.buildForCaretListener();
|
||||
private final AtomicBoolean syncInProgress = new AtomicBoolean(false);
|
||||
|
||||
private boolean splitView = false;
|
||||
private final JCheckBox splitCheckboxNormal;
|
||||
private final JTabbedPane leftTabbedPane;
|
||||
private @Nullable JTabbedPane rightTabbedPane;
|
||||
private CodePanel javaCodePanel;
|
||||
private CodePanel smaliCodePanel;
|
||||
|
||||
public ClassCodeContentPanel(TabbedPane panel, JClass jCls) {
|
||||
super(panel, jCls);
|
||||
private boolean isSplitViewActivated = false;
|
||||
|
||||
javaCodePanel = new CodePanel(new CodeArea(this, jCls));
|
||||
smaliCodePanel = new CodePanel(new SmaliArea(this, jCls, false));
|
||||
areaTabbedPane = buildTabbedPane(jCls);
|
||||
splitCheckboxNormal = addCustomControls(areaTabbedPane, false);
|
||||
|
||||
javaCodePanel.load();
|
||||
initView(false);
|
||||
public ClassCodeContentPanel(TabbedPane panel, JClass jClass) {
|
||||
super(panel, jClass);
|
||||
jCls = jClass;
|
||||
leftTabbedPane = buildTabbedPane(jClass, true);
|
||||
addCustomControls(leftTabbedPane);
|
||||
initView();
|
||||
activateCodePanel(javaCodePanel);
|
||||
}
|
||||
|
||||
private void initView(boolean splitViewEnabled) {
|
||||
splitView = splitViewEnabled;
|
||||
private void initView() {
|
||||
removeAll();
|
||||
setLayout(new BorderLayout());
|
||||
setBorder(new EmptyBorder(0, 0, 0, 0));
|
||||
if (splitViewEnabled) {
|
||||
setupSplitPane();
|
||||
if (isSplitViewActivated) {
|
||||
rightTabbedPane = buildTabbedPane(jCls, false);
|
||||
rightTabbedPane.setSelectedIndex(1); // default to Smali
|
||||
|
||||
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftTabbedPane, rightTabbedPane);
|
||||
splitPane.setResizeWeight(0.5);
|
||||
add(splitPane);
|
||||
revalidate();
|
||||
repaint();
|
||||
|
||||
// set divider location after layout
|
||||
SwingUtilities.invokeLater(() -> splitPane.setDividerLocation(0.5));
|
||||
} else {
|
||||
javaCodePanel.load();
|
||||
smaliCodePanel.load();
|
||||
attachSyncListeners(javaCodePanel, smaliCodePanel);
|
||||
areaTabbedPane.setSelectedIndex(0); // default to Java
|
||||
splitCheckboxNormal.setSelected(false);
|
||||
add(areaTabbedPane);
|
||||
disposeTabbedPane(rightTabbedPane);
|
||||
rightTabbedPane = null;
|
||||
add(leftTabbedPane);
|
||||
revalidate();
|
||||
repaint();
|
||||
}
|
||||
revalidate();
|
||||
repaint();
|
||||
}
|
||||
|
||||
private void attachSyncListeners(CodePanel javaPanel, CodePanel smaliPanel) {
|
||||
javaPanel.getCodeArea().addCaretListener(e -> {
|
||||
if (syncInProgress.get()) {
|
||||
return;
|
||||
}
|
||||
syncInProgress.set(true);
|
||||
syncToMethod(javaPanel, smaliPanel);
|
||||
syncInProgress.set(false);
|
||||
});
|
||||
|
||||
smaliPanel.getCodeArea().addCaretListener(e -> {
|
||||
if (syncInProgress.get()) {
|
||||
return;
|
||||
}
|
||||
syncInProgress.set(true);
|
||||
syncToMethod(smaliPanel, javaPanel);
|
||||
syncInProgress.set(false);
|
||||
});
|
||||
}
|
||||
|
||||
private void setupSplitPane() {
|
||||
JTabbedPane leftTabbedPane = new JTabbedPane(JTabbedPane.BOTTOM);
|
||||
JTabbedPane rightTabbedPane = new JTabbedPane(JTabbedPane.BOTTOM);
|
||||
|
||||
CodePanel[] leftPanels = {
|
||||
new CodePanel(new CodeArea(this, (JClass) node)), // Java
|
||||
new CodePanel(new SmaliArea(this, (JClass) node, false)), // Smali
|
||||
new CodePanel(new SmaliArea(this, (JClass) node, true)), // Smali with Dalvik
|
||||
new CodePanel(new CodeArea(this, new JCodeMode((JClass) node, DecompilationMode.SIMPLE))), // Simple
|
||||
new CodePanel(new CodeArea(this, new JCodeMode((JClass) node, DecompilationMode.FALLBACK))) // Fallback
|
||||
};
|
||||
|
||||
CodePanel[] rightPanels = {
|
||||
new CodePanel(new SmaliArea(this, (JClass) node, false)), // Smali
|
||||
new CodePanel(new SmaliArea(this, (JClass) node, true)), // Smali with Dalvik
|
||||
new CodePanel(new CodeArea(this, (JClass) node)), // Java
|
||||
new CodePanel(new CodeArea(this, new JCodeMode((JClass) node, DecompilationMode.SIMPLE))), // Simple
|
||||
new CodePanel(new CodeArea(this, new JCodeMode((JClass) node, DecompilationMode.FALLBACK))) // Fallback
|
||||
};
|
||||
|
||||
leftTabbedPane.add(leftPanels[0], NLS.str("tabs.code"));
|
||||
leftTabbedPane.add(leftPanels[1], NLS.str("tabs.smali"));
|
||||
leftTabbedPane.add(leftPanels[2], NLS.str("tabs.smali_bytecode"));
|
||||
leftTabbedPane.add(leftPanels[3], "Simple");
|
||||
leftTabbedPane.add(leftPanels[4], "Fallback");
|
||||
|
||||
rightTabbedPane.add(rightPanels[0], NLS.str("tabs.smali"));
|
||||
rightTabbedPane.add(rightPanels[1], NLS.str("tabs.smali_bytecode"));
|
||||
rightTabbedPane.add(rightPanels[2], NLS.str("tabs.code"));
|
||||
rightTabbedPane.add(rightPanels[3], "Simple");
|
||||
rightTabbedPane.add(rightPanels[4], "Fallback");
|
||||
|
||||
for (CodePanel p : leftPanels) {
|
||||
p.load();
|
||||
}
|
||||
for (CodePanel p : rightPanels) {
|
||||
p.load();
|
||||
}
|
||||
|
||||
leftTabbedPane.addChangeListener(e -> ((CodePanel) leftTabbedPane.getSelectedComponent()).load());
|
||||
rightTabbedPane.addChangeListener(e -> ((CodePanel) rightTabbedPane.getSelectedComponent()).load());
|
||||
|
||||
// Attach caret sync between all combinations
|
||||
for (CodePanel leftPanel : leftPanels) {
|
||||
for (CodePanel rightPanel : rightPanels) {
|
||||
attachSyncListeners(leftPanel, rightPanel);
|
||||
}
|
||||
}
|
||||
|
||||
// Create and configure split pane
|
||||
JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftTabbedPane, rightTabbedPane);
|
||||
splitPane.setResizeWeight(0.5);
|
||||
leftTabbedPane.setMinimumSize(new Dimension(200, 200));
|
||||
rightTabbedPane.setMinimumSize(new Dimension(200, 200));
|
||||
add(splitPane);
|
||||
|
||||
// Set divider location after layout
|
||||
SwingUtilities.invokeLater(() -> splitPane.setDividerLocation(0.5));
|
||||
|
||||
rightTabbedPane.setSelectedIndex(0);
|
||||
addCustomControls(leftTabbedPane, true);
|
||||
}
|
||||
|
||||
private JTabbedPane buildTabbedPane(JClass jCls) {
|
||||
private JTabbedPane buildTabbedPane(JClass jCls, boolean leftPanel) {
|
||||
JTabbedPane areaTabbedPane = new JTabbedPane(JTabbedPane.BOTTOM);
|
||||
areaTabbedPane.setBorder(new EmptyBorder(0, 0, 0, 0));
|
||||
areaTabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
|
||||
areaTabbedPane.add(javaCodePanel, NLS.str("tabs.code"));
|
||||
areaTabbedPane.add(smaliCodePanel, NLS.str("tabs.smali"));
|
||||
CodePanel javaPanel = new CodePanel(new CodeArea(this, jCls));
|
||||
CodePanel smaliPanel = new CodePanel(new SmaliArea(this, jCls, false));
|
||||
if (leftPanel) {
|
||||
this.javaCodePanel = javaPanel;
|
||||
this.smaliCodePanel = smaliPanel;
|
||||
}
|
||||
areaTabbedPane.add(javaPanel, NLS.str("tabs.code"));
|
||||
areaTabbedPane.add(smaliPanel, NLS.str("tabs.smali"));
|
||||
areaTabbedPane.add(new CodePanel(new SmaliArea(this, jCls, true)), NLS.str("tabs.smali_bytecode"));
|
||||
areaTabbedPane.add(new CodePanel(new CodeArea(this, new JCodeMode(jCls, DecompilationMode.SIMPLE))), "Simple");
|
||||
areaTabbedPane.add(new CodePanel(new CodeArea(this, new JCodeMode(jCls, DecompilationMode.FALLBACK))), "Fallback");
|
||||
areaTabbedPane.addChangeListener(e -> {
|
||||
CodePanel selectedPanel = (CodePanel) areaTabbedPane.getSelectedComponent();
|
||||
// TODO: to run background load extract ui update to other method
|
||||
selectedPanel.load();
|
||||
// execInBackground(selectedPanel::load);
|
||||
});
|
||||
areaTabbedPane.setMinimumSize(new Dimension(200, 200));
|
||||
areaTabbedPane.addChangeListener(e -> onCodePanelActivation((CodePanel) areaTabbedPane.getSelectedComponent()));
|
||||
return areaTabbedPane;
|
||||
}
|
||||
|
||||
private JCheckBox addCustomControls(JTabbedPane tabbedPane, boolean splitCheckboxInitialState) {
|
||||
JCheckBox splitCheckBox = new JCheckBox("Split view", splitCheckboxInitialState);
|
||||
private void onCodePanelActivation(CodePanel selectedPanel) {
|
||||
selectedPanel.load();
|
||||
updateSync();
|
||||
}
|
||||
|
||||
private void activateCodePanel(CodePanel javaCodePanel) {
|
||||
if (leftTabbedPane.getSelectedComponent() == javaCodePanel) {
|
||||
// already selected, change listener will not be called, run update manually
|
||||
onCodePanelActivation(javaCodePanel);
|
||||
} else {
|
||||
leftTabbedPane.setSelectedComponent(javaCodePanel);
|
||||
}
|
||||
}
|
||||
|
||||
private void addCustomControls(JTabbedPane tabbedPane) {
|
||||
JCheckBox splitCheckBox = new JCheckBox("Split view", false);
|
||||
splitCheckBox.addItemListener(e -> {
|
||||
boolean newSplitView = splitCheckBox.isSelected();
|
||||
if (splitView != newSplitView) {
|
||||
this.initView(newSplitView);
|
||||
if (isSplitViewActivated != newSplitView) {
|
||||
isSplitViewActivated = newSplitView;
|
||||
initView();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -197,18 +144,77 @@ public final class ClassCodeContentPanel extends AbstractCodeContentPanel implem
|
||||
trailing.addSeparator(new Dimension(50, 1));
|
||||
trailing.add(splitCheckBox);
|
||||
tabbedPane.putClientProperty(TABBED_PANE_TRAILING_COMPONENT, trailing);
|
||||
return splitCheckBox;
|
||||
}
|
||||
|
||||
private void updateSync() {
|
||||
caretListeners.removeAll();
|
||||
if (!isSplitViewActivated) {
|
||||
return;
|
||||
}
|
||||
AbstractCodeArea leftArea = getCodePanel(leftTabbedPane).getCodeArea();
|
||||
AbstractCodeArea rightArea = getCodePanel(rightTabbedPane).getCodeArea();
|
||||
if (leftArea instanceof CodeAreaSyncee && rightArea instanceof CodeAreaSyncee) {
|
||||
CodeAreaSyncer leftSyncer = buildCodeAreaSyncer(leftArea);
|
||||
CodeAreaSyncer rightSyncer = buildCodeAreaSyncer(rightArea);
|
||||
if (leftSyncer != null && rightSyncer != null) {
|
||||
caretListeners.add(leftArea, e -> syncCodeArea(leftArea, rightArea, leftSyncer));
|
||||
caretListeners.add(rightArea, e -> syncCodeArea(rightArea, leftArea, rightSyncer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void syncCodeArea(AbstractCodeArea fromArea, AbstractCodeArea toArea, CodeAreaSyncer syncer) {
|
||||
if (syncInProgress.get()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
syncInProgress.set(true);
|
||||
boolean synced = ((CodeAreaSyncee) toArea).sync(syncer);
|
||||
if (!synced) {
|
||||
if (!FallbackSyncer.sync(fromArea, toArea)) {
|
||||
LOG.warn("Code pane area sync not possible");
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LOG.warn("Failed to sync method/class across views: {}", ex.getLocalizedMessage());
|
||||
} finally {
|
||||
syncInProgress.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
private static CodePanel getCodePanel(@Nullable JTabbedPane tabbedPane) {
|
||||
if (tabbedPane == null) {
|
||||
throw new IllegalStateException("tabbedPane is null");
|
||||
}
|
||||
return (CodePanel) tabbedPane.getSelectedComponent();
|
||||
}
|
||||
|
||||
private static @Nullable CodeAreaSyncer buildCodeAreaSyncer(AbstractCodeArea codeArea) {
|
||||
if (codeArea instanceof CodeAreaSyncerAbstractFactory) {
|
||||
return ((CodeAreaSyncerAbstractFactory) codeArea).createCodeAreaSyncer();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadSettings() {
|
||||
javaCodePanel.loadSettings();
|
||||
smaliCodePanel.loadSettings();
|
||||
for (Component component : leftTabbedPane.getComponents()) {
|
||||
if (component instanceof CodePanel) {
|
||||
((CodePanel) component).loadSettings();
|
||||
}
|
||||
}
|
||||
if (rightTabbedPane != null) {
|
||||
for (Component component : rightTabbedPane.getComponents()) {
|
||||
if (component instanceof CodePanel) {
|
||||
((CodePanel) component).loadSettings();
|
||||
}
|
||||
}
|
||||
}
|
||||
updateUI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AbstractCodeArea getCodeArea() {
|
||||
public @NotNull AbstractCodeArea getCodeArea() {
|
||||
return javaCodePanel.getCodeArea();
|
||||
}
|
||||
|
||||
@@ -222,12 +228,12 @@ public final class ClassCodeContentPanel extends AbstractCodeContentPanel implem
|
||||
}
|
||||
|
||||
public void switchPanel() {
|
||||
boolean toSmali = areaTabbedPane.getSelectedComponent() == javaCodePanel;
|
||||
areaTabbedPane.setSelectedComponent(toSmali ? smaliCodePanel : javaCodePanel);
|
||||
boolean toSmali = leftTabbedPane.getSelectedComponent() == javaCodePanel;
|
||||
activateCodePanel(toSmali ? smaliCodePanel : javaCodePanel);
|
||||
}
|
||||
|
||||
public AbstractCodeArea getCurrentCodeArea() {
|
||||
return ((CodePanel) areaTabbedPane.getSelectedComponent()).getCodeArea();
|
||||
return ((CodePanel) leftTabbedPane.getSelectedComponent()).getCodeArea();
|
||||
}
|
||||
|
||||
public AbstractCodeArea getSmaliCodeArea() {
|
||||
@@ -235,25 +241,40 @@ public final class ClassCodeContentPanel extends AbstractCodeContentPanel implem
|
||||
}
|
||||
|
||||
public void showSmaliPane() {
|
||||
areaTabbedPane.setSelectedComponent(smaliCodePanel);
|
||||
activateCodePanel(smaliCodePanel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveEditorViewState(EditorViewState viewState) {
|
||||
CodePanel codePanel = (CodePanel) areaTabbedPane.getSelectedComponent();
|
||||
CodePanel codePanel = (CodePanel) leftTabbedPane.getSelectedComponent();
|
||||
int caretPos = codePanel.getCodeArea().getCaretPosition();
|
||||
Point viewPoint = codePanel.getCodeScrollPane().getViewport().getViewPosition();
|
||||
String subPath = codePanel == javaCodePanel ? "java" : "smali";
|
||||
viewState.setSubPath(subPath);
|
||||
viewState.setSubPath(String.valueOf(leftTabbedPane.getSelectedIndex()));
|
||||
viewState.setCaretPos(caretPos);
|
||||
viewState.setViewPoint(viewPoint);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreEditorViewState(EditorViewState viewState) {
|
||||
boolean isJava = viewState.getSubPath().equals("java");
|
||||
CodePanel activePanel = isJava ? javaCodePanel : smaliCodePanel;
|
||||
areaTabbedPane.setSelectedComponent(activePanel);
|
||||
UiUtils.uiThreadGuard();
|
||||
String subPath = viewState.getSubPath();
|
||||
CodePanel activePanel = null;
|
||||
if (subPath.equals("java")) {
|
||||
activePanel = javaCodePanel;
|
||||
} else if (subPath.equals("smali")) {
|
||||
activePanel = smaliCodePanel;
|
||||
} else {
|
||||
try {
|
||||
int index = Utils.safeParseInt(subPath, 0);
|
||||
activePanel = (CodePanel) leftTabbedPane.getComponentAt(index);
|
||||
} catch (Exception e) {
|
||||
LOG.debug("Failed to restore active code panel: {}", subPath, e);
|
||||
}
|
||||
}
|
||||
if (activePanel == null) {
|
||||
return;
|
||||
}
|
||||
activateCodePanel(activePanel);
|
||||
try {
|
||||
activePanel.getCodeScrollPane().getViewport().setViewPosition(viewState.getViewPoint());
|
||||
} catch (Exception e) {
|
||||
@@ -273,36 +294,19 @@ public final class ClassCodeContentPanel extends AbstractCodeContentPanel implem
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
javaCodePanel.dispose();
|
||||
smaliCodePanel.dispose();
|
||||
for (Component component : areaTabbedPane.getComponents()) {
|
||||
if (component instanceof CodePanel) {
|
||||
((CodePanel) component).dispose();
|
||||
}
|
||||
}
|
||||
caretListeners.removeAll();
|
||||
disposeTabbedPane(leftTabbedPane);
|
||||
disposeTabbedPane(rightTabbedPane);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
private void syncToMethod(CodePanel fromPanel, CodePanel toPanel) {
|
||||
if (!fromPanel.isShowing() || !toPanel.isShowing()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
AbstractCodeArea from = fromPanel.getCodeArea();
|
||||
AbstractCodeArea to = toPanel.getCodeArea();
|
||||
toPanel.load();
|
||||
|
||||
if (from instanceof CodePanelSyncerAbstractFactory && to instanceof CodePanelSyncee) {
|
||||
CodePanelSyncer syncer = ((CodePanelSyncerAbstractFactory) from).createCodePanelSyncer();
|
||||
if (((CodePanelSyncee) to).sync(syncer)) {
|
||||
return;
|
||||
private void disposeTabbedPane(@Nullable JTabbedPane tabbedPane) {
|
||||
if (tabbedPane != null) {
|
||||
for (Component component : tabbedPane.getComponents()) {
|
||||
if (component instanceof CodePanel) {
|
||||
((CodePanel) component).dispose();
|
||||
}
|
||||
}
|
||||
if (!FallbackSyncer.sync(fromPanel, toPanel)) {
|
||||
LOG.warn("Code pane area sync not possible");
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
LOG.warn("Failed to sync method/class across views: {}", ex.getLocalizedMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.awt.Point;
|
||||
import java.awt.event.InputEvent;
|
||||
import java.awt.event.MouseAdapter;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
@@ -15,6 +16,7 @@ import javax.swing.event.PopupMenuEvent;
|
||||
import org.fife.ui.rsyntaxtextarea.RSyntaxDocument;
|
||||
import org.fife.ui.rsyntaxtextarea.Token;
|
||||
import org.fife.ui.rsyntaxtextarea.TokenTypes;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -50,9 +52,9 @@ import jadx.gui.ui.action.ViewRawControlFlowGraphAction;
|
||||
import jadx.gui.ui.action.ViewRegionControlFlowGraphAction;
|
||||
import jadx.gui.ui.action.XposedAction;
|
||||
import jadx.gui.ui.codearea.mode.JCodeMode;
|
||||
import jadx.gui.ui.codearea.sync.CodePanelSyncee;
|
||||
import jadx.gui.ui.codearea.sync.CodePanelSyncer;
|
||||
import jadx.gui.ui.codearea.sync.CodePanelSyncerAbstractFactory;
|
||||
import jadx.gui.ui.codearea.sync.CodeAreaSyncee;
|
||||
import jadx.gui.ui.codearea.sync.CodeAreaSyncer;
|
||||
import jadx.gui.ui.codearea.sync.CodeAreaSyncerAbstractFactory;
|
||||
import jadx.gui.ui.codearea.sync.JavaSyncer;
|
||||
import jadx.gui.ui.panel.ContentPanel;
|
||||
import jadx.gui.utils.CaretPositionFix;
|
||||
@@ -67,7 +69,7 @@ import jadx.gui.utils.shortcut.ShortcutsController;
|
||||
* The {@link AbstractCodeArea} implementation used for displaying Java code and text based
|
||||
* resources (e.g. AndroidManifest.xml)
|
||||
*/
|
||||
public final class CodeArea extends AbstractCodeArea implements CodePanelSyncerAbstractFactory, CodePanelSyncee {
|
||||
public final class CodeArea extends AbstractCodeArea implements CodeAreaSyncerAbstractFactory, CodeAreaSyncee {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(CodeArea.class);
|
||||
|
||||
private static final long serialVersionUID = 6312736869579635796L;
|
||||
@@ -335,7 +337,7 @@ public final class CodeArea extends AbstractCodeArea implements CodePanelSyncerA
|
||||
/**
|
||||
* Search referenced java node by offset in {@code jCls} code
|
||||
*/
|
||||
public JavaNode getJavaNodeAtOffset(int offset) {
|
||||
public @Nullable JavaNode getJavaNodeAtOffset(int offset) {
|
||||
if (offset == -1) {
|
||||
return null;
|
||||
}
|
||||
@@ -347,7 +349,7 @@ public final class CodeArea extends AbstractCodeArea implements CodePanelSyncerA
|
||||
return null;
|
||||
}
|
||||
|
||||
public JavaNode getClosestJavaNode(int offset) {
|
||||
public @Nullable JavaNode getClosestJavaNode(int offset) {
|
||||
if (offset == -1) {
|
||||
return null;
|
||||
}
|
||||
@@ -359,7 +361,7 @@ public final class CodeArea extends AbstractCodeArea implements CodePanelSyncerA
|
||||
}
|
||||
}
|
||||
|
||||
public JavaNode getEnclosingJavaNode(int offset) {
|
||||
public @Nullable JavaNode getEnclosingJavaNode(int offset) {
|
||||
if (offset == -1) {
|
||||
return null;
|
||||
}
|
||||
@@ -459,20 +461,19 @@ public final class CodeArea extends AbstractCodeArea implements CodePanelSyncerA
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodePanelSyncer createCodePanelSyncer() {
|
||||
public CodeAreaSyncer createCodeAreaSyncer() {
|
||||
return new JavaSyncer(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sync(CodePanelSyncer codePanelSyncer) {
|
||||
return codePanelSyncer.syncTo(this);
|
||||
public boolean sync(CodeAreaSyncer codeAreaSyncer) {
|
||||
return codeAreaSyncer.syncTo(this);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public ICodeMetadata getCodeMetadata() {
|
||||
public @Nullable ICodeMetadata getCodeMetadata() {
|
||||
ICodeInfo codeInfo = getCodeInfo();
|
||||
if (!codeInfo.hasMetadata()) {
|
||||
LOG.warn("No code info metadata for {}", codeInfo.toString());
|
||||
LOG.warn("No code info metadata for {}", codeInfo);
|
||||
return null;
|
||||
}
|
||||
return codeInfo.getCodeMetadata();
|
||||
@@ -487,34 +488,44 @@ public final class CodeArea extends AbstractCodeArea implements CodePanelSyncerA
|
||||
public Map<Integer, Integer> getLineMappings() {
|
||||
ICodeInfo codeInfo = getCodeInfo();
|
||||
if (!codeInfo.hasMetadata()) {
|
||||
LOG.debug("No code info metadata for {}", codeInfo.toString());
|
||||
return Map.of();
|
||||
LOG.debug("No code info metadata for {}", codeInfo);
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
Map<Integer, Integer> lineMapping = codeInfo.getCodeMetadata().getLineMapping();
|
||||
if (lineMapping.isEmpty()) {
|
||||
LOG.debug("Line mappings are empty for {}", codeInfo.toString());
|
||||
return Map.of();
|
||||
LOG.debug("Line mappings are empty for {}", codeInfo);
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
return lineMapping;
|
||||
}
|
||||
|
||||
private Map<Integer, Integer> cachedUniqueLineMappings;
|
||||
|
||||
/**
|
||||
* Returns the same as {@link #getLineMappings()} but only if each value (dex debug line number)
|
||||
* appears only once.
|
||||
* If a value appears more than once then it suggests that methods might share dex debug line
|
||||
* numbers.
|
||||
* If this is the case then the line mapping cannot be used for code sync correlation.
|
||||
*
|
||||
* @return the line mapping
|
||||
*/
|
||||
public Map<Integer, Integer> getFunctionUniqueLineMappings() {
|
||||
final var lineMappings = getLineMappings();
|
||||
final boolean isAnyRepeated =
|
||||
lineMappings.values().stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting())).values().stream()
|
||||
.filter(v -> v > 1).findAny().isPresent();
|
||||
Map<Integer, Integer> mappings = cachedUniqueLineMappings;
|
||||
if (mappings == null) {
|
||||
mappings = calcUniqueLineMappings();
|
||||
cachedUniqueLineMappings = mappings;
|
||||
}
|
||||
return mappings;
|
||||
}
|
||||
|
||||
private @NotNull Map<Integer, Integer> calcUniqueLineMappings() {
|
||||
Map<Integer, Integer> lineMappings = getLineMappings();
|
||||
boolean isAnyRepeated = lineMappings.values().stream()
|
||||
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
|
||||
.values().stream()
|
||||
.anyMatch(v -> v > 1);
|
||||
if (isAnyRepeated) {
|
||||
LOG.debug("Dex debug line mappings are not unique");
|
||||
return Map.of();
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
return lineMappings;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ import java.util.Map;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JCheckBoxMenuItem;
|
||||
import javax.swing.KeyStroke;
|
||||
import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.EditorKit;
|
||||
@@ -39,19 +38,17 @@ import jadx.gui.device.debugger.BreakpointManager;
|
||||
import jadx.gui.device.debugger.DbgUtils;
|
||||
import jadx.gui.jobs.IBackgroundTask;
|
||||
import jadx.gui.jobs.LoadTask;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.treemodel.TextNode;
|
||||
import jadx.gui.ui.codearea.sync.CodePanelSyncee;
|
||||
import jadx.gui.ui.codearea.sync.CodePanelSyncer;
|
||||
import jadx.gui.ui.codearea.sync.CodePanelSyncerAbstractFactory;
|
||||
import jadx.gui.ui.codearea.sync.CodeAreaSyncee;
|
||||
import jadx.gui.ui.codearea.sync.CodeAreaSyncer;
|
||||
import jadx.gui.ui.codearea.sync.CodeAreaSyncerAbstractFactory;
|
||||
import jadx.gui.ui.codearea.sync.SmaliSyncer;
|
||||
import jadx.gui.ui.panel.ContentPanel;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
public final class SmaliArea extends AbstractCodeArea implements CodePanelSyncerAbstractFactory, CodePanelSyncee {
|
||||
public final class SmaliArea extends AbstractCodeArea implements CodeAreaSyncerAbstractFactory, CodeAreaSyncee {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SmaliArea.class);
|
||||
|
||||
private static final long serialVersionUID = 1334485631870306494L;
|
||||
@@ -62,49 +59,22 @@ public final class SmaliArea extends AbstractCodeArea implements CodePanelSyncer
|
||||
private static final Color DEBUG_LINE_COLOR = Color.decode("#9c1138");
|
||||
|
||||
private final JNode textNode;
|
||||
private final JCheckBoxMenuItem cbUseSmaliV2;
|
||||
private final boolean allowToggleV2 = false; // add to constructor args to change back
|
||||
private final boolean initialDisplayV2;
|
||||
private final SmaliModel model;
|
||||
|
||||
private boolean curVersion = false;
|
||||
private SmaliModel model;
|
||||
|
||||
SmaliArea(ContentPanel contentPanel, JClass node, boolean initialDisplayV2) {
|
||||
SmaliArea(ContentPanel contentPanel, JClass node, boolean showBytecode) {
|
||||
super(contentPanel, node);
|
||||
this.textNode = new TextNode(node.getName());
|
||||
this.initialDisplayV2 = initialDisplayV2;
|
||||
|
||||
setCodeFoldingEnabled(true);
|
||||
|
||||
cbUseSmaliV2 = new JCheckBoxMenuItem(NLS.str("popup.bytecode_col"), shouldUseSmaliPrinterV2());
|
||||
cbUseSmaliV2.setAction(new AbstractAction(NLS.str("popup.bytecode_col")) {
|
||||
private static final long serialVersionUID = -1111111202103170737L;
|
||||
|
||||
@Override
|
||||
public void actionPerformed(ActionEvent e) {
|
||||
JadxSettings settings = getContentPanel().getMainWindow().getSettings();
|
||||
settings.setSmaliAreaShowBytecode(!settings.isSmaliAreaShowBytecode());
|
||||
contentPanel.getTabbedPane().getTabs().forEach(v -> {
|
||||
if (v instanceof ClassCodeContentPanel) {
|
||||
switchModel();
|
||||
((ClassCodeContentPanel) v).getSmaliCodeArea().refresh();
|
||||
}
|
||||
});
|
||||
settings.sync();
|
||||
}
|
||||
});
|
||||
if (allowToggleV2) {
|
||||
getPopupMenu().add(cbUseSmaliV2);
|
||||
}
|
||||
switchModel();
|
||||
this.textNode = new TextNode(node.getName());
|
||||
this.model = showBytecode ? new DebugModel() : new NormalModel(this);
|
||||
setUnLoaded();
|
||||
load();
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBackgroundTask getLoadTask() {
|
||||
return new LoadTask<>(
|
||||
() -> model.loadCode(),
|
||||
model::loadCode,
|
||||
code -> {
|
||||
curVersion = shouldUseSmaliPrinterV2();
|
||||
model.loadUI(code);
|
||||
setCaretPosition(0);
|
||||
setLoaded();
|
||||
@@ -135,24 +105,7 @@ public final class SmaliArea extends AbstractCodeArea implements CodePanelSyncer
|
||||
return (JClass) node;
|
||||
}
|
||||
|
||||
private void switchModel() {
|
||||
if (model != null) {
|
||||
model.unload();
|
||||
}
|
||||
curVersion = shouldUseSmaliPrinterV2();
|
||||
model = curVersion ? new DebugModel() : new NormalModel(this);
|
||||
setUnLoaded();
|
||||
load();
|
||||
}
|
||||
|
||||
public void scrollToDebugPos(int pos) {
|
||||
// don't sync when it's set programmatically.
|
||||
getContentPanel().getMainWindow().getSettings().setSmaliAreaShowBytecode(true);
|
||||
cbUseSmaliV2.setState(shouldUseSmaliPrinterV2());
|
||||
if (!(model instanceof DebugModel)) {
|
||||
switchModel();
|
||||
refresh();
|
||||
}
|
||||
model.togglePosHighlight(pos);
|
||||
}
|
||||
|
||||
@@ -169,10 +122,6 @@ public final class SmaliArea extends AbstractCodeArea implements CodePanelSyncer
|
||||
return getFont();
|
||||
}
|
||||
|
||||
private boolean shouldUseSmaliPrinterV2() {
|
||||
return getContentPanel().getMainWindow().getSettings().isSmaliAreaShowBytecode();
|
||||
}
|
||||
|
||||
private abstract class SmaliModel {
|
||||
abstract String loadCode();
|
||||
|
||||
@@ -196,7 +145,7 @@ public final class SmaliArea extends AbstractCodeArea implements CodePanelSyncer
|
||||
}
|
||||
|
||||
private class NormalModel extends SmaliModel {
|
||||
public NormalModel(SmaliArea smaliArea) {
|
||||
private NormalModel(SmaliArea smaliArea) {
|
||||
getContentPanel().getMainWindow().getEditorThemeManager().apply(smaliArea);
|
||||
setSyntaxEditingStyle(SYNTAX_STYLE_SMALI);
|
||||
}
|
||||
@@ -228,7 +177,7 @@ public final class SmaliArea extends AbstractCodeArea implements CodePanelSyncer
|
||||
}
|
||||
};
|
||||
|
||||
public DebugModel() {
|
||||
private DebugModel() {
|
||||
loadV2Style();
|
||||
setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_ASSEMBLER_6502);
|
||||
addPropertyChangeListener(SYNTAX_SCHEME_PROPERTY, schemeListener);
|
||||
@@ -470,12 +419,12 @@ public final class SmaliArea extends AbstractCodeArea implements CodePanelSyncer
|
||||
}
|
||||
|
||||
@Override
|
||||
public CodePanelSyncer createCodePanelSyncer() {
|
||||
public CodeAreaSyncer createCodeAreaSyncer() {
|
||||
return new SmaliSyncer(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sync(CodePanelSyncer codePanelSyncer) {
|
||||
return codePanelSyncer.syncTo(this);
|
||||
public boolean sync(CodeAreaSyncer codeAreaSyncer) {
|
||||
return codeAreaSyncer.syncTo(this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package jadx.gui.ui.codearea.sync;
|
||||
|
||||
/**
|
||||
* Accepts syncer for syncing code areas
|
||||
*/
|
||||
public interface CodeAreaSyncee {
|
||||
boolean sync(CodeAreaSyncer syncer);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package jadx.gui.ui.codearea.sync;
|
||||
|
||||
public interface CodeAreaSyncer extends IToJavaSyncStrategy, IToSmaliSyncStrategy {
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package jadx.gui.ui.codearea.sync;
|
||||
|
||||
public interface CodeAreaSyncerAbstractFactory {
|
||||
CodeAreaSyncer createCodeAreaSyncer();
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package jadx.gui.ui.codearea.sync;
|
||||
|
||||
/**
|
||||
* Accepts a code panel syncer for syncing code areas
|
||||
*/
|
||||
public interface CodePanelSyncee {
|
||||
boolean sync(CodePanelSyncer syncer);
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
package jadx.gui.ui.codearea.sync;
|
||||
|
||||
public interface CodePanelSyncer extends IToJavaSyncStrategy, IToSmaliSyncStrategy {
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package jadx.gui.ui.codearea.sync;
|
||||
|
||||
public interface CodePanelSyncerAbstractFactory {
|
||||
CodePanelSyncer createCodePanelSyncer();
|
||||
}
|
||||
@@ -25,16 +25,19 @@ public class DebugLineJavaSyncer implements IToSmaliSyncStrategy, IToJavaSyncStr
|
||||
public boolean syncTo(CodeArea to) {
|
||||
// This might be any combination between java/simple/fallback
|
||||
// We cannot just rely on the current line.
|
||||
// Instead try to correlate with line mappings.
|
||||
// Instead, try to correlate with line mappings.
|
||||
try {
|
||||
int lineIndex = from.getCaretLineNumber();
|
||||
Map<Integer, Integer> toLineMapping = to.getFunctionUniqueLineMappings();
|
||||
if (toLineMapping.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
int lineIndex = from.getCaretLineNumber();
|
||||
// lineIndex is 0-indexed whereas the line mappings are based off a 1-index.
|
||||
Integer sourceLine = getClosestSourceLine(lineIndex + 1);
|
||||
if (sourceLine == null) {
|
||||
return false;
|
||||
}
|
||||
// find the equivalent linenumber in the 'to' by a reverse lookup from the source line
|
||||
// find the equivalent line number in the 'to' by a reverse lookup from the source line
|
||||
for (Map.Entry<Integer, Integer> entry : toLineMapping.entrySet()) {
|
||||
int toLine = entry.getKey();
|
||||
int candidateSourceLine = entry.getValue();
|
||||
@@ -85,7 +88,7 @@ public class DebugLineJavaSyncer implements IToSmaliSyncStrategy, IToJavaSyncStr
|
||||
private @Nullable Integer getClosestSourceLine(int lineNum) {
|
||||
// get the line mappings of the Java/Simple/Fallback code
|
||||
Map<Integer, Integer> lineMapping = from.getFunctionUniqueLineMappings();
|
||||
if (lineMapping == null || lineMapping.isEmpty()) {
|
||||
if (lineMapping.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
// get the source line from the decomp line
|
||||
|
||||
@@ -26,7 +26,7 @@ public class DebugLineSmaliSyncer implements IToJavaSyncStrategy {
|
||||
@Override
|
||||
public boolean syncTo(CodeArea to) {
|
||||
try {
|
||||
// Get the from lines and currentline index
|
||||
// Get the from lines and current line index
|
||||
int lineIndex = from.getCaretLineNumber();
|
||||
String[] fromLines = from.getText().split("\\R");
|
||||
if (lineIndex >= fromLines.length) {
|
||||
@@ -62,8 +62,7 @@ public class DebugLineSmaliSyncer implements IToJavaSyncStrategy {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Anchor findNearestAnchor(int smaliLineNumber, String[] lines) {
|
||||
private @Nullable Anchor findNearestAnchor(int smaliLineNumber, String[] lines) {
|
||||
for (int i = smaliLineNumber; i >= 0; i--) {
|
||||
String trimmedLine = lines[i].trim();
|
||||
if (trimmedLine.startsWith(".line")) {
|
||||
|
||||
@@ -11,6 +11,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.api.metadata.ICodeMetadata;
|
||||
import jadx.api.metadata.ICodeNodeRef;
|
||||
import jadx.api.metadata.annotations.InsnCodeOffset;
|
||||
import jadx.api.metadata.annotations.NodeDeclareRef;
|
||||
@@ -95,27 +96,20 @@ public class InsnOffsetJavaSyncer implements IToJavaSyncStrategy, IToSmaliSyncSt
|
||||
if (fromMthRange == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Integer mthDefPos = fromMthRange.getStart().getKey();
|
||||
Integer mthEndPos = fromMthRange.getEnd().getKey();
|
||||
|
||||
LOG.debug("InsnOffsetJavaSyncer caretPos = {}", caretPos);
|
||||
LOG.debug("InsnOffsetJavaSyncer mthDefPos = {}", mthDefPos);
|
||||
LOG.debug("InsnOffsetJavaSyncer mthEndPos = {}", mthEndPos);
|
||||
LOG.debug("InsnOffsetJavaSyncer caretPos = {}, mthDefPos = {}, mthEndPos = {}", caretPos, mthDefPos, mthEndPos);
|
||||
|
||||
CodeMetadataRange fromInsnOffsetRange = findOffsetRange(caretPos, mthDefPos, mthEndPos);
|
||||
if (fromInsnOffsetRange == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String mthID = getMthRawFullID(mthDefPos);
|
||||
|
||||
// now search for this range within the target area
|
||||
CodeMetadataRange toMthRange = findMethodRange(mthID, to);
|
||||
if (toMthRange == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// search for the first insn offset
|
||||
int firstInsnOffset = ((InsnCodeOffset) fromInsnOffsetRange.getStart().getValue()).getOffset();
|
||||
Integer highlightPosStart = to.getCodeMetadata().searchDown(toMthRange.getStart().getKey(), (offset, ann) -> {
|
||||
@@ -149,7 +143,6 @@ public class InsnOffsetJavaSyncer implements IToJavaSyncStrategy, IToSmaliSyncSt
|
||||
if (highlightPosEnd == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
to.scrollToPos(highlightPosStart);
|
||||
try {
|
||||
CodeSyncHighlighter.defaultHighlighter().highlightRange(to, highlightPosStart, highlightPosEnd);
|
||||
@@ -162,9 +155,12 @@ public class InsnOffsetJavaSyncer implements IToJavaSyncStrategy, IToSmaliSyncSt
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static CodeMetadataRange findMethodRange(String mthFullRawID, CodeArea area) {
|
||||
Map.Entry<Integer, ICodeAnnotation> toMthDecl = area.getCodeMetadata().searchDown(0, (offset, ann) -> {
|
||||
private static @Nullable CodeMetadataRange findMethodRange(String mthFullRawID, CodeArea area) {
|
||||
ICodeMetadata codeMetadata = area.getCodeMetadata();
|
||||
if (codeMetadata == null) {
|
||||
return null;
|
||||
}
|
||||
Map.Entry<Integer, ICodeAnnotation> toMthDecl = codeMetadata.searchDown(0, (offset, ann) -> {
|
||||
if (ann.getAnnType() != ICodeAnnotation.AnnType.DECLARATION) {
|
||||
return null;
|
||||
}
|
||||
@@ -179,28 +175,27 @@ public class InsnOffsetJavaSyncer implements IToJavaSyncStrategy, IToSmaliSyncSt
|
||||
}
|
||||
return new SimpleEntry<>(offset, ann);
|
||||
});
|
||||
|
||||
if (toMthDecl == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map.Entry<Integer, ICodeAnnotation> toMthEnd = area.getCodeMetadata().searchDown(toMthDecl.getKey(), (offset, ann) -> {
|
||||
Map.Entry<Integer, ICodeAnnotation> toMthEnd = codeMetadata.searchDown(toMthDecl.getKey(), (offset, ann) -> {
|
||||
if (ann.getAnnType() != ICodeAnnotation.AnnType.END) {
|
||||
return null;
|
||||
}
|
||||
return new SimpleEntry<>(offset, ann);
|
||||
});
|
||||
|
||||
if (toMthEnd == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new CodeMetadataRange(toMthDecl, toMthEnd);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private CodeMetadataRange findEnclosingMethodRange(Integer startPos) {
|
||||
Map.Entry<Integer, ICodeAnnotation> mthDef = from.getCodeMetadata().searchUp(startPos, (offset, ann) -> {
|
||||
private @Nullable CodeMetadataRange findEnclosingMethodRange(Integer startPos) {
|
||||
ICodeMetadata codeMetadata = from.getCodeMetadata();
|
||||
if (codeMetadata == null) {
|
||||
return null;
|
||||
}
|
||||
Map.Entry<Integer, ICodeAnnotation> mthDef = codeMetadata.searchUp(startPos, (offset, ann) -> {
|
||||
if (ann.getAnnType() != ICodeAnnotation.AnnType.DECLARATION) {
|
||||
return null;
|
||||
}
|
||||
@@ -211,22 +206,18 @@ public class InsnOffsetJavaSyncer implements IToJavaSyncStrategy, IToSmaliSyncSt
|
||||
}
|
||||
return new SimpleEntry<>(offset, ann);
|
||||
});
|
||||
|
||||
if (mthDef == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Map.Entry<Integer, ICodeAnnotation> mthEnd = from.getCodeMetadata().searchDown(startPos, (offset, ann) -> {
|
||||
Map.Entry<Integer, ICodeAnnotation> mthEnd = codeMetadata.searchDown(startPos, (offset, ann) -> {
|
||||
if (ann.getAnnType() != ICodeAnnotation.AnnType.END) {
|
||||
return null;
|
||||
}
|
||||
return new SimpleEntry<>(offset, ann);
|
||||
});
|
||||
|
||||
if (mthEnd == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new CodeMetadataRange(mthDef, mthEnd);
|
||||
}
|
||||
|
||||
@@ -234,12 +225,11 @@ public class InsnOffsetJavaSyncer implements IToJavaSyncStrategy, IToSmaliSyncSt
|
||||
* Gets a CodeMetadataRange for the from CodeArea where start and end
|
||||
* are InsnCodeOffsets whose offsets are monotonically increasing.
|
||||
*
|
||||
* @param - startPos the starting position to start searching from
|
||||
* @param - mthDefPos the method node decl position enclosing the range
|
||||
* @param - mthEndPos the method end position enclosing the range
|
||||
* @param startPos the starting position to start searching from
|
||||
* @param mthDefPos the method node decl position enclosing the range
|
||||
* @param mthEndPos the method end position enclosing the range
|
||||
*/
|
||||
@Nullable
|
||||
private CodeMetadataRange findOffsetRange(Integer startPos, Integer mthDefPos, Integer mthEndPos) {
|
||||
private @Nullable CodeMetadataRange findOffsetRange(Integer startPos, Integer mthDefPos, Integer mthEndPos) {
|
||||
Map.Entry<Integer, ICodeAnnotation> first = findInsnOffsetBeforePos(startPos, mthDefPos);
|
||||
Map.Entry<Integer, ICodeAnnotation> second = findInsnOffsetAfterPos(startPos, mthEndPos);
|
||||
if (first == null || second == null) {
|
||||
@@ -256,29 +246,35 @@ public class InsnOffsetJavaSyncer implements IToJavaSyncStrategy, IToSmaliSyncSt
|
||||
return new CodeMetadataRange(first, second);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Map.Entry<Integer, ICodeAnnotation> findInsnOffsetBeforePos(Integer startPos, Integer limit) {
|
||||
return from.getCodeMetadata().searchUp(startPos, (offset, ann) -> {
|
||||
private @Nullable Map.Entry<Integer, ICodeAnnotation> findInsnOffsetBeforePos(Integer startPos, Integer limit) {
|
||||
ICodeMetadata codeMetadata = from.getCodeMetadata();
|
||||
if (codeMetadata == null) {
|
||||
return null;
|
||||
}
|
||||
return codeMetadata.searchUp(startPos, (offset, ann) -> {
|
||||
if (offset <= limit) {
|
||||
return null;
|
||||
}
|
||||
if (ann.getAnnType() != ICodeAnnotation.AnnType.OFFSET) {
|
||||
return null;
|
||||
}
|
||||
return new SimpleEntry<Integer, ICodeAnnotation>(offset, ann);
|
||||
return new SimpleEntry<>(offset, ann);
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Map.Entry<Integer, ICodeAnnotation> findInsnOffsetAfterPos(Integer startPos, Integer limit) {
|
||||
return from.getCodeMetadata().searchDown(startPos, (offset, ann) -> {
|
||||
private @Nullable Map.Entry<Integer, ICodeAnnotation> findInsnOffsetAfterPos(Integer startPos, Integer limit) {
|
||||
ICodeMetadata codeMetadata = from.getCodeMetadata();
|
||||
if (codeMetadata == null) {
|
||||
return null;
|
||||
}
|
||||
return codeMetadata.searchDown(startPos, (offset, ann) -> {
|
||||
if (offset >= limit) {
|
||||
return null;
|
||||
}
|
||||
if (ann.getAnnType() != ICodeAnnotation.AnnType.OFFSET) {
|
||||
return null;
|
||||
}
|
||||
return new SimpleEntry<Integer, ICodeAnnotation>(offset, ann);
|
||||
return new SimpleEntry<>(offset, ann);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -298,7 +294,6 @@ public class InsnOffsetJavaSyncer implements IToJavaSyncStrategy, IToSmaliSyncSt
|
||||
*
|
||||
* @param smaliMethodNode - method of interest
|
||||
* @param insnCodeOffsetRange - code offset range from the caret pos
|
||||
* @return
|
||||
*/
|
||||
private static List<Integer> getMappedSmaliLines(
|
||||
SmaliMethodNode smaliMethodNode,
|
||||
|
||||
@@ -1,17 +1,12 @@
|
||||
package jadx.gui.ui.codearea.sync;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.gui.ui.codearea.CodeArea;
|
||||
import jadx.gui.ui.codearea.SmaliArea;
|
||||
|
||||
/**
|
||||
* Syncs a Java code panel area (Java/Simple/Fallback) to another area
|
||||
*/
|
||||
public class JavaSyncer implements CodePanelSyncer {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(JavaSyncer.class);
|
||||
|
||||
public class JavaSyncer implements CodeAreaSyncer {
|
||||
private final DebugLineJavaSyncer debugLineSyncer;
|
||||
private final InsnOffsetJavaSyncer insnOffsetSyncer;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import jadx.gui.ui.codearea.SmaliArea;
|
||||
/**
|
||||
* Syncs a Smali code panel area to another area
|
||||
*/
|
||||
public class SmaliSyncer implements CodePanelSyncer {
|
||||
public class SmaliSyncer implements CodeAreaSyncer {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(SmaliSyncer.class);
|
||||
|
||||
private final SmaliArea from;
|
||||
|
||||
@@ -10,17 +10,16 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.gui.ui.codearea.AbstractCodeArea;
|
||||
import jadx.gui.ui.codearea.CodeArea;
|
||||
import jadx.gui.ui.codearea.CodePanel;
|
||||
import jadx.gui.ui.codearea.SmaliArea;
|
||||
import jadx.gui.ui.codearea.sync.CodeSyncHighlighter;
|
||||
|
||||
/**
|
||||
* Regex/String based sync strategy of toPanel when clicking in fromPanel
|
||||
* Regex/String based sync strategy of toArea when clicking in fromArea
|
||||
* Summary of syncing strategy:
|
||||
* 1) Look for an identifying class member token under the caret position.
|
||||
* 2) If found look for the enclosing method or class declaration.
|
||||
* 3) If the line is a declaration line, find the equivalent line in the other code panel.
|
||||
* 4) Otherwise find the nth occurence of the token in the enclosing method/class in the other code
|
||||
* 4) Otherwise find the nth occurrence of the token in the enclosing method/class in the other code
|
||||
* panel.
|
||||
* The following are not yet supported:
|
||||
* - generic classes/methods
|
||||
@@ -31,15 +30,12 @@ import jadx.gui.ui.codearea.sync.CodeSyncHighlighter;
|
||||
public class FallbackSyncer {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(FallbackSyncer.class);
|
||||
|
||||
public static boolean sync(CodePanel fromPanel, CodePanel toPanel) throws BadLocationException, Exception {
|
||||
public static boolean sync(AbstractCodeArea fromArea, AbstractCodeArea toArea) throws Exception {
|
||||
LOG.debug("FALLBACK SYNC START");
|
||||
try {
|
||||
AbstractCodeArea from = fromPanel.getCodeArea();
|
||||
AbstractCodeArea to = toPanel.getCodeArea();
|
||||
|
||||
int caretPos = from.getCaretPosition();
|
||||
int lineIndex = from.getLineOfOffset(caretPos);
|
||||
String[] fromLines = from.getText().split("\\R");
|
||||
int caretPos = fromArea.getCaretPosition();
|
||||
int lineIndex = fromArea.getLineOfOffset(caretPos);
|
||||
String[] fromLines = fromArea.getText().split("\\R");
|
||||
if (lineIndex >= fromLines.length) {
|
||||
return false;
|
||||
}
|
||||
@@ -48,7 +44,7 @@ public class FallbackSyncer {
|
||||
LOG.debug("Caret line [{}]: {}", caretPos, caretLine);
|
||||
|
||||
// Extract token under caret (string literal or identifier)
|
||||
AbstractCodeAreaToken areaToken = FallbackSyncer.getToken(from, caretPos);
|
||||
AbstractCodeAreaToken areaToken = FallbackSyncer.getToken(fromArea, caretPos);
|
||||
String token = areaToken.getStr();
|
||||
LOG.debug("Token at caret: '{}'", token);
|
||||
if (token == null || token.isEmpty()) {
|
||||
@@ -60,14 +56,14 @@ public class FallbackSyncer {
|
||||
return false;
|
||||
}
|
||||
|
||||
return syncToIdentifyingNthOccurence(areaToken, to);
|
||||
return syncToIdentifyingNthOccurence(areaToken, toArea);
|
||||
} finally {
|
||||
LOG.debug("FALLBACK SYNC END");
|
||||
}
|
||||
}
|
||||
|
||||
// This function just serves as a way to create the correct Token type
|
||||
// FallbackSyncer should be refactored to use CodePanelSyncer
|
||||
// FallbackSyncer should be refactored to use CodeAreaSyncer
|
||||
private static AbstractCodeAreaToken getToken(AbstractCodeArea from, int caretPos) throws BadLocationException, FallbackSyncException {
|
||||
if (from instanceof SmaliArea) {
|
||||
return new SmaliAreaToken((SmaliArea) from, caretPos);
|
||||
@@ -196,7 +192,7 @@ public class FallbackSyncer {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Similar with the function above if refactored to use the CodePanelSyncer Abstraction we can
|
||||
// Similar with the function above if refactored to use the CodeAreaSyncer Abstraction we can
|
||||
// remove this.
|
||||
private static AbstractCodeAreaLine getLine(AbstractCodeArea area, int lineIndex) throws BadLocationException, FallbackSyncException {
|
||||
if (area instanceof SmaliArea) {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package jadx.gui.utils.ui;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import javax.swing.event.CaretListener;
|
||||
import javax.swing.text.JTextComponent;
|
||||
|
||||
/**
|
||||
* Track attached UI listeners and remove on request
|
||||
*/
|
||||
public class ListenersHelper<C, L> {
|
||||
|
||||
public static ListenersHelper<JTextComponent, CaretListener> buildForCaretListener() {
|
||||
return new ListenersHelper<>(JTextComponent::addCaretListener, JTextComponent::removeCaretListener);
|
||||
}
|
||||
|
||||
private final Map<C, List<L>> listenerMap = new IdentityHashMap<>();
|
||||
|
||||
private final BiConsumer<C, L> addMth;
|
||||
private final BiConsumer<C, L> removeMth;
|
||||
|
||||
private ListenersHelper(BiConsumer<C, L> add, BiConsumer<C, L> remove) {
|
||||
this.addMth = add;
|
||||
this.removeMth = remove;
|
||||
}
|
||||
|
||||
public synchronized void add(C component, L listener) {
|
||||
addMth.accept(component, listener);
|
||||
listenerMap.computeIfAbsent(component, c -> new ArrayList<>()).add(listener);
|
||||
}
|
||||
|
||||
public synchronized void removeAll() {
|
||||
listenerMap.forEach((comp, list) -> {
|
||||
for (L l : list) {
|
||||
remove(comp, l);
|
||||
}
|
||||
});
|
||||
listenerMap.clear();
|
||||
}
|
||||
|
||||
public synchronized void removeFor(C component) {
|
||||
List<L> list = listenerMap.get(component);
|
||||
if (list != null) {
|
||||
list.forEach(l -> remove(component, l));
|
||||
listenerMap.remove(component);
|
||||
}
|
||||
}
|
||||
|
||||
private void remove(C component, L listener) {
|
||||
removeMth.accept(component, listener);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -112,7 +112,7 @@ tabs.closeAllRight=Alles rechts schließen
|
||||
#tabs.closeAllLeft=Close All Left
|
||||
tabs.code=Code
|
||||
tabs.smali=Smali
|
||||
tabs.smali_bytecode=Smali+Bytecode
|
||||
#tabs.smali_bytecode=Bytecode
|
||||
|
||||
nav.back=Zurück
|
||||
nav.forward=Vorwärts
|
||||
@@ -360,7 +360,6 @@ msg.cant_add_comment=Kann hier keinen Kommentar hinzufügen
|
||||
|
||||
#methods_dialog.title=Select methods
|
||||
|
||||
popup.bytecode_col=Dalvik-Bytecode anzeigen
|
||||
popup.line_wrap=Zeilenumbruch
|
||||
popup.undo=Rückgängig
|
||||
popup.redo=Wiederholen
|
||||
|
||||
@@ -112,7 +112,7 @@ tabs.closeAllRight=Close All Right
|
||||
tabs.closeAllLeft=Close All Left
|
||||
tabs.code=Code
|
||||
tabs.smali=Smali
|
||||
tabs.smali_bytecode=Smali+Bytecode
|
||||
tabs.smali_bytecode=Bytecode
|
||||
|
||||
nav.back=Back
|
||||
nav.forward=Forward
|
||||
@@ -360,7 +360,6 @@ msg.non_displayable_chars.title=Undisplayed Strings
|
||||
|
||||
methods_dialog.title=Select methods
|
||||
|
||||
popup.bytecode_col=Show Dalvik Bytecode
|
||||
popup.line_wrap=Line Wrap
|
||||
popup.undo=Undo
|
||||
popup.redo=Redo
|
||||
|
||||
@@ -112,7 +112,7 @@ tabs.closeAllRight=Cierra todo a la derecha
|
||||
#tabs.closeAllLeft=Close All Left
|
||||
#tabs.code=Code
|
||||
#tabs.smali=Smali
|
||||
#tabs.smali_bytecode=Smali+Bytecode
|
||||
#tabs.smali_bytecode=Bytecode
|
||||
|
||||
nav.back=Atrás
|
||||
nav.forward=Adelante
|
||||
@@ -360,7 +360,6 @@ msg.language_changed=El nuevo idioma se mostrará la próxima vez que la aplicac
|
||||
|
||||
#methods_dialog.title=Select methods
|
||||
|
||||
#popup.bytecode_col=Show Dalvik Bytecode
|
||||
#popup.line_wrap=Line Wrap
|
||||
popup.undo=Deshacer
|
||||
popup.redo=Rehacer
|
||||
|
||||
@@ -112,7 +112,7 @@ tabs.closeAllRight=Tutup Semua yang Kanan
|
||||
#tabs.closeAllLeft=Close All Left
|
||||
tabs.code=Kode
|
||||
tabs.smali=Smali
|
||||
tabs.smali_bytecode=Smali+Bytecode
|
||||
#tabs.smali_bytecode=Bytecode
|
||||
|
||||
nav.back=Kembali
|
||||
nav.forward=Maju
|
||||
@@ -360,7 +360,6 @@ msg.cant_add_comment=Tidak dapat menambahkan komentar di sini
|
||||
|
||||
#methods_dialog.title=Select methods
|
||||
|
||||
popup.bytecode_col=Tampilkan Bytecode Dalvik
|
||||
popup.line_wrap=Baris Wrap
|
||||
popup.undo=Kembalikan
|
||||
popup.redo=Ulang
|
||||
|
||||
@@ -112,7 +112,7 @@ tabs.closeAllRight=오른쪽의 모든 것을 닫으십시오
|
||||
#tabs.closeAllLeft=Close All Left
|
||||
tabs.code=코드
|
||||
tabs.smali=Smali
|
||||
tabs.smali_bytecode=Smali+Bytecode
|
||||
#tabs.smali_bytecode=Bytecode
|
||||
|
||||
nav.back=뒤로
|
||||
nav.forward=앞으로
|
||||
@@ -360,7 +360,6 @@ msg.cant_add_comment=여기에 주석을 추가할수 없음
|
||||
|
||||
#methods_dialog.title=Select methods
|
||||
|
||||
popup.bytecode_col=Dalvik Bytecode 보이기
|
||||
popup.line_wrap=줄 바꿈
|
||||
popup.undo=실행 취소
|
||||
popup.redo=다시 실행
|
||||
|
||||
@@ -112,7 +112,7 @@ tabs.closeAllRight=Feche tudo à direita
|
||||
#tabs.closeAllLeft=Close All Left
|
||||
tabs.code=Código
|
||||
tabs.smali=Smali
|
||||
tabs.smali_bytecode=Smali+Bytecode
|
||||
#tabs.smali_bytecode=Bytecode
|
||||
|
||||
nav.back=Voltar
|
||||
nav.forward=Avançar
|
||||
@@ -360,7 +360,6 @@ msg.cant_add_comment=Não é possível adicionar comentários aqui
|
||||
|
||||
#methods_dialog.title=Select methods
|
||||
|
||||
popup.bytecode_col=Mostrar Dalvik Bytecode
|
||||
popup.line_wrap=Quebra de linha
|
||||
popup.undo=Desfazer
|
||||
popup.redo=Refazer
|
||||
|
||||
@@ -112,7 +112,7 @@ tabs.closeAllRight=Закройте все справа
|
||||
#tabs.closeAllLeft=Close All Left
|
||||
tabs.code=Код
|
||||
tabs.smali=Smali
|
||||
tabs.smali_bytecode=Smali+Bytecode
|
||||
#tabs.smali_bytecode=Bytecode
|
||||
|
||||
nav.back=Назад
|
||||
nav.forward=Вперед
|
||||
@@ -360,7 +360,6 @@ msg.cant_add_comment=Невозможно добавить комментари
|
||||
|
||||
#methods_dialog.title=Select methods
|
||||
|
||||
popup.bytecode_col=Показать Dalvik байткод
|
||||
popup.line_wrap=Перенос строк
|
||||
popup.undo=Отменить
|
||||
popup.redo=Вернуть
|
||||
|
||||
@@ -112,7 +112,7 @@ tabs.closeAllRight=关闭右边的所有
|
||||
tabs.closeAllLeft=关闭左边的所有
|
||||
tabs.code=代码
|
||||
tabs.smali=Smali
|
||||
tabs.smali_bytecode=Smali+Bytecode
|
||||
#tabs.smali_bytecode=Bytecode
|
||||
|
||||
nav.back=后退
|
||||
nav.forward=前进
|
||||
@@ -360,7 +360,6 @@ msg.non_displayable_chars.title=未显示的字符串
|
||||
|
||||
methods_dialog.title=选择方法
|
||||
|
||||
popup.bytecode_col=显示Dalvik字节码
|
||||
popup.line_wrap=自动换行
|
||||
popup.undo=撤销
|
||||
popup.redo=重做
|
||||
|
||||
@@ -112,7 +112,7 @@ tabs.closeAllRight=關閉右邊的所有
|
||||
#tabs.closeAllLeft=Close All Left
|
||||
tabs.code=程式碼
|
||||
tabs.smali=Smali
|
||||
tabs.smali_bytecode=Smali+Bytecode
|
||||
#tabs.smali_bytecode=Bytecode
|
||||
|
||||
nav.back=返回
|
||||
nav.forward=向前
|
||||
@@ -360,7 +360,6 @@ msg.cant_add_comment=無法在此新增註解
|
||||
|
||||
methods_dialog.title=選擇方法
|
||||
|
||||
popup.bytecode_col=顯示 Dalvik 位元組碼
|
||||
popup.line_wrap=自動換行
|
||||
popup.undo=復原
|
||||
popup.redo=重做
|
||||
|
||||
Reference in New Issue
Block a user