boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java
author Jaroslav Tulach <jtulach@netbeans.org>
Tue, 26 Aug 2014 18:13:30 +0200
changeset 838 bdc3d696dd4a
parent 790 30f20d9c0986
child 851 69ed96e7f41b
permissions -rw-r--r--
During the API review process (bug 246133) the reviewers decided that in order to include html4j to NetBeans Platform, we need to stop using org.apidesign namespace and switch to NetBeans one. Repackaging all SPI packages into org.netbeans.html.smthng.spi.
     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 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/netbeans/html/boot/spi/Fn;"
   296                 );
   297                 super.visitInsn(Opcodes.DUP);
   298                 super.visitMethodInsn(
   299                         Opcodes.INVOKESTATIC,
   300                         "org/netbeans/html/boot/spi/Fn", "isValid",
   301                         "(Lorg/netbeans/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/netbeans/html/boot/spi/Fn", "define",
   324                         "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/String;)Lorg/netbeans/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/netbeans/html/boot/spi/Fn", "preload",
   336                             "(Lorg/netbeans/html/boot/spi/Fn;Ljava/lang/Class;Ljava/lang/String;)Lorg/netbeans/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/netbeans/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                 if (fia.wait4js) {
   471                     super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
   472                             "org/netbeans/html/boot/spi/Fn", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"
   473                     );
   474                     switch (sv.returnType.getSort()) {
   475                         case Type.VOID:
   476                             super.visitInsn(Opcodes.RETURN);
   477                             break;
   478                         case Type.ARRAY:
   479                         case Type.OBJECT:
   480                             super.visitTypeInsn(Opcodes.CHECKCAST, sv.returnType.getInternalName());
   481                             super.visitInsn(Opcodes.ARETURN);
   482                             break;
   483                         case Type.BOOLEAN:
   484                             super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Boolean");
   485                             super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
   486                                     "java/lang/Boolean", "booleanValue", "()Z"
   487                             );
   488                             super.visitInsn(Opcodes.IRETURN);
   489                             break;
   490                         default:
   491                             super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Number");
   492                             super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
   493                                     "java/lang/Number", sv.returnType.getClassName() + "Value", "()" + sv.returnType.getDescriptor()
   494                             );
   495                             super.visitInsn(sv.returnType.getOpcode(Opcodes.IRETURN));
   496                     }
   497                 } else {
   498                     super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
   499                             "org/netbeans/html/boot/spi/Fn", "invokeLater", "(Ljava/lang/Object;[Ljava/lang/Object;)V"
   500                     );
   501                     super.visitInsn(Opcodes.RETURN);
   502                 }
   503                 if (hasCode) {
   504                     super.visitLabel(noPresenter);
   505                     super.visitCode();
   506                 }
   507                 return true;
   508             }
   509 
   510             @Override
   511             public void visitEnd() {
   512                 super.visitEnd();
   513                 if (fia != null) {
   514                     if (generateBody(false)) {
   515                         // native method
   516                         super.visitMaxs(1, 0);
   517                     }
   518                     FindInClass.this.visitField(
   519                             Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC,
   520                             "$$fn$$" + name + "_" + found,
   521                             "Lorg/netbeans/html/boot/spi/Fn;",
   522                             null, null
   523                     );
   524                 }
   525             }
   526 
   527             private final class FindInAnno extends AnnotationVisitor {
   528 
   529                 List<String> args = new ArrayList<String>();
   530                 String body;
   531                 boolean javacall = false;
   532                 boolean wait4js = true;
   533 
   534                 public FindInAnno() {
   535                     super(Opcodes.ASM4);
   536                 }
   537 
   538                 @Override
   539                 public void visit(String name, Object value) {
   540                     if (name == null) {
   541                         args.add((String) value);
   542                         return;
   543                     }
   544                     if (name.equals("javacall")) { // NOI18N
   545                         javacall = (Boolean) value;
   546                         return;
   547                     }
   548                     if (name.equals("wait4js")) { // NOI18N
   549                         wait4js = (Boolean) value;
   550                         return;
   551                     }
   552                     assert name.equals("body");
   553                     body = (String) value;
   554                 }
   555 
   556                 @Override
   557                 public AnnotationVisitor visitArray(String name) {
   558                     return this;
   559                 }
   560 
   561                 @Override
   562                 public void visitEnd() {
   563                     if (body != null) {
   564                         generateJSBody(this);
   565                     }
   566                 }
   567             }
   568         }
   569 
   570         private final class LoadResource extends AnnotationVisitor {
   571             public LoadResource(AnnotationVisitor av) {
   572                 super(Opcodes.ASM4, av);
   573             }
   574 
   575             @Override
   576             public void visit(String attrName, Object value) {
   577                 super.visit(attrName, value);
   578                 String relPath = (String) value;
   579                 if (relPath.startsWith("/")) {
   580                     resource = relPath;
   581                 } else {
   582                     int last = name.lastIndexOf('/');
   583                     String fullPath = name.substring(0, last + 1) + relPath;
   584                     resource = fullPath;
   585                 }
   586             }
   587         }
   588     }
   589 
   590     private static class ClassWriterEx extends ClassWriter {
   591 
   592         private ClassLoader loader;
   593 
   594         public ClassWriterEx(ClassLoader l, ClassReader classReader, int flags) {
   595             super(classReader, flags);
   596             this.loader = l;
   597         }
   598 
   599         @Override
   600         protected String getCommonSuperClass(final String type1, final String type2) {
   601             Class<?> c, d;
   602             try {
   603                 c = Class.forName(type1.replace('/', '.'), false, loader);
   604                 d = Class.forName(type2.replace('/', '.'), false, loader);
   605             } catch (Exception e) {
   606                 throw new RuntimeException(e.toString());
   607             }
   608             if (c.isAssignableFrom(d)) {
   609                 return type1;
   610             }
   611             if (d.isAssignableFrom(c)) {
   612                 return type2;
   613             }
   614             if (c.isInterface() || d.isInterface()) {
   615                 return "java/lang/Object";
   616             } else {
   617                 do {
   618                     c = c.getSuperclass();
   619                 } while (!c.isAssignableFrom(d));
   620                 return c.getName().replace('.', '/');
   621             }
   622         }
   623     }
   624 }