Skip to content

Populate default methods, when interfaces are in classpath and not in input directory #136

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class ClassAnalyzer {
private final Map<Type, ClassInfo> classes = new HashMap<>();
private final Map<MethodRef, MethodRef> relocatedMethods = new HashMap<>();
private final Map<MethodRef, MethodRef> renamedLambdaMethods = new HashMap<>();
private final LibraryInterfaces libraryInterfaces = new LibraryInterfaces();

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

@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
libraryInterfaces.addRequiredInterfaces(interfaces);
this.owner = name;
}

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

@Override
Expand Down Expand Up @@ -126,6 +129,10 @@ public MethodVisitor visitMethod(int access, String name, String desc, String si
}, ClassReader.SKIP_CODE);
}

public LibraryInterfaces getLibraryInterfaces() {
return libraryInterfaces;
}

private static boolean isDefaultMethod(int access) {
return !isAbstractMethod(access)
&& !isStaticMethod(access)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public static void run(Config config) throws Throwable {

ClassAnalyzer analyzer = new ClassAnalyzer();
OutputDirectory outputDirectory = new OutputDirectory(outputDir);
outputDirectory.setClassNamePredicate(analyzer.getLibraryInterfaces().getAcceptedPredicate());
Transformers transformers = new Transformers(bytecodeVersion, defaultMethodsEnabled, analyzer);
LambdaClassSaver lambdaClassSaver = new LambdaClassSaver(outputDirectory, transformers);

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

// Because Transformers.backportLambdaClass() analyzes the lambda class,
// adding it to the analyzer's list of classes, we must take care to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@

import java.io.IOException;
import java.nio.file.*;
import java.util.function.Predicate;

public class OutputDirectory {

private final Path outputDir;
private Predicate<String> classNamePredicate;

public OutputDirectory(Path outputDir) {
this.outputDir = outputDir;
Expand All @@ -22,13 +24,20 @@ public void writeClass(byte[] bytecode) throws IOException {
return;
}
ClassReader cr = new ClassReader(bytecode);
Path relativePath = outputDir.getFileSystem().getPath(cr.getClassName() + ".class");
writeFile(relativePath, bytecode);
String classname = cr.getClassName();
if (classNamePredicate == null || classNamePredicate.test(classname)) {
Path relativePath = outputDir.getFileSystem().getPath(classname + ".class");
writeFile(relativePath, bytecode);
}
}

public void writeFile(Path relativePath, byte[] content) throws IOException {
Path outputFile = outputDir.resolve(relativePath);
Files.createDirectories(outputFile.getParent());
Files.write(outputFile, content);
}

public void setClassNamePredicate(Predicate<String> classNamePredicate) {
this.classNamePredicate = classNamePredicate;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// Copyright © 2016 Panayotis Katsaloulis <www.panayotis.com>
// 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.interfaces;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.function.Predicate;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

public class LibraryInterfaces {

private final Collection<String> requiredInterfaces = new HashSet<>();
private final Collection<String> inputDirInterfaces = new HashSet<>();
private final Collection<String> resolvedInterfaces = new HashSet<>();

public void addRequiredInterfaces(String[] interfaceNames) {
requiredInterfaces.addAll(Arrays.asList(interfaceNames));
}

public void addFoundInterface(String interfaceName) {
inputDirInterfaces.add(interfaceName);
}

public Collection<byte[]> getMissingInterfaces(List<Path> classpath) {
Collection<String> missingInterfaces = new HashSet<>(requiredInterfaces);
missingInterfaces.removeAll(inputDirInterfaces);
Collection<String> justFound = new HashSet<>();
Collection<byte[]> result = new HashSet<>();

for (Path path : classpath) {
if (Files.isDirectory(path))
for (String missingName : missingInterfaces) {
Path possibleTarget = path.resolve(missingName + ".class");
if (Files.isRegularFile(possibleTarget))
try {
result.add(Files.readAllBytes(possibleTarget));
justFound.add(missingName);
} catch (IOException ex) {
}
}
else if (Files.isRegularFile(path) && path.getFileName().toString().toLowerCase().endsWith(".jar")) {
JarFile jar = null;
try {
jar = new JarFile(path.toFile());
} catch (IOException ex) {
}
if (jar != null)
for (String missingName : missingInterfaces) {
JarEntry entry = jar.getJarEntry(missingName + ".class");
if (entry != null)
try (InputStream inputStream = jar.getInputStream(entry)) {
byte[] bytes = getBytes(inputStream);
if (bytes != null)
result.add(bytes);
justFound.add(missingName);
} catch (IOException ex) {
}
}
}
missingInterfaces.removeAll(justFound);
resolvedInterfaces.addAll(justFound);
justFound.clear();
}
return result;
}

public Predicate<String> getAcceptedPredicate() {
return className -> {
if (className.endsWith("$"))
className = className.substring(0, className.length() - 1);
/**
* Default implementation classes will be emitted only when this
* class appears in the current classes location, not inside the
* library classpath. It is the library responsibility to provide
* default implementation classes for itself. Thus the
* implementation classes will be emitted only once, when the
* library is (possibly) downgraded, and not every time the library
* is consumed by any other project.
*/
return !resolvedInterfaces.contains(className);
};
}

private byte[] getBytes(InputStream in) {
ByteArrayOutputStream out = new ByteArrayOutputStream(100);
int value;
try {
while ((value = in.read()) >= 0)
out.write(value);
} catch (IOException ex) {
return null;
}
return out.toByteArray();
}

}