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