rt/aot/src/main/java/org/apidesign/bck2brwsr/aot/Bck2BrwsrJars.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Thu, 20 Nov 2014 05:56:47 +0100
changeset 1724 50ad005d1597
parent 1710 6db177c4f72c
child 1728 1d850aa501bb
permissions -rw-r--r--
Test whether we can parse OSGi export package manifest tag
     1 /**
     2  * Back 2 Browser Bytecode Translator
     3  * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     4  *
     5  * This program is free software: you can redistribute it and/or modify
     6  * it under the terms of the GNU General Public License as published by
     7  * the Free Software Foundation, version 2 of the License.
     8  *
     9  * This program is distributed in the hope that it will be useful,
    10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  * GNU General Public License for more details.
    13  *
    14  * You should have received a copy of the GNU General Public License
    15  * along with this program. Look for COPYING file in the top folder.
    16  * If not, see http://opensource.org/licenses/GPL-2.0.
    17  */
    18 package org.apidesign.bck2brwsr.aot;
    19 
    20 import java.io.BufferedReader;
    21 import java.io.ByteArrayInputStream;
    22 import java.io.File;
    23 import java.io.FileInputStream;
    24 import java.io.IOException;
    25 import java.io.InputStream;
    26 import java.io.InputStreamReader;
    27 import java.net.URL;
    28 import java.util.ArrayList;
    29 import java.util.Enumeration;
    30 import java.util.HashMap;
    31 import java.util.HashSet;
    32 import java.util.List;
    33 import java.util.Map;
    34 import java.util.Set;
    35 import java.util.jar.Attributes;
    36 import java.util.jar.JarEntry;
    37 import java.util.jar.JarFile;
    38 import java.util.logging.Level;
    39 import java.util.logging.Logger;
    40 import java.util.zip.ZipEntry;
    41 import org.apidesign.vm4brwsr.Bck2Brwsr;
    42 
    43 /** Utilities to process JAR files and set a compiler
    44  * up.
    45  *
    46  * @since 0.9
    47  * @author Jaroslav Tulach
    48  */
    49 public final class Bck2BrwsrJars {
    50     private static final Logger LOG = Logger.getLogger(Bck2BrwsrJars.class.getName());
    51 
    52     private Bck2BrwsrJars() {
    53     }
    54     
    55     /** Creates new compiler pre-configured from the content of 
    56      * provided JAR file. The compiler will compile all classes.
    57      * The system understands OSGi manifest entries and will export
    58      * all packages that are exported in the JAR file. The system
    59      * also recognizes META-INF/services and makes sure the class names
    60      * are not mangled.
    61      * 
    62      * @param c the compiler to {@link Bck2Brwsr#addClasses(java.lang.String...) add classes},
    63      *    {@link Bck2Brwsr#addResources(java.lang.String...) add resources} and
    64      *    {@link Bck2Brwsr#addExported(java.lang.String...) exported objects} to.
    65      *    Can be <code>null</code> - in such case an 
    66      *    {@link Bck2Brwsr#newCompiler() empty compiler} is constructed.
    67      * @param jar the file to process
    68      * @return newly configured compiler
    69      * @throws IOException if something goes wrong
    70      */
    71     public static Bck2Brwsr configureFrom(Bck2Brwsr c, File jar) throws IOException {
    72         return configureFrom(c, jar, null);
    73     }
    74     
    75     /** Creates new compiler pre-configured from the content of 
    76      * provided JAR file. The compiler will compile all classes.
    77      * The system understands OSGi manifest entries and will export
    78      * all packages that are exported in the JAR file. The system
    79      * also recognizes META-INF/services and makes sure the class names
    80      * are not mangled.
    81      * 
    82      * @param c the compiler to {@link Bck2Brwsr#addClasses(java.lang.String...) add classes},
    83      *    {@link Bck2Brwsr#addResources(java.lang.String...) add resources} and
    84      *    {@link Bck2Brwsr#addExported(java.lang.String...) exported objects} to.
    85      *    Can be <code>null</code> - in such case an 
    86      *    {@link Bck2Brwsr#newCompiler() empty compiler} is constructed.
    87      * @param jar the file to process
    88      * @param classpath additional resources to make available during
    89      *   compilation, but not include them in the generated JavaScript
    90      * @return newly configured compiler
    91      * @throws IOException if something goes wrong
    92      * @since 0.11
    93      */
    94     public static Bck2Brwsr configureFrom(
    95         Bck2Brwsr c, File jar, final ClassLoader classpath
    96     ) throws IOException {
    97         if (jar.isDirectory()) {
    98             return configureDir(c, jar, classpath);
    99         }
   100         final JarFile jf = new JarFile(jar);
   101         final List<String> classes = new ArrayList<>();
   102         List<String> resources = new ArrayList<>();
   103         Set<String> exported = new HashSet<>();
   104         class JarRes extends EmulationResources implements Bck2Brwsr.Resources {
   105             JarRes() {
   106                 super(classpath, classes);
   107             }
   108             @Override
   109             public InputStream get(String resource) throws IOException {
   110                 InputStream is = getConverted(resource);
   111                 if (is != null) {
   112                     return is;
   113                 }
   114                 is = jf.getInputStream(new ZipEntry(resource));
   115                 return is == null ? super.get(resource) : is;
   116             }
   117         }
   118         JarRes jarRes = new JarRes();
   119 
   120         listJAR(jf, jarRes, resources, exported);
   121         
   122         String cp = jf.getManifest().getMainAttributes().getValue("Class-Path"); // NOI18N
   123         String[] parts = cp == null ? new String[0] : cp.split(" ");
   124 
   125         if (c == null) {
   126             c = Bck2Brwsr.newCompiler();
   127         }
   128         
   129         return c
   130             .library(parts)
   131             .addClasses(classes.toArray(new String[classes.size()]))
   132             .addExported(exported.toArray(new String[exported.size()]))
   133             .addResources(resources.toArray(new String[resources.size()]))
   134             .resources(jarRes);
   135     }
   136     
   137     private static void listJAR(
   138         JarFile j, EmulationResources classes,
   139         List<String> resources, Set<String> keep
   140     ) throws IOException {
   141         Enumeration<JarEntry> en = j.entries();
   142         while (en.hasMoreElements()) {
   143             JarEntry e = en.nextElement();
   144             final String n = e.getName();
   145             if (n.endsWith("/")) {
   146                 continue;
   147             }
   148             if (n.startsWith("META-INF/maven/")) {
   149                 continue;
   150             }
   151             int last = n.lastIndexOf('/');
   152             String pkg = n.substring(0, last + 1);
   153             if (pkg.startsWith("java/")) {
   154                 keep.add(pkg);
   155             }
   156             if (n.endsWith(".class")) {
   157                 classes.addClassResource(n);
   158             } else {
   159                 resources.add(n);
   160                 if (n.startsWith("META-INF/services/") && keep != null) {
   161                     BufferedReader r = new BufferedReader(new InputStreamReader(j.getInputStream(e)));
   162                     for (;;) {
   163                         String l = r.readLine();
   164                         if (l == null) {
   165                             break;
   166                         }
   167                         if (l.startsWith("#")) {
   168                             continue;
   169                         }
   170                         keep.add(l.replace('.', '/'));
   171                     }
   172                 }
   173             }
   174         }
   175         if (keep != null) {
   176             final Attributes mainAttr = j.getManifest().getMainAttributes();
   177             exportPublicPackages(mainAttr, keep);
   178         }
   179     }
   180 
   181     static void exportPublicPackages(final Attributes mainAttr, Set<String> keep) {
   182         String exp = mainAttr.getValue("Export-Package"); // NOI18N
   183         if (exp != null) {
   184             for (String def : exp.split(",")) {
   185                 for (String sep : def.split(";")) {
   186                     keep.add(sep.replace('.', '/') + "/");
   187                     break;
   188                 }
   189             }
   190         }
   191     }
   192     
   193     static byte[] readFrom(InputStream is) throws IOException {
   194         int expLen = is.available();
   195         if (expLen < 1) {
   196             expLen = 1;
   197         }
   198         byte[] arr = new byte[expLen];
   199         int pos = 0;
   200         for (;;) {
   201             int read = is.read(arr, pos, arr.length - pos);
   202             if (read == -1) {
   203                 break;
   204             }
   205             pos += read;
   206             if (pos == arr.length) {
   207                 byte[] tmp = new byte[arr.length * 2];
   208                 System.arraycopy(arr, 0, tmp, 0, arr.length);
   209                 arr = tmp;
   210             }
   211         }
   212         if (pos != arr.length) {
   213             byte[] tmp = new byte[pos];
   214             System.arraycopy(arr, 0, tmp, 0, pos);
   215             arr = tmp;
   216         }
   217         return arr;
   218     }
   219     
   220 
   221     static class EmulationResources implements Bck2Brwsr.Resources {
   222         private final List<String> classes;
   223         private final Map<String,byte[]> converted = new HashMap<>();
   224         private final BytecodeProcessor proc;
   225         private final ClassLoader cp;
   226 
   227         protected EmulationResources(ClassLoader cp, List<String> classes) {
   228             this.classes = classes;
   229             this.cp = cp != null ? cp : Bck2BrwsrJars.class.getClassLoader();
   230             BytecodeProcessor p;
   231             try {
   232                 Class<?> bpClass = Class.forName("org.apidesign.bck2brwsr.aot.RetroLambda");
   233                 p = (BytecodeProcessor) bpClass.newInstance();
   234             } catch (Throwable t) {
   235                 p = null;
   236             }
   237             this.proc = p;
   238         }
   239 
   240         protected final InputStream getConverted(String name) throws IOException {
   241             byte[] arr = converted.get(name);
   242             if (arr != null) {
   243                 return new ByteArrayInputStream(arr);
   244             }
   245             return null;
   246         }
   247         
   248         @Override
   249         public InputStream get(String name) throws IOException {
   250             InputStream is = getConverted(name);
   251             if (is != null) {
   252                 return is;
   253             }
   254             Enumeration<URL> en = cp.getResources(name);
   255             URL u = null;
   256             while (en.hasMoreElements()) {
   257                 u = en.nextElement();
   258             }
   259             if (u == null) {
   260                 LOG.log(Level.WARNING, "Cannot find {0}", name);
   261                 return null;
   262             }
   263             if (u.toExternalForm().contains("/rt.jar!")) {
   264                 LOG.log(Level.WARNING, "{0}No bootdelegation for ", name);
   265                 return null;
   266             }
   267             return u.openStream();
   268         }
   269 
   270         private void addClassResource(String n) throws IOException {
   271             if (proc != null) {
   272                 try (InputStream is = this.get(n)) {
   273                     Map<String, byte[]> conv = proc.process(n, readFrom(is), this);
   274                     if (conv != null) {
   275                         boolean found = false;
   276                         for (Map.Entry<String, byte[]> entrySet : conv.entrySet()) {
   277                             String res = entrySet.getKey();
   278                             byte[] bytes = entrySet.getValue();
   279                             if (res.equals(n)) {
   280                                 found = true;
   281                             }
   282                             assert res.endsWith(".class") : "Wrong resource: " + res;
   283                             converted.put(res, bytes);
   284                             classes.add(res.substring(0, res.length() - 6));
   285                         }
   286                         if (!found) {
   287                             throw new IOException("Cannot find " + n + " among " + conv);
   288                         }
   289                         return;
   290                     }
   291                 }
   292             }
   293             classes.add(n.substring(0, n.length() - 6));
   294         }
   295     }
   296     
   297     private static Bck2Brwsr configureDir(Bck2Brwsr c, final File dir, ClassLoader cp) throws IOException {
   298         List<String> arr = new ArrayList<>();
   299         List<String> classes = new ArrayList<>();
   300         class DirRes extends EmulationResources {
   301             public DirRes(ClassLoader cp, List<String> classes) {
   302                 super(cp, classes);
   303             }
   304 
   305             @Override
   306             public InputStream get(String name) throws IOException {
   307                 InputStream is = super.get(name);
   308                 if (is != null) {
   309                     return is;
   310                 }
   311                 File r = new File(dir, name.replace('/', File.separatorChar));
   312                 if (r.exists()) {
   313                     return new FileInputStream(r);
   314                 }
   315                 return null;
   316             }
   317         }
   318         DirRes dirRes = new DirRes(cp, classes);
   319         listDir(dir, null, dirRes, arr);
   320         if (c == null) {
   321             c = Bck2Brwsr.newCompiler();
   322         }
   323         return c
   324         .addRootClasses(classes.toArray(new String[0]))
   325         .addResources(arr.toArray(new String[0]))
   326         .library()
   327         //.obfuscation(ObfuscationLevel.FULL)
   328         .resources(dirRes);
   329     }
   330 
   331     private static void listDir(
   332         File f, String pref, EmulationResources res, List<String> resources
   333     ) throws IOException {
   334         File[] arr = f.listFiles();
   335         if (arr == null) {
   336             if (f.getName().endsWith(".class")) {
   337                 res.addClassResource(pref + f.getName());
   338             } else {
   339                 resources.add(pref + f.getName());
   340             }
   341         } else {
   342             for (File ch : arr) {
   343                 listDir(ch, pref == null ? "" : pref + f.getName() + "/", res, resources);
   344             }
   345         }
   346     }
   347     
   348 }