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