From f164c806b3c4911c1d047c1c58412c9c6e220dce Mon Sep 17 00:00:00 2001 From: Panayotis Katsaloulis Date: Mon, 23 Oct 2017 03:35:08 +0300 Subject: [PATCH] Populate default methods, when interfaces are in classpath and not in input directory --- .../orfjackal/retrolambda/ClassAnalyzer.java | 7 ++ .../orfjackal/retrolambda/Retrolambda.java | 3 + .../retrolambda/files/OutputDirectory.java | 13 ++- .../interfaces/LibraryInterfaces.java | 105 ++++++++++++++++++ 4 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 retrolambda/src/main/java/net/orfjackal/retrolambda/interfaces/LibraryInterfaces.java diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/ClassAnalyzer.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/ClassAnalyzer.java index fc92732a..f957d15a 100644 --- a/retrolambda/src/main/java/net/orfjackal/retrolambda/ClassAnalyzer.java +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/ClassAnalyzer.java @@ -20,6 +20,7 @@ public class ClassAnalyzer { private final Map classes = new HashMap<>(); private final Map relocatedMethods = new HashMap<>(); private final Map renamedLambdaMethods = new HashMap<>(); + private final LibraryInterfaces libraryInterfaces = new LibraryInterfaces(); public void analyze(byte[] bytecode) { analyze(new ClassReader(bytecode)); @@ -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; } @@ -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 @@ -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) diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/Retrolambda.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/Retrolambda.java index 5e49512f..7e4f2035 100644 --- a/retrolambda/src/main/java/net/orfjackal/retrolambda/Retrolambda.java +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/Retrolambda.java @@ -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); @@ -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 diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/files/OutputDirectory.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/files/OutputDirectory.java index 88cec60e..676cbc31 100644 --- a/retrolambda/src/main/java/net/orfjackal/retrolambda/files/OutputDirectory.java +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/files/OutputDirectory.java @@ -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 classNamePredicate; public OutputDirectory(Path outputDir) { this.outputDir = outputDir; @@ -22,8 +24,11 @@ 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 { @@ -31,4 +36,8 @@ public void writeFile(Path relativePath, byte[] content) throws IOException { Files.createDirectories(outputFile.getParent()); Files.write(outputFile, content); } + + public void setClassNamePredicate(Predicate classNamePredicate) { + this.classNamePredicate = classNamePredicate; + } } diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/interfaces/LibraryInterfaces.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/interfaces/LibraryInterfaces.java new file mode 100644 index 00000000..4f05ea49 --- /dev/null +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/interfaces/LibraryInterfaces.java @@ -0,0 +1,105 @@ +// Copyright © 2016 Panayotis Katsaloulis +// 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 requiredInterfaces = new HashSet<>(); + private final Collection inputDirInterfaces = new HashSet<>(); + private final Collection resolvedInterfaces = new HashSet<>(); + + public void addRequiredInterfaces(String[] interfaceNames) { + requiredInterfaces.addAll(Arrays.asList(interfaceNames)); + } + + public void addFoundInterface(String interfaceName) { + inputDirInterfaces.add(interfaceName); + } + + public Collection getMissingInterfaces(List classpath) { + Collection missingInterfaces = new HashSet<>(requiredInterfaces); + missingInterfaces.removeAll(inputDirInterfaces); + Collection justFound = new HashSet<>(); + Collection 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 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(); + } + +}