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.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;
70 * @author Jaroslav Tulach <jtulach@netbeans.org>
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 isJavaScriptCapable(ClassLoader l) {
112 if (l instanceof JsClassLoader) {
115 if (l.getResource("META-INF/net.java.html.js.classes") != null) {
121 public static boolean isValid(Fn fn) {
122 return fn != null && fn.isValid();
125 public static ClassLoader newLoader(final FindResources f, final Fn.Presenter d, ClassLoader parent) {
126 return new JsClassLoader(parent) {
128 protected URL findResource(String name) {
129 List<URL> l = res(name, true);
130 return l.isEmpty() ? null : l.get(0);
134 protected Enumeration<URL> findResources(String name) {
135 return Collections.enumeration(res(name, false));
138 private List<URL> res(String name, boolean oneIsEnough) {
139 List<URL> l = new ArrayList<URL>();
140 f.findResources(name, l, oneIsEnough);
145 protected Fn defineFn(String code, String... names) {
146 return d.defineFn(code, names);
150 protected void loadScript(Reader code) throws Exception {
156 static String callback(final String body) {
157 return new JsCallback() {
159 protected CharSequence callMethod(
160 String ident, String fqn, String method, String params
162 StringBuilder sb = new StringBuilder();
163 sb.append("vm.").append(mangle(fqn, method, params));
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);
182 isr = new InputStreamReader(script, "UTF-8");
183 FnContext.currentPresenter(false).loadScript(isr);
189 } catch (Exception ex) {
190 throw new IllegalStateException("Can't execute " + resource, ex);
195 private static final class FindInClass extends ClassVisitor {
198 private ClassLoader loader;
199 private String resource;
201 public FindInClass(ClassLoader l, ClassVisitor cv) {
202 super(Opcodes.ASM4, cv);
207 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
209 super.visit(version, access, name, signature, superName, interfaces);
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);
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)
228 private final class FindInMethod extends MethodVisitor {
230 private final String name;
231 private final String desc;
232 private final int access;
233 private FindInAnno fia;
234 private boolean bodyGenerated;
236 public FindInMethod(int access, String name, String desc, MethodVisitor mv) {
237 super(Opcodes.ASM4, mv);
238 this.access = access;
244 public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
245 if ("Lnet/java/html/js/JavaScriptBody;".equals(desc)) { // NOI18N
247 return new FindInAnno();
249 return super.visitAnnotation(desc, visible);
252 private void generateJSBody(FindInAnno fia) {
257 public void visitCode() {
264 private boolean generateBody(boolean hasCode) {
268 bodyGenerated = true;
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);
276 va.visit("javacall", fia.javacall);
277 va.visit("body", fia.body);
284 body = callback(fia.body);
285 args = new ArrayList<String>(fia.args);
292 super.visitFieldInsn(
293 Opcodes.GETSTATIC, FindInClass.this.name,
294 "$$fn$$" + name + "_" + found,
295 "Lorg/apidesign/html/boot/spi/Fn;"
297 super.visitInsn(Opcodes.DUP);
298 super.visitMethodInsn(
299 Opcodes.INVOKESTATIC,
300 "org/apidesign/html/boot/spi/Fn", "isValid",
301 "(Lorg/apidesign/html/boot/spi/Fn;)Z"
303 Label ifNotNull = new Label();
304 super.visitJumpInsn(Opcodes.IFNE, ifNotNull);
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++) {
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);
322 super.visitMethodInsn(Opcodes.INVOKESTATIC,
323 "org/apidesign/html/boot/spi/Fn", "define",
324 "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/String;)Lorg/apidesign/html/boot/spi/Fn;"
326 Label noPresenter = new Label();
328 super.visitInsn(Opcodes.DUP);
329 super.visitJumpInsn(Opcodes.IFNULL, noPresenter);
331 if (resource != null) {
332 super.visitLdcInsn(Type.getObjectType(FindInClass.this.name));
333 super.visitLdcInsn(resource);
334 super.visitMethodInsn(Opcodes.INVOKESTATIC,
335 "org/apidesign/html/boot/spi/Fn", "preload",
336 "(Lorg/apidesign/html/boot/spi/Fn;Ljava/lang/Class;Ljava/lang/String;)Lorg/apidesign/html/boot/spi/Fn;"
339 super.visitInsn(Opcodes.DUP);
340 super.visitFieldInsn(
341 Opcodes.PUTSTATIC, FindInClass.this.name,
342 "$$fn$$" + name + "_" + found,
343 "Lorg/apidesign/html/boot/spi/Fn;"
347 super.visitLabel(ifNotNull);
350 if ((access & Opcodes.ACC_STATIC) == 0) {
352 super.visitIntInsn(Opcodes.ALOAD, 0);
355 super.visitInsn(Opcodes.ACONST_NULL);
358 super.visitIntInsn(Opcodes.SIPUSH, args.size());
359 super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
361 class SV extends SignatureVisitor {
363 private boolean nowReturn;
364 private Type returnType;
366 private int loadIndex = offset;
373 public void visitBaseType(char descriptor) {
374 final Type t = Type.getType("" + descriptor);
379 FindInMethod.super.visitInsn(Opcodes.DUP);
380 FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++);
381 FindInMethod.super.visitVarInsn(t.getOpcode(Opcodes.ILOAD), loadIndex++);
383 switch (descriptor) {
385 factory = "java/lang/Integer";
388 factory = "java/lang/Long";
392 factory = "java/lang/Short";
395 factory = "java/lang/Float";
398 factory = "java/lang/Double";
402 factory = "java/lang/Boolean";
405 factory = "java/lang/Character";
408 factory = "java/lang/Byte";
411 throw new IllegalStateException(t.toString());
413 FindInMethod.super.visitMethodInsn(Opcodes.INVOKESTATIC,
414 factory, "valueOf", "(" + descriptor + ")L" + factory + ";"
416 FindInMethod.super.visitInsn(Opcodes.AASTORE);
420 public SignatureVisitor visitArrayType() {
422 return new SignatureVisitor(Opcodes.ASM4) {
424 public void visitClassType(String name) {
425 returnType = Type.getType("[" + Type.getObjectType(name).getDescriptor());
430 return new SignatureWriter();
434 public void visitClassType(String name) {
436 returnType = Type.getObjectType(name);
443 public SignatureVisitor visitReturnType() {
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);
457 SignatureReader sr = new SignatureReader(desc);
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);
470 super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
471 "org/apidesign/html/boot/spi/Fn", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"
473 switch (sv.returnType.getSort()) {
475 super.visitInsn(Opcodes.RETURN);
479 super.visitTypeInsn(Opcodes.CHECKCAST, sv.returnType.getInternalName());
480 super.visitInsn(Opcodes.ARETURN);
483 super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Boolean");
484 super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
485 "java/lang/Boolean", "booleanValue", "()Z"
487 super.visitInsn(Opcodes.IRETURN);
490 super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Number");
491 super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
492 "java/lang/Number", sv.returnType.getClassName() + "Value", "()" + sv.returnType.getDescriptor()
494 super.visitInsn(sv.returnType.getOpcode(Opcodes.IRETURN));
497 super.visitLabel(noPresenter);
504 public void visitEnd() {
507 if (generateBody(false)) {
509 super.visitMaxs(1, 0);
511 FindInClass.this.visitField(
512 Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC,
513 "$$fn$$" + name + "_" + found,
514 "Lorg/apidesign/html/boot/spi/Fn;",
520 private final class FindInAnno extends AnnotationVisitor {
522 List<String> args = new ArrayList<String>();
524 boolean javacall = false;
526 public FindInAnno() {
531 public void visit(String name, Object value) {
533 args.add((String) value);
536 if (name.equals("javacall")) { // NOI18N
537 javacall = (Boolean) value;
540 assert name.equals("body");
541 body = (String) value;
545 public AnnotationVisitor visitArray(String name) {
550 public void visitEnd() {
552 generateJSBody(this);
558 private final class LoadResource extends AnnotationVisitor {
559 public LoadResource(AnnotationVisitor av) {
560 super(Opcodes.ASM4, av);
564 public void visit(String attrName, Object value) {
565 super.visit(attrName, value);
566 String relPath = (String) value;
567 if (relPath.startsWith("/")) {
570 int last = name.lastIndexOf('/');
571 String fullPath = name.substring(0, last + 1) + relPath;
578 private static class ClassWriterEx extends ClassWriter {
580 private ClassLoader loader;
582 public ClassWriterEx(ClassLoader l, ClassReader classReader, int flags) {
583 super(classReader, flags);
588 protected String getCommonSuperClass(final String type1, final String type2) {
591 c = Class.forName(type1.replace('/', '.'), false, loader);
592 d = Class.forName(type2.replace('/', '.'), false, loader);
593 } catch (Exception e) {
594 throw new RuntimeException(e.toString());
596 if (c.isAssignableFrom(d)) {
599 if (d.isAssignableFrom(c)) {
602 if (c.isInterface() || d.isInterface()) {
603 return "java/lang/Object";
606 c = c.getSuperclass();
607 } while (!c.isAssignableFrom(d));
608 return c.getName().replace('.', '/');