boot/src/main/java/org/netbeans/html/boot/impl/FnUtils.java
author Jaroslav Tulach <jtulach@netbeans.org>
Fri, 12 Dec 2014 11:22:40 +0100
branchgc
changeset 900 2ee22312e414
parent 884 af690d50d7d6
child 906 e1291f7b7626
permissions -rw-r--r--
Giving API users better control over GC aspects of their objects
     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.visitInsn(fia.keepAlive ? Opcodes.ICONST_1 : Opcodes.ICONST_0);
   300                 super.visitLdcInsn(body);
   301                 super.visitIntInsn(Opcodes.SIPUSH, args.size());
   302                 super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String");
   303                 boolean needsVM = false;
   304                 for (int i = 0; i < args.size(); i++) {
   305                     assert !needsVM;
   306                     String argName = args.get(i);
   307                     needsVM = "vm".equals(argName);
   308                     super.visitInsn(Opcodes.DUP);
   309                     super.visitIntInsn(Opcodes.BIPUSH, i);
   310                     super.visitLdcInsn(argName);
   311                     super.visitInsn(Opcodes.AASTORE);
   312                 }
   313                 super.visitMethodInsn(Opcodes.INVOKESTATIC,
   314                         "org/netbeans/html/boot/spi/Fn", "define",
   315                         "(Ljava/lang/Class;ZLjava/lang/String;[Ljava/lang/String;)Lorg/netbeans/html/boot/spi/Fn;"
   316                 );
   317                 Label noPresenter = new Label();
   318                 super.visitInsn(Opcodes.DUP);
   319                 super.visitJumpInsn(Opcodes.IFNULL, noPresenter);
   320                 if (resource != null) {
   321                     super.visitLdcInsn(Type.getObjectType(FindInClass.this.name));
   322                     super.visitLdcInsn(resource);
   323                     super.visitMethodInsn(Opcodes.INVOKESTATIC,
   324                             "org/netbeans/html/boot/spi/Fn", "preload",
   325                             "(Lorg/netbeans/html/boot/spi/Fn;Ljava/lang/Class;Ljava/lang/String;)Lorg/netbeans/html/boot/spi/Fn;"
   326                     );
   327                 }
   328                 super.visitInsn(Opcodes.DUP);
   329                 super.visitFieldInsn(
   330                         Opcodes.PUTSTATIC, FindInClass.this.name,
   331                         "$$fn$$" + name + "_" + found,
   332                         "Lorg/netbeans/html/boot/spi/Fn;"
   333                 );
   334                 // end of Fn init
   335 
   336                 super.visitLabel(ifNotNull);
   337 
   338                 final int offset;
   339                 if ((access & Opcodes.ACC_STATIC) == 0) {
   340                     offset = 1;
   341                     super.visitIntInsn(Opcodes.ALOAD, 0);
   342                 } else {
   343                     offset = 0;
   344                     super.visitInsn(Opcodes.ACONST_NULL);
   345                 }
   346 
   347                 super.visitIntInsn(Opcodes.SIPUSH, args.size());
   348                 super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
   349 
   350                 class SV extends SignatureVisitor {
   351 
   352                     private boolean nowReturn;
   353                     private Type returnType;
   354                     private int index;
   355                     private int loadIndex = offset;
   356 
   357                     public SV() {
   358                         super(Opcodes.ASM4);
   359                     }
   360 
   361                     @Override
   362                     public void visitBaseType(char descriptor) {
   363                         final Type t = Type.getType("" + descriptor);
   364                         if (nowReturn) {
   365                             returnType = t;
   366                             return;
   367                         }
   368                         FindInMethod.super.visitInsn(Opcodes.DUP);
   369                         FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++);
   370                         FindInMethod.super.visitVarInsn(t.getOpcode(Opcodes.ILOAD), loadIndex++);
   371                         String factory;
   372                         switch (descriptor) {
   373                             case 'I':
   374                                 factory = "java/lang/Integer";
   375                                 break;
   376                             case 'J':
   377                                 factory = "java/lang/Long";
   378                                 loadIndex++;
   379                                 break;
   380                             case 'S':
   381                                 factory = "java/lang/Short";
   382                                 break;
   383                             case 'F':
   384                                 factory = "java/lang/Float";
   385                                 break;
   386                             case 'D':
   387                                 factory = "java/lang/Double";
   388                                 loadIndex++;
   389                                 break;
   390                             case 'Z':
   391                                 factory = "java/lang/Boolean";
   392                                 break;
   393                             case 'C':
   394                                 factory = "java/lang/Character";
   395                                 break;
   396                             case 'B':
   397                                 factory = "java/lang/Byte";
   398                                 break;
   399                             default:
   400                                 throw new IllegalStateException(t.toString());
   401                         }
   402                         FindInMethod.super.visitMethodInsn(Opcodes.INVOKESTATIC,
   403                                 factory, "valueOf", "(" + descriptor + ")L" + factory + ";"
   404                         );
   405                         FindInMethod.super.visitInsn(Opcodes.AASTORE);
   406                     }
   407 
   408                     @Override
   409                     public SignatureVisitor visitArrayType() {
   410                         if (nowReturn) {
   411                             return new SignatureVisitor(Opcodes.ASM4) {
   412                                 @Override
   413                                 public void visitClassType(String name) {
   414                                     returnType = Type.getType("[" + Type.getObjectType(name).getDescriptor());
   415                                 }
   416                             };
   417                         }
   418                         loadObject();
   419                         return new SignatureWriter();
   420                     }
   421 
   422                     @Override
   423                     public void visitClassType(String name) {
   424                         if (nowReturn) {
   425                             returnType = Type.getObjectType(name);
   426                             return;
   427                         }
   428                         loadObject();
   429                     }
   430 
   431                     @Override
   432                     public SignatureVisitor visitReturnType() {
   433                         nowReturn = true;
   434                         return this;
   435                     }
   436 
   437                     private void loadObject() {
   438                         FindInMethod.super.visitInsn(Opcodes.DUP);
   439                         FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++);
   440                         FindInMethod.super.visitVarInsn(Opcodes.ALOAD, loadIndex++);
   441                         FindInMethod.super.visitInsn(Opcodes.AASTORE);
   442                     }
   443 
   444                 }
   445                 SV sv = new SV();
   446                 SignatureReader sr = new SignatureReader(desc);
   447                 sr.accept(sv);
   448 
   449                 if (needsVM) {
   450                     FindInMethod.super.visitInsn(Opcodes.DUP);
   451                     FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, sv.index);
   452                     int lastSlash = FindInClass.this.name.lastIndexOf('/');
   453                     String jsCallbacks = FindInClass.this.name.substring(0, lastSlash + 1) + "$JsCallbacks$";
   454                     FindInMethod.super.visitFieldInsn(Opcodes.GETSTATIC, jsCallbacks, "VM", "L" + jsCallbacks + ";");
   455                     FindInMethod.super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, jsCallbacks, "current", "()L" + jsCallbacks + ";");
   456                     FindInMethod.super.visitInsn(Opcodes.AASTORE);
   457                 }
   458 
   459                 if (fia.wait4js) {
   460                     super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
   461                             "org/netbeans/html/boot/spi/Fn", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"
   462                     );
   463                     switch (sv.returnType.getSort()) {
   464                         case Type.VOID:
   465                             super.visitInsn(Opcodes.RETURN);
   466                             break;
   467                         case Type.ARRAY:
   468                         case Type.OBJECT:
   469                             super.visitTypeInsn(Opcodes.CHECKCAST, sv.returnType.getInternalName());
   470                             super.visitInsn(Opcodes.ARETURN);
   471                             break;
   472                         case Type.BOOLEAN:
   473                             super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Boolean");
   474                             super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
   475                                     "java/lang/Boolean", "booleanValue", "()Z"
   476                             );
   477                             super.visitInsn(Opcodes.IRETURN);
   478                             break;
   479                         default:
   480                             super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Number");
   481                             super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
   482                                     "java/lang/Number", sv.returnType.getClassName() + "Value", "()" + sv.returnType.getDescriptor()
   483                             );
   484                             super.visitInsn(sv.returnType.getOpcode(Opcodes.IRETURN));
   485                     }
   486                 } else {
   487                     super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
   488                             "org/netbeans/html/boot/spi/Fn", "invokeLater", "(Ljava/lang/Object;[Ljava/lang/Object;)V"
   489                     );
   490                     super.visitInsn(Opcodes.RETURN);
   491                 }
   492                 super.visitLabel(noPresenter);
   493                 if (hasCode) {
   494                     super.visitCode();
   495                 } else {
   496                     super.visitTypeInsn(Opcodes.NEW, "java/lang/IllegalStateException");
   497                     super.visitInsn(Opcodes.DUP);
   498                     super.visitLdcInsn("No presenter active. Use BrwsrCtx.execute!");
   499                     super.visitMethodInsn(Opcodes.INVOKESPECIAL, 
   500                         "java/lang/IllegalStateException", "<init>", "(Ljava/lang/String;)V"
   501                     );
   502                     this.visitInsn(Opcodes.ATHROW);
   503                 }
   504                 return true;
   505             }
   506 
   507             @Override
   508             public void visitEnd() {
   509                 super.visitEnd();
   510                 if (fia != null) {
   511                     if (generateBody(false)) {
   512                         // native method
   513                         super.visitMaxs(1, 0);
   514                     }
   515                     FindInClass.this.visitField(
   516                             Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC,
   517                             "$$fn$$" + name + "_" + found,
   518                             "Lorg/netbeans/html/boot/spi/Fn;",
   519                             null, null
   520                     );
   521                 }
   522             }
   523 
   524             private final class FindInAnno extends AnnotationVisitor {
   525 
   526                 List<String> args = new ArrayList<String>();
   527                 String body;
   528                 boolean javacall = false;
   529                 boolean wait4js = true;
   530                 boolean keepAlive = false;
   531 
   532                 public FindInAnno() {
   533                     super(Opcodes.ASM4);
   534                 }
   535 
   536                 @Override
   537                 public void visit(String name, Object value) {
   538                     if (name == null) {
   539                         args.add((String) value);
   540                         return;
   541                     }
   542                     if (name.equals("javacall")) { // NOI18N
   543                         javacall = (Boolean) value;
   544                         return;
   545                     }
   546                     if (name.equals("wait4js")) { // NOI18N
   547                         wait4js = (Boolean) value;
   548                         return;
   549                     }
   550                     if (name.equals("keepAlive")) { // NOI18N
   551                         keepAlive = (Boolean) value;
   552                         return;
   553                     }
   554                     assert name.equals("body"); // NOI18N
   555                     body = (String) value;
   556                 }
   557 
   558                 @Override
   559                 public AnnotationVisitor visitArray(String name) {
   560                     return this;
   561                 }
   562 
   563                 @Override
   564                 public void visitEnd() {
   565                     if (body != null) {
   566                         generateJSBody(this);
   567                     }
   568                 }
   569             }
   570         }
   571 
   572         private final class LoadResource extends AnnotationVisitor {
   573             public LoadResource(AnnotationVisitor av) {
   574                 super(Opcodes.ASM4, av);
   575             }
   576 
   577             @Override
   578             public void visit(String attrName, Object value) {
   579                 super.visit(attrName, value);
   580                 String relPath = (String) value;
   581                 if (relPath.startsWith("/")) {
   582                     resource = relPath;
   583                 } else {
   584                     int last = name.lastIndexOf('/');
   585                     String fullPath = name.substring(0, last + 1) + relPath;
   586                     resource = fullPath;
   587                 }
   588             }
   589         }
   590     }
   591 
   592     private static class ClassWriterEx extends ClassWriter {
   593 
   594         private ClassLoader loader;
   595 
   596         public ClassWriterEx(ClassLoader l, ClassReader classReader, int flags) {
   597             super(classReader, flags);
   598             this.loader = l;
   599         }
   600 
   601         @Override
   602         protected String getCommonSuperClass(final String type1, final String type2) {
   603             Class<?> c, d;
   604             try {
   605                 c = Class.forName(type1.replace('/', '.'), false, loader);
   606                 d = Class.forName(type2.replace('/', '.'), false, loader);
   607             } catch (Exception e) {
   608                 throw new RuntimeException(e.toString());
   609             }
   610             if (c.isAssignableFrom(d)) {
   611                 return type1;
   612             }
   613             if (d.isAssignableFrom(c)) {
   614                 return type2;
   615             }
   616             if (c.isInterface() || d.isInterface()) {
   617                 return "java/lang/Object";
   618             } else {
   619                 do {
   620                     c = c.getSuperclass();
   621                 } while (!c.isAssignableFrom(d));
   622                 return c.getName().replace('.', '/');
   623             }
   624         }
   625     }
   626 }