1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/boot/src/main/java/org/apidesign/html/boot/impl/JsClassLoader.java Wed Jun 19 12:52:23 2013 +0200
1.3 @@ -0,0 +1,400 @@
1.4 +/**
1.5 + * HTML via Java(tm) Language Bindings
1.6 + * Copyright (C) 2013 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
1.7 + *
1.8 + * This program is free software: you can redistribute it and/or modify
1.9 + * it under the terms of the GNU General Public License as published by
1.10 + * the Free Software Foundation, version 2 of the License.
1.11 + *
1.12 + * This program is distributed in the hope that it will be useful,
1.13 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1.14 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1.15 + * GNU General Public License for more details. apidesign.org
1.16 + * designates this particular file as subject to the
1.17 + * "Classpath" exception as provided by apidesign.org
1.18 + * in the License file that accompanied this code.
1.19 + *
1.20 + * You should have received a copy of the GNU General Public License
1.21 + * along with this program. Look for COPYING file in the top folder.
1.22 + * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
1.23 + */
1.24 +package org.apidesign.html.boot.impl;
1.25 +
1.26 +import org.apidesign.html.boot.spi.Fn;
1.27 +import java.io.IOException;
1.28 +import java.io.InputStream;
1.29 +import java.net.URL;
1.30 +import java.util.ArrayList;
1.31 +import java.util.Enumeration;
1.32 +import java.util.List;
1.33 +import org.objectweb.asm.AnnotationVisitor;
1.34 +import org.objectweb.asm.ClassReader;
1.35 +import org.objectweb.asm.ClassVisitor;
1.36 +import org.objectweb.asm.ClassWriter;
1.37 +import org.objectweb.asm.Label;
1.38 +import org.objectweb.asm.MethodVisitor;
1.39 +import org.objectweb.asm.Opcodes;
1.40 +import org.objectweb.asm.Type;
1.41 +import org.objectweb.asm.signature.SignatureReader;
1.42 +import org.objectweb.asm.signature.SignatureVisitor;
1.43 +
1.44 +/**
1.45 + *
1.46 + * @author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
1.47 + */
1.48 +abstract class JsClassLoader extends ClassLoader {
1.49 + JsClassLoader(ClassLoader parent) {
1.50 + super(parent);
1.51 + }
1.52 +
1.53 + @Override
1.54 + protected abstract URL findResource(String name);
1.55 +
1.56 + @Override
1.57 + protected abstract Enumeration<URL> findResources(String name);
1.58 +
1.59 + @Override
1.60 + protected Class<?> findClass(String name) throws ClassNotFoundException {
1.61 + if (name.startsWith("javafx")) {
1.62 + return Class.forName(name);
1.63 + }
1.64 + if (name.startsWith("netscape")) {
1.65 + return Class.forName(name);
1.66 + }
1.67 + if (name.startsWith("com.sun")) {
1.68 + return Class.forName(name);
1.69 + }
1.70 + if (name.equals(JsClassLoader.class.getName())) {
1.71 + return JsClassLoader.class;
1.72 + }
1.73 + if (name.equals(Fn.class.getName())) {
1.74 + return Fn.class;
1.75 + }
1.76 + URL u = findResource(name.replace('.', '/') + ".class");
1.77 + if (u != null) {
1.78 + InputStream is = null;
1.79 + try {
1.80 + is = u.openStream();
1.81 + byte[] arr = new byte[is.available()];
1.82 + int len = 0;
1.83 + while (len < arr.length) {
1.84 + int read = is.read(arr, len, arr.length - len);
1.85 + if (read == -1) {
1.86 + throw new IOException("Can't read " + u);
1.87 + }
1.88 + len += read;
1.89 + }
1.90 + is.close();
1.91 + is = null;
1.92 + ClassReader cr = new ClassReader(arr);
1.93 + FindInClass tst = new FindInClass(null);
1.94 + cr.accept(tst, 0);
1.95 + if (tst.found > 0) {
1.96 + ClassWriter w = new ClassWriterEx(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
1.97 + FindInClass fic = new FindInClass(w);
1.98 + cr.accept(fic, 0);
1.99 + arr = w.toByteArray();
1.100 + }
1.101 + if (arr != null) {
1.102 + return defineClass(name, arr, 0, arr.length);
1.103 + }
1.104 + } catch (IOException ex) {
1.105 + throw new ClassNotFoundException("Can't load " + name, ex);
1.106 + } finally {
1.107 + try {
1.108 + if (is != null) is.close();
1.109 + } catch (IOException ex) {
1.110 + throw new ClassNotFoundException(null, ex);
1.111 + }
1.112 + }
1.113 + }
1.114 + if (name.startsWith("org.apidesign.html.boot.spi.Fn")) {
1.115 + return Class.forName(name);
1.116 + }
1.117 +
1.118 + return super.findClass(name);
1.119 + }
1.120 +
1.121 + protected abstract Fn defineFn(String code, String... names);
1.122 +
1.123 +
1.124 + private static final class FindInClass extends ClassVisitor {
1.125 + private String name;
1.126 + private int found;
1.127 +
1.128 + public FindInClass(ClassVisitor cv) {
1.129 + super(Opcodes.ASM4, cv);
1.130 + }
1.131 +
1.132 + @Override
1.133 + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
1.134 + this.name = name;
1.135 + super.visit(version, access, name, signature, superName, interfaces);
1.136 + }
1.137 +
1.138 +
1.139 +
1.140 + @Override
1.141 + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
1.142 + return new FindInMethod(access, name, desc,
1.143 + super.visitMethod(access & (~Opcodes.ACC_NATIVE), name, desc, signature, exceptions)
1.144 + );
1.145 + }
1.146 +
1.147 + private final class FindInMethod extends MethodVisitor {
1.148 + private final String name;
1.149 + private final String desc;
1.150 + private final int access;
1.151 + private List<String> args;
1.152 + private String body;
1.153 + private boolean bodyGenerated;
1.154 +
1.155 + public FindInMethod(int access, String name, String desc, MethodVisitor mv) {
1.156 + super(Opcodes.ASM4, mv);
1.157 + this.access = access;
1.158 + this.name = name;
1.159 + this.desc = desc;
1.160 + }
1.161 +
1.162 + @Override
1.163 + public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
1.164 + if ("Lnet/java/html/js/JavaScriptBody;".equals(desc)) { // NOI18N
1.165 + found++;
1.166 + return new FindInAnno();
1.167 + }
1.168 + return super.visitAnnotation(desc, visible);
1.169 + }
1.170 +
1.171 + private void generateJSBody(List<String> args, String body) {
1.172 + this.args = args;
1.173 + this.body = body;
1.174 + }
1.175 +
1.176 + @Override
1.177 + public void visitCode() {
1.178 + if (body == null) {
1.179 + return;
1.180 + }
1.181 + generateBody();
1.182 + }
1.183 +
1.184 + private boolean generateBody() {
1.185 + if (bodyGenerated) {
1.186 + return false;
1.187 + }
1.188 + bodyGenerated = true;
1.189 +
1.190 + super.visitFieldInsn(
1.191 + Opcodes.GETSTATIC, FindInClass.this.name,
1.192 + "$$fn$$" + name + "_" + found,
1.193 + "Lorg/apidesign/html/boot/spi/Fn;"
1.194 + );
1.195 + super.visitInsn(Opcodes.DUP);
1.196 + Label ifNotNull = new Label();
1.197 + super.visitJumpInsn(Opcodes.IFNONNULL, ifNotNull);
1.198 +
1.199 + // init Fn
1.200 + super.visitInsn(Opcodes.POP);
1.201 + super.visitLdcInsn(Type.getObjectType(FindInClass.this.name));
1.202 + super.visitLdcInsn(body);
1.203 + super.visitIntInsn(Opcodes.SIPUSH, args.size());
1.204 + super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String");
1.205 + for (int i = 0; i < args.size(); i++) {
1.206 + String name = args.get(i);
1.207 + super.visitInsn(Opcodes.DUP);
1.208 + super.visitIntInsn(Opcodes.BIPUSH, i);
1.209 + super.visitLdcInsn(name);
1.210 + super.visitInsn(Opcodes.AASTORE);
1.211 + }
1.212 + super.visitMethodInsn(Opcodes.INVOKESTATIC,
1.213 + "org/apidesign/html/boot/spi/Fn", "define",
1.214 + "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/String;)Lorg/apidesign/html/boot/spi/Fn;"
1.215 + );
1.216 + // end of Fn init
1.217 +
1.218 + super.visitLabel(ifNotNull);
1.219 +
1.220 + final int offset;
1.221 + if ((access & Opcodes.ACC_STATIC) == 0) {
1.222 + offset = 1;
1.223 + super.visitIntInsn(Opcodes.ALOAD, 0);
1.224 + } else {
1.225 + offset = 0;
1.226 + super.visitInsn(Opcodes.ACONST_NULL);
1.227 + }
1.228 +
1.229 + super.visitIntInsn(Opcodes.SIPUSH, args.size());
1.230 + super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
1.231 +
1.232 + class SV extends SignatureVisitor {
1.233 + private boolean nowReturn;
1.234 + private Type returnType;
1.235 + private int index;
1.236 +
1.237 + public SV() {
1.238 + super(Opcodes.ASM4);
1.239 + }
1.240 +
1.241 + @Override
1.242 + public void visitBaseType(char descriptor) {
1.243 + final Type t = Type.getType("" + descriptor);
1.244 + if (nowReturn) {
1.245 + returnType = t;
1.246 + return;
1.247 + }
1.248 + FindInMethod.super.visitInsn(Opcodes.DUP);
1.249 + FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index);
1.250 + FindInMethod.super.visitVarInsn(t.getOpcode(Opcodes.ILOAD), index + offset);
1.251 + String factory;
1.252 + switch (descriptor) {
1.253 + case 'I': factory = "java/lang/Integer"; break;
1.254 + case 'J': factory = "java/lang/Long"; break;
1.255 + case 'S': factory = "java/lang/Short"; break;
1.256 + case 'F': factory = "java/lang/Float"; break;
1.257 + case 'D': factory = "java/lang/Double"; break;
1.258 + case 'Z': factory = "java/lang/Boolean"; break;
1.259 + case 'C': factory = "java/lang/Character"; break;
1.260 + case 'B': factory = "java/lang/Byte"; break;
1.261 + default: throw new IllegalStateException(t.toString());
1.262 + }
1.263 + FindInMethod.super.visitMethodInsn(Opcodes.INVOKESTATIC,
1.264 + factory, "valueOf", "(" + descriptor + ")L" + factory + ";"
1.265 + );
1.266 + FindInMethod.super.visitInsn(Opcodes.AASTORE);
1.267 + index++;
1.268 + }
1.269 +
1.270 + @Override
1.271 + public void visitClassType(String name) {
1.272 + if (nowReturn) {
1.273 + returnType = Type.getObjectType(name);
1.274 + return;
1.275 + }
1.276 + FindInMethod.super.visitInsn(Opcodes.DUP);
1.277 + FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index);
1.278 + FindInMethod.super.visitVarInsn(Opcodes.ALOAD, index + offset);
1.279 + FindInMethod.super.visitInsn(Opcodes.AASTORE);
1.280 + index++;
1.281 + }
1.282 +
1.283 + @Override
1.284 + public SignatureVisitor visitReturnType() {
1.285 + nowReturn = true;
1.286 + return this;
1.287 + }
1.288 +
1.289 +
1.290 + }
1.291 + SV sv = new SV();
1.292 + SignatureReader sr = new SignatureReader(desc);
1.293 + sr.accept(sv);
1.294 +
1.295 + super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
1.296 + "org/apidesign/html/boot/spi/Fn", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"
1.297 + );
1.298 + switch (sv.returnType.getSort()) {
1.299 + case Type.VOID:
1.300 + super.visitInsn(Opcodes.RETURN);
1.301 + break;
1.302 + case Type.ARRAY:
1.303 + case Type.OBJECT:
1.304 + super.visitTypeInsn(Opcodes.CHECKCAST, sv.returnType.getInternalName());
1.305 + super.visitInsn(Opcodes.ARETURN);
1.306 + break;
1.307 + default:
1.308 + super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Number");
1.309 + super.visitMethodInsn(Opcodes.INVOKEVIRTUAL,
1.310 + "java/lang/Number", sv.returnType.getClassName() + "Value", "()" + sv.returnType.getDescriptor()
1.311 + );
1.312 + super.visitInsn(sv.returnType.getOpcode(Opcodes.IRETURN));
1.313 + }
1.314 + return true;
1.315 + }
1.316 +
1.317 + @Override
1.318 + public void visitEnd() {
1.319 + super.visitEnd();
1.320 + if (body != null) {
1.321 + if (generateBody()) {
1.322 + // native method
1.323 + super.visitMaxs(1, 0);
1.324 + }
1.325 + FindInClass.this.visitField(
1.326 + Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC,
1.327 + "$$fn$$" + name + "_" + found,
1.328 + "Lorg/apidesign/html/boot/spi/Fn;",
1.329 + null, null
1.330 + );
1.331 + }
1.332 + }
1.333 +
1.334 +
1.335 +
1.336 +
1.337 +
1.338 + private final class FindInAnno extends AnnotationVisitor {
1.339 + private List<String> args = new ArrayList<String>();
1.340 + private String body;
1.341 +
1.342 + public FindInAnno() {
1.343 + super(Opcodes.ASM4);
1.344 + }
1.345 +
1.346 + @Override
1.347 + public void visit(String name, Object value) {
1.348 + if (name == null) {
1.349 + args.add((String) value);
1.350 + return;
1.351 + }
1.352 + assert name.equals("body");
1.353 + body = (String) value;
1.354 + }
1.355 +
1.356 + @Override
1.357 + public AnnotationVisitor visitArray(String name) {
1.358 + return this;
1.359 + }
1.360 +
1.361 + @Override
1.362 + public void visitEnd() {
1.363 + if (body != null) {
1.364 + generateJSBody(args, body);
1.365 + }
1.366 + }
1.367 + }
1.368 + }
1.369 + }
1.370 +
1.371 + private class ClassWriterEx extends ClassWriter {
1.372 +
1.373 + public ClassWriterEx(ClassReader classReader, int flags) {
1.374 + super(classReader, flags);
1.375 + }
1.376 +
1.377 + @Override
1.378 + protected String getCommonSuperClass(final String type1, final String type2) {
1.379 + Class<?> c, d;
1.380 + ClassLoader classLoader = JsClassLoader.this;
1.381 + try {
1.382 + c = Class.forName(type1.replace('/', '.'), false, classLoader);
1.383 + d = Class.forName(type2.replace('/', '.'), false, classLoader);
1.384 + } catch (Exception e) {
1.385 + throw new RuntimeException(e.toString());
1.386 + }
1.387 + if (c.isAssignableFrom(d)) {
1.388 + return type1;
1.389 + }
1.390 + if (d.isAssignableFrom(c)) {
1.391 + return type2;
1.392 + }
1.393 + if (c.isInterface() || d.isInterface()) {
1.394 + return "java/lang/Object";
1.395 + } else {
1.396 + do {
1.397 + c = c.getSuperclass();
1.398 + } while (!c.isAssignableFrom(d));
1.399 + return c.getName().replace('.', '/');
1.400 + }
1.401 + }
1.402 + }
1.403 +}