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