refactor: use package nodes in api and ui
This commit is contained in:
@@ -6,7 +6,6 @@ import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -45,6 +44,7 @@ import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
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.dex.visitors.SaveCode;
|
||||
import jadx.core.export.ExportGradleProject;
|
||||
@@ -444,25 +444,7 @@ public final class JadxDecompiler implements IJadxDecompiler, Closeable {
|
||||
}
|
||||
|
||||
public List<JavaPackage> getPackages() {
|
||||
List<JavaClass> classList = getClasses();
|
||||
if (classList.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
Map<String, List<JavaClass>> map = new HashMap<>();
|
||||
for (JavaClass javaClass : classList) {
|
||||
String pkg = javaClass.getPackage();
|
||||
List<JavaClass> clsList = map.computeIfAbsent(pkg, k -> new ArrayList<>());
|
||||
clsList.add(javaClass);
|
||||
}
|
||||
List<JavaPackage> packages = new ArrayList<>(map.size());
|
||||
for (Map.Entry<String, List<JavaClass>> entry : map.entrySet()) {
|
||||
packages.add(new JavaPackage(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
Collections.sort(packages);
|
||||
for (JavaPackage pkg : packages) {
|
||||
pkg.getClasses().sort(Comparator.comparing(JavaClass::getName, String.CASE_INSENSITIVE_ORDER));
|
||||
}
|
||||
return Collections.unmodifiableList(packages);
|
||||
return Utils.collectionMap(root.getPackages(), this::convertPackageNode);
|
||||
}
|
||||
|
||||
public int getErrorsCount() {
|
||||
@@ -545,6 +527,26 @@ public final class JadxDecompiler implements IJadxDecompiler, Closeable {
|
||||
return javaMethod;
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
synchronized JavaPackage convertPackageNode(PackageNode pkg) {
|
||||
JavaPackage foundPkg = pkg.getJavaNode();
|
||||
if (foundPkg != null) {
|
||||
return foundPkg;
|
||||
}
|
||||
List<JavaClass> clsList = Utils.collectionMap(pkg.getClasses(), this::convertClassNode);
|
||||
int subPkgsCount = pkg.getSubPackages().size();
|
||||
List<JavaPackage> subPkgs = subPkgsCount == 0 ? Collections.emptyList() : new ArrayList<>(subPkgsCount);
|
||||
JavaPackage javaPkg = new JavaPackage(pkg, clsList, subPkgs);
|
||||
if (subPkgsCount != 0) {
|
||||
// add subpackages after parent to avoid endless recursion
|
||||
for (PackageNode subPackage : pkg.getSubPackages()) {
|
||||
subPkgs.add(convertPackageNode(subPackage));
|
||||
}
|
||||
}
|
||||
pkg.setJavaNode(javaPkg);
|
||||
return javaPkg;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public JavaClass searchJavaClassByOrigFullName(String fullName) {
|
||||
return getRoot().getClasses().stream()
|
||||
|
||||
@@ -18,8 +18,7 @@ public interface JavaNode {
|
||||
|
||||
List<JavaNode> getUseIn();
|
||||
|
||||
default void removeAlias() {
|
||||
}
|
||||
void removeAlias();
|
||||
|
||||
boolean isOwnCodeAnnotation(ICodeAnnotation ann);
|
||||
}
|
||||
|
||||
@@ -1,36 +1,85 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.jetbrains.annotations.ApiStatus.Internal;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import jadx.api.metadata.ICodeAnnotation;
|
||||
import jadx.core.dex.info.PackageInfo;
|
||||
import jadx.core.dex.nodes.PackageNode;
|
||||
|
||||
public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
||||
private final String name;
|
||||
private final PackageNode pkgNode;
|
||||
private final List<JavaClass> classes;
|
||||
private final List<JavaPackage> subPkgs;
|
||||
|
||||
JavaPackage(String name, List<JavaClass> classes) {
|
||||
this.name = name;
|
||||
JavaPackage(PackageNode pkgNode, List<JavaClass> classes, List<JavaPackage> subPkgs) {
|
||||
this.pkgNode = pkgNode;
|
||||
this.classes = classes;
|
||||
this.subPkgs = subPkgs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
return pkgNode.getAliasPkgInfo().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFullName() {
|
||||
// TODO: store full package name
|
||||
return name;
|
||||
return pkgNode.getAliasPkgInfo().getFullName();
|
||||
}
|
||||
|
||||
public String getRawName() {
|
||||
return pkgNode.getPkgInfo().getName();
|
||||
}
|
||||
|
||||
public String getRawFullName() {
|
||||
return pkgNode.getPkgInfo().getFullName();
|
||||
}
|
||||
|
||||
public List<JavaPackage> getSubPackages() {
|
||||
return subPkgs;
|
||||
}
|
||||
|
||||
public List<JavaClass> getClasses() {
|
||||
return classes;
|
||||
}
|
||||
|
||||
public boolean isRoot() {
|
||||
return pkgNode.isRoot();
|
||||
}
|
||||
|
||||
public boolean isLeaf() {
|
||||
return pkgNode.isLeaf();
|
||||
}
|
||||
|
||||
public boolean isDefault() {
|
||||
return getFullName().isEmpty();
|
||||
}
|
||||
|
||||
public void rename(String alias) {
|
||||
pkgNode.rename(alias);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAlias() {
|
||||
pkgNode.removeAlias();
|
||||
}
|
||||
|
||||
public boolean isParentRenamed() {
|
||||
PackageInfo parent = pkgNode.getPkgInfo().getParentPkg();
|
||||
PackageInfo aliasParent = pkgNode.getAliasPkgInfo().getParentPkg();
|
||||
return !Objects.equals(parent, aliasParent);
|
||||
}
|
||||
|
||||
@Internal
|
||||
public PackageNode getPkgNode() {
|
||||
return pkgNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JavaClass getDeclaringClass() {
|
||||
return null;
|
||||
@@ -48,7 +97,16 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
||||
|
||||
@Override
|
||||
public List<JavaNode> getUseIn() {
|
||||
return Collections.emptyList();
|
||||
List<JavaNode> list = new ArrayList<>();
|
||||
addUseIn(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
public void addUseIn(List<JavaNode> list) {
|
||||
list.addAll(classes);
|
||||
for (JavaPackage subPkg : subPkgs) {
|
||||
subPkg.addUseIn(list);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -58,7 +116,7 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull JavaPackage o) {
|
||||
return name.compareTo(o.name);
|
||||
return pkgNode.compareTo(o.pkgNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -70,16 +128,16 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
|
||||
return false;
|
||||
}
|
||||
JavaPackage that = (JavaPackage) o;
|
||||
return name.equals(that.name);
|
||||
return pkgNode.equals(that.pkgNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode();
|
||||
return pkgNode.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
return pkgNode.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -71,6 +71,10 @@ public class JavaVariable implements JavaNode {
|
||||
return Collections.singletonList(mth);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAlias() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
|
||||
if (ann.getAnnType() == ICodeAnnotation.AnnType.VAR_REF) {
|
||||
|
||||
@@ -85,4 +85,12 @@ public class JadxCodeRename implements ICodeRename {
|
||||
public int hashCode() {
|
||||
return 31 * getNodeRef().hashCode() + Objects.hashCode(getCodeRef());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JadxCodeRename{" + nodeRef
|
||||
+ ", codeRef=" + codeRef
|
||||
+ ", newName='" + newName + '\''
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import static jadx.core.utils.StringUtils.notEmpty;
|
||||
|
||||
public class NameMapper {
|
||||
|
||||
private static final Pattern VALID_JAVA_IDENTIFIER = Pattern.compile(
|
||||
public static final Pattern VALID_JAVA_IDENTIFIER = Pattern.compile(
|
||||
"\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*");
|
||||
|
||||
private static final Pattern VALID_JAVA_FULL_IDENTIFIER = Pattern.compile(
|
||||
|
||||
@@ -7,6 +7,7 @@ import java.util.Objects;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.JavaPackage;
|
||||
import jadx.core.dex.info.PackageInfo;
|
||||
|
||||
public class PackageNode implements IPackageUpdate, IDexNode, Comparable<PackageNode> {
|
||||
@@ -19,6 +20,8 @@ public class PackageNode implements IPackageUpdate, IDexNode, Comparable<Package
|
||||
|
||||
private PackageInfo aliasPkgInfo;
|
||||
|
||||
private JavaPackage javaNode;
|
||||
|
||||
public static PackageNode getForClass(RootNode root, String fullPkg, ClassNode cls) {
|
||||
PackageNode pkg = getOrBuild(root, fullPkg);
|
||||
pkg.getClasses().add(cls);
|
||||
@@ -144,6 +147,10 @@ public class PackageNode implements IPackageUpdate, IDexNode, Comparable<Package
|
||||
return !Objects.equals(pkgInfo.getParentPkg(), aliasPkgInfo.getParentPkg());
|
||||
}
|
||||
|
||||
public void removeAlias() {
|
||||
aliasPkgInfo = pkgInfo;
|
||||
}
|
||||
|
||||
public PackageNode getParentPkg() {
|
||||
return parentPkg;
|
||||
}
|
||||
@@ -168,6 +175,14 @@ public class PackageNode implements IPackageUpdate, IDexNode, Comparable<Package
|
||||
return classes;
|
||||
}
|
||||
|
||||
public JavaPackage getJavaNode() {
|
||||
return javaNode;
|
||||
}
|
||||
|
||||
public void setJavaNode(JavaPackage javaNode) {
|
||||
this.javaNode = javaNode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String typeName() {
|
||||
return "package";
|
||||
|
||||
@@ -302,6 +302,9 @@ public class RootNode implements IRootNode {
|
||||
}
|
||||
}
|
||||
classes.forEach(ClassNode::updateParentClass);
|
||||
for (PackageNode pkg : packages) {
|
||||
pkg.getClasses().removeIf(ClassNode::isInner);
|
||||
}
|
||||
}
|
||||
|
||||
public void mergePasses(Map<JadxPassType, List<JadxPass>> customPasses) {
|
||||
|
||||
@@ -225,7 +225,7 @@ public class DebugUtils {
|
||||
}
|
||||
|
||||
public static void printStackTrace(String label) {
|
||||
LOG.debug("StackTrace: {}\n{}", label, Utils.getStackTrace(new Exception()));
|
||||
LOG.debug("StackTrace: {}\n{}", label, Utils.getFullStackTrace(new Exception()));
|
||||
}
|
||||
|
||||
public static void printMethodOverrideTop(RootNode root) {
|
||||
|
||||
@@ -3,10 +3,12 @@ package jadx.core.utils;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Deque;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
@@ -14,6 +16,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -116,13 +119,23 @@ public class Utils {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String getFullStackTrace(Throwable throwable) {
|
||||
return getStackTrace(throwable, false);
|
||||
}
|
||||
|
||||
public static String getStackTrace(Throwable throwable) {
|
||||
return getStackTrace(throwable, true);
|
||||
}
|
||||
|
||||
private static String getStackTrace(Throwable throwable, boolean filter) {
|
||||
if (throwable == null) {
|
||||
return "";
|
||||
}
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw, true);
|
||||
filterRecursive(throwable);
|
||||
if (filter) {
|
||||
filterRecursive(throwable);
|
||||
}
|
||||
throwable.printStackTrace(pw);
|
||||
return sw.getBuffer().toString();
|
||||
}
|
||||
@@ -345,6 +358,27 @@ public class Utils {
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple DFS visit for tree (cycles not allowed)
|
||||
*/
|
||||
public static <T> void treeDfsVisit(T root, Function<T, List<T>> childrenProvider, Consumer<T> visitor) {
|
||||
multiRootTreeDfsVisit(Collections.singletonList(root), childrenProvider, visitor);
|
||||
}
|
||||
|
||||
public static <T> void multiRootTreeDfsVisit(List<T> roots, Function<T, List<T>> childrenProvider, Consumer<T> visitor) {
|
||||
Deque<T> queue = new ArrayDeque<>(roots);
|
||||
while (true) {
|
||||
T current = queue.pollLast();
|
||||
if (current == null) {
|
||||
return;
|
||||
}
|
||||
visitor.accept(current);
|
||||
for (T child : childrenProvider.apply(current)) {
|
||||
queue.addLast(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static <T> T getOne(@Nullable List<T> list) {
|
||||
if (list == null || list.size() != 1) {
|
||||
|
||||
@@ -32,6 +32,7 @@ import jadx.gui.plugins.context.PluginsContext;
|
||||
import jadx.gui.settings.JadxProject;
|
||||
import jadx.gui.settings.JadxSettings;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.CacheObject;
|
||||
import jadx.gui.utils.codecache.CodeStringCache;
|
||||
import jadx.gui.utils.codecache.disk.BufferCodeCache;
|
||||
import jadx.gui.utils.codecache.disk.DiskCodeCache;
|
||||
@@ -264,6 +265,10 @@ public class JadxWrapper {
|
||||
return mainWindow.getSettings();
|
||||
}
|
||||
|
||||
public CacheObject getCache() {
|
||||
return mainWindow.getCacheObject();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param fullName
|
||||
* Full name of an outer class. Inner classes are not supported.
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package jadx.gui.treemodel;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
import javax.swing.JPopupMenu;
|
||||
@@ -12,6 +15,10 @@ import jadx.api.JavaClass;
|
||||
import jadx.api.JavaField;
|
||||
import jadx.api.JavaMethod;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.api.data.ICodeRename;
|
||||
import jadx.api.data.impl.JadxCodeRename;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
@@ -24,7 +31,7 @@ import jadx.gui.utils.Icons;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
public class JClass extends JLoadableNode {
|
||||
public class JClass extends JLoadableNode implements JRenameNode {
|
||||
private static final long serialVersionUID = -1239986875244097177L;
|
||||
|
||||
private static final ImageIcon ICON_CLASS_ABSTRACT = UiUtils.openSvgIcon("nodes/abstractClass");
|
||||
@@ -39,16 +46,10 @@ public class JClass extends JLoadableNode {
|
||||
private final transient JClass jParent;
|
||||
private transient boolean loaded;
|
||||
|
||||
public JClass(JavaClass cls) {
|
||||
this.cls = cls;
|
||||
this.jParent = null;
|
||||
this.loaded = false;
|
||||
}
|
||||
|
||||
public JClass(JavaClass cls, JClass parent) {
|
||||
this.cls = cls;
|
||||
this.jParent = parent;
|
||||
this.loaded = true;
|
||||
this.loaded = parent != null;
|
||||
}
|
||||
|
||||
public JavaClass getCls() {
|
||||
@@ -185,6 +186,37 @@ public class JClass extends JLoadableNode {
|
||||
return cls.getFullName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return makeLongStringHtml();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidName(String newName) {
|
||||
return NameMapper.isValidIdentifier(newName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICodeRename buildCodeRename(String newName, Set<ICodeRename> renames) {
|
||||
return new JadxCodeRename(JadxNodeRef.forCls(cls), newName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAlias() {
|
||||
cls.removeAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUpdateNodes(List<JavaNode> toUpdate) {
|
||||
toUpdate.add(cls);
|
||||
toUpdate.addAll(cls.getUseIn());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload(MainWindow mainWindow) {
|
||||
mainWindow.reloadTree();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return cls.hashCode();
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package jadx.gui.treemodel;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
@@ -11,6 +13,10 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import jadx.api.JavaField;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.api.data.ICodeRename;
|
||||
import jadx.api.data.impl.JadxCodeRename;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
@@ -18,7 +24,7 @@ import jadx.gui.ui.dialog.RenameDialog;
|
||||
import jadx.gui.utils.Icons;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
public class JField extends JNode {
|
||||
public class JField extends JNode implements JRenameNode {
|
||||
private static final long serialVersionUID = 1712572192106793359L;
|
||||
|
||||
private static final ImageIcon ICON_FLD_PRI = UiUtils.openSvgIcon("nodes/privateField");
|
||||
@@ -61,6 +67,37 @@ public class JField extends JNode {
|
||||
return RenameDialog.buildRenamePopup(mainWindow, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return makeLongStringHtml();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICodeRename buildCodeRename(String newName, Set<ICodeRename> renames) {
|
||||
return new JadxCodeRename(JadxNodeRef.forFld(field), newName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidName(String newName) {
|
||||
return NameMapper.isValidIdentifier(newName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAlias() {
|
||||
field.removeAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUpdateNodes(List<JavaNode> toUpdate) {
|
||||
toUpdate.add(field);
|
||||
toUpdate.addAll(field.getUseIn());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload(MainWindow mainWindow) {
|
||||
mainWindow.reloadTree();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
AccessInfo af = field.getAccessFlags();
|
||||
|
||||
@@ -2,6 +2,8 @@ package jadx.gui.treemodel;
|
||||
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
@@ -12,6 +14,10 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import jadx.api.JavaMethod;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.api.data.ICodeRename;
|
||||
import jadx.api.data.impl.JadxCodeRename;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.dex.attributes.AFlag;
|
||||
import jadx.core.dex.info.AccessInfo;
|
||||
import jadx.core.dex.instructions.args.ArgType;
|
||||
@@ -21,7 +27,7 @@ import jadx.gui.utils.Icons;
|
||||
import jadx.gui.utils.OverlayIcon;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
public class JMethod extends JNode {
|
||||
public class JMethod extends JNode implements JRenameNode {
|
||||
private static final long serialVersionUID = 3834526867464663751L;
|
||||
private static final ImageIcon ICON_METHOD_ABSTRACT = UiUtils.openSvgIcon("nodes/abstractMethod");
|
||||
private static final ImageIcon ICON_METHOD_PRIVATE = UiUtils.openSvgIcon("nodes/privateMethod");
|
||||
@@ -100,14 +106,6 @@ public class JMethod extends JNode {
|
||||
return SyntaxConstants.SYNTAX_STYLE_JAVA;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRename() {
|
||||
if (mth.isClassInit()) {
|
||||
return false;
|
||||
}
|
||||
return !mth.getMethodNode().contains(AFlag.DONT_RENAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JPopupMenu onTreePopupMenu(MainWindow mainWindow) {
|
||||
return RenameDialog.buildRenamePopup(mainWindow, this);
|
||||
@@ -139,6 +137,65 @@ public class JMethod extends JNode {
|
||||
return mth.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return makeLongStringHtml();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRename() {
|
||||
if (mth.isClassInit()) {
|
||||
return false;
|
||||
}
|
||||
return !mth.getMethodNode().contains(AFlag.DONT_RENAME);
|
||||
}
|
||||
|
||||
@Override
|
||||
public JRenameNode replace() {
|
||||
if (mth.isConstructor()) {
|
||||
// rename class instead constructor
|
||||
return jParent;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICodeRename buildCodeRename(String newName, Set<ICodeRename> renames) {
|
||||
List<JavaMethod> relatedMethods = mth.getOverrideRelatedMethods();
|
||||
if (!relatedMethods.isEmpty()) {
|
||||
for (JavaMethod relatedMethod : relatedMethods) {
|
||||
renames.remove(new JadxCodeRename(JadxNodeRef.forMth(relatedMethod), ""));
|
||||
}
|
||||
}
|
||||
return new JadxCodeRename(JadxNodeRef.forMth(mth), newName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidName(String newName) {
|
||||
return NameMapper.isValidIdentifier(newName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAlias() {
|
||||
mth.removeAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUpdateNodes(List<JavaNode> toUpdate) {
|
||||
toUpdate.add(mth);
|
||||
toUpdate.addAll(mth.getUseIn());
|
||||
List<JavaMethod> overrideRelatedMethods = mth.getOverrideRelatedMethods();
|
||||
toUpdate.addAll(overrideRelatedMethods);
|
||||
for (JavaMethod ovrdMth : overrideRelatedMethods) {
|
||||
toUpdate.addAll(ovrdMth.getUseIn());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload(MainWindow mainWindow) {
|
||||
mainWindow.reloadTree();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String makeString() {
|
||||
return UiUtils.typeFormat(makeBaseString(), getReturnType());
|
||||
|
||||
@@ -63,10 +63,6 @@ public abstract class JNode extends DefaultMutableTreeNode implements Comparable
|
||||
return javaNode.getName();
|
||||
}
|
||||
|
||||
public boolean canRename() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public @Nullable JPopupMenu onTreePopupMenu(MainWindow mainWindow) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,63 +1,50 @@
|
||||
package jadx.gui.treemodel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JPopupMenu;
|
||||
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.api.JavaPackage;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.gui.JadxWrapper;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.popupmenu.JPackagePopupMenu;
|
||||
import jadx.gui.utils.Icons;
|
||||
|
||||
import static jadx.gui.utils.UiUtils.escapeHtml;
|
||||
import static jadx.gui.utils.UiUtils.fadeHtml;
|
||||
import static jadx.gui.utils.UiUtils.wrapHtml;
|
||||
|
||||
public class JPackage extends JNode {
|
||||
private static final long serialVersionUID = -4120718634156839804L;
|
||||
|
||||
private String fullName;
|
||||
public static final String PACKAGE_DEFAULT_HTML_STR = wrapHtml(fadeHtml(escapeHtml("<empty>")));
|
||||
|
||||
private final JavaPackage pkg;
|
||||
private final boolean enabled;
|
||||
private final List<JClass> classes;
|
||||
private final List<JPackage> subPackages;
|
||||
|
||||
/**
|
||||
* Package created by full package alias, don't have a raw package reference.
|
||||
* `pkg` field point to the closest raw package leaf.
|
||||
*/
|
||||
private final boolean synthetic;
|
||||
|
||||
private String name;
|
||||
private boolean enabled;
|
||||
private List<JClass> classes;
|
||||
private List<JPackage> innerPackages;
|
||||
|
||||
public JPackage(JavaPackage pkg, JadxWrapper wrapper) {
|
||||
this(pkg.getName(), pkg.getName(),
|
||||
isPkgEnabled(wrapper, pkg.getName()),
|
||||
Utils.collectionMap(pkg.getClasses(), JClass::new),
|
||||
new ArrayList<>());
|
||||
update();
|
||||
}
|
||||
|
||||
public JPackage(String fullName, JadxWrapper wrapper) {
|
||||
this(fullName, fullName, isPkgEnabled(wrapper, fullName), new ArrayList<>(), new ArrayList<>());
|
||||
}
|
||||
|
||||
public JPackage(String fullName, String name) {
|
||||
this(fullName, name, true, Collections.emptyList(), Collections.emptyList());
|
||||
}
|
||||
|
||||
private JPackage(String fullName, String name, boolean enabled, List<JClass> classes, List<JPackage> innerPackages) {
|
||||
this.fullName = fullName;
|
||||
this.name = name;
|
||||
public JPackage(JavaPackage pkg, boolean enabled, List<JClass> classes, List<JPackage> subPackages, boolean synthetic) {
|
||||
this.pkg = pkg;
|
||||
this.enabled = enabled;
|
||||
this.classes = classes;
|
||||
this.innerPackages = innerPackages;
|
||||
this.subPackages = subPackages;
|
||||
this.synthetic = synthetic;
|
||||
}
|
||||
|
||||
private static boolean isPkgEnabled(JadxWrapper wrapper, String fullPkgName) {
|
||||
List<String> excludedPackages = wrapper.getExcludedPackages();
|
||||
return excludedPackages.isEmpty()
|
||||
|| excludedPackages.stream().filter(p -> !p.isEmpty())
|
||||
.noneMatch(p -> fullPkgName.equals(p) || fullPkgName.startsWith(p + '.'));
|
||||
}
|
||||
|
||||
public final void update() {
|
||||
public void update() {
|
||||
removeAllChildren();
|
||||
if (isEnabled()) {
|
||||
for (JPackage pkg : innerPackages) {
|
||||
for (JPackage pkg : subPackages) {
|
||||
pkg.update();
|
||||
add(pkg);
|
||||
}
|
||||
@@ -73,44 +60,37 @@ public class JPackage extends JNode {
|
||||
return new JPackagePopupMenu(mainWindow, this);
|
||||
}
|
||||
|
||||
public JavaPackage getPkg() {
|
||||
return pkg;
|
||||
}
|
||||
|
||||
public JavaNode getJavaNode() {
|
||||
return pkg;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRename() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
public void updateBothNames(String fullName, String name, JadxWrapper wrapper) {
|
||||
this.fullName = fullName;
|
||||
this.name = name;
|
||||
this.enabled = isPkgEnabled(wrapper, fullName);
|
||||
}
|
||||
|
||||
public void updateName(String name) {
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public List<JPackage> getInnerPackages() {
|
||||
return innerPackages;
|
||||
}
|
||||
|
||||
public void setInnerPackages(List<JPackage> innerPackages) {
|
||||
this.innerPackages = innerPackages;
|
||||
public List<JPackage> getSubPackages() {
|
||||
return subPackages;
|
||||
}
|
||||
|
||||
public List<JClass> getClasses() {
|
||||
return classes;
|
||||
}
|
||||
|
||||
public void setClasses(List<JClass> classes) {
|
||||
this.classes = classes;
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public boolean isSynthetic() {
|
||||
return synthetic;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -131,12 +111,12 @@ public class JPackage extends JNode {
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
return name.equals(((JPackage) o).name);
|
||||
return pkg.equals(((JPackage) o).pkg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode();
|
||||
return pkg.hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -145,11 +125,20 @@ public class JPackage extends JNode {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String makeLongString() {
|
||||
public String makeStringHtml() {
|
||||
if (name.isEmpty()) {
|
||||
return PACKAGE_DEFAULT_HTML_STR;
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
@Override
|
||||
public String makeLongString() {
|
||||
return pkg.getFullName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package jadx.gui.treemodel;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.api.data.ICodeRename;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
|
||||
public interface JRenameNode {
|
||||
|
||||
String getTitle();
|
||||
|
||||
String getName();
|
||||
|
||||
Icon getIcon();
|
||||
|
||||
boolean canRename();
|
||||
|
||||
default JRenameNode replace() {
|
||||
return this;
|
||||
}
|
||||
|
||||
ICodeRename buildCodeRename(String newName, Set<ICodeRename> renames);
|
||||
|
||||
boolean isValidName(String newName);
|
||||
|
||||
void removeAlias();
|
||||
|
||||
void addUpdateNodes(List<JavaNode> toUpdate);
|
||||
|
||||
void reload(MainWindow mainWindow);
|
||||
}
|
||||
@@ -1,20 +1,14 @@
|
||||
package jadx.gui.treemodel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.ImageIcon;
|
||||
|
||||
import jadx.api.JavaPackage;
|
||||
import jadx.gui.JadxWrapper;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
import jadx.gui.utils.pkgs.PackageHelper;
|
||||
|
||||
public class JSources extends JNode {
|
||||
private static final long serialVersionUID = 8962924556824862801L;
|
||||
@@ -32,89 +26,15 @@ public class JSources extends JNode {
|
||||
|
||||
public final void update() {
|
||||
removeAllChildren();
|
||||
if (flatPackages) {
|
||||
for (JavaPackage pkg : wrapper.getPackages()) {
|
||||
add(new JPackage(pkg, wrapper));
|
||||
}
|
||||
} else {
|
||||
// build packages hierarchy
|
||||
List<JPackage> rootPkgs = getHierarchyPackages(wrapper.getPackages());
|
||||
for (JPackage jPackage : rootPkgs) {
|
||||
jPackage.update();
|
||||
add(jPackage);
|
||||
}
|
||||
PackageHelper packageHelper = wrapper.getCache().getPackageHelper();
|
||||
if (packageHelper == null) {
|
||||
packageHelper = new PackageHelper(wrapper);
|
||||
wrapper.getCache().setPackageHelper(packageHelper);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert packages list to hierarchical packages representation
|
||||
*
|
||||
* @param packages input packages list
|
||||
* @return root packages
|
||||
*/
|
||||
List<JPackage> getHierarchyPackages(List<JavaPackage> packages) {
|
||||
Map<String, JPackage> pkgMap = new HashMap<>();
|
||||
for (JavaPackage pkg : packages) {
|
||||
addPackage(pkgMap, new JPackage(pkg, wrapper));
|
||||
}
|
||||
// merge packages without classes
|
||||
boolean repeat;
|
||||
do {
|
||||
repeat = false;
|
||||
for (JPackage pkg : pkgMap.values()) {
|
||||
List<JPackage> innerPackages = pkg.getInnerPackages();
|
||||
if (innerPackages.size() == 1 && pkg.getClasses().isEmpty()) {
|
||||
JPackage innerPkg = innerPackages.get(0);
|
||||
pkg.setInnerPackages(innerPkg.getInnerPackages());
|
||||
pkg.setClasses(innerPkg.getClasses());
|
||||
String innerName = '.' + innerPkg.getName();
|
||||
pkg.updateBothNames(pkg.getFullName() + innerName, pkg.getName() + innerName, wrapper);
|
||||
|
||||
innerPkg.setInnerPackages(Collections.emptyList());
|
||||
innerPkg.setClasses(Collections.emptyList());
|
||||
repeat = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} while (repeat);
|
||||
|
||||
// remove empty packages
|
||||
pkgMap.values().removeIf(pkg -> pkg.getInnerPackages().isEmpty() && pkg.getClasses().isEmpty());
|
||||
|
||||
// use identity set for collect inner packages
|
||||
Set<JPackage> innerPackages = Collections.newSetFromMap(new IdentityHashMap<>());
|
||||
for (JPackage pkg : pkgMap.values()) {
|
||||
innerPackages.addAll(pkg.getInnerPackages());
|
||||
}
|
||||
// find root packages
|
||||
List<JPackage> rootPkgs = new ArrayList<>();
|
||||
for (JPackage pkg : pkgMap.values()) {
|
||||
if (!innerPackages.contains(pkg)) {
|
||||
rootPkgs.add(pkg);
|
||||
}
|
||||
}
|
||||
Collections.sort(rootPkgs);
|
||||
return rootPkgs;
|
||||
}
|
||||
|
||||
private void addPackage(Map<String, JPackage> pkgs, JPackage pkg) {
|
||||
String pkgName = pkg.getFullName();
|
||||
JPackage replaced = pkgs.put(pkgName, pkg);
|
||||
if (replaced != null) {
|
||||
pkg.getInnerPackages().addAll(replaced.getInnerPackages());
|
||||
pkg.getClasses().addAll(replaced.getClasses());
|
||||
}
|
||||
int dot = pkgName.lastIndexOf('.');
|
||||
if (dot > 0) {
|
||||
String prevPart = pkgName.substring(0, dot);
|
||||
String shortName = pkgName.substring(dot + 1);
|
||||
pkg.updateName(shortName);
|
||||
JPackage prevPkg = pkgs.get(prevPart);
|
||||
if (prevPkg == null) {
|
||||
prevPkg = new JPackage(prevPart, wrapper);
|
||||
addPackage(pkgs, prevPkg);
|
||||
}
|
||||
prevPkg.getInnerPackages().add(pkg);
|
||||
List<JPackage> roots = packageHelper.getRoots(flatPackages);
|
||||
for (JPackage rootPkg : roots) {
|
||||
rootPkg.update();
|
||||
add(rootPkg);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
package jadx.gui.treemodel;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.api.JavaVariable;
|
||||
import jadx.api.data.ICodeRename;
|
||||
import jadx.api.data.impl.JadxCodeRef;
|
||||
import jadx.api.data.impl.JadxCodeRename;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.UiUtils;
|
||||
|
||||
public class JVariable extends JNode {
|
||||
public class JVariable extends JNode implements JRenameNode {
|
||||
private static final long serialVersionUID = -3002100457834453783L;
|
||||
|
||||
private final JMethod jMth;
|
||||
@@ -77,4 +86,33 @@ public class JVariable extends JNode {
|
||||
public boolean canRename() {
|
||||
return var.getName() != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return makeLongStringHtml();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidName(String newName) {
|
||||
return NameMapper.isValidIdentifier(newName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICodeRename buildCodeRename(String newName, Set<ICodeRename> renames) {
|
||||
return new JadxCodeRename(JadxNodeRef.forMth(var.getMth()), JadxCodeRef.forVar(var), newName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAlias() {
|
||||
var.removeAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUpdateNodes(List<JavaNode> toUpdate) {
|
||||
toUpdate.add(var.getMth());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload(MainWindow mainWindow) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package jadx.gui.ui.codearea;
|
||||
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.treemodel.JRenameNode;
|
||||
import jadx.gui.ui.dialog.RenameDialog;
|
||||
import jadx.gui.utils.NLS;
|
||||
|
||||
@@ -17,11 +18,17 @@ public final class RenameAction extends JNodeAction {
|
||||
|
||||
@Override
|
||||
public boolean isActionEnabled(JNode node) {
|
||||
return node != null && node.canRename();
|
||||
if (node == null) {
|
||||
return false;
|
||||
}
|
||||
if (node instanceof JRenameNode) {
|
||||
return ((JRenameNode) node).canRename();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void runAction(JNode node) {
|
||||
RenameDialog.rename(getCodeArea().getMainWindow(), getCodeArea().getNode(), node);
|
||||
RenameDialog.rename(getCodeArea().getMainWindow(), getCodeArea().getNode(), (JRenameNode) node);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,29 +27,19 @@ import javax.swing.JTextField;
|
||||
import javax.swing.WindowConstants;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaMethod;
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.api.JavaVariable;
|
||||
import jadx.api.data.ICodeRename;
|
||||
import jadx.api.data.impl.JadxCodeData;
|
||||
import jadx.api.data.impl.JadxCodeRef;
|
||||
import jadx.api.data.impl.JadxCodeRename;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.core.utils.exceptions.JadxRuntimeException;
|
||||
import jadx.gui.jobs.TaskStatus;
|
||||
import jadx.gui.settings.JadxProject;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JField;
|
||||
import jadx.gui.treemodel.JMethod;
|
||||
import jadx.gui.treemodel.JNode;
|
||||
import jadx.gui.treemodel.JPackage;
|
||||
import jadx.gui.treemodel.JVariable;
|
||||
import jadx.gui.treemodel.JRenameNode;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.TabbedPane;
|
||||
import jadx.gui.ui.codearea.ClassCodeContentPanel;
|
||||
@@ -70,36 +60,32 @@ public class RenameDialog extends JDialog {
|
||||
|
||||
private final transient MainWindow mainWindow;
|
||||
private final transient CacheObject cache;
|
||||
private final transient JNode source;
|
||||
private final transient JNode node;
|
||||
private final transient @Nullable JNode source;
|
||||
private final transient JRenameNode node;
|
||||
private transient JTextField renameField;
|
||||
private transient JButton renameBtn;
|
||||
|
||||
public static boolean rename(MainWindow mainWindow, JNode node) {
|
||||
return rename(mainWindow, node, node);
|
||||
}
|
||||
|
||||
public static boolean rename(MainWindow mainWindow, JNode source, JNode node) {
|
||||
public static boolean rename(MainWindow mainWindow, JNode source, JRenameNode node) {
|
||||
RenameDialog renameDialog = new RenameDialog(mainWindow, source, node);
|
||||
UiUtils.uiRun(() -> renameDialog.setVisible(true));
|
||||
UiUtils.uiRun(renameDialog::initRenameField); // wait for UI events to propagate
|
||||
return true;
|
||||
}
|
||||
|
||||
public static JPopupMenu buildRenamePopup(MainWindow mainWindow, JNode node) {
|
||||
public static JPopupMenu buildRenamePopup(MainWindow mainWindow, JRenameNode node) {
|
||||
JMenuItem jmi = new JMenuItem(NLS.str("popup.rename"));
|
||||
jmi.addActionListener(action -> RenameDialog.rename(mainWindow, node));
|
||||
jmi.addActionListener(action -> RenameDialog.rename(mainWindow, null, node));
|
||||
JPopupMenu menu = new JPopupMenu();
|
||||
menu.add(jmi);
|
||||
return menu;
|
||||
}
|
||||
|
||||
private RenameDialog(MainWindow mainWindow, JNode source, JNode node) {
|
||||
private RenameDialog(MainWindow mainWindow, JNode source, JRenameNode node) {
|
||||
super(mainWindow);
|
||||
this.mainWindow = mainWindow;
|
||||
this.cache = mainWindow.getCacheObject();
|
||||
this.source = source;
|
||||
this.node = replaceNode(node);
|
||||
this.node = node.replace();
|
||||
initUI();
|
||||
}
|
||||
|
||||
@@ -108,27 +94,12 @@ public class RenameDialog extends JDialog {
|
||||
renameField.selectAll();
|
||||
}
|
||||
|
||||
private JNode replaceNode(JNode node) {
|
||||
if (node instanceof JMethod) {
|
||||
JavaMethod javaMethod = ((JMethod) node).getJavaMethod();
|
||||
if (javaMethod.isClassInit()) {
|
||||
throw new JadxRuntimeException("Can't rename class init method: " + node);
|
||||
}
|
||||
if (javaMethod.isConstructor()) {
|
||||
// rename class instead constructor
|
||||
return node.getJParent();
|
||||
}
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
private boolean checkNewName() {
|
||||
String newName = renameField.getText();
|
||||
private boolean checkNewName(String newName) {
|
||||
if (newName.isEmpty()) {
|
||||
// use empty name to reset rename (revert to original)
|
||||
return true;
|
||||
}
|
||||
boolean valid = NameMapper.isValidIdentifier(newName);
|
||||
boolean valid = node.isValidName(newName);
|
||||
if (renameBtn.isEnabled() != valid) {
|
||||
renameBtn.setEnabled(valid);
|
||||
renameField.putClientProperty("JComponent.outline", valid ? "" : "error");
|
||||
@@ -137,11 +108,12 @@ public class RenameDialog extends JDialog {
|
||||
}
|
||||
|
||||
private void rename() {
|
||||
if (!checkNewName()) {
|
||||
String newName = renameField.getText().trim();
|
||||
if (!checkNewName(newName)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
updateCodeRenames(set -> processRename(node, renameField.getText(), set));
|
||||
updateCodeRenames(set -> processRename(newName, set));
|
||||
refreshState();
|
||||
} catch (Exception e) {
|
||||
LOG.error("Rename failed", e);
|
||||
@@ -150,46 +122,15 @@ public class RenameDialog extends JDialog {
|
||||
dispose();
|
||||
}
|
||||
|
||||
private void processRename(JNode node, String newName, Set<ICodeRename> renames) {
|
||||
JadxCodeRename rename = buildRename(node, newName, renames);
|
||||
private void processRename(String newName, Set<ICodeRename> renames) {
|
||||
ICodeRename rename = node.buildCodeRename(newName, renames);
|
||||
renames.remove(rename);
|
||||
JavaNode javaNode = node.getJavaNode();
|
||||
if (javaNode != null) {
|
||||
javaNode.removeAlias();
|
||||
}
|
||||
node.removeAlias();
|
||||
if (!newName.isEmpty()) {
|
||||
renames.add(rename);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private JadxCodeRename buildRename(JNode node, String newName, Set<ICodeRename> renames) {
|
||||
if (node instanceof JMethod) {
|
||||
JavaMethod javaMethod = ((JMethod) node).getJavaMethod();
|
||||
List<JavaMethod> relatedMethods = javaMethod.getOverrideRelatedMethods();
|
||||
if (!relatedMethods.isEmpty()) {
|
||||
for (JavaMethod relatedMethod : relatedMethods) {
|
||||
renames.remove(new JadxCodeRename(JadxNodeRef.forMth(relatedMethod), ""));
|
||||
}
|
||||
}
|
||||
return new JadxCodeRename(JadxNodeRef.forMth(javaMethod), newName);
|
||||
}
|
||||
if (node instanceof JField) {
|
||||
return new JadxCodeRename(JadxNodeRef.forFld(((JField) node).getJavaField()), newName);
|
||||
}
|
||||
if (node instanceof JClass) {
|
||||
return new JadxCodeRename(JadxNodeRef.forCls(((JClass) node).getCls()), newName);
|
||||
}
|
||||
if (node instanceof JPackage) {
|
||||
return new JadxCodeRename(JadxNodeRef.forPkg(((JPackage) node).getFullName()), newName);
|
||||
}
|
||||
if (node instanceof JVariable) {
|
||||
JavaVariable javaVar = ((JVariable) node).getJavaVarNode();
|
||||
return new JadxCodeRename(JadxNodeRef.forMth(javaVar.getMth()), JadxCodeRef.forVar(javaVar), newName);
|
||||
}
|
||||
throw new JadxRuntimeException("Failed to build rename node for: " + node);
|
||||
}
|
||||
|
||||
private void updateCodeRenames(Consumer<Set<ICodeRename>> updater) {
|
||||
JadxProject project = mainWindow.getProject();
|
||||
JadxCodeData codeData = project.getCodeData();
|
||||
@@ -208,24 +149,13 @@ public class RenameDialog extends JDialog {
|
||||
private void refreshState() {
|
||||
mainWindow.getWrapper().reInitRenameVisitor();
|
||||
|
||||
JNodeCache nodeCache = cache.getNodeCache();
|
||||
JavaNode javaNode = node.getJavaNode();
|
||||
|
||||
List<JavaNode> toUpdate = new ArrayList<>();
|
||||
if (source != null && source != node) {
|
||||
toUpdate.add(source.getJavaNode());
|
||||
}
|
||||
if (javaNode != null) {
|
||||
toUpdate.add(javaNode);
|
||||
toUpdate.addAll(javaNode.getUseIn());
|
||||
if (node instanceof JMethod) {
|
||||
toUpdate.addAll(((JMethod) node).getJavaMethod().getOverrideRelatedMethods());
|
||||
}
|
||||
} else if (node instanceof JPackage) {
|
||||
processPackage(toUpdate);
|
||||
} else {
|
||||
throw new JadxRuntimeException("Unexpected node type: " + node);
|
||||
}
|
||||
node.addUpdateNodes(toUpdate);
|
||||
|
||||
JNodeCache nodeCache = cache.getNodeCache();
|
||||
Set<JClass> updatedTopClasses = toUpdate
|
||||
.stream()
|
||||
.map(JavaNode::getTopParentClass)
|
||||
@@ -245,28 +175,11 @@ public class RenameDialog extends JDialog {
|
||||
mainWindow.showHeapUsageBar();
|
||||
UiUtils.errorMessage(this, NLS.str("message.memoryLow"));
|
||||
}
|
||||
if (node instanceof JPackage) {
|
||||
mainWindow.getTreeRoot().update();
|
||||
}
|
||||
mainWindow.reloadTree();
|
||||
node.reload(mainWindow);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void processPackage(List<JavaNode> toUpdate) {
|
||||
String rawFullPkg = ((JPackage) node).getFullName();
|
||||
String rawFullPkgDot = rawFullPkg + ".";
|
||||
for (JavaClass cls : mainWindow.getWrapper().getClasses()) {
|
||||
String clsPkg = cls.getClassNode().getClassInfo().getPackage();
|
||||
// search all classes in package
|
||||
if (clsPkg.equals(rawFullPkg) || clsPkg.startsWith(rawFullPkgDot)) {
|
||||
toUpdate.add(cls);
|
||||
// also include all usages (for import fix)
|
||||
toUpdate.addAll(cls.getUseIn());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshClasses(Set<JClass> updatedTopClasses) {
|
||||
if (updatedTopClasses.size() < 10) {
|
||||
// small batch => reload
|
||||
@@ -323,11 +236,15 @@ public class RenameDialog extends JDialog {
|
||||
|
||||
private void initUI() {
|
||||
JLabel lbl = new JLabel(NLS.str("popup.rename"));
|
||||
JLabel nodeLabel = NodeLabel.longName(node);
|
||||
NodeLabel nodeLabel = new NodeLabel(node.getTitle());
|
||||
nodeLabel.setIcon(node.getIcon());
|
||||
if (node instanceof JNode) {
|
||||
nodeLabel.disableHtml(((JNode) node).disableHtml());
|
||||
}
|
||||
lbl.setLabelFor(nodeLabel);
|
||||
|
||||
renameField = new JTextField(40);
|
||||
renameField.getDocument().addDocumentListener(new DocumentUpdateListener(ev -> checkNewName()));
|
||||
renameField.getDocument().addDocumentListener(new DocumentUpdateListener(ev -> checkNewName(renameField.getText())));
|
||||
renameField.addActionListener(e -> rename());
|
||||
new TextStandardActions(renameField);
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package jadx.gui.ui.popupmenu;
|
||||
|
||||
import java.awt.event.ActionEvent;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import javax.swing.AbstractAction;
|
||||
@@ -10,17 +9,17 @@ import javax.swing.JMenu;
|
||||
import javax.swing.JMenuItem;
|
||||
import javax.swing.JPopupMenu;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.gui.JadxWrapper;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JPackage;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.ui.dialog.ExcludePkgDialog;
|
||||
import jadx.gui.ui.dialog.RenameDialog;
|
||||
import jadx.gui.utils.NLS;
|
||||
import jadx.gui.utils.pkgs.JRenamePackage;
|
||||
import jadx.gui.utils.pkgs.PackageHelper;
|
||||
|
||||
public class JPackagePopupMenu extends JPopupMenu {
|
||||
private static final long serialVersionUID = -7781009781149224131L;
|
||||
@@ -34,79 +33,24 @@ public class JPackagePopupMenu extends JPopupMenu {
|
||||
|
||||
add(makeExcludeItem(pkg));
|
||||
add(makeExcludeItem());
|
||||
JMenuItem menuItem = makeRenameMenuItem(pkg);
|
||||
if (menuItem != null) {
|
||||
add(menuItem);
|
||||
}
|
||||
add(makeRenameMenuItem(pkg));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private JMenuItem makeRenameMenuItem(JPackage pkg) {
|
||||
List<String> aliasShortParts = splitPackage(pkg.getName());
|
||||
int count = aliasShortParts.size();
|
||||
if (count == 0) {
|
||||
return null;
|
||||
}
|
||||
String rawPackage = getRawPackage(pkg);
|
||||
if (rawPackage == null) {
|
||||
return null;
|
||||
}
|
||||
List<String> aliasParts = splitPackage(pkg.getFullName());
|
||||
List<String> rawParts = splitPackage(rawPackage); // can be longer then alias parts
|
||||
int start = aliasParts.size() - count;
|
||||
if (count == 1) {
|
||||
// single case => no submenu
|
||||
JPackage renamePkg = new JPackage(concat(rawParts, start), aliasParts.get(start));
|
||||
JMenuItem pkgItem = new JMenuItem(NLS.str("popup.rename"));
|
||||
pkgItem.addActionListener(e -> rename(renamePkg));
|
||||
return pkgItem;
|
||||
}
|
||||
JMenuItem renameSubMenu = new JMenu(NLS.str("popup.rename"));
|
||||
for (int i = start; i < aliasParts.size(); i++) {
|
||||
String aliasShortPkg = aliasParts.get(i);
|
||||
JPackage pkgPart = new JPackage(concat(rawParts, i), aliasShortPkg);
|
||||
JMenuItem pkgPartItem = new JMenuItem(aliasShortPkg);
|
||||
pkgPartItem.addActionListener(e -> rename(pkgPart));
|
||||
PackageHelper packageHelper = mainWindow.getCacheObject().getPackageHelper();
|
||||
List<JRenamePackage> nodes = packageHelper.getRenameNodes(pkg);
|
||||
for (JRenamePackage node : nodes) {
|
||||
JMenuItem pkgPartItem = new JMenuItem(node.getTitle(), node.getIcon());
|
||||
pkgPartItem.addActionListener(e -> rename(node));
|
||||
renameSubMenu.add(pkgPartItem);
|
||||
}
|
||||
return renameSubMenu;
|
||||
}
|
||||
|
||||
private String concat(List<String> parts, int n) {
|
||||
if (n == 0) {
|
||||
return parts.get(0);
|
||||
}
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(parts.get(0));
|
||||
int count = parts.size();
|
||||
for (int i = 1; i < count && i <= n; i++) {
|
||||
sb.append('.');
|
||||
sb.append(parts.get(i));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private void rename(JPackage pkg) {
|
||||
LOG.debug("Renaming package: fullName={}, name={}", pkg.getFullName(), pkg.getName());
|
||||
RenameDialog.rename(mainWindow, pkg);
|
||||
}
|
||||
|
||||
private List<String> splitPackage(String rawPackage) {
|
||||
return Arrays.asList(rawPackage.split("\\."));
|
||||
}
|
||||
|
||||
private String getRawPackage(JPackage pkg) {
|
||||
List<JClass> classes = pkg.getClasses();
|
||||
if (!classes.isEmpty()) {
|
||||
return classes.get(0).getRootClass().getCls().getClassNode().getClassInfo().getPackage();
|
||||
}
|
||||
for (JPackage innerPkg : pkg.getInnerPackages()) {
|
||||
String rawPackage = getRawPackage(innerPkg);
|
||||
if (rawPackage != null) {
|
||||
return rawPackage;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
private void rename(JRenamePackage pkg) {
|
||||
LOG.debug("Renaming package: {}", pkg);
|
||||
RenameDialog.rename(mainWindow, null, pkg);
|
||||
}
|
||||
|
||||
private JMenuItem makeExcludeItem(JPackage pkg) {
|
||||
@@ -114,7 +58,7 @@ public class JPackagePopupMenu extends JPopupMenu {
|
||||
excludeItem.setSelected(!pkg.isEnabled());
|
||||
excludeItem.addItemListener(e -> {
|
||||
JadxWrapper wrapper = mainWindow.getWrapper();
|
||||
String fullName = pkg.getFullName();
|
||||
String fullName = pkg.getPkg().getFullName();
|
||||
if (excludeItem.isSelected()) {
|
||||
wrapper.addExcludedPackage(fullName);
|
||||
} else {
|
||||
|
||||
@@ -9,6 +9,7 @@ import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.gui.ui.dialog.SearchDialog;
|
||||
import jadx.gui.utils.pkgs.PackageHelper;
|
||||
|
||||
public class CacheObject {
|
||||
|
||||
@@ -17,6 +18,7 @@ public class CacheObject {
|
||||
private Map<SearchDialog.SearchPreset, Set<SearchDialog.SearchOptions>> lastSearchOptions;
|
||||
|
||||
private List<List<JavaClass>> decompileBatches;
|
||||
private PackageHelper packageHelper;
|
||||
|
||||
private volatile boolean fullDecompilationFinished;
|
||||
|
||||
@@ -29,6 +31,7 @@ public class CacheObject {
|
||||
jNodeCache = new JNodeCache();
|
||||
lastSearchOptions = new HashMap<>();
|
||||
decompileBatches = null;
|
||||
packageHelper = null;
|
||||
fullDecompilationFinished = false;
|
||||
}
|
||||
|
||||
@@ -57,6 +60,14 @@ public class CacheObject {
|
||||
this.decompileBatches = decompileBatches;
|
||||
}
|
||||
|
||||
public PackageHelper getPackageHelper() {
|
||||
return packageHelper;
|
||||
}
|
||||
|
||||
public void setPackageHelper(PackageHelper packageHelper) {
|
||||
this.packageHelper = packageHelper;
|
||||
}
|
||||
|
||||
public boolean isFullDecompilationFinished() {
|
||||
return fullDecompilationFinished;
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ public class JNodeCache {
|
||||
if (javaNode == null) {
|
||||
return null;
|
||||
}
|
||||
// don't use 'computeIfAbsent' method here, it this cause 'Recursive update' exception
|
||||
// don't use 'computeIfAbsent' method here, it will cause 'Recursive update' exception
|
||||
JNode jNode = cache.get(javaNode);
|
||||
if (jNode == null || jNode.getJavaNode() != javaNode) {
|
||||
jNode = convert(javaNode);
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
package jadx.gui.utils.pkgs;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.swing.Icon;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import jadx.api.JavaNode;
|
||||
import jadx.api.JavaPackage;
|
||||
import jadx.api.data.ICodeRename;
|
||||
import jadx.api.data.impl.JadxCodeRename;
|
||||
import jadx.api.data.impl.JadxNodeRef;
|
||||
import jadx.core.deobf.NameMapper;
|
||||
import jadx.gui.treemodel.JRenameNode;
|
||||
import jadx.gui.ui.MainWindow;
|
||||
import jadx.gui.utils.Icons;
|
||||
|
||||
import static jadx.core.deobf.NameMapper.VALID_JAVA_IDENTIFIER;
|
||||
|
||||
public class JRenamePackage implements JRenameNode {
|
||||
|
||||
private final JavaPackage refPkg;
|
||||
private final String rawFullName;
|
||||
private final String fullName;
|
||||
private final String name;
|
||||
|
||||
public JRenamePackage(JavaPackage refPkg, String rawFullName, String fullName, String name) {
|
||||
this.refPkg = refPkg;
|
||||
this.rawFullName = rawFullName;
|
||||
this.fullName = fullName;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTitle() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Icon getIcon() {
|
||||
return Icons.PACKAGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRename() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICodeRename buildCodeRename(String newName, Set<ICodeRename> renames) {
|
||||
return new JadxCodeRename(JadxNodeRef.forPkg(rawFullName), newName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValidName(String newName) {
|
||||
return isValidPackageName(newName);
|
||||
}
|
||||
|
||||
private static final Pattern PACKAGE_RENAME_PATTERN = Pattern.compile(
|
||||
"PKG(\\.PKG)*(\\.)?".replace("PKG", VALID_JAVA_IDENTIFIER.pattern()));
|
||||
|
||||
static boolean isValidPackageName(String newName) {
|
||||
if (newName == null || newName.isEmpty() || NameMapper.isReserved(newName)) {
|
||||
return false;
|
||||
}
|
||||
Matcher matcher = PACKAGE_RENAME_PATTERN.matcher(newName);
|
||||
if (!matcher.matches()) {
|
||||
return false;
|
||||
}
|
||||
for (String part : StringUtils.split(newName, '.')) {
|
||||
if (NameMapper.isReserved(part)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeAlias() {
|
||||
refPkg.removeAlias();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addUpdateNodes(List<JavaNode> toUpdate) {
|
||||
refPkg.addUseIn(toUpdate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void reload(MainWindow mainWindow) {
|
||||
mainWindow.getCacheObject().setPackageHelper(null);
|
||||
mainWindow.getTreeRoot().update();
|
||||
mainWindow.reloadTree();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package jadx.gui.utils.pkgs;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import jadx.api.JavaPackage;
|
||||
import jadx.core.dex.info.PackageInfo;
|
||||
import jadx.core.utils.ListUtils;
|
||||
import jadx.core.utils.Utils;
|
||||
import jadx.gui.JadxWrapper;
|
||||
import jadx.gui.treemodel.JClass;
|
||||
import jadx.gui.treemodel.JPackage;
|
||||
import jadx.gui.utils.JNodeCache;
|
||||
|
||||
public class PackageHelper {
|
||||
private static final Logger LOG = LoggerFactory.getLogger(PackageHelper.class);
|
||||
|
||||
private static final Comparator<JClass> CLASS_COMPARATOR = Comparator.comparing(JClass::getName, String.CASE_INSENSITIVE_ORDER);
|
||||
private static final Comparator<JPackage> PKG_COMPARATOR = Comparator.comparing(JPackage::getName, String.CASE_INSENSITIVE_ORDER);
|
||||
|
||||
private final JadxWrapper wrapper;
|
||||
private List<String> excludedPackages;
|
||||
private JNodeCache nodeCache;
|
||||
|
||||
private final Map<PackageInfo, JPackage> pkgInfoMap = new HashMap<>();
|
||||
|
||||
public PackageHelper(JadxWrapper wrapper) {
|
||||
this.wrapper = wrapper;
|
||||
}
|
||||
|
||||
public List<JPackage> getRoots(boolean flatPackages) {
|
||||
excludedPackages = wrapper.getExcludedPackages();
|
||||
nodeCache = wrapper.getCache().getNodeCache();
|
||||
pkgInfoMap.clear();
|
||||
if (flatPackages) {
|
||||
return prepareFlatPackages();
|
||||
}
|
||||
long start = System.currentTimeMillis();
|
||||
List<JPackage> roots = prepareHierarchyPackages();
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("Prepare hierarchy packages in {} ms", System.currentTimeMillis() - start);
|
||||
}
|
||||
return roots;
|
||||
}
|
||||
|
||||
public List<JRenamePackage> getRenameNodes(JPackage pkg) {
|
||||
List<JRenamePackage> list = new ArrayList<>();
|
||||
PackageInfo pkgInfo = pkg.getPkg().getPkgNode().getAliasPkgInfo();
|
||||
Set<String> added = new HashSet<>();
|
||||
do {
|
||||
JPackage jPkg = pkgInfoMap.get(pkgInfo);
|
||||
if (jPkg != null) {
|
||||
JavaPackage javaPkg = jPkg.getPkg();
|
||||
String fullName = javaPkg.isDefault() ? JPackage.PACKAGE_DEFAULT_HTML_STR : javaPkg.getFullName();
|
||||
String name = jPkg.isSynthetic() || javaPkg.isParentRenamed() ? fullName : javaPkg.getName();
|
||||
JRenamePackage renamePkg = new JRenamePackage(javaPkg, javaPkg.getRawFullName(), fullName, name);
|
||||
if (added.add(fullName)) {
|
||||
list.add(renamePkg);
|
||||
}
|
||||
}
|
||||
pkgInfo = pkgInfo.getParentPkg();
|
||||
} while (pkgInfo != null);
|
||||
return list;
|
||||
}
|
||||
|
||||
private List<JPackage> prepareFlatPackages() {
|
||||
List<JPackage> list = new ArrayList<>();
|
||||
for (JavaPackage javaPkg : wrapper.getPackages()) {
|
||||
if (javaPkg.isLeaf()) {
|
||||
JPackage pkg = buildJPackage(javaPkg, false);
|
||||
pkg.setName(javaPkg.getFullName());
|
||||
list.add(pkg);
|
||||
pkgInfoMap.put(javaPkg.getPkgNode().getAliasPkgInfo(), pkg);
|
||||
}
|
||||
}
|
||||
list.sort(PKG_COMPARATOR);
|
||||
return list;
|
||||
}
|
||||
|
||||
private List<JPackage> prepareHierarchyPackages() {
|
||||
JPackage root = new JPackage(null, true, Collections.emptyList(), new ArrayList<>(), true);
|
||||
List<JavaPackage> packages = wrapper.getPackages();
|
||||
List<JPackage> jPackages = new ArrayList<>(packages.size());
|
||||
// create nodes for exists packages
|
||||
for (JavaPackage javaPkg : packages) {
|
||||
JPackage jPkg = buildJPackage(javaPkg, false);
|
||||
jPackages.add(jPkg);
|
||||
PackageInfo aliasPkgInfo = javaPkg.getPkgNode().getAliasPkgInfo();
|
||||
jPkg.setName(aliasPkgInfo.getName());
|
||||
pkgInfoMap.put(aliasPkgInfo, jPkg);
|
||||
if (aliasPkgInfo.isRoot()) {
|
||||
root.getSubPackages().add(jPkg);
|
||||
}
|
||||
}
|
||||
// link subpackages, create missing packages created by renames
|
||||
for (JPackage jPkg : jPackages) {
|
||||
if (jPkg.getPkg().isLeaf()) {
|
||||
buildLeafPath(jPkg, root, pkgInfoMap);
|
||||
}
|
||||
}
|
||||
List<JPackage> toMerge = new ArrayList<>();
|
||||
traverseMiddlePackages(root, toMerge);
|
||||
Utils.treeDfsVisit(root, JPackage::getSubPackages, v -> v.getSubPackages().sort(PKG_COMPARATOR));
|
||||
return root.getSubPackages();
|
||||
}
|
||||
|
||||
private void buildLeafPath(JPackage jPkg, JPackage root, Map<PackageInfo, JPackage> pkgMap) {
|
||||
JPackage currentJPkg = jPkg;
|
||||
PackageInfo current = jPkg.getPkg().getPkgNode().getAliasPkgInfo();
|
||||
while (true) {
|
||||
current = current.getParentPkg();
|
||||
if (current == null) {
|
||||
break;
|
||||
}
|
||||
JPackage parentJPkg = pkgMap.get(current);
|
||||
if (parentJPkg == null) {
|
||||
parentJPkg = buildJPackage(currentJPkg.getPkg(), true);
|
||||
parentJPkg.setName(current.getName());
|
||||
pkgMap.put(current, parentJPkg);
|
||||
if (current.isRoot()) {
|
||||
root.getSubPackages().add(parentJPkg);
|
||||
}
|
||||
}
|
||||
List<JPackage> subPackages = parentJPkg.getSubPackages();
|
||||
String pkgName = currentJPkg.getName();
|
||||
if (ListUtils.noneMatch(subPackages, p -> p.getName().equals(pkgName))) {
|
||||
subPackages.add(currentJPkg);
|
||||
}
|
||||
currentJPkg = parentJPkg;
|
||||
}
|
||||
}
|
||||
|
||||
private static void traverseMiddlePackages(JPackage pkg, List<JPackage> toMerge) {
|
||||
List<JPackage> subPackages = pkg.getSubPackages();
|
||||
int count = subPackages.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
JPackage subPackage = subPackages.get(i);
|
||||
JPackage replacePkg = mergeMiddlePackages(subPackage, toMerge);
|
||||
if (replacePkg != subPackage) {
|
||||
subPackages.set(i, replacePkg);
|
||||
}
|
||||
traverseMiddlePackages(replacePkg, toMerge);
|
||||
}
|
||||
}
|
||||
|
||||
private static JPackage mergeMiddlePackages(JPackage jPkg, List<JPackage> merged) {
|
||||
List<JPackage> subPackages = jPkg.getSubPackages();
|
||||
if (subPackages.size() == 1 && jPkg.getClasses().isEmpty()) {
|
||||
merged.add(jPkg);
|
||||
JPackage endPkg = mergeMiddlePackages(subPackages.get(0), merged);
|
||||
merged.clear();
|
||||
return endPkg;
|
||||
}
|
||||
if (!merged.isEmpty()) {
|
||||
merged.add(jPkg);
|
||||
jPkg.setName(Utils.listToString(merged, ".", JPackage::getName));
|
||||
}
|
||||
return jPkg;
|
||||
}
|
||||
|
||||
private JPackage buildJPackage(JavaPackage javaPkg, boolean synthetic) {
|
||||
boolean pkgEnabled = isPkgEnabled(javaPkg.getRawFullName(), excludedPackages);
|
||||
List<JClass> classes;
|
||||
if (synthetic) {
|
||||
classes = Collections.emptyList();
|
||||
} else {
|
||||
classes = Utils.collectionMap(javaPkg.getClasses(), nodeCache::makeFrom);
|
||||
classes.sort(CLASS_COMPARATOR);
|
||||
}
|
||||
return new JPackage(javaPkg, pkgEnabled, classes, new ArrayList<>(), synthetic);
|
||||
}
|
||||
|
||||
private static boolean isPkgEnabled(String fullPkgName, List<String> excludedPackages) {
|
||||
return excludedPackages.isEmpty()
|
||||
|| excludedPackages.stream()
|
||||
.noneMatch(p -> fullPkgName.equals(p) || fullPkgName.startsWith(p + '.'));
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package jadx.api;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
|
||||
public class Factory {
|
||||
|
||||
public static JavaPackage newPackage(String name, List<JavaClass> classes) {
|
||||
return new JavaPackage(name, classes);
|
||||
}
|
||||
|
||||
public static JavaClass newClass(JadxDecompiler decompiler, ClassNode classNode) {
|
||||
return new JavaClass(classNode, decompiler);
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
package jadx.gui.treemodel;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import jadx.api.Factory;
|
||||
import jadx.api.JadxArgs;
|
||||
import jadx.api.JadxDecompiler;
|
||||
import jadx.api.JavaClass;
|
||||
import jadx.api.JavaPackage;
|
||||
import jadx.core.dex.nodes.ClassNode;
|
||||
import jadx.gui.JadxWrapper;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.hasSize;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class JSourcesTest {
|
||||
|
||||
private JSources sources;
|
||||
private JadxDecompiler decompiler;
|
||||
|
||||
@BeforeEach
|
||||
public void init() {
|
||||
JRoot root = mock(JRoot.class);
|
||||
when(root.isFlatPackages()).thenReturn(false);
|
||||
JadxWrapper wrapper = mock(JadxWrapper.class);
|
||||
sources = new JSources(root, wrapper);
|
||||
decompiler = new JadxDecompiler(new JadxArgs());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHierarchyPackages() {
|
||||
String pkgName = "a.b.c.d.e";
|
||||
|
||||
List<JavaPackage> packages = Collections.singletonList(newPkg(pkgName));
|
||||
List<JPackage> out = sources.getHierarchyPackages(packages);
|
||||
|
||||
assertThat(out, hasSize(1));
|
||||
JPackage jPkg = out.get(0);
|
||||
assertThat(jPkg.getName(), is(pkgName));
|
||||
assertThat(jPkg.getClasses(), hasSize(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHierarchyPackages2() {
|
||||
List<JavaPackage> packages = asList(
|
||||
newPkg("a.b"),
|
||||
newPkg("a.c"),
|
||||
newPkg("a.d"));
|
||||
List<JPackage> out = sources.getHierarchyPackages(packages);
|
||||
|
||||
assertThat(out, hasSize(1));
|
||||
JPackage jPkg = out.get(0);
|
||||
assertThat(jPkg.getName(), is("a"));
|
||||
assertThat(jPkg.getClasses(), hasSize(0));
|
||||
assertThat(jPkg.getInnerPackages(), hasSize(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHierarchyPackages3() {
|
||||
List<JavaPackage> packages = asList(
|
||||
newPkg("a.b.p1"),
|
||||
newPkg("a.b.p2"),
|
||||
newPkg("a.b.p3"));
|
||||
List<JPackage> out = sources.getHierarchyPackages(packages);
|
||||
|
||||
assertThat(out, hasSize(1));
|
||||
JPackage jPkg = out.get(0);
|
||||
assertThat(jPkg.getName(), is("a.b"));
|
||||
assertThat(jPkg.getClasses(), hasSize(0));
|
||||
assertThat(jPkg.getInnerPackages(), hasSize(3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHierarchyPackages4() {
|
||||
List<JavaPackage> packages = asList(
|
||||
newPkg("a.p1"),
|
||||
newPkg("a.b.c.p2"),
|
||||
newPkg("a.b.c.p3"),
|
||||
newPkg("d.e"),
|
||||
newPkg("d.f.a"));
|
||||
List<JPackage> out = sources.getHierarchyPackages(packages);
|
||||
|
||||
assertThat(out, hasSize(2));
|
||||
assertThat(out.get(0).getName(), is("a"));
|
||||
assertThat(out.get(0).getInnerPackages(), hasSize(2));
|
||||
assertThat(out.get(1).getName(), is("d"));
|
||||
assertThat(out.get(1).getInnerPackages(), hasSize(2));
|
||||
}
|
||||
|
||||
private JavaPackage newPkg(String name) {
|
||||
return Factory.newPackage(name, Collections.singletonList(newClass()));
|
||||
}
|
||||
|
||||
private JavaClass newClass() {
|
||||
return Factory.newClass(decompiler, mock(ClassNode.class));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package jadx.gui.utils.pkgs;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class TestJRenamePackage {
|
||||
|
||||
@Test
|
||||
void isValidName() {
|
||||
valid("foo");
|
||||
valid("foo.bar");
|
||||
valid("foo.bar.");
|
||||
|
||||
invalid("");
|
||||
invalid("0foo");
|
||||
invalid(".foo");
|
||||
invalid("do");
|
||||
invalid("foo.if");
|
||||
invalid("foo.if.bar");
|
||||
}
|
||||
|
||||
private void valid(String name) {
|
||||
assertThat(JRenamePackage.isValidPackageName(name))
|
||||
.as("expect valid: %s", name)
|
||||
.isEqualTo(true);
|
||||
}
|
||||
|
||||
private void invalid(String name) {
|
||||
assertThat(JRenamePackage.isValidPackageName(name))
|
||||
.as("expect invalid: %s", name)
|
||||
.isEqualTo(false);
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ dependencies {
|
||||
|
||||
implementation("io.github.microutils:kotlin-logging-jvm:3.0.2")
|
||||
|
||||
// manual imports ( IDE can't import dependencies by scripts annotations)
|
||||
// manual imports (IDE can't import dependencies by scripts annotations)
|
||||
implementation("com.github.javafaker:javafaker:1.0.2")
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user