/
Transformers.java
127 lines (109 loc) · 5.19 KB
/
Transformers.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
// Copyright © 2013-2015 Esko Luontola <www.orfjackal.net>
// This software is released under the Apache License 2.0.
// The license text is at http://www.apache.org/licenses/LICENSE-2.0
package net.orfjackal.retrolambda;
import net.orfjackal.retrolambda.interfaces.*;
import net.orfjackal.retrolambda.lambdas.*;
import net.orfjackal.retrolambda.trywithresources.SwallowSuppressedExceptions;
import org.objectweb.asm.*;
import org.objectweb.asm.tree.ClassNode;
import java.util.*;
import java.util.function.Consumer;
public class Transformers {
private final int targetVersion;
private final boolean defaultMethodsEnabled;
private final ClassHierarchyAnalyzer analyzer;
public Transformers(int targetVersion, boolean defaultMethodsEnabled, ClassHierarchyAnalyzer analyzer) {
this.targetVersion = targetVersion;
this.defaultMethodsEnabled = defaultMethodsEnabled;
this.analyzer = analyzer;
}
public byte[] backportLambdaClass(ClassReader reader) {
return transform(reader, (next) -> {
if (defaultMethodsEnabled) {
// Lambda classes are generated dynamically, so they were not
// part of the original analytics and must be analyzed now,
// in case they implement interfaces with default methods.
analyzer.analyze(reader);
next = new UpdateRelocatedMethodInvocations(next, analyzer);
next = new AddMethodDefaultImplementations(next, analyzer);
} else {
next = new UpdateRelocatedMethodInvocations(next, analyzer); // needed for lambdas in an interface's constant initializer
}
next = new BackportLambdaClass(next);
return next;
});
}
public byte[] backportClass(ClassReader reader) {
return transform(reader, (next) -> {
if (defaultMethodsEnabled) {
next = new UpdateRelocatedMethodInvocations(next, analyzer);
next = new AddMethodDefaultImplementations(next, analyzer);
}
next = new BackportLambdaInvocations(next);
return next;
});
}
public List<byte[]> backportInterface(ClassReader reader) {
// The lambdas must be backported only once, because bad things will happen if a lambda
// is called by different class name in the interface and its companion class, and then
// the wrong one of them is written to disk last.
ClassNode lambdasBackported = new ClassNode();
ClassVisitor next = lambdasBackported;
next = new BackportLambdaInvocations(next);
reader.accept(next, 0);
List<byte[]> results = new ArrayList<>();
results.add(backportInterface2(lambdasBackported));
results.addAll(extractInterfaceCompanion(lambdasBackported));
return results;
}
private byte[] backportInterface2(ClassNode clazz) {
return transform(clazz, (next) -> {
if (defaultMethodsEnabled) {
next = new RemoveStaticMethods(next);
next = new RemoveDefaultMethodBodies(next);
next = new UpdateRelocatedMethodInvocations(next, analyzer);
} else {
// XXX: It would be better to remove only those static methods which are lambda implementation methods,
// but that would either require the use of naming patterns (not guaranteed to work with every Java compiler)
// or passing around information that which relocated static methods are because of lambdas.
next = new RemoveStaticMethods(next); // needed for lambdas in an interface's constant initializer
next = new WarnAboutDefaultAndStaticMethods(next);
}
next = new RemoveBridgeMethods(next);
return next;
});
}
private List<byte[]> extractInterfaceCompanion(ClassNode clazz) {
Optional<Type> companion = analyzer.getCompanionClass(Type.getObjectType(clazz.name));
if (!companion.isPresent()) {
return Collections.emptyList();
}
return Arrays.asList(transform(clazz, (next) -> {
next = new UpdateRelocatedMethodInvocations(next, analyzer);
next = new ExtractInterfaceCompanionClass(next, companion.get());
return next;
}));
}
private byte[] transform(ClassNode node, ClassVisitorChain chain) {
return transform(node::accept, chain);
}
private byte[] transform(ClassReader reader, ClassVisitorChain chain) {
return transform(cv -> reader.accept(cv, 0), chain);
}
private byte[] transform(Consumer<ClassVisitor> reader, ClassVisitorChain chain) {
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_MAXS);
ClassVisitor next = writer;
next = new LowerBytecodeVersion(next, targetVersion);
if (targetVersion < Opcodes.V1_7) {
next = new SwallowSuppressedExceptions(next);
}
next = new FixInvokeStaticOnInterfaceMethod(next);
next = chain.wrap(next);
reader.accept(next);
return writer.toByteArray();
}
private interface ClassVisitorChain {
ClassVisitor wrap(ClassVisitor next);
}
}