# HG changeset patch # User Jaroslav Tulach # Date 1410608641 -7200 # Node ID 35daab73e225396b9832ecf63fcc5e3007da5765 # Parent cd50c1894ce5ceaef61983655e88f5266556039e Can call RetroLambda processor in JAR files. diff -r cd50c1894ce5 -r 35daab73e225 rt/aot/pom.xml --- a/rt/aot/pom.xml Sun Aug 17 20:15:56 2014 +0200 +++ b/rt/aot/pom.xml Sat Sep 13 13:44:01 2014 +0200 @@ -27,5 +27,11 @@ ${project.version} jar + + net.orfjackal.retrolambda + retrolambda + 1.6.1 + jar + diff -r cd50c1894ce5 -r 35daab73e225 rt/aot/src/main/java/org/apidesign/bck2brwsr/aot/Bck2BrwsrJars.java --- a/rt/aot/src/main/java/org/apidesign/bck2brwsr/aot/Bck2BrwsrJars.java Sun Aug 17 20:15:56 2014 +0200 +++ b/rt/aot/src/main/java/org/apidesign/bck2brwsr/aot/Bck2BrwsrJars.java Sat Sep 13 13:44:01 2014 +0200 @@ -25,8 +25,10 @@ import java.net.URL; import java.util.ArrayList; import java.util.Enumeration; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.jar.JarEntry; import java.util.jar.JarFile; @@ -65,23 +67,26 @@ */ public static Bck2Brwsr configureFrom(Bck2Brwsr c, File jar) throws IOException { final JarFile jf = new JarFile(jar); - List classes = new ArrayList<>(); + final List classes = new ArrayList<>(); List resources = new ArrayList<>(); Set exported = new HashSet<>(); - - listJAR(jf, classes, resources, exported); - - String cp = jf.getManifest().getMainAttributes().getValue("Class-Path"); // NOI18N - String[] classpath = cp == null ? new String[0] : cp.split(" "); - class JarRes extends EmulationResources implements Bck2Brwsr.Resources { - + JarRes() { + super(classes); + } @Override public InputStream get(String resource) throws IOException { InputStream is = jf.getInputStream(new ZipEntry(resource)); return is == null ? super.get(resource) : is; } } + JarRes jarRes = new JarRes(); + + listJAR(jf, jarRes, resources, exported); + + String cp = jf.getManifest().getMainAttributes().getValue("Class-Path"); // NOI18N + String[] classpath = cp == null ? new String[0] : cp.split(" "); + if (c == null) { c = Bck2Brwsr.newCompiler(); } @@ -91,11 +96,11 @@ .addClasses(classes.toArray(new String[classes.size()])) .addExported(exported.toArray(new String[exported.size()])) .addResources(resources.toArray(new String[resources.size()])) - .resources(new JarRes()); + .resources(jarRes); } private static void listJAR( - JarFile j, List classes, + JarFile j, EmulationResources classes, List resources, Set keep ) throws IOException { Enumeration en = j.entries(); @@ -114,7 +119,7 @@ keep.add(pkg); } if (n.endsWith(".class")) { - classes.add(n.substring(0, n.length() - 6)); + classes.addClassResource(n); } else { resources.add(n); if (n.startsWith("META-INF/services/") && keep != null) { @@ -142,8 +147,51 @@ } } } + + static byte[] readFrom(InputStream is) throws IOException { + int expLen = is.available(); + if (expLen < 1) { + expLen = 1; + } + byte[] arr = new byte[expLen]; + int pos = 0; + for (;;) { + int read = is.read(arr, pos, arr.length - pos); + if (read == -1) { + break; + } + pos += read; + if (pos == arr.length) { + byte[] tmp = new byte[arr.length * 2]; + System.arraycopy(arr, 0, tmp, 0, arr.length); + arr = tmp; + } + } + if (pos != arr.length) { + byte[] tmp = new byte[pos]; + System.arraycopy(arr, 0, tmp, 0, pos); + arr = tmp; + } + return arr; + } + static class EmulationResources implements Bck2Brwsr.Resources { + private final List classes; + private final Map converted = new HashMap<>(); + private final BytecodeProcessor proc; + + protected EmulationResources(List classes) { + this.classes = classes; + BytecodeProcessor p; + try { + Class bpClass = Class.forName("org.apidesign.bck2brwsr.aot.RetroLambda"); + p = (BytecodeProcessor) bpClass.newInstance(); + } catch (Throwable t) { + p = null; + } + this.proc = p; + } @Override public InputStream get(String name) throws IOException { @@ -162,6 +210,23 @@ } return u.openStream(); } + + private void addClassResource(String n) throws IOException { + if (proc != null) { + try (InputStream is = this.get(n)) { + Map conv = proc.process(n, readFrom(is), this); + if (conv != null) { + if (!conv.containsKey(n)) { + throw new IOException("Cannot find " + n + " among " + conv); + } + converted.putAll(conv); + return; + } + } + } + classes.add(n.substring(0, n.length() - 6)); + } } + } diff -r cd50c1894ce5 -r 35daab73e225 rt/aot/src/main/java/org/apidesign/bck2brwsr/aot/BytecodeProcessor.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/aot/src/main/java/org/apidesign/bck2brwsr/aot/BytecodeProcessor.java Sat Sep 13 13:44:01 2014 +0200 @@ -0,0 +1,40 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.bck2brwsr.aot; + +import java.io.IOException; +import java.util.Map; +import org.apidesign.vm4brwsr.Bck2Brwsr; + +/** Replace bytecode of a single class with many new bytecodes. + * + * @author Jaroslav Tulach + */ +interface BytecodeProcessor { + /** Does the conversion. + * + * @param className the resource of the class to replace + * @param byteCode the bytecode of the class + * @param resources access to other resources in the system + * @return map of resource to bytecode which must include at least + * one element of name className + * @throws IOException + */ + public Map process(String className, byte[] byteCode, Bck2Brwsr.Resources resources) + throws IOException; +} diff -r cd50c1894ce5 -r 35daab73e225 rt/aot/src/main/java/org/apidesign/bck2brwsr/aot/RetroLambda.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rt/aot/src/main/java/org/apidesign/bck2brwsr/aot/RetroLambda.java Sat Sep 13 13:44:01 2014 +0200 @@ -0,0 +1,121 @@ +/** + * Back 2 Browser Bytecode Translator + * Copyright (C) 2012 Jaroslav Tulach + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. Look for COPYING file in the top folder. + * If not, see http://opensource.org/licenses/GPL-2.0. + */ +package org.apidesign.bck2brwsr.aot; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import net.orfjackal.retrolambda.LambdaClassBackporter; +import net.orfjackal.retrolambda.LambdaClassDumper; +import net.orfjackal.retrolambda.LambdaClassSaver; +import net.orfjackal.retrolambda.LambdaReifier; +import net.orfjackal.retrolambda.LambdaUsageBackporter; +import net.orfjackal.retrolambda.asm.Opcodes; +import org.apidesign.bck2brwsr.core.ExtraJavaScript; +import org.apidesign.vm4brwsr.Bck2Brwsr; + +/** + * + * @author Jaroslav Tulach + */ +@ExtraJavaScript(processByteCode = false, resource="") +final class RetroLambda extends LambdaClassSaver implements BytecodeProcessor { + private Map converted; + + public RetroLambda() { + super(null, Opcodes.V1_7); + } + + @Override + public void saveIfLambda(String className, byte[] bytecode) { + if (LambdaReifier.isLambdaClassToReify(className)) { + try { + System.out.println("Saving lambda class: " + className); + byte[] backportedBytecode = LambdaClassBackporter.transform(bytecode, Opcodes.V1_7); + putBytecode(className, backportedBytecode); + } catch (Throwable t) { + // print to stdout to keep in sync with other log output + throw new IllegalStateException("ERROR: Failed to backport lambda class: " + className); + } + } + } + + private void putBytecode(String className, byte[] backportedBytecode) { + if (converted == null) { + converted = new HashMap<>(); + } + converted.put(className, backportedBytecode); + } + + @Override + public Map process( + String className, byte[] byteCode, Bck2Brwsr.Resources resources + ) throws IOException { + int minor = byteCode[4] << 8 | byteCode[5]; + int major = byteCode[6] << 8 | byteCode[7]; + System.err.println("processing: " + className + " major: "+ major + " minor: " + minor); + if (major <= 51) { + return null; + } + + ClassLoader prev = Thread.currentThread().getContextClassLoader(); + try (LambdaClassDumper dumper = new LambdaClassDumper(this)) { + Thread.currentThread().setContextClassLoader(new ResLdr(resources)); + dumper.install(); + + byte[] newB = LambdaUsageBackporter.transform(byteCode, Opcodes.V1_7); + if (!Arrays.equals(newB, byteCode)) { + putBytecode(className, newB); + } + } finally { + Thread.currentThread().setContextClassLoader(prev); + } + + Map ret = converted; + converted = null; + return ret; + } + + private static final class ResLdr extends ClassLoader { + private final Bck2Brwsr.Resources res; + + public ResLdr(Bck2Brwsr.Resources res) { + this.res = res; + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + Class c = findLoadedClass(name); + if (c != null) { + return c; + } + String r = name.replace('.', '/') + ".class"; + try (InputStream is = res.get(r)) { + if (is == null) { + throw new ClassNotFoundException(name); + } + byte[] arr = Bck2BrwsrJars.readFrom(is); + return defineClass(name, arr, 0, arr.length); + } catch (IOException e) { + return super.loadClass(name); + } + } + } +}