2 * Back 2 Browser Bytecode Translator
3 * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
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.
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.
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.
18 package org.apidesign.vm4brwsr;
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;
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;
41 * @author Jaroslav Tulach <jtulach@netbeans.org>
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" */ };
47 private final ClosuresObfuscationDelegate obfuscationDelegate;
48 private final Bck2Brwsr.Resources res;
49 private final StringArray classes;
51 private String compiledCode;
52 private String externsCode;
54 private ClosureWrapper(Appendable out,
55 String compilationLevel,
56 ClosuresObfuscationDelegate obfuscationDelegate,
57 Bck2Brwsr.Resources res, StringArray classes) {
59 generateArguments(compilationLevel),
60 new PrintStream(new APS(out)), System.err
62 this.obfuscationDelegate = obfuscationDelegate;
64 this.classes = classes;
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);
72 return Collections.nCopies(
74 SourceFile.fromGenerator(
76 new SourceFile.Generator() {
78 public String getCode() {
79 return getCompiledCode();
86 protected List<SourceFile> createExterns()
87 throws FlagUsageException, IOException {
88 final List<SourceFile> externsFiles =
89 new ArrayList<SourceFile>(super.createExterns());
92 SourceFile.fromGenerator(
93 "bck2brwsr_externs.js",
94 new SourceFile.Generator() {
96 public String getCode() {
97 return getExternsCode();
103 private String getCompiledCode() {
104 if (compiledCode == null) {
105 StringBuilder sb = new StringBuilder();
107 VM.compile(res, sb, classes, obfuscationDelegate);
108 compiledCode = sb.toString();
109 } catch (IOException ex) {
110 compiledCode = ex.getMessage();
116 private String getExternsCode() {
117 if (externsCode == null) {
118 // need compiled code at this point
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");
125 externsCode = sb.toString();
130 private static final class APS extends OutputStream {
131 private final Appendable out;
133 public APS(Appendable out) {
137 public void write(int b) throws IOException {
142 private static String[] generateArguments(String compilationLevel) {
143 String[] finalArgs = ARGS.clone();
144 finalArgs[1] = compilationLevel;
149 static int produceTo(Appendable w, ObfuscationLevel obfuscationLevel, Bck2Brwsr.Resources resources, StringArray arr) throws IOException {
150 ClosureWrapper cw = create(w, obfuscationLevel, resources, arr);
153 } catch (FlagUsageException ex) {
154 throw new IOException(ex);
158 private static ClosureWrapper create(Appendable w,
159 ObfuscationLevel obfuscationLevel,
160 Bck2Brwsr.Resources resources,
162 switch (obfuscationLevel) {
164 return new ClosureWrapper(w, "SIMPLE_OPTIMIZATIONS",
165 new SimpleObfuscationDelegate(),
169 return new ClosureWrapper(w, "ADVANCED_OPTIMIZATIONS",
170 new MediumObfuscationDelegate(
175 return new ClosureWrapper(w, "ADVANCED_OPTIMIZATIONS",
176 new FullObfuscationDelegate(
180 throw new IllegalArgumentException(
181 "Unsupported level: " + obfuscationLevel);
185 private static abstract class ClosuresObfuscationDelegate
186 extends ObfuscationDelegate {
187 public abstract Collection<String> getExterns();
190 private static final class SimpleObfuscationDelegate
191 extends ClosuresObfuscationDelegate {
193 public void exportJSProperty(Appendable out,
195 String propertyName) throws IOException {
199 public void exportClass(Appendable out,
202 ClassData classData) throws IOException {
206 public void exportMethod(Appendable out,
209 MethodData methodData) throws IOException {
213 public void exportField(Appendable out,
216 FieldData fieldData) throws IOException {
220 public Collection<String> getExterns() {
221 return Collections.EMPTY_LIST;
225 private static abstract class AdvancedObfuscationDelegate
226 extends ClosuresObfuscationDelegate {
227 private static final String[] INITIAL_EXTERNS = {
269 "getClass__Ljava_lang_Class_2",
270 "clone__Ljava_lang_Object_2"
273 private final Bck2Brwsr.Resources resources;
275 private final Collection<String> externs;
276 private final Map<Object, Boolean> isMarkedAsExportedCache;
278 protected AdvancedObfuscationDelegate(Bck2Brwsr.Resources resources) {
279 this.resources = resources;
281 externs = new ArrayList<String>(Arrays.asList(INITIAL_EXTERNS));
282 isMarkedAsExportedCache = new HashMap<Object, Boolean>();
286 public void exportClass(Appendable out,
289 ClassData classData) throws IOException {
290 if (isExportedClass(classData)) {
291 exportJSProperty(out, destObject, mangledName);
296 public void exportMethod(Appendable out,
299 MethodData methodData) throws IOException {
300 if (isAccessible(methodData.access)
301 && isExportedClass(methodData.cls)
302 || isMarkedAsExported(methodData)) {
303 exportJSProperty(out, destObject, mangledName);
308 public void exportField(Appendable out,
311 FieldData fieldData) throws IOException {
312 if (isAccessible(fieldData.access)
313 && isExportedClass(fieldData.cls)
314 || isMarkedAsExported(fieldData)) {
315 exportJSProperty(out, destObject, mangledName);
320 public Collection<String> getExterns() {
324 protected void addExtern(String extern) {
328 private boolean isExportedClass(ClassData classData)
330 return classData.isPublic() && isMarkedAsExportedPackage(
331 classData.getPkgName())
332 || isMarkedAsExported(classData);
335 private boolean isMarkedAsExportedPackage(String pkgName) {
336 if (pkgName == null) {
340 final Boolean cachedValue = isMarkedAsExportedCache.get(pkgName);
341 if (cachedValue != null) {
345 final boolean newValue = resolveIsMarkedAsExportedPackage(pkgName);
346 isMarkedAsExportedCache.put(pkgName, newValue);
351 private boolean isMarkedAsExported(ClassData classData)
353 final Boolean cachedValue = isMarkedAsExportedCache.get(classData);
354 if (cachedValue != null) {
358 final boolean newValue =
359 isMarkedAsExported(classData.findAnnotationData(true),
361 isMarkedAsExportedCache.put(classData, newValue);
366 private boolean isMarkedAsExported(MethodData methodData)
368 return isMarkedAsExported(methodData.findAnnotationData(true),
372 private boolean isMarkedAsExported(FieldData fieldData)
374 return isMarkedAsExported(fieldData.findAnnotationData(true),
378 private boolean resolveIsMarkedAsExportedPackage(String pkgName) {
380 final InputStream is =
381 resources.get(pkgName + "/package-info.class");
384 final ClassData pkgInfoClass = new ClassData(is);
385 return isMarkedAsExported(
386 pkgInfoClass.findAnnotationData(true),
391 } catch (final IOException e) {
396 private boolean isMarkedAsExported(byte[] arrData, ClassData cd)
398 if (arrData == null) {
402 final boolean[] found = { false };
403 final AnnotationParser annotationParser =
404 new AnnotationParser(false, false) {
406 protected void visitAnnotationStart(
409 if (top && type.equals("Lorg/apidesign/bck2brwsr"
410 + "/core/Exported;")) {
415 annotationParser.parse(arrData, cd);
419 private static boolean isAccessible(int access) {
420 return (access & (ByteCodeParser.ACC_PUBLIC
421 | ByteCodeParser.ACC_PROTECTED)) != 0;
425 private static final class MediumObfuscationDelegate
426 extends AdvancedObfuscationDelegate {
427 public MediumObfuscationDelegate(Bck2Brwsr.Resources resources) {
432 public void exportJSProperty(Appendable out,
434 String propertyName) {
435 addExtern(propertyName);
439 private static final class FullObfuscationDelegate
440 extends AdvancedObfuscationDelegate {
441 public FullObfuscationDelegate(Bck2Brwsr.Resources resources) {
446 public void exportJSProperty(Appendable out,
448 String propertyName) throws IOException {
449 out.append("\n").append(destObject).append("['")
450 .append(propertyName)
452 .append(destObject).append(".").append(propertyName)