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