feat: improve better name algorithm (PR #2299)
This commit is contained in:
@@ -81,7 +81,7 @@ public class SourceFileRename extends AbstractVisitor {
|
||||
case ALWAYS:
|
||||
return sourceName;
|
||||
case IF_BETTER:
|
||||
return BetterName.compareAndGet(sourceName, currentName);
|
||||
return BetterName.getBetterClassName(sourceName, currentName);
|
||||
case NEVER:
|
||||
return currentName;
|
||||
default:
|
||||
|
||||
@@ -15,6 +15,78 @@ public class BetterName {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
private static final double TOLERANCE = 0.001;
|
||||
|
||||
/**
|
||||
* Compares two class names and returns the "better" one.
|
||||
* If both names are equally good, {@code firstName} is returned.
|
||||
*/
|
||||
public static String getBetterClassName(String firstName, String secondName) {
|
||||
return getBetterName(firstName, secondName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two resource names and returns the "better" one.
|
||||
* If both names are equally good, {@code firstName} is returned.
|
||||
*/
|
||||
public static String getBetterResourceName(String firstName, String secondName) {
|
||||
return getBetterName(firstName, secondName);
|
||||
}
|
||||
|
||||
private static String getBetterName(String firstName, String secondName) {
|
||||
if (Objects.equals(firstName, secondName)) {
|
||||
return firstName;
|
||||
}
|
||||
|
||||
if (StringUtils.isEmpty(firstName) || StringUtils.isEmpty(secondName)) {
|
||||
return StringUtils.notEmpty(firstName)
|
||||
? firstName
|
||||
: secondName;
|
||||
}
|
||||
|
||||
final var firstResult = analyze(firstName);
|
||||
final var secondResult = analyze(secondName);
|
||||
|
||||
if (firstResult.digitCount != 0 || secondResult.digitCount != 0) {
|
||||
final var firstRatio = (float) firstResult.digitCount / firstResult.length;
|
||||
final var secondRatio = (float) secondResult.digitCount / secondResult.length;
|
||||
|
||||
if (Math.abs(secondRatio - firstRatio) >= TOLERANCE) {
|
||||
return firstRatio <= secondRatio
|
||||
? firstName
|
||||
: secondName;
|
||||
}
|
||||
}
|
||||
|
||||
return firstResult.length >= secondResult.length
|
||||
? firstName
|
||||
: secondName;
|
||||
}
|
||||
|
||||
private static AnalyzeResult analyze(String name) {
|
||||
final var result = new AnalyzeResult();
|
||||
|
||||
StringUtils.visitCodePoints(name, cp -> {
|
||||
if (Character.isDigit(cp)) {
|
||||
result.digitCount++;
|
||||
}
|
||||
|
||||
result.length++;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static class AnalyzeResult {
|
||||
private int length;
|
||||
private int digitCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #getBetterClassName(String, String)} or
|
||||
* {@link #getBetterResourceName(String, String)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public static String compareAndGet(String first, String second) {
|
||||
if (Objects.equals(first, second)) {
|
||||
return first;
|
||||
@@ -32,6 +104,11 @@ public class BetterName {
|
||||
return firstBetter ? first : second;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated This function is an implementation detail of deprecated
|
||||
* {@link #compareAndGet(String, String)} and should not be used outside tests.
|
||||
*/
|
||||
@Deprecated
|
||||
public static int calcRating(String str) {
|
||||
int rating = str.length() * 3;
|
||||
rating += differentCharsCount(str) * 20;
|
||||
@@ -49,6 +126,7 @@ public class BetterName {
|
||||
return rating;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
private static int differentCharsCount(String str) {
|
||||
String lower = str.toLowerCase(Locale.ROOT);
|
||||
Set<Integer> chars = new HashSet<>();
|
||||
|
||||
@@ -490,7 +490,7 @@ public class ResTableBinaryParser extends CommonBinaryParser implements IResTabl
|
||||
public static String getBetterName(ResourceNameSource nameSource, String resName, String codeName) {
|
||||
switch (nameSource) {
|
||||
case AUTO:
|
||||
return BetterName.compareAndGet(resName, codeName);
|
||||
return BetterName.getBetterResourceName(resName, codeName);
|
||||
case RESOURCES:
|
||||
return resName;
|
||||
case CODE:
|
||||
|
||||
@@ -7,12 +7,14 @@ import static jadx.tests.api.utils.assertj.JadxAssertions.assertThat;
|
||||
|
||||
public class TestBetterName {
|
||||
|
||||
@Deprecated
|
||||
@Test
|
||||
public void test() {
|
||||
expectFirst("color_main", "t0");
|
||||
expectFirst("done", "oOo0oO0o");
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
private void expectFirst(String first, String second) {
|
||||
String best = BetterName.compareAndGet(first, second);
|
||||
assertThat(best)
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
package jadx.core.utils;
|
||||
|
||||
import org.assertj.core.api.AbstractStringAssert;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static jadx.core.utils.BetterName.getBetterClassName;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestGetBetterClassName {
|
||||
|
||||
@Test
|
||||
public void testGoodNamesVsGeneratedAliases() {
|
||||
assertThatBetterClassName("AppCompatButton", "C2404e2").isEqualTo("AppCompatButton");
|
||||
assertThatBetterClassName("ContextThemeWrapper", "C2106b1").isEqualTo("ContextThemeWrapper");
|
||||
assertThatBetterClassName("ListPopupWindow", "C2344a3").isEqualTo("ListPopupWindow");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShortGoodNamesVsGeneratedAliases() {
|
||||
assertThatBetterClassName("Room", "C2937kh").isEqualTo("Room");
|
||||
assertThatBetterClassName("Fade", "C1428qi").isEqualTo("Fade");
|
||||
assertThatBetterClassName("Scene", "C4063yi").isEqualTo("Scene");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGoodNamesVsGeneratedAliasesWithPrefix() {
|
||||
assertThatBetterClassName("AppCompatActivity", "ActivityC2646h0").isEqualTo("AppCompatActivity");
|
||||
assertThatBetterClassName("PagerAdapter", "AbstractC3038lk").isEqualTo("PagerAdapter");
|
||||
assertThatBetterClassName("Lazy", "InterfaceC6434a").isEqualTo("Lazy");
|
||||
assertThatBetterClassName("MembersInjector", "InterfaceC6435b").isEqualTo("MembersInjector");
|
||||
assertThatBetterClassName("Subscriber", "InterfaceC6439c").isEqualTo("Subscriber");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGoodNamesWithDigitsVsGeneratedAliases() {
|
||||
assertThatBetterClassName("ISO8061Formatter", "C1121uq4").isEqualTo("ISO8061Formatter");
|
||||
assertThatBetterClassName("Jdk9Platform", "C1189rn6").isEqualTo("Jdk9Platform");
|
||||
assertThatBetterClassName("WrappedDrawableApi14", "C2847i9").isEqualTo("WrappedDrawableApi14");
|
||||
assertThatBetterClassName("WrappedDrawableApi21", "C2888j9").isEqualTo("WrappedDrawableApi21");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testShortNamesVsLongNames() {
|
||||
assertThatBetterClassName("az", "Observer").isEqualTo("Observer");
|
||||
assertThatBetterClassName("bb", "RenderEvent").isEqualTo("RenderEvent");
|
||||
assertThatBetterClassName("aaaa", "FontUtils").isEqualTo("FontUtils");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link BetterName#getBetterClassName(String, String)} on equally good names.
|
||||
* In this case, according to the documentation, the method should return the first argument.
|
||||
*
|
||||
* @see BetterName#getBetterClassName(String, String)
|
||||
*/
|
||||
@Test
|
||||
public void testEquallyGoodNames() {
|
||||
assertThatBetterClassName("AAAA", "BBBB").isEqualTo("AAAA");
|
||||
assertThatBetterClassName("BBBB", "AAAA").isEqualTo("BBBB");
|
||||
|
||||
assertThatBetterClassName("XYXYXY", "YZYZYZ").isEqualTo("XYXYXY");
|
||||
assertThatBetterClassName("YZYZYZ", "XYXYXY").isEqualTo("YZYZYZ");
|
||||
}
|
||||
|
||||
private AbstractStringAssert<?> assertThatBetterClassName(String firstName, String secondName) {
|
||||
return assertThat(getBetterClassName(firstName, secondName));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package jadx.core.utils;
|
||||
|
||||
import org.assertj.core.api.AbstractStringAssert;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static jadx.core.utils.BetterName.getBetterResourceName;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class TestGetBetterResourceName {
|
||||
|
||||
@Test
|
||||
public void testGoodNamesVsSyntheticNames() {
|
||||
assertThatBetterResourceName("color_main", "t0").isEqualTo("color_main");
|
||||
assertThatBetterResourceName("done", "oOo0oO0o").isEqualTo("done");
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests {@link BetterName#getBetterResourceName(String, String)} on equally good names.
|
||||
* In this case, according to the documentation, the method should return the first argument.
|
||||
*
|
||||
* @see BetterName#getBetterResourceName(String, String)
|
||||
*/
|
||||
@Test
|
||||
public void testEquallyGoodNames() {
|
||||
assertThatBetterResourceName("AAAA", "BBBB").isEqualTo("AAAA");
|
||||
assertThatBetterResourceName("BBBB", "AAAA").isEqualTo("BBBB");
|
||||
|
||||
assertThatBetterResourceName("Theme.AppCompat.Light", "Theme_AppCompat_Light")
|
||||
.isEqualTo("Theme.AppCompat.Light");
|
||||
assertThatBetterResourceName("Theme_AppCompat_Light", "Theme.AppCompat.Light")
|
||||
.isEqualTo("Theme_AppCompat_Light");
|
||||
}
|
||||
|
||||
private AbstractStringAssert<?> assertThatBetterResourceName(String firstName, String secondName) {
|
||||
return assertThat(getBetterResourceName(firstName, secondName));
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,14 @@ public class TestUsingSourceFileName extends SmaliTest {
|
||||
.containsOne("class b {");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIfBetterUseSourceName() {
|
||||
args.setUseSourceNameAsClassNameAlias(UseSourceNameAsClassNameAlias.IF_BETTER);
|
||||
assertThat(searchCls(loadFromSmaliFiles(), "b"))
|
||||
.code()
|
||||
.containsOne("class a {");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlwaysUseSourceName() {
|
||||
args.setUseSourceNameAsClassNameAlias(UseSourceNameAsClassNameAlias.ALWAYS);
|
||||
@@ -36,6 +44,17 @@ public class TestUsingSourceFileName extends SmaliTest {
|
||||
.containsOne("/* compiled from: a.java */");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIfBetterUseSourceNameWithDeobf() {
|
||||
args.setUseSourceNameAsClassNameAlias(UseSourceNameAsClassNameAlias.IF_BETTER);
|
||||
enableDeobfuscation();
|
||||
args.setDeobfuscationMinLength(100); // rename everything
|
||||
assertThat(searchCls(loadFromSmaliFiles(), "b"))
|
||||
.code()
|
||||
.containsOne("class a {")
|
||||
.containsOne("/* compiled from: a.java */");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlwaysUseSourceNameWithDeobf() {
|
||||
args.setUseSourceNameAsClassNameAlias(UseSourceNameAsClassNameAlias.ALWAYS);
|
||||
@@ -66,9 +85,9 @@ public class TestUsingSourceFileName extends SmaliTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeprecatedUseSourceNameWithDeobf() {
|
||||
public void testDeprecatedDontUseSourceNameWithDeobf() {
|
||||
// noinspection deprecation
|
||||
args.setUseSourceNameAsClassAlias(true);
|
||||
args.setUseSourceNameAsClassAlias(false);
|
||||
enableDeobfuscation();
|
||||
args.setDeobfuscationMinLength(100); // rename everything
|
||||
assertThat(searchCls(loadFromSmaliFiles(), "b"))
|
||||
@@ -76,4 +95,16 @@ public class TestUsingSourceFileName extends SmaliTest {
|
||||
.containsOne("class C0000b {")
|
||||
.containsOne("/* compiled from: a.java */");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeprecatedUseSourceNameWithDeobf() {
|
||||
// noinspection deprecation
|
||||
args.setUseSourceNameAsClassAlias(true);
|
||||
enableDeobfuscation();
|
||||
args.setDeobfuscationMinLength(100); // rename everything
|
||||
assertThat(searchCls(loadFromSmaliFiles(), "b"))
|
||||
.code()
|
||||
.containsOne("class a {")
|
||||
.containsOne("/* compiled from: a.java */");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user