feat: allow to move class to another package
This commit is contained in:
@@ -329,7 +329,7 @@ public final class JavaClass implements JavaNode {
|
||||
|
||||
@Override
|
||||
public void removeAlias() {
|
||||
this.cls.getClassInfo().removeAlias();
|
||||
cls.removeAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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, '$');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,6 +79,15 @@ public final class ClassInfo implements Comparable<ClassInfo> {
|
||||
}
|
||||
}
|
||||
|
||||
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<ClassInfo> {
|
||||
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<ClassInfo> {
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ArgType> 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
|
||||
|
||||
@@ -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<PackageNode> {
|
||||
|
||||
private final RootNode root;
|
||||
@@ -66,16 +68,16 @@ public class PackageNode implements IPackageUpdate, IDexNode, Comparable<Package
|
||||
public void rename(String newName, boolean runUpdates) {
|
||||
String alias;
|
||||
boolean isFullAlias;
|
||||
if (newName.indexOf('/') != -1) {
|
||||
if (containsChar(newName, '/')) {
|
||||
alias = newName.replace('/', '.');
|
||||
isFullAlias = true;
|
||||
} else if (newName.endsWith(".")) {
|
||||
// treat as full pkg, remove ending dot
|
||||
alias = newName.substring(0, newName.length() - 1);
|
||||
} else if (newName.startsWith(".")) {
|
||||
// treat as full pkg, remove start dot
|
||||
alias = newName.substring(1);
|
||||
isFullAlias = true;
|
||||
} else {
|
||||
alias = newName;
|
||||
isFullAlias = alias.indexOf('.') != -1;
|
||||
isFullAlias = containsChar(newName, '.');
|
||||
}
|
||||
if (isFullAlias) {
|
||||
setFullAlias(alias, runUpdates);
|
||||
@@ -151,7 +153,7 @@ public class PackageNode implements IPackageUpdate, IDexNode, Comparable<Package
|
||||
aliasPkgInfo = pkgInfo;
|
||||
}
|
||||
|
||||
public PackageNode getParentPkg() {
|
||||
public @Nullable PackageNode getParentPkg() {
|
||||
return parentPkg;
|
||||
}
|
||||
|
||||
@@ -183,6 +185,10 @@ public class PackageNode implements IPackageUpdate, IDexNode, Comparable<Package
|
||||
this.javaNode = javaNode;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return classes.isEmpty() && subPackages.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String typeName() {
|
||||
return "package";
|
||||
|
||||
@@ -399,6 +399,33 @@ public class RootNode {
|
||||
packages.add(pkg);
|
||||
}
|
||||
|
||||
public void removePackage(PackageNode pkg) {
|
||||
if (pkgMap.remove(pkg.getPkgInfo().getFullName()) != null) {
|
||||
packages.remove(pkg);
|
||||
PackageNode parentPkg = pkg.getParentPkg();
|
||||
if (parentPkg != null) {
|
||||
parentPkg.getSubPackages().remove(pkg);
|
||||
if (parentPkg.isEmpty()) {
|
||||
removePackage(parentPkg);
|
||||
}
|
||||
}
|
||||
for (PackageNode subPkg : pkg.getSubPackages()) {
|
||||
removePackage(subPkg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void sortPackages() {
|
||||
Collections.sort(packages);
|
||||
}
|
||||
|
||||
public void removeClsFromPackage(PackageNode pkg, ClassNode cls) {
|
||||
boolean removed = pkg.getClasses().remove(cls);
|
||||
if (removed && pkg.isEmpty()) {
|
||||
removePackage(pkg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update sub packages
|
||||
*/
|
||||
|
||||
@@ -278,6 +278,30 @@ public class StringUtils {
|
||||
return count;
|
||||
}
|
||||
|
||||
public static boolean containsChar(String str, char ch) {
|
||||
return str.indexOf(ch) != -1;
|
||||
}
|
||||
|
||||
public static String removeChar(String str, char ch) {
|
||||
int pos = str.indexOf(ch);
|
||||
if (pos == -1) {
|
||||
return str;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder(str.length());
|
||||
int cur = 0;
|
||||
int next = pos;
|
||||
while (true) {
|
||||
sb.append(str, cur, next);
|
||||
cur = next + 1;
|
||||
next = str.indexOf(ch, cur);
|
||||
if (next == -1) {
|
||||
sb.append(str, cur, str.length());
|
||||
break;
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* returns how many lines does it have between start to pos in content.
|
||||
*/
|
||||
|
||||
@@ -199,7 +199,18 @@ public class JClass extends JLoadableNode implements JRenameNode {
|
||||
|
||||
@Override
|
||||
public boolean isValidName(String newName) {
|
||||
return NameMapper.isValidIdentifier(newName);
|
||||
if (NameMapper.isValidIdentifier(newName)) {
|
||||
return true;
|
||||
}
|
||||
if (cls.isInner()) {
|
||||
// disallow to change package for inner classes
|
||||
return false;
|
||||
}
|
||||
if (NameMapper.isValidFullIdentifier(newName)) {
|
||||
return true;
|
||||
}
|
||||
// moving to default pkg
|
||||
return newName.startsWith(".") && NameMapper.isValidIdentifier(newName.substring(1));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -220,6 +231,8 @@ public class JClass extends JLoadableNode implements JRenameNode {
|
||||
|
||||
@Override
|
||||
public void reload(MainWindow mainWindow) {
|
||||
// TODO: rebuild packages only if class package has been changed
|
||||
mainWindow.rebuildPackagesTree();
|
||||
mainWindow.reloadTree();
|
||||
}
|
||||
|
||||
|
||||
@@ -819,6 +819,11 @@ public class MainWindow extends JFrame {
|
||||
treeReloading = false;
|
||||
}
|
||||
|
||||
public void rebuildPackagesTree() {
|
||||
cacheObject.setPackageHelper(null);
|
||||
treeRoot.update();
|
||||
}
|
||||
|
||||
private void expand(TreeNode node, List<String[]> treeExpansions) {
|
||||
TreeNode[] pathNodes = treeModel.getPathToRoot(node);
|
||||
if (pathNodes == null) {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user