boot/src/main/java/org/apidesign/html/boot/impl/JsClassLoader.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Thu, 11 Jul 2013 21:10:18 +0200
changeset 190 c53532068a03
parent 189 5c78e49bd297
child 192 db8dcc30da25
permissions -rw-r--r--
Removing no longer used 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                 // end of Fn init
   236                 
   237                 super.visitLabel(ifNotNull);
   238                 
   239                 final int offset;
   240                 if ((access & Opcodes.ACC_STATIC) == 0) {
   241                     offset = 1;
   242                     super.visitIntInsn(Opcodes.ALOAD, 0);
   243                 } else {
   244                     offset = 0;
   245                     super.visitInsn(Opcodes.ACONST_NULL);
   246                 }
   247                 
   248                 super.visitIntInsn(Opcodes.SIPUSH, args.size());
   249                 super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
   250                 
   251                 class SV extends SignatureVisitor {
   252                     private boolean nowReturn;
   253                     private Type returnType;
   254                     private int index;
   255                     
   256                     public SV() {
   257                         super(Opcodes.ASM4);
   258                     }
   259                     
   260                     @Override
   261                     public void visitBaseType(char descriptor) {
   262                         final Type t = Type.getType("" + descriptor);
   263                         if (nowReturn) {
   264                             returnType = t;
   265                             return;
   266                         }
   267                         FindInMethod.super.visitInsn(Opcodes.DUP);
   268                         FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index);
   269                         FindInMethod.super.visitVarInsn(t.getOpcode(Opcodes.ILOAD), index + offset);
   270                         String factory;
   271                         switch (descriptor) {
   272                         case 'I': factory = "java/lang/Integer"; break;
   273                         case 'J': factory = "java/lang/Long"; break;
   274                         case 'S': factory = "java/lang/Short"; break;
   275                         case 'F': factory = "java/lang/Float"; break;
   276                         case 'D': factory = "java/lang/Double"; break;
   277                         case 'Z': factory = "java/lang/Boolean"; break;
   278                         case 'C': factory = "java/lang/Character"; break;
   279                         case 'B': factory = "java/lang/Byte"; break;
   280                         default: throw new IllegalStateException(t.toString());
   281                         }
   282                         FindInMethod.super.visitMethodInsn(Opcodes.INVOKESTATIC,
   283                             factory, "valueOf", "(" + descriptor + ")L" + factory + ";"
   284                         );
   285                         FindInMethod.super.visitInsn(Opcodes.AASTORE);
   286                         index++;
   287                     }
   288 
   289                     @Override
   290                     public SignatureVisitor visitArrayType() {
   291                         if (nowReturn) {
   292                             throw new IllegalStateException("Not supported yet");
   293                         }
   294                         loadObject();
   295                         return new SignatureWriter();
   296                     }
   297 
   298                     @Override
   299                     public void visitClassType(String name) {
   300                         if (nowReturn) {
   301                             returnType = Type.getObjectType(name);
   302                             return;
   303                         }
   304                         loadObject();
   305                     }
   306 
   307                     @Override
   308                     public SignatureVisitor visitReturnType() {
   309                         nowReturn = true;
   310                         return this;
   311                     }
   312 
   313                     private void loadObject() {
   314                         FindInMethod.super.visitInsn(Opcodes.DUP);
   315                         FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index);
   316                         FindInMethod.super.visitVarInsn(Opcodes.ALOAD, index + offset);
   317                         FindInMethod.super.visitInsn(Opcodes.AASTORE);
   318                         index++;
   319                     }
   320                     
   321                 }
   322                 SV sv = new SV();
   323                 SignatureReader sr = new SignatureReader(desc);
   324                 sr.accept(sv);
   325                 
   326                 if (needsVM) {
   327                     FindInMethod.super.visitInsn(Opcodes.DUP);
   328                     FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, sv.index);
   329                     int lastSlash = FindInClass.this.name.lastIndexOf('/');
   330                     String jsCallbacks = FindInClass.this.name.substring(0, lastSlash + 1) + "$JsCallbacks$";
   331                     FindInMethod.super.visitFieldInsn(Opcodes.GETSTATIC, jsCallbacks, "VM", "L" + jsCallbacks + ";");
   332                     FindInMethod.super.visitInsn(Opcodes.AASTORE);
   333                 }
   334                 
   335                 super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, 
   336                     "org/apidesign/html/boot/spi/Fn", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"
   337                 );
   338                 switch (sv.returnType.getSort()) {
   339                 case Type.VOID: 
   340                     super.visitInsn(Opcodes.RETURN);
   341                     break;
   342                 case Type.ARRAY:
   343                 case Type.OBJECT:
   344                     super.visitTypeInsn(Opcodes.CHECKCAST, sv.returnType.getInternalName());
   345                     super.visitInsn(Opcodes.ARETURN);
   346                     break;
   347                 case Type.BOOLEAN:
   348                     super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Boolean");
   349                     super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, 
   350                         "java/lang/Boolean", "booleanValue", "()Z"
   351                     );
   352                     super.visitInsn(Opcodes.IRETURN);
   353                     break;
   354                 default:
   355                     super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Number");
   356                     super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, 
   357                         "java/lang/Number", sv.returnType.getClassName() + "Value", "()" + sv.returnType.getDescriptor()
   358                     );
   359                     super.visitInsn(sv.returnType.getOpcode(Opcodes.IRETURN));
   360                 }
   361                 return true;
   362             }
   363 
   364             @Override
   365             public void visitEnd() {
   366                 super.visitEnd();
   367                 if (body != null) {
   368                     if (generateBody()) {
   369                         // native method
   370                         super.visitMaxs(1, 0);
   371                     }
   372                     FindInClass.this.visitField(
   373                         Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, 
   374                         "$$fn$$" + name + "_" + found, 
   375                         "Lorg/apidesign/html/boot/spi/Fn;", 
   376                         null, null
   377                     );
   378                 }
   379             }
   380             
   381             
   382             
   383             
   384         
   385             private final class FindInAnno extends AnnotationVisitor {
   386                 private List<String> args = new ArrayList<String>();
   387                 private String body;
   388                 private boolean javacall = false;
   389 
   390                 public FindInAnno() {
   391                     super(Opcodes.ASM4);
   392                 }
   393 
   394                 @Override
   395                 public void visit(String name, Object value) {
   396                     if (name == null) {
   397                         args.add((String) value);
   398                         return;
   399                     }
   400                     if (name.equals("javacall")) { // NOI18N
   401                         javacall = (Boolean)value;
   402                         return;
   403                     }
   404                     assert name.equals("body");
   405                     body = (String) value;
   406                 }
   407 
   408                 @Override
   409                 public AnnotationVisitor visitArray(String name) {
   410                     return this;
   411                 }
   412 
   413                 @Override
   414                 public void visitEnd() {
   415                     if (body != null) {
   416                         if (javacall) {
   417                             body = FnUtils.callback(body);
   418                             args.add("vm");
   419                         }
   420                         generateJSBody(args, body);
   421                     }
   422                 }
   423             }
   424         }
   425         
   426         private final class LoadResource extends AnnotationVisitor {
   427             public LoadResource() {
   428                 super(Opcodes.ASM4);
   429             }
   430             
   431             @Override
   432             public void visit(String attrName, Object value)  {
   433                 String relPath = (String) value;
   434                 if (relPath.startsWith("/")) {
   435                     FnUtils.loadScript(JsClassLoader.this, relPath);
   436                 } else {
   437                     int last = name.lastIndexOf('/');
   438                     String fullPath = name.substring(0, last + 1) + relPath;
   439                     FnUtils.loadScript(JsClassLoader.this, fullPath);
   440                 }
   441             }
   442         }
   443     }
   444     
   445     private class ClassWriterEx extends ClassWriter {
   446 
   447         public ClassWriterEx(ClassReader classReader, int flags) {
   448             super(classReader, flags);
   449         }
   450         
   451         @Override
   452         protected String getCommonSuperClass(final String type1, final String type2) {
   453             Class<?> c, d;
   454             ClassLoader classLoader = JsClassLoader.this;
   455             try {
   456                 c = Class.forName(type1.replace('/', '.'), false, classLoader);
   457                 d = Class.forName(type2.replace('/', '.'), false, classLoader);
   458             } catch (Exception e) {
   459                 throw new RuntimeException(e.toString());
   460             }
   461             if (c.isAssignableFrom(d)) {
   462                 return type1;
   463             }
   464             if (d.isAssignableFrom(c)) {
   465                 return type2;
   466             }
   467             if (c.isInterface() || d.isInterface()) {
   468                 return "java/lang/Object";
   469             } else {
   470                 do {
   471                     c = c.getSuperclass();
   472                 } while (!c.isAssignableFrom(d));
   473                 return c.getName().replace('.', '/');
   474             }
   475         }        
   476     }
   477 }