Projects STRLCPY jadx Commits 3d8f98a5
🤬
  • feat: allow to move class to another package

  • Loading...
  • Skylot committed 1 year ago
    3d8f98a5
    1 parent 8fb07290
  • ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/api/JadxDecompiler.java
    skipped 6 lines
    7 7  import java.util.Collection;
    8 8  import java.util.Collections;
    9 9  import java.util.HashMap;
     10 +import java.util.IdentityHashMap;
    10 11  import java.util.List;
    11 12  import java.util.Map;
    12 13  import java.util.Objects;
    skipped 86 lines
    99 100   private final Map<ClassNode, JavaClass> classesMap = new ConcurrentHashMap<>();
    100 101   private final Map<MethodNode, JavaMethod> methodsMap = new ConcurrentHashMap<>();
    101 102   private final Map<FieldNode, JavaField> fieldsMap = new ConcurrentHashMap<>();
    102  - private final Map<PackageNode, JavaPackage> pkgsMap = new ConcurrentHashMap<>();
     103 + private final Map<PackageNode, JavaPackage> pkgsMap = Collections.synchronizedMap(new IdentityHashMap<>());
    103 104   
    104 105   private final IDecompileScheduler decompileScheduler = new DecompilerScheduler();
    105 106   
    skipped 312 lines
    418 419   }
    419 420   
    420 421   public List<JavaPackage> getPackages() {
     422 + pkgsMap.clear(); // reset cache
    421 423   return Utils.collectionMap(root.getPackages(), this::convertPackageNode);
    422 424   }
    423 425   
    skipped 262 lines
  • ■ ■ ■ ■
    jadx-core/src/main/java/jadx/api/JavaClass.java
    skipped 328 lines
    329 329   
    330 330   @Override
    331 331   public void removeAlias() {
    332  - this.cls.getClassInfo().removeAlias();
     332 + cls.removeAlias();
    333 333   }
    334 334   
    335 335   @Override
    skipped 20 lines
  • ■ ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/core/deobf/DeobfAliasProvider.java
    skipped 7 lines
    8 8  import jadx.core.dex.nodes.MethodNode;
    9 9  import jadx.core.dex.nodes.PackageNode;
    10 10  import jadx.core.dex.nodes.RootNode;
     11 +import jadx.core.utils.StringUtils;
    11 12   
    12 13  public class DeobfAliasProvider implements IAliasProvider {
    13 14   
    skipped 60 lines
    74 75   } else if (cls.getAccessFlags().isAbstract()) {
    75 76   result.append("Abstract");
    76 77   }
     78 + result.append(getBaseName(cls));
     79 + return result.toString();
     80 + }
    77 81   
    78  - // Process current class and all super classes
     82 + /**
     83 + * Process current class and all super classes to get meaningful parent name
     84 + */
     85 + private static String getBaseName(ClassNode cls) {
    79 86   ClassNode currentCls = cls;
    80  - outerLoop: while (currentCls != null) {
    81  - if (currentCls.getSuperClass() != null) {
    82  - String superClsName = currentCls.getSuperClass().getObject();
    83  - if (superClsName.startsWith("android.app.")) {
    84  - // e.g. Activity or Fragment
    85  - result.append(superClsName.substring(12));
    86  - break;
    87  - } else if (superClsName.startsWith("android.os.")) {
    88  - // e.g. AsyncTask
    89  - result.append(superClsName.substring(11));
    90  - break;
     87 + while (currentCls != null) {
     88 + ArgType superCls = currentCls.getSuperClass();
     89 + if (superCls != null) {
     90 + String superClsName = superCls.getObject();
     91 + if (superClsName.startsWith("android.app.") // e.g. Activity or Fragment
     92 + || superClsName.startsWith("android.os.") // e.g. AsyncTask
     93 + ) {
     94 + return getClsName(superClsName);
    91 95   }
    92 96   }
    93  - for (ArgType intf : cls.getInterfaces()) {
    94  - String intfClsName = intf.getObject();
    95  - if (intfClsName.equals("java.lang.Runnable")) {
    96  - result.append("Runnable");
    97  - break outerLoop;
    98  - } else if (intfClsName.startsWith("java.util.concurrent.")) {
    99  - // e.g. Callable
    100  - result.append(intfClsName.substring(21));
    101  - break outerLoop;
    102  - } else if (intfClsName.startsWith("android.view.")) {
    103  - // e.g. View.OnClickListener
    104  - result.append(intfClsName.substring(13));
    105  - break outerLoop;
    106  - } else if (intfClsName.startsWith("android.content.")) {
    107  - // e.g. DialogInterface.OnClickListener
    108  - result.append(intfClsName.substring(16));
    109  - break outerLoop;
     97 + for (ArgType interfaceType : cls.getInterfaces()) {
     98 + String name = interfaceType.getObject();
     99 + if (name.equals("java.lang.Runnable")) {
     100 + return "Runnable";
     101 + }
     102 + if (name.startsWith("java.util.concurrent.") // e.g. Callable
     103 + || name.startsWith("android.view.") // e.g. View.OnClickListener
     104 + || name.startsWith("android.content.") // e.g. DialogInterface.OnClickListener
     105 + ) {
     106 + return getClsName(name);
    110 107   }
    111 108   }
    112  - if (currentCls.getSuperClass() == null) {
     109 + if (superCls == null) {
    113 110   break;
    114 111   }
    115  - currentCls = cls.root().resolveClass(currentCls.getSuperClass());
     112 + currentCls = cls.root().resolveClass(superCls);
    116 113   }
    117  - return result.toString();
     114 + return "";
     115 + }
     116 + 
     117 + private static String getClsName(String name) {
     118 + int pgkEnd = name.lastIndexOf('.');
     119 + String clsName = name.substring(pgkEnd + 1);
     120 + return StringUtils.removeChar(clsName, '$');
    118 121   }
    119 122  }
    120 123   
  • ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/core/dex/info/ClassInfo.java
    skipped 78 lines
    79 79   }
    80 80   }
    81 81   
     82 + public void changePkgAndName(String aliasPkg, String aliasShortName) {
     83 + if (isInner()) {
     84 + throw new JadxRuntimeException("Can't change package for inner class");
     85 + }
     86 + ClassAliasInfo newAlias = new ClassAliasInfo(aliasPkg, aliasShortName);
     87 + fillAliasFullName(newAlias);
     88 + this.alias = newAlias;
     89 + }
     90 + 
    82 91   private void fillAliasFullName(ClassAliasInfo alias) {
    83 92   if (parentClass == null) {
    84 93   alias.setFullName(makeFullClsName(alias.getPkg(), alias.getShortName(), null, true, false));
    skipped 30 lines
    115 124   return true;
    116 125   }
    117 126   return parentClass != null && parentClass.hasAlias();
     127 + }
     128 + 
     129 + public boolean hasAliasPkg() {
     130 + return !getPackage().equals(getAliasPkg());
    118 131   }
    119 132   
    120 133   public void removeAlias() {
    skipped 151 lines
    272 285   return true;
    273 286   }
    274 287   if (obj instanceof ClassInfo) {
    275  - ClassInfo other = (ClassInfo) obj;
    276  - return this.type.equals(other.type);
     288 + return type.equals(((ClassInfo) obj).type);
    277 289   }
    278 290   return false;
    279 291   }
    280 292   
    281 293   @Override
    282  - public int compareTo(@NotNull ClassInfo o) {
    283  - return getFullName().compareTo(o.getFullName());
     294 + public int compareTo(@NotNull ClassInfo other) {
     295 + return getRawName().compareTo(other.getRawName());
    284 296   }
    285 297  }
    286 298   
  • ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/core/dex/nodes/ClassNode.java
    skipped 12 lines
    13 13   
    14 14  import org.jetbrains.annotations.NotNull;
    15 15  import org.jetbrains.annotations.Nullable;
    16  -import org.slf4j.Logger;
    17  -import org.slf4j.LoggerFactory;
    18 16   
    19 17  import jadx.api.DecompilationMode;
    20 18  import jadx.api.ICodeCache;
    skipped 36 lines
    57 55   
    58 56  public class ClassNode extends NotificationAttrNode
    59 57   implements ILoadable, ICodeNode, IPackageUpdate, Comparable<ClassNode> {
    60  - private static final Logger LOG = LoggerFactory.getLogger(ClassNode.class);
    61 58   
    62 59   private final RootNode root;
    63 60   private final IClassData clsData;
    64 61   
    65 62   private final ClassInfo clsInfo;
    66  - private final PackageNode packageNode;
     63 + private PackageNode packageNode;
    67 64   private AccessInfo accessFlags;
    68 65   private ArgType superClass;
    69 66   private List<ArgType> interfaces;
    skipped 490 lines
    560 557   parentClass = this;
    561 558   }
    562 559   
     560 + /**
     561 + * Change class name and package (if full name provided)
     562 + * Leading dot can be used to move to default package.
     563 + * Package for inner classes can't be changed.
     564 + */
    563 565   @Override
    564 566   public void rename(String newName) {
    565  - clsInfo.changeShortName(newName);
     567 + int lastDot = newName.lastIndexOf('.');
     568 + if (lastDot == -1) {
     569 + clsInfo.changeShortName(newName);
     570 + return;
     571 + }
     572 + if (isInner()) {
     573 + addWarn("Can't change package for inner class: " + this + " to " + newName);
     574 + return;
     575 + }
     576 + // change class package
     577 + String newPkg = newName.substring(0, lastDot);
     578 + String newShortName = newName.substring(lastDot + 1);
     579 + if (changeClassNodePackage(newPkg)) {
     580 + clsInfo.changePkgAndName(newPkg, newShortName);
     581 + } else {
     582 + clsInfo.changeShortName(newShortName);
     583 + }
     584 + }
     585 + 
     586 + private boolean changeClassNodePackage(String fullPkg) {
     587 + if (clsInfo.isInner()) {
     588 + throw new JadxRuntimeException("Can't change package for inner class");
     589 + }
     590 + if (fullPkg.equals(clsInfo.getAliasPkg())) {
     591 + return false;
     592 + }
     593 + root.removeClsFromPackage(packageNode, this);
     594 + packageNode = PackageNode.getForClass(root, fullPkg, this);
     595 + root.sortPackages();
     596 + return true;
     597 + }
     598 + 
     599 + public void removeAlias() {
     600 + if (!clsInfo.isInner()) {
     601 + changeClassNodePackage(clsInfo.getPackage());
     602 + }
     603 + clsInfo.removeAlias();
    566 604   }
    567 605   
    568 606   @Override
    skipped 1 lines
    570 608   if (isInner()) {
    571 609   return;
    572 610   }
    573  - getClassInfo().changePkg(packageNode.getAliasPkgInfo().getFullName());
     611 + clsInfo.changePkg(packageNode.getAliasPkgInfo().getFullName());
    574 612   }
    575 613   
    576 614   public PackageNode getPackageNode() {
    skipped 300 lines
    877 915   
    878 916   @Override
    879 917   public int compareTo(@NotNull ClassNode o) {
    880  - return this.getFullName().compareTo(o.getFullName());
     918 + return this.clsInfo.compareTo(o.clsInfo);
    881 919   }
    882 920   
    883 921   @Override
    skipped 5 lines
  • ■ ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/core/dex/nodes/PackageNode.java
    skipped 8 lines
    9 9   
    10 10  import jadx.core.dex.info.PackageInfo;
    11 11   
     12 +import static jadx.core.utils.StringUtils.containsChar;
     13 + 
    12 14  public class PackageNode implements IPackageUpdate, IDexNode, Comparable<PackageNode> {
    13 15   
    14 16   private final RootNode root;
    skipped 48 lines
    63 65   public void rename(String newName, boolean runUpdates) {
    64 66   String alias;
    65 67   boolean isFullAlias;
    66  - if (newName.indexOf('/') != -1) {
     68 + if (containsChar(newName, '/')) {
    67 69   alias = newName.replace('/', '.');
    68 70   isFullAlias = true;
    69  - } else if (newName.endsWith(".")) {
    70  - // treat as full pkg, remove ending dot
    71  - alias = newName.substring(0, newName.length() - 1);
     71 + } else if (newName.startsWith(".")) {
     72 + // treat as full pkg, remove start dot
     73 + alias = newName.substring(1);
    72 74   isFullAlias = true;
    73 75   } else {
    74 76   alias = newName;
    75  - isFullAlias = alias.indexOf('.') != -1;
     77 + isFullAlias = containsChar(newName, '.');
    76 78   }
    77 79   if (isFullAlias) {
    78 80   setFullAlias(alias, runUpdates);
    skipped 69 lines
    148 150   aliasPkgInfo = pkgInfo;
    149 151   }
    150 152   
    151  - public PackageNode getParentPkg() {
     153 + public @Nullable PackageNode getParentPkg() {
    152 154   return parentPkg;
    153 155   }
    154 156   
    skipped 15 lines
    170 172   
    171 173   public List<ClassNode> getClasses() {
    172 174   return classes;
     175 + }
     176 + 
     177 + public boolean isEmpty() {
     178 + return classes.isEmpty() && subPackages.isEmpty();
    173 179   }
    174 180   
    175 181   @Override
    skipped 41 lines
  • ■ ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/core/dex/nodes/RootNode.java
    skipped 391 lines
    392 392   packages.add(pkg);
    393 393   }
    394 394   
     395 + public void removePackage(PackageNode pkg) {
     396 + if (pkgMap.remove(pkg.getPkgInfo().getFullName()) != null) {
     397 + packages.remove(pkg);
     398 + PackageNode parentPkg = pkg.getParentPkg();
     399 + if (parentPkg != null) {
     400 + parentPkg.getSubPackages().remove(pkg);
     401 + if (parentPkg.isEmpty()) {
     402 + removePackage(parentPkg);
     403 + }
     404 + }
     405 + for (PackageNode subPkg : pkg.getSubPackages()) {
     406 + removePackage(subPkg);
     407 + }
     408 + }
     409 + }
     410 + 
     411 + public void sortPackages() {
     412 + Collections.sort(packages);
     413 + }
     414 + 
     415 + public void removeClsFromPackage(PackageNode pkg, ClassNode cls) {
     416 + boolean removed = pkg.getClasses().remove(cls);
     417 + if (removed && pkg.isEmpty()) {
     418 + removePackage(pkg);
     419 + }
     420 + }
     421 + 
    395 422   /**
    396 423   * Update sub packages
    397 424   */
    skipped 268 lines
  • ■ ■ ■ ■ ■ ■
    jadx-core/src/main/java/jadx/core/utils/StringUtils.java
    skipped 277 lines
    278 278   return count;
    279 279   }
    280 280   
     281 + public static boolean containsChar(String str, char ch) {
     282 + return str.indexOf(ch) != -1;
     283 + }
     284 + 
     285 + public static String removeChar(String str, char ch) {
     286 + int pos = str.indexOf(ch);
     287 + if (pos == -1) {
     288 + return str;
     289 + }
     290 + StringBuilder sb = new StringBuilder(str.length());
     291 + int cur = 0;
     292 + int next = pos;
     293 + while (true) {
     294 + sb.append(str, cur, next);
     295 + cur = next + 1;
     296 + next = str.indexOf(ch, cur);
     297 + if (next == -1) {
     298 + sb.append(str, cur, str.length());
     299 + break;
     300 + }
     301 + }
     302 + return sb.toString();
     303 + }
     304 + 
    281 305   /**
    282 306   * returns how many lines does it have between start to pos in content.
    283 307   */
    skipped 67 lines
  • ■ ■ ■ ■ ■ ■
    jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java
    skipped 198 lines
    199 199   
    200 200   @Override
    201 201   public boolean isValidName(String newName) {
    202  - return NameMapper.isValidIdentifier(newName);
     202 + if (NameMapper.isValidIdentifier(newName)) {
     203 + return true;
     204 + }
     205 + if (cls.isInner()) {
     206 + // disallow to change package for inner classes
     207 + return false;
     208 + }
     209 + if (NameMapper.isValidFullIdentifier(newName)) {
     210 + return true;
     211 + }
     212 + // moving to default pkg
     213 + return newName.startsWith(".") && NameMapper.isValidIdentifier(newName.substring(1));
    203 214   }
    204 215   
    205 216   @Override
    skipped 14 lines
    220 231   
    221 232   @Override
    222 233   public void reload(MainWindow mainWindow) {
     234 + // TODO: rebuild packages only if class package has been changed
     235 + mainWindow.rebuildPackagesTree();
    223 236   mainWindow.reloadTree();
    224 237   }
    225 238   
    skipped 40 lines
  • ■ ■ ■ ■ ■
    jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java
    skipped 852 lines
    853 853   treeReloading = false;
    854 854   }
    855 855   
     856 + public void rebuildPackagesTree() {
     857 + cacheObject.setPackageHelper(null);
     858 + treeRoot.update();
     859 + }
     860 + 
    856 861   private void expand(TreeNode node, List<String[]> treeExpansions) {
    857 862   TreeNode[] pathNodes = treeModel.getPathToRoot(node);
    858 863   if (pathNodes == null) {
    skipped 978 lines
  • ■ ■ ■ ■ ■
    jadx-gui/src/main/java/jadx/gui/utils/pkgs/JRenamePackage.java
    skipped 65 lines
    66 66   }
    67 67   
    68 68   private static final Pattern PACKAGE_RENAME_PATTERN = Pattern.compile(
    69  - "PKG(\\.PKG)*(\\.)?".replace("PKG", VALID_JAVA_IDENTIFIER.pattern()));
     69 + "(\\.)?PKG(\\.PKG)*".replace("PKG", VALID_JAVA_IDENTIFIER.pattern()));
    70 70   
    71 71   static boolean isValidPackageName(String newName) {
    72 72   if (newName == null || newName.isEmpty() || NameMapper.isReserved(newName)) {
    skipped 23 lines
    96 96   
    97 97   @Override
    98 98   public void reload(MainWindow mainWindow) {
    99  - mainWindow.getCacheObject().setPackageHelper(null);
    100  - mainWindow.getTreeRoot().update();
     99 + mainWindow.rebuildPackagesTree();
    101 100   mainWindow.reloadTree();
    102 101   }
    103 102  }
    skipped 1 lines
  • ■ ■ ■ ■ ■ ■
    jadx-gui/src/test/java/jadx/gui/utils/pkgs/TestJRenamePackage.java
    skipped 9 lines
    10 10   void isValidName() {
    11 11   valid("foo");
    12 12   valid("foo.bar");
    13  - valid("foo.bar.");
     13 + valid(".bar");
    14 14   
    15 15   invalid("");
    16 16   invalid("0foo");
    17  - invalid(".foo");
     17 + invalid("foo.");
    18 18   invalid("do");
    19 19   invalid("foo.if");
    20 20   invalid("foo.if.bar");
    skipped 15 lines
Please wait...
Page is in error, reload to recover