rt/aot/src/main/java/org/apidesign/bck2brwsr/aot/Bck2BrwsrJars.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Sun, 23 Nov 2014 21:59:21 +0100
changeset 1728 1d850aa501bb
parent 1724 50ad005d1597
child 1729 384468666b2d
permissions -rw-r--r--
Recognizes NetBeans manifest tags
     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             return;
   191         }
   192         exp = mainAttr.getValue("OpenIDE-Module-Public-Packages");
   193         if (exp != null) {
   194             for (String def : exp.split(",")) {
   195                 def = def.trim();
   196                 if (def.endsWith(".*")) {
   197                     keep.add(def.substring(0, def.length() - 1).replace('.', '/'));
   198                 }
   199             }
   200         }
   201     }
   202     
   203     static byte[] readFrom(InputStream is) throws IOException {
   204         int expLen = is.available();
   205         if (expLen < 1) {
   206             expLen = 1;
   207         }
   208         byte[] arr = new byte[expLen];
   209         int pos = 0;
   210         for (;;) {
   211             int read = is.read(arr, pos, arr.length - pos);
   212             if (read == -1) {
   213                 break;
   214             }
   215             pos += read;
   216             if (pos == arr.length) {
   217                 byte[] tmp = new byte[arr.length * 2];
   218                 System.arraycopy(arr, 0, tmp, 0, arr.length);
   219                 arr = tmp;
   220             }
   221         }
   222         if (pos != arr.length) {
   223             byte[] tmp = new byte[pos];
   224             System.arraycopy(arr, 0, tmp, 0, pos);
   225             arr = tmp;
   226         }
   227         return arr;
   228     }
   229     
   230 
   231     static class EmulationResources implements Bck2Brwsr.Resources {
   232         private final List<String> classes;
   233         private final Map<String,byte[]> converted = new HashMap<>();
   234         private final BytecodeProcessor proc;
   235         private final ClassLoader cp;
   236 
   237         protected EmulationResources(ClassLoader cp, List<String> classes) {
   238             this.classes = classes;
   239             this.cp = cp != null ? cp : Bck2BrwsrJars.class.getClassLoader();
   240             BytecodeProcessor p;
   241             try {
   242                 Class<?> bpClass = Class.forName("org.apidesign.bck2brwsr.aot.RetroLambda");
   243                 p = (BytecodeProcessor) bpClass.newInstance();
   244             } catch (Throwable t) {
   245                 p = null;
   246             }
   247             this.proc = p;
   248         }
   249 
   250         protected final InputStream getConverted(String name) throws IOException {
   251             byte[] arr = converted.get(name);
   252             if (arr != null) {
   253                 return new ByteArrayInputStream(arr);
   254             }
   255             return null;
   256         }
   257         
   258         @Override
   259         public InputStream get(String name) throws IOException {
   260             InputStream is = getConverted(name);
   261             if (is != null) {
   262                 return is;
   263             }
   264             Enumeration<URL> en = cp.getResources(name);
   265             URL u = null;
   266             while (en.hasMoreElements()) {
   267                 u = en.nextElement();
   268             }
   269             if (u == null) {
   270                 LOG.log(Level.WARNING, "Cannot find {0}", name);
   271                 return null;
   272             }
   273             if (u.toExternalForm().contains("/rt.jar!")) {
   274                 LOG.log(Level.WARNING, "{0}No bootdelegation for ", name);
   275                 return null;
   276             }
   277             return u.openStream();
   278         }
   279 
   280         private void addClassResource(String n) throws IOException {
   281             if (proc != null) {
   282                 try (InputStream is = this.get(n)) {
   283                     Map<String, byte[]> conv = proc.process(n, readFrom(is), this);
   284                     if (conv != null) {
   285                         boolean found = false;
   286                         for (Map.Entry<String, byte[]> entrySet : conv.entrySet()) {
   287                             String res = entrySet.getKey();
   288                             byte[] bytes = entrySet.getValue();
   289                             if (res.equals(n)) {
   290                                 found = true;
   291                             }
   292                             assert res.endsWith(".class") : "Wrong resource: " + res;
   293                             converted.put(res, bytes);
   294                             classes.add(res.substring(0, res.length() - 6));
   295                         }
   296                         if (!found) {
   297                             throw new IOException("Cannot find " + n + " among " + conv);
   298                         }
   299                         return;
   300                     }
   301                 }
   302             }
   303             classes.add(n.substring(0, n.length() - 6));
   304         }
   305     }
   306     
   307     private static Bck2Brwsr configureDir(Bck2Brwsr c, final File dir, ClassLoader cp) throws IOException {
   308         List<String> arr = new ArrayList<>();
   309         List<String> classes = new ArrayList<>();
   310         class DirRes extends EmulationResources {
   311             public DirRes(ClassLoader cp, List<String> classes) {
   312                 super(cp, classes);
   313             }
   314 
   315             @Override
   316             public InputStream get(String name) throws IOException {
   317                 InputStream is = super.get(name);
   318                 if (is != null) {
   319                     return is;
   320                 }
   321                 File r = new File(dir, name.replace('/', File.separatorChar));
   322                 if (r.exists()) {
   323                     return new FileInputStream(r);
   324                 }
   325                 return null;
   326             }
   327         }
   328         DirRes dirRes = new DirRes(cp, classes);
   329         listDir(dir, null, dirRes, arr);
   330         if (c == null) {
   331             c = Bck2Brwsr.newCompiler();
   332         }
   333         return c
   334         .addRootClasses(classes.toArray(new String[0]))
   335         .addResources(arr.toArray(new String[0]))
   336         .library()
   337         //.obfuscation(ObfuscationLevel.FULL)
   338         .resources(dirRes);
   339     }
   340 
   341     private static void listDir(
   342         File f, String pref, EmulationResources res, List<String> resources
   343     ) throws IOException {
   344         File[] arr = f.listFiles();
   345         if (arr == null) {
   346             if (f.getName().endsWith(".class")) {
   347                 res.addClassResource(pref + f.getName());
   348             } else {
   349                 resources.add(pref + f.getName());
   350             }
   351         } else {
   352             for (File ch : arr) {
   353                 listDir(ch, pref == null ? "" : pref + f.getName() + "/", res, resources);
   354             }
   355         }
   356     }
   357     
   358 }