未验证 提交 e07bc7f6 编写于 作者: S Skylot

refactor: use package nodes in api and ui

上级 5894fadc
......@@ -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;
......@@ -46,6 +45,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;
......@@ -102,6 +102,7 @@ public final class JadxDecompiler implements IJadxDecompiler, Closeable {
private final Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
private final Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
private final Map<PackageNode, JavaPackage> pkgsMap = new ConcurrentHashMap<>();
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler();
......@@ -441,25 +442,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() {
......@@ -539,6 +522,26 @@ public final class JadxDecompiler implements IJadxDecompiler, Closeable {
});
}
@ApiStatus.Internal
JavaPackage convertPackageNode(PackageNode pkg) {
JavaPackage foundPkg = pkgsMap.get(pkg);
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);
pkgsMap.put(pkg, javaPkg);
if (subPkgsCount != 0) {
// add subpackages after parent to avoid endless recursion
for (PackageNode subPackage : pkg.getSubPackages()) {
subPkgs.add(convertPackageNode(subPackage));
}
}
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);
}
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(
......
......@@ -144,6 +144,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;
}
......
......@@ -295,6 +295,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.
......
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();
......
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;
}
......
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;
private String name;
private boolean enabled;
private List<JClass> classes;
private List<JPackage> innerPackages;
public static final String PACKAGE_DEFAULT_HTML_STR = wrapHtml(fadeHtml(escapeHtml("<empty>")));
public JPackage(JavaPackage pkg, JadxWrapper wrapper) {
this(pkg.getName(), pkg.getName(),
isPkgEnabled(wrapper, pkg.getName()),
Utils.collectionMap(pkg.getClasses(), JClass::new),
new ArrayList<>());
update();
}
private final JavaPackage pkg;
private final boolean enabled;
private final List<JClass> classes;
private final List<JPackage> subPackages;
public JPackage(String fullName, JadxWrapper wrapper) {
this(fullName, fullName, isPkgEnabled(wrapper, fullName), new ArrayList<>(), new ArrayList<>());
}
/**
* 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;
public JPackage(String fullName, String name) {
this(fullName, name, true, Collections.emptyList(), Collections.emptyList());
}
private String name;
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;
}
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 + '.'));
this.subPackages = subPackages;
this.synthetic = synthetic;
}
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);
}
@Override
public String getName() {
return name;
public JavaPackage getPkg() {
return pkg;
}
@Override
public boolean canRename() {
return true;
public JavaNode getJavaNode() {
return pkg;
}
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);
@Override
public String getName() {
return name;
}
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;
}
}
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);
}
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);
}
}
......
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;
......@@ -72,4 +81,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) {
}
}
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);
}
}
......@@ -28,29 +28,19 @@ import javax.swing.SwingConstants;
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,15 @@ public class RenameDialog extends JDialog {
mainWindow.showHeapUsageBar();
UiUtils.errorMessage(this, NLS.str("message.memoryLow"));
}
if (node instanceof JPackage) {
mainWindow.getTreeRoot().update();
}
mainWindow.reloadTree();
// 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 +240,11 @@ public class RenameDialog extends JDialog {
private void initUI() {
JLabel lbl = new JLabel(NLS.str("popup.rename"));
JLabel nodeLabel = new JLabel(this.node.makeLongStringHtml(), this.node.getIcon(), SwingConstants.LEFT);
JLabel nodeLabel = new JLabel(node.getTitle(), node.getIcon(), SwingConstants.LEFT);
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);
......
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 {
......
......@@ -11,6 +11,7 @@ import jadx.api.JavaClass;
import jadx.gui.settings.JadxSettings;
import jadx.gui.treemodel.JRoot;
import jadx.gui.ui.dialog.SearchDialog;
import jadx.gui.utils.pkgs.PackageHelper;
public class CacheObject {
......@@ -22,6 +23,7 @@ public class CacheObject {
private JadxSettings settings;
private List<List<JavaClass>> decompileBatches;
private PackageHelper packageHelper;
public CacheObject() {
reset();
......@@ -34,6 +36,7 @@ public class CacheObject {
jNodeCache = new JNodeCache();
lastSearchOptions = new HashMap<>();
decompileBatches = null;
packageHelper = null;
}
@Nullable
......@@ -76,4 +79,12 @@ public class CacheObject {
public void setDecompileBatches(List<List<JavaClass>> decompileBatches) {
this.decompileBatches = decompileBatches;
}
public PackageHelper getPackageHelper() {
return packageHelper;
}
public void setPackageHelper(PackageHelper packageHelper) {
this.packageHelper = packageHelper;
}
}
......@@ -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);
......
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();
}
}
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 + '.'));
}
}
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);
}
}
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));
}
}
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);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册