2 * HTML via Java(tm) Language Bindings
3 * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 2 of the License.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details. apidesign.org
13 * designates this particular file as subject to the
14 * "Classpath" exception as provided by apidesign.org
15 * in the License file that accompanied this code.
17 * You should have received a copy of the GNU General Public License
18 * along with this program. Look for COPYING file in the top folder.
19 * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
21 package org.apidesign.html.boot.impl;
23 import java.io.Closeable;
24 import java.io.InputStream;
25 import java.io.InputStreamReader;
26 import java.io.Reader;
28 import java.util.ArrayList;
29 import java.util.Collections;
30 import java.util.Enumeration;
31 import java.util.List;
32 import java.util.concurrent.Callable;
33 import org.apidesign.html.boot.spi.Fn;
34 import org.objectweb.asm.AnnotationVisitor;
35 import org.objectweb.asm.ClassReader;
36 import org.objectweb.asm.ClassVisitor;
37 import org.objectweb.asm.ClassWriter;
38 import org.objectweb.asm.Label;
39 import org.objectweb.asm.MethodVisitor;
40 import org.objectweb.asm.Opcodes;
41 import org.objectweb.asm.Type;
42 import org.objectweb.asm.signature.SignatureReader;
43 import org.objectweb.asm.signature.SignatureVisitor;
44 import org.objectweb.asm.signature.SignatureWriter;
48 * @author Jaroslav Tulach <jtulach@netbeans.org>
50 public final class FnUtils implements Fn.Presenter {
55 public static boolean isJavaScriptCapable(ClassLoader l) {
56 if (l instanceof JsClassLoader) {
60 try (Closeable c = Fn.activate(new FnUtils())) {
61 clazz = Class.forName(Test.class.getName(), true, l);
62 final Object is = ((Callable<?>)clazz.newInstance()).call();
63 return Boolean.TRUE.equals(is);
64 } catch (Exception ex) {
69 public static boolean isValid(Fn fn) {
70 return fn != null && fn.isValid();
73 public static ClassLoader newLoader(final FindResources f, final Fn.Presenter d, ClassLoader parent) {
74 return new JsClassLoader(parent) {
76 protected URL findResource(String name) {
77 List<URL> l = res(name, true);
78 return l.isEmpty() ? null : l.get(0);
82 protected Enumeration<URL> findResources(String name) {
83 return Collections.enumeration(res(name, false));
86 private List<URL> res(String name, boolean oneIsEnough) {
87 List<URL> l = new ArrayList<URL>();
88 f.findResources(name, l, oneIsEnough);
93 protected Fn defineFn(String code, String... names) {
94 return d.defineFn(code, names);
98 protected void loadScript(Reader code) throws Exception {
104 static String callback(final String body) {
105 return new JsCallback() {
107 protected CharSequence callMethod(
108 String ident, String fqn, String method, String params
110 StringBuilder sb = new StringBuilder();
111 sb.append("vm.").append(mangle(fqn, method, params));
122 static void loadScript(ClassLoader jcl, String resource) {
123 final InputStream script = jcl.getResourceAsStream(resource);
124 if (script == null) {
125 throw new NullPointerException("Can't find " + resource);
130 isr = new InputStreamReader(script, "UTF-8");
131 FnContext.currentPresenter().loadScript(isr);
137 } catch (Exception ex) {
138 throw new IllegalStateException("Can't execute " + resource, ex);
143 public Fn defineFn(String code, String... names) {
148 public void displayPage(URL page, Runnable onPageLoad) {
152 public void loadScript(Reader code) throws Exception {
155 private static final class FindInClass extends ClassVisitor {
158 private ClassLoader loader;
159 private String resource;
161 public FindInClass(ClassLoader l, ClassVisitor cv) {
162 super(Opcodes.ASM4, cv);
167 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
169 super.visit(version, access, name, signature, superName, interfaces);
173 public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
174 if ("Lnet/java/html/js/JavaScriptResource;".equals(desc)) {
175 return new LoadResource();
177 return super.visitAnnotation(desc, visible);
181 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
182 return new FindInMethod(access, name, desc,
183 super.visitMethod(access & (~Opcodes.ACC_NATIVE), name, desc, signature, exceptions)
187 private final class FindInMethod extends MethodVisitor {
189 private final String name;
190 private final String desc;
191 private final int access;
192 private List<String> args;
194 private boolean bodyGenerated;
196 public FindInMethod(int access, String name, String desc, MethodVisitor mv) {
197 super(Opcodes.ASM4, mv);
198 this.access = access;
204 public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
205 if ("Lnet/java/html/js/JavaScriptBody;".equals(desc) // NOI18N
206 || "Lorg/apidesign/bck2brwsr/core/JavaScriptBody;".equals(desc) // NOI18N
209 return new FindInAnno();
211 return super.visitAnnotation(desc, visible);
214 private void generateJSBody(List<String> args, String body) {
220 public void visitCode() {
227 private boolean generateBody() {
231 bodyGenerated = true;
233 super.visitFieldInsn(
234 Opcodes.GETSTATIC, FindInClass.this.name,
235 "$$fn$$" + name + "_" + found,
236 "Lorg/apidesign/html/boot/spi/Fn;"
238 super.visitInsn(Opcodes.DUP);
239 super.visitMethodInsn(
240 Opcodes.INVOKESTATIC,
241 "org/apidesign/html/boot/spi/Fn", "isValid",
242 "(Lorg/apidesign/html/boot/spi/Fn;)Z"
244 Label ifNotNull = new Label();
245 super.visitJumpInsn(Opcodes.IFNE, ifNotNull);
248 super.visitInsn(Opcodes.POP);
249 super.visitLdcInsn(Type.getObjectType(FindInClass.this.name));
250 super.visitLdcInsn(body);
251 super.visitIntInsn(Opcodes.SIPUSH, args.size());
252 super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String");
253 boolean needsVM = false;
254 for (int i = 0; i < args.size(); i++) {
256 String argName = args.get(i);
257 needsVM = "vm".equals(argName);
258 super.visitInsn(Opcodes.DUP);
259 super.visitIntInsn(Opcodes.BIPUSH, i);
260 super.visitLdcInsn(argName);
261 super.visitInsn(Opcodes.AASTORE);
263 super.visitMethodInsn(Opcodes.INVOKESTATIC,
264 "org/apidesign/html/boot/spi/Fn", "define",
265 "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/String;)Lorg/apidesign/html/boot/spi/Fn;"
267 if (resource != null) {
268 super.visitLdcInsn(Type.getObjectType(FindInClass.this.name));
269 super.visitLdcInsn(resource);
270 super.visitMethodInsn(Opcodes.INVOKESTATIC,
271 "org/apidesign/html/boot/spi/Fn", "preload",
272 "(Lorg/apidesign/html/boot/spi/Fn;Ljava/lang/Class;Ljava/lang/String;)Lorg/apidesign/html/boot/spi/Fn;"
275 super.visitInsn(Opcodes.DUP);
276 super.visitFieldInsn(
277 Opcodes.PUTSTATIC, FindInClass.this.name,
278 "$$fn$$" + name + "_" + found,
279 "Lorg/apidesign/html/boot/spi/Fn;"
283 super.visitLabel(ifNotNull);
286 if ((access & Opcodes.ACC_STATIC) == 0) {
288 super.visitIntInsn(Opcodes.ALOAD, 0);
291 super.visitInsn(Opcodes.ACONST_NULL);
294 super.visitIntInsn(Opcodes.SIPUSH, args.size());
295 super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
297 class SV extends SignatureVisitor {
299 private boolean nowReturn;
300 private Type returnType;
302 private int loadIndex = offset;
309 public void visitBaseType(char descriptor) {
310 final Type t = Type.getType("" + descriptor);
315 FindInMethod.super.visitInsn(Opcodes.DUP);
316 FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++);
317 FindInMethod.super.visitVarInsn(t.getOpcode(Opcodes.ILOAD), loadIndex++);
319 switch (descriptor) {
321 factory = "java/lang/Integer";
324 factory = "java/lang/Long";
328 factory = "java/lang/Short";
331 factory = "java/lang/Float";
334 factory = "java/lang/Double";
338 factory = "java/lang/Boolean";
341 factory = "java/lang/Character";
344 factory = "java/lang/Byte";
347 throw new IllegalStateException(t.toString());
349 FindInMethod.super.visitMethodInsn(Opcodes.INVOKESTATIC,
350 factory, "valueOf", "(" + descriptor + ")L" + factory + ";"
352 FindInMethod.super.visitInsn(Opcodes.AASTORE);
356 public SignatureVisitor visitArrayType() {
358 throw new IllegalStateException("Not supported yet");
361 return new SignatureWriter();
365 public void visitClassType(String name) {
367 returnType = Type.getObjectType(name);
374 public SignatureVisitor visitReturnType() {
379 private void loadObject() {
380 FindInMethod.super.visitInsn(Opcodes.DUP);
381 FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++);
382 FindInMethod.super.visitVarInsn(Opcodes.ALOAD, loadIndex++);
383 FindInMethod.super.visitInsn(Opcodes.AASTORE);
388 SignatureReader sr = new SignatureReader(desc);
392 FindInMethod.super.visitInsn(Opcodes.DUP);
393 FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, sv.index);
394 int lastSlash = FindInClass.this.name.lastIndexOf('/');
395 String jsCallbacks = FindInClass.this.name.substring(0, lastSlash + 1) + "$JsCallbacks$";
396 FindInMethod.super.visitFieldInsn(Opcodes.GETSTATIC, jsCallbacks, "VM", "L" + jsCallbacks + ";");
397 FindInMethod.super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, jsCallbacks, "current", "()L" + jsCallbacks + ";");
398 FindInMethod.super.visitInsn(Opcodes.AASTORE);
401 super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
402 "org/apidesign/html/boot/spi/Fn", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"
404 switch (sv.returnType.getSort()) {
406 super.visitInsn(Opcodes.RETURN);
410 super.visitTypeInsn(Opcodes.CHECKCAST, sv.returnType.getInternalName());
411 super.visitInsn(Opcodes.ARETURN);
414 super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Boolean");
415 super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
416 "java/lang/Boolean", "booleanValue", "()Z"
418 super.visitInsn(Opcodes.IRETURN);
421 super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Number");
422 super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
423 "java/lang/Number", sv.returnType.getClassName() + "Value", "()" + sv.returnType.getDescriptor()
425 super.visitInsn(sv.returnType.getOpcode(Opcodes.IRETURN));
431 public void visitEnd() {
434 if (generateBody()) {
436 super.visitMaxs(1, 0);
438 FindInClass.this.visitField(
439 Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC,
440 "$$fn$$" + name + "_" + found,
441 "Lorg/apidesign/html/boot/spi/Fn;",
447 private final class FindInAnno extends AnnotationVisitor {
449 private List<String> args = new ArrayList<String>();
451 private boolean javacall = false;
453 public FindInAnno() {
458 public void visit(String name, Object value) {
460 args.add((String) value);
463 if (name.equals("javacall")) { // NOI18N
464 javacall = (Boolean) value;
467 assert name.equals("body");
468 body = (String) value;
472 public AnnotationVisitor visitArray(String name) {
477 public void visitEnd() {
480 body = callback(body);
483 generateJSBody(args, body);
489 private final class LoadResource extends AnnotationVisitor {
491 public LoadResource() {
496 public void visit(String attrName, Object value) {
497 String relPath = (String) value;
498 if (relPath.startsWith("/")) {
501 int last = name.lastIndexOf('/');
502 String fullPath = name.substring(0, last + 1) + relPath;
509 private static class ClassWriterEx extends ClassWriter {
511 private ClassLoader loader;
513 public ClassWriterEx(ClassLoader l, ClassReader classReader, int flags) {
514 super(classReader, flags);
519 protected String getCommonSuperClass(final String type1, final String type2) {
522 c = Class.forName(type1.replace('/', '.'), false, loader);
523 d = Class.forName(type2.replace('/', '.'), false, loader);
524 } catch (Exception e) {
525 throw new RuntimeException(e.toString());
527 if (c.isAssignableFrom(d)) {
530 if (d.isAssignableFrom(c)) {
533 if (c.isInterface() || d.isInterface()) {
534 return "java/lang/Object";
537 c = c.getSuperclass();
538 } while (!c.isAssignableFrom(d));
539 return c.getName().replace('.', '/');
544 static byte[] transform(ClassLoader loader, byte[] arr) {
545 ClassReader cr = new ClassReader(arr) {
546 // to allow us to compile with -profile compact1 on
547 // JDK8 while processing the class as JDK7, the highest
548 // class format asm 4.1 understands to
550 public short readShort(int index) {
551 short s = super.readShort(index);
552 if (index == 6 && s > Opcodes.V1_7) {
558 FindInClass tst = new FindInClass(loader, null);
561 ClassWriter w = new ClassWriterEx(loader, cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
562 FindInClass fic = new FindInClass(loader, w);
564 arr = w.toByteArray();
569 private static final class TrueFn extends Fn {
571 public Object invoke(Object thiz, Object... args) throws Exception {