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