From d5219e7f0cafd39470dc479da8ca15b822a6eeca Mon Sep 17 00:00:00 2001 From: Skylot Date: Tue, 15 Nov 2022 19:00:54 +0000 Subject: [PATCH] feat: allow to move class to another package --- .../src/main/java/jadx/api/JavaClass.java | 2 +- .../jadx/core/deobf/DeobfAliasProvider.java | 83 ++++++++++--------- .../java/jadx/core/dex/info/ClassInfo.java | 20 ++++- .../java/jadx/core/dex/nodes/ClassNode.java | 49 ++++++++++- .../java/jadx/core/dex/nodes/PackageNode.java | 18 ++-- .../java/jadx/core/dex/nodes/RootNode.java | 27 ++++++ .../java/jadx/core/utils/StringUtils.java | 24 ++++++ .../main/java/jadx/gui/treemodel/JClass.java | 15 +++- .../src/main/java/jadx/gui/ui/MainWindow.java | 5 ++ .../jadx/gui/utils/pkgs/JRenamePackage.java | 5 +- .../gui/utils/pkgs/TestJRenamePackage.java | 4 +- 11 files changed, 191 insertions(+), 61 deletions(-) diff --git a/jadx-core/src/main/java/jadx/api/JavaClass.java b/jadx-core/src/main/java/jadx/api/JavaClass.java index 54e817ec7..91bd9c7f9 100644 --- a/jadx-core/src/main/java/jadx/api/JavaClass.java +++ b/jadx-core/src/main/java/jadx/api/JavaClass.java @@ -329,7 +329,7 @@ public final class JavaClass implements JavaNode { @Override public void removeAlias() { - this.cls.getClassInfo().removeAlias(); + cls.removeAlias(); } @Override diff --git a/jadx-core/src/main/java/jadx/core/deobf/DeobfAliasProvider.java b/jadx-core/src/main/java/jadx/core/deobf/DeobfAliasProvider.java index 2177ca7f2..d617f5c93 100644 --- a/jadx-core/src/main/java/jadx/core/deobf/DeobfAliasProvider.java +++ b/jadx-core/src/main/java/jadx/core/deobf/DeobfAliasProvider.java @@ -8,6 +8,7 @@ import jadx.core.dex.nodes.FieldNode; import jadx.core.dex.nodes.MethodNode; import jadx.core.dex.nodes.PackageNode; import jadx.core.dex.nodes.RootNode; +import jadx.core.utils.StringUtils; public class DeobfAliasProvider implements IAliasProvider { @@ -74,46 +75,48 @@ public class DeobfAliasProvider implements IAliasProvider { } else if (cls.getAccessFlags().isAbstract()) { result.append("Abstract"); } - - // Process current class and all super classes - ClassNode currentCls = cls; - outerLoop: while (currentCls != null) { - if (currentCls.getSuperClass() != null) { - String superClsName = currentCls.getSuperClass().getObject(); - if (superClsName.startsWith("android.app.")) { - // e.g. Activity or Fragment - result.append(superClsName.substring(12)); - break; - } else if (superClsName.startsWith("android.os.")) { - // e.g. AsyncTask - result.append(superClsName.substring(11)); - break; - } - } - for (ArgType intf : cls.getInterfaces()) { - String intfClsName = intf.getObject(); - if (intfClsName.equals("java.lang.Runnable")) { - result.append("Runnable"); - break outerLoop; - } else if (intfClsName.startsWith("java.util.concurrent.")) { - // e.g. Callable - result.append(intfClsName.substring(21)); - break outerLoop; - } else if (intfClsName.startsWith("android.view.")) { - // e.g. View.OnClickListener - result.append(intfClsName.substring(13)); - break outerLoop; - } else if (intfClsName.startsWith("android.content.")) { - // e.g. DialogInterface.OnClickListener - result.append(intfClsName.substring(16)); - break outerLoop; - } - } - if (currentCls.getSuperClass() == null) { - break; - } - currentCls = cls.root().resolveClass(currentCls.getSuperClass()); - } + result.append(getBaseName(cls)); return result.toString(); } + + /** + * Process current class and all super classes to get meaningful parent name + */ + private static String getBaseName(ClassNode cls) { + ClassNode currentCls = cls; + while (currentCls != null) { + ArgType superCls = currentCls.getSuperClass(); + if (superCls != null) { + String superClsName = superCls.getObject(); + if (superClsName.startsWith("android.app.") // e.g. Activity or Fragment + || superClsName.startsWith("android.os.") // e.g. AsyncTask + ) { + return getClsName(superClsName); + } + } + for (ArgType interfaceType : cls.getInterfaces()) { + String name = interfaceType.getObject(); + if (name.equals("java.lang.Runnable")) { + return "Runnable"; + } + if (name.startsWith("java.util.concurrent.") // e.g. Callable + || name.startsWith("android.view.") // e.g. View.OnClickListener + || name.startsWith("android.content.") // e.g. DialogInterface.OnClickListener + ) { + return getClsName(name); + } + } + if (superCls == null) { + break; + } + currentCls = cls.root().resolveClass(superCls); + } + return ""; + } + + private static String getClsName(String name) { + int pgkEnd = name.lastIndexOf('.'); + String clsName = name.substring(pgkEnd + 1); + return StringUtils.removeChar(clsName, '$'); + } } diff --git a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java index e073fa4b9..2041381e0 100644 --- a/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java +++ b/jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java @@ -79,6 +79,15 @@ public final class ClassInfo implements Comparable { } } + public void changePkgAndName(String aliasPkg, String aliasShortName) { + if (isInner()) { + throw new JadxRuntimeException("Can't change package for inner class"); + } + ClassAliasInfo newAlias = new ClassAliasInfo(aliasPkg, aliasShortName); + fillAliasFullName(newAlias); + this.alias = newAlias; + } + private void fillAliasFullName(ClassAliasInfo alias) { if (parentClass == null) { alias.setFullName(makeFullClsName(alias.getPkg(), alias.getShortName(), null, true, false)); @@ -117,6 +126,10 @@ public final class ClassInfo implements Comparable { return parentClass != null && parentClass.hasAlias(); } + public boolean hasAliasPkg() { + return !getPackage().equals(getAliasPkg()); + } + public void removeAlias() { this.alias = null; } @@ -272,14 +285,13 @@ public final class ClassInfo implements Comparable { return true; } if (obj instanceof ClassInfo) { - ClassInfo other = (ClassInfo) obj; - return this.type.equals(other.type); + return type.equals(((ClassInfo) obj).type); } return false; } @Override - public int compareTo(@NotNull ClassInfo o) { - return getFullName().compareTo(o.getFullName()); + public int compareTo(@NotNull ClassInfo other) { + return getRawName().compareTo(other.getRawName()); } } diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java index c818f9533..f77580bfc 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java @@ -60,7 +60,7 @@ public class ClassNode extends NotificationAttrNode private final IClassData clsData; private final ClassInfo clsInfo; - private final PackageNode packageNode; + private PackageNode packageNode; private AccessInfo accessFlags; private ArgType superClass; private List interfaces; @@ -559,9 +559,50 @@ public class ClassNode extends NotificationAttrNode parentClass = this; } + /** + * Change class name and package (if full name provided) + * Leading dot can be used to move to default package. + * Package for inner classes can't be changed. + */ @Override public void rename(String newName) { - clsInfo.changeShortName(newName); + int lastDot = newName.lastIndexOf('.'); + if (lastDot == -1) { + clsInfo.changeShortName(newName); + return; + } + if (isInner()) { + addWarn("Can't change package for inner class: " + this + " to " + newName); + return; + } + // change class package + String newPkg = newName.substring(0, lastDot); + String newShortName = newName.substring(lastDot + 1); + if (changeClassNodePackage(newPkg)) { + clsInfo.changePkgAndName(newPkg, newShortName); + } else { + clsInfo.changeShortName(newShortName); + } + } + + private boolean changeClassNodePackage(String fullPkg) { + if (clsInfo.isInner()) { + throw new JadxRuntimeException("Can't change package for inner class"); + } + if (fullPkg.equals(clsInfo.getAliasPkg())) { + return false; + } + root.removeClsFromPackage(packageNode, this); + packageNode = PackageNode.getForClass(root, fullPkg, this); + root.sortPackages(); + return true; + } + + public void removeAlias() { + if (!clsInfo.isInner()) { + changeClassNodePackage(clsInfo.getPackage()); + } + clsInfo.removeAlias(); } @Override @@ -569,7 +610,7 @@ public class ClassNode extends NotificationAttrNode if (isInner()) { return; } - getClassInfo().changePkg(packageNode.getAliasPkgInfo().getFullName()); + clsInfo.changePkg(packageNode.getAliasPkgInfo().getFullName()); } public PackageNode getPackageNode() { @@ -884,7 +925,7 @@ public class ClassNode extends NotificationAttrNode @Override public int compareTo(@NotNull ClassNode o) { - return this.getFullName().compareTo(o.getFullName()); + return this.clsInfo.compareTo(o.clsInfo); } @Override diff --git a/jadx-core/src/main/java/jadx/core/dex/nodes/PackageNode.java b/jadx-core/src/main/java/jadx/core/dex/nodes/PackageNode.java index b5ff66f16..1756b5939 100644 --- a/jadx-core/src/main/java/jadx/core/dex/nodes/PackageNode.java +++ b/jadx-core/src/main/java/jadx/core/dex/nodes/PackageNode.java @@ -10,6 +10,8 @@ import org.jetbrains.annotations.Nullable; import jadx.api.JavaPackage; import jadx.core.dex.info.PackageInfo; +import static jadx.core.utils.StringUtils.containsChar; + public class PackageNode implements IPackageUpdate, IDexNode, Comparable { private final RootNode root; @@ -66,16 +68,16 @@ public class PackageNode implements IPackageUpdate, IDexNode, Comparable treeExpansions) { TreeNode[] pathNodes = treeModel.getPathToRoot(node); if (pathNodes == null) { diff --git a/jadx-gui/src/main/java/jadx/gui/utils/pkgs/JRenamePackage.java b/jadx-gui/src/main/java/jadx/gui/utils/pkgs/JRenamePackage.java index 0a8d9db0b..615efa115 100644 --- a/jadx-gui/src/main/java/jadx/gui/utils/pkgs/JRenamePackage.java +++ b/jadx-gui/src/main/java/jadx/gui/utils/pkgs/JRenamePackage.java @@ -66,7 +66,7 @@ public class JRenamePackage implements JRenameNode { } private static final Pattern PACKAGE_RENAME_PATTERN = Pattern.compile( - "PKG(\\.PKG)*(\\.)?".replace("PKG", VALID_JAVA_IDENTIFIER.pattern())); + "(\\.)?PKG(\\.PKG)*".replace("PKG", VALID_JAVA_IDENTIFIER.pattern())); static boolean isValidPackageName(String newName) { if (newName == null || newName.isEmpty() || NameMapper.isReserved(newName)) { @@ -96,8 +96,7 @@ public class JRenamePackage implements JRenameNode { @Override public void reload(MainWindow mainWindow) { - mainWindow.getCacheObject().setPackageHelper(null); - mainWindow.getTreeRoot().update(); + mainWindow.rebuildPackagesTree(); mainWindow.reloadTree(); } } diff --git a/jadx-gui/src/test/java/jadx/gui/utils/pkgs/TestJRenamePackage.java b/jadx-gui/src/test/java/jadx/gui/utils/pkgs/TestJRenamePackage.java index 2c7b9d604..3442af1a2 100644 --- a/jadx-gui/src/test/java/jadx/gui/utils/pkgs/TestJRenamePackage.java +++ b/jadx-gui/src/test/java/jadx/gui/utils/pkgs/TestJRenamePackage.java @@ -10,11 +10,11 @@ class TestJRenamePackage { void isValidName() { valid("foo"); valid("foo.bar"); - valid("foo.bar."); + valid(".bar"); invalid(""); invalid("0foo"); - invalid(".foo"); + invalid("foo."); invalid("do"); invalid("foo.if"); invalid("foo.if.bar");