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