diff --git a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java index 7b4330535..e664162b9 100644 --- a/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java +++ b/jadx-core/src/main/java/jadx/core/codegen/ClassGen.java @@ -44,7 +44,7 @@ public class ClassGen { private final boolean fallback; private final Set imports = new HashSet(); - private int clsDeclLine = 0; + private int clsDeclLine; public ClassGen(ClassNode cls, ClassGen parentClsGen, boolean fallback) { this.cls = cls; @@ -103,7 +103,9 @@ public class ClassGen { if (af.isInterface()) { af = af.remove(AccessFlags.ACC_ABSTRACT); } else if (af.isEnum()) { - af = af.remove(AccessFlags.ACC_FINAL).remove(AccessFlags.ACC_ABSTRACT); + af = af.remove(AccessFlags.ACC_FINAL) + .remove(AccessFlags.ACC_ABSTRACT) + .remove(AccessFlags.ACC_STATIC); } annotationGen.addForClass(clsCode); @@ -202,32 +204,53 @@ public class ClassGen { } private void addInnerClasses(CodeWriter code, ClassNode cls) throws CodegenException { + for (ClassNode innerCls : cls.getInnerClasses()) { + if (innerCls.contains(AFlag.DONT_GENERATE) + || innerCls.isAnonymous()) { + continue; + } + ClassGen inClGen = new ClassGen(innerCls, getParentGen(), fallback); + code.newLine(); + inClGen.addClassCode(code); + imports.addAll(inClGen.getImports()); + } + } + + private boolean isInnerClassesPresents() { for (ClassNode innerCls : cls.getInnerClasses()) { if (!innerCls.isAnonymous()) { - ClassGen inClGen = new ClassGen(innerCls, getParentGen(), fallback); - code.newLine(); - inClGen.addClassCode(code); - imports.addAll(inClGen.getImports()); + return true; } } + return false; } private void addMethods(CodeWriter code) { for (MethodNode mth : cls.getMethods()) { - if (!mth.contains(AFlag.DONT_GENERATE)) { - try { - if (code.getLine() != clsDeclLine) { - code.newLine(); - } - addMethod(code, mth); - } catch (Exception e) { - String msg = ErrorsCounter.methodError(mth, "Method generation error", e); - code.startLine("/* " + msg + CodeWriter.NL + Utils.getStackTrace(e) + " */"); - } + if (mth.contains(AFlag.DONT_GENERATE)) { + continue; + } + if (code.getLine() != clsDeclLine) { + code.newLine(); + } + try { + addMethod(code, mth); + } catch (Exception e) { + String msg = ErrorsCounter.methodError(mth, "Method generation error", e); + code.startLine("/* " + msg + CodeWriter.NL + Utils.getStackTrace(e) + " */"); } } } + private boolean isMethodsPresents() { + for (MethodNode mth : cls.getMethods()) { + if (!mth.contains(AFlag.DONT_GENERATE)) { + return true; + } + } + return false; + } + private void addMethod(CodeWriter code, MethodNode mth) throws CodegenException { MethodGen mthGen = new MethodGen(this, mth); if (mth.getAccessFlags().isAbstract() || mth.getAccessFlags().isNative()) { @@ -284,6 +307,15 @@ public class ClassGen { } } + private boolean isFieldsPresents() { + for (FieldNode field : cls.getFields()) { + if (!field.contains(AFlag.DONT_GENERATE)) { + return true; + } + } + return false; + } + private void addEnumFields(CodeWriter code) throws CodegenException { EnumClassAttr enumFields = cls.get(AType.ENUM_CLASS); if (enumFields == null) { @@ -310,21 +342,23 @@ public class ClassGen { code.add(')'); } if (f.getCls() != null) { + code.add(' '); new ClassGen(f.getCls(), this, fallback).addClassBody(code); } if (it.hasNext()) { code.add(','); } } - if (enumFields.getFields().isEmpty()) { - code.startLine(); + if (isMethodsPresents() || isFieldsPresents() || isInnerClassesPresents()) { + if (enumFields.getFields().isEmpty()) { + code.startLine(); + } + code.add(';'); } - code.add(';'); - code.newLine(); } public void useType(CodeWriter code, ArgType type) { - final PrimitiveType stype = type.getPrimitiveType(); + PrimitiveType stype = type.getPrimitiveType(); if (stype == null) { code.add(type.toString()); } else if (stype == PrimitiveType.OBJECT) { diff --git a/jadx-core/src/test/java/jadx/tests/internal/enums/TestEnums.java b/jadx-core/src/test/java/jadx/tests/internal/enums/TestEnums.java new file mode 100644 index 000000000..51d89a8ea --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/internal/enums/TestEnums.java @@ -0,0 +1,71 @@ +package jadx.tests.internal.enums; + +import jadx.api.InternalJadxTest; +import jadx.core.dex.nodes.ClassNode; + +import org.junit.Test; + +import static jadx.tests.utils.JadxMatchers.containsLines; +import static org.junit.Assert.assertThat; + +public class TestEnums extends InternalJadxTest { + + public static class TestCls { + + public enum EmptyEnum { + } + + public enum EmptyEnum2 { + ; + + public static void mth() { + } + } + + public enum Direction { + NORTH, + SOUTH, + EAST, + WEST + } + + public enum Singleton { + INSTANCE; + + public String test() { + return ""; + } + } + } + + @Test + public void test() { + ClassNode cls = getClassNode(TestCls.class); + String code = cls.getCode().toString(); + System.out.println(code); + + assertThat(code, containsLines(1, "public enum EmptyEnum {", "}")); + assertThat(code, containsLines(1, + "public enum EmptyEnum2 {", + indent(1) + ";", + "", + indent(1) + "public static void mth() {", + indent(1) + "}", + "}")); + + assertThat(code, containsLines(1, "public enum Direction {", + indent(1) + "NORTH,", + indent(1) + "SOUTH,", + indent(1) + "EAST,", + indent(1) + "WEST", + "}")); + + assertThat(code, containsLines(1, "public enum Singleton {", + indent(1) + "INSTANCE;", + "", + indent(1) + "public String test() {", + indent(2) + "return \"\";", + indent(1) + "}", + "}")); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/internal/enums/TestEnums2.java b/jadx-core/src/test/java/jadx/tests/internal/enums/TestEnums2.java new file mode 100644 index 000000000..e2d3d7a16 --- /dev/null +++ b/jadx-core/src/test/java/jadx/tests/internal/enums/TestEnums2.java @@ -0,0 +1,54 @@ +package jadx.tests.internal.enums; + +import jadx.api.InternalJadxTest; +import jadx.core.dex.nodes.ClassNode; +import jadx.tests.utils.JadxMatchers; + +import org.junit.Test; + +import static org.junit.Assert.assertThat; + +public class TestEnums2 extends InternalJadxTest { + + public static class TestCls { + + public enum Operation { + PLUS { + int apply(int x, int y) { + return x + y; + } + }, + MINUS { + int apply(int x, int y) { + return x - y; + } + }; + + abstract int apply(int x, int y); + } + } + + @Test + public void test() { + ClassNode cls = getClassNode(TestCls.class); + String code = cls.getCode().toString(); + System.out.println(code); + + assertThat(code, JadxMatchers.containsLines(1, + "public enum Operation {", + indent(1) + "PLUS {", + indent(2) + "int apply(int x, int y) {", + indent(3) + "return x + y;", + indent(2) + "}", + indent(1) + "},", + indent(1) + "MINUS {", + indent(2) + "int apply(int x, int y) {", + indent(3) + "return x - y;", + indent(2) + "}", + indent(1) + "};", + "", + indent(1) + "abstract int apply(int i, int i2);", + "}" + )); + } +} diff --git a/jadx-core/src/test/java/jadx/tests/utils/CountString.java b/jadx-core/src/test/java/jadx/tests/utils/CountString.java index 0b3714d7e..688666073 100644 --- a/jadx-core/src/test/java/jadx/tests/utils/CountString.java +++ b/jadx-core/src/test/java/jadx/tests/utils/CountString.java @@ -1,5 +1,8 @@ package jadx.tests.utils; +import jadx.api.TestUtils; + +import org.hamcrest.Description; import org.hamcrest.core.SubstringMatcher; public class CountString extends SubstringMatcher { @@ -13,21 +16,21 @@ public class CountString extends SubstringMatcher { @Override protected boolean evalSubstringOf(String string) { - return this.count == countStr(string, substring); + return this.count == count(string); } @Override protected String relationship() { - return "containing " + count + " occurrence of"; + return "containing <" + count + "> occurrence of"; } - private static int countStr(String string, String substring) { - int cnt = 0; - int idx = 0; - while ((idx = string.indexOf(substring, idx)) != -1) { - idx++; - cnt++; - } - return cnt; + @Override + public void describeMismatchSafely(String item, Description mismatchDescription) { + mismatchDescription.appendText("found ").appendValue(count(item)) + .appendText(" in \"").appendText(item).appendText("\""); + } + + private int count(String string) { + return TestUtils.count(string, substring); } } diff --git a/jadx-core/src/test/java/jadx/tests/utils/JadxMatchers.java b/jadx-core/src/test/java/jadx/tests/utils/JadxMatchers.java index 3c78731fb..92dfe33c5 100644 --- a/jadx-core/src/test/java/jadx/tests/utils/JadxMatchers.java +++ b/jadx-core/src/test/java/jadx/tests/utils/JadxMatchers.java @@ -1,5 +1,8 @@ package jadx.tests.utils; +import jadx.api.TestUtils; +import jadx.core.codegen.CodeWriter; + import org.hamcrest.Matcher; public class JadxMatchers { @@ -9,6 +12,27 @@ public class JadxMatchers { } public static Matcher containsOne(String substring) { - return new CountString(1, substring); + return countString(1, substring); + } + + public static Matcher containsLines(String... lines) { + StringBuilder sb = new StringBuilder(); + for (String line : lines) { + sb.append(line).append(CodeWriter.NL); + } + return countString(1, sb.toString()); + } + + public static Matcher containsLines(int commonIndent, String... lines) { + String indent = TestUtils.indent(commonIndent); + StringBuilder sb = new StringBuilder(); + for (String line : lines) { + if (!line.isEmpty()) { + sb.append(indent); + sb.append(line); + } + sb.append(CodeWriter.NL); + } + return countString(1, sb.toString()); } }