boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java
author Jaroslav Tulach <jaroslav.tulach@netbeans.org>
Fri, 07 Feb 2014 07:44:34 +0100
changeset 551 7ca2253fa86d
parent 534 15907d1499fb
child 570 0e831501e072
permissions -rw-r--r--
Updating copyright headers to mention current year
     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.apidesign.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 <jtulach@netbeans.org>
    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 isJavaScriptCapable(ClassLoader l) {
   112         if (l instanceof JsClassLoader) {
   113             return true;
   114         }
   115         if (l.getResource("META-INF/net.java.html.js.classes") != null) {
   116             return false;
   117         }
   118         return true;
   119     }
   120     
   121     public static boolean isValid(Fn fn) {
   122         return fn != null && fn.isValid();
   123     }
   124 
   125     public static ClassLoader newLoader(final FindResources f, final Fn.Presenter d, ClassLoader parent) {
   126         return new JsClassLoader(parent) {
   127             @Override
   128             protected URL findResource(String name) {
   129                 List<URL> l = res(name, true);
   130                 return l.isEmpty() ? null : l.get(0);
   131             }
   132             
   133             @Override
   134             protected Enumeration<URL> findResources(String name) {
   135                 return Collections.enumeration(res(name, false));
   136             }
   137             
   138             private List<URL> res(String name, boolean oneIsEnough) {
   139                 List<URL> l = new ArrayList<URL>();
   140                 f.findResources(name, l, oneIsEnough);
   141                 return l;
   142             }
   143             
   144             @Override
   145             protected Fn defineFn(String code, String... names) {
   146                 return d.defineFn(code, names);
   147             }
   148 
   149             @Override
   150             protected void loadScript(Reader code) throws Exception {
   151                 d.loadScript(code);
   152             }
   153         };
   154     }
   155 
   156     static String callback(final String body) {
   157         return new JsCallback() {
   158             @Override
   159             protected CharSequence callMethod(
   160                 String ident, String fqn, String method, String params
   161             ) {
   162                 StringBuilder sb = new StringBuilder();
   163                 sb.append("vm.").append(mangle(fqn, method, params));
   164                 sb.append("(");
   165                 if (ident != null) {
   166                     sb.append(ident);
   167                 }
   168                 return sb;
   169             }
   170 
   171         }.parse(body);
   172     }
   173 
   174     static void loadScript(ClassLoader jcl, String resource) {
   175         final InputStream script = jcl.getResourceAsStream(resource);
   176         if (script == null) {
   177             throw new NullPointerException("Can't find " + resource);
   178         }
   179         try {
   180             Reader isr = null;
   181             try {
   182                 isr = new InputStreamReader(script, "UTF-8");
   183                 FnContext.currentPresenter(false).loadScript(isr);
   184             } finally {
   185                 if (isr != null) {
   186                     isr.close();
   187                 }
   188             }
   189         } catch (Exception ex) {
   190             throw new IllegalStateException("Can't execute " + resource, ex);
   191         } 
   192     }
   193     
   194     
   195     private static final class FindInClass extends ClassVisitor {
   196         private String name;
   197         private int found;
   198         private ClassLoader loader;
   199         private String resource;
   200 
   201         public FindInClass(ClassLoader l, ClassVisitor cv) {
   202             super(Opcodes.ASM4, cv);
   203             this.loader = l;
   204         }
   205 
   206         @Override
   207         public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
   208             this.name = name;
   209             super.visit(version, access, name, signature, superName, interfaces);
   210         }
   211 
   212         @Override
   213         public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
   214             final AnnotationVisitor del = super.visitAnnotation(desc, visible);
   215             if ("Lnet/java/html/js/JavaScriptResource;".equals(desc)) {
   216                 return new LoadResource(del);
   217             }
   218             return del;
   219         }
   220 
   221         @Override
   222         public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
   223             return new FindInMethod(access, name, desc,
   224                     super.visitMethod(access & (~Opcodes.ACC_NATIVE), name, desc, signature, exceptions)
   225             );
   226         }
   227 
   228         private final class FindInMethod extends MethodVisitor {
   229 
   230             private final String name;
   231             private final String desc;
   232             private final int access;
   233             private FindInAnno fia;
   234             private boolean bodyGenerated;
   235 
   236             public FindInMethod(int access, String name, String desc, MethodVisitor mv) {
   237                 super(Opcodes.ASM4, mv);
   238                 this.access = access;
   239                 this.name = name;
   240                 this.desc = desc;
   241             }
   242 
   243             @Override
   244             public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
   245                 if ("Lnet/java/html/js/JavaScriptBody;".equals(desc)) { // NOI18N
   246                     found++;
   247                     return new FindInAnno();
   248                 }
   249                 return super.visitAnnotation(desc, visible);
   250             }
   251 
   252             private void generateJSBody(FindInAnno fia) {
   253                 this.fia = fia;
   254             }
   255 
   256             @Override
   257             public void visitCode() {
   258                 if (fia == null) {
   259                     return;
   260                 }
   261                 generateBody(true);
   262             }
   263 
   264             private boolean generateBody(boolean hasCode) {
   265                 if (bodyGenerated) {
   266                     return false;
   267                 }
   268                 bodyGenerated = true;
   269                 if (mv != null) {
   270                     AnnotationVisitor va = super.visitAnnotation("Lnet/java/html/js/JavaScriptBody;", false);
   271                     AnnotationVisitor varr = va.visitArray("args");
   272                     for (String argName : fia.args) {
   273                         varr.visit(null, argName);
   274                     }
   275                     varr.visitEnd();
   276                     va.visit("javacall", fia.javacall);
   277                     va.visit("body", fia.body);
   278                     va.visitEnd();
   279                 }
   280                 
   281                 String body;
   282                 List<String> args;
   283                 if (fia.javacall) {
   284                     body = callback(fia.body);
   285                     args = new ArrayList<String>(fia.args);
   286                     args.add("vm");
   287                 } else {
   288                     body = fia.body;
   289                     args = fia.args;
   290                 }
   291 
   292                 super.visitFieldInsn(
   293                         Opcodes.GETSTATIC, FindInClass.this.name,
   294                         "$$fn$$" + name + "_" + found,
   295                         "Lorg/apidesign/html/boot/spi/Fn;"
   296                 );
   297                 super.visitInsn(Opcodes.DUP);
   298                 super.visitMethodInsn(
   299                         Opcodes.INVOKESTATIC,
   300                         "org/apidesign/html/boot/spi/Fn", "isValid",
   301                         "(Lorg/apidesign/html/boot/spi/Fn;)Z"
   302                 );
   303                 Label ifNotNull = new Label();
   304                 super.visitJumpInsn(Opcodes.IFNE, ifNotNull);
   305 
   306                 // init Fn
   307                 super.visitInsn(Opcodes.POP);
   308                 super.visitLdcInsn(Type.getObjectType(FindInClass.this.name));
   309                 super.visitLdcInsn(body);
   310                 super.visitIntInsn(Opcodes.SIPUSH, args.size());
   311                 super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String");
   312                 boolean needsVM = false;
   313                 for (int i = 0; i < args.size(); i++) {
   314                     assert !needsVM;
   315                     String argName = args.get(i);
   316                     needsVM = "vm".equals(argName);
   317                     super.visitInsn(Opcodes.DUP);
   318                     super.visitIntInsn(Opcodes.BIPUSH, i);
   319                     super.visitLdcInsn(argName);
   320                     super.visitInsn(Opcodes.AASTORE);
   321                 }
   322                 super.visitMethodInsn(Opcodes.INVOKESTATIC,
   323                         "org/apidesign/html/boot/spi/Fn", "define",
   324                         "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/String;)Lorg/apidesign/html/boot/spi/Fn;"
   325                 );
   326                 Label noPresenter = new Label();
   327                 if (hasCode) {
   328                     super.visitInsn(Opcodes.DUP);
   329                     super.visitJumpInsn(Opcodes.IFNULL, noPresenter);
   330                 }
   331                 if (resource != null) {
   332                     super.visitLdcInsn(Type.getObjectType(FindInClass.this.name));
   333                     super.visitLdcInsn(resource);
   334                     super.visitMethodInsn(Opcodes.INVOKESTATIC,
   335                             "org/apidesign/html/boot/spi/Fn", "preload",
   336                             "(Lorg/apidesign/html/boot/spi/Fn;Ljava/lang/Class;Ljava/lang/String;)Lorg/apidesign/html/boot/spi/Fn;"
   337                     );
   338                 }
   339                 super.visitInsn(Opcodes.DUP);
   340                 super.visitFieldInsn(
   341                         Opcodes.PUTSTATIC, FindInClass.this.name,
   342                         "$$fn$$" + name + "_" + found,
   343                         "Lorg/apidesign/html/boot/spi/Fn;"
   344                 );
   345                 // end of Fn init
   346 
   347                 super.visitLabel(ifNotNull);
   348 
   349                 final int offset;
   350                 if ((access & Opcodes.ACC_STATIC) == 0) {
   351                     offset = 1;
   352                     super.visitIntInsn(Opcodes.ALOAD, 0);
   353                 } else {
   354                     offset = 0;
   355                     super.visitInsn(Opcodes.ACONST_NULL);
   356                 }
   357 
   358                 super.visitIntInsn(Opcodes.SIPUSH, args.size());
   359                 super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
   360 
   361                 class SV extends SignatureVisitor {
   362 
   363                     private boolean nowReturn;
   364                     private Type returnType;
   365                     private int index;
   366                     private int loadIndex = offset;
   367 
   368                     public SV() {
   369                         super(Opcodes.ASM4);
   370                     }
   371 
   372                     @Override
   373                     public void visitBaseType(char descriptor) {
   374                         final Type t = Type.getType("" + descriptor);
   375                         if (nowReturn) {
   376                             returnType = t;
   377                             return;
   378                         }
   379                         FindInMethod.super.visitInsn(Opcodes.DUP);
   380                         FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++);
   381                         FindInMethod.super.visitVarInsn(t.getOpcode(Opcodes.ILOAD), loadIndex++);
   382                         String factory;
   383                         switch (descriptor) {
   384                             case 'I':
   385                                 factory = "java/lang/Integer";
   386                                 break;
   387                             case 'J':
   388                                 factory = "java/lang/Long";
   389                                 loadIndex++;
   390                                 break;
   391                             case 'S':
   392                                 factory = "java/lang/Short";
   393                                 break;
   394                             case 'F':
   395                                 factory = "java/lang/Float";
   396                                 break;
   397                             case 'D':
   398                                 factory = "java/lang/Double";
   399                                 loadIndex++;
   400                                 break;
   401                             case 'Z':
   402                                 factory = "java/lang/Boolean";
   403                                 break;
   404                             case 'C':
   405                                 factory = "java/lang/Character";
   406                                 break;
   407                             case 'B':
   408                                 factory = "java/lang/Byte";
   409                                 break;
   410                             default:
   411                                 throw new IllegalStateException(t.toString());
   412                         }
   413                         FindInMethod.super.visitMethodInsn(Opcodes.INVOKESTATIC,
   414                                 factory, "valueOf", "(" + descriptor + ")L" + factory + ";"
   415                         );
   416                         FindInMethod.super.visitInsn(Opcodes.AASTORE);
   417                     }
   418 
   419                     @Override
   420                     public SignatureVisitor visitArrayType() {
   421                         if (nowReturn) {
   422                             return new SignatureVisitor(Opcodes.ASM4) {
   423                                 @Override
   424                                 public void visitClassType(String name) {
   425                                     returnType = Type.getType("[" + Type.getObjectType(name).getDescriptor());
   426                                 }
   427                             };
   428                         }
   429                         loadObject();
   430                         return new SignatureWriter();
   431                     }
   432 
   433                     @Override
   434                     public void visitClassType(String name) {
   435                         if (nowReturn) {
   436                             returnType = Type.getObjectType(name);
   437                             return;
   438                         }
   439                         loadObject();
   440                     }
   441 
   442                     @Override
   443                     public SignatureVisitor visitReturnType() {
   444                         nowReturn = true;
   445                         return this;
   446                     }
   447 
   448                     private void loadObject() {
   449                         FindInMethod.super.visitInsn(Opcodes.DUP);
   450                         FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++);
   451                         FindInMethod.super.visitVarInsn(Opcodes.ALOAD, loadIndex++);
   452                         FindInMethod.super.visitInsn(Opcodes.AASTORE);
   453                     }
   454 
   455                 }
   456                 SV sv = new SV();
   457                 SignatureReader sr = new SignatureReader(desc);
   458                 sr.accept(sv);
   459 
   460                 if (needsVM) {
   461                     FindInMethod.super.visitInsn(Opcodes.DUP);
   462                     FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, sv.index);
   463                     int lastSlash = FindInClass.this.name.lastIndexOf('/');
   464                     String jsCallbacks = FindInClass.this.name.substring(0, lastSlash + 1) + "$JsCallbacks$";
   465                     FindInMethod.super.visitFieldInsn(Opcodes.GETSTATIC, jsCallbacks, "VM", "L" + jsCallbacks + ";");
   466                     FindInMethod.super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, jsCallbacks, "current", "()L" + jsCallbacks + ";");
   467                     FindInMethod.super.visitInsn(Opcodes.AASTORE);
   468                 }
   469 
   470                 super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
   471                         "org/apidesign/html/boot/spi/Fn", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"
   472                 );
   473                 switch (sv.returnType.getSort()) {
   474                     case Type.VOID:
   475                         super.visitInsn(Opcodes.RETURN);
   476                         break;
   477                     case Type.ARRAY:
   478                     case Type.OBJECT:
   479                         super.visitTypeInsn(Opcodes.CHECKCAST, sv.returnType.getInternalName());
   480                         super.visitInsn(Opcodes.ARETURN);
   481                         break;
   482                     case Type.BOOLEAN:
   483                         super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Boolean");
   484                         super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
   485                                 "java/lang/Boolean", "booleanValue", "()Z"
   486                         );
   487                         super.visitInsn(Opcodes.IRETURN);
   488                         break;
   489                     default:
   490                         super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Number");
   491                         super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
   492                                 "java/lang/Number", sv.returnType.getClassName() + "Value", "()" + sv.returnType.getDescriptor()
   493                         );
   494                         super.visitInsn(sv.returnType.getOpcode(Opcodes.IRETURN));
   495                 }
   496                 if (hasCode) {
   497                     super.visitLabel(noPresenter);
   498                     super.visitCode();
   499                 }
   500                 return true;
   501             }
   502 
   503             @Override
   504             public void visitEnd() {
   505                 super.visitEnd();
   506                 if (fia != null) {
   507                     if (generateBody(false)) {
   508                         // native method
   509                         super.visitMaxs(1, 0);
   510                     }
   511                     FindInClass.this.visitField(
   512                             Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC,
   513                             "$$fn$$" + name + "_" + found,
   514                             "Lorg/apidesign/html/boot/spi/Fn;",
   515                             null, null
   516                     );
   517                 }
   518             }
   519 
   520             private final class FindInAnno extends AnnotationVisitor {
   521 
   522                 List<String> args = new ArrayList<String>();
   523                 String body;
   524                 boolean javacall = false;
   525 
   526                 public FindInAnno() {
   527                     super(Opcodes.ASM4);
   528                 }
   529 
   530                 @Override
   531                 public void visit(String name, Object value) {
   532                     if (name == null) {
   533                         args.add((String) value);
   534                         return;
   535                     }
   536                     if (name.equals("javacall")) { // NOI18N
   537                         javacall = (Boolean) value;
   538                         return;
   539                     }
   540                     assert name.equals("body");
   541                     body = (String) value;
   542                 }
   543 
   544                 @Override
   545                 public AnnotationVisitor visitArray(String name) {
   546                     return this;
   547                 }
   548 
   549                 @Override
   550                 public void visitEnd() {
   551                     if (body != null) {
   552                         generateJSBody(this);
   553                     }
   554                 }
   555             }
   556         }
   557 
   558         private final class LoadResource extends AnnotationVisitor {
   559             public LoadResource(AnnotationVisitor av) {
   560                 super(Opcodes.ASM4, av);
   561             }
   562 
   563             @Override
   564             public void visit(String attrName, Object value) {
   565                 super.visit(attrName, value);
   566                 String relPath = (String) value;
   567                 if (relPath.startsWith("/")) {
   568                     resource = relPath;
   569                 } else {
   570                     int last = name.lastIndexOf('/');
   571                     String fullPath = name.substring(0, last + 1) + relPath;
   572                     resource = fullPath;
   573                 }
   574             }
   575         }
   576     }
   577 
   578     private static class ClassWriterEx extends ClassWriter {
   579 
   580         private ClassLoader loader;
   581 
   582         public ClassWriterEx(ClassLoader l, ClassReader classReader, int flags) {
   583             super(classReader, flags);
   584             this.loader = l;
   585         }
   586 
   587         @Override
   588         protected String getCommonSuperClass(final String type1, final String type2) {
   589             Class<?> c, d;
   590             try {
   591                 c = Class.forName(type1.replace('/', '.'), false, loader);
   592                 d = Class.forName(type2.replace('/', '.'), false, loader);
   593             } catch (Exception e) {
   594                 throw new RuntimeException(e.toString());
   595             }
   596             if (c.isAssignableFrom(d)) {
   597                 return type1;
   598             }
   599             if (d.isAssignableFrom(c)) {
   600                 return type2;
   601             }
   602             if (c.isInterface() || d.isInterface()) {
   603                 return "java/lang/Object";
   604             } else {
   605                 do {
   606                     c = c.getSuperclass();
   607                 } while (!c.isAssignableFrom(d));
   608                 return c.getName().replace('.', '/');
   609             }
   610         }
   611     }
   612 }