2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
4 * Copyright 2013-2014 Oracle and/or its affiliates. All rights reserved.
6 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
7 * Other names may be trademarks of their respective owners.
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]"
29 * The Original Software is NetBeans. The Initial Developer of the Original
30 * Software is Oracle. Portions Copyright 2013-2014 Oracle. All Rights Reserved.
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.
43 package org.netbeans.html.boot.impl;
45 import java.io.InputStream;
46 import java.io.InputStreamReader;
47 import java.io.Reader;
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;
70 * @author Jaroslav Tulach
72 public final class FnUtils {
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.
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
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
92 public short readShort(int index) {
93 short s = super.readShort(index);
94 if (index == 6 && s > Opcodes.V1_7) {
100 FindInClass tst = new FindInClass(loader, null);
103 ClassWriter w = new ClassWriterEx(loader, cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
104 FindInClass fic = new FindInClass(loader, w);
106 bytecode = w.toByteArray();
111 public static boolean isValid(Fn fn) {
112 return fn != null && fn.isValid();
115 public static ClassLoader newLoader(final FindResources f, final Fn.Presenter d, ClassLoader parent) {
116 return new JsClassLoader(parent) {
118 protected URL findResource(String name) {
119 List<URL> l = res(name, true);
120 return l.isEmpty() ? null : l.get(0);
124 protected Enumeration<URL> findResources(String name) {
125 return Collections.enumeration(res(name, false));
128 private List<URL> res(String name, boolean oneIsEnough) {
129 List<URL> l = new ArrayList<URL>();
130 f.findResources(name, l, oneIsEnough);
135 protected Fn defineFn(String code, String... names) {
136 return d.defineFn(code, names);
140 protected void loadScript(Reader code) throws Exception {
146 static String callback(final String body) {
147 return new JsCallback() {
149 protected CharSequence callMethod(
150 String ident, String fqn, String method, String params
152 StringBuilder sb = new StringBuilder();
153 sb.append("vm.").append(mangle(fqn, method, params));
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);
172 isr = new InputStreamReader(script, "UTF-8");
173 FnContext.currentPresenter(false).loadScript(isr);
179 } catch (Exception ex) {
180 throw new IllegalStateException("Can't execute " + resource, ex);
185 private static final class FindInClass extends ClassVisitor {
188 private ClassLoader loader;
189 private String resource;
191 public FindInClass(ClassLoader l, ClassVisitor cv) {
192 super(Opcodes.ASM4, cv);
197 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
199 super.visit(version, access, name, signature, superName, interfaces);
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);
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)
218 private final class FindInMethod extends MethodVisitor {
220 private final String name;
221 private final String desc;
222 private final int access;
223 private FindInAnno fia;
224 private boolean bodyGenerated;
226 public FindInMethod(int access, String name, String desc, MethodVisitor mv) {
227 super(Opcodes.ASM4, mv);
228 this.access = access;
234 public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
235 if ("Lnet/java/html/js/JavaScriptBody;".equals(desc)) { // NOI18N
237 return new FindInAnno();
239 return super.visitAnnotation(desc, visible);
242 private void generateJSBody(FindInAnno fia) {
247 public void visitCode() {
254 private boolean generateBody(boolean hasCode) {
258 bodyGenerated = true;
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);
266 va.visit("javacall", fia.javacall);
267 va.visit("body", fia.body);
274 body = callback(fia.body);
275 args = new ArrayList<String>(fia.args);
282 super.visitFieldInsn(
283 Opcodes.GETSTATIC, FindInClass.this.name,
284 "$$fn$$" + name + "_" + found,
285 "Lorg/netbeans/html/boot/spi/Fn;"
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"
293 Label ifNotNull = new Label();
294 super.visitJumpInsn(Opcodes.IFNE, ifNotNull);
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++) {
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);
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;"
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;"
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;"
336 super.visitLabel(ifNotNull);
339 if ((access & Opcodes.ACC_STATIC) == 0) {
341 super.visitIntInsn(Opcodes.ALOAD, 0);
344 super.visitInsn(Opcodes.ACONST_NULL);
347 super.visitIntInsn(Opcodes.SIPUSH, args.size());
348 super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
350 class SV extends SignatureVisitor {
352 private boolean nowReturn;
353 private Type returnType;
355 private int loadIndex = offset;
362 public void visitBaseType(char descriptor) {
363 final Type t = Type.getType("" + descriptor);
368 FindInMethod.super.visitInsn(Opcodes.DUP);
369 FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++);
370 FindInMethod.super.visitVarInsn(t.getOpcode(Opcodes.ILOAD), loadIndex++);
372 switch (descriptor) {
374 factory = "java/lang/Integer";
377 factory = "java/lang/Long";
381 factory = "java/lang/Short";
384 factory = "java/lang/Float";
387 factory = "java/lang/Double";
391 factory = "java/lang/Boolean";
394 factory = "java/lang/Character";
397 factory = "java/lang/Byte";
400 throw new IllegalStateException(t.toString());
402 FindInMethod.super.visitMethodInsn(Opcodes.INVOKESTATIC,
403 factory, "valueOf", "(" + descriptor + ")L" + factory + ";"
405 FindInMethod.super.visitInsn(Opcodes.AASTORE);
409 public SignatureVisitor visitArrayType() {
411 return new SignatureVisitor(Opcodes.ASM4) {
413 public void visitClassType(String name) {
414 returnType = Type.getType("[" + Type.getObjectType(name).getDescriptor());
419 return new SignatureWriter();
423 public void visitClassType(String name) {
425 returnType = Type.getObjectType(name);
432 public SignatureVisitor visitReturnType() {
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);
446 SignatureReader sr = new SignatureReader(desc);
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);
460 super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
461 "org/netbeans/html/boot/spi/Fn", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"
463 switch (sv.returnType.getSort()) {
465 super.visitInsn(Opcodes.RETURN);
469 super.visitTypeInsn(Opcodes.CHECKCAST, sv.returnType.getInternalName());
470 super.visitInsn(Opcodes.ARETURN);
473 super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Boolean");
474 super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
475 "java/lang/Boolean", "booleanValue", "()Z"
477 super.visitInsn(Opcodes.IRETURN);
480 super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Number");
481 super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
482 "java/lang/Number", sv.returnType.getClassName() + "Value", "()" + sv.returnType.getDescriptor()
484 super.visitInsn(sv.returnType.getOpcode(Opcodes.IRETURN));
487 super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
488 "org/netbeans/html/boot/spi/Fn", "invokeLater", "(Ljava/lang/Object;[Ljava/lang/Object;)V"
490 super.visitInsn(Opcodes.RETURN);
492 super.visitLabel(noPresenter);
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"
502 this.visitInsn(Opcodes.ATHROW);
508 public void visitEnd() {
511 if (generateBody(false)) {
513 super.visitMaxs(1, 0);
515 FindInClass.this.visitField(
516 Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC,
517 "$$fn$$" + name + "_" + found,
518 "Lorg/netbeans/html/boot/spi/Fn;",
524 private final class FindInAnno extends AnnotationVisitor {
526 List<String> args = new ArrayList<String>();
528 boolean javacall = false;
529 boolean wait4js = true;
530 boolean keepAlive = false;
532 public FindInAnno() {
537 public void visit(String name, Object value) {
539 args.add((String) value);
542 if (name.equals("javacall")) { // NOI18N
543 javacall = (Boolean) value;
546 if (name.equals("wait4js")) { // NOI18N
547 wait4js = (Boolean) value;
550 if (name.equals("keepAlive")) { // NOI18N
551 keepAlive = (Boolean) value;
554 assert name.equals("body"); // NOI18N
555 body = (String) value;
559 public AnnotationVisitor visitArray(String name) {
564 public void visitEnd() {
566 generateJSBody(this);
572 private final class LoadResource extends AnnotationVisitor {
573 public LoadResource(AnnotationVisitor av) {
574 super(Opcodes.ASM4, av);
578 public void visit(String attrName, Object value) {
579 super.visit(attrName, value);
580 String relPath = (String) value;
581 if (relPath.startsWith("/")) {
584 int last = name.lastIndexOf('/');
585 String fullPath = name.substring(0, last + 1) + relPath;
592 private static class ClassWriterEx extends ClassWriter {
594 private ClassLoader loader;
596 public ClassWriterEx(ClassLoader l, ClassReader classReader, int flags) {
597 super(classReader, flags);
602 protected String getCommonSuperClass(final String type1, final String type2) {
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());
610 if (c.isAssignableFrom(d)) {
613 if (d.isAssignableFrom(c)) {
616 if (c.isInterface() || d.isInterface()) {
617 return "java/lang/Object";
620 c = c.getSuperclass();
621 } while (!c.isAssignableFrom(d));
622 return c.getName().replace('.', '/');