boot/src/main/java/org/apidesign/html/boot/impl/JsClassLoader.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Wed, 26 Jun 2013 08:43:32 +0200
branchclassloader
changeset 163 2652760705d6
parent 161 ea5ca9cc685d
child 164 7235d50dd452
permissions -rw-r--r--
Loading of external JavaScript files simplified
     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 java.util.logging.Level;
    32 import java.util.logging.Logger;
    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);
    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);
   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         
   136         public FindInClass(ClassVisitor cv) {
   137             super(Opcodes.ASM4, cv);
   138         }
   139 
   140         @Override
   141         public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
   142             this.name = name;
   143             super.visit(version, access, name, signature, superName, interfaces);
   144         }
   145 
   146         @Override
   147         public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
   148             if ("Lnet/java/html/js/JavaScriptResource;".equals(desc)) {
   149                 return new LoadResource();
   150             }
   151             return super.visitAnnotation(desc, visible);
   152         }
   153         
   154 
   155         @Override
   156         public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
   157             return new FindInMethod(access, name, desc,
   158                 super.visitMethod(access & (~Opcodes.ACC_NATIVE), name, desc, signature, exceptions)
   159             );
   160         }
   161         
   162         private final class FindInMethod extends MethodVisitor {
   163             private final String name;
   164             private final String desc;
   165             private final int access;
   166             private List<String> args;
   167             private String body;
   168             private boolean bodyGenerated;
   169             
   170             public FindInMethod(int access, String name, String desc, MethodVisitor mv) {
   171                 super(Opcodes.ASM4, mv);
   172                 this.access = access;
   173                 this.name = name;
   174                 this.desc = desc;
   175             }
   176 
   177             @Override
   178             public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
   179                 if (
   180                     "Lnet/java/html/js/JavaScriptBody;".equals(desc) // NOI18N
   181                     || "Lorg/apidesign/bck2brwsr/core/JavaScriptBody;".equals(desc) // NOI18N
   182                 ) {
   183                     found++;
   184                     return new FindInAnno();
   185                 }
   186                 return super.visitAnnotation(desc, visible);
   187             }
   188 
   189             private void generateJSBody(List<String> args, String body) {
   190                 this.args = args;
   191                 this.body = body;
   192             }
   193             
   194             @Override
   195             public void visitCode() {
   196                 if (body == null) {
   197                     return;
   198                 } 
   199                 generateBody();
   200             }
   201             
   202             private boolean generateBody() {
   203                 if (bodyGenerated) {
   204                     return false;
   205                 }
   206                 bodyGenerated = true;
   207                 
   208                 super.visitFieldInsn(
   209                     Opcodes.GETSTATIC, FindInClass.this.name, 
   210                     "$$fn$$" + name + "_" + found, 
   211                     "Lorg/apidesign/html/boot/spi/Fn;"
   212                 );
   213                 super.visitInsn(Opcodes.DUP);
   214                 Label ifNotNull = new Label();
   215                 super.visitJumpInsn(Opcodes.IFNONNULL, ifNotNull);
   216                 
   217                 // init Fn
   218                 super.visitInsn(Opcodes.POP);
   219                 super.visitLdcInsn(Type.getObjectType(FindInClass.this.name));
   220                 super.visitLdcInsn(body);
   221                 super.visitIntInsn(Opcodes.SIPUSH, args.size());
   222                 super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String");
   223                 for (int i = 0; i < args.size(); i++) {
   224                     String name = args.get(i);
   225                     super.visitInsn(Opcodes.DUP);
   226                     super.visitIntInsn(Opcodes.BIPUSH, i);
   227                     super.visitLdcInsn(name);
   228                     super.visitInsn(Opcodes.AASTORE);
   229                 }
   230                 super.visitMethodInsn(Opcodes.INVOKESTATIC, 
   231                     "org/apidesign/html/boot/impl/FnUtils", "define", 
   232                     "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/String;)Lorg/apidesign/html/boot/spi/Fn;"
   233                 );
   234                 // end of Fn init
   235                 
   236                 super.visitLabel(ifNotNull);
   237                 
   238                 final int offset;
   239                 if ((access & Opcodes.ACC_STATIC) == 0) {
   240                     offset = 1;
   241                     super.visitIntInsn(Opcodes.ALOAD, 0);
   242                 } else {
   243                     offset = 0;
   244                     super.visitInsn(Opcodes.ACONST_NULL);
   245                 }
   246                 
   247                 super.visitIntInsn(Opcodes.SIPUSH, args.size());
   248                 super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
   249                 
   250                 class SV extends SignatureVisitor {
   251                     private boolean nowReturn;
   252                     private Type returnType;
   253                     private int index;
   254                     
   255                     public SV() {
   256                         super(Opcodes.ASM4);
   257                     }
   258                     
   259                     @Override
   260                     public void visitBaseType(char descriptor) {
   261                         final Type t = Type.getType("" + descriptor);
   262                         if (nowReturn) {
   263                             returnType = t;
   264                             return;
   265                         }
   266                         FindInMethod.super.visitInsn(Opcodes.DUP);
   267                         FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index);
   268                         FindInMethod.super.visitVarInsn(t.getOpcode(Opcodes.ILOAD), index + offset);
   269                         String factory;
   270                         switch (descriptor) {
   271                         case 'I': factory = "java/lang/Integer"; break;
   272                         case 'J': factory = "java/lang/Long"; break;
   273                         case 'S': factory = "java/lang/Short"; break;
   274                         case 'F': factory = "java/lang/Float"; break;
   275                         case 'D': factory = "java/lang/Double"; break;
   276                         case 'Z': factory = "java/lang/Boolean"; break;
   277                         case 'C': factory = "java/lang/Character"; break;
   278                         case 'B': factory = "java/lang/Byte"; break;
   279                         default: throw new IllegalStateException(t.toString());
   280                         }
   281                         FindInMethod.super.visitMethodInsn(Opcodes.INVOKESTATIC,
   282                             factory, "valueOf", "(" + descriptor + ")L" + factory + ";"
   283                         );
   284                         FindInMethod.super.visitInsn(Opcodes.AASTORE);
   285                         index++;
   286                     }
   287 
   288                     @Override
   289                     public SignatureVisitor visitArrayType() {
   290                         if (nowReturn) {
   291                             throw new IllegalStateException("Not supported yet");
   292                         }
   293                         loadObject();
   294                         return new SignatureWriter();
   295                     }
   296 
   297                     @Override
   298                     public void visitClassType(String name) {
   299                         if (nowReturn) {
   300                             returnType = Type.getObjectType(name);
   301                             return;
   302                         }
   303                         loadObject();
   304                     }
   305 
   306                     @Override
   307                     public SignatureVisitor visitReturnType() {
   308                         nowReturn = true;
   309                         return this;
   310                     }
   311 
   312                     private void loadObject() {
   313                         FindInMethod.super.visitInsn(Opcodes.DUP);
   314                         FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index);
   315                         FindInMethod.super.visitVarInsn(Opcodes.ALOAD, index + offset);
   316                         FindInMethod.super.visitInsn(Opcodes.AASTORE);
   317                         index++;
   318                     }
   319                     
   320                 }
   321                 SV sv = new SV();
   322                 SignatureReader sr = new SignatureReader(desc);
   323                 sr.accept(sv);
   324                 
   325                 super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, 
   326                     "org/apidesign/html/boot/spi/Fn", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"
   327                 );
   328                 switch (sv.returnType.getSort()) {
   329                 case Type.VOID: 
   330                     super.visitInsn(Opcodes.RETURN);
   331                     break;
   332                 case Type.ARRAY:
   333                 case Type.OBJECT:
   334                     super.visitTypeInsn(Opcodes.CHECKCAST, sv.returnType.getInternalName());
   335                     super.visitInsn(Opcodes.ARETURN);
   336                     break;
   337                 case Type.BOOLEAN:
   338                     super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Boolean");
   339                     super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, 
   340                         "java/lang/Boolean", "booleanValue", "()Z"
   341                     );
   342                     super.visitInsn(Opcodes.IRETURN);
   343                     break;
   344                 default:
   345                     super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Number");
   346                     super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, 
   347                         "java/lang/Number", sv.returnType.getClassName() + "Value", "()" + sv.returnType.getDescriptor()
   348                     );
   349                     super.visitInsn(sv.returnType.getOpcode(Opcodes.IRETURN));
   350                 }
   351                 return true;
   352             }
   353 
   354             @Override
   355             public void visitEnd() {
   356                 super.visitEnd();
   357                 if (body != null) {
   358                     if (generateBody()) {
   359                         // native method
   360                         super.visitMaxs(1, 0);
   361                     }
   362                     FindInClass.this.visitField(
   363                         Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, 
   364                         "$$fn$$" + name + "_" + found, 
   365                         "Lorg/apidesign/html/boot/spi/Fn;", 
   366                         null, null
   367                     );
   368                 }
   369             }
   370             
   371             
   372             
   373             
   374         
   375             private final class FindInAnno extends AnnotationVisitor {
   376                 private List<String> args = new ArrayList<String>();
   377                 private String body;
   378                 private boolean javacall = false;
   379 
   380                 public FindInAnno() {
   381                     super(Opcodes.ASM4);
   382                 }
   383 
   384                 @Override
   385                 public void visit(String name, Object value) {
   386                     if (name == null) {
   387                         args.add((String) value);
   388                         return;
   389                     }
   390                     if (name.equals("javacall")) { // NOI18N
   391                         javacall = (Boolean)value;
   392                         return;
   393                     }
   394                     assert name.equals("body");
   395                     body = (String) value;
   396                 }
   397 
   398                 @Override
   399                 public AnnotationVisitor visitArray(String name) {
   400                     return this;
   401                 }
   402 
   403                 @Override
   404                 public void visitEnd() {
   405                     if (body != null) {
   406                         generateJSBody(args, javacall ? 
   407                             FnUtils.callback(body, JsClassLoader.this) : 
   408                             body
   409                         );
   410                     }
   411                 }
   412             }
   413         }
   414         
   415         private final class LoadResource extends AnnotationVisitor {
   416             public LoadResource() {
   417                 super(Opcodes.ASM4);
   418             }
   419             
   420             @Override
   421             public void visit(String attrName, Object value)  {
   422                 String relPath = (String) value;
   423                 if (relPath.startsWith("/")) {
   424                     FnUtils.loadScript(JsClassLoader.this, relPath);
   425                 } else {
   426                     int last = name.lastIndexOf('/');
   427                     String fullPath = name.substring(0, last + 1) + relPath;
   428                     FnUtils.loadScript(JsClassLoader.this, fullPath);
   429                 }
   430             }
   431         }
   432     }
   433     
   434     private class ClassWriterEx extends ClassWriter {
   435 
   436         public ClassWriterEx(ClassReader classReader, int flags) {
   437             super(classReader, flags);
   438         }
   439         
   440         @Override
   441         protected String getCommonSuperClass(final String type1, final String type2) {
   442             Class<?> c, d;
   443             ClassLoader classLoader = JsClassLoader.this;
   444             try {
   445                 c = Class.forName(type1.replace('/', '.'), false, classLoader);
   446                 d = Class.forName(type2.replace('/', '.'), false, classLoader);
   447             } catch (Exception e) {
   448                 throw new RuntimeException(e.toString());
   449             }
   450             if (c.isAssignableFrom(d)) {
   451                 return type1;
   452             }
   453             if (d.isAssignableFrom(c)) {
   454                 return type2;
   455             }
   456             if (c.isInterface() || d.isInterface()) {
   457                 return "java/lang/Object";
   458             } else {
   459                 do {
   460                     c = c.getSuperclass();
   461                 } while (!c.isAssignableFrom(d));
   462                 return c.getName().replace('.', '/');
   463             }
   464         }        
   465     }
   466 }