diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java index 5ea18ccd5..edce073e1 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettings.java @@ -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); - } - } diff --git a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsData.java b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsData.java index 9fe5077d3..9e71d433c 100644 --- a/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsData.java +++ b/jadx-gui/src/main/java/jadx/gui/settings/JadxSettingsData.java @@ -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; } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeContentPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeContentPanel.java index 450ee78cc..bdc546afc 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeContentPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeContentPanel.java @@ -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) { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/ClassCodeContentPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/ClassCodeContentPanel.java index 38c55ca92..9a4122f3f 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/ClassCodeContentPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/ClassCodeContentPanel.java @@ -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 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()); } } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java index faf0f1276..e34d44b88 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeArea.java @@ -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 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 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 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 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 mappings = cachedUniqueLineMappings; + if (mappings == null) { + mappings = calcUniqueLineMappings(); + cachedUniqueLineMappings = mappings; + } + return mappings; + } + + private @NotNull Map calcUniqueLineMappings() { + Map 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; } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/SmaliArea.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/SmaliArea.java index 204709924..859d91d0e 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/SmaliArea.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/SmaliArea.java @@ -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); } } diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/CodeAreaSyncee.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/CodeAreaSyncee.java new file mode 100644 index 000000000..48db26a88 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/CodeAreaSyncee.java @@ -0,0 +1,8 @@ +package jadx.gui.ui.codearea.sync; + +/** + * Accepts syncer for syncing code areas + */ +public interface CodeAreaSyncee { + boolean sync(CodeAreaSyncer syncer); +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/CodeAreaSyncer.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/CodeAreaSyncer.java new file mode 100644 index 000000000..156ca6227 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/CodeAreaSyncer.java @@ -0,0 +1,4 @@ +package jadx.gui.ui.codearea.sync; + +public interface CodeAreaSyncer extends IToJavaSyncStrategy, IToSmaliSyncStrategy { +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/CodeAreaSyncerAbstractFactory.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/CodeAreaSyncerAbstractFactory.java new file mode 100644 index 000000000..c5aafa1de --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/CodeAreaSyncerAbstractFactory.java @@ -0,0 +1,5 @@ +package jadx.gui.ui.codearea.sync; + +public interface CodeAreaSyncerAbstractFactory { + CodeAreaSyncer createCodeAreaSyncer(); +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/CodePanelSyncee.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/CodePanelSyncee.java deleted file mode 100644 index b0e855987..000000000 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/CodePanelSyncee.java +++ /dev/null @@ -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); -} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/CodePanelSyncer.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/CodePanelSyncer.java deleted file mode 100644 index 63ac218b5..000000000 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/CodePanelSyncer.java +++ /dev/null @@ -1,4 +0,0 @@ -package jadx.gui.ui.codearea.sync; - -public interface CodePanelSyncer extends IToJavaSyncStrategy, IToSmaliSyncStrategy { -} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/CodePanelSyncerAbstractFactory.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/CodePanelSyncerAbstractFactory.java deleted file mode 100644 index a4fb0c2d2..000000000 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/CodePanelSyncerAbstractFactory.java +++ /dev/null @@ -1,5 +0,0 @@ -package jadx.gui.ui.codearea.sync; - -public interface CodePanelSyncerAbstractFactory { - CodePanelSyncer createCodePanelSyncer(); -} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/DebugLineJavaSyncer.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/DebugLineJavaSyncer.java index b9334d4b2..f4e042ac6 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/DebugLineJavaSyncer.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/DebugLineJavaSyncer.java @@ -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 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 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 lineMapping = from.getFunctionUniqueLineMappings(); - if (lineMapping == null || lineMapping.isEmpty()) { + if (lineMapping.isEmpty()) { return null; } // get the source line from the decomp line diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/DebugLineSmaliSyncer.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/DebugLineSmaliSyncer.java index 3f7c5cfc2..017160459 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/DebugLineSmaliSyncer.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/DebugLineSmaliSyncer.java @@ -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")) { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/InsnOffsetJavaSyncer.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/InsnOffsetJavaSyncer.java index cd5abf904..55fa1f880 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/InsnOffsetJavaSyncer.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/InsnOffsetJavaSyncer.java @@ -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 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 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 toMthEnd = area.getCodeMetadata().searchDown(toMthDecl.getKey(), (offset, ann) -> { + Map.Entry 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 mthDef = from.getCodeMetadata().searchUp(startPos, (offset, ann) -> { + private @Nullable CodeMetadataRange findEnclosingMethodRange(Integer startPos) { + ICodeMetadata codeMetadata = from.getCodeMetadata(); + if (codeMetadata == null) { + return null; + } + Map.Entry 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 mthEnd = from.getCodeMetadata().searchDown(startPos, (offset, ann) -> { + Map.Entry 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 first = findInsnOffsetBeforePos(startPos, mthDefPos); Map.Entry 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 findInsnOffsetBeforePos(Integer startPos, Integer limit) { - return from.getCodeMetadata().searchUp(startPos, (offset, ann) -> { + private @Nullable Map.Entry 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(offset, ann); + return new SimpleEntry<>(offset, ann); }); } - @Nullable - private Map.Entry findInsnOffsetAfterPos(Integer startPos, Integer limit) { - return from.getCodeMetadata().searchDown(startPos, (offset, ann) -> { + private @Nullable Map.Entry 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(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 getMappedSmaliLines( SmaliMethodNode smaliMethodNode, diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/JavaSyncer.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/JavaSyncer.java index 415f8cc00..e2afadc95 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/JavaSyncer.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/JavaSyncer.java @@ -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; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/SmaliSyncer.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/SmaliSyncer.java index ac450c05d..2aae847a7 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/SmaliSyncer.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/SmaliSyncer.java @@ -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; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/fallback/FallbackSyncer.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/fallback/FallbackSyncer.java index 59a5551a3..c08c44501 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/fallback/FallbackSyncer.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/sync/fallback/FallbackSyncer.java @@ -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) { diff --git a/jadx-gui/src/main/java/jadx/gui/utils/ui/ListenersHelper.java b/jadx-gui/src/main/java/jadx/gui/utils/ui/ListenersHelper.java new file mode 100644 index 000000000..4263d5336 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/utils/ui/ListenersHelper.java @@ -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 { + + public static ListenersHelper buildForCaretListener() { + return new ListenersHelper<>(JTextComponent::addCaretListener, JTextComponent::removeCaretListener); + } + + private final Map> listenerMap = new IdentityHashMap<>(); + + private final BiConsumer addMth; + private final BiConsumer removeMth; + + private ListenersHelper(BiConsumer add, BiConsumer 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 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); + } + +} diff --git a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties index ba9558c3e..aa75bc3f2 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_de_DE.properties @@ -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 diff --git a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties index 5a0414bef..a060a0e1d 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_en_US.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_en_US.properties @@ -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 diff --git a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties index 85304fa09..2c4b6c2c6 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_es_ES.properties @@ -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 diff --git a/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties b/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties index ce0651c3d..584621ed1 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_id_ID.properties @@ -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 diff --git a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties index 16df0f21f..c0220779c 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ko_KR.properties @@ -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=다시 실행 diff --git a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties index 71768804e..df1b8b8dd 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_pt_BR.properties @@ -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 diff --git a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties index 26bc14ae8..a1dde29af 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_ru_RU.properties @@ -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=Вернуть diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties index 5613ec300..ee8b59497 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_CN.properties @@ -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=重做 diff --git a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties index ba9b3bf93..5a803b1df 100644 --- a/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties +++ b/jadx-gui/src/main/resources/i18n/Messages_zh_TW.properties @@ -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=重做