boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java
author Jaroslav Tulach <jtulach@netbeans.org>
Sat, 29 Nov 2014 22:25:40 +0100
changeset 884 af690d50d7d6
parent 851 69ed96e7f41b
child 900 2ee22312e414
permissions -rw-r--r--
Use optional dependency on asm-5.0.jar, so we don't have to bundle it when the classes are post processed.
     1 /**
     2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
     3  *
     4  * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
     5  *
     6  * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
     7  * Other names may be trademarks of their respective owners.
     8  *
     9  * The contents of this file are subject to the terms of either the GNU
    10  * General Public License Version 2 only ("GPL") or the Common
    11  * Development and Distribution License("CDDL") (collectively, the
    12  * "License"). You may not use this file except in compliance with the
    13  * License. You can obtain a copy of the License at
    14  * http://www.netbeans.org/cddl-gplv2.html
    15  * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
    16  * specific language governing permissions and limitations under the
    17  * License.  When distributing the software, include this License Header
    18  * Notice in each file and include the License file at
    19  * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
    20  * particular file as subject to the "Classpath" exception as provided
    21  * by Oracle in the GPL Version 2 section of the License file that
    22  * accompanied this code. If applicable, add the following below the
    23  * License Header, with the fields enclosed by brackets [] replaced by
    24  * your own identifying information:
    25  * "Portions Copyrighted [year] [name of copyright owner]"
    26  *
    27  * Contributor(s):
    28  *
    29  * The Original Software is NetBeans. The Initial Developer of the Original
    30  * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
    31  *
    32  * If you wish your version of this file to be governed by only the CDDL
    33  * or only the GPL Version 2, indicate your decision by adding
    34  * "[Contributor] elects to include this software in this distribution
    35  * under the [CDDL or GPL Version 2] license." If you do not indicate a
    36  * single choice of license, a recipient has the option to distribute
    37  * your version of this file under either the CDDL, the GPL Version 2 or
    38  * to extend the choice of license to its licensees as provided above.
    39  * However, if you add GPL Version 2 code and therefore, elected the GPL
    40  * Version 2 license, then the option applies only if the new code is
    41  * made subject to such option by the copyright holder.
    42  */
    43 package org.netbeans.html.boot.impl;
    44 
    45 import java.io.InputStream;
    46 import java.io.InputStreamReader;
    47 import java.io.Reader;
    48 import java.net.URL;
    49 import java.util.ArrayList;
    50 import java.util.Collections;
    51 import java.util.Enumeration;
    52 import java.util.List;
    53 import net.java.html.js.JavaScriptBody;
    54 import net.java.html.js.JavaScriptResource;
    55 import org.netbeans.html.boot.spi.Fn;
    56 import org.objectweb.asm.AnnotationVisitor;
    57 import org.objectweb.asm.ClassReader;
    58 import org.objectweb.asm.ClassVisitor;
    59 import org.objectweb.asm.ClassWriter;
    60 import org.objectweb.asm.Label;
    61 import org.objectweb.asm.MethodVisitor;
    62 import org.objectweb.asm.Opcodes;
    63 import org.objectweb.asm.Type;
    64 import org.objectweb.asm.signature.SignatureReader;
    65 import org.objectweb.asm.signature.SignatureVisitor;
    66 import org.objectweb.asm.signature.SignatureWriter;
    67 
    68 /**
    69  *
    70  * @author Jaroslav Tulach
    71  */
    72 public final class FnUtils {
    73     
    74     private FnUtils() {
    75     }
    76     
    77     /** Seeks for {@link JavaScriptBody} and {@link JavaScriptResource} annotations
    78      * in the bytecode and converts them into real code. Used by Maven plugin
    79      * postprocessing classes.
    80      * 
    81      * @param bytecode the original bytecode with javascript specific annotations
    82      * @param loader the loader to load resources (scripts and classes) when needed
    83      * @return the transformed bytecode
    84      * @since 0.7
    85      */
    86     public static byte[] transform(byte[] bytecode, ClassLoader loader) {
    87         ClassReader cr = new ClassReader(bytecode) {
    88             // to allow us to compile with -profile compact1 on 
    89             // JDK8 while processing the class as JDK7, the highest
    90             // class format asm 4.1 understands to
    91             @Override
    92             public short readShort(int index) {
    93                 short s = super.readShort(index);
    94                 if (index == 6 && s > Opcodes.V1_7) {
    95                     return Opcodes.V1_7;
    96                 }
    97                 return s;
    98             }
    99         };
   100         FindInClass tst = new FindInClass(loader, null);
   101         cr.accept(tst, 0);
   102         if (tst.found > 0) {
   103             ClassWriter w = new ClassWriterEx(loader, cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
   104             FindInClass fic = new FindInClass(loader, w);
   105             cr.accept(fic, 0);
   106             bytecode = w.toByteArray();
   107         }
   108         return bytecode;
   109     }
   110     
   111     public static boolean isValid(Fn fn) {
   112         return fn != null && fn.isValid();
   113     }
   114 
   115     public static ClassLoader newLoader(final FindResources f, final Fn.Presenter d, ClassLoader parent) {
   116         return new JsClassLoader(parent) {
   117             @Override
   118             protected URL findResource(String name) {
   119                 List<URL> l = res(name, true);
   120                 return l.isEmpty() ? null : l.get(0);
   121             }
   122             
   123             @Override
   124             protected Enumeration<URL> findResources(String name) {
   125                 return Collections.enumeration(res(name, false));
   126             }
   127             
   128             private List<URL> res(String name, boolean oneIsEnough) {
   129                 List<URL> l = new ArrayList<URL>();
   130                 f.findResources(name, l, oneIsEnough);
   131                 return l;
   132             }
   133             
   134             @Override
   135             protected Fn defineFn(String code, String... names) {
   136                 return d.defineFn(code, names);
   137             }
   138 
   139             @Override
   140             protected void loadScript(Reader code) throws Exception {
   141                 d.loadScript(code);
   142             }
   143         };
   144     }
   145 
   146     static String callback(final String body) {
   147         return new JsCallback() {
   148             @Override
   149             protected CharSequence callMethod(
   150                 String ident, String fqn, String method, String params
   151             ) {
   152                 StringBuilder sb = new StringBuilder();
   153                 sb.append("vm.").append(mangle(fqn, method, params));
   154                 sb.append("(");
   155                 if (ident != null) {
   156                     sb.append(ident);
   157                 }
   158                 return sb;
   159             }
   160 
   161         }.parse(body);
   162     }
   163 
   164     static void loadScript(ClassLoader jcl, String resource) {
   165         final InputStream script = jcl.getResourceAsStream(resource);
   166         if (script == null) {
   167             throw new NullPointerException("Can't find " + resource);
   168         }
   169         try {
   170             Reader isr = null;
   171             try {
   172                 isr = new InputStreamReader(script, "UTF-8");
   173                 FnContext.currentPresenter(false).loadScript(isr);
   174             } finally {
   175                 if (isr != null) {
   176                     isr.close();
   177                 }
   178             }
   179         } catch (Exception ex) {
   180             throw new IllegalStateException("Can't execute " + resource, ex);
   181         } 
   182     }
   183     
   184     
   185     private static final class FindInClass extends ClassVisitor {
   186         private String name;
   187         private int found;
   188         private ClassLoader loader;
   189         private String resource;
   190 
   191         public FindInClass(ClassLoader l, ClassVisitor cv) {
   192             super(Opcodes.ASM4, cv);
   193             this.loader = l;
   194         }
   195 
   196         @Override
   197         public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
   198             this.name = name;
   199             super.visit(version, access, name, signature, superName, interfaces);
   200         }
   201 
   202         @Override
   203         public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
   204             final AnnotationVisitor del = super.visitAnnotation(desc, visible);
   205             if ("Lnet/java/html/js/JavaScriptResource;".equals(desc)) {
   206                 return new LoadResource(del);
   207             }
   208             return del;
   209         }
   210 
   211         @Override
   212         public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
   213             return new FindInMethod(access, name, desc,
   214                     super.visitMethod(access & (~Opcodes.ACC_NATIVE), name, desc, signature, exceptions)
   215             );
   216         }
   217 
   218         private final class FindInMethod extends MethodVisitor {
   219 
   220             private final String name;
   221             private final String desc;
   222             private final int access;
   223             private FindInAnno fia;
   224             private boolean bodyGenerated;
   225 
   226             public FindInMethod(int access, String name, String desc, MethodVisitor mv) {
   227                 super(Opcodes.ASM4, mv);
   228                 this.access = access;
   229                 this.name = name;
   230                 this.desc = desc;
   231             }
   232 
   233             @Override
   234             public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
   235                 if ("Lnet/java/html/js/JavaScriptBody;".equals(desc)) { // NOI18N
   236                     found++;
   237                     return new FindInAnno();
   238                 }
   239                 return super.visitAnnotation(desc, visible);
   240             }
   241 
   242             private void generateJSBody(FindInAnno fia) {
   243                 this.fia = fia;
   244             }
   245 
   246             @Override
   247             public void visitCode() {
   248                 if (fia == null) {
   249                     return;
   250                 }
   251                 generateBody(true);
   252             }
   253 
   254             private boolean generateBody(boolean hasCode) {
   255                 if (bodyGenerated) {
   256                     return false;
   257                 }
   258                 bodyGenerated = true;
   259                 if (mv != null) {
   260                     AnnotationVisitor va = super.visitAnnotation("Lnet/java/html/js/JavaScriptBody;", false);
   261                     AnnotationVisitor varr = va.visitArray("args");
   262                     for (String argName : fia.args) {
   263                         varr.visit(null, argName);
   264                     }
   265                     varr.visitEnd();
   266                     va.visit("javacall", fia.javacall);
   267                     va.visit("body", fia.body);
   268                     va.visitEnd();
   269                 }
   270                 
   271                 String body;
   272                 List<String> args;
   273                 if (fia.javacall) {
   274                     body = callback(fia.body);
   275                     args = new ArrayList<String>(fia.args);
   276                     args.add("vm");
   277                 } else {
   278                     body = fia.body;
   279                     args = fia.args;
   280                 }
   281 
   282                 super.visitFieldInsn(
   283                         Opcodes.GETSTATIC, FindInClass.this.name,
   284                         "$$fn$$" + name + "_" + found,
   285                         "Lorg/netbeans/html/boot/spi/Fn;"
   286                 );
   287                 super.visitInsn(Opcodes.DUP);
   288                 super.visitMethodInsn(
   289                         Opcodes.INVOKESTATIC,
   290                         "org/netbeans/html/boot/spi/Fn", "isValid",
   291                         "(Lorg/netbeans/html/boot/spi/Fn;)Z"
   292                 );
   293                 Label ifNotNull = new Label();
   294                 super.visitJumpInsn(Opcodes.IFNE, ifNotNull);
   295 
   296                 // init Fn
   297                 super.visitInsn(Opcodes.POP);
   298                 super.visitLdcInsn(Type.getObjectType(FindInClass.this.name));
   299                 super.visitLdcInsn(body);
   300                 super.visitIntInsn(Opcodes.SIPUSH, args.size());
   301                 super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String");
   302                 boolean needsVM = false;
   303                 for (int i = 0; i < args.size(); i++) {
   304                     assert !needsVM;
   305                     String argName = args.get(i);
   306                     needsVM = "vm".equals(argName);
   307                     super.visitInsn(Opcodes.DUP);
   308                     super.visitIntInsn(Opcodes.BIPUSH, i);
   309                     super.visitLdcInsn(argName);
   310                     super.visitInsn(Opcodes.AASTORE);
   311                 }
   312                 super.visitMethodInsn(Opcodes.INVOKESTATIC,
   313                         "org/netbeans/html/boot/spi/Fn", "define",
   314                         "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/String;)Lorg/netbeans/html/boot/spi/Fn;"
   315                 );
   316                 Label noPresenter = new Label();
   317                 super.visitInsn(Opcodes.DUP);
   318                 super.visitJumpInsn(Opcodes.IFNULL, noPresenter);
   319                 if (resource != null) {
   320                     super.visitLdcInsn(Type.getObjectType(FindInClass.this.name));
   321                     super.visitLdcInsn(resource);
   322                     super.visitMethodInsn(Opcodes.INVOKESTATIC,
   323                             "org/netbeans/html/boot/spi/Fn", "preload",
   324                             "(Lorg/netbeans/html/boot/spi/Fn;Ljava/lang/Class;Ljava/lang/String;)Lorg/netbeans/html/boot/spi/Fn;"
   325                     );
   326                 }
   327                 super.visitInsn(Opcodes.DUP);
   328                 super.visitFieldInsn(
   329                         Opcodes.PUTSTATIC, FindInClass.this.name,
   330                         "$$fn$$" + name + "_" + found,
   331                         "Lorg/netbeans/html/boot/spi/Fn;"
   332                 );
   333                 // end of Fn init
   334 
   335                 super.visitLabel(ifNotNull);
   336 
   337                 final int offset;
   338                 if ((access & Opcodes.ACC_STATIC) == 0) {
   339                     offset = 1;
   340                     super.visitIntInsn(Opcodes.ALOAD, 0);
   341                 } else {
   342                     offset = 0;
   343                     super.visitInsn(Opcodes.ACONST_NULL);
   344                 }
   345 
   346                 super.visitIntInsn(Opcodes.SIPUSH, args.size());
   347                 super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
   348 
   349                 class SV extends SignatureVisitor {
   350 
   351                     private boolean nowReturn;
   352                     private Type returnType;
   353                     private int index;
   354                     private int loadIndex = offset;
   355 
   356                     public SV() {
   357                         super(Opcodes.ASM4);
   358                     }
   359 
   360                     @Override
   361                     public void visitBaseType(char descriptor) {
   362                         final Type t = Type.getType("" + descriptor);
   363                         if (nowReturn) {
   364                             returnType = t;
   365                             return;
   366                         }
   367                         FindInMethod.super.visitInsn(Opcodes.DUP);
   368                         FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++);
   369                         FindInMethod.super.visitVarInsn(t.getOpcode(Opcodes.ILOAD), loadIndex++);
   370                         String factory;
   371                         switch (descriptor) {
   372                             case 'I':
   373                                 factory = "java/lang/Integer";
   374                                 break;
   375                             case 'J':
   376                                 factory = "java/lang/Long";
   377                                 loadIndex++;
   378                                 break;
   379                             case 'S':
   380                                 factory = "java/lang/Short";
   381                                 break;
   382                             case 'F':
   383                                 factory = "java/lang/Float";
   384                                 break;
   385                             case 'D':
   386                                 factory = "java/lang/Double";
   387                                 loadIndex++;
   388                                 break;
   389                             case 'Z':
   390                                 factory = "java/lang/Boolean";
   391                                 break;
   392                             case 'C':
   393                                 factory = "java/lang/Character";
   394                                 break;
   395                             case 'B':
   396                                 factory = "java/lang/Byte";
   397                                 break;
   398                             default:
   399                                 throw new IllegalStateException(t.toString());
   400                         }
   401                         FindInMethod.super.visitMethodInsn(Opcodes.INVOKESTATIC,
   402                                 factory, "valueOf", "(" + descriptor + ")L" + factory + ";"
   403                         );
   404                         FindInMethod.super.visitInsn(Opcodes.AASTORE);
   405                     }
   406 
   407                     @Override
   408                     public SignatureVisitor visitArrayType() {
   409                         if (nowReturn) {
   410                             return new SignatureVisitor(Opcodes.ASM4) {
   411                                 @Override
   412                                 public void visitClassType(String name) {
   413                                     returnType = Type.getType("[" + Type.getObjectType(name).getDescriptor());
   414                                 }
   415                             };
   416                         }
   417                         loadObject();
   418                         return new SignatureWriter();
   419                     }
   420 
   421                     @Override
   422                     public void visitClassType(String name) {
   423                         if (nowReturn) {
   424                             returnType = Type.getObjectType(name);
   425                             return;
   426                         }
   427                         loadObject();
   428                     }
   429 
   430                     @Override
   431                     public SignatureVisitor visitReturnType() {
   432                         nowReturn = true;
   433                         return this;
   434                     }
   435 
   436                     private void loadObject() {
   437                         FindInMethod.super.visitInsn(Opcodes.DUP);
   438                         FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++);
   439                         FindInMethod.super.visitVarInsn(Opcodes.ALOAD, loadIndex++);
   440                         FindInMethod.super.visitInsn(Opcodes.AASTORE);
   441                     }
   442 
   443                 }
   444                 SV sv = new SV();
   445                 SignatureReader sr = new SignatureReader(desc);
   446                 sr.accept(sv);
   447 
   448                 if (needsVM) {
   449                     FindInMethod.super.visitInsn(Opcodes.DUP);
   450                     FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, sv.index);
   451                     int lastSlash = FindInClass.this.name.lastIndexOf('/');
   452                     String jsCallbacks = FindInClass.this.name.substring(0, lastSlash + 1) + "$JsCallbacks$";
   453                     FindInMethod.super.visitFieldInsn(Opcodes.GETSTATIC, jsCallbacks, "VM", "L" + jsCallbacks + ";");
   454                     FindInMethod.super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, jsCallbacks, "current", "()L" + jsCallbacks + ";");
   455                     FindInMethod.super.visitInsn(Opcodes.AASTORE);
   456                 }
   457 
   458                 if (fia.wait4js) {
   459                     super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
   460                             "org/netbeans/html/boot/spi/Fn", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"
   461                     );
   462                     switch (sv.returnType.getSort()) {
   463                         case Type.VOID:
   464                             super.visitInsn(Opcodes.RETURN);
   465                             break;
   466                         case Type.ARRAY:
   467                         case Type.OBJECT:
   468                             super.visitTypeInsn(Opcodes.CHECKCAST, sv.returnType.getInternalName());
   469                             super.visitInsn(Opcodes.ARETURN);
   470                             break;
   471                         case Type.BOOLEAN:
   472                             super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Boolean");
   473                             super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
   474                                     "java/lang/Boolean", "booleanValue", "()Z"
   475                             );
   476                             super.visitInsn(Opcodes.IRETURN);
   477                             break;
   478                         default:
   479                             super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Number");
   480                             super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
   481                                     "java/lang/Number", sv.returnType.getClassName() + "Value", "()" + sv.returnType.getDescriptor()
   482                             );
   483                             super.visitInsn(sv.returnType.getOpcode(Opcodes.IRETURN));
   484                     }
   485                 } else {
   486                     super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
   487                             "org/netbeans/html/boot/spi/Fn", "invokeLater", "(Ljava/lang/Object;[Ljava/lang/Object;)V"
   488                     );
   489                     super.visitInsn(Opcodes.RETURN);
   490                 }
   491                 super.visitLabel(noPresenter);
   492                 if (hasCode) {
   493                     super.visitCode();
   494                 } else {
   495                     super.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalStateException");
   496                     super.visitInsn(Opcodes.DUP);
   497                     super.visitLdcInsn("No presenter active. Use BrwsrCtx.execute!");
   498                     super.visitMethodInsn(Opcodes.INVOKESPECIAL, 
   499                         "java/lang/IllegalStateException", "<init>", "(Ljava/lang/String;)V"
   500                     );
   501                     this.visitInsn(Opcodes.ATHROW);
   502                 }
   503                 return true;
   504             }
   505 
   506             @Override
   507             public void visitEnd() {
   508                 super.visitEnd();
   509                 if (fia != null) {
   510                     if (generateBody(false)) {
   511                         // native method
   512                         super.visitMaxs(1, 0);
   513                     }
   514                     FindInClass.this.visitField(
   515                             Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC,
   516                             "$$fn$$" + name + "_" + found,
   517                             "Lorg/netbeans/html/boot/spi/Fn;",
   518                             null, null
   519                     );
   520                 }
   521             }
   522 
   523             private final class FindInAnno extends AnnotationVisitor {
   524 
   525                 List<String> args = new ArrayList<String>();
   526                 String body;
   527                 boolean javacall = false;
   528                 boolean wait4js = true;
   529 
   530                 public FindInAnno() {
   531                     super(Opcodes.ASM4);
   532                 }
   533 
   534                 @Override
   535                 public void visit(String name, Object value) {
   536                     if (name == null) {
   537                         args.add((String) value);
   538                         return;
   539                     }
   540                     if (name.equals("javacall")) { // NOI18N
   541                         javacall = (Boolean) value;
   542                         return;
   543                     }
   544                     if (name.equals("wait4js")) { // NOI18N
   545                         wait4js = (Boolean) value;
   546                         return;
   547                     }
   548                     assert name.equals("body");
   549                     body = (String) value;
   550                 }
   551 
   552                 @Override
   553                 public AnnotationVisitor visitArray(String name) {
   554                     return this;
   555                 }
   556 
   557                 @Override
   558                 public void visitEnd() {
   559                     if (body != null) {
   560                         generateJSBody(this);
   561                     }
   562                 }
   563             }
   564         }
   565 
   566         private final class LoadResource extends AnnotationVisitor {
   567             public LoadResource(AnnotationVisitor av) {
   568                 super(Opcodes.ASM4, av);
   569             }
   570 
   571             @Override
   572             public void visit(String attrName, Object value) {
   573                 super.visit(attrName, value);
   574                 String relPath = (String) value;
   575                 if (relPath.startsWith("/")) {
   576                     resource = relPath;
   577                 } else {
   578                     int last = name.lastIndexOf('/');
   579                     String fullPath = name.substring(0, last + 1) + relPath;
   580                     resource = fullPath;
   581                 }
   582             }
   583         }
   584     }
   585 
   586     private static class ClassWriterEx extends ClassWriter {
   587 
   588         private ClassLoader loader;
   589 
   590         public ClassWriterEx(ClassLoader l, ClassReader classReader, int flags) {
   591             super(classReader, flags);
   592             this.loader = l;
   593         }
   594 
   595         @Override
   596         protected String getCommonSuperClass(final String type1, final String type2) {
   597             Class<?> c, d;
   598             try {
   599                 c = Class.forName(type1.replace('/', '.'), false, loader);
   600                 d = Class.forName(type2.replace('/', '.'), false, loader);
   601             } catch (Exception e) {
   602                 throw new RuntimeException(e.toString());
   603             }
   604             if (c.isAssignableFrom(d)) {
   605                 return type1;
   606             }
   607             if (d.isAssignableFrom(c)) {
   608                 return type2;
   609             }
   610             if (c.isInterface() || d.isInterface()) {
   611                 return "java/lang/Object";
   612             } else {
   613                 do {
   614                     c = c.getSuperclass();
   615                 } while (!c.isAssignableFrom(d));
   616                 return c.getName().replace('.', '/');
   617             }
   618         }
   619     }
   620 }