launcher/fx/src/main/java/org/apidesign/bck2brwsr/launcher/fximpl/JsClassLoader.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Thu, 13 Jun 2013 16:48:17 +0200
branchclassloader
changeset 1175 15c6903c8612
parent 1174 f78cdceed17b
child 1176 0250b8a739de
permissions -rw-r--r--
New classloader can be used to load the twitter application
     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.launcher.fximpl;
    19 
    20 import java.io.IOException;
    21 import java.io.InputStream;
    22 import java.net.URL;
    23 import java.net.URLConnection;
    24 import java.util.ArrayList;
    25 import java.util.Enumeration;
    26 import java.util.List;
    27 import org.objectweb.asm.AnnotationVisitor;
    28 import org.objectweb.asm.ClassReader;
    29 import org.objectweb.asm.ClassVisitor;
    30 import org.objectweb.asm.ClassWriter;
    31 import org.objectweb.asm.Label;
    32 import org.objectweb.asm.MethodVisitor;
    33 import org.objectweb.asm.Opcodes;
    34 import org.objectweb.asm.Type;
    35 import org.objectweb.asm.signature.SignatureReader;
    36 import org.objectweb.asm.signature.SignatureVisitor;
    37 
    38 /** 
    39  *
    40  * @author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    41  */
    42 abstract class JsClassLoader extends ClassLoader {
    43     JsClassLoader(ClassLoader parent) {
    44         super(parent);
    45     }
    46     
    47     @Override
    48     protected abstract URL findResource(String name);
    49     
    50     @Override
    51     protected abstract Enumeration<URL> findResources(String name);
    52 
    53     @Override
    54     protected Class<?> findClass(String name) throws ClassNotFoundException {
    55         URL u = findResource(name.replace('.', '/') + ".class");
    56         if (u != null) {
    57             InputStream is = null;
    58             try {
    59                 is = u.openStream();
    60                 byte[] arr = new byte[is.available()];
    61                 int len = is.read(arr);
    62                 if (len != arr.length) {
    63                     arr = null;
    64                 }
    65                 is.close();
    66                 is = null;
    67                 ClassReader cr = new ClassReader(arr);
    68                 FindInClass tst = new FindInClass(null);
    69                 cr.accept(tst, 0);
    70                 if (tst.found) {
    71                     ClassWriter w = new ClassWriterEx(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
    72                     FindInClass fic = new FindInClass(w);
    73                     cr.accept(fic, 0);
    74                     arr = w.toByteArray();
    75                 }
    76                 if (arr != null) {
    77                     return defineClass(name, arr, 0, arr.length);
    78                 }
    79             } catch (IOException ex) {
    80                 throw new ClassNotFoundException("Can't load " + name, ex);
    81             } finally {
    82                 try {
    83                     if (is != null) is.close();
    84                 } catch (IOException ex) {
    85                     throw new ClassNotFoundException(null, ex);
    86                 }
    87             }
    88         }
    89         if (name.startsWith("org.apidesign.bck2brwsr.launcher.fximpl.Fn")) {
    90             return Class.forName(name);
    91         }
    92         
    93         return super.findClass(name);
    94     }
    95     
    96     protected abstract Fn defineFn(String code, String... names);
    97     
    98     
    99     private static final class FindInClass extends ClassVisitor {
   100         private String name;
   101         private boolean found;
   102         
   103         public FindInClass(ClassVisitor cv) {
   104             super(Opcodes.ASM4, cv);
   105         }
   106 
   107         @Override
   108         public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
   109             this.name = name;
   110             super.visit(version, access, name, signature, superName, interfaces);
   111         }
   112         
   113         
   114 
   115         @Override
   116         public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
   117             return new FindInMethod(name, desc,
   118                 super.visitMethod(access, name, desc, signature, exceptions)
   119             );
   120         }
   121         
   122         private final class FindInMethod extends MethodVisitor {
   123             private final String name;
   124             private final String desc;
   125             private List<String> args;
   126             private String body;
   127             
   128             public FindInMethod(String name, String desc, MethodVisitor mv) {
   129                 super(Opcodes.ASM4, mv);
   130                 this.name = name;
   131                 this.desc = desc;
   132             }
   133 
   134             @Override
   135             public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
   136                 if ("Lorg/apidesign/bck2brwsr/core/JavaScriptBody;".equals(desc)) { // NOI18N
   137                     found = true;
   138                     return new FindInAnno();
   139                 }
   140                 return super.visitAnnotation(desc, visible);
   141             }
   142 
   143             private void generateJSBody(List<String> args, String body) {
   144                 this.args = args;
   145                 this.body = body;
   146             }
   147             
   148             @Override
   149             public void visitCode() {
   150                 if (body == null) {
   151                     return;
   152                 } 
   153                 
   154                 super.visitFieldInsn(
   155                     Opcodes.GETSTATIC, FindInClass.this.name, 
   156                     "$$bck2brwsr$$" + name, 
   157                     "Lorg/apidesign/bck2brwsr/launcher/fximpl/Fn;"
   158                 );
   159                 super.visitInsn(Opcodes.DUP);
   160                 Label ifNotNull = new Label();
   161                 super.visitJumpInsn(Opcodes.IFNONNULL, ifNotNull);
   162                 
   163                 // init Fn
   164                 super.visitInsn(Opcodes.POP);
   165                 super.visitLdcInsn(Type.getObjectType(FindInClass.this.name));
   166                 super.visitLdcInsn(body);
   167                 super.visitIntInsn(Opcodes.SIPUSH, args.size());
   168                 super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String");
   169                 for (int i = 0; i < args.size(); i++) {
   170                     String name = args.get(i);
   171                     super.visitInsn(Opcodes.DUP);
   172                     super.visitIntInsn(Opcodes.BIPUSH, i);
   173                     super.visitLdcInsn(name);
   174                     super.visitInsn(Opcodes.AASTORE);
   175                 }
   176                 super.visitMethodInsn(Opcodes.INVOKESTATIC, 
   177                     "org/apidesign/bck2brwsr/launcher/fximpl/Fn", "define", 
   178                     "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/String;)Lorg/apidesign/bck2brwsr/launcher/fximpl/Fn;"
   179                 );
   180                 // end of Fn init
   181                 
   182                 super.visitLabel(ifNotNull);
   183                 super.visitIntInsn(Opcodes.SIPUSH, args.size());
   184                 super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
   185                 
   186                 class SV extends SignatureVisitor {
   187                     private boolean nowReturn;
   188                     private Type returnType;
   189                     private int index;
   190                     
   191                     public SV() {
   192                         super(Opcodes.ASM4);
   193                     }
   194                     
   195                     @Override
   196                     public void visitBaseType(char descriptor) {
   197                         final Type t = Type.getType("" + descriptor);
   198                         if (nowReturn) {
   199                             returnType = t;
   200                             return;
   201                         }
   202                         FindInMethod.super.visitInsn(Opcodes.DUP);
   203                         FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index);
   204                         FindInMethod.super.visitVarInsn(t.getOpcode(Opcodes.ILOAD), index);
   205                         String factory;
   206                         switch (descriptor) {
   207                         case 'I': factory = "java/lang/Integer"; break;
   208                         case 'J': factory = "java/lang/Long"; break;
   209                         case 'S': factory = "java/lang/Short"; break;
   210                         case 'F': factory = "java/lang/Float"; break;
   211                         case 'D': factory = "java/lang/Double"; break;
   212                         case 'Z': factory = "java/lang/Boolean"; break;
   213                         case 'C': factory = "java/lang/Character"; break;
   214                         case 'B': factory = "java/lang/Byte"; break;
   215                         default: throw new IllegalStateException(t.toString());
   216                         }
   217                         FindInMethod.super.visitMethodInsn(Opcodes.INVOKESTATIC,
   218                             factory, "valueOf", "(" + descriptor + ")L" + factory + ";"
   219                         );
   220                         FindInMethod.super.visitInsn(Opcodes.AASTORE);
   221                         index++;
   222                     }
   223 
   224                     @Override
   225                     public void visitClassType(String name) {
   226                         if (nowReturn) {
   227                             returnType = Type.getObjectType(name);
   228                             return;
   229                         }
   230                         FindInMethod.super.visitInsn(Opcodes.DUP);
   231                         FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index);
   232                         FindInMethod.super.visitVarInsn(Opcodes.ALOAD, index);
   233                         FindInMethod.super.visitInsn(Opcodes.AASTORE);
   234                         index++;
   235                     }
   236 
   237                     @Override
   238                     public SignatureVisitor visitReturnType() {
   239                         nowReturn = true;
   240                         return this;
   241                     }
   242                     
   243                     
   244                 }
   245                 SV sv = new SV();
   246                 SignatureReader sr = new SignatureReader(desc);
   247                 sr.accept(sv);
   248                 
   249                 super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, 
   250                     "org/apidesign/bck2brwsr/launcher/fximpl/Fn", "invoke", "([Ljava/lang/Object;)Ljava/lang/Object;"
   251                 );
   252                 switch (sv.returnType.getSort()) {
   253                 case Type.VOID: 
   254                     super.visitInsn(Opcodes.RETURN);
   255                     return;
   256                 case Type.ARRAY:
   257                 case Type.OBJECT:
   258                     super.visitTypeInsn(Opcodes.CHECKCAST, sv.returnType.getInternalName());
   259                     super.visitInsn(Opcodes.ARETURN);
   260                     return;
   261                 default:
   262                     super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Number");
   263                     super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, 
   264                         "java/lang/Number", sv.returnType.getClassName() + "Value", "()" + sv.returnType.getDescriptor()
   265                     );
   266                     super.visitInsn(sv.returnType.getOpcode(Opcodes.IRETURN));
   267                 }
   268             }
   269 
   270             @Override
   271             public void visitEnd() {
   272                 super.visitEnd();
   273                 if (body != null) {
   274                     FindInClass.this.visitField(
   275                         Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, 
   276                         "$$bck2brwsr$$" + name, 
   277                         "Lorg/apidesign/bck2brwsr/launcher/fximpl/Fn;", 
   278                         null, null
   279                     );
   280                 }
   281             }
   282             
   283             
   284             
   285             
   286         
   287             private final class FindInAnno extends AnnotationVisitor {
   288                 private List<String> args = new ArrayList<String>();
   289                 private String body;
   290 
   291                 public FindInAnno() {
   292                     super(Opcodes.ASM4);
   293                 }
   294 
   295                 @Override
   296                 public void visit(String name, Object value) {
   297                     if (name == null) {
   298                         args.add((String) value);
   299                         return;
   300                     }
   301                     assert name.equals("body");
   302                     body = (String) value;
   303                 }
   304 
   305                 @Override
   306                 public AnnotationVisitor visitArray(String name) {
   307                     return this;
   308                 }
   309 
   310                 @Override
   311                 public void visitEnd() {
   312                     if (body != null) {
   313                         generateJSBody(args, body);
   314                     }
   315                 }
   316             }
   317         }
   318     }
   319     
   320     private class ClassWriterEx extends ClassWriter {
   321 
   322         public ClassWriterEx(ClassReader classReader, int flags) {
   323             super(classReader, flags);
   324         }
   325         
   326         @Override
   327         protected String getCommonSuperClass(final String type1, final String type2) {
   328             Class<?> c, d;
   329             ClassLoader classLoader = JsClassLoader.this;
   330             try {
   331                 c = Class.forName(type1.replace('/', '.'), false, classLoader);
   332                 d = Class.forName(type2.replace('/', '.'), false, classLoader);
   333             } catch (Exception e) {
   334                 throw new RuntimeException(e.toString());
   335             }
   336             if (c.isAssignableFrom(d)) {
   337                 return type1;
   338             }
   339             if (d.isAssignableFrom(c)) {
   340                 return type2;
   341             }
   342             if (c.isInterface() || d.isInterface()) {
   343                 return "java/lang/Object";
   344             } else {
   345                 do {
   346                     c = c.getSuperclass();
   347                 } while (!c.isAssignableFrom(d));
   348                 return c.getName().replace('.', '/');
   349             }
   350         }        
   351     }
   352 }