Introducing Agent-Class to allow java.lang.instrument-like transforms of the @JavaScriptBody annotation
1.1 --- a/boot/pom.xml Tue Nov 05 23:06:32 2013 +0100
1.2 +++ b/boot/pom.xml Wed Nov 06 15:15:54 2013 +0100
1.3 @@ -21,6 +21,11 @@
1.4 <plugin>
1.5 <groupId>org.apache.felix</groupId>
1.6 <artifactId>maven-bundle-plugin</artifactId>
1.7 + <configuration>
1.8 + <instructions>
1.9 + <Agent-Class>org.apidesign.html.boot.impl.JsAgent</Agent-Class>
1.10 + </instructions>
1.11 + </configuration>
1.12 </plugin>
1.13 <plugin>
1.14 <groupId>org.apache.maven.plugins</groupId>
2.1 --- a/boot/src/main/java/org/apidesign/html/boot/impl/FnContext.java Tue Nov 05 23:06:32 2013 +0100
2.2 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/FnContext.java Wed Nov 06 15:15:54 2013 +0100
2.3 @@ -32,16 +32,16 @@
2.4 public final class FnContext implements Closeable {
2.5 private static final Logger LOG = Logger.getLogger(FnContext.class.getName());
2.6
2.7 - private Fn.Presenter prev;
2.8 + private Object prev;
2.9 private FnContext(Fn.Presenter p) {
2.10 this.prev = p;
2.11 }
2.12
2.13 @Override
2.14 public void close() throws IOException {
2.15 - if (prev != null) {
2.16 - currentPresenter(prev);
2.17 - prev = null;
2.18 + if (prev != this) {
2.19 + currentPresenter((Fn.Presenter)prev);
2.20 + prev = this;
2.21 }
2.22 }
2.23 /*
3.1 --- a/boot/src/main/java/org/apidesign/html/boot/impl/FnUtils.java Tue Nov 05 23:06:32 2013 +0100
3.2 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/FnUtils.java Wed Nov 06 15:15:54 2013 +0100
3.3 @@ -20,6 +20,7 @@
3.4 */
3.5 package org.apidesign.html.boot.impl;
3.6
3.7 +import java.io.Closeable;
3.8 import java.io.InputStream;
3.9 import java.io.InputStreamReader;
3.10 import java.io.Reader;
3.11 @@ -28,23 +29,41 @@
3.12 import java.util.Collections;
3.13 import java.util.Enumeration;
3.14 import java.util.List;
3.15 +import java.util.concurrent.Callable;
3.16 import org.apidesign.html.boot.spi.Fn;
3.17 +import org.objectweb.asm.AnnotationVisitor;
3.18 +import org.objectweb.asm.ClassReader;
3.19 +import org.objectweb.asm.ClassVisitor;
3.20 +import org.objectweb.asm.ClassWriter;
3.21 +import org.objectweb.asm.Label;
3.22 +import org.objectweb.asm.MethodVisitor;
3.23 +import org.objectweb.asm.Opcodes;
3.24 +import org.objectweb.asm.Type;
3.25 +import org.objectweb.asm.signature.SignatureReader;
3.26 +import org.objectweb.asm.signature.SignatureVisitor;
3.27 +import org.objectweb.asm.signature.SignatureWriter;
3.28
3.29 /**
3.30 *
3.31 * @author Jaroslav Tulach <jtulach@netbeans.org>
3.32 */
3.33 -public final class FnUtils {
3.34 +public final class FnUtils implements Fn.Presenter {
3.35
3.36 private FnUtils() {
3.37 }
3.38
3.39 - public static Fn define(Class<?> caller, String code, String... names) {
3.40 - return FnContext.currentPresenter().defineFn(code, names);
3.41 - }
3.42 -
3.43 public static boolean isJavaScriptCapable(ClassLoader l) {
3.44 - return l instanceof JsClassLoader;
3.45 + if (l instanceof JsClassLoader) {
3.46 + return true;
3.47 + }
3.48 + Class<?> clazz;
3.49 + try (Closeable c = Fn.activate(new FnUtils())) {
3.50 + clazz = Class.forName(Test.class.getName(), true, l);
3.51 + final Object is = ((Callable<?>)clazz.newInstance()).call();
3.52 + return Boolean.TRUE.equals(is);
3.53 + } catch (Exception ex) {
3.54 + return false;
3.55 + }
3.56 }
3.57
3.58 public static boolean isValid(Fn fn) {
3.59 @@ -100,7 +119,7 @@
3.60 }.parse(body);
3.61 }
3.62
3.63 - static void loadScript(JsClassLoader jcl, String resource) {
3.64 + static void loadScript(ClassLoader jcl, String resource) {
3.65 final InputStream script = jcl.getResourceAsStream(resource);
3.66 if (script == null) {
3.67 throw new NullPointerException("Can't find " + resource);
3.68 @@ -109,7 +128,7 @@
3.69 Reader isr = null;
3.70 try {
3.71 isr = new InputStreamReader(script, "UTF-8");
3.72 - jcl.loadScript(isr);
3.73 + FnContext.currentPresenter().loadScript(isr);
3.74 } finally {
3.75 if (isr != null) {
3.76 isr.close();
3.77 @@ -119,4 +138,429 @@
3.78 throw new IllegalStateException("Can't execute " + resource, ex);
3.79 }
3.80 }
3.81 +
3.82 + @Override
3.83 + public Fn defineFn(String code, String... names) {
3.84 + return new TrueFn();
3.85 + }
3.86 +
3.87 + @Override
3.88 + public void displayPage(URL page, Runnable onPageLoad) {
3.89 + }
3.90 +
3.91 + @Override
3.92 + public void loadScript(Reader code) throws Exception {
3.93 + }
3.94 +
3.95 + private static final class FindInClass extends ClassVisitor {
3.96 + private String name;
3.97 + private int found;
3.98 + private ClassLoader loader;
3.99 +
3.100 + public FindInClass(ClassLoader l, ClassVisitor cv) {
3.101 + super(Opcodes.ASM4, cv);
3.102 + this.loader = l;
3.103 + }
3.104 +
3.105 + @Override
3.106 + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
3.107 + this.name = name;
3.108 + super.visit(version, access, name, signature, superName, interfaces);
3.109 + }
3.110 +
3.111 + @Override
3.112 + public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
3.113 + if ("Lnet/java/html/js/JavaScriptResource;".equals(desc)) {
3.114 + return new LoadResource();
3.115 + }
3.116 + return super.visitAnnotation(desc, visible);
3.117 + }
3.118 +
3.119 + @Override
3.120 + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
3.121 + return new FindInMethod(access, name, desc,
3.122 + super.visitMethod(access & (~Opcodes.ACC_NATIVE), name, desc, signature, exceptions)
3.123 + );
3.124 + }
3.125 +
3.126 + private final class FindInMethod extends MethodVisitor {
3.127 +
3.128 + private final String name;
3.129 + private final String desc;
3.130 + private final int access;
3.131 + private List<String> args;
3.132 + private String body;
3.133 + private boolean bodyGenerated;
3.134 +
3.135 + public FindInMethod(int access, String name, String desc, MethodVisitor mv) {
3.136 + super(Opcodes.ASM4, mv);
3.137 + this.access = access;
3.138 + this.name = name;
3.139 + this.desc = desc;
3.140 + }
3.141 +
3.142 + @Override
3.143 + public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
3.144 + if ("Lnet/java/html/js/JavaScriptBody;".equals(desc) // NOI18N
3.145 + || "Lorg/apidesign/bck2brwsr/core/JavaScriptBody;".equals(desc) // NOI18N
3.146 + ) {
3.147 + found++;
3.148 + return new FindInAnno();
3.149 + }
3.150 + return super.visitAnnotation(desc, visible);
3.151 + }
3.152 +
3.153 + private void generateJSBody(List<String> args, String body) {
3.154 + this.args = args;
3.155 + this.body = body;
3.156 + }
3.157 +
3.158 + @Override
3.159 + public void visitCode() {
3.160 + if (body == null) {
3.161 + return;
3.162 + }
3.163 + generateBody();
3.164 + }
3.165 +
3.166 + private boolean generateBody() {
3.167 + if (bodyGenerated) {
3.168 + return false;
3.169 + }
3.170 + bodyGenerated = true;
3.171 +
3.172 + super.visitFieldInsn(
3.173 + Opcodes.GETSTATIC, FindInClass.this.name,
3.174 + "$$fn$$" + name + "_" + found,
3.175 + "Lorg/apidesign/html/boot/spi/Fn;"
3.176 + );
3.177 + super.visitInsn(Opcodes.DUP);
3.178 + super.visitMethodInsn(
3.179 + Opcodes.INVOKESTATIC,
3.180 + "org/apidesign/html/boot/spi/Fn", "isValid",
3.181 + "(Lorg/apidesign/html/boot/spi/Fn;)Z"
3.182 + );
3.183 + Label ifNotNull = new Label();
3.184 + super.visitJumpInsn(Opcodes.IFNE, ifNotNull);
3.185 +
3.186 + // init Fn
3.187 + super.visitInsn(Opcodes.POP);
3.188 + super.visitLdcInsn(Type.getObjectType(FindInClass.this.name));
3.189 + super.visitLdcInsn(body);
3.190 + super.visitIntInsn(Opcodes.SIPUSH, args.size());
3.191 + super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String");
3.192 + boolean needsVM = false;
3.193 + for (int i = 0; i < args.size(); i++) {
3.194 + assert !needsVM;
3.195 + String argName = args.get(i);
3.196 + needsVM = "vm".equals(argName);
3.197 + super.visitInsn(Opcodes.DUP);
3.198 + super.visitIntInsn(Opcodes.BIPUSH, i);
3.199 + super.visitLdcInsn(argName);
3.200 + super.visitInsn(Opcodes.AASTORE);
3.201 + }
3.202 + super.visitMethodInsn(Opcodes.INVOKESTATIC,
3.203 + "org/apidesign/html/boot/spi/Fn", "define",
3.204 + "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/String;)Lorg/apidesign/html/boot/spi/Fn;"
3.205 + );
3.206 + super.visitInsn(Opcodes.DUP);
3.207 + super.visitFieldInsn(
3.208 + Opcodes.PUTSTATIC, FindInClass.this.name,
3.209 + "$$fn$$" + name + "_" + found,
3.210 + "Lorg/apidesign/html/boot/spi/Fn;"
3.211 + );
3.212 + // end of Fn init
3.213 +
3.214 + super.visitLabel(ifNotNull);
3.215 +
3.216 + final int offset;
3.217 + if ((access & Opcodes.ACC_STATIC) == 0) {
3.218 + offset = 1;
3.219 + super.visitIntInsn(Opcodes.ALOAD, 0);
3.220 + } else {
3.221 + offset = 0;
3.222 + super.visitInsn(Opcodes.ACONST_NULL);
3.223 + }
3.224 +
3.225 + super.visitIntInsn(Opcodes.SIPUSH, args.size());
3.226 + super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
3.227 +
3.228 + class SV extends SignatureVisitor {
3.229 +
3.230 + private boolean nowReturn;
3.231 + private Type returnType;
3.232 + private int index;
3.233 + private int loadIndex = offset;
3.234 +
3.235 + public SV() {
3.236 + super(Opcodes.ASM4);
3.237 + }
3.238 +
3.239 + @Override
3.240 + public void visitBaseType(char descriptor) {
3.241 + final Type t = Type.getType("" + descriptor);
3.242 + if (nowReturn) {
3.243 + returnType = t;
3.244 + return;
3.245 + }
3.246 + FindInMethod.super.visitInsn(Opcodes.DUP);
3.247 + FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++);
3.248 + FindInMethod.super.visitVarInsn(t.getOpcode(Opcodes.ILOAD), loadIndex++);
3.249 + String factory;
3.250 + switch (descriptor) {
3.251 + case 'I':
3.252 + factory = "java/lang/Integer";
3.253 + break;
3.254 + case 'J':
3.255 + factory = "java/lang/Long";
3.256 + loadIndex++;
3.257 + break;
3.258 + case 'S':
3.259 + factory = "java/lang/Short";
3.260 + break;
3.261 + case 'F':
3.262 + factory = "java/lang/Float";
3.263 + break;
3.264 + case 'D':
3.265 + factory = "java/lang/Double";
3.266 + loadIndex++;
3.267 + break;
3.268 + case 'Z':
3.269 + factory = "java/lang/Boolean";
3.270 + break;
3.271 + case 'C':
3.272 + factory = "java/lang/Character";
3.273 + break;
3.274 + case 'B':
3.275 + factory = "java/lang/Byte";
3.276 + break;
3.277 + default:
3.278 + throw new IllegalStateException(t.toString());
3.279 + }
3.280 + FindInMethod.super.visitMethodInsn(Opcodes.INVOKESTATIC,
3.281 + factory, "valueOf", "(" + descriptor + ")L" + factory + ";"
3.282 + );
3.283 + FindInMethod.super.visitInsn(Opcodes.AASTORE);
3.284 + }
3.285 +
3.286 + @Override
3.287 + public SignatureVisitor visitArrayType() {
3.288 + if (nowReturn) {
3.289 + throw new IllegalStateException("Not supported yet");
3.290 + }
3.291 + loadObject();
3.292 + return new SignatureWriter();
3.293 + }
3.294 +
3.295 + @Override
3.296 + public void visitClassType(String name) {
3.297 + if (nowReturn) {
3.298 + returnType = Type.getObjectType(name);
3.299 + return;
3.300 + }
3.301 + loadObject();
3.302 + }
3.303 +
3.304 + @Override
3.305 + public SignatureVisitor visitReturnType() {
3.306 + nowReturn = true;
3.307 + return this;
3.308 + }
3.309 +
3.310 + private void loadObject() {
3.311 + FindInMethod.super.visitInsn(Opcodes.DUP);
3.312 + FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++);
3.313 + FindInMethod.super.visitVarInsn(Opcodes.ALOAD, loadIndex++);
3.314 + FindInMethod.super.visitInsn(Opcodes.AASTORE);
3.315 + }
3.316 +
3.317 + }
3.318 + SV sv = new SV();
3.319 + SignatureReader sr = new SignatureReader(desc);
3.320 + sr.accept(sv);
3.321 +
3.322 + if (needsVM) {
3.323 + FindInMethod.super.visitInsn(Opcodes.DUP);
3.324 + FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, sv.index);
3.325 + int lastSlash = FindInClass.this.name.lastIndexOf('/');
3.326 + String jsCallbacks = FindInClass.this.name.substring(0, lastSlash + 1) + "$JsCallbacks$";
3.327 + FindInMethod.super.visitFieldInsn(Opcodes.GETSTATIC, jsCallbacks, "VM", "L" + jsCallbacks + ";");
3.328 + FindInMethod.super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, jsCallbacks, "current", "()L" + jsCallbacks + ";");
3.329 + FindInMethod.super.visitInsn(Opcodes.AASTORE);
3.330 + }
3.331 +
3.332 + super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
3.333 + "org/apidesign/html/boot/spi/Fn", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"
3.334 + );
3.335 + switch (sv.returnType.getSort()) {
3.336 + case Type.VOID:
3.337 + super.visitInsn(Opcodes.RETURN);
3.338 + break;
3.339 + case Type.ARRAY:
3.340 + case Type.OBJECT:
3.341 + super.visitTypeInsn(Opcodes.CHECKCAST, sv.returnType.getInternalName());
3.342 + super.visitInsn(Opcodes.ARETURN);
3.343 + break;
3.344 + case Type.BOOLEAN:
3.345 + super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Boolean");
3.346 + super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
3.347 + "java/lang/Boolean", "booleanValue", "()Z"
3.348 + );
3.349 + super.visitInsn(Opcodes.IRETURN);
3.350 + break;
3.351 + default:
3.352 + super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Number");
3.353 + super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
3.354 + "java/lang/Number", sv.returnType.getClassName() + "Value", "()" + sv.returnType.getDescriptor()
3.355 + );
3.356 + super.visitInsn(sv.returnType.getOpcode(Opcodes.IRETURN));
3.357 + }
3.358 + return true;
3.359 + }
3.360 +
3.361 + @Override
3.362 + public void visitEnd() {
3.363 + super.visitEnd();
3.364 + if (body != null) {
3.365 + if (generateBody()) {
3.366 + // native method
3.367 + super.visitMaxs(1, 0);
3.368 + }
3.369 + FindInClass.this.visitField(
3.370 + Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC,
3.371 + "$$fn$$" + name + "_" + found,
3.372 + "Lorg/apidesign/html/boot/spi/Fn;",
3.373 + null, null
3.374 + );
3.375 + }
3.376 + }
3.377 +
3.378 + private final class FindInAnno extends AnnotationVisitor {
3.379 +
3.380 + private List<String> args = new ArrayList<String>();
3.381 + private String body;
3.382 + private boolean javacall = false;
3.383 +
3.384 + public FindInAnno() {
3.385 + super(Opcodes.ASM4);
3.386 + }
3.387 +
3.388 + @Override
3.389 + public void visit(String name, Object value) {
3.390 + if (name == null) {
3.391 + args.add((String) value);
3.392 + return;
3.393 + }
3.394 + if (name.equals("javacall")) { // NOI18N
3.395 + javacall = (Boolean) value;
3.396 + return;
3.397 + }
3.398 + assert name.equals("body");
3.399 + body = (String) value;
3.400 + }
3.401 +
3.402 + @Override
3.403 + public AnnotationVisitor visitArray(String name) {
3.404 + return this;
3.405 + }
3.406 +
3.407 + @Override
3.408 + public void visitEnd() {
3.409 + if (body != null) {
3.410 + if (javacall) {
3.411 + body = callback(body);
3.412 + args.add("vm");
3.413 + }
3.414 + generateJSBody(args, body);
3.415 + }
3.416 + }
3.417 + }
3.418 + }
3.419 +
3.420 + private final class LoadResource extends AnnotationVisitor {
3.421 +
3.422 + public LoadResource() {
3.423 + super(Opcodes.ASM4);
3.424 + }
3.425 +
3.426 + @Override
3.427 + public void visit(String attrName, Object value) {
3.428 + String relPath = (String) value;
3.429 + if (relPath.startsWith("/")) {
3.430 + loadScript(loader, relPath);
3.431 + } else {
3.432 + int last = name.lastIndexOf('/');
3.433 + String fullPath = name.substring(0, last + 1) + relPath;
3.434 + loadScript(loader, fullPath);
3.435 + }
3.436 + }
3.437 + }
3.438 + }
3.439 +
3.440 + private static class ClassWriterEx extends ClassWriter {
3.441 +
3.442 + private ClassLoader loader;
3.443 +
3.444 + public ClassWriterEx(ClassLoader l, ClassReader classReader, int flags) {
3.445 + super(classReader, flags);
3.446 + this.loader = l;
3.447 + }
3.448 +
3.449 + @Override
3.450 + protected String getCommonSuperClass(final String type1, final String type2) {
3.451 + Class<?> c, d;
3.452 + try {
3.453 + c = Class.forName(type1.replace('/', '.'), false, loader);
3.454 + d = Class.forName(type2.replace('/', '.'), false, loader);
3.455 + } catch (Exception e) {
3.456 + throw new RuntimeException(e.toString());
3.457 + }
3.458 + if (c.isAssignableFrom(d)) {
3.459 + return type1;
3.460 + }
3.461 + if (d.isAssignableFrom(c)) {
3.462 + return type2;
3.463 + }
3.464 + if (c.isInterface() || d.isInterface()) {
3.465 + return "java/lang/Object";
3.466 + } else {
3.467 + do {
3.468 + c = c.getSuperclass();
3.469 + } while (!c.isAssignableFrom(d));
3.470 + return c.getName().replace('.', '/');
3.471 + }
3.472 + }
3.473 + }
3.474 +
3.475 + static byte[] transform(ClassLoader loader, byte[] arr) {
3.476 + ClassReader cr = new ClassReader(arr) {
3.477 + // to allow us to compile with -profile compact1 on
3.478 + // JDK8 while processing the class as JDK7, the highest
3.479 + // class format asm 4.1 understands to
3.480 + @Override
3.481 + public short readShort(int index) {
3.482 + short s = super.readShort(index);
3.483 + if (index == 6 && s > Opcodes.V1_7) {
3.484 + return Opcodes.V1_7;
3.485 + }
3.486 + return s;
3.487 + }
3.488 + };
3.489 + FindInClass tst = new FindInClass(loader, null);
3.490 + cr.accept(tst, 0);
3.491 + if (tst.found > 0) {
3.492 + ClassWriter w = new ClassWriterEx(loader, cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
3.493 + FindInClass fic = new FindInClass(loader, w);
3.494 + cr.accept(fic, 0);
3.495 + arr = w.toByteArray();
3.496 + }
3.497 + return arr;
3.498 + }
3.499 +
3.500 + private static final class TrueFn extends Fn {
3.501 + @Override
3.502 + public Object invoke(Object thiz, Object... args) throws Exception {
3.503 + return Boolean.TRUE;
3.504 + }
3.505 + } // end of TrueFn
3.506 }
4.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
4.2 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/JsAgent.java Wed Nov 06 15:15:54 2013 +0100
4.3 @@ -0,0 +1,45 @@
4.4 +/**
4.5 + * HTML via Java(tm) Language Bindings
4.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
4.7 + *
4.8 + * This program is free software: you can redistribute it and/or modify
4.9 + * it under the terms of the GNU General Public License as published by
4.10 + * the Free Software Foundation, version 2 of the License.
4.11 + *
4.12 + * This program is distributed in the hope that it will be useful,
4.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
4.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
4.15 + * GNU General Public License for more details. apidesign.org
4.16 + * designates this particular file as subject to the
4.17 + * "Classpath" exception as provided by apidesign.org
4.18 + * in the License file that accompanied this code.
4.19 + *
4.20 + * You should have received a copy of the GNU General Public License
4.21 + * along with this program. Look for COPYING file in the top folder.
4.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
4.23 + */
4.24 +package org.apidesign.html.boot.impl;
4.25 +
4.26 +import java.lang.instrument.ClassFileTransformer;
4.27 +import java.lang.instrument.IllegalClassFormatException;
4.28 +import java.lang.instrument.Instrumentation;
4.29 +import java.security.ProtectionDomain;
4.30 +
4.31 +/**
4.32 + *
4.33 + * @author Jaroslav Tulach <jtulach@netbeans.org>
4.34 + */
4.35 +public final class JsAgent implements ClassFileTransformer {
4.36 + public static void agentmain(String args, Instrumentation instr) {
4.37 + instr.addTransformer(new JsAgent());
4.38 + }
4.39 +
4.40 + @Override
4.41 + public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
4.42 + try {
4.43 + return FnUtils.transform(loader, classfileBuffer);
4.44 + } catch (Exception ex) {
4.45 + return classfileBuffer;
4.46 + }
4.47 + }
4.48 +}
5.1 --- a/boot/src/main/java/org/apidesign/html/boot/impl/JsClassLoader.java Tue Nov 05 23:06:32 2013 +0100
5.2 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/JsClassLoader.java Wed Nov 06 15:15:54 2013 +0100
5.3 @@ -25,20 +25,7 @@
5.4 import java.io.InputStream;
5.5 import java.io.Reader;
5.6 import java.net.URL;
5.7 -import java.util.ArrayList;
5.8 import java.util.Enumeration;
5.9 -import java.util.List;
5.10 -import org.objectweb.asm.AnnotationVisitor;
5.11 -import org.objectweb.asm.ClassReader;
5.12 -import org.objectweb.asm.ClassVisitor;
5.13 -import org.objectweb.asm.ClassWriter;
5.14 -import org.objectweb.asm.Label;
5.15 -import org.objectweb.asm.MethodVisitor;
5.16 -import org.objectweb.asm.Opcodes;
5.17 -import org.objectweb.asm.Type;
5.18 -import org.objectweb.asm.signature.SignatureReader;
5.19 -import org.objectweb.asm.signature.SignatureVisitor;
5.20 -import org.objectweb.asm.signature.SignatureWriter;
5.21
5.22 /**
5.23 *
5.24 @@ -102,27 +89,7 @@
5.25 }
5.26 is.close();
5.27 is = null;
5.28 - ClassReader cr = new ClassReader(arr) {
5.29 - // to allow us to compile with -profile compact1 on
5.30 - // JDK8 while processing the class as JDK7, the highest
5.31 - // class format asm 4.1 understands to
5.32 - @Override
5.33 - public short readShort(int index) {
5.34 - short s = super.readShort(index);
5.35 - if (index == 6 && s > Opcodes.V1_7) {
5.36 - return Opcodes.V1_7;
5.37 - }
5.38 - return s;
5.39 - }
5.40 - };
5.41 - FindInClass tst = new FindInClass(null);
5.42 - cr.accept(tst, 0);
5.43 - if (tst.found > 0) {
5.44 - ClassWriter w = new ClassWriterEx(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
5.45 - FindInClass fic = new FindInClass(w);
5.46 - cr.accept(fic, 0);
5.47 - arr = w.toByteArray();
5.48 - }
5.49 + arr = FnUtils.transform(JsClassLoader.this, arr);
5.50 if (arr != null) {
5.51 return defineClass(name, arr, 0, arr.length);
5.52 }
5.53 @@ -141,363 +108,4 @@
5.54
5.55 protected abstract Fn defineFn(String code, String... names);
5.56 protected abstract void loadScript(Reader code) throws Exception;
5.57 -
5.58 - private final class FindInClass extends ClassVisitor {
5.59 - private String name;
5.60 - private int found;
5.61 -
5.62 - public FindInClass(ClassVisitor cv) {
5.63 - super(Opcodes.ASM4, cv);
5.64 - }
5.65 -
5.66 - @Override
5.67 - public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
5.68 - this.name = name;
5.69 - super.visit(version, access, name, signature, superName, interfaces);
5.70 - }
5.71 -
5.72 - @Override
5.73 - public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
5.74 - if ("Lnet/java/html/js/JavaScriptResource;".equals(desc)) {
5.75 - return new LoadResource();
5.76 - }
5.77 - return super.visitAnnotation(desc, visible);
5.78 - }
5.79 -
5.80 -
5.81 - @Override
5.82 - public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
5.83 - return new FindInMethod(access, name, desc,
5.84 - super.visitMethod(access & (~Opcodes.ACC_NATIVE), name, desc, signature, exceptions)
5.85 - );
5.86 - }
5.87 -
5.88 - private final class FindInMethod extends MethodVisitor {
5.89 - private final String name;
5.90 - private final String desc;
5.91 - private final int access;
5.92 - private List<String> args;
5.93 - private String body;
5.94 - private boolean bodyGenerated;
5.95 -
5.96 - public FindInMethod(int access, String name, String desc, MethodVisitor mv) {
5.97 - super(Opcodes.ASM4, mv);
5.98 - this.access = access;
5.99 - this.name = name;
5.100 - this.desc = desc;
5.101 - }
5.102 -
5.103 - @Override
5.104 - public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
5.105 - if (
5.106 - "Lnet/java/html/js/JavaScriptBody;".equals(desc) // NOI18N
5.107 - || "Lorg/apidesign/bck2brwsr/core/JavaScriptBody;".equals(desc) // NOI18N
5.108 - ) {
5.109 - found++;
5.110 - return new FindInAnno();
5.111 - }
5.112 - return super.visitAnnotation(desc, visible);
5.113 - }
5.114 -
5.115 - private void generateJSBody(List<String> args, String body) {
5.116 - this.args = args;
5.117 - this.body = body;
5.118 - }
5.119 -
5.120 - @Override
5.121 - public void visitCode() {
5.122 - if (body == null) {
5.123 - return;
5.124 - }
5.125 - generateBody();
5.126 - }
5.127 -
5.128 - private boolean generateBody() {
5.129 - if (bodyGenerated) {
5.130 - return false;
5.131 - }
5.132 - bodyGenerated = true;
5.133 -
5.134 - super.visitFieldInsn(
5.135 - Opcodes.GETSTATIC, FindInClass.this.name,
5.136 - "$$fn$$" + name + "_" + found,
5.137 - "Lorg/apidesign/html/boot/spi/Fn;"
5.138 - );
5.139 - super.visitInsn(Opcodes.DUP);
5.140 - super.visitMethodInsn(
5.141 - Opcodes.INVOKESTATIC,
5.142 - "org/apidesign/html/boot/impl/FnUtils", "isValid",
5.143 - "(Lorg/apidesign/html/boot/spi/Fn;)Z"
5.144 - );
5.145 - Label ifNotNull = new Label();
5.146 - super.visitJumpInsn(Opcodes.IFNE, ifNotNull);
5.147 -
5.148 - // init Fn
5.149 - super.visitInsn(Opcodes.POP);
5.150 - super.visitLdcInsn(Type.getObjectType(FindInClass.this.name));
5.151 - super.visitLdcInsn(body);
5.152 - super.visitIntInsn(Opcodes.SIPUSH, args.size());
5.153 - super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String");
5.154 - boolean needsVM = false;
5.155 - for (int i = 0; i < args.size(); i++) {
5.156 - assert !needsVM;
5.157 - String argName = args.get(i);
5.158 - needsVM = "vm".equals(argName);
5.159 - super.visitInsn(Opcodes.DUP);
5.160 - super.visitIntInsn(Opcodes.BIPUSH, i);
5.161 - super.visitLdcInsn(argName);
5.162 - super.visitInsn(Opcodes.AASTORE);
5.163 - }
5.164 - super.visitMethodInsn(Opcodes.INVOKESTATIC,
5.165 - "org/apidesign/html/boot/impl/FnUtils", "define",
5.166 - "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/String;)Lorg/apidesign/html/boot/spi/Fn;"
5.167 - );
5.168 - super.visitInsn(Opcodes.DUP);
5.169 - super.visitFieldInsn(
5.170 - Opcodes.PUTSTATIC, FindInClass.this.name,
5.171 - "$$fn$$" + name + "_" + found,
5.172 - "Lorg/apidesign/html/boot/spi/Fn;"
5.173 - );
5.174 - // end of Fn init
5.175 -
5.176 - super.visitLabel(ifNotNull);
5.177 -
5.178 - final int offset;
5.179 - if ((access & Opcodes.ACC_STATIC) == 0) {
5.180 - offset = 1;
5.181 - super.visitIntInsn(Opcodes.ALOAD, 0);
5.182 - } else {
5.183 - offset = 0;
5.184 - super.visitInsn(Opcodes.ACONST_NULL);
5.185 - }
5.186 -
5.187 - super.visitIntInsn(Opcodes.SIPUSH, args.size());
5.188 - super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
5.189 -
5.190 - class SV extends SignatureVisitor {
5.191 - private boolean nowReturn;
5.192 - private Type returnType;
5.193 - private int index;
5.194 - private int loadIndex = offset;
5.195 -
5.196 - public SV() {
5.197 - super(Opcodes.ASM4);
5.198 - }
5.199 -
5.200 - @Override
5.201 - public void visitBaseType(char descriptor) {
5.202 - final Type t = Type.getType("" + descriptor);
5.203 - if (nowReturn) {
5.204 - returnType = t;
5.205 - return;
5.206 - }
5.207 - FindInMethod.super.visitInsn(Opcodes.DUP);
5.208 - FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++);
5.209 - FindInMethod.super.visitVarInsn(t.getOpcode(Opcodes.ILOAD), loadIndex++);
5.210 - String factory;
5.211 - switch (descriptor) {
5.212 - case 'I': factory = "java/lang/Integer"; break;
5.213 - case 'J': factory = "java/lang/Long"; loadIndex++; break;
5.214 - case 'S': factory = "java/lang/Short"; break;
5.215 - case 'F': factory = "java/lang/Float"; break;
5.216 - case 'D': factory = "java/lang/Double"; loadIndex++; break;
5.217 - case 'Z': factory = "java/lang/Boolean"; break;
5.218 - case 'C': factory = "java/lang/Character"; break;
5.219 - case 'B': factory = "java/lang/Byte"; break;
5.220 - default: throw new IllegalStateException(t.toString());
5.221 - }
5.222 - FindInMethod.super.visitMethodInsn(Opcodes.INVOKESTATIC,
5.223 - factory, "valueOf", "(" + descriptor + ")L" + factory + ";"
5.224 - );
5.225 - FindInMethod.super.visitInsn(Opcodes.AASTORE);
5.226 - }
5.227 -
5.228 - @Override
5.229 - public SignatureVisitor visitArrayType() {
5.230 - if (nowReturn) {
5.231 - throw new IllegalStateException("Not supported yet");
5.232 - }
5.233 - loadObject();
5.234 - return new SignatureWriter();
5.235 - }
5.236 -
5.237 - @Override
5.238 - public void visitClassType(String name) {
5.239 - if (nowReturn) {
5.240 - returnType = Type.getObjectType(name);
5.241 - return;
5.242 - }
5.243 - loadObject();
5.244 - }
5.245 -
5.246 - @Override
5.247 - public SignatureVisitor visitReturnType() {
5.248 - nowReturn = true;
5.249 - return this;
5.250 - }
5.251 -
5.252 - private void loadObject() {
5.253 - FindInMethod.super.visitInsn(Opcodes.DUP);
5.254 - FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++);
5.255 - FindInMethod.super.visitVarInsn(Opcodes.ALOAD, loadIndex++);
5.256 - FindInMethod.super.visitInsn(Opcodes.AASTORE);
5.257 - }
5.258 -
5.259 - }
5.260 - SV sv = new SV();
5.261 - SignatureReader sr = new SignatureReader(desc);
5.262 - sr.accept(sv);
5.263 -
5.264 - if (needsVM) {
5.265 - FindInMethod.super.visitInsn(Opcodes.DUP);
5.266 - FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, sv.index);
5.267 - int lastSlash = FindInClass.this.name.lastIndexOf('/');
5.268 - String jsCallbacks = FindInClass.this.name.substring(0, lastSlash + 1) + "$JsCallbacks$";
5.269 - FindInMethod.super.visitFieldInsn(Opcodes.GETSTATIC, jsCallbacks, "VM", "L" + jsCallbacks + ";");
5.270 - FindInMethod.super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, jsCallbacks, "current", "()L" + jsCallbacks + ";");
5.271 - FindInMethod.super.visitInsn(Opcodes.AASTORE);
5.272 - }
5.273 -
5.274 - super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
5.275 - "org/apidesign/html/boot/spi/Fn", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"
5.276 - );
5.277 - switch (sv.returnType.getSort()) {
5.278 - case Type.VOID:
5.279 - super.visitInsn(Opcodes.RETURN);
5.280 - break;
5.281 - case Type.ARRAY:
5.282 - case Type.OBJECT:
5.283 - super.visitTypeInsn(Opcodes.CHECKCAST, sv.returnType.getInternalName());
5.284 - super.visitInsn(Opcodes.ARETURN);
5.285 - break;
5.286 - case Type.BOOLEAN:
5.287 - super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Boolean");
5.288 - super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
5.289 - "java/lang/Boolean", "booleanValue", "()Z"
5.290 - );
5.291 - super.visitInsn(Opcodes.IRETURN);
5.292 - break;
5.293 - default:
5.294 - super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Number");
5.295 - super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
5.296 - "java/lang/Number", sv.returnType.getClassName() + "Value", "()" + sv.returnType.getDescriptor()
5.297 - );
5.298 - super.visitInsn(sv.returnType.getOpcode(Opcodes.IRETURN));
5.299 - }
5.300 - return true;
5.301 - }
5.302 -
5.303 - @Override
5.304 - public void visitEnd() {
5.305 - super.visitEnd();
5.306 - if (body != null) {
5.307 - if (generateBody()) {
5.308 - // native method
5.309 - super.visitMaxs(1, 0);
5.310 - }
5.311 - FindInClass.this.visitField(
5.312 - Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC,
5.313 - "$$fn$$" + name + "_" + found,
5.314 - "Lorg/apidesign/html/boot/spi/Fn;",
5.315 - null, null
5.316 - );
5.317 - }
5.318 - }
5.319 -
5.320 -
5.321 -
5.322 -
5.323 -
5.324 - private final class FindInAnno extends AnnotationVisitor {
5.325 - private List<String> args = new ArrayList<String>();
5.326 - private String body;
5.327 - private boolean javacall = false;
5.328 -
5.329 - public FindInAnno() {
5.330 - super(Opcodes.ASM4);
5.331 - }
5.332 -
5.333 - @Override
5.334 - public void visit(String name, Object value) {
5.335 - if (name == null) {
5.336 - args.add((String) value);
5.337 - return;
5.338 - }
5.339 - if (name.equals("javacall")) { // NOI18N
5.340 - javacall = (Boolean)value;
5.341 - return;
5.342 - }
5.343 - assert name.equals("body");
5.344 - body = (String) value;
5.345 - }
5.346 -
5.347 - @Override
5.348 - public AnnotationVisitor visitArray(String name) {
5.349 - return this;
5.350 - }
5.351 -
5.352 - @Override
5.353 - public void visitEnd() {
5.354 - if (body != null) {
5.355 - if (javacall) {
5.356 - body = FnUtils.callback(body);
5.357 - args.add("vm");
5.358 - }
5.359 - generateJSBody(args, body);
5.360 - }
5.361 - }
5.362 - }
5.363 - }
5.364 -
5.365 - private final class LoadResource extends AnnotationVisitor {
5.366 - public LoadResource() {
5.367 - super(Opcodes.ASM4);
5.368 - }
5.369 -
5.370 - @Override
5.371 - public void visit(String attrName, Object value) {
5.372 - String relPath = (String) value;
5.373 - if (relPath.startsWith("/")) {
5.374 - FnUtils.loadScript(JsClassLoader.this, relPath);
5.375 - } else {
5.376 - int last = name.lastIndexOf('/');
5.377 - String fullPath = name.substring(0, last + 1) + relPath;
5.378 - FnUtils.loadScript(JsClassLoader.this, fullPath);
5.379 - }
5.380 - }
5.381 - }
5.382 - }
5.383 -
5.384 - private class ClassWriterEx extends ClassWriter {
5.385 -
5.386 - public ClassWriterEx(ClassReader classReader, int flags) {
5.387 - super(classReader, flags);
5.388 - }
5.389 -
5.390 - @Override
5.391 - protected String getCommonSuperClass(final String type1, final String type2) {
5.392 - Class<?> c, d;
5.393 - ClassLoader classLoader = JsClassLoader.this;
5.394 - try {
5.395 - c = Class.forName(type1.replace('/', '.'), false, classLoader);
5.396 - d = Class.forName(type2.replace('/', '.'), false, classLoader);
5.397 - } catch (Exception e) {
5.398 - throw new RuntimeException(e.toString());
5.399 - }
5.400 - if (c.isAssignableFrom(d)) {
5.401 - return type1;
5.402 - }
5.403 - if (d.isAssignableFrom(c)) {
5.404 - return type2;
5.405 - }
5.406 - if (c.isInterface() || d.isInterface()) {
5.407 - return "java/lang/Object";
5.408 - } else {
5.409 - do {
5.410 - c = c.getSuperclass();
5.411 - } while (!c.isAssignableFrom(d));
5.412 - return c.getName().replace('.', '/');
5.413 - }
5.414 - }
5.415 - }
5.416 }
6.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
6.2 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/Test.java Wed Nov 06 15:15:54 2013 +0100
6.3 @@ -0,0 +1,35 @@
6.4 +/**
6.5 + * HTML via Java(tm) Language Bindings
6.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
6.7 + *
6.8 + * This program is free software: you can redistribute it and/or modify
6.9 + * it under the terms of the GNU General Public License as published by
6.10 + * the Free Software Foundation, version 2 of the License.
6.11 + *
6.12 + * This program is distributed in the hope that it will be useful,
6.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
6.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
6.15 + * GNU General Public License for more details. apidesign.org
6.16 + * designates this particular file as subject to the
6.17 + * "Classpath" exception as provided by apidesign.org
6.18 + * in the License file that accompanied this code.
6.19 + *
6.20 + * You should have received a copy of the GNU General Public License
6.21 + * along with this program. Look for COPYING file in the top folder.
6.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
6.23 + */
6.24 +package org.apidesign.html.boot.impl;
6.25 +
6.26 +import java.util.concurrent.Callable;
6.27 +import net.java.html.js.JavaScriptBody;
6.28 +
6.29 +/**
6.30 + *
6.31 + * @author Jaroslav Tulach <jtulach@netbeans.org>
6.32 + */
6.33 +public final class Test implements Callable<Boolean> {
6.34 + @Override @JavaScriptBody(args = {}, body = "return true;")
6.35 + public Boolean call() {
6.36 + return false;
6.37 + }
6.38 +}
7.1 --- a/boot/src/main/java/org/apidesign/html/boot/spi/Fn.java Tue Nov 05 23:06:32 2013 +0100
7.2 +++ b/boot/src/main/java/org/apidesign/html/boot/spi/Fn.java Wed Nov 06 15:15:54 2013 +0100
7.3 @@ -62,6 +62,31 @@
7.4 return FnContext.currentPresenter() == presenter;
7.5 }
7.6
7.7 + /** Helper method to check if the provided instance is valid function.
7.8 + * Checks if the parameter is non-null and if so, does {@link #isValid()}
7.9 + * check.
7.10 + *
7.11 + * @param fnOrNull function or <code>null</code>
7.12 + * @return true if the parameter is non-null and valid
7.13 + * @since 0.7
7.14 + */
7.15 + public static boolean isValid(Fn fnOrNull) {
7.16 + return fnOrNull != null && fnOrNull.isValid();
7.17 + }
7.18 +
7.19 + /** Helper method to find current presenter and ask it to define new
7.20 + * function by calling {@link Presenter#defineFn(java.lang.String, java.lang.String...)}.
7.21 + *
7.22 + * @param caller the class who wishes to define the function
7.23 + * @param code the body of the function (can reference <code>this</code> and <code>names</code> variables)
7.24 + * @param names names of individual parameters
7.25 + * @return the function object that can be {@link Fn#invoke(java.lang.Object, java.lang.Object...) invoked}
7.26 + * @since 0.7
7.27 + */
7.28 + public static Fn define(Class<?> caller, String code, String... names) {
7.29 + return FnContext.currentPresenter().defineFn(code, names);
7.30 + }
7.31 +
7.32 /** The currently active presenter.
7.33 *
7.34 * @return the currently active presenter or <code>null</code>
8.1 --- a/boot/src/test/java/org/apidesign/html/boot/impl/FnTest.java Tue Nov 05 23:06:32 2013 +0100
8.2 +++ b/boot/src/test/java/org/apidesign/html/boot/impl/FnTest.java Wed Nov 06 15:15:54 2013 +0100
8.3 @@ -21,6 +21,7 @@
8.4
8.5 package org.apidesign.html.boot.impl;
8.6
8.7 +import java.io.Closeable;
8.8 import java.io.Reader;
8.9 import java.net.URL;
8.10 import java.net.URLClassLoader;
8.11 @@ -115,7 +116,9 @@
8.12 ClassLoader loader = FnUtils.newLoader(impl, impl, parent);
8.13 presenter = impl;
8.14
8.15 + Closeable close = FnContext.activate(impl);
8.16 methodClass = loader.loadClass(JsMethods.class.getName());
8.17 + close.close();
8.18 }
8.19
8.20 @BeforeMethod public void initPresenter() {
9.1 --- a/boot/src/test/java/org/apidesign/html/boot/impl/JsClassLoaderTest.java Tue Nov 05 23:06:32 2013 +0100
9.2 +++ b/boot/src/test/java/org/apidesign/html/boot/impl/JsClassLoaderTest.java Wed Nov 06 15:15:54 2013 +0100
9.3 @@ -20,6 +20,7 @@
9.4 */
9.5 package org.apidesign.html.boot.impl;
9.6
9.7 +import java.io.Closeable;
9.8 import java.io.Reader;
9.9 import org.apidesign.html.boot.spi.Fn;
9.10 import java.net.URL;
9.11 @@ -115,7 +116,9 @@
9.12 };
9.13
9.14 MyCL l = new MyCL(parent);
9.15 + Closeable close = FnContext.activate(l);
9.16 methodClass = l.loadClass(JsMethods.class.getName());
9.17 + close.close();
9.18 loader = l;
9.19 }
9.20