Projects STRLCPY jadx Commits a545065f
🤬
  • refactor: use package nodes in api and ui

  • Loading...
  • Skylot committed 1 year ago
    a545065f
    1 parent 812ba6e8
  • ■ ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/api/JadxDecompiler.java
    skipped 5 lines
    6 6  import java.util.ArrayList;
    7 7  import java.util.Collection;
    8 8  import java.util.Collections;
    9  -import java.util.Comparator;
    10 9  import java.util.HashMap;
    11 10  import java.util.List;
    12 11  import java.util.Map;
    skipped 33 lines
    46 45  import jadx.core.dex.nodes.ClassNode;
    47 46  import jadx.core.dex.nodes.FieldNode;
    48 47  import jadx.core.dex.nodes.MethodNode;
     48 +import jadx.core.dex.nodes.PackageNode;
    49 49  import jadx.core.dex.nodes.RootNode;
    50 50  import jadx.core.dex.visitors.SaveCode;
    51 51  import jadx.core.export.ExportGradleProject;
    skipped 50 lines
    102 102   private final Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
    103 103   private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
    104 104   private final Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
     105 + private final Map<PackageNode, JavaPackage> pkgsMap = new ConcurrentHashMap<>();
    105 106   
    106 107   private final IDecompileScheduler decompileScheduler = new DecompilerScheduler();
    107 108   
    skipped 333 lines
    441 442   }
    442 443   
    443 444   public List<JavaPackage> getPackages() {
    444  - List<JavaClass> classList = getClasses();
    445  - if (classList.isEmpty()) {
    446  - return Collections.emptyList();
    447  - }
    448  - Map<String, List<JavaClass>> map = new HashMap<>();
    449  - for (JavaClass javaClass : classList) {
    450  - String pkg = javaClass.getPackage();
    451  - List<JavaClass> clsList = map.computeIfAbsent(pkg, k -> new ArrayList<>());
    452  - clsList.add(javaClass);
    453  - }
    454  - List<JavaPackage> packages = new ArrayList<>(map.size());
    455  - for (Map.Entry<String, List<JavaClass>> entry : map.entrySet()) {
    456  - packages.add(new JavaPackage(entry.getKey(), entry.getValue()));
    457  - }
    458  - Collections.sort(packages);
    459  - for (JavaPackage pkg : packages) {
    460  - pkg.getClasses().sort(Comparator.comparing(JavaClass::getName, String.CASE_INSENSITIVE_ORDER));
    461  - }
    462  - return Collections.unmodifiableList(packages);
     445 + return Utils.collectionMap(root.getPackages(), this::convertPackageNode);
    463 446   }
    464 447   
    465 448   public int getErrorsCount() {
    skipped 71 lines
    537 520   ClassNode parentCls = mthNode.getParentClass();
    538 521   return new JavaMethod(convertClassNode(parentCls), mthNode);
    539 522   });
     523 + }
     524 + 
     525 + @ApiStatus.Internal
     526 + JavaPackage convertPackageNode(PackageNode pkg) {
     527 + JavaPackage foundPkg = pkgsMap.get(pkg);
     528 + if (foundPkg != null) {
     529 + return foundPkg;
     530 + }
     531 + List<JavaClass> clsList = Utils.collectionMap(pkg.getClasses(), this::convertClassNode);
     532 + int subPkgsCount = pkg.getSubPackages().size();
     533 + List<JavaPackage> subPkgs = subPkgsCount == 0 ? Collections.emptyList() : new ArrayList<>(subPkgsCount);
     534 + JavaPackage javaPkg = new JavaPackage(pkg, clsList, subPkgs);
     535 + pkgsMap.put(pkg, javaPkg);
     536 + if (subPkgsCount != 0) {
     537 + // add subpackages after parent to avoid endless recursion
     538 + for (PackageNode subPackage : pkg.getSubPackages()) {
     539 + subPkgs.add(convertPackageNode(subPackage));
     540 + }
     541 + }
     542 + return javaPkg;
    540 543   }
    541 544   
    542 545   @Nullable
    skipped 164 lines
  • ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/api/JavaNode.java
    skipped 17 lines
    18 18   
    19 19   List<JavaNode> getUseIn();
    20 20   
    21  - default void removeAlias() {
    22  - }
     21 + void removeAlias();
    23 22   
    24 23   boolean isOwnCodeAnnotation(ICodeAnnotation ann);
    25 24  }
    skipped 1 lines
  • ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/api/JavaPackage.java
    1 1  package jadx.api;
    2 2   
    3  -import java.util.Collections;
     3 +import java.util.ArrayList;
    4 4  import java.util.List;
     5 +import java.util.Objects;
    5 6   
     7 +import org.jetbrains.annotations.ApiStatus.Internal;
    6 8  import org.jetbrains.annotations.NotNull;
    7 9   
    8 10  import jadx.api.metadata.ICodeAnnotation;
     11 +import jadx.core.dex.info.PackageInfo;
     12 +import jadx.core.dex.nodes.PackageNode;
    9 13   
    10 14  public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
    11  - private final String name;
     15 + private final PackageNode pkgNode;
    12 16   private final List<JavaClass> classes;
     17 + private final List<JavaPackage> subPkgs;
    13 18   
    14  - JavaPackage(String name, List<JavaClass> classes) {
    15  - this.name = name;
     19 + JavaPackage(PackageNode pkgNode, List<JavaClass> classes, List<JavaPackage> subPkgs) {
     20 + this.pkgNode = pkgNode;
    16 21   this.classes = classes;
     22 + this.subPkgs = subPkgs;
    17 23   }
    18 24   
    19 25   @Override
    20 26   public String getName() {
    21  - return name;
     27 + return pkgNode.getAliasPkgInfo().getName();
    22 28   }
    23 29   
    24 30   @Override
    25 31   public String getFullName() {
    26  - // TODO: store full package name
    27  - return name;
     32 + return pkgNode.getAliasPkgInfo().getFullName();
     33 + }
     34 + 
     35 + public String getRawName() {
     36 + return pkgNode.getPkgInfo().getName();
     37 + }
     38 + 
     39 + public String getRawFullName() {
     40 + return pkgNode.getPkgInfo().getFullName();
     41 + }
     42 + 
     43 + public List<JavaPackage> getSubPackages() {
     44 + return subPkgs;
    28 45   }
    29 46   
    30 47   public List<JavaClass> getClasses() {
    31 48   return classes;
    32 49   }
    33 50   
     51 + public boolean isRoot() {
     52 + return pkgNode.isRoot();
     53 + }
     54 + 
     55 + public boolean isLeaf() {
     56 + return pkgNode.isLeaf();
     57 + }
     58 + 
     59 + public boolean isDefault() {
     60 + return getFullName().isEmpty();
     61 + }
     62 + 
     63 + public void rename(String alias) {
     64 + pkgNode.rename(alias);
     65 + }
     66 + 
     67 + @Override
     68 + public void removeAlias() {
     69 + pkgNode.removeAlias();
     70 + }
     71 + 
     72 + public boolean isParentRenamed() {
     73 + PackageInfo parent = pkgNode.getPkgInfo().getParentPkg();
     74 + PackageInfo aliasParent = pkgNode.getAliasPkgInfo().getParentPkg();
     75 + return !Objects.equals(parent, aliasParent);
     76 + }
     77 + 
     78 + @Internal
     79 + public PackageNode getPkgNode() {
     80 + return pkgNode;
     81 + }
     82 + 
    34 83   @Override
    35 84   public JavaClass getDeclaringClass() {
    36 85   return null;
    skipped 11 lines
    48 97   
    49 98   @Override
    50 99   public List<JavaNode> getUseIn() {
    51  - return Collections.emptyList();
     100 + List<JavaNode> list = new ArrayList<>();
     101 + addUseIn(list);
     102 + return list;
     103 + }
     104 + 
     105 + public void addUseIn(List<JavaNode> list) {
     106 + list.addAll(classes);
     107 + for (JavaPackage subPkg : subPkgs) {
     108 + subPkg.addUseIn(list);
     109 + }
    52 110   }
    53 111   
    54 112   @Override
    skipped 3 lines
    58 116   
    59 117   @Override
    60 118   public int compareTo(@NotNull JavaPackage o) {
    61  - return name.compareTo(o.name);
     119 + return pkgNode.compareTo(o.pkgNode);
    62 120   }
    63 121   
    64 122   @Override
    skipped 5 lines
    70 128   return false;
    71 129   }
    72 130   JavaPackage that = (JavaPackage) o;
    73  - return name.equals(that.name);
     131 + return pkgNode.equals(that.pkgNode);
    74 132   }
    75 133   
    76 134   @Override
    77 135   public int hashCode() {
    78  - return name.hashCode();
     136 + return pkgNode.hashCode();
    79 137   }
    80 138   
    81 139   @Override
    82 140   public String toString() {
    83  - return name;
     141 + return pkgNode.toString();
    84 142   }
    85 143  }
    86 144   
  • ■ ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/api/JavaVariable.java
    skipped 71 lines
    72 72   }
    73 73   
    74 74   @Override
     75 + public void removeAlias() {
     76 + }
     77 + 
     78 + @Override
    75 79   public boolean isOwnCodeAnnotation(ICodeAnnotation ann) {
    76 80   if (ann.getAnnType() == ICodeAnnotation.AnnType.VAR_REF) {
    77 81   VarRef varRef = (VarRef) ann;
    skipped 22 lines
  • ■ ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/api/data/impl/JadxCodeRename.java
    skipped 84 lines
    85 85   public int hashCode() {
    86 86   return 31 * getNodeRef().hashCode() + Objects.hashCode(getCodeRef());
    87 87   }
     88 + 
     89 + @Override
     90 + public String toString() {
     91 + return "JadxCodeRename{" + nodeRef
     92 + + ", codeRef=" + codeRef
     93 + + ", newName='" + newName + '\''
     94 + + '}';
     95 + }
    88 96  }
    89 97   
  • ■ ■ ■ ■
    jadx-core/src/main/java/jadx/core/deobf/NameMapper.java
    skipped 10 lines
    11 11   
    12 12  public class NameMapper {
    13 13   
    14  - private static final Pattern VALID_JAVA_IDENTIFIER = Pattern.compile(
     14 + public static final Pattern VALID_JAVA_IDENTIFIER = Pattern.compile(
    15 15   "\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*");
    16 16   
    17 17   private static final Pattern VALID_JAVA_FULL_IDENTIFIER = Pattern.compile(
    skipped 182 lines
  • ■ ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/core/dex/nodes/PackageNode.java
    skipped 143 lines
    144 144   return !Objects.equals(pkgInfo.getParentPkg(), aliasPkgInfo.getParentPkg());
    145 145   }
    146 146   
     147 + public void removeAlias() {
     148 + aliasPkgInfo = pkgInfo;
     149 + }
     150 + 
    147 151   public PackageNode getParentPkg() {
    148 152   return parentPkg;
    149 153   }
    skipped 63 lines
  • ■ ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java
    skipped 294 lines
    295 295   }
    296 296   }
    297 297   classes.forEach(ClassNode::updateParentClass);
     298 + for (PackageNode pkg : packages) {
     299 + pkg.getClasses().removeIf(ClassNode::isInner);
     300 + }
    298 301   }
    299 302   
    300 303   public void mergePasses(Map<JadxPassType, List<JadxPass>> customPasses) {
    skipped 333 lines
  • ■ ■ ■ ■
    jadx-core/src/main/java/jadx/core/utils/DebugUtils.java
    skipped 224 lines
    225 225   }
    226 226   
    227 227   public static void printStackTrace(String label) {
    228  - LOG.debug("StackTrace: {}\n{}", label, Utils.getStackTrace(new Exception()));
     228 + LOG.debug("StackTrace: {}\n{}", label, Utils.getFullStackTrace(new Exception()));
    229 229   }
    230 230   
    231 231   public static void printMethodOverrideTop(RootNode root) {
    skipped 44 lines
  • ■ ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/core/utils/Utils.java
    skipped 2 lines
    3 3  import java.io.OutputStream;
    4 4  import java.io.PrintWriter;
    5 5  import java.io.StringWriter;
     6 +import java.util.ArrayDeque;
    6 7  import java.util.ArrayList;
    7 8  import java.util.Arrays;
    8 9  import java.util.Collection;
    9 10  import java.util.Collections;
     11 +import java.util.Deque;
    10 12  import java.util.HashMap;
    11 13  import java.util.HashSet;
    12 14  import java.util.Iterator;
    skipped 1 lines
    14 16  import java.util.Map;
    15 17  import java.util.Objects;
    16 18  import java.util.Set;
     19 +import java.util.function.Consumer;
    17 20  import java.util.function.Function;
    18 21   
    19 22  import org.jetbrains.annotations.Nullable;
    skipped 96 lines
    116 119   return sb.toString();
    117 120   }
    118 121   
     122 + public static String getFullStackTrace(Throwable throwable) {
     123 + return getStackTrace(throwable, false);
     124 + }
     125 + 
    119 126   public static String getStackTrace(Throwable throwable) {
     127 + return getStackTrace(throwable, true);
     128 + }
     129 + 
     130 + private static String getStackTrace(Throwable throwable, boolean filter) {
    120 131   if (throwable == null) {
    121 132   return "";
    122 133   }
    123 134   StringWriter sw = new StringWriter();
    124 135   PrintWriter pw = new PrintWriter(sw, true);
    125  - filterRecursive(throwable);
     136 + if (filter) {
     137 + filterRecursive(throwable);
     138 + }
    126 139   throwable.printStackTrace(pw);
    127 140   return sw.getBuffer().toString();
    128 141   }
    skipped 214 lines
    343 356   map.put(mapKey.apply(v), v);
    344 357   }
    345 358   return map;
     359 + }
     360 + 
     361 + /**
     362 + * Simple DFS visit for tree (cycles not allowed)
     363 + */
     364 + public static <T> void treeDfsVisit(T root, Function<T, List<T>> childrenProvider, Consumer<T> visitor) {
     365 + multiRootTreeDfsVisit(Collections.singletonList(root), childrenProvider, visitor);
     366 + }
     367 + 
     368 + public static <T> void multiRootTreeDfsVisit(List<T> roots, Function<T, List<T>> childrenProvider, Consumer<T> visitor) {
     369 + Deque<T> queue = new ArrayDeque<>(roots);
     370 + while (true) {
     371 + T current = queue.pollLast();
     372 + if (current == null) {
     373 + return;
     374 + }
     375 + visitor.accept(current);
     376 + for (T child : childrenProvider.apply(current)) {
     377 + queue.addLast(child);
     378 + }
     379 + }
    346 380   }
    347 381   
    348 382   @Nullable
    skipped 88 lines
  • ■ ■ ■ ■ ■
    jadx-gui/src/main/java/jadx/gui/JadxWrapper.java
    skipped 31 lines
    32 32  import jadx.gui.settings.JadxProject;
    33 33  import jadx.gui.settings.JadxSettings;
    34 34  import jadx.gui.ui.MainWindow;
     35 +import jadx.gui.utils.CacheObject;
    35 36  import jadx.gui.utils.codecache.CodeStringCache;
    36 37  import jadx.gui.utils.codecache.disk.BufferCodeCache;
    37 38  import jadx.gui.utils.codecache.disk.DiskCodeCache;
    skipped 224 lines
    262 263   
    263 264   public JadxSettings getSettings() {
    264 265   return mainWindow.getSettings();
     266 + }
     267 + 
     268 + public CacheObject getCache() {
     269 + return mainWindow.getCacheObject();
    265 270   }
    266 271   
    267 272   /**
    skipped 26 lines
  • ■ ■ ■ ■ ■
    jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java
    1 1  package jadx.gui.treemodel;
    2 2   
     3 +import java.util.List;
     4 +import java.util.Set;
     5 + 
    3 6  import javax.swing.Icon;
    4 7  import javax.swing.ImageIcon;
    5 8  import javax.swing.JPopupMenu;
    skipped 6 lines
    12 15  import jadx.api.JavaField;
    13 16  import jadx.api.JavaMethod;
    14 17  import jadx.api.JavaNode;
     18 +import jadx.api.data.ICodeRename;
     19 +import jadx.api.data.impl.JadxCodeRename;
     20 +import jadx.api.data.impl.JadxNodeRef;
     21 +import jadx.core.deobf.NameMapper;
    15 22  import jadx.core.dex.attributes.AFlag;
    16 23  import jadx.core.dex.info.AccessInfo;
    17 24  import jadx.gui.ui.MainWindow;
    skipped 6 lines
    24 31  import jadx.gui.utils.NLS;
    25 32  import jadx.gui.utils.UiUtils;
    26 33   
    27  -public class JClass extends JLoadableNode {
     34 +public class JClass extends JLoadableNode implements JRenameNode {
    28 35   private static final long serialVersionUID = -1239986875244097177L;
    29 36   
    30 37   private static final ImageIcon ICON_CLASS_ABSTRACT = UiUtils.openSvgIcon("nodes/abstractClass");
    skipped 8 lines
    39 46   private final transient JClass jParent;
    40 47   private transient boolean loaded;
    41 48   
    42  - public JClass(JavaClass cls) {
    43  - this.cls = cls;
    44  - this.jParent = null;
    45  - this.loaded = false;
    46  - }
    47  - 
    48 49   public JClass(JavaClass cls, JClass parent) {
    49 50   this.cls = cls;
    50 51   this.jParent = parent;
    51  - this.loaded = true;
     52 + this.loaded = parent != null;
    52 53   }
    53 54   
    54 55   public JavaClass getCls() {
    skipped 128 lines
    183 184   
    184 185   public String getFullName() {
    185 186   return cls.getFullName();
     187 + }
     188 + 
     189 + @Override
     190 + public String getTitle() {
     191 + return makeLongStringHtml();
     192 + }
     193 + 
     194 + @Override
     195 + public boolean isValidName(String newName) {
     196 + return NameMapper.isValidIdentifier(newName);
     197 + }
     198 + 
     199 + @Override
     200 + public ICodeRename buildCodeRename(String newName, Set<ICodeRename> renames) {
     201 + return new JadxCodeRename(JadxNodeRef.forCls(cls), newName);
     202 + }
     203 + 
     204 + @Override
     205 + public void removeAlias() {
     206 + cls.removeAlias();
     207 + }
     208 + 
     209 + @Override
     210 + public void addUpdateNodes(List<JavaNode> toUpdate) {
     211 + toUpdate.add(cls);
     212 + toUpdate.addAll(cls.getUseIn());
     213 + }
     214 + 
     215 + @Override
     216 + public void reload(MainWindow mainWindow) {
     217 + mainWindow.reloadTree();
    186 218   }
    187 219   
    188 220   @Override
    skipped 39 lines
  • ■ ■ ■ ■ ■ ■
    jadx-gui/src/main/java/jadx/gui/treemodel/JField.java
    1 1  package jadx.gui.treemodel;
    2 2   
    3 3  import java.util.Comparator;
     4 +import java.util.List;
     5 +import java.util.Set;
    4 6   
    5 7  import javax.swing.Icon;
    6 8  import javax.swing.ImageIcon;
    skipped 4 lines
    11 13   
    12 14  import jadx.api.JavaField;
    13 15  import jadx.api.JavaNode;
     16 +import jadx.api.data.ICodeRename;
     17 +import jadx.api.data.impl.JadxCodeRename;
     18 +import jadx.api.data.impl.JadxNodeRef;
     19 +import jadx.core.deobf.NameMapper;
    14 20  import jadx.core.dex.attributes.AFlag;
    15 21  import jadx.core.dex.info.AccessInfo;
    16 22  import jadx.gui.ui.MainWindow;
    skipped 1 lines
    18 24  import jadx.gui.utils.Icons;
    19 25  import jadx.gui.utils.UiUtils;
    20 26   
    21  -public class JField extends JNode {
     27 +public class JField extends JNode implements JRenameNode {
    22 28   private static final long serialVersionUID = 1712572192106793359L;
    23 29   
    24 30   private static final ImageIcon ICON_FLD_PRI = UiUtils.openSvgIcon("nodes/privateField");
    skipped 34 lines
    59 65   @Override
    60 66   public JPopupMenu onTreePopupMenu(MainWindow mainWindow) {
    61 67   return RenameDialog.buildRenamePopup(mainWindow, this);
     68 + }
     69 + 
     70 + @Override
     71 + public String getTitle() {
     72 + return makeLongStringHtml();
     73 + }
     74 + 
     75 + @Override
     76 + public ICodeRename buildCodeRename(String newName, Set<ICodeRename> renames) {
     77 + return new JadxCodeRename(JadxNodeRef.forFld(field), newName);
     78 + }
     79 + 
     80 + @Override
     81 + public boolean isValidName(String newName) {
     82 + return NameMapper.isValidIdentifier(newName);
     83 + }
     84 + 
     85 + @Override
     86 + public void removeAlias() {
     87 + field.removeAlias();
     88 + }
     89 + 
     90 + @Override
     91 + public void addUpdateNodes(List<JavaNode> toUpdate) {
     92 + toUpdate.add(field);
     93 + toUpdate.addAll(field.getUseIn());
     94 + }
     95 + 
     96 + @Override
     97 + public void reload(MainWindow mainWindow) {
     98 + mainWindow.reloadTree();
    62 99   }
    63 100   
    64 101   @Override
    skipped 79 lines
  • ■ ■ ■ ■ ■
    jadx-gui/src/main/java/jadx/gui/treemodel/JMethod.java
    skipped 1 lines
    2 2   
    3 3  import java.util.Comparator;
    4 4  import java.util.Iterator;
     5 +import java.util.List;
     6 +import java.util.Set;
    5 7   
    6 8  import javax.swing.Icon;
    7 9  import javax.swing.ImageIcon;
    skipped 4 lines
    12 14   
    13 15  import jadx.api.JavaMethod;
    14 16  import jadx.api.JavaNode;
     17 +import jadx.api.data.ICodeRename;
     18 +import jadx.api.data.impl.JadxCodeRename;
     19 +import jadx.api.data.impl.JadxNodeRef;
     20 +import jadx.core.deobf.NameMapper;
    15 21  import jadx.core.dex.attributes.AFlag;
    16 22  import jadx.core.dex.info.AccessInfo;
    17 23  import jadx.core.dex.instructions.args.ArgType;
    skipped 3 lines
    21 27  import jadx.gui.utils.OverlayIcon;
    22 28  import jadx.gui.utils.UiUtils;
    23 29   
    24  -public class JMethod extends JNode {
     30 +public class JMethod extends JNode implements JRenameNode {
    25 31   private static final long serialVersionUID = 3834526867464663751L;
    26 32   private static final ImageIcon ICON_METHOD_ABSTRACT = UiUtils.openSvgIcon("nodes/abstractMethod");
    27 33   private static final ImageIcon ICON_METHOD_PRIVATE = UiUtils.openSvgIcon("nodes/privateMethod");
    skipped 73 lines
    101 107   }
    102 108   
    103 109   @Override
    104  - public boolean canRename() {
    105  - if (mth.isClassInit()) {
    106  - return false;
    107  - }
    108  - return !mth.getMethodNode().contains(AFlag.DONT_RENAME);
    109  - }
    110  - 
    111  - @Override
    112 110   public JPopupMenu onTreePopupMenu(MainWindow mainWindow) {
    113 111   return RenameDialog.buildRenamePopup(mainWindow, this);
    114 112   }
    skipped 22 lines
    137 135   @Override
    138 136   public String getName() {
    139 137   return mth.getName();
     138 + }
     139 + 
     140 + @Override
     141 + public String getTitle() {
     142 + return makeLongStringHtml();
     143 + }
     144 + 
     145 + @Override
     146 + public boolean canRename() {
     147 + if (mth.isClassInit()) {
     148 + return false;
     149 + }
     150 + return !mth.getMethodNode().contains(AFlag.DONT_RENAME);
     151 + }
     152 + 
     153 + @Override
     154 + public JRenameNode replace() {
     155 + if (mth.isConstructor()) {
     156 + // rename class instead constructor
     157 + return jParent;
     158 + }
     159 + return this;
     160 + }
     161 + 
     162 + @Override
     163 + public ICodeRename buildCodeRename(String newName, Set<ICodeRename> renames) {
     164 + List<JavaMethod> relatedMethods = mth.getOverrideRelatedMethods();
     165 + if (!relatedMethods.isEmpty()) {
     166 + for (JavaMethod relatedMethod : relatedMethods) {
     167 + renames.remove(new JadxCodeRename(JadxNodeRef.forMth(relatedMethod), ""));
     168 + }
     169 + }
     170 + return new JadxCodeRename(JadxNodeRef.forMth(mth), newName);
     171 + }
     172 + 
     173 + @Override
     174 + public boolean isValidName(String newName) {
     175 + return NameMapper.isValidIdentifier(newName);
     176 + }
     177 + 
     178 + @Override
     179 + public void removeAlias() {
     180 + mth.removeAlias();
     181 + }
     182 + 
     183 + @Override
     184 + public void addUpdateNodes(List<JavaNode> toUpdate) {
     185 + toUpdate.add(mth);
     186 + toUpdate.addAll(mth.getUseIn());
     187 + List<JavaMethod> overrideRelatedMethods = mth.getOverrideRelatedMethods();
     188 + toUpdate.addAll(overrideRelatedMethods);
     189 + for (JavaMethod ovrdMth : overrideRelatedMethods) {
     190 + toUpdate.addAll(ovrdMth.getUseIn());
     191 + }
     192 + }
     193 + 
     194 + @Override
     195 + public void reload(MainWindow mainWindow) {
     196 + mainWindow.reloadTree();
    140 197   }
    141 198   
    142 199   @Override
    skipped 77 lines
  • ■ ■ ■ ■ ■ ■
    jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java
    skipped 62 lines
    63 63   return javaNode.getName();
    64 64   }
    65 65   
    66  - public boolean canRename() {
    67  - return false;
    68  - }
    69  - 
    70 66   public @Nullable JPopupMenu onTreePopupMenu(MainWindow mainWindow) {
    71 67   return null;
    72 68   }
    skipped 65 lines
  • ■ ■ ■ ■ ■ ■
    jadx-gui/src/main/java/jadx/gui/treemodel/JPackage.java
    1 1  package jadx.gui.treemodel;
    2 2   
    3  -import java.util.ArrayList;
    4  -import java.util.Collections;
    5 3  import java.util.List;
    6 4   
    7 5  import javax.swing.Icon;
    8 6  import javax.swing.JPopupMenu;
    9 7   
     8 +import jadx.api.JavaNode;
    10 9  import jadx.api.JavaPackage;
    11  -import jadx.core.utils.Utils;
    12  -import jadx.gui.JadxWrapper;
    13 10  import jadx.gui.ui.MainWindow;
    14 11  import jadx.gui.ui.popupmenu.JPackagePopupMenu;
    15 12  import jadx.gui.utils.Icons;
    16 13   
     14 +import static jadx.gui.utils.UiUtils.escapeHtml;
     15 +import static jadx.gui.utils.UiUtils.fadeHtml;
     16 +import static jadx.gui.utils.UiUtils.wrapHtml;
     17 + 
    17 18  public class JPackage extends JNode {
    18 19   private static final long serialVersionUID = -4120718634156839804L;
    19 20   
    20  - private String fullName;
    21  - private String name;
    22  - private boolean enabled;
    23  - private List<JClass> classes;
    24  - private List<JPackage> innerPackages;
     21 + public static final String PACKAGE_DEFAULT_HTML_STR = wrapHtml(fadeHtml(escapeHtml("<empty>")));
    25 22   
    26  - public JPackage(JavaPackage pkg, JadxWrapper wrapper) {
    27  - this(pkg.getName(), pkg.getName(),
    28  - isPkgEnabled(wrapper, pkg.getName()),
    29  - Utils.collectionMap(pkg.getClasses(), JClass::new),
    30  - new ArrayList<>());
    31  - update();
    32  - }
     23 + private final JavaPackage pkg;
     24 + private final boolean enabled;
     25 + private final List<JClass> classes;
     26 + private final List<JPackage> subPackages;
    33 27   
    34  - public JPackage(String fullName, JadxWrapper wrapper) {
    35  - this(fullName, fullName, isPkgEnabled(wrapper, fullName), new ArrayList<>(), new ArrayList<>());
    36  - }
     28 + /**
     29 + * Package created by full package alias, don't have a raw package reference.
     30 + * `pkg` field point to the closest raw package leaf.
     31 + */
     32 + private final boolean synthetic;
    37 33   
    38  - public JPackage(String fullName, String name) {
    39  - this(fullName, name, true, Collections.emptyList(), Collections.emptyList());
    40  - }
     34 + private String name;
    41 35   
    42  - private JPackage(String fullName, String name, boolean enabled, List<JClass> classes, List<JPackage> innerPackages) {
    43  - this.fullName = fullName;
    44  - this.name = name;
     36 + public JPackage(JavaPackage pkg, boolean enabled, List<JClass> classes, List<JPackage> subPackages, boolean synthetic) {
     37 + this.pkg = pkg;
    45 38   this.enabled = enabled;
    46 39   this.classes = classes;
    47  - this.innerPackages = innerPackages;
     40 + this.subPackages = subPackages;
     41 + this.synthetic = synthetic;
    48 42   }
    49 43   
    50  - private static boolean isPkgEnabled(JadxWrapper wrapper, String fullPkgName) {
    51  - List<String> excludedPackages = wrapper.getExcludedPackages();
    52  - return excludedPackages.isEmpty()
    53  - || excludedPackages.stream().filter(p -> !p.isEmpty())
    54  - .noneMatch(p -> fullPkgName.equals(p) || fullPkgName.startsWith(p + '.'));
    55  - }
    56  - 
    57  - public final void update() {
     44 + public void update() {
    58 45   removeAllChildren();
    59 46   if (isEnabled()) {
    60  - for (JPackage pkg : innerPackages) {
     47 + for (JPackage pkg : subPackages) {
    61 48   pkg.update();
    62 49   add(pkg);
    63 50   }
    skipped 9 lines
    73 60   return new JPackagePopupMenu(mainWindow, this);
    74 61   }
    75 62   
    76  - @Override
    77  - public String getName() {
    78  - return name;
     63 + public JavaPackage getPkg() {
     64 + return pkg;
    79 65   }
    80 66   
    81  - @Override
    82  - public boolean canRename() {
    83  - return true;
     67 + public JavaNode getJavaNode() {
     68 + return pkg;
    84 69   }
    85 70   
    86  - public String getFullName() {
    87  - return fullName;
     71 + @Override
     72 + public String getName() {
     73 + return name;
    88 74   }
    89 75   
    90  - public void updateBothNames(String fullName, String name, JadxWrapper wrapper) {
    91  - this.fullName = fullName;
     76 + public void setName(String name) {
    92 77   this.name = name;
    93  - this.enabled = isPkgEnabled(wrapper, fullName);
    94 78   }
    95 79   
    96  - public void updateName(String name) {
    97  - this.name = name;
     80 + public List<JPackage> getSubPackages() {
     81 + return subPackages;
    98 82   }
    99 83   
    100  - public List<JPackage> getInnerPackages() {
    101  - return innerPackages;
     84 + public List<JClass> getClasses() {
     85 + return classes;
    102 86   }
    103 87   
    104  - public void setInnerPackages(List<JPackage> innerPackages) {
    105  - this.innerPackages = innerPackages;
     88 + public boolean isEnabled() {
     89 + return enabled;
    106 90   }
    107 91   
    108  - public List<JClass> getClasses() {
    109  - return classes;
    110  - }
    111  - 
    112  - public void setClasses(List<JClass> classes) {
    113  - this.classes = classes;
     92 + public boolean isSynthetic() {
     93 + return synthetic;
    114 94   }
    115 95   
    116 96   @Override
    skipped 14 lines
    131 111   if (o == null || getClass() != o.getClass()) {
    132 112   return false;
    133 113   }
    134  - return name.equals(((JPackage) o).name);
     114 + return pkg.equals(((JPackage) o).pkg);
    135 115   }
    136 116   
    137 117   @Override
    138 118   public int hashCode() {
    139  - return name.hashCode();
     119 + return pkg.hashCode();
    140 120   }
    141 121   
    142 122   @Override
    skipped 2 lines
    145 125   }
    146 126   
    147 127   @Override
    148  - public String makeLongString() {
     128 + public String makeStringHtml() {
     129 + if (name.isEmpty()) {
     130 + return PACKAGE_DEFAULT_HTML_STR;
     131 + }
    149 132   return name;
    150 133   }
    151 134   
    152  - public boolean isEnabled() {
    153  - return enabled;
     135 + @Override
     136 + public String makeLongString() {
     137 + return pkg.getFullName();
     138 + }
     139 + 
     140 + @Override
     141 + public String toString() {
     142 + return name;
    154 143   }
    155 144  }
    156 145   
  • ■ ■ ■ ■ ■ ■
    jadx-gui/src/main/java/jadx/gui/treemodel/JRenameNode.java
     1 +package jadx.gui.treemodel;
     2 + 
     3 +import java.util.List;
     4 +import java.util.Set;
     5 + 
     6 +import javax.swing.Icon;
     7 + 
     8 +import jadx.api.JavaNode;
     9 +import jadx.api.data.ICodeRename;
     10 +import jadx.gui.ui.MainWindow;
     11 + 
     12 +public interface JRenameNode {
     13 + 
     14 + String getTitle();
     15 + 
     16 + String getName();
     17 + 
     18 + Icon getIcon();
     19 + 
     20 + boolean canRename();
     21 + 
     22 + default JRenameNode replace() {
     23 + return this;
     24 + }
     25 + 
     26 + ICodeRename buildCodeRename(String newName, Set<ICodeRename> renames);
     27 + 
     28 + boolean isValidName(String newName);
     29 + 
     30 + void removeAlias();
     31 + 
     32 + void addUpdateNodes(List<JavaNode> toUpdate);
     33 + 
     34 + void reload(MainWindow mainWindow);
     35 +}
     36 + 
  • ■ ■ ■ ■ ■
    jadx-gui/src/main/java/jadx/gui/treemodel/JSources.java
    1 1  package jadx.gui.treemodel;
    2 2   
    3  -import java.util.ArrayList;
    4  -import java.util.Collections;
    5  -import java.util.HashMap;
    6  -import java.util.IdentityHashMap;
    7 3  import java.util.List;
    8  -import java.util.Map;
    9  -import java.util.Set;
    10 4   
    11 5  import javax.swing.Icon;
    12 6  import javax.swing.ImageIcon;
    13 7   
    14  -import jadx.api.JavaPackage;
    15 8  import jadx.gui.JadxWrapper;
    16 9  import jadx.gui.utils.NLS;
    17 10  import jadx.gui.utils.UiUtils;
     11 +import jadx.gui.utils.pkgs.PackageHelper;
    18 12   
    19 13  public class JSources extends JNode {
    20 14   private static final long serialVersionUID = 8962924556824862801L;
    skipped 11 lines
    32 26   
    33 27   public final void update() {
    34 28   removeAllChildren();
    35  - if (flatPackages) {
    36  - for (JavaPackage pkg : wrapper.getPackages()) {
    37  - add(new JPackage(pkg, wrapper));
    38  - }
    39  - } else {
    40  - // build packages hierarchy
    41  - List<JPackage> rootPkgs = getHierarchyPackages(wrapper.getPackages());
    42  - for (JPackage jPackage : rootPkgs) {
    43  - jPackage.update();
    44  - add(jPackage);
    45  - }
    46  - }
    47  - }
    48  - 
    49  - /**
    50  - * Convert packages list to hierarchical packages representation
    51  - *
    52  - * @param packages input packages list
    53  - * @return root packages
    54  - */
    55  - List<JPackage> getHierarchyPackages(List<JavaPackage> packages) {
    56  - Map<String, JPackage> pkgMap = new HashMap<>();
    57  - for (JavaPackage pkg : packages) {
    58  - addPackage(pkgMap, new JPackage(pkg, wrapper));
    59  - }
    60  - // merge packages without classes
    61  - boolean repeat;
    62  - do {
    63  - repeat = false;
    64  - for (JPackage pkg : pkgMap.values()) {
    65  - List<JPackage> innerPackages = pkg.getInnerPackages();
    66  - if (innerPackages.size() == 1 && pkg.getClasses().isEmpty()) {
    67  - JPackage innerPkg = innerPackages.get(0);
    68  - pkg.setInnerPackages(innerPkg.getInnerPackages());
    69  - pkg.setClasses(innerPkg.getClasses());
    70  - String innerName = '.' + innerPkg.getName();
    71  - pkg.updateBothNames(pkg.getFullName() + innerName, pkg.getName() + innerName, wrapper);
    72  - 
    73  - innerPkg.setInnerPackages(Collections.emptyList());
    74  - innerPkg.setClasses(Collections.emptyList());
    75  - repeat = true;
    76  - break;
    77  - }
    78  - }
    79  - } while (repeat);
    80  - 
    81  - // remove empty packages
    82  - pkgMap.values().removeIf(pkg -> pkg.getInnerPackages().isEmpty() && pkg.getClasses().isEmpty());
    83  - 
    84  - // use identity set for collect inner packages
    85  - Set<JPackage> innerPackages = Collections.newSetFromMap(new IdentityHashMap<>());
    86  - for (JPackage pkg : pkgMap.values()) {
    87  - innerPackages.addAll(pkg.getInnerPackages());
     29 + PackageHelper packageHelper = wrapper.getCache().getPackageHelper();
     30 + if (packageHelper == null) {
     31 + packageHelper = new PackageHelper(wrapper);
     32 + wrapper.getCache().setPackageHelper(packageHelper);
    88 33   }
    89  - // find root packages
    90  - List<JPackage> rootPkgs = new ArrayList<>();
    91  - for (JPackage pkg : pkgMap.values()) {
    92  - if (!innerPackages.contains(pkg)) {
    93  - rootPkgs.add(pkg);
    94  - }
    95  - }
    96  - Collections.sort(rootPkgs);
    97  - return rootPkgs;
    98  - }
    99  - 
    100  - private void addPackage(Map<String, JPackage> pkgs, JPackage pkg) {
    101  - String pkgName = pkg.getFullName();
    102  - JPackage replaced = pkgs.put(pkgName, pkg);
    103  - if (replaced != null) {
    104  - pkg.getInnerPackages().addAll(replaced.getInnerPackages());
    105  - pkg.getClasses().addAll(replaced.getClasses());
    106  - }
    107  - int dot = pkgName.lastIndexOf('.');
    108  - if (dot > 0) {
    109  - String prevPart = pkgName.substring(0, dot);
    110  - String shortName = pkgName.substring(dot + 1);
    111  - pkg.updateName(shortName);
    112  - JPackage prevPkg = pkgs.get(prevPart);
    113  - if (prevPkg == null) {
    114  - prevPkg = new JPackage(prevPart, wrapper);
    115  - addPackage(pkgs, prevPkg);
    116  - }
    117  - prevPkg.getInnerPackages().add(pkg);
     34 + List<JPackage> roots = packageHelper.getRoots(flatPackages);
     35 + for (JPackage rootPkg : roots) {
     36 + rootPkg.update();
     37 + add(rootPkg);
    118 38   }
    119 39   }
    120 40   
    skipped 16 lines
  • ■ ■ ■ ■ ■ ■
    jadx-gui/src/main/java/jadx/gui/treemodel/JVariable.java
    1 1  package jadx.gui.treemodel;
    2 2   
     3 +import java.util.List;
     4 +import java.util.Set;
     5 + 
    3 6  import javax.swing.Icon;
    4 7   
    5 8  import jadx.api.JavaNode;
    6 9  import jadx.api.JavaVariable;
     10 +import jadx.api.data.ICodeRename;
     11 +import jadx.api.data.impl.JadxCodeRef;
     12 +import jadx.api.data.impl.JadxCodeRename;
     13 +import jadx.api.data.impl.JadxNodeRef;
     14 +import jadx.core.deobf.NameMapper;
     15 +import jadx.gui.ui.MainWindow;
    7 16  import jadx.gui.utils.UiUtils;
    8 17   
    9  -public class JVariable extends JNode {
     18 +public class JVariable extends JNode implements JRenameNode {
    10 19   private static final long serialVersionUID = -3002100457834453783L;
    11 20   
    12 21   private final JMethod jMth;
    skipped 58 lines
    71 80   @Override
    72 81   public boolean canRename() {
    73 82   return var.getName() != null;
     83 + }
     84 + 
     85 + @Override
     86 + public String getTitle() {
     87 + return makeLongStringHtml();
     88 + }
     89 + 
     90 + @Override
     91 + public boolean isValidName(String newName) {
     92 + return NameMapper.isValidIdentifier(newName);
     93 + }
     94 + 
     95 + @Override
     96 + public ICodeRename buildCodeRename(String newName, Set<ICodeRename> renames) {
     97 + return new JadxCodeRename(JadxNodeRef.forMth(var.getMth()), JadxCodeRef.forVar(var), newName);
     98 + }
     99 + 
     100 + @Override
     101 + public void removeAlias() {
     102 + var.removeAlias();
     103 + }
     104 + 
     105 + @Override
     106 + public void addUpdateNodes(List<JavaNode> toUpdate) {
     107 + toUpdate.add(var.getMth());
     108 + }
     109 + 
     110 + @Override
     111 + public void reload(MainWindow mainWindow) {
    74 112   }
    75 113  }
    76 114   
  • ■ ■ ■ ■ ■
    jadx-gui/src/main/java/jadx/gui/ui/codearea/RenameAction.java
    1 1  package jadx.gui.ui.codearea;
    2 2   
    3 3  import jadx.gui.treemodel.JNode;
     4 +import jadx.gui.treemodel.JRenameNode;
    4 5  import jadx.gui.ui.dialog.RenameDialog;
    5 6  import jadx.gui.utils.NLS;
    6 7   
    skipped 10 lines
    17 18   
    18 19   @Override
    19 20   public boolean isActionEnabled(JNode node) {
    20  - return node != null && node.canRename();
     21 + if (node == null) {
     22 + return false;
     23 + }
     24 + if (node instanceof JRenameNode) {
     25 + return ((JRenameNode) node).canRename();
     26 + }
     27 + return false;
    21 28   }
    22 29   
    23 30   @Override
    24 31   public void runAction(JNode node) {
    25  - RenameDialog.rename(getCodeArea().getMainWindow(), getCodeArea().getNode(), node);
     32 + RenameDialog.rename(getCodeArea().getMainWindow(), getCodeArea().getNode(), (JRenameNode) node);
    26 33   }
    27 34  }
    28 35   
  • ■ ■ ■ ■ ■
    jadx-gui/src/main/java/jadx/gui/ui/dialog/RenameDialog.java
    skipped 26 lines
    27 27  import javax.swing.WindowConstants;
    28 28   
    29 29  import org.jetbrains.annotations.NotNull;
     30 +import org.jetbrains.annotations.Nullable;
    30 31  import org.slf4j.Logger;
    31 32  import org.slf4j.LoggerFactory;
    32 33   
    33  -import jadx.api.JavaClass;
    34  -import jadx.api.JavaMethod;
    35 34  import jadx.api.JavaNode;
    36  -import jadx.api.JavaVariable;
    37 35  import jadx.api.data.ICodeRename;
    38 36  import jadx.api.data.impl.JadxCodeData;
    39  -import jadx.api.data.impl.JadxCodeRef;
    40  -import jadx.api.data.impl.JadxCodeRename;
    41  -import jadx.api.data.impl.JadxNodeRef;
    42  -import jadx.core.deobf.NameMapper;
    43 37  import jadx.core.utils.Utils;
    44  -import jadx.core.utils.exceptions.JadxRuntimeException;
    45 38  import jadx.gui.jobs.TaskStatus;
    46 39  import jadx.gui.settings.JadxProject;
    47 40  import jadx.gui.treemodel.JClass;
    48  -import jadx.gui.treemodel.JField;
    49  -import jadx.gui.treemodel.JMethod;
    50 41  import jadx.gui.treemodel.JNode;
    51  -import jadx.gui.treemodel.JPackage;
    52  -import jadx.gui.treemodel.JVariable;
     42 +import jadx.gui.treemodel.JRenameNode;
    53 43  import jadx.gui.ui.MainWindow;
    54 44  import jadx.gui.ui.TabbedPane;
    55 45  import jadx.gui.ui.codearea.ClassCodeContentPanel;
    skipped 14 lines
    70 60   
    71 61   private final transient MainWindow mainWindow;
    72 62   private final transient CacheObject cache;
    73  - private final transient JNode source;
    74  - private final transient JNode node;
     63 + private final transient @Nullable JNode source;
     64 + private final transient JRenameNode node;
    75 65   private transient JTextField renameField;
    76 66   private transient JButton renameBtn;
    77 67   
    78  - public static boolean rename(MainWindow mainWindow, JNode node) {
    79  - return rename(mainWindow, node, node);
    80  - }
    81  - 
    82  - public static boolean rename(MainWindow mainWindow, JNode source, JNode node) {
     68 + public static boolean rename(MainWindow mainWindow, JNode source, JRenameNode node) {
    83 69   RenameDialog renameDialog = new RenameDialog(mainWindow, source, node);
    84 70   UiUtils.uiRun(() -> renameDialog.setVisible(true));
    85 71   UiUtils.uiRun(renameDialog::initRenameField); // wait for UI events to propagate
    86 72   return true;
    87 73   }
    88 74   
    89  - public static JPopupMenu buildRenamePopup(MainWindow mainWindow, JNode node) {
     75 + public static JPopupMenu buildRenamePopup(MainWindow mainWindow, JRenameNode node) {
    90 76   JMenuItem jmi = new JMenuItem(NLS.str("popup.rename"));
    91  - jmi.addActionListener(action -> RenameDialog.rename(mainWindow, node));
     77 + jmi.addActionListener(action -> RenameDialog.rename(mainWindow, null, node));
    92 78   JPopupMenu menu = new JPopupMenu();
    93 79   menu.add(jmi);
    94 80   return menu;
    95 81   }
    96 82   
    97  - private RenameDialog(MainWindow mainWindow, JNode source, JNode node) {
     83 + private RenameDialog(MainWindow mainWindow, JNode source, JRenameNode node) {
    98 84   super(mainWindow);
    99 85   this.mainWindow = mainWindow;
    100 86   this.cache = mainWindow.getCacheObject();
    101 87   this.source = source;
    102  - this.node = replaceNode(node);
     88 + this.node = node.replace();
    103 89   initUI();
    104 90   }
    105 91   
    skipped 2 lines
    108 94   renameField.selectAll();
    109 95   }
    110 96   
    111  - private JNode replaceNode(JNode node) {
    112  - if (node instanceof JMethod) {
    113  - JavaMethod javaMethod = ((JMethod) node).getJavaMethod();
    114  - if (javaMethod.isClassInit()) {
    115  - throw new JadxRuntimeException("Can't rename class init method: " + node);
    116  - }
    117  - if (javaMethod.isConstructor()) {
    118  - // rename class instead constructor
    119  - return node.getJParent();
    120  - }
    121  - }
    122  - return node;
    123  - }
    124  - 
    125  - private boolean checkNewName() {
    126  - String newName = renameField.getText();
     97 + private boolean checkNewName(String newName) {
    127 98   if (newName.isEmpty()) {
    128 99   // use empty name to reset rename (revert to original)
    129 100   return true;
    130 101   }
    131  - boolean valid = NameMapper.isValidIdentifier(newName);
     102 + boolean valid = node.isValidName(newName);
    132 103   if (renameBtn.isEnabled() != valid) {
    133 104   renameBtn.setEnabled(valid);
    134 105   renameField.putClientProperty("JComponent.outline", valid ? "" : "error");
    skipped 2 lines
    137 108   }
    138 109   
    139 110   private void rename() {
    140  - if (!checkNewName()) {
     111 + String newName = renameField.getText().trim();
     112 + if (!checkNewName(newName)) {
    141 113   return;
    142 114   }
    143 115   try {
    144  - updateCodeRenames(set -> processRename(node, renameField.getText(), set));
     116 + updateCodeRenames(set -> processRename(newName, set));
    145 117   refreshState();
    146 118   } catch (Exception e) {
    147 119   LOG.error("Rename failed", e);
    skipped 2 lines
    150 122   dispose();
    151 123   }
    152 124   
    153  - private void processRename(JNode node, String newName, Set<ICodeRename> renames) {
    154  - JadxCodeRename rename = buildRename(node, newName, renames);
     125 + private void processRename(String newName, Set<ICodeRename> renames) {
     126 + ICodeRename rename = node.buildCodeRename(newName, renames);
    155 127   renames.remove(rename);
    156  - JavaNode javaNode = node.getJavaNode();
    157  - if (javaNode != null) {
    158  - javaNode.removeAlias();
    159  - }
     128 + node.removeAlias();
    160 129   if (!newName.isEmpty()) {
    161 130   renames.add(rename);
    162 131   }
    163 132   }
    164 133   
    165  - @NotNull
    166  - private JadxCodeRename buildRename(JNode node, String newName, Set<ICodeRename> renames) {
    167  - if (node instanceof JMethod) {
    168  - JavaMethod javaMethod = ((JMethod) node).getJavaMethod();
    169  - List<JavaMethod> relatedMethods = javaMethod.getOverrideRelatedMethods();
    170  - if (!relatedMethods.isEmpty()) {
    171  - for (JavaMethod relatedMethod : relatedMethods) {
    172  - renames.remove(new JadxCodeRename(JadxNodeRef.forMth(relatedMethod), ""));
    173  - }
    174  - }
    175  - return new JadxCodeRename(JadxNodeRef.forMth(javaMethod), newName);
    176  - }
    177  - if (node instanceof JField) {
    178  - return new JadxCodeRename(JadxNodeRef.forFld(((JField) node).getJavaField()), newName);
    179  - }
    180  - if (node instanceof JClass) {
    181  - return new JadxCodeRename(JadxNodeRef.forCls(((JClass) node).getCls()), newName);
    182  - }
    183  - if (node instanceof JPackage) {
    184  - return new JadxCodeRename(JadxNodeRef.forPkg(((JPackage) node).getFullName()), newName);
    185  - }
    186  - if (node instanceof JVariable) {
    187  - JavaVariable javaVar = ((JVariable) node).getJavaVarNode();
    188  - return new JadxCodeRename(JadxNodeRef.forMth(javaVar.getMth()), JadxCodeRef.forVar(javaVar), newName);
    189  - }
    190  - throw new JadxRuntimeException("Failed to build rename node for: " + node);
    191  - }
    192  - 
    193 134   private void updateCodeRenames(Consumer<Set<ICodeRename>> updater) {
    194 135   JadxProject project = mainWindow.getProject();
    195 136   JadxCodeData codeData = project.getCodeData();
    skipped 12 lines
    208 149   private void refreshState() {
    209 150   mainWindow.getWrapper().reInitRenameVisitor();
    210 151   
    211  - JNodeCache nodeCache = cache.getNodeCache();
    212  - JavaNode javaNode = node.getJavaNode();
    213  - 
    214 152   List<JavaNode> toUpdate = new ArrayList<>();
    215 153   if (source != null && source != node) {
    216 154   toUpdate.add(source.getJavaNode());
    217 155   }
    218  - if (javaNode != null) {
    219  - toUpdate.add(javaNode);
    220  - toUpdate.addAll(javaNode.getUseIn());
    221  - if (node instanceof JMethod) {
    222  - toUpdate.addAll(((JMethod) node).getJavaMethod().getOverrideRelatedMethods());
    223  - }
    224  - } else if (node instanceof JPackage) {
    225  - processPackage(toUpdate);
    226  - } else {
    227  - throw new JadxRuntimeException("Unexpected node type: " + node);
    228  - }
     156 + node.addUpdateNodes(toUpdate);
     157 + 
     158 + JNodeCache nodeCache = cache.getNodeCache();
    229 159   Set<JClass> updatedTopClasses = toUpdate
    230 160   .stream()
    231 161   .map(JavaNode::getTopParentClass)
    skipped 13 lines
    245 175   mainWindow.showHeapUsageBar();
    246 176   UiUtils.errorMessage(this, NLS.str("message.memoryLow"));
    247 177   }
    248  - if (node instanceof JPackage) {
    249  - mainWindow.getTreeRoot().update();
    250  - }
    251  - mainWindow.reloadTree();
     178 + node.reload(mainWindow);
    252 179   });
    253  - }
    254  - }
    255  - 
    256  - private void processPackage(List<JavaNode> toUpdate) {
    257  - String rawFullPkg = ((JPackage) node).getFullName();
    258  - String rawFullPkgDot = rawFullPkg + ".";
    259  - for (JavaClass cls : mainWindow.getWrapper().getClasses()) {
    260  - String clsPkg = cls.getClassNode().getClassInfo().getPackage();
    261  - // search all classes in package
    262  - if (clsPkg.equals(rawFullPkg) || clsPkg.startsWith(rawFullPkgDot)) {
    263  - toUpdate.add(cls);
    264  - // also include all usages (for import fix)
    265  - toUpdate.addAll(cls.getUseIn());
    266  - }
    267 180   }
    268 181   }
    269 182   
    skipped 53 lines
    323 236   
    324 237   private void initUI() {
    325 238   JLabel lbl = new JLabel(NLS.str("popup.rename"));
    326  - JLabel nodeLabel = NodeLabel.longName(node);
     239 + NodeLabel nodeLabel = new NodeLabel(node.getTitle());
     240 + nodeLabel.setIcon(node.getIcon());
     241 + if (node instanceof JNode) {
     242 + nodeLabel.disableHtml(((JNode) node).disableHtml());
     243 + }
    327 244   lbl.setLabelFor(nodeLabel);
    328 245   
    329 246   renameField = new JTextField(40);
    330  - renameField.getDocument().addDocumentListener(new DocumentUpdateListener(ev -> checkNewName()));
     247 + renameField.getDocument().addDocumentListener(new DocumentUpdateListener(ev -> checkNewName(renameField.getText())));
    331 248   renameField.addActionListener(e -> rename());
    332 249   new TextStandardActions(renameField);
    333 250   
    skipped 38 lines
  • ■ ■ ■ ■ ■
    jadx-gui/src/main/java/jadx/gui/ui/popupmenu/JPackagePopupMenu.java
    1 1  package jadx.gui.ui.popupmenu;
    2 2   
    3 3  import java.awt.event.ActionEvent;
    4  -import java.util.Arrays;
    5 4  import java.util.List;
    6 5   
    7 6  import javax.swing.AbstractAction;
    skipped 2 lines
    10 9  import javax.swing.JMenuItem;
    11 10  import javax.swing.JPopupMenu;
    12 11   
    13  -import org.jetbrains.annotations.Nullable;
    14 12  import org.slf4j.Logger;
    15 13  import org.slf4j.LoggerFactory;
    16 14   
    17 15  import jadx.gui.JadxWrapper;
    18  -import jadx.gui.treemodel.JClass;
    19 16  import jadx.gui.treemodel.JPackage;
    20 17  import jadx.gui.ui.MainWindow;
    21 18  import jadx.gui.ui.dialog.ExcludePkgDialog;
    22 19  import jadx.gui.ui.dialog.RenameDialog;
    23 20  import jadx.gui.utils.NLS;
     21 +import jadx.gui.utils.pkgs.JRenamePackage;
     22 +import jadx.gui.utils.pkgs.PackageHelper;
    24 23   
    25 24  public class JPackagePopupMenu extends JPopupMenu {
    26 25   private static final long serialVersionUID = -7781009781149224131L;
    skipped 7 lines
    34 33   
    35 34   add(makeExcludeItem(pkg));
    36 35   add(makeExcludeItem());
    37  - JMenuItem menuItem = makeRenameMenuItem(pkg);
    38  - if (menuItem != null) {
    39  - add(menuItem);
    40  - }
     36 + add(makeRenameMenuItem(pkg));
    41 37   }
    42 38   
    43  - @Nullable
    44 39   private JMenuItem makeRenameMenuItem(JPackage pkg) {
    45  - List<String> aliasShortParts = splitPackage(pkg.getName());
    46  - int count = aliasShortParts.size();
    47  - if (count == 0) {
    48  - return null;
    49  - }
    50  - String rawPackage = getRawPackage(pkg);
    51  - if (rawPackage == null) {
    52  - return null;
    53  - }
    54  - List<String> aliasParts = splitPackage(pkg.getFullName());
    55  - List<String> rawParts = splitPackage(rawPackage); // can be longer then alias parts
    56  - int start = aliasParts.size() - count;
    57  - if (count == 1) {
    58  - // single case => no submenu
    59  - JPackage renamePkg = new JPackage(concat(rawParts, start), aliasParts.get(start));
    60  - JMenuItem pkgItem = new JMenuItem(NLS.str("popup.rename"));
    61  - pkgItem.addActionListener(e -> rename(renamePkg));
    62  - return pkgItem;
    63  - }
    64 40   JMenuItem renameSubMenu = new JMenu(NLS.str("popup.rename"));
    65  - for (int i = start; i < aliasParts.size(); i++) {
    66  - String aliasShortPkg = aliasParts.get(i);
    67  - JPackage pkgPart = new JPackage(concat(rawParts, i), aliasShortPkg);
    68  - JMenuItem pkgPartItem = new JMenuItem(aliasShortPkg);
    69  - pkgPartItem.addActionListener(e -> rename(pkgPart));
     41 + PackageHelper packageHelper = mainWindow.getCacheObject().getPackageHelper();
     42 + List<JRenamePackage> nodes = packageHelper.getRenameNodes(pkg);
     43 + for (JRenamePackage node : nodes) {
     44 + JMenuItem pkgPartItem = new JMenuItem(node.getTitle(), node.getIcon());
     45 + pkgPartItem.addActionListener(e -> rename(node));
    70 46   renameSubMenu.add(pkgPartItem);
    71 47   }
    72 48   return renameSubMenu;
    73 49   }
    74 50   
    75  - private String concat(List<String> parts, int n) {
    76  - if (n == 0) {
    77  - return parts.get(0);
    78  - }
    79  - StringBuilder sb = new StringBuilder();
    80  - sb.append(parts.get(0));
    81  - int count = parts.size();
    82  - for (int i = 1; i < count && i <= n; i++) {
    83  - sb.append('.');
    84  - sb.append(parts.get(i));
    85  - }
    86  - return sb.toString();
    87  - }
    88  - 
    89  - private void rename(JPackage pkg) {
    90  - LOG.debug("Renaming package: fullName={}, name={}", pkg.getFullName(), pkg.getName());
    91  - RenameDialog.rename(mainWindow, pkg);
    92  - }
    93  - 
    94  - private List<String> splitPackage(String rawPackage) {
    95  - return Arrays.asList(rawPackage.split("\\."));
    96  - }
    97  - 
    98  - private String getRawPackage(JPackage pkg) {
    99  - List<JClass> classes = pkg.getClasses();
    100  - if (!classes.isEmpty()) {
    101  - return classes.get(0).getRootClass().getCls().getClassNode().getClassInfo().getPackage();
    102  - }
    103  - for (JPackage innerPkg : pkg.getInnerPackages()) {
    104  - String rawPackage = getRawPackage(innerPkg);
    105  - if (rawPackage != null) {
    106  - return rawPackage;
    107  - }
    108  - }
    109  - return null;
     51 + private void rename(JRenamePackage pkg) {
     52 + LOG.debug("Renaming package: {}", pkg);
     53 + RenameDialog.rename(mainWindow, null, pkg);
    110 54   }
    111 55   
    112 56   private JMenuItem makeExcludeItem(JPackage pkg) {
    skipped 1 lines
    114 58   excludeItem.setSelected(!pkg.isEnabled());
    115 59   excludeItem.addItemListener(e -> {
    116 60   JadxWrapper wrapper = mainWindow.getWrapper();
    117  - String fullName = pkg.getFullName();
     61 + String fullName = pkg.getPkg().getFullName();
    118 62   if (excludeItem.isSelected()) {
    119 63   wrapper.addExcludedPackage(fullName);
    120 64   } else {
    skipped 19 lines
  • ■ ■ ■ ■ ■ ■
    jadx-gui/src/main/java/jadx/gui/utils/CacheObject.java
    skipped 8 lines
    9 9   
    10 10  import jadx.api.JavaClass;
    11 11  import jadx.gui.ui.dialog.SearchDialog;
     12 +import jadx.gui.utils.pkgs.PackageHelper;
    12 13   
    13 14  public class CacheObject {
    14 15   
    skipped 2 lines
    17 18   private Map<SearchDialog.SearchPreset, Set<SearchDialog.SearchOptions>> lastSearchOptions;
    18 19   
    19 20   private List<List<JavaClass>> decompileBatches;
     21 + private PackageHelper packageHelper;
    20 22   
    21 23   public CacheObject() {
    22 24   reset();
    skipped 4 lines
    27 29   jNodeCache = new JNodeCache();
    28 30   lastSearchOptions = new HashMap<>();
    29 31   decompileBatches = null;
     32 + packageHelper = null;
    30 33   }
    31 34   
    32 35   @Nullable
    skipped 19 lines
    52 55   
    53 56   public void setDecompileBatches(List<List<JavaClass>> decompileBatches) {
    54 57   this.decompileBatches = decompileBatches;
     58 + }
     59 + 
     60 + public PackageHelper getPackageHelper() {
     61 + return packageHelper;
     62 + }
     63 + 
     64 + public void setPackageHelper(PackageHelper packageHelper) {
     65 + this.packageHelper = packageHelper;
    55 66   }
    56 67  }
    57 68   
  • ■ ■ ■ ■
    jadx-gui/src/main/java/jadx/gui/utils/JNodeCache.java
    skipped 22 lines
    23 23   if (javaNode == null) {
    24 24   return null;
    25 25   }
    26  - // don't use 'computeIfAbsent' method here, it this cause 'Recursive update' exception
     26 + // don't use 'computeIfAbsent' method here, it will cause 'Recursive update' exception
    27 27   JNode jNode = cache.get(javaNode);
    28 28   if (jNode == null || jNode.getJavaNode() != javaNode) {
    29 29   jNode = convert(javaNode);
    skipped 59 lines
  • ■ ■ ■ ■ ■ ■
    jadx-gui/src/main/java/jadx/gui/utils/pkgs/JRenamePackage.java
     1 +package jadx.gui.utils.pkgs;
     2 + 
     3 +import java.util.List;
     4 +import java.util.Set;
     5 +import java.util.regex.Matcher;
     6 +import java.util.regex.Pattern;
     7 + 
     8 +import javax.swing.Icon;
     9 + 
     10 +import org.apache.commons.lang3.StringUtils;
     11 + 
     12 +import jadx.api.JavaNode;
     13 +import jadx.api.JavaPackage;
     14 +import jadx.api.data.ICodeRename;
     15 +import jadx.api.data.impl.JadxCodeRename;
     16 +import jadx.api.data.impl.JadxNodeRef;
     17 +import jadx.core.deobf.NameMapper;
     18 +import jadx.gui.treemodel.JRenameNode;
     19 +import jadx.gui.ui.MainWindow;
     20 +import jadx.gui.utils.Icons;
     21 + 
     22 +import static jadx.core.deobf.NameMapper.VALID_JAVA_IDENTIFIER;
     23 + 
     24 +public class JRenamePackage implements JRenameNode {
     25 + 
     26 + private final JavaPackage refPkg;
     27 + private final String rawFullName;
     28 + private final String fullName;
     29 + private final String name;
     30 + 
     31 + public JRenamePackage(JavaPackage refPkg, String rawFullName, String fullName, String name) {
     32 + this.refPkg = refPkg;
     33 + this.rawFullName = rawFullName;
     34 + this.fullName = fullName;
     35 + this.name = name;
     36 + }
     37 + 
     38 + @Override
     39 + public String getTitle() {
     40 + return fullName;
     41 + }
     42 + 
     43 + @Override
     44 + public String getName() {
     45 + return name;
     46 + }
     47 + 
     48 + @Override
     49 + public Icon getIcon() {
     50 + return Icons.PACKAGE;
     51 + }
     52 + 
     53 + @Override
     54 + public boolean canRename() {
     55 + return true;
     56 + }
     57 + 
     58 + @Override
     59 + public ICodeRename buildCodeRename(String newName, Set<ICodeRename> renames) {
     60 + return new JadxCodeRename(JadxNodeRef.forPkg(rawFullName), newName);
     61 + }
     62 + 
     63 + @Override
     64 + public boolean isValidName(String newName) {
     65 + return isValidPackageName(newName);
     66 + }
     67 + 
     68 + private static final Pattern PACKAGE_RENAME_PATTERN = Pattern.compile(
     69 + "PKG(\\.PKG)*(\\.)?".replace("PKG", VALID_JAVA_IDENTIFIER.pattern()));
     70 + 
     71 + static boolean isValidPackageName(String newName) {
     72 + if (newName == null || newName.isEmpty() || NameMapper.isReserved(newName)) {
     73 + return false;
     74 + }
     75 + Matcher matcher = PACKAGE_RENAME_PATTERN.matcher(newName);
     76 + if (!matcher.matches()) {
     77 + return false;
     78 + }
     79 + for (String part : StringUtils.split(newName, '.')) {
     80 + if (NameMapper.isReserved(part)) {
     81 + return false;
     82 + }
     83 + }
     84 + return true;
     85 + }
     86 + 
     87 + @Override
     88 + public void removeAlias() {
     89 + refPkg.removeAlias();
     90 + }
     91 + 
     92 + @Override
     93 + public void addUpdateNodes(List<JavaNode> toUpdate) {
     94 + refPkg.addUseIn(toUpdate);
     95 + }
     96 + 
     97 + @Override
     98 + public void reload(MainWindow mainWindow) {
     99 + mainWindow.getCacheObject().setPackageHelper(null);
     100 + mainWindow.getTreeRoot().update();
     101 + mainWindow.reloadTree();
     102 + }
     103 +}
     104 + 
  • ■ ■ ■ ■ ■ ■
    jadx-gui/src/main/java/jadx/gui/utils/pkgs/PackageHelper.java
     1 +package jadx.gui.utils.pkgs;
     2 + 
     3 +import java.util.ArrayList;
     4 +import java.util.Collections;
     5 +import java.util.Comparator;
     6 +import java.util.HashMap;
     7 +import java.util.HashSet;
     8 +import java.util.List;
     9 +import java.util.Map;
     10 +import java.util.Set;
     11 + 
     12 +import org.slf4j.Logger;
     13 +import org.slf4j.LoggerFactory;
     14 + 
     15 +import jadx.api.JavaPackage;
     16 +import jadx.core.dex.info.PackageInfo;
     17 +import jadx.core.utils.ListUtils;
     18 +import jadx.core.utils.Utils;
     19 +import jadx.gui.JadxWrapper;
     20 +import jadx.gui.treemodel.JClass;
     21 +import jadx.gui.treemodel.JPackage;
     22 +import jadx.gui.utils.JNodeCache;
     23 + 
     24 +public class PackageHelper {
     25 + private static final Logger LOG = LoggerFactory.getLogger(PackageHelper.class);
     26 + 
     27 + private static final Comparator<JClass> CLASS_COMPARATOR = Comparator.comparing(JClass::getName, String.CASE_INSENSITIVE_ORDER);
     28 + private static final Comparator<JPackage> PKG_COMPARATOR = Comparator.comparing(JPackage::getName, String.CASE_INSENSITIVE_ORDER);
     29 + 
     30 + private final JadxWrapper wrapper;
     31 + private List<String> excludedPackages;
     32 + private JNodeCache nodeCache;
     33 + 
     34 + private final Map<PackageInfo, JPackage> pkgInfoMap = new HashMap<>();
     35 + 
     36 + public PackageHelper(JadxWrapper wrapper) {
     37 + this.wrapper = wrapper;
     38 + }
     39 + 
     40 + public List<JPackage> getRoots(boolean flatPackages) {
     41 + excludedPackages = wrapper.getExcludedPackages();
     42 + nodeCache = wrapper.getCache().getNodeCache();
     43 + pkgInfoMap.clear();
     44 + if (flatPackages) {
     45 + return prepareFlatPackages();
     46 + }
     47 + long start = System.currentTimeMillis();
     48 + List<JPackage> roots = prepareHierarchyPackages();
     49 + if (LOG.isDebugEnabled()) {
     50 + LOG.debug("Prepare hierarchy packages in {} ms", System.currentTimeMillis() - start);
     51 + }
     52 + return roots;
     53 + }
     54 + 
     55 + public List<JRenamePackage> getRenameNodes(JPackage pkg) {
     56 + List<JRenamePackage> list = new ArrayList<>();
     57 + PackageInfo pkgInfo = pkg.getPkg().getPkgNode().getAliasPkgInfo();
     58 + Set<String> added = new HashSet<>();
     59 + do {
     60 + JPackage jPkg = pkgInfoMap.get(pkgInfo);
     61 + if (jPkg != null) {
     62 + JavaPackage javaPkg = jPkg.getPkg();
     63 + String fullName = javaPkg.isDefault() ? JPackage.PACKAGE_DEFAULT_HTML_STR : javaPkg.getFullName();
     64 + String name = jPkg.isSynthetic() || javaPkg.isParentRenamed() ? fullName : javaPkg.getName();
     65 + JRenamePackage renamePkg = new JRenamePackage(javaPkg, javaPkg.getRawFullName(), fullName, name);
     66 + if (added.add(fullName)) {
     67 + list.add(renamePkg);
     68 + }
     69 + }
     70 + pkgInfo = pkgInfo.getParentPkg();
     71 + } while (pkgInfo != null);
     72 + return list;
     73 + }
     74 + 
     75 + private List<JPackage> prepareFlatPackages() {
     76 + List<JPackage> list = new ArrayList<>();
     77 + for (JavaPackage javaPkg : wrapper.getPackages()) {
     78 + if (javaPkg.isLeaf()) {
     79 + JPackage pkg = buildJPackage(javaPkg, false);
     80 + pkg.setName(javaPkg.getFullName());
     81 + list.add(pkg);
     82 + pkgInfoMap.put(javaPkg.getPkgNode().getAliasPkgInfo(), pkg);
     83 + }
     84 + }
     85 + list.sort(PKG_COMPARATOR);
     86 + return list;
     87 + }
     88 + 
     89 + private List<JPackage> prepareHierarchyPackages() {
     90 + JPackage root = new JPackage(null, true, Collections.emptyList(), new ArrayList<>(), true);
     91 + List<JavaPackage> packages = wrapper.getPackages();
     92 + List<JPackage> jPackages = new ArrayList<>(packages.size());
     93 + // create nodes for exists packages
     94 + for (JavaPackage javaPkg : packages) {
     95 + JPackage jPkg = buildJPackage(javaPkg, false);
     96 + jPackages.add(jPkg);
     97 + PackageInfo aliasPkgInfo = javaPkg.getPkgNode().getAliasPkgInfo();
     98 + jPkg.setName(aliasPkgInfo.getName());
     99 + pkgInfoMap.put(aliasPkgInfo, jPkg);
     100 + if (aliasPkgInfo.isRoot()) {
     101 + root.getSubPackages().add(jPkg);
     102 + }
     103 + }
     104 + // link subpackages, create missing packages created by renames
     105 + for (JPackage jPkg : jPackages) {
     106 + if (jPkg.getPkg().isLeaf()) {
     107 + buildLeafPath(jPkg, root, pkgInfoMap);
     108 + }
     109 + }
     110 + List<JPackage> toMerge = new ArrayList<>();
     111 + traverseMiddlePackages(root, toMerge);
     112 + Utils.treeDfsVisit(root, JPackage::getSubPackages, v -> v.getSubPackages().sort(PKG_COMPARATOR));
     113 + return root.getSubPackages();
     114 + }
     115 + 
     116 + private void buildLeafPath(JPackage jPkg, JPackage root, Map<PackageInfo, JPackage> pkgMap) {
     117 + JPackage currentJPkg = jPkg;
     118 + PackageInfo current = jPkg.getPkg().getPkgNode().getAliasPkgInfo();
     119 + while (true) {
     120 + current = current.getParentPkg();
     121 + if (current == null) {
     122 + break;
     123 + }
     124 + JPackage parentJPkg = pkgMap.get(current);
     125 + if (parentJPkg == null) {
     126 + parentJPkg = buildJPackage(currentJPkg.getPkg(), true);
     127 + parentJPkg.setName(current.getName());
     128 + pkgMap.put(current, parentJPkg);
     129 + if (current.isRoot()) {
     130 + root.getSubPackages().add(parentJPkg);
     131 + }
     132 + }
     133 + List<JPackage> subPackages = parentJPkg.getSubPackages();
     134 + String pkgName = currentJPkg.getName();
     135 + if (ListUtils.noneMatch(subPackages, p -> p.getName().equals(pkgName))) {
     136 + subPackages.add(currentJPkg);
     137 + }
     138 + currentJPkg = parentJPkg;
     139 + }
     140 + }
     141 + 
     142 + private static void traverseMiddlePackages(JPackage pkg, List<JPackage> toMerge) {
     143 + List<JPackage> subPackages = pkg.getSubPackages();
     144 + int count = subPackages.size();
     145 + for (int i = 0; i < count; i++) {
     146 + JPackage subPackage = subPackages.get(i);
     147 + JPackage replacePkg = mergeMiddlePackages(subPackage, toMerge);
     148 + if (replacePkg != subPackage) {
     149 + subPackages.set(i, replacePkg);
     150 + }
     151 + traverseMiddlePackages(replacePkg, toMerge);
     152 + }
     153 + }
     154 + 
     155 + private static JPackage mergeMiddlePackages(JPackage jPkg, List<JPackage> merged) {
     156 + List<JPackage> subPackages = jPkg.getSubPackages();
     157 + if (subPackages.size() == 1 && jPkg.getClasses().isEmpty()) {
     158 + merged.add(jPkg);
     159 + JPackage endPkg = mergeMiddlePackages(subPackages.get(0), merged);
     160 + merged.clear();
     161 + return endPkg;
     162 + }
     163 + if (!merged.isEmpty()) {
     164 + merged.add(jPkg);
     165 + jPkg.setName(Utils.listToString(merged, ".", JPackage::getName));
     166 + }
     167 + return jPkg;
     168 + }
     169 + 
     170 + private JPackage buildJPackage(JavaPackage javaPkg, boolean synthetic) {
     171 + boolean pkgEnabled = isPkgEnabled(javaPkg.getRawFullName(), excludedPackages);
     172 + List<JClass> classes;
     173 + if (synthetic) {
     174 + classes = Collections.emptyList();
     175 + } else {
     176 + classes = Utils.collectionMap(javaPkg.getClasses(), nodeCache::makeFrom);
     177 + classes.sort(CLASS_COMPARATOR);
     178 + }
     179 + return new JPackage(javaPkg, pkgEnabled, classes, new ArrayList<>(), synthetic);
     180 + }
     181 + 
     182 + private static boolean isPkgEnabled(String fullPkgName, List<String> excludedPackages) {
     183 + return excludedPackages.isEmpty()
     184 + || excludedPackages.stream()
     185 + .noneMatch(p -> fullPkgName.equals(p) || fullPkgName.startsWith(p + '.'));
     186 + }
     187 +}
     188 + 
  • ■ ■ ■ ■ ■ ■
    jadx-gui/src/test/java/jadx/api/Factory.java
    1  -package jadx.api;
    2  - 
    3  -import java.util.List;
    4  - 
    5  -import jadx.core.dex.nodes.ClassNode;
    6  - 
    7  -public class Factory {
    8  - 
    9  - public static JavaPackage newPackage(String name, List<JavaClass> classes) {
    10  - return new JavaPackage(name, classes);
    11  - }
    12  - 
    13  - public static JavaClass newClass(JadxDecompiler decompiler, ClassNode classNode) {
    14  - return new JavaClass(classNode, decompiler);
    15  - }
    16  -}
    17  - 
  • ■ ■ ■ ■ ■ ■
    jadx-gui/src/test/java/jadx/gui/treemodel/JSourcesTest.java
    1  -package jadx.gui.treemodel;
    2  - 
    3  -import java.util.Collections;
    4  -import java.util.List;
    5  - 
    6  -import org.junit.jupiter.api.BeforeEach;
    7  -import org.junit.jupiter.api.Test;
    8  - 
    9  -import jadx.api.Factory;
    10  -import jadx.api.JadxArgs;
    11  -import jadx.api.JadxDecompiler;
    12  -import jadx.api.JavaClass;
    13  -import jadx.api.JavaPackage;
    14  -import jadx.core.dex.nodes.ClassNode;
    15  -import jadx.gui.JadxWrapper;
    16  - 
    17  -import static java.util.Arrays.asList;
    18  -import static org.hamcrest.MatcherAssert.assertThat;
    19  -import static org.hamcrest.Matchers.hasSize;
    20  -import static org.hamcrest.Matchers.is;
    21  -import static org.mockito.Mockito.mock;
    22  -import static org.mockito.Mockito.when;
    23  - 
    24  -public class JSourcesTest {
    25  - 
    26  - private JSources sources;
    27  - private JadxDecompiler decompiler;
    28  - 
    29  - @BeforeEach
    30  - public void init() {
    31  - JRoot root = mock(JRoot.class);
    32  - when(root.isFlatPackages()).thenReturn(false);
    33  - JadxWrapper wrapper = mock(JadxWrapper.class);
    34  - sources = new JSources(root, wrapper);
    35  - decompiler = new JadxDecompiler(new JadxArgs());
    36  - }
    37  - 
    38  - @Test
    39  - public void testHierarchyPackages() {
    40  - String pkgName = "a.b.c.d.e";
    41  - 
    42  - List<JavaPackage> packages = Collections.singletonList(newPkg(pkgName));
    43  - List<JPackage> out = sources.getHierarchyPackages(packages);
    44  - 
    45  - assertThat(out, hasSize(1));
    46  - JPackage jPkg = out.get(0);
    47  - assertThat(jPkg.getName(), is(pkgName));
    48  - assertThat(jPkg.getClasses(), hasSize(1));
    49  - }
    50  - 
    51  - @Test
    52  - public void testHierarchyPackages2() {
    53  - List<JavaPackage> packages = asList(
    54  - newPkg("a.b"),
    55  - newPkg("a.c"),
    56  - newPkg("a.d"));
    57  - List<JPackage> out = sources.getHierarchyPackages(packages);
    58  - 
    59  - assertThat(out, hasSize(1));
    60  - JPackage jPkg = out.get(0);
    61  - assertThat(jPkg.getName(), is("a"));
    62  - assertThat(jPkg.getClasses(), hasSize(0));
    63  - assertThat(jPkg.getInnerPackages(), hasSize(3));
    64  - }
    65  - 
    66  - @Test
    67  - public void testHierarchyPackages3() {
    68  - List<JavaPackage> packages = asList(
    69  - newPkg("a.b.p1"),
    70  - newPkg("a.b.p2"),
    71  - newPkg("a.b.p3"));
    72  - List<JPackage> out = sources.getHierarchyPackages(packages);
    73  - 
    74  - assertThat(out, hasSize(1));
    75  - JPackage jPkg = out.get(0);
    76  - assertThat(jPkg.getName(), is("a.b"));
    77  - assertThat(jPkg.getClasses(), hasSize(0));
    78  - assertThat(jPkg.getInnerPackages(), hasSize(3));
    79  - }
    80  - 
    81  - @Test
    82  - public void testHierarchyPackages4() {
    83  - List<JavaPackage> packages = asList(
    84  - newPkg("a.p1"),
    85  - newPkg("a.b.c.p2"),
    86  - newPkg("a.b.c.p3"),
    87  - newPkg("d.e"),
    88  - newPkg("d.f.a"));
    89  - List<JPackage> out = sources.getHierarchyPackages(packages);
    90  - 
    91  - assertThat(out, hasSize(2));
    92  - assertThat(out.get(0).getName(), is("a"));
    93  - assertThat(out.get(0).getInnerPackages(), hasSize(2));
    94  - assertThat(out.get(1).getName(), is("d"));
    95  - assertThat(out.get(1).getInnerPackages(), hasSize(2));
    96  - }
    97  - 
    98  - private JavaPackage newPkg(String name) {
    99  - return Factory.newPackage(name, Collections.singletonList(newClass()));
    100  - }
    101  - 
    102  - private JavaClass newClass() {
    103  - return Factory.newClass(decompiler, mock(ClassNode.class));
    104  - }
    105  -}
    106  - 
  • ■ ■ ■ ■ ■ ■
    jadx-gui/src/test/java/jadx/gui/utils/pkgs/TestJRenamePackage.java
     1 +package jadx.gui.utils.pkgs;
     2 + 
     3 +import org.junit.jupiter.api.Test;
     4 + 
     5 +import static org.assertj.core.api.Assertions.assertThat;
     6 + 
     7 +class TestJRenamePackage {
     8 + 
     9 + @Test
     10 + void isValidName() {
     11 + valid("foo");
     12 + valid("foo.bar");
     13 + valid("foo.bar.");
     14 + 
     15 + invalid("");
     16 + invalid("0foo");
     17 + invalid(".foo");
     18 + invalid("do");
     19 + invalid("foo.if");
     20 + invalid("foo.if.bar");
     21 + }
     22 + 
     23 + private void valid(String name) {
     24 + assertThat(JRenamePackage.isValidPackageName(name))
     25 + .as("expect valid: %s", name)
     26 + .isEqualTo(true);
     27 + }
     28 + 
     29 + private void invalid(String name) {
     30 + assertThat(JRenamePackage.isValidPackageName(name))
     31 + .as("expect invalid: %s", name)
     32 + .isEqualTo(false);
     33 + }
     34 +}
     35 + 
  • ■ ■ ■ ■
    jadx-plugins/jadx-script/examples/build.gradle.kts
    skipped 9 lines
    10 10   
    11 11   implementation("io.github.microutils:kotlin-logging-jvm:3.0.2")
    12 12   
    13  - // manual imports ( IDE can't import dependencies by scripts annotations)
     13 + // manual imports (IDE can't import dependencies by scripts annotations)
    14 14   implementation("com.github.javafaker:javafaker:1.0.2")
    15 15  }
    16 16   
    skipped 6 lines
Please wait...
Page is in error, reload to recover