jaroslav@1599: /** jaroslav@1599: * Back 2 Browser Bytecode Translator jaroslav@1787: * Copyright (C) 2012-2015 Jaroslav Tulach jaroslav@1599: * jaroslav@1599: * This program is free software: you can redistribute it and/or modify jaroslav@1599: * it under the terms of the GNU General Public License as published by jaroslav@1599: * the Free Software Foundation, version 2 of the License. jaroslav@1599: * jaroslav@1599: * This program is distributed in the hope that it will be useful, jaroslav@1599: * but WITHOUT ANY WARRANTY; without even the implied warranty of jaroslav@1599: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the jaroslav@1599: * GNU General Public License for more details. jaroslav@1599: * jaroslav@1599: * You should have received a copy of the GNU General Public License jaroslav@1599: * along with this program. Look for COPYING file in the top folder. jaroslav@1599: * If not, see http://opensource.org/licenses/GPL-2.0. jaroslav@1599: */ jaroslav@1599: package org.apidesign.bck2brwsr.aot; jaroslav@1599: jaroslav@1599: import java.io.BufferedReader; jaroslav@1684: import java.io.ByteArrayInputStream; jaroslav@1599: import java.io.File; jaroslav@1679: import java.io.FileInputStream; jaroslav@1599: import java.io.IOException; jaroslav@1599: import java.io.InputStream; jaroslav@1599: import java.io.InputStreamReader; jaroslav@1599: import java.net.URL; jaroslav@1599: import java.util.ArrayList; jaroslav@1599: import java.util.Enumeration; jaroslav@1678: import java.util.HashMap; jaroslav@1599: import java.util.HashSet; jaroslav@1599: import java.util.List; jaroslav@1678: import java.util.Map; jaroslav@1599: import java.util.Set; jaroslav@1724: import java.util.jar.Attributes; jaroslav@1599: import java.util.jar.JarEntry; jaroslav@1599: import java.util.jar.JarFile; jaroslav@1979: import java.util.jar.Manifest; jaroslav@1599: import java.util.logging.Level; jaroslav@1599: import java.util.logging.Logger; jaroslav@1599: import java.util.zip.ZipEntry; jaroslav@1599: import org.apidesign.vm4brwsr.Bck2Brwsr; jaroslav@1599: jaroslav@1599: /** Utilities to process JAR files and set a compiler jaroslav@1603: * up. jaroslav@1599: * jaroslav@1603: * @since 0.9 jaroslav@1599: * @author Jaroslav Tulach jaroslav@1599: */ jaroslav@1599: public final class Bck2BrwsrJars { jaroslav@1599: private static final Logger LOG = Logger.getLogger(Bck2BrwsrJars.class.getName()); jaroslav@1679: jaroslav@1599: private Bck2BrwsrJars() { jaroslav@1599: } jaroslav@1599: jaroslav@1599: /** Creates new compiler pre-configured from the content of jaroslav@1599: * provided JAR file. The compiler will compile all classes. jaroslav@1729: * The system understands OSGi manifest entries and NetBeans jaroslav@1729: * module system manifest entries and will export jaroslav@1599: * all packages that are exported in the JAR file. The system jaroslav@1628: * also recognizes META-INF/services and makes sure the class names jaroslav@1599: * are not mangled. jaroslav@1599: * jaroslav@1599: * @param c the compiler to {@link Bck2Brwsr#addClasses(java.lang.String...) add classes}, jaroslav@1599: * {@link Bck2Brwsr#addResources(java.lang.String...) add resources} and jaroslav@1599: * {@link Bck2Brwsr#addExported(java.lang.String...) exported objects} to. jaroslav@1599: * Can be null - in such case an jaroslav@1599: * {@link Bck2Brwsr#newCompiler() empty compiler} is constructed. jaroslav@1599: * @param jar the file to process jaroslav@1599: * @return newly configured compiler jaroslav@1599: * @throws IOException if something goes wrong jaroslav@1599: */ jaroslav@1599: public static Bck2Brwsr configureFrom(Bck2Brwsr c, File jar) throws IOException { jaroslav@1710: return configureFrom(c, jar, null); jaroslav@1710: } jaroslav@1710: jaroslav@1710: /** Creates new compiler pre-configured from the content of jaroslav@1710: * provided JAR file. The compiler will compile all classes. jaroslav@1729: * The system understands OSGi manifest entries and NetBeans jaroslav@1729: * module system manifest entries and will export jaroslav@1710: * all packages that are exported in the JAR file. The system jaroslav@1710: * also recognizes META-INF/services and makes sure the class names jaroslav@1710: * are not mangled. jaroslav@1710: * jaroslav@1710: * @param c the compiler to {@link Bck2Brwsr#addClasses(java.lang.String...) add classes}, jaroslav@1710: * {@link Bck2Brwsr#addResources(java.lang.String...) add resources} and jaroslav@1710: * {@link Bck2Brwsr#addExported(java.lang.String...) exported objects} to. jaroslav@1710: * Can be null - in such case an jaroslav@1710: * {@link Bck2Brwsr#newCompiler() empty compiler} is constructed. jaroslav@1710: * @param jar the file to process jaroslav@1710: * @param classpath additional resources to make available during jaroslav@1710: * compilation, but not include them in the generated JavaScript jaroslav@1710: * @return newly configured compiler jaroslav@1710: * @throws IOException if something goes wrong jaroslav@1710: * @since 0.11 jaroslav@1710: */ jaroslav@1710: public static Bck2Brwsr configureFrom( jaroslav@1710: Bck2Brwsr c, File jar, final ClassLoader classpath jaroslav@1710: ) throws IOException { jaroslav@1769: return configureFrom(c, jar, classpath, true); jaroslav@1769: } jaroslav@1769: jaroslav@1769: /** Creates new compiler pre-configured from the content of jaroslav@1769: * provided JAR file. The compiler will compile all classes. jaroslav@1769: * The system understands OSGi manifest entries and NetBeans jaroslav@1769: * module system manifest entries and will export jaroslav@1769: * all packages that are exported in the JAR file. The system jaroslav@1769: * also recognizes META-INF/services and makes sure the class names jaroslav@1769: * are not mangled. jaroslav@1769: * jaroslav@1769: * @param c the compiler to {@link Bck2Brwsr#addClasses(java.lang.String...) add classes}, jaroslav@1769: * {@link Bck2Brwsr#addResources(java.lang.String...) add resources} and jaroslav@1769: * {@link Bck2Brwsr#addExported(java.lang.String...) exported objects} to. jaroslav@1769: * Can be null - in such case an jaroslav@1769: * {@link Bck2Brwsr#newCompiler() empty compiler} is constructed. jaroslav@1769: * @param jar the file to process jaroslav@1769: * @param classpath additional resources to make available during jaroslav@1769: * compilation, but not include them in the generated JavaScript jaroslav@1769: * @param ignoreBootClassPath should we ignore classes on bootclasspath? jaroslav@1769: * @return newly configured compiler jaroslav@1769: * @throws IOException if something goes wrong jaroslav@1769: * @since 0.14 jaroslav@1769: */ jaroslav@1769: public static Bck2Brwsr configureFrom( jaroslav@1769: Bck2Brwsr c, File jar, final ClassLoader classpath, final boolean ignoreBootClassPath jaroslav@1769: ) throws IOException { jaroslav@1679: if (jar.isDirectory()) { jaroslav@1769: return configureDir(ignoreBootClassPath, c, jar, classpath); jaroslav@1679: } jaroslav@1601: final JarFile jf = new JarFile(jar); jaroslav@1678: final List classes = new ArrayList<>(); jaroslav@1601: List resources = new ArrayList<>(); jaroslav@1601: Set exported = new HashSet<>(); jaroslav@1601: class JarRes extends EmulationResources implements Bck2Brwsr.Resources { jaroslav@1678: JarRes() { jaroslav@1769: super(ignoreBootClassPath, classpath, classes); jaroslav@1678: } jaroslav@1601: @Override jaroslav@1601: public InputStream get(String resource) throws IOException { jaroslav@1696: InputStream is = getConverted(resource); jaroslav@1696: if (is != null) { jaroslav@1696: return is; jaroslav@1696: } jaroslav@1821: if (resource.startsWith("/")) { jaroslav@1821: resource = resource.substring(1); jaroslav@1821: } jaroslav@1828: ZipEntry ze = jf.getEntry(resource); jaroslav@1828: if (ze != null) { jaroslav@1828: is = jf.getInputStream(ze); jaroslav@1828: } jaroslav@1601: return is == null ? super.get(resource) : is; jaroslav@1599: } jaroslav@1599: } jaroslav@1678: JarRes jarRes = new JarRes(); jaroslav@1678: jaroslav@1678: listJAR(jf, jarRes, resources, exported); jaroslav@1980: final Manifest manifest = jf.getManifest(); jaroslav@1980: final Attributes mainAttributes = manifest == null ? null : manifest.getMainAttributes(); jaroslav@1980: String cp = mainAttributes == null ? null : mainAttributes.getValue("Class-Path"); // NOI18N jaroslav@1710: String[] parts = cp == null ? new String[0] : cp.split(" "); jaroslav@1678: jaroslav@1622: if (c == null) { jaroslav@1622: c = Bck2Brwsr.newCompiler(); jaroslav@1622: } jaroslav@1622: jaroslav@1622: return c jaroslav@1710: .library(parts) jaroslav@1601: .addClasses(classes.toArray(new String[classes.size()])) jaroslav@1601: .addExported(exported.toArray(new String[exported.size()])) jaroslav@1601: .addResources(resources.toArray(new String[resources.size()])) jaroslav@1678: .resources(jarRes); jaroslav@1599: } jaroslav@1599: jaroslav@1599: private static void listJAR( jaroslav@1678: JarFile j, EmulationResources classes, jaroslav@1599: List resources, Set keep jaroslav@1599: ) throws IOException { jaroslav@1599: Enumeration en = j.entries(); jaroslav@1599: while (en.hasMoreElements()) { jaroslav@1599: JarEntry e = en.nextElement(); jaroslav@1599: final String n = e.getName(); jaroslav@1599: if (n.endsWith("/")) { jaroslav@1599: continue; jaroslav@1599: } jaroslav@1602: if (n.startsWith("META-INF/maven/")) { jaroslav@1602: continue; jaroslav@1602: } jaroslav@1599: int last = n.lastIndexOf('/'); jaroslav@1599: String pkg = n.substring(0, last + 1); jaroslav@1599: if (pkg.startsWith("java/")) { jaroslav@1599: keep.add(pkg); jaroslav@1599: } jaroslav@1599: if (n.endsWith(".class")) { jaroslav@1678: classes.addClassResource(n); jaroslav@1599: } else { jaroslav@1599: resources.add(n); jaroslav@1599: if (n.startsWith("META-INF/services/") && keep != null) { jaroslav@1599: BufferedReader r = new BufferedReader(new InputStreamReader(j.getInputStream(e))); jaroslav@1599: for (;;) { jaroslav@1599: String l = r.readLine(); jaroslav@1599: if (l == null) { jaroslav@1599: break; jaroslav@1599: } jaroslav@1599: if (l.startsWith("#")) { jaroslav@1599: continue; jaroslav@1599: } jaroslav@1599: keep.add(l.replace('.', '/')); jaroslav@1599: } jaroslav@1764: r.close(); jaroslav@1599: } jaroslav@1599: } jaroslav@1599: } jaroslav@1724: if (keep != null) { jaroslav@1979: final Manifest manifest = j.getManifest(); jaroslav@1979: if (manifest != null) { jaroslav@1979: final Attributes mainAttr = manifest.getMainAttributes(); jaroslav@1979: if (mainAttr != null) { jaroslav@1979: exportPublicPackages(mainAttr, keep); jaroslav@1979: } jaroslav@1979: } jaroslav@1724: } jaroslav@1724: } jaroslav@1724: jaroslav@1724: static void exportPublicPackages(final Attributes mainAttr, Set keep) { jaroslav@1724: String exp = mainAttr.getValue("Export-Package"); // NOI18N jaroslav@1724: if (exp != null) { jaroslav@1599: for (String def : exp.split(",")) { jaroslav@1599: for (String sep : def.split(";")) { jaroslav@1599: keep.add(sep.replace('.', '/') + "/"); jaroslav@1599: break; jaroslav@1599: } jaroslav@1599: } jaroslav@1728: return; jaroslav@1728: } jaroslav@1728: exp = mainAttr.getValue("OpenIDE-Module-Public-Packages"); jaroslav@1728: if (exp != null) { jaroslav@1728: for (String def : exp.split(",")) { jaroslav@1728: def = def.trim(); jaroslav@1728: if (def.endsWith(".*")) { jaroslav@1728: keep.add(def.substring(0, def.length() - 1).replace('.', '/')); jaroslav@1728: } jaroslav@1728: } jaroslav@1599: } jaroslav@1599: } jaroslav@1678: jaroslav@1678: static byte[] readFrom(InputStream is) throws IOException { jaroslav@1678: int expLen = is.available(); jaroslav@1678: if (expLen < 1) { jaroslav@1678: expLen = 1; jaroslav@1678: } jaroslav@1678: byte[] arr = new byte[expLen]; jaroslav@1678: int pos = 0; jaroslav@1678: for (;;) { jaroslav@1678: int read = is.read(arr, pos, arr.length - pos); jaroslav@1678: if (read == -1) { jaroslav@1678: break; jaroslav@1678: } jaroslav@1678: pos += read; jaroslav@1678: if (pos == arr.length) { jaroslav@1678: byte[] tmp = new byte[arr.length * 2]; jaroslav@1678: System.arraycopy(arr, 0, tmp, 0, arr.length); jaroslav@1678: arr = tmp; jaroslav@1678: } jaroslav@1678: } jaroslav@1678: if (pos != arr.length) { jaroslav@1678: byte[] tmp = new byte[pos]; jaroslav@1678: System.arraycopy(arr, 0, tmp, 0, pos); jaroslav@1678: arr = tmp; jaroslav@1678: } jaroslav@1678: return arr; jaroslav@1678: } jaroslav@1678: jaroslav@1599: jaroslav@1599: static class EmulationResources implements Bck2Brwsr.Resources { jaroslav@1678: private final List classes; jaroslav@1678: private final Map converted = new HashMap<>(); jaroslav@1678: private final BytecodeProcessor proc; jaroslav@1710: private final ClassLoader cp; jaroslav@1769: private final boolean ignoreBootClassPath; jaroslav@1678: jaroslav@1769: protected EmulationResources(boolean ignoreBootClassPath, ClassLoader cp, List classes) { jaroslav@1769: this.ignoreBootClassPath = ignoreBootClassPath; jaroslav@1678: this.classes = classes; jaroslav@1710: this.cp = cp != null ? cp : Bck2BrwsrJars.class.getClassLoader(); jaroslav@1678: BytecodeProcessor p; jaroslav@1678: try { jaroslav@1678: Class bpClass = Class.forName("org.apidesign.bck2brwsr.aot.RetroLambda"); jaroslav@1678: p = (BytecodeProcessor) bpClass.newInstance(); jaroslav@1678: } catch (Throwable t) { jaroslav@1678: p = null; jaroslav@1678: } jaroslav@1678: this.proc = p; jaroslav@1678: } jaroslav@1599: jaroslav@1696: protected final InputStream getConverted(String name) throws IOException { jaroslav@1684: byte[] arr = converted.get(name); jaroslav@1684: if (arr != null) { jaroslav@1684: return new ByteArrayInputStream(arr); jaroslav@1684: } jaroslav@1696: return null; jaroslav@1696: } jaroslav@1696: jaroslav@1696: @Override jaroslav@1696: public InputStream get(String name) throws IOException { jaroslav@1696: InputStream is = getConverted(name); jaroslav@1696: if (is != null) { jaroslav@1696: return is; jaroslav@1696: } jaroslav@1886: return getFromCp(name); jaroslav@1886: } jaroslav@1886: jaroslav@1886: private InputStream getFromCp(String name) throws IOException { jaroslav@1710: Enumeration en = cp.getResources(name); jaroslav@1599: URL u = null; jaroslav@1599: while (en.hasMoreElements()) { jaroslav@1599: u = en.nextElement(); jaroslav@1599: } jaroslav@1599: if (u == null) { jaroslav@1763: LOG.log(Level.FINE, "Cannot find {0}", name); jaroslav@1599: return null; jaroslav@1599: } jaroslav@1769: if (ignoreBootClassPath && u.toExternalForm().contains("/rt.jar!")) { jaroslav@1763: LOG.log(Level.WARNING, "No bootdelegation for {0}", name); jaroslav@1599: return null; jaroslav@1599: } jaroslav@1599: return u.openStream(); jaroslav@1599: } jaroslav@1678: jaroslav@1886: private final class NoConvRes implements Bck2Brwsr.Resources { jaroslav@1886: @Override jaroslav@1886: public InputStream get(String resource) throws IOException { jaroslav@1886: return getFromCp(resource); jaroslav@1886: } jaroslav@1886: } jaroslav@1886: jaroslav@1678: private void addClassResource(String n) throws IOException { jaroslav@1678: if (proc != null) { jaroslav@1678: try (InputStream is = this.get(n)) { jaroslav@1886: Map conv = proc.process(n, readFrom(is), new NoConvRes()); jaroslav@1678: if (conv != null) { jaroslav@1681: boolean found = false; jaroslav@1681: for (Map.Entry entrySet : conv.entrySet()) { jaroslav@1681: String res = entrySet.getKey(); jaroslav@1681: byte[] bytes = entrySet.getValue(); jaroslav@1681: if (res.equals(n)) { jaroslav@1681: found = true; jaroslav@1681: } jaroslav@1684: assert res.endsWith(".class") : "Wrong resource: " + res; jaroslav@1681: converted.put(res, bytes); jaroslav@1681: classes.add(res.substring(0, res.length() - 6)); jaroslav@1681: } jaroslav@1681: if (!found) { jaroslav@1678: throw new IOException("Cannot find " + n + " among " + conv); jaroslav@1678: } jaroslav@1678: return; jaroslav@1678: } jaroslav@1678: } jaroslav@1678: } jaroslav@1678: classes.add(n.substring(0, n.length() - 6)); jaroslav@1678: } jaroslav@1599: } jaroslav@1599: jaroslav@1769: private static Bck2Brwsr configureDir(final boolean ignoreBootClassPath, Bck2Brwsr c, final File dir, ClassLoader cp) throws IOException { jaroslav@1679: List arr = new ArrayList<>(); jaroslav@1679: List classes = new ArrayList<>(); jaroslav@1679: class DirRes extends EmulationResources { jaroslav@1710: public DirRes(ClassLoader cp, List classes) { jaroslav@1769: super(ignoreBootClassPath, cp, classes); jaroslav@1679: } jaroslav@1679: jaroslav@1679: @Override jaroslav@1679: public InputStream get(String name) throws IOException { jaroslav@1679: InputStream is = super.get(name); jaroslav@1679: if (is != null) { jaroslav@1679: return is; jaroslav@1679: } jaroslav@1679: File r = new File(dir, name.replace('/', File.separatorChar)); jaroslav@1679: if (r.exists()) { jaroslav@1679: return new FileInputStream(r); jaroslav@1679: } jaroslav@1679: return null; jaroslav@1679: } jaroslav@1679: } jaroslav@1710: DirRes dirRes = new DirRes(cp, classes); jaroslav@1679: listDir(dir, null, dirRes, arr); jaroslav@1679: if (c == null) { jaroslav@1679: c = Bck2Brwsr.newCompiler(); jaroslav@1679: } jaroslav@1679: return c jaroslav@1679: .addRootClasses(classes.toArray(new String[0])) jaroslav@1679: .addResources(arr.toArray(new String[0])) jaroslav@1679: .library() jaroslav@1679: //.obfuscation(ObfuscationLevel.FULL) jaroslav@1679: .resources(dirRes); jaroslav@1679: } jaroslav@1679: jaroslav@1679: private static void listDir( jaroslav@1679: File f, String pref, EmulationResources res, List resources jaroslav@1679: ) throws IOException { jaroslav@1679: File[] arr = f.listFiles(); jaroslav@1679: if (arr == null) { jaroslav@1679: if (f.getName().endsWith(".class")) { jaroslav@1679: res.addClassResource(pref + f.getName()); jaroslav@1679: } else { jaroslav@1679: resources.add(pref + f.getName()); jaroslav@1679: } jaroslav@1679: } else { jaroslav@1679: for (File ch : arr) { jaroslav@1679: listDir(ch, pref == null ? "" : pref + f.getName() + "/", res, resources); jaroslav@1679: } jaroslav@1679: } jaroslav@1679: } jaroslav@1678: jaroslav@1599: }