From 358962dfa8b2b55d0e1a0c158640924bb0423d2b Mon Sep 17 00:00:00 2001 From: Alex Bertram Date: Fri, 5 Oct 2018 16:33:57 +0200 Subject: [PATCH] Add initial support for remapping new API references (proof of concept) * Automatically Replaces classes and calls to specific static methods with calls to equivalent backported classes/methods * Reads configuration from a mapping file, can be used with guava or stream support libraries, or custom implementations --- .idea/compiler.xml | 4 + .idea/encodings.xml | 2 + .idea/modules.xml | 2 + README.md | 31 ++- end-to-end-tests/pom.xml | 10 + .../retrolambda/test/ApiMapperTest.java | 39 ++++ .../retrolambda/test/BackportedApis.java | 8 + .../retrolambda/api/RetrolambdaApi.java | 1 + .../retrolambda/maven/ProcessClassesMojo.java | 18 ++ retrolambda/retrolambda.iml | 1 + .../net/orfjackal/retrolambda/Config.java | 2 + .../orfjackal/retrolambda/Retrolambda.java | 5 +- .../retrolambda/SystemPropertiesConfig.java | 23 +++ .../orfjackal/retrolambda/Transformers.java | 8 +- .../retrolambda/api/ApiMappingSet.java | 183 ++++++++++++++++++ .../retrolambda/api/ApiMappingType.java | 29 +++ .../retrolambda/api/ApiRemapper.java | 21 ++ .../api/InvalidApiMappingSyntax.java | 19 ++ .../orfjackal/retrolambda/api/Mapping.java | 30 +++ .../retrolambda/api/RewriteApiReferences.java | 49 +++++ .../net/orfjackal/retrolambda/api/java6-guava | 5 + .../net/orfjackal/retrolambda/api/java7-guava | 14 ++ .../orfjackal/retrolambda/api/streamsupport | 6 + .../retrolambda/api/ApiMappingSetTest.java | 28 +++ 24 files changed, 535 insertions(+), 3 deletions(-) create mode 100644 end-to-end-tests/src/test/java/net/orfjackal/retrolambda/test/ApiMapperTest.java create mode 100644 end-to-end-tests/src/test/java/net/orfjackal/retrolambda/test/BackportedApis.java create mode 100644 retrolambda/src/main/java/net/orfjackal/retrolambda/api/ApiMappingSet.java create mode 100644 retrolambda/src/main/java/net/orfjackal/retrolambda/api/ApiMappingType.java create mode 100644 retrolambda/src/main/java/net/orfjackal/retrolambda/api/ApiRemapper.java create mode 100644 retrolambda/src/main/java/net/orfjackal/retrolambda/api/InvalidApiMappingSyntax.java create mode 100644 retrolambda/src/main/java/net/orfjackal/retrolambda/api/Mapping.java create mode 100644 retrolambda/src/main/java/net/orfjackal/retrolambda/api/RewriteApiReferences.java create mode 100644 retrolambda/src/main/resources/net/orfjackal/retrolambda/api/java6-guava create mode 100644 retrolambda/src/main/resources/net/orfjackal/retrolambda/api/java7-guava create mode 100644 retrolambda/src/main/resources/net/orfjackal/retrolambda/api/streamsupport create mode 100644 retrolambda/src/test/java/net/orfjackal/retrolambda/api/ApiMappingSetTest.java diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 32d6403d..d259fafb 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -12,6 +12,8 @@ + + @@ -22,6 +24,8 @@ + + diff --git a/.idea/encodings.xml b/.idea/encodings.xml index d3d91187..32612cec 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -2,6 +2,8 @@ + + diff --git a/.idea/modules.xml b/.idea/modules.xml index bf80aba3..decb4f5e 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -7,6 +7,8 @@ + + diff --git a/README.md b/README.md index 584f9395..fa450b43 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,11 @@ Configurable system properties: Reduces the amount of logging. Disabled by default. Enable by setting to "true" + retrolambda.apiMappings + Maps static field and method references to backported classes. + Disabled by default. + Provide a list of mapping files separated by ':' to enable. + If the Java agent is used, then Retrolambda will use it to capture the lambda classes generated by Java. Otherwise Retrolambda will hook into Java's internal lambda dumping API, which is more susceptible to suddenly @@ -215,10 +220,34 @@ updated. This may cause weird error messages if static methods on interfaces are accidentally used without enabling default method support.* +Backported APIs +--------------- + +Retrolambda can be configured to automatically replace references to +new Java APIs with backported versions. + +You can use one of the prepackaged mappings, or write your own. For +details on the syntax of the mapping files, see the included +[java7-guava](retrolambda/src/main/resources/net/orfjackal/retrolambda/api/java7-guava) or +[streamsupport](retrolambda/src/main/resources/net/orfjackal/retrolambda/api/streamsupport). + +Currently included mappings: + + * **java6-guava**: Maps new Java 1.7 APIs to existing guava methods. + For example, from `java.nio.StandardCharsets` to `com.google.common.base.Charsets` + + * **java7-guava**: Maps new Java 1.8 APIs to existing guava methods. + For example, from `java.lang.Integer.toUnsignedLong()` to `com.google.common.primitives.UnsignedInts.toLong()` + + * **streamsupport**: Maps java.util.function.* interfaces to the + java8.util.function replacements in the [streamsupport](http://sourceforge.net/projects/streamsupport/) library. + + + Known Limitations ----------------- -Does not backport Java 8 APIs. +Does not backport all Java 8 APIs. Backporting default methods and static methods on interfaces requires all backported interfaces and all classes which implement them or call their diff --git a/end-to-end-tests/pom.xml b/end-to-end-tests/pom.xml index 408f954a..ef47f400 100644 --- a/end-to-end-tests/pom.xml +++ b/end-to-end-tests/pom.xml @@ -49,6 +49,12 @@ ${basedir}/src/test/lib/java-lang-dummies.jar + + net.sourceforge.streamsupport + streamsupport + 1.6.3 + + @@ -119,6 +125,10 @@ ${testDefaultMethods} ${testFork} true + + java7-guava + streamsupport + diff --git a/end-to-end-tests/src/test/java/net/orfjackal/retrolambda/test/ApiMapperTest.java b/end-to-end-tests/src/test/java/net/orfjackal/retrolambda/test/ApiMapperTest.java new file mode 100644 index 00000000..6e63a301 --- /dev/null +++ b/end-to-end-tests/src/test/java/net/orfjackal/retrolambda/test/ApiMapperTest.java @@ -0,0 +1,39 @@ +// Copyright © 2013-2018 Esko Luontola and other Retrolambda contributors +// 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.test; + +import org.junit.Test; + +import java.nio.charset.*; +import java.util.*; +import java.util.function.Predicate; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.assertThat; + +public class ApiMapperTest { + + @Test + public void toUnsignedLong() { + + assertThat(Integer.toUnsignedLong(0xffffffff), equalTo(4294967295L)); + } + + @Test + public void predicates() { + assertThat(filter(Arrays.asList("Hello World", "Goodbye World"), x -> x.startsWith("Hello")), hasSize(1)); + } + + private List filter(List strings, Predicate predicate) { + List result = new ArrayList<>(); + for (String string : strings) { + if(predicate.test(string)) { + result.add(string); + } + } + return result; + } +} diff --git a/end-to-end-tests/src/test/java/net/orfjackal/retrolambda/test/BackportedApis.java b/end-to-end-tests/src/test/java/net/orfjackal/retrolambda/test/BackportedApis.java new file mode 100644 index 00000000..9ada3900 --- /dev/null +++ b/end-to-end-tests/src/test/java/net/orfjackal/retrolambda/test/BackportedApis.java @@ -0,0 +1,8 @@ +// Copyright © 2013-2018 Esko Luontola and other Retrolambda contributors +// 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.test; + +public class BackportedApis { +} diff --git a/retrolambda-api/src/main/java/net/orfjackal/retrolambda/api/RetrolambdaApi.java b/retrolambda-api/src/main/java/net/orfjackal/retrolambda/api/RetrolambdaApi.java index 7276b978..a01d35be 100644 --- a/retrolambda-api/src/main/java/net/orfjackal/retrolambda/api/RetrolambdaApi.java +++ b/retrolambda-api/src/main/java/net/orfjackal/retrolambda/api/RetrolambdaApi.java @@ -17,4 +17,5 @@ public class RetrolambdaApi { public static final String DEFAULT_METHODS = PREFIX + "defaultMethods"; public static final String BYTECODE_VERSION = PREFIX + "bytecodeVersion"; public static final String JAVAC_HACKS = PREFIX + "javacHacks"; + public static final String API_MAPPINGS = PREFIX + "apiMappings"; } diff --git a/retrolambda-maven-plugin/src/main/java/net/orfjackal/retrolambda/maven/ProcessClassesMojo.java b/retrolambda-maven-plugin/src/main/java/net/orfjackal/retrolambda/maven/ProcessClassesMojo.java index 06b94aab..ee61f1e9 100644 --- a/retrolambda-maven-plugin/src/main/java/net/orfjackal/retrolambda/maven/ProcessClassesMojo.java +++ b/retrolambda-maven-plugin/src/main/java/net/orfjackal/retrolambda/maven/ProcessClassesMojo.java @@ -7,6 +7,7 @@ import com.google.common.base.*; import com.google.common.collect.ImmutableMap; import com.google.common.io.Files; +import net.orfjackal.retrolambda.Retrolambda; import net.orfjackal.retrolambda.api.RetrolambdaApi; import org.apache.commons.lang3.*; import org.apache.maven.artifact.DependencyResolutionRequiredException; @@ -107,6 +108,9 @@ abstract class ProcessClassesMojo extends AbstractMojo { @Parameter(defaultValue = "false") public boolean fork; + @Parameter + public List apiMappings; + protected abstract File getInputDir(); protected abstract File getOutputDir(); @@ -126,6 +130,7 @@ public void execute() throws MojoExecutionException { config.setProperty(RetrolambdaApi.OUTPUT_DIR, getOutputDir().getAbsolutePath()); config.setProperty(RetrolambdaApi.CLASSPATH, getClasspath()); config.setProperty(RetrolambdaApi.JAVAC_HACKS, "" + javacHacks); + config.setProperty(RetrolambdaApi.API_MAPPINGS, apiMappingsList()); if (fork) { processClassesInForkedProcess(config); @@ -134,6 +139,19 @@ public void execute() throws MojoExecutionException { } } + private String apiMappingsList() { + StringBuilder list = new StringBuilder(); + if(apiMappings != null) { + for (String apiMapping : apiMappings) { + if(list.length() > 0) { + list.append(File.pathSeparator); + } + list.append(apiMapping); + } + } + return list.toString(); + } + private void validateTarget() throws MojoExecutionException { if (!targetBytecodeVersions.containsKey(target)) { String possibleValues = Joiner.on(", ").join(new TreeSet(targetBytecodeVersions.keySet())); diff --git a/retrolambda/retrolambda.iml b/retrolambda/retrolambda.iml index f58fb626..5ca4c98b 100644 --- a/retrolambda/retrolambda.iml +++ b/retrolambda/retrolambda.iml @@ -6,6 +6,7 @@ + diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/Config.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/Config.java index e5d2c983..86dd8fd1 100644 --- a/retrolambda/src/main/java/net/orfjackal/retrolambda/Config.java +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/Config.java @@ -21,6 +21,8 @@ public interface Config { List getIncludedFiles(); + List getApiMappings(); + boolean isJavacHacksEnabled(); boolean isQuiet(); diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/Retrolambda.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/Retrolambda.java index d6d49579..0a0e9ef4 100644 --- a/retrolambda/src/main/java/net/orfjackal/retrolambda/Retrolambda.java +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/Retrolambda.java @@ -5,6 +5,7 @@ package net.orfjackal.retrolambda; import com.esotericsoftware.minlog.Log; +import net.orfjackal.retrolambda.api.ApiMappingSet; import net.orfjackal.retrolambda.files.*; import net.orfjackal.retrolambda.interfaces.ClassInfo; import net.orfjackal.retrolambda.lambdas.*; @@ -33,6 +34,7 @@ public static void run(Config config) throws Throwable { List classpath = config.getClasspath(); List includedFiles = config.getIncludedFiles(); boolean isJavacHacksEnabled = config.isJavacHacksEnabled(); + List apiMappings = config.getApiMappings(); if (config.isQuiet()) { Log.WARN(); } else { @@ -57,7 +59,8 @@ public static void run(Config config) throws Throwable { ClassAnalyzer analyzer = new ClassAnalyzer(); OutputDirectory outputDirectory = new OutputDirectory(outputDir); - Transformers transformers = new Transformers(bytecodeVersion, defaultMethodsEnabled, analyzer); + ApiMappingSet apiMappingSet = new ApiMappingSet(config.getApiMappings()); + Transformers transformers = new Transformers(bytecodeVersion, defaultMethodsEnabled, analyzer, apiMappingSet); LambdaClassSaver lambdaClassSaver = new LambdaClassSaver(outputDirectory, transformers, isJavacHacksEnabled); try (LambdaClassDumper dumper = new LambdaClassDumper(lambdaClassSaver)) { diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/SystemPropertiesConfig.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/SystemPropertiesConfig.java index 18afb719..72697d30 100644 --- a/retrolambda/src/main/java/net/orfjackal/retrolambda/SystemPropertiesConfig.java +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/SystemPropertiesConfig.java @@ -200,6 +200,8 @@ public List getIncludedFiles() { } + + @Override public boolean isJavacHacksEnabled() { return Boolean.parseBoolean(p.getProperty(JAVAC_HACKS, "false")); @@ -221,6 +223,27 @@ public boolean isQuiet() { } + + // API Mappings + + static { + optionalParameterHelp(API_MAPPINGS, + "Maps static field and method references to backported classes.", + "Disabled by default.", + "Provide a list of mapping files separated by '" + File.pathSeparator + "' to enable."); + } + + + @Override + public List getApiMappings() { + String list = p.getProperty(API_MAPPINGS, ""); + if(list.isEmpty()) { + return Collections.emptyList(); + } else { + return Arrays.asList(list.split(File.pathSeparator)); + } + } + // help public String getHelp() { diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/Transformers.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/Transformers.java index 7e219287..54a4a7da 100644 --- a/retrolambda/src/main/java/net/orfjackal/retrolambda/Transformers.java +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/Transformers.java @@ -4,6 +4,7 @@ package net.orfjackal.retrolambda; +import net.orfjackal.retrolambda.api.*; import net.orfjackal.retrolambda.interfaces.*; import net.orfjackal.retrolambda.lambdas.*; import net.orfjackal.retrolambda.requirenonnull.RequireNonNull; @@ -19,11 +20,13 @@ public class Transformers { private final int targetVersion; private final boolean defaultMethodsEnabled; private final ClassAnalyzer analyzer; + private final ApiMappingSet mapping; - public Transformers(int targetVersion, boolean defaultMethodsEnabled, ClassAnalyzer analyzer) { + public Transformers(int targetVersion, boolean defaultMethodsEnabled, ClassAnalyzer analyzer, ApiMappingSet mapping) { this.targetVersion = targetVersion; this.defaultMethodsEnabled = defaultMethodsEnabled; this.analyzer = analyzer; + this.mapping = mapping; } public byte[] backportLambdaClass(ClassReader reader) { @@ -50,6 +53,9 @@ public byte[] backportClass(ClassReader reader) { next = new AddMethodDefaultImplementations(next, analyzer); } next = new BackportLambdaInvocations(next, analyzer); + if (mapping.isEnabled()) { + next = new RewriteApiReferences(next, mapping); + } return next; }); } diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/api/ApiMappingSet.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/api/ApiMappingSet.java new file mode 100644 index 00000000..b857708a --- /dev/null +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/api/ApiMappingSet.java @@ -0,0 +1,183 @@ +// Copyright © 2013-2018 Esko Luontola and other Retrolambda contributors +// 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.api; + +import com.google.common.base.Charsets; +import com.google.common.collect.ImmutableList; +import com.google.common.io.*; + +import java.io.*; +import java.net.URL; +import java.util.*; + +/** + * Set of mappings from new API classes or methods (like {@link Double#isFinite(double)} or + * {@link java.util.function.Predicate} to backported classes in third-party libraries, like + * {@link com.google.common.primitives.Doubles#isFinite(double)} or {@code java8.util.function.Predicate} in + * the streamsupport library. + */ +public class ApiMappingSet { + + /** + * Mapping from unsupported API package to new package, using the internal + * format. For example, "java/util/function/Predicate" to "java8/util/function/Predicate" + */ + private Map packageMappings = new HashMap<>(); + + /** + * Mapping from unsupported API class to a new class, using the internal name format. + * For example, from "java/nio/StandardCharsets" to "com/google/common/base/Charsets" + */ + private Map classNameMappings = new HashMap<>(); + + /** + * Mapping from a new static field to a static field of the same type defined elsewhere. + * + * For example, from "java/nio/StandardCharsets.UTF_8" to "com/google/common/base/Charsets.UTF_8" + */ + private Map fieldMappings = new HashMap<>(); + + /** + * Mapping from a single static method to a different static method with the same signature. + * + * For example, from "java/lang/Double.isFinite" to "com/google/common/primitives/Doubles.isFinite" + */ + private Map staticMethodMappings = new HashMap<>(); + + public ApiMappingSet(List mappings) throws IOException { + for (String mapping : mappings) { + read(mapping); + } + } + + /** + * @return {@code true} if any mappings are defined + */ + public boolean isEnabled() { + return !classNameMappings.isEmpty() || + !fieldMappings.isEmpty() || + !staticMethodMappings.isEmpty() || + !packageMappings.isEmpty(); + } + + private void read(String mapping) throws IOException { + + CharSource charSource = tryReadResource(mapping).orElseGet(() -> readFile(mapping)); + + parse(charSource); + } + + private Optional tryReadResource(String mapping) { + URL url; + try { + url = Resources.getResource(ApiMappingSet.class, mapping); + } catch (IllegalArgumentException e) { + return Optional.empty(); + } + + return Optional.of(Resources.asByteSource(url).asCharSource(Charsets.UTF_8)); + } + + private CharSource readFile(String path) { + File file = new File(path); + return Files.asCharSource(file, Charsets.UTF_8); + } + + private void parse(CharSource charSource) throws IOException { + + ImmutableList lines = charSource.readLines(); + for (int i = 0; i < lines.size(); i++) { + String line = lines.get(i); + + // Strip comments + int commentStart = line.indexOf('#'); + if(commentStart != -1) { + line = line.substring(0, commentStart); + } + + // Skip blank lines + if(line.trim().isEmpty()) { + continue; + } + + String[] columns = line.split("\\s+"); + + if(columns.length != 3) { + throw new InvalidApiMappingSyntax(i, "Expected a line with three columns, separated with whitespace"); + } + + + ApiMappingType type; + try { + type = ApiMappingType.valueOf(columns[0].toUpperCase()); + } catch (IllegalArgumentException | ArrayIndexOutOfBoundsException e) { + throw new InvalidApiMappingSyntax(i, "Expected a line start with one of: " + + Arrays.toString(ApiMappingType.values())); + } + + switch (type) { + + case PACKAGE: + packageMappings.put(columns[1], columns[2]); + break; + + case CLASS: + classNameMappings.put(columns[1], columns[2]); + break; + + case INVOKESTATIC: + staticMethodMappings.put(columns[1], new Mapping(columns[2])); + break; + + case GETSTATIC: + fieldMappings.put(columns[1], new Mapping(columns[2])); + break; + } + } + } + + /** + * Maps an internal class name to the backported class name. + */ + public String mapClass(String internalTypeName) { + + // Try to match by exact class name + String className = classNameMappings.get(internalTypeName); + if(className != null) { + return className; + } + + // Try to match by package + int packageStart = internalTypeName.lastIndexOf('/'); + while(packageStart != -1) { + String prefix = internalTypeName.substring(0, packageStart); + String suffix = internalTypeName.substring(packageStart); + if(packageMappings.containsKey(prefix)) { + return packageMappings.get(prefix) + suffix; + } + packageStart = internalTypeName.lastIndexOf('/', packageStart - 1); + } + + // No mappings, use the original + return internalTypeName; + } + + public Mapping mapField(String owner, String name, String descriptor) { + return mapMember(fieldMappings, owner, name, descriptor); + } + + public Mapping mapStaticMethod(String owner, String name, String descriptor) { + return mapMember(staticMethodMappings, owner, name, descriptor); + } + + private Mapping mapMember(Map dictionary, String owner, String name, String descriptor) { + Mapping mapping = dictionary.get(owner + "." + name + descriptor); + if(mapping != null) { + return mapping; + } + + return new Mapping(mapClass(owner), name); + } +} \ No newline at end of file diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/api/ApiMappingType.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/api/ApiMappingType.java new file mode 100644 index 00000000..6e938c9a --- /dev/null +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/api/ApiMappingType.java @@ -0,0 +1,29 @@ +// Copyright © 2013-2018 Esko Luontola and other Retrolambda contributors +// 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.api; + +public enum ApiMappingType { + + /** + * Maps all classes in a package and its subpackages to a new package + */ + PACKAGE, + + /** + * Maps a class and all its member to a new class + */ + CLASS, + + /** + * Maps a static method from one class to a new class and method name + */ + INVOKESTATIC, + + /** + * Maps a static field from one class to a new class and field name + */ + GETSTATIC + +} diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/api/ApiRemapper.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/api/ApiRemapper.java new file mode 100644 index 00000000..7006f87b --- /dev/null +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/api/ApiRemapper.java @@ -0,0 +1,21 @@ +// Copyright © 2013-2018 Esko Luontola and other Retrolambda contributors +// 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.api; + +import org.objectweb.asm.commons.Remapper; + +class ApiRemapper extends Remapper { + + private ApiMappingSet mappingSet; + + public ApiRemapper(ApiMappingSet mappingSet) { + this.mappingSet = mappingSet; + } + + @Override + public String map(String typeName) { + return mappingSet.mapClass(typeName); + } +} diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/api/InvalidApiMappingSyntax.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/api/InvalidApiMappingSyntax.java new file mode 100644 index 00000000..4640e0f8 --- /dev/null +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/api/InvalidApiMappingSyntax.java @@ -0,0 +1,19 @@ +// Copyright © 2013-2018 Esko Luontola and other Retrolambda contributors +// 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.api; + +import java.io.IOException; + +public class InvalidApiMappingSyntax extends IOException { + + public InvalidApiMappingSyntax(String message) { + super(message); + } + + + public InvalidApiMappingSyntax(int lineNumber, String message) { + super("Line " + (lineNumber + 1) + ": " + message); + } +} \ No newline at end of file diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/api/Mapping.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/api/Mapping.java new file mode 100644 index 00000000..1912ba31 --- /dev/null +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/api/Mapping.java @@ -0,0 +1,30 @@ +// Copyright © 2013-2018 Esko Luontola and other Retrolambda contributors +// 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.api; + +class Mapping { + + private final String owner; + private final String name; + + Mapping(String signature) { + String[] parts = signature.split("\\."); + this.owner = parts[0]; + this.name = parts[1]; + } + + public Mapping(String owner, String name) { + this.owner = owner; + this.name = name; + } + + public String getOwner() { + return owner; + } + + public String getName() { + return name; + } +} diff --git a/retrolambda/src/main/java/net/orfjackal/retrolambda/api/RewriteApiReferences.java b/retrolambda/src/main/java/net/orfjackal/retrolambda/api/RewriteApiReferences.java new file mode 100644 index 00000000..79701a26 --- /dev/null +++ b/retrolambda/src/main/java/net/orfjackal/retrolambda/api/RewriteApiReferences.java @@ -0,0 +1,49 @@ +// Copyright © 2013-2018 Esko Luontola and other Retrolambda contributors +// 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.api; + +import org.objectweb.asm.*; +import org.objectweb.asm.commons.*; + +/** + * Rewrites calls new Java APIs to backported classes + */ +public class RewriteApiReferences extends ClassRemapper { + + private final ApiMappingSet mapping; + + public RewriteApiReferences(ClassVisitor next, ApiMappingSet mapping) { + super(Opcodes.ASM5, next, new ApiRemapper(mapping)); + this.mapping = mapping; + } + + @Override + protected MethodVisitor createMethodRemapper(MethodVisitor mv) { + return new MethodRemapper(mv, remapper) { + @Override + public void visitFieldInsn(int opcode, String owner, String name, String desc) { + if(opcode == Opcodes.GETSTATIC) { + Mapping fieldMapping = mapping.mapField(owner, name, desc); + super.visitFieldInsn(opcode, fieldMapping.getOwner(), fieldMapping.getName(), desc); + } else { + super.visitFieldInsn(opcode, owner, name, desc); + } + } + + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) { + if(opcode == Opcodes.INVOKESTATIC) { + Mapping methodMapping = mapping.mapStaticMethod(owner, name, desc); + super.visitMethodInsn(Opcodes.INVOKESTATIC, + methodMapping.getOwner(), + methodMapping.getName(), + remapper.mapDesc(desc), itf); + } else { + super.visitMethodInsn(opcode, owner, name, desc, itf); + } + } + }; + } +} diff --git a/retrolambda/src/main/resources/net/orfjackal/retrolambda/api/java6-guava b/retrolambda/src/main/resources/net/orfjackal/retrolambda/api/java6-guava new file mode 100644 index 00000000..c929c13e --- /dev/null +++ b/retrolambda/src/main/resources/net/orfjackal/retrolambda/api/java6-guava @@ -0,0 +1,5 @@ + +# Work-in-progress! +# More can be added + +CLASS java/nio/charset/StandardCharsets com/google/common/base/Charsets diff --git a/retrolambda/src/main/resources/net/orfjackal/retrolambda/api/java7-guava b/retrolambda/src/main/resources/net/orfjackal/retrolambda/api/java7-guava new file mode 100644 index 00000000..c733bac2 --- /dev/null +++ b/retrolambda/src/main/resources/net/orfjackal/retrolambda/api/java7-guava @@ -0,0 +1,14 @@ + +# Work-in-progress! +# More can be added + +INVOKESTATIC java/lang/Integer.toUnsignedLong(I)J com/google/common/primitives/UnsignedInts.toLong +INVOKESTATIC java/lang/Integer.toUnsignedString(I)Ljava/lang/String; com/google/common/primitives/UnsignedInts.toString + +INVOKESTATIC java/lang/Long.toUnsignedString(J)Ljava/lang/String; com/google/common/primitives/UnsignedLongs.toString +INVOKESTATIC java/lang/Long.toUnsignedString(JI)Ljava/lang/String; com/google/common/primitives/UnsignedLongs.toString + +INVOKESTATIC java/lang/Double.isFinite(D)Z com/google/common/primitives/Doubles.isFinite + +INVOKESTATIC java/lang/Long.parseUnsignedLong(Ljava/lang/String;I)J com/google/common/primitives/UnsignedLongs.parseUnsignedLong + diff --git a/retrolambda/src/main/resources/net/orfjackal/retrolambda/api/streamsupport b/retrolambda/src/main/resources/net/orfjackal/retrolambda/api/streamsupport new file mode 100644 index 00000000..12feb57d --- /dev/null +++ b/retrolambda/src/main/resources/net/orfjackal/retrolambda/api/streamsupport @@ -0,0 +1,6 @@ + + +# Work-in-progress! +# More can be added + +PACKAGE java/util/function java8/util/function diff --git a/retrolambda/src/test/java/net/orfjackal/retrolambda/api/ApiMappingSetTest.java b/retrolambda/src/test/java/net/orfjackal/retrolambda/api/ApiMappingSetTest.java new file mode 100644 index 00000000..226141c9 --- /dev/null +++ b/retrolambda/src/test/java/net/orfjackal/retrolambda/api/ApiMappingSetTest.java @@ -0,0 +1,28 @@ +// Copyright © 2013-2018 Esko Luontola and other Retrolambda contributors +// 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.api; + +import org.junit.Test; + +import java.io.IOException; +import java.util.Collections; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.*; + +public class ApiMappingSetTest { + + @Test + public void mapPackage() throws IOException { + ApiMappingSet mappingSet = new ApiMappingSet(Collections.singletonList("streamsupport")); + + assertThat(mappingSet.mapClass("java/util/function/Function"), equalTo("java8/util/function/Function")); + assertThat(mappingSet.mapClass("java/util/function/Predicate"), equalTo("java8/util/function/Predicate")); + assertThat(mappingSet.mapClass("java/util/function/subpackage/MyClass"), equalTo("java8/util/function/subpackage/MyClass")); + + } + + +} \ No newline at end of file