Skip to content

Commit 376b155

Browse files
Add --method-to-decompile option (#519)
1 parent 61291af commit 376b155

File tree

7 files changed

+249
-43
lines changed

7 files changed

+249
-43
lines changed

src/org/jetbrains/java/decompiler/main/ClassWriter.java

Lines changed: 58 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -415,64 +415,74 @@ public void writeClass(ClassNode node, TextBuffer buffer, int indent) {
415415
hasContent.set(true);
416416
};
417417

418+
String methodToDecompile = (String) DecompilerContext.getProperty(IFernflowerPreferences.METHOD_TO_DECOMPILE);
419+
418420
// fields
419-
List<StructRecordComponent> components = cl.getRecordComponents();
421+
if (methodToDecompile.isEmpty()) {
422+
List<StructRecordComponent> components = cl.getRecordComponents();
420423

421-
List<StructField> enumFields = new ArrayList<>();
422-
List<StructField> nonEnumFields = new ArrayList<>();
424+
List<StructField> enumFields = new ArrayList<>();
425+
List<StructField> nonEnumFields = new ArrayList<>();
423426

424-
for (StructField fd : cl.getFields()) {
425-
boolean isEnum = fd.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM);
426-
if (isEnum) {
427-
enumFields.add(fd);
428-
} else {
429-
nonEnumFields.add(fd);
427+
for (StructField fd : cl.getFields()) {
428+
boolean isEnum = fd.hasModifier(CodeConstants.ACC_ENUM) && DecompilerContext.getOption(IFernflowerPreferences.DECOMPILE_ENUM);
429+
if (isEnum) {
430+
enumFields.add(fd);
431+
} else {
432+
nonEnumFields.add(fd);
433+
}
430434
}
431-
}
432435

433-
boolean enums = false;
434-
for (StructField fd : enumFields) {
435-
if (enums) {
436-
buffer.append(',').appendLineSeparator();
437-
}
438-
enums = true;
436+
boolean enums = false;
437+
for (StructField fd : enumFields) {
438+
if (enums) {
439+
buffer.append(',').appendLineSeparator();
440+
}
441+
enums = true;
439442

440-
haveContent.run();
441-
writeField(buffer, indent, fd, wrapper);
442-
}
443+
haveContent.run();
444+
writeField(buffer, indent, fd, wrapper);
445+
}
443446

444-
if (enums) {
445-
buffer.append(';').appendLineSeparator();
446-
}
447+
if (enums) {
448+
buffer.append(';').appendLineSeparator();
449+
}
447450

448-
for (StructField fd : nonEnumFields) {
449-
boolean hide = fd.isSynthetic() && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC) ||
450-
wrapper.getHiddenMembers().contains(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor()));
451-
if (hide) continue;
451+
for (StructField fd : nonEnumFields) {
452+
boolean hide = fd.isSynthetic() && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC) ||
453+
wrapper.getHiddenMembers().contains(InterpreterUtil.makeUniqueKey(fd.getName(), fd.getDescriptor()));
454+
if (hide) continue;
452455

453-
if (components != null && fd.getAccessFlags() == (CodeConstants.ACC_FINAL | CodeConstants.ACC_PRIVATE) &&
456+
if (components != null && fd.getAccessFlags() == (CodeConstants.ACC_FINAL | CodeConstants.ACC_PRIVATE) &&
454457
components.stream().anyMatch(c -> c.getName().equals(fd.getName()) && c.getDescriptor().equals(fd.getDescriptor()))) {
455-
// Record component field: skip it
456-
continue;
457-
}
458+
// Record component field: skip it
459+
continue;
460+
}
458461

459-
if (enums) {
460-
// Add an extra line break between enums and non-enum fields
461-
buffer.appendLineSeparator();
462-
enums = false;
463-
}
462+
if (enums) {
463+
// Add an extra line break between enums and non-enum fields
464+
buffer.appendLineSeparator();
465+
enums = false;
466+
}
464467

465-
haveContent.run();
466-
writeField(buffer, indent, fd, wrapper);
468+
haveContent.run();
469+
writeField(buffer, indent, fd, wrapper);
470+
}
467471
}
468472

469473
// methods
470474
VBStyleCollection<StructMethod, String> methods = cl.getMethods();
471475
for (int i = 0; i < methods.size(); i++) {
472476
StructMethod mt = methods.get(i);
473-
boolean hide = mt.isSynthetic() && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC) ||
474-
mt.hasModifier(CodeConstants.ACC_BRIDGE) && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_BRIDGE) ||
475-
wrapper.getHiddenMembers().contains(InterpreterUtil.makeUniqueKey(mt.getName(), mt.getDescriptor()));
477+
boolean hide;
478+
if (methodToDecompile.isEmpty() || (node.type != ClassNode.Type.ROOT && node.type != ClassNode.Type.MEMBER)) {
479+
hide = mt.isSynthetic() && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC) ||
480+
mt.hasModifier(CodeConstants.ACC_BRIDGE) && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_BRIDGE) ||
481+
wrapper.getHiddenMembers().contains(InterpreterUtil.makeUniqueKey(mt.getName(), mt.getDescriptor()));
482+
} else {
483+
hide = !methodToDecompile.equals(cl.qualifiedName + "." + mt.getName() + mt.getDescriptor()) &&
484+
(node.type != ClassNode.Type.ROOT || !methodToDecompile.equals(mt.getName() + mt.getDescriptor()));
485+
}
476486
if (hide) continue;
477487

478488
TextBuffer methodBuffer = new TextBuffer();
@@ -490,9 +500,14 @@ public void writeClass(ClassNode node, TextBuffer buffer, int indent) {
490500
for (ClassNode inner : node.nested) {
491501
if (inner.type == ClassNode.Type.MEMBER) {
492502
StructClass innerCl = inner.classStruct;
493-
boolean isSynthetic = (inner.access & CodeConstants.ACC_SYNTHETIC) != 0 || innerCl.isSynthetic();
494-
boolean hide = isSynthetic && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC) ||
495-
wrapper.getHiddenMembers().contains(innerCl.qualifiedName);
503+
boolean hide;
504+
if (methodToDecompile.isEmpty()) {
505+
boolean isSynthetic = (inner.access & CodeConstants.ACC_SYNTHETIC) != 0 || innerCl.isSynthetic();
506+
hide = isSynthetic && DecompilerContext.getOption(IFernflowerPreferences.REMOVE_SYNTHETIC) ||
507+
wrapper.getHiddenMembers().contains(innerCl.qualifiedName);
508+
} else {
509+
hide = !methodToDecompile.startsWith(innerCl.qualifiedName + ".");
510+
}
496511
if (hide) continue;
497512

498513
if (hasContent.get()) {

src/org/jetbrains/java/decompiler/main/extern/IFernflowerPreferences.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,11 @@ public interface IFernflowerPreferences {
408408
@Type(DecompilerOption.Type.BOOLEAN)
409409
String PRETTIFY_IFS = "prettify-ifs";
410410

411+
@Name("Method to decompile")
412+
@Description("Option to decompile a single method. Set to owner + \".\" + name + descriptor, e.g. foo/Bar.baz()V, or simply name + descriptor if you're only decompiling a single file and the method belongs to the root class.")
413+
@Type(DecompilerOption.Type.STRING)
414+
String METHOD_TO_DECOMPILE = "method-to-decompile";
415+
411416
Map<String, Object> DEFAULTS = getDefaults();
412417

413418
static Map<String, Object> getDefaults() {
@@ -483,6 +488,7 @@ static Map<String, Object> getDefaults() {
483488
defaults.put(EXCLUDED_CLASSES, "");
484489
defaults.put(VALIDATE_INNER_CLASSES_NAMES, "1");
485490
defaults.put(PRETTIFY_IFS, "1");
491+
defaults.put(METHOD_TO_DECOMPILE, "");
486492

487493
return Collections.unmodifiableMap(defaults);
488494
}

test/org/jetbrains/java/decompiler/SingleClassesTest.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,26 @@ public String getMethodDoc(StructClass structClass, StructMethod structMethod) {
154154
IFernflowerPreferences.LAMBDA_TO_ANONYMOUS_CLASS, "1",
155155
IFernflowerPreferences.VERIFY_PRE_POST_VARIABLE_MERGES, "1"
156156
);
157+
registerSet("Single method", this::registerSingleMethod,
158+
IFernflowerPreferences.BYTECODE_SOURCE_MAPPING, "1",
159+
IFernflowerPreferences.DUMP_ORIGINAL_LINES, "1",
160+
IFernflowerPreferences.DUMP_EXCEPTION_ON_ERROR, "0",
161+
IFernflowerPreferences.IGNORE_INVALID_BYTECODE, "1",
162+
IFernflowerPreferences.VERIFY_ANONYMOUS_CLASSES, "1",
163+
IFernflowerPreferences.INCLUDE_ENTIRE_CLASSPATH, "1",
164+
IFernflowerPreferences.VERIFY_PRE_POST_VARIABLE_MERGES, "1",
165+
IFernflowerPreferences.METHOD_TO_DECOMPILE, "test()V"
166+
);
167+
registerSet("Single method in inner class", this::registerSingleMethodInInnerClass,
168+
IFernflowerPreferences.BYTECODE_SOURCE_MAPPING, "1",
169+
IFernflowerPreferences.DUMP_ORIGINAL_LINES, "1",
170+
IFernflowerPreferences.DUMP_EXCEPTION_ON_ERROR, "0",
171+
IFernflowerPreferences.IGNORE_INVALID_BYTECODE, "1",
172+
IFernflowerPreferences.VERIFY_ANONYMOUS_CLASSES, "1",
173+
IFernflowerPreferences.INCLUDE_ENTIRE_CLASSPATH, "1",
174+
IFernflowerPreferences.VERIFY_PRE_POST_VARIABLE_MERGES, "1",
175+
IFernflowerPreferences.METHOD_TO_DECOMPILE, "pkg/TestSingleMethodInInnerClass$Inner.test()V"
176+
);
157177
// TODO: user renamer class test
158178
}
159179

@@ -916,4 +936,12 @@ private void registerLambdaToAnonymousClass() {
916936
register(JAVA_8, "TestLambdaToAnonymousClass");
917937
register(JAVA_8, "TestLambdaToAnonymousClass2");
918938
}
939+
940+
private void registerSingleMethod() {
941+
register(JAVA_8, "TestSingleMethod");
942+
}
943+
944+
private void registerSingleMethodInInnerClass() {
945+
register(JAVA_8, "TestSingleMethodInInnerClass");
946+
}
919947
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package pkg;
2+
3+
public class TestSingleMethod {
4+
public void test() {
5+
System.out.println("Hello from test");// 15
6+
new Object() {
7+
void foo() {
8+
System.out.println("Hello from anonymous class");// 18
9+
}// 19
10+
};
11+
12+
class Local {
13+
void foo() {
14+
System.out.println("Hello from local class");// 24
15+
}// 25
16+
}
17+
18+
}// 27
19+
}
20+
21+
class 'pkg/TestSingleMethod' {
22+
method 'test ()V' {
23+
0 4
24+
1 4
25+
2 4
26+
3 4
27+
4 4
28+
5 4
29+
6 4
30+
7 4
31+
11 17
32+
}
33+
}
34+
35+
class 'pkg/TestSingleMethod$1' {
36+
method 'foo ()V' {
37+
0 7
38+
1 7
39+
2 7
40+
3 7
41+
4 7
42+
5 7
43+
6 7
44+
7 7
45+
8 8
46+
}
47+
}
48+
49+
class 'pkg/TestSingleMethod$1Local' {
50+
method 'foo ()V' {
51+
0 13
52+
1 13
53+
2 13
54+
3 13
55+
4 13
56+
5 13
57+
6 13
58+
7 13
59+
8 14
60+
}
61+
}
62+
63+
Lines mapping:
64+
15 <-> 5
65+
18 <-> 8
66+
19 <-> 9
67+
24 <-> 14
68+
25 <-> 15
69+
27 <-> 18
70+
Not mapped:
71+
16
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package pkg;
2+
3+
public class TestSingleMethodInInnerClass {
4+
static class Inner {
5+
void test() {
6+
System.out.println("Hello from inner class");// 10
7+
}// 11
8+
}
9+
}
10+
11+
class 'pkg/TestSingleMethodInInnerClass$Inner' {
12+
method 'test ()V' {
13+
0 5
14+
1 5
15+
2 5
16+
3 5
17+
4 5
18+
5 5
19+
6 5
20+
7 5
21+
8 6
22+
}
23+
}
24+
25+
Lines mapping:
26+
10 <-> 6
27+
11 <-> 7
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package pkg;
2+
3+
public class TestSingleMethod {
4+
public double field = Math.random();
5+
6+
static {
7+
System.out.println("Hello from static block");
8+
}
9+
10+
public TestSingleMethod() {
11+
System.out.println("Hello from constructor");
12+
}
13+
14+
public void test() {
15+
System.out.println("Hello from test");
16+
new Object() {
17+
void foo() {
18+
System.out.println("Hello from anonymous class");
19+
}
20+
};
21+
22+
class Local {
23+
void foo() {
24+
System.out.println("Hello from local class");
25+
}
26+
}
27+
}
28+
29+
public void test(int i) {
30+
System.out.println("Hello from test with int arg");
31+
}
32+
33+
public void test2() {
34+
System.out.println("Hello from test2");
35+
}
36+
37+
static class Inner {
38+
void foo() {
39+
System.out.println("Hello from inner class");
40+
}
41+
}
42+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package pkg;
2+
3+
public class TestSingleMethodInInnerClass {
4+
void test() {
5+
System.out.println("Hello from outer class");
6+
}
7+
8+
static class Inner {
9+
void test() {
10+
System.out.println("Hello from inner class");
11+
}
12+
13+
void test2() {
14+
System.out.println("Hello from inner class 2");
15+
}
16+
}
17+
}

0 commit comments

Comments
 (0)