提交 e3055b95 编写于 作者: S Soul Trace 提交者: skylot

feat(gui): support for renaming methods, classes and fields (PR #794 #791)

* Add getRealFullName() to ClassNode and JavaClass and searchJavaClassByRealName() to JadxWrapper

Those methods is like getFullName() and searchJavaClassByClassName(), but for class names without aliases.
It is necessary for renaming classes/methods/fields.

* core: Make getFieldNode(), getMethodNode() and getRoot() public

This is necessary for renaming functionality

* jadx-gui: Add Rename popup menu entry (renames classes, methods and fields)

It allows user to rename classes, methods and fields.
It updates deobfuscation map and reload file.
This may be suboptimal, and maybe some RenameVisitor should be added.
Deobfuscation should be enabled in order to allow this.
上级 78eed862
......@@ -281,7 +281,7 @@ public final class JadxDecompiler {
root.getErrorsCounter().printReport();
}
RootNode getRoot() {
public RootNode getRoot() {
return root;
}
......
......@@ -46,7 +46,7 @@ public final class JavaField implements JavaNode {
return field.getDecompiledLine();
}
FieldNode getFieldNode() {
public FieldNode getFieldNode() {
return field;
}
......
......@@ -74,7 +74,7 @@ public final class JavaMethod implements JavaNode {
return mth.getDecompiledLine();
}
MethodNode getMethodNode() {
public MethodNode getMethodNode() {
return mth;
}
......
package jadx.gui.ui;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.List;
import javax.swing.*;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.JavaClass;
import jadx.api.JavaField;
import jadx.api.JavaMethod;
import jadx.api.JavaNode;
import jadx.core.dex.nodes.DexNode;
import jadx.core.dex.nodes.RootNode;
import jadx.core.utils.files.InputFile;
import jadx.gui.treemodel.*;
import jadx.gui.ui.codearea.CodeArea;
import jadx.gui.utils.NLS;
import jadx.gui.utils.TextStandardActions;
public class RenameDialog extends JDialog {
private static final long serialVersionUID = -3269715644416902410L;
private static final Logger LOG = LoggerFactory.getLogger(RenameDialog.class);
protected final transient MainWindow mainWindow;
private final transient JNode node;
private JTextField renameField;
private CodeArea codeArea;
public RenameDialog(CodeArea codeArea, JNode node) {
super(codeArea.getMainWindow());
mainWindow = codeArea.getMainWindow();
this.codeArea = codeArea;
this.node = node;
initUI();
loadWindowPos();
}
private void loadWindowPos() {
mainWindow.getSettings().loadWindowPos(this);
}
@Override
public void dispose() {
mainWindow.getSettings().saveWindowPos(this);
super.dispose();
}
private Path getDeobfMapPath(RootNode root) {
List<DexNode> dexNodes = root.getDexNodes();
if (dexNodes.isEmpty()) {
return null;
}
InputFile firstInputFile = dexNodes.get(0).getDexFile().getInputFile();
Path inputFilePath = firstInputFile.getFile().getAbsoluteFile().toPath();
String inputName = inputFilePath.getFileName().toString();
String baseName = inputName.substring(0, inputName.lastIndexOf('.'));
return inputFilePath.getParent().resolve(baseName + ".jobf");
}
private String getNodeAlias(String renameText) {
String type = "";
String id = "";
if (node instanceof JMethod) {
JavaMethod javaMethod = (JavaMethod) node.getJavaNode();
type = "m";
id = javaMethod.getMethodNode().getMethodInfo().getRawFullId();
} else if (node instanceof JField) {
JavaField javaField = (JavaField) node.getJavaNode();
type = "f";
id = javaField.getFieldNode().getFieldInfo().getRawFullId();
} else if (node instanceof JClass) {
type = "c";
JavaNode javaNode = node.getJavaNode();
id = javaNode.getFullName();
if (javaNode instanceof JavaClass) {
JavaClass javaClass = (JavaClass) javaNode;
id = javaClass.getRealFullName();
}
} else if (node instanceof JPackage) {
type = "p";
id = node.getJavaNode().getFullName();
}
return String.format("%s %s = %s", type, id, renameText);
}
private boolean updateDeobfMap(String renameText, RootNode root) {
Path deobfMapPath = getDeobfMapPath(root);
if (deobfMapPath == null) {
LOG.error("rename(): Failed deofbMapFile is null");
return false;
}
String alias = getNodeAlias(renameText);
LOG.info("rename(): " + alias);
try {
List<String> deobfMap = readAndUpdateDeobfMap(deobfMapPath, alias);
File tmpFile = File.createTempFile("deobf_tmp_", ".txt");
FileOutputStream fileOut = new FileOutputStream(tmpFile);
for (String entry : deobfMap) {
fileOut.write(entry.getBytes());
fileOut.write(System.lineSeparator().getBytes());
}
fileOut.close();
File oldMap = File.createTempFile("deobf_bak_", ".txt");
Files.copy(deobfMapPath, oldMap.toPath(), StandardCopyOption.REPLACE_EXISTING);
Files.copy(tmpFile.toPath(), deobfMapPath, StandardCopyOption.REPLACE_EXISTING);
Files.delete(oldMap.toPath());
} catch (IOException e) {
LOG.error("rename(): Failed to write deofbMapFile {}", deobfMapPath);
e.printStackTrace();
return false;
}
return true;
}
private List<String> readAndUpdateDeobfMap(Path deobfMapPath, String alias) throws IOException {
List<String> deobfMap = Files.readAllLines(deobfMapPath, StandardCharsets.UTF_8);
String id = alias.split("=")[0];
LOG.info("Id = " + id);
int i = 0;
while (i < deobfMap.size()) {
if (deobfMap.get(i).startsWith(id)) {
LOG.info("Removing entry " + deobfMap.get(i));
deobfMap.remove(i);
} else {
i++;
}
}
deobfMap.add(alias);
return deobfMap;
}
private void rename() {
String renameText = renameField.getText();
if (renameText == null || renameText.length() == 0 || codeArea.getText() == null) {
return;
}
RootNode root = mainWindow.getWrapper().getDecompiler().getRoot();
if (node == null) {
LOG.error("rename(): rootNode is null!");
dispose();
return;
}
if (!updateDeobfMap(renameText, root)) {
LOG.error("rename(): updateDeobfMap() failed");
dispose();
return;
}
mainWindow.reOpenFile();
dispose();
}
private void initCommon() {
KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
getRootPane().registerKeyboardAction(e -> dispose(), stroke, JComponent.WHEN_IN_FOCUSED_WINDOW);
}
@NotNull
private JPanel initButtonsPanel() {
JButton cancelButton = new JButton(NLS.str("search_dialog.cancel"));
cancelButton.addActionListener(event -> dispose());
JButton renameBtn = new JButton(NLS.str("popup.rename"));
renameBtn.addActionListener(event -> rename());
getRootPane().setDefaultButton(renameBtn);
JPanel buttonPane = new JPanel();
buttonPane.setLayout(new BoxLayout(buttonPane, BoxLayout.LINE_AXIS));
buttonPane.setBorder(BorderFactory.createEmptyBorder(0, 10, 10, 10));
buttonPane.add(Box.createRigidArea(new Dimension(5, 0)));
buttonPane.add(Box.createHorizontalGlue());
buttonPane.add(renameBtn);
buttonPane.add(Box.createRigidArea(new Dimension(10, 0)));
buttonPane.add(cancelButton);
return buttonPane;
}
private void initUI() {
JLabel lbl = new JLabel(NLS.str("popup.rename"));
JLabel nodeLabel = new JLabel(this.node.makeLongString(), this.node.getIcon(), SwingConstants.LEFT);
lbl.setLabelFor(nodeLabel);
renameField = new JTextField(40);
renameField.addActionListener(e -> rename());
renameField.setText(node.getName());
renameField.selectAll();
new TextStandardActions(renameField);
JPanel renamePane = new JPanel();
renamePane.setLayout(new FlowLayout(FlowLayout.LEFT));
renamePane.add(lbl);
renamePane.add(nodeLabel);
renamePane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
JPanel textPane = new JPanel();
textPane.setLayout(new FlowLayout(FlowLayout.LEFT));
textPane.add(renameField);
textPane.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
initCommon();
JPanel buttonPane = initButtonsPanel();
Container contentPane = getContentPane();
contentPane.add(renamePane, BorderLayout.PAGE_START);
contentPane.add(textPane, BorderLayout.CENTER);
contentPane.add(buttonPane, BorderLayout.PAGE_END);
setTitle(NLS.str("popup.rename"));
pack();
setSize(800, 80);
setLocationRelativeTo(null);
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
setModalityType(ModalityType.MODELESS);
}
}
......@@ -54,13 +54,16 @@ public final class CodeArea extends AbstractCodeArea {
private void addMenuItems() {
FindUsageAction findUsage = new FindUsageAction(this);
GoToDeclarationAction goToDeclaration = new GoToDeclarationAction(this);
RenameAction rename = new RenameAction(this);
JPopupMenu popup = getPopupMenu();
popup.addSeparator();
popup.add(findUsage);
popup.add(goToDeclaration);
popup.add(rename);
popup.addPopupMenuListener(findUsage);
popup.addPopupMenuListener(goToDeclaration);
popup.addPopupMenuListener(rename);
}
public int adjustOffsetForToken(@Nullable Token token) {
......
package jadx.gui.ui.codearea;
import java.awt.event.ActionEvent;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.RenameDialog;
import jadx.gui.utils.NLS;
public final class RenameAction extends JNodeMenuAction<JNode> {
private static final long serialVersionUID = -4680872086148463289L;
private static final Logger LOG = LoggerFactory.getLogger(RenameAction.class);
public RenameAction(CodeArea codeArea) {
super(NLS.str("popup.rename"), codeArea);
}
@Override
public void actionPerformed(ActionEvent e) {
if (node == null) {
LOG.info("node == null!");
return;
}
RenameDialog renameDialog = new RenameDialog(codeArea, node);
renameDialog.setVisible(true);
}
@Nullable
@Override
public JNode getNodeByOffset(int offset) {
return codeArea.getJNodeAtOffset(offset);
}
}
......@@ -145,6 +145,7 @@ popup.select_all=Select All
popup.find_usage=Find Usage
popup.go_to_declaration=Go to declaration
popup.exclude=Exclude
popup.rename=Rename
confirm.save_as_title=Confirm Save as
confirm.save_as_message=%s already exists.\nDo you want to replace it?
......
......@@ -145,6 +145,7 @@ popup.select_all=Seleccionar todo
#popup.find_usage=
#popup.go_to_declaration=
#popup.exclude=
popup.rename=Nimeta ümber
#confirm.save_as_title=
#confirm.save_as_message=
......
......@@ -145,6 +145,7 @@ popup.select_all=全选
popup.find_usage=查找用例
popup.go_to_declaration=跳到声明
popup.exclude=排除
popup.rename=改名
confirm.save_as_title=确认另存为
confirm.save_as_message=%s 已存在。\n你想替换它吗?
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册