boot/src/main/java/org/apidesign/html/boot/impl/JsClassLoader.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Tue, 25 Jun 2013 21:09:48 +0200
branchclassloader
changeset 161 ea5ca9cc685d
parent 160 b5a06e2b3b92
child 163 2652760705d6
permissions -rw-r--r--
Can call method with two integer arguments from JavaScript
     1 /**
     2  * HTML via Java(tm) Language Bindings
     3  * Copyright (C) 2013 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. apidesign.org
    13  * designates this particular file as subject to the
    14  * "Classpath" exception as provided by apidesign.org
    15  * in the License file that accompanied this code.
    16  *
    17  * You should have received a copy of the GNU General Public License
    18  * along with this program. Look for COPYING file in the top folder.
    19  * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
    20  */
    21 package org.apidesign.html.boot.impl;
    22 
    23 import org.apidesign.html.boot.spi.Fn;
    24 import java.io.IOException;
    25 import java.io.InputStream;
    26 import java.net.URL;
    27 import java.util.ArrayList;
    28 import java.util.Enumeration;
    29 import java.util.List;
    30 import java.util.logging.Level;
    31 import java.util.logging.Logger;
    32 import org.objectweb.asm.AnnotationVisitor;
    33 import org.objectweb.asm.ClassReader;
    34 import org.objectweb.asm.ClassVisitor;
    35 import org.objectweb.asm.ClassWriter;
    36 import org.objectweb.asm.Label;
    37 import org.objectweb.asm.MethodVisitor;
    38 import org.objectweb.asm.Opcodes;
    39 import org.objectweb.asm.Type;
    40 import org.objectweb.asm.signature.SignatureReader;
    41 import org.objectweb.asm.signature.SignatureVisitor;
    42 import org.objectweb.asm.signature.SignatureWriter;
    43 
    44 /** 
    45  *
    46  * @author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
    47  */
    48 abstract class JsClassLoader extends ClassLoader {
    49     JsClassLoader(ClassLoader parent) {
    50         super(parent);
    51         setDefaultAssertionStatus(JsClassLoader.class.desiredAssertionStatus());
    52     }
    53     
    54     @Override
    55     protected abstract URL findResource(String name);
    56     
    57     @Override
    58     protected abstract Enumeration<URL> findResources(String name);
    59 
    60     @Override
    61     protected Class<?> findClass(String name) throws ClassNotFoundException {
    62         if (name.startsWith("javafx")) {
    63             return Class.forName(name);
    64         }
    65         if (name.startsWith("netscape")) {
    66             return Class.forName(name);
    67         }
    68         if (name.startsWith("com.sun")) {
    69             return Class.forName(name);
    70         }
    71         if (name.equals(JsClassLoader.class.getName())) {
    72             return JsClassLoader.class;
    73         }
    74         if (name.equals(Fn.class.getName())) {
    75             return Fn.class;
    76         }
    77         if (name.equals(FnUtils.class.getName())) {
    78             return FnUtils.class;
    79         }
    80         URL u = findResource(name.replace('.', '/') + ".class");
    81         if (u != null) {
    82             InputStream is = null;
    83             try {
    84                 is = u.openStream();
    85                 byte[] arr = new byte[is.available()];
    86                 int len = 0;
    87                 while (len < arr.length) {
    88                     int read = is.read(arr, len, arr.length - len);
    89                     if (read == -1) {
    90                         throw new IOException("Can't read " + u);
    91                     }
    92                     len += read;
    93                 }
    94                 is.close();
    95                 is = null;
    96                 ClassReader cr = new ClassReader(arr);
    97                 FindInClass tst = new FindInClass(null);
    98                 cr.accept(tst, 0);
    99                 if (tst.found > 0) {
   100                     ClassWriter w = new ClassWriterEx(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
   101                     FindInClass fic = new FindInClass(w);
   102                     cr.accept(fic, 0);
   103                     arr = w.toByteArray();
   104                 }
   105                 if (arr != null) {
   106                     return defineClass(name, arr, 0, arr.length);
   107                 }
   108             } catch (IOException ex) {
   109                 throw new ClassNotFoundException("Can't load " + name, ex);
   110             } finally {
   111                 try {
   112                     if (is != null) is.close();
   113                 } catch (IOException ex) {
   114                     throw new ClassNotFoundException(null, ex);
   115                 }
   116             }
   117         }
   118         if (
   119             name.equals("org.apidesign.html.boot.spi.Fn") ||
   120             name.equals("org.apidesign.html.boot.impl.FnUtils")
   121         ) {
   122             return Class.forName(name);
   123         }
   124         
   125         return super.findClass(name);
   126     }
   127     
   128     protected abstract Fn defineFn(String code, String... names);
   129     
   130     
   131     private final class FindInClass extends ClassVisitor {
   132         private String name;
   133         private int found;
   134         
   135         public FindInClass(ClassVisitor cv) {
   136             super(Opcodes.ASM4, cv);
   137         }
   138 
   139         @Override
   140         public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
   141             this.name = name;
   142             super.visit(version, access, name, signature, superName, interfaces);
   143         }
   144         
   145         
   146 
   147         @Override
   148         public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
   149             return new FindInMethod(access, name, desc,
   150                 super.visitMethod(access & (~Opcodes.ACC_NATIVE), name, desc, signature, exceptions)
   151             );
   152         }
   153         
   154         private final class FindInMethod extends MethodVisitor {
   155             private final String name;
   156             private final String desc;
   157             private final int access;
   158             private List<String> args;
   159             private String body;
   160             private boolean bodyGenerated;
   161             
   162             public FindInMethod(int access, String name, String desc, MethodVisitor mv) {
   163                 super(Opcodes.ASM4, mv);
   164                 this.access = access;
   165                 this.name = name;
   166                 this.desc = desc;
   167             }
   168 
   169             @Override
   170             public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
   171                 if (
   172                     "Lnet/java/html/js/JavaScriptBody;".equals(desc) // NOI18N
   173                     || "Lorg/apidesign/bck2brwsr/core/JavaScriptBody;".equals(desc) // NOI18N
   174                 ) {
   175                     found++;
   176                     return new FindInAnno();
   177                 }
   178                 return super.visitAnnotation(desc, visible);
   179             }
   180 
   181             private void generateJSBody(List<String> args, String body) {
   182                 this.args = args;
   183                 this.body = body;
   184             }
   185             
   186             @Override
   187             public void visitCode() {
   188                 if (body == null) {
   189                     return;
   190                 } 
   191                 generateBody();
   192             }
   193             
   194             private boolean generateBody() {
   195                 if (bodyGenerated) {
   196                     return false;
   197                 }
   198                 bodyGenerated = true;
   199                 
   200                 super.visitFieldInsn(
   201                     Opcodes.GETSTATIC, FindInClass.this.name, 
   202                     "$$fn$$" + name + "_" + found, 
   203                     "Lorg/apidesign/html/boot/spi/Fn;"
   204                 );
   205                 super.visitInsn(Opcodes.DUP);
   206                 Label ifNotNull = new Label();
   207                 super.visitJumpInsn(Opcodes.IFNONNULL, ifNotNull);
   208                 
   209                 // init Fn
   210                 super.visitInsn(Opcodes.POP);
   211                 super.visitLdcInsn(Type.getObjectType(FindInClass.this.name));
   212                 super.visitLdcInsn(body);
   213                 super.visitIntInsn(Opcodes.SIPUSH, args.size());
   214                 super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String");
   215                 for (int i = 0; i < args.size(); i++) {
   216                     String name = args.get(i);
   217                     super.visitInsn(Opcodes.DUP);
   218                     super.visitIntInsn(Opcodes.BIPUSH, i);
   219                     super.visitLdcInsn(name);
   220                     super.visitInsn(Opcodes.AASTORE);
   221                 }
   222                 super.visitMethodInsn(Opcodes.INVOKESTATIC, 
   223                     "org/apidesign/html/boot/impl/FnUtils", "define", 
   224                     "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/String;)Lorg/apidesign/html/boot/spi/Fn;"
   225                 );
   226                 // end of Fn init
   227                 
   228                 super.visitLabel(ifNotNull);
   229                 
   230                 final int offset;
   231                 if ((access & Opcodes.ACC_STATIC) == 0) {
   232                     offset = 1;
   233                     super.visitIntInsn(Opcodes.ALOAD, 0);
   234                 } else {
   235                     offset = 0;
   236                     super.visitInsn(Opcodes.ACONST_NULL);
   237                 }
   238                 
   239                 super.visitIntInsn(Opcodes.SIPUSH, args.size());
   240                 super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
   241                 
   242                 class SV extends SignatureVisitor {
   243                     private boolean nowReturn;
   244                     private Type returnType;
   245                     private int index;
   246                     
   247                     public SV() {
   248                         super(Opcodes.ASM4);
   249                     }
   250                     
   251                     @Override
   252                     public void visitBaseType(char descriptor) {
   253                         final Type t = Type.getType("" + descriptor);
   254                         if (nowReturn) {
   255                             returnType = t;
   256                             return;
   257                         }
   258                         FindInMethod.super.visitInsn(Opcodes.DUP);
   259                         FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index);
   260                         FindInMethod.super.visitVarInsn(t.getOpcode(Opcodes.ILOAD), index + offset);
   261                         String factory;
   262                         switch (descriptor) {
   263                         case 'I': factory = "java/lang/Integer"; break;
   264                         case 'J': factory = "java/lang/Long"; break;
   265                         case 'S': factory = "java/lang/Short"; break;
   266                         case 'F': factory = "java/lang/Float"; break;
   267                         case 'D': factory = "java/lang/Double"; break;
   268                         case 'Z': factory = "java/lang/Boolean"; break;
   269                         case 'C': factory = "java/lang/Character"; break;
   270                         case 'B': factory = "java/lang/Byte"; break;
   271                         default: throw new IllegalStateException(t.toString());
   272                         }
   273                         FindInMethod.super.visitMethodInsn(Opcodes.INVOKESTATIC,
   274                             factory, "valueOf", "(" + descriptor + ")L" + factory + ";"
   275                         );
   276                         FindInMethod.super.visitInsn(Opcodes.AASTORE);
   277                         index++;
   278                     }
   279 
   280                     @Override
   281                     public SignatureVisitor visitArrayType() {
   282                         if (nowReturn) {
   283                             throw new IllegalStateException("Not supported yet");
   284                         }
   285                         loadObject();
   286                         return new SignatureWriter();
   287                     }
   288 
   289                     @Override
   290                     public void visitClassType(String name) {
   291                         if (nowReturn) {
   292                             returnType = Type.getObjectType(name);
   293                             return;
   294                         }
   295                         loadObject();
   296                     }
   297 
   298                     @Override
   299                     public SignatureVisitor visitReturnType() {
   300                         nowReturn = true;
   301                         return this;
   302                     }
   303 
   304                     private void loadObject() {
   305                         FindInMethod.super.visitInsn(Opcodes.DUP);
   306                         FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index);
   307                         FindInMethod.super.visitVarInsn(Opcodes.ALOAD, index + offset);
   308                         FindInMethod.super.visitInsn(Opcodes.AASTORE);
   309                         index++;
   310                     }
   311                     
   312                 }
   313                 SV sv = new SV();
   314                 SignatureReader sr = new SignatureReader(desc);
   315                 sr.accept(sv);
   316                 
   317                 super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, 
   318                     "org/apidesign/html/boot/spi/Fn", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"
   319                 );
   320                 switch (sv.returnType.getSort()) {
   321                 case Type.VOID: 
   322                     super.visitInsn(Opcodes.RETURN);
   323                     break;
   324                 case Type.ARRAY:
   325                 case Type.OBJECT:
   326                     super.visitTypeInsn(Opcodes.CHECKCAST, sv.returnType.getInternalName());
   327                     super.visitInsn(Opcodes.ARETURN);
   328                     break;
   329                 case Type.BOOLEAN:
   330                     super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Boolean");
   331                     super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, 
   332                         "java/lang/Boolean", "booleanValue", "()Z"
   333                     );
   334                     super.visitInsn(Opcodes.IRETURN);
   335                     break;
   336                 default:
   337                     super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Number");
   338                     super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, 
   339                         "java/lang/Number", sv.returnType.getClassName() + "Value", "()" + sv.returnType.getDescriptor()
   340                     );
   341                     super.visitInsn(sv.returnType.getOpcode(Opcodes.IRETURN));
   342                 }
   343                 return true;
   344             }
   345 
   346             @Override
   347             public void visitEnd() {
   348                 super.visitEnd();
   349                 if (body != null) {
   350                     if (generateBody()) {
   351                         // native method
   352                         super.visitMaxs(1, 0);
   353                     }
   354                     FindInClass.this.visitField(
   355                         Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, 
   356                         "$$fn$$" + name + "_" + found, 
   357                         "Lorg/apidesign/html/boot/spi/Fn;", 
   358                         null, null
   359                     );
   360                 }
   361             }
   362             
   363             
   364             
   365             
   366         
   367             private final class FindInAnno extends AnnotationVisitor {
   368                 private List<String> args = new ArrayList<String>();
   369                 private String body;
   370                 private boolean javacall = false;
   371 
   372                 public FindInAnno() {
   373                     super(Opcodes.ASM4);
   374                 }
   375 
   376                 @Override
   377                 public void visit(String name, Object value) {
   378                     if (name == null) {
   379                         args.add((String) value);
   380                         return;
   381                     }
   382                     if (name.equals("javacall")) { // NOI18N
   383                         javacall = (Boolean)value;
   384                         return;
   385                     }
   386                     assert name.equals("body");
   387                     body = (String) value;
   388                 }
   389 
   390                 @Override
   391                 public AnnotationVisitor visitArray(String name) {
   392                     return this;
   393                 }
   394 
   395                 @Override
   396                 public void visitEnd() {
   397                     if (body != null) {
   398                         generateJSBody(args, javacall ? 
   399                             FnUtils.callback(body, JsClassLoader.this) : 
   400                             body
   401                         );
   402                     }
   403                 }
   404             }
   405         }
   406     }
   407     
   408     private class ClassWriterEx extends ClassWriter {
   409 
   410         public ClassWriterEx(ClassReader classReader, int flags) {
   411             super(classReader, flags);
   412         }
   413         
   414         @Override
   415         protected String getCommonSuperClass(final String type1, final String type2) {
   416             Class<?> c, d;
   417             ClassLoader classLoader = JsClassLoader.this;
   418             try {
   419                 c = Class.forName(type1.replace('/', '.'), false, classLoader);
   420                 d = Class.forName(type2.replace('/', '.'), false, classLoader);
   421             } catch (Exception e) {
   422                 throw new RuntimeException(e.toString());
   423             }
   424             if (c.isAssignableFrom(d)) {
   425                 return type1;
   426             }
   427             if (d.isAssignableFrom(c)) {
   428                 return type2;
   429             }
   430             if (c.isInterface() || d.isInterface()) {
   431                 return "java/lang/Object";
   432             } else {
   433                 do {
   434                     c = c.getSuperclass();
   435                 } while (!c.isAssignableFrom(d));
   436                 return c.getName().replace('.', '/');
   437             }
   438         }        
   439     }
   440 }