Skip to content

Commit dd8cd8f

Browse files
committed
Populate default methods, when interfaces are in classpath and not in input directory
1 parent 6039108 commit dd8cd8f

File tree

4 files changed

+126
-2
lines changed

4 files changed

+126
-2
lines changed

retrolambda/src/main/java/net/orfjackal/retrolambda/ClassAnalyzer.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public class ClassAnalyzer {
2020
private final Map<Type, ClassInfo> classes = new HashMap<>();
2121
private final Map<MethodRef, MethodRef> relocatedMethods = new HashMap<>();
2222
private final Map<MethodRef, MethodRef> renamedLambdaMethods = new HashMap<>();
23+
private final LibraryInterfaces libraryInterfaces = new LibraryInterfaces();
2324

2425
public void analyze(byte[] bytecode) {
2526
analyze(new ClassReader(bytecode));
@@ -43,6 +44,7 @@ private void analyzeClass(ClassInfo c, ClassReader cr) {
4344

4445
@Override
4546
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
47+
libraryInterfaces.addRequiredInterfaces(interfaces);
4648
this.owner = name;
4749
}
4850

@@ -73,6 +75,7 @@ private void analyzeInterface(ClassInfo c, ClassReader cr) {
7375
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
7476
this.owner = name;
7577
this.companion = name + "$";
78+
libraryInterfaces.addFoundInterface(name);
7679
}
7780

7881
@Override
@@ -126,6 +129,10 @@ public MethodVisitor visitMethod(int access, String name, String desc, String si
126129
}, ClassReader.SKIP_CODE);
127130
}
128131

132+
public LibraryInterfaces getLibraryInterfaces() {
133+
return libraryInterfaces;
134+
}
135+
129136
private static boolean isDefaultMethod(int access) {
130137
return !isAbstractMethod(access)
131138
&& !isStaticMethod(access)

retrolambda/src/main/java/net/orfjackal/retrolambda/Retrolambda.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ public static void run(Config config) throws Throwable {
4646

4747
ClassAnalyzer analyzer = new ClassAnalyzer();
4848
OutputDirectory outputDirectory = new OutputDirectory(outputDir);
49+
outputDirectory.setClassNamePredicate(analyzer.getLibraryInterfaces().getAcceptedPredicate(defaultMethodsEnabled));
4950
Transformers transformers = new Transformers(bytecodeVersion, defaultMethodsEnabled, analyzer);
5051
LambdaClassSaver lambdaClassSaver = new LambdaClassSaver(outputDirectory, transformers);
5152

@@ -67,6 +68,8 @@ protected void visitResource(Path relativePath, byte[] content) throws IOExcepti
6768
outputDirectory.writeFile(relativePath, content);
6869
}
6970
});
71+
for(byte[] interf : analyzer.getLibraryInterfaces().getMissingInterfaces(classpath))
72+
analyzer.analyze(interf);
7073

7174
// Because Transformers.backportLambdaClass() analyzes the lambda class,
7275
// adding it to the analyzer's list of classes, we must take care to

retrolambda/src/main/java/net/orfjackal/retrolambda/files/OutputDirectory.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88

99
import java.io.IOException;
1010
import java.nio.file.*;
11+
import java.util.function.Predicate;
1112

1213
public class OutputDirectory {
1314

1415
private final Path outputDir;
16+
private Predicate<String> classNamePredicate;
1517

1618
public OutputDirectory(Path outputDir) {
1719
this.outputDir = outputDir;
@@ -22,13 +24,20 @@ public void writeClass(byte[] bytecode) throws IOException {
2224
return;
2325
}
2426
ClassReader cr = new ClassReader(bytecode);
25-
Path relativePath = outputDir.getFileSystem().getPath(cr.getClassName() + ".class");
26-
writeFile(relativePath, bytecode);
27+
String classname = cr.getClassName();
28+
if (classNamePredicate == null || classNamePredicate.test(classname)) {
29+
Path relativePath = outputDir.getFileSystem().getPath(classname + ".class");
30+
writeFile(relativePath, bytecode);
31+
}
2732
}
2833

2934
public void writeFile(Path relativePath, byte[] content) throws IOException {
3035
Path outputFile = outputDir.resolve(relativePath);
3136
Files.createDirectories(outputFile.getParent());
3237
Files.write(outputFile, content);
3338
}
39+
40+
public void setClassNamePredicate(Predicate<String> classNamePredicate) {
41+
this.classNamePredicate = classNamePredicate;
42+
}
3443
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Copyright © 2016 Panayotis Katsaloulis <www.panayotis.com>
2+
// This software is released under the Apache License 2.0.
3+
// The license text is at http://www.apache.org/licenses/LICENSE-2.0
4+
package net.orfjackal.retrolambda.interfaces;
5+
6+
import java.io.ByteArrayOutputStream;
7+
import java.io.IOException;
8+
import java.io.InputStream;
9+
import java.nio.file.Files;
10+
import java.nio.file.Path;
11+
import java.util.Arrays;
12+
import java.util.Collection;
13+
import java.util.HashSet;
14+
import java.util.List;
15+
import java.util.function.Predicate;
16+
import java.util.jar.JarEntry;
17+
import java.util.jar.JarFile;
18+
19+
public class LibraryInterfaces {
20+
21+
private final Collection<String> requiredInterfaces = new HashSet<>();
22+
private final Collection<String> inputDirInterfaces = new HashSet<>();
23+
private final Collection<String> resolvedInterfaces = new HashSet<>();
24+
25+
public void addRequiredInterfaces(String[] interfaceNames) {
26+
requiredInterfaces.addAll(Arrays.asList(interfaceNames));
27+
}
28+
29+
public void addFoundInterface(String interfaceName) {
30+
inputDirInterfaces.add(interfaceName);
31+
}
32+
33+
public Collection<byte[]> getMissingInterfaces(List<Path> classpath) {
34+
Collection<String> missingInterfaces = new HashSet<>(requiredInterfaces);
35+
missingInterfaces.removeAll(inputDirInterfaces);
36+
Collection<String> justFound = new HashSet<>();
37+
Collection<byte[]> result = new HashSet<>();
38+
39+
for (Path path : classpath) {
40+
if (Files.isDirectory(path))
41+
for (String missingName : missingInterfaces) {
42+
Path possibleTarget = path.resolve(missingName + ".class");
43+
if (Files.isRegularFile(possibleTarget))
44+
try {
45+
result.add(Files.readAllBytes(possibleTarget));
46+
justFound.add(missingName);
47+
} catch (IOException ex) {
48+
}
49+
}
50+
else if (Files.isRegularFile(path) && path.getFileName().toString().toLowerCase().endsWith(".jar")) {
51+
JarFile jar = null;
52+
try {
53+
jar = new JarFile(path.toFile());
54+
} catch (IOException ex) {
55+
}
56+
if (jar != null)
57+
for (String missingName : missingInterfaces) {
58+
JarEntry entry = jar.getJarEntry(missingName + ".class");
59+
if (entry != null)
60+
try (InputStream inputStream = jar.getInputStream(entry)) {
61+
byte[] bytes = getBytes(inputStream);
62+
if (bytes != null)
63+
result.add(bytes);
64+
justFound.add(missingName);
65+
} catch (IOException ex) {
66+
}
67+
}
68+
}
69+
missingInterfaces.removeAll(justFound);
70+
resolvedInterfaces.addAll(justFound);
71+
justFound.clear();
72+
}
73+
return result;
74+
}
75+
76+
public Predicate<String> getAcceptedPredicate(boolean defaultMethodsEnabled) {
77+
return className -> {
78+
if (className.endsWith("$"))
79+
className = className.substring(0, className.length() - 1);
80+
/**
81+
* Default implementation classes will be emitted only when this
82+
* class appears in the current classes location, not inside the
83+
* library classpath. It is the library responsibility to provide
84+
* default implementation classes for itself. Thus the
85+
* implementation classes will be emitted only once, when the
86+
* library is (possibly) downgraded, and not every time the library
87+
* is consumed by any other project.
88+
*/
89+
return !resolvedInterfaces.contains(className);
90+
};
91+
}
92+
93+
private byte[] getBytes(InputStream in) {
94+
ByteArrayOutputStream out = new ByteArrayOutputStream(100);
95+
int value;
96+
try {
97+
while ((value = in.read()) >= 0)
98+
out.write(value);
99+
} catch (IOException ex) {
100+
return null;
101+
}
102+
return out.toByteArray();
103+
}
104+
105+
}

0 commit comments

Comments
 (0)