rt/vm/src/main/java/org/apidesign/vm4brwsr/ClosureWrapper.java
author Lubomir Nerad <lubomir.nerad@oracle.com>
Fri, 12 Apr 2013 18:48:48 +0200
branchclosure
changeset 967 f19f17f8f8dc
parent 882 60d9ea48ec99
child 1029 b1fe994d4267
permissions -rw-r--r--
Introduced Exported annotation
     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.vm4brwsr;
    19 
    20 import com.google.javascript.jscomp.CommandLineRunner;
    21 import com.google.javascript.jscomp.SourceFile;
    22 import java.io.IOException;
    23 import java.io.InputStream;
    24 import java.io.OutputStream;
    25 import java.io.PrintStream;
    26 import java.util.ArrayList;
    27 import java.util.Arrays;
    28 import java.util.Collection;
    29 import java.util.Collections;
    30 import java.util.HashMap;
    31 import java.util.List;
    32 import java.util.Map;
    33 import org.apidesign.bck2brwsr.core.ExtraJavaScript;
    34 import org.apidesign.vm4brwsr.ByteCodeParser.AnnotationParser;
    35 import org.apidesign.vm4brwsr.ByteCodeParser.ClassData;
    36 import org.apidesign.vm4brwsr.ByteCodeParser.FieldData;
    37 import org.apidesign.vm4brwsr.ByteCodeParser.MethodData;
    38 
    39 /**
    40  *
    41  * @author Jaroslav Tulach <jtulach@netbeans.org>
    42  */
    43 @ExtraJavaScript(processByteCode = false, resource="")
    44 final class ClosureWrapper extends CommandLineRunner {
    45     private static final String[] ARGS = { "--compilation_level", "SIMPLE_OPTIMIZATIONS", "--js", "bck2brwsr-raw.js" /*, "--debug", "--formatting", "PRETTY_PRINT" */ };
    46 
    47     private final ClosuresObfuscationDelegate obfuscationDelegate;
    48     private final Bck2Brwsr.Resources res;
    49     private final StringArray classes;
    50 
    51     private String compiledCode;
    52     private String externsCode;
    53 
    54     private ClosureWrapper(Appendable out, 
    55                            String compilationLevel,
    56                            ClosuresObfuscationDelegate obfuscationDelegate,
    57                            Bck2Brwsr.Resources res, StringArray classes) {
    58         super(
    59             generateArguments(compilationLevel),
    60             new PrintStream(new APS(out)), System.err
    61         );
    62         this.obfuscationDelegate = obfuscationDelegate;
    63         this.res = res;
    64         this.classes = classes;
    65     }
    66 
    67     @Override
    68     protected List<SourceFile> createInputs(List<String> files, boolean allowStdIn) throws FlagUsageException, IOException {
    69         if (files.size() != 1 || !"bck2brwsr-raw.js".equals(files.get(0))) {
    70             throw new IOException("Unexpected files: " + files);
    71         }
    72         return Collections.nCopies(
    73                    1,
    74                    SourceFile.fromGenerator(
    75                        "bck2brwsr-raw.js",
    76                        new SourceFile.Generator() {
    77                            @Override
    78                            public String getCode() {
    79                                return getCompiledCode();
    80                            }
    81                        }));
    82     }
    83 
    84 
    85     @Override
    86     protected List<SourceFile> createExterns()
    87             throws FlagUsageException, IOException {
    88         final List<SourceFile> externsFiles =
    89                 new ArrayList<SourceFile>(super.createExterns());
    90 
    91         externsFiles.add(
    92                 SourceFile.fromGenerator(
    93                         "bck2brwsr_externs.js",
    94                         new SourceFile.Generator() {
    95                             @Override
    96                             public String getCode() {
    97                                 return getExternsCode();
    98                             }
    99                         }));
   100         return externsFiles;
   101     }
   102 
   103     private String getCompiledCode() {
   104         if (compiledCode == null) {
   105             StringBuilder sb = new StringBuilder();
   106             try {
   107                 VM.compile(res, sb, classes, obfuscationDelegate);
   108                 compiledCode = sb.toString();
   109             } catch (IOException ex) {
   110                 compiledCode = ex.getMessage();
   111             }
   112         }
   113         return compiledCode;
   114     }
   115 
   116     private String getExternsCode() {
   117         if (externsCode == null) {
   118             // need compiled code at this point
   119             getCompiledCode();
   120 
   121             final StringBuilder sb = new StringBuilder("function RAW() {};\n");
   122             for (final String extern: obfuscationDelegate.getExterns()) {
   123                 sb.append("RAW.prototype.").append(extern).append(";\n");
   124             }
   125             externsCode = sb.toString();
   126         }
   127         return externsCode;
   128     }
   129 
   130     private static final class APS extends OutputStream {
   131         private final Appendable out;
   132 
   133         public APS(Appendable out) {
   134             this.out = out;
   135         }
   136         @Override
   137         public void write(int b) throws IOException {
   138             out.append((char)b);
   139         }
   140     }
   141 
   142     private static String[] generateArguments(String compilationLevel) {
   143         String[] finalArgs = ARGS.clone();
   144         finalArgs[1] = compilationLevel;
   145 
   146         return finalArgs;
   147     }
   148 
   149     static int produceTo(Appendable w, ObfuscationLevel obfuscationLevel, Bck2Brwsr.Resources resources, StringArray arr) throws IOException {
   150         ClosureWrapper cw = create(w, obfuscationLevel, resources, arr);
   151         try {
   152             return cw.doRun();
   153         } catch (FlagUsageException ex) {
   154             throw new IOException(ex);
   155         }
   156     }
   157 
   158     private static ClosureWrapper create(Appendable w,
   159                                          ObfuscationLevel obfuscationLevel,
   160                                          Bck2Brwsr.Resources resources,
   161                                          StringArray arr) {
   162         switch (obfuscationLevel) {
   163             case MINIMAL:
   164                 return new ClosureWrapper(w, "SIMPLE_OPTIMIZATIONS",
   165                                           new SimpleObfuscationDelegate(),
   166                                           resources, arr);
   167 /*                
   168             case MEDIUM:
   169                 return new ClosureWrapper(w, "ADVANCED_OPTIMIZATIONS",
   170                                           new MediumObfuscationDelegate(
   171                                                   resources),
   172                                           resources, arr);
   173 */
   174             case FULL:
   175                 return new ClosureWrapper(w, "ADVANCED_OPTIMIZATIONS",
   176                                           new FullObfuscationDelegate(
   177                                                   resources),
   178                                           resources, arr);
   179             default:
   180                 throw new IllegalArgumentException(
   181                         "Unsupported level: " + obfuscationLevel);
   182         }
   183     }
   184 
   185     private static abstract class ClosuresObfuscationDelegate
   186             extends ObfuscationDelegate {
   187         public abstract Collection<String> getExterns();
   188     }
   189 
   190     private static final class SimpleObfuscationDelegate
   191             extends ClosuresObfuscationDelegate {
   192         @Override
   193         public void exportJSProperty(Appendable out,
   194                                      String destObject,
   195                                      String propertyName) throws IOException {
   196         }
   197 
   198         @Override
   199         public void exportClass(Appendable out,
   200                                 String destObject,
   201                                 String mangledName,
   202                                 ClassData classData) throws IOException {
   203         }
   204 
   205         @Override
   206         public void exportMethod(Appendable out,
   207                                  String destObject,
   208                                  String mangledName,
   209                                  MethodData methodData) throws IOException {
   210         }
   211 
   212         @Override
   213         public void exportField(Appendable out,
   214                                 String destObject,
   215                                 String mangledName,
   216                                 FieldData fieldData) throws IOException {
   217         }
   218 
   219         @Override
   220         public Collection<String> getExterns() {
   221             return Collections.EMPTY_LIST;
   222         }
   223     }
   224 
   225     private static abstract class AdvancedObfuscationDelegate
   226             extends ClosuresObfuscationDelegate {
   227         private static final String[] INITIAL_EXTERNS = {
   228             "bck2brwsr",
   229             "$class",
   230             "anno",
   231             "array",
   232             "access",
   233             "cls",
   234             "vm",
   235             "loadClass",
   236             "loadBytes",
   237             "jvmName",
   238             "primitive",
   239             "superclass",
   240             "cnstr",
   241             "add32",
   242             "sub32",
   243             "mul32",
   244             "neg32",
   245             "toInt8",
   246             "toInt16",
   247             "next32",
   248             "high32",
   249             "toInt32",
   250             "toFP",
   251             "toLong",
   252             "toExactString",
   253             "add64",
   254             "sub64",
   255             "mul64",
   256             "and64",
   257             "or64",
   258             "xor64",
   259             "shl64",
   260             "shr64",
   261             "ushr64",
   262             "compare64",
   263             "neg64",
   264             "div32",
   265             "mod32",
   266             "div64",
   267             "mod64",
   268             "at",
   269             "getClass__Ljava_lang_Class_2",
   270             "clone__Ljava_lang_Object_2"
   271         };
   272 
   273         private final Bck2Brwsr.Resources resources;
   274 
   275         private final Collection<String> externs;
   276         private final Map<Object, Boolean> isMarkedAsExportedCache;
   277 
   278         protected AdvancedObfuscationDelegate(Bck2Brwsr.Resources resources) {
   279             this.resources = resources;
   280 
   281             externs = new ArrayList<String>(Arrays.asList(INITIAL_EXTERNS));
   282             isMarkedAsExportedCache = new HashMap<Object, Boolean>();
   283         }
   284 
   285         @Override
   286         public void exportClass(Appendable out,
   287                                 String destObject,
   288                                 String mangledName,
   289                                 ClassData classData) throws IOException {
   290             if (isExportedClass(classData)) {
   291                 exportJSProperty(out, destObject, mangledName);
   292             }
   293         }
   294 
   295         @Override
   296         public void exportMethod(Appendable out,
   297                                  String destObject,
   298                                  String mangledName,
   299                                  MethodData methodData) throws IOException {
   300             if (isAccessible(methodData.access)
   301                         && isExportedClass(methodData.cls)
   302                     || isMarkedAsExported(methodData)) {
   303                 exportJSProperty(out, destObject, mangledName);
   304             }
   305         }
   306 
   307         @Override
   308         public void exportField(Appendable out,
   309                                 String destObject,
   310                                 String mangledName,
   311                                 FieldData fieldData) throws IOException {
   312             if (isAccessible(fieldData.access)
   313                         && isExportedClass(fieldData.cls)
   314                     || isMarkedAsExported(fieldData)) {
   315                 exportJSProperty(out, destObject, mangledName);
   316             }
   317         }
   318 
   319         @Override
   320         public Collection<String> getExterns() {
   321             return externs;
   322         }
   323 
   324         protected void addExtern(String extern) {
   325             externs.add(extern);
   326         }
   327 
   328         private boolean isExportedClass(ClassData classData)
   329                 throws IOException {
   330             return classData.isPublic() && isMarkedAsExportedPackage(
   331                                                classData.getPkgName())
   332                        || isMarkedAsExported(classData);
   333         }
   334 
   335         private boolean isMarkedAsExportedPackage(String pkgName) {
   336             if (pkgName == null) {
   337                 return false;
   338             }
   339 
   340             final Boolean cachedValue = isMarkedAsExportedCache.get(pkgName);
   341             if (cachedValue != null) {
   342                 return cachedValue;
   343             }
   344 
   345             final boolean newValue = resolveIsMarkedAsExportedPackage(pkgName);
   346             isMarkedAsExportedCache.put(pkgName, newValue);
   347 
   348             return newValue;
   349         }
   350 
   351         private boolean isMarkedAsExported(ClassData classData)
   352                 throws IOException {
   353             final Boolean cachedValue = isMarkedAsExportedCache.get(classData);
   354             if (cachedValue != null) {
   355                 return cachedValue;
   356             }
   357 
   358             final boolean newValue =
   359                     isMarkedAsExported(classData.findAnnotationData(true),
   360                                        classData);
   361             isMarkedAsExportedCache.put(classData, newValue);
   362 
   363             return newValue;
   364         }
   365 
   366         private boolean isMarkedAsExported(MethodData methodData)
   367                 throws IOException {
   368             return isMarkedAsExported(methodData.findAnnotationData(true),
   369                                       methodData.cls);
   370         }
   371 
   372         private boolean isMarkedAsExported(FieldData fieldData)
   373                 throws IOException {
   374             return isMarkedAsExported(fieldData.findAnnotationData(true),
   375                                       fieldData.cls);
   376         }
   377 
   378         private boolean resolveIsMarkedAsExportedPackage(String pkgName) {
   379             try {
   380                 final InputStream is =
   381                         resources.get(pkgName + "/package-info.class");
   382 
   383                 try {
   384                     final ClassData pkgInfoClass = new ClassData(is);
   385                     return isMarkedAsExported(
   386                                    pkgInfoClass.findAnnotationData(true),
   387                                    pkgInfoClass);
   388                 } finally {
   389                     is.close();
   390                 }
   391             } catch (final IOException e) {
   392                 return false;
   393             }
   394         }
   395 
   396         private boolean isMarkedAsExported(byte[] arrData, ClassData cd)
   397                 throws IOException {
   398             if (arrData == null) {
   399                 return false;
   400             }
   401 
   402             final boolean[] found = { false };
   403             final AnnotationParser annotationParser =
   404                     new AnnotationParser(false, false) {
   405                         @Override
   406                         protected void visitAnnotationStart(
   407                                 String type,
   408                                 boolean top) {
   409                             if (top && type.equals("Lorg/apidesign/bck2brwsr"
   410                                                        + "/core/Exported;")) {
   411                                 found[0] = true;
   412                             }
   413                         }
   414                     };
   415             annotationParser.parse(arrData, cd);
   416             return found[0];
   417         }
   418 
   419         private static boolean isAccessible(int access) {
   420             return (access & (ByteCodeParser.ACC_PUBLIC
   421                                   | ByteCodeParser.ACC_PROTECTED)) != 0;
   422         }
   423     }
   424 
   425     private static final class MediumObfuscationDelegate
   426             extends AdvancedObfuscationDelegate {
   427         public MediumObfuscationDelegate(Bck2Brwsr.Resources resources) {
   428             super(resources);
   429         }
   430 
   431         @Override
   432         public void exportJSProperty(Appendable out,
   433                                      String destObject,
   434                                      String propertyName) {
   435             addExtern(propertyName);
   436         }
   437     }
   438 
   439     private static final class FullObfuscationDelegate
   440             extends AdvancedObfuscationDelegate {
   441         public FullObfuscationDelegate(Bck2Brwsr.Resources resources) {
   442             super(resources);
   443         }
   444 
   445         @Override
   446         public void exportJSProperty(Appendable out,
   447                                      String destObject,
   448                                      String propertyName) throws IOException {
   449             out.append("\n").append(destObject).append("['")
   450                                                .append(propertyName)
   451                                                .append("'] = ")
   452                             .append(destObject).append(".").append(propertyName)
   453                .append(";\n");
   454         }
   455     }
   456 }