All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apidesign.html.boot.impl.JsClassLoader Maven / Gradle / Ivy

The newest version!
/**
 * HTML via Java(tm) Language Bindings
 * Copyright (C) 2013 Jaroslav Tulach 
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 2 of the License.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details. apidesign.org
 * designates this particular file as subject to the
 * "Classpath" exception as provided by apidesign.org
 * in the License file that accompanied this code.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. Look for COPYING file in the top folder.
 * If not, see http://wiki.apidesign.org/wiki/GPLwithClassPathException
 */
package org.apidesign.html.boot.impl;

import org.apidesign.html.boot.spi.Fn;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.signature.SignatureReader;
import org.objectweb.asm.signature.SignatureVisitor;
import org.objectweb.asm.signature.SignatureWriter;

/** 
 *
 * @author Jaroslav Tulach 
 */
abstract class JsClassLoader extends ClassLoader {
    JsClassLoader(ClassLoader parent) {
        super(parent);
        setDefaultAssertionStatus(JsClassLoader.class.desiredAssertionStatus());
    }
    
    @Override
    protected abstract URL findResource(String name);
    
    @Override
    protected abstract Enumeration findResources(String name);

    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        if (name.startsWith("javafx")) {
            return Class.forName(name);
        }
        if (name.startsWith("netscape")) {
            return Class.forName(name);
        }
        if (name.startsWith("com.sun")) {
            return Class.forName(name);
        }
        if (name.equals(JsClassLoader.class.getName())) {
            return JsClassLoader.class;
        }
        if (name.equals(Fn.class.getName())) {
            return Fn.class;
        }
        if (name.equals(Fn.Presenter.class.getName())) {
            return Fn.Presenter.class;
        }
        if (name.equals(FnUtils.class.getName())) {
            return FnUtils.class;
        }
        URL u = findResource(name.replace('.', '/') + ".class");
        if (u != null) {
            InputStream is = null;
            try {
                is = u.openStream();
                byte[] arr = new byte[is.available()];
                int len = 0;
                while (len < arr.length) {
                    int read = is.read(arr, len, arr.length - len);
                    if (read == -1) {
                        throw new IOException("Can't read " + u);
                    }
                    len += read;
                }
                is.close();
                is = null;
                ClassReader cr = new ClassReader(arr) {
                    // to allow us to compile with -profile compact1 on 
                    // JDK8 while processing the class as JDK7, the highest
                    // class format asm 4.1 understands to
                    @Override
                    public short readShort(int index) {
                        short s = super.readShort(index);
                        if (index == 6 && s > Opcodes.V1_7) {
                            return Opcodes.V1_7;
                        }
                        return s;
                    }
                };
                FindInClass tst = new FindInClass(null);
                cr.accept(tst, 0);
                if (tst.found > 0) {
                    ClassWriter w = new ClassWriterEx(cr, ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
                    FindInClass fic = new FindInClass(w);
                    cr.accept(fic, 0);
                    arr = w.toByteArray();
                }
                if (arr != null) {
                    return defineClass(name, arr, 0, arr.length);
                }
            } catch (IOException ex) {
                throw new ClassNotFoundException("Can't load " + name, ex);
            } finally {
                try {
                    if (is != null) is.close();
                } catch (IOException ex) {
                    throw new ClassNotFoundException(null, ex);
                }
            }
        }
        if (
            name.equals("org.apidesign.html.boot.spi.Fn") ||
            name.equals("org.apidesign.html.boot.impl.FnUtils")
        ) {
            return Class.forName(name);
        }
        
        return super.findClass(name);
    }
    
    protected abstract Fn defineFn(String code, String... names);
    protected abstract void loadScript(Reader code) throws Exception;
    
    private final class FindInClass extends ClassVisitor {
        private String name;
        private int found;
        
        public FindInClass(ClassVisitor cv) {
            super(Opcodes.ASM4, cv);
        }

        @Override
        public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
            this.name = name;
            super.visit(version, access, name, signature, superName, interfaces);
        }

        @Override
        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
            if ("Lnet/java/html/js/JavaScriptResource;".equals(desc)) {
                return new LoadResource();
            }
            return super.visitAnnotation(desc, visible);
        }
        

        @Override
        public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
            return new FindInMethod(access, name, desc,
                super.visitMethod(access & (~Opcodes.ACC_NATIVE), name, desc, signature, exceptions)
            );
        }
        
        private final class FindInMethod extends MethodVisitor {
            private final String name;
            private final String desc;
            private final int access;
            private List args;
            private String body;
            private boolean bodyGenerated;
            
            public FindInMethod(int access, String name, String desc, MethodVisitor mv) {
                super(Opcodes.ASM4, mv);
                this.access = access;
                this.name = name;
                this.desc = desc;
            }

            @Override
            public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
                if (
                    "Lnet/java/html/js/JavaScriptBody;".equals(desc) // NOI18N
                    || "Lorg/apidesign/bck2brwsr/core/JavaScriptBody;".equals(desc) // NOI18N
                ) {
                    found++;
                    return new FindInAnno();
                }
                return super.visitAnnotation(desc, visible);
            }

            private void generateJSBody(List args, String body) {
                this.args = args;
                this.body = body;
            }
            
            @Override
            public void visitCode() {
                if (body == null) {
                    return;
                } 
                generateBody();
            }
            
            private boolean generateBody() {
                if (bodyGenerated) {
                    return false;
                }
                bodyGenerated = true;
                
                super.visitFieldInsn(
                    Opcodes.GETSTATIC, FindInClass.this.name, 
                    "$$fn$$" + name + "_" + found, 
                    "Lorg/apidesign/html/boot/spi/Fn;"
                );
                super.visitInsn(Opcodes.DUP);
                super.visitMethodInsn(
                    Opcodes.INVOKESTATIC, 
                    "org/apidesign/html/boot/impl/FnUtils", "isValid", 
                    "(Lorg/apidesign/html/boot/spi/Fn;)Z"
                );
                Label ifNotNull = new Label();
                super.visitJumpInsn(Opcodes.IFNE, ifNotNull);
                
                // init Fn
                super.visitInsn(Opcodes.POP);
                super.visitLdcInsn(Type.getObjectType(FindInClass.this.name));
                super.visitLdcInsn(body);
                super.visitIntInsn(Opcodes.SIPUSH, args.size());
                super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/String");
                boolean needsVM = false;
                for (int i = 0; i < args.size(); i++) {
                    assert !needsVM;
                    String argName = args.get(i);
                    needsVM = "vm".equals(argName);
                    super.visitInsn(Opcodes.DUP);
                    super.visitIntInsn(Opcodes.BIPUSH, i);
                    super.visitLdcInsn(argName);
                    super.visitInsn(Opcodes.AASTORE);
                }
                super.visitMethodInsn(Opcodes.INVOKESTATIC, 
                    "org/apidesign/html/boot/impl/FnUtils", "define", 
                    "(Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/String;)Lorg/apidesign/html/boot/spi/Fn;"
                );
                super.visitInsn(Opcodes.DUP);
                super.visitFieldInsn(
                    Opcodes.PUTSTATIC, FindInClass.this.name, 
                    "$$fn$$" + name + "_" + found, 
                    "Lorg/apidesign/html/boot/spi/Fn;"
                );
                // end of Fn init
                
                super.visitLabel(ifNotNull);
                
                final int offset;
                if ((access & Opcodes.ACC_STATIC) == 0) {
                    offset = 1;
                    super.visitIntInsn(Opcodes.ALOAD, 0);
                } else {
                    offset = 0;
                    super.visitInsn(Opcodes.ACONST_NULL);
                }
                
                super.visitIntInsn(Opcodes.SIPUSH, args.size());
                super.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
                
                class SV extends SignatureVisitor {
                    private boolean nowReturn;
                    private Type returnType;
                    private int index;
                    private int loadIndex = offset;
                    
                    public SV() {
                        super(Opcodes.ASM4);
                    }
                    
                    @Override
                    public void visitBaseType(char descriptor) {
                        final Type t = Type.getType("" + descriptor);
                        if (nowReturn) {
                            returnType = t;
                            return;
                        }
                        FindInMethod.super.visitInsn(Opcodes.DUP);
                        FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++);
                        FindInMethod.super.visitVarInsn(t.getOpcode(Opcodes.ILOAD), loadIndex++);
                        String factory;
                        switch (descriptor) {
                        case 'I': factory = "java/lang/Integer"; break;
                        case 'J': factory = "java/lang/Long"; loadIndex++; break;
                        case 'S': factory = "java/lang/Short"; break;
                        case 'F': factory = "java/lang/Float"; break;
                        case 'D': factory = "java/lang/Double"; loadIndex++; break;
                        case 'Z': factory = "java/lang/Boolean"; break;
                        case 'C': factory = "java/lang/Character"; break;
                        case 'B': factory = "java/lang/Byte"; break;
                        default: throw new IllegalStateException(t.toString());
                        }
                        FindInMethod.super.visitMethodInsn(Opcodes.INVOKESTATIC,
                            factory, "valueOf", "(" + descriptor + ")L" + factory + ";"
                        );
                        FindInMethod.super.visitInsn(Opcodes.AASTORE);
                    }

                    @Override
                    public SignatureVisitor visitArrayType() {
                        if (nowReturn) {
                            throw new IllegalStateException("Not supported yet");
                        }
                        loadObject();
                        return new SignatureWriter();
                    }

                    @Override
                    public void visitClassType(String name) {
                        if (nowReturn) {
                            returnType = Type.getObjectType(name);
                            return;
                        }
                        loadObject();
                    }

                    @Override
                    public SignatureVisitor visitReturnType() {
                        nowReturn = true;
                        return this;
                    }

                    private void loadObject() {
                        FindInMethod.super.visitInsn(Opcodes.DUP);
                        FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, index++);
                        FindInMethod.super.visitVarInsn(Opcodes.ALOAD, loadIndex++);
                        FindInMethod.super.visitInsn(Opcodes.AASTORE);
                    }
                    
                }
                SV sv = new SV();
                SignatureReader sr = new SignatureReader(desc);
                sr.accept(sv);
                
                if (needsVM) {
                    FindInMethod.super.visitInsn(Opcodes.DUP);
                    FindInMethod.super.visitIntInsn(Opcodes.SIPUSH, sv.index);
                    int lastSlash = FindInClass.this.name.lastIndexOf('/');
                    String jsCallbacks = FindInClass.this.name.substring(0, lastSlash + 1) + "$JsCallbacks$";
                    FindInMethod.super.visitFieldInsn(Opcodes.GETSTATIC, jsCallbacks, "VM", "L" + jsCallbacks + ";");
                    FindInMethod.super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, jsCallbacks, "current", "()L" + jsCallbacks + ";");
                    FindInMethod.super.visitInsn(Opcodes.AASTORE);
                }
                
                super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, 
                    "org/apidesign/html/boot/spi/Fn", "invoke", "(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;"
                );
                switch (sv.returnType.getSort()) {
                case Type.VOID: 
                    super.visitInsn(Opcodes.RETURN);
                    break;
                case Type.ARRAY:
                case Type.OBJECT:
                    super.visitTypeInsn(Opcodes.CHECKCAST, sv.returnType.getInternalName());
                    super.visitInsn(Opcodes.ARETURN);
                    break;
                case Type.BOOLEAN:
                    super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Boolean");
                    super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, 
                        "java/lang/Boolean", "booleanValue", "()Z"
                    );
                    super.visitInsn(Opcodes.IRETURN);
                    break;
                default:
                    super.visitTypeInsn(Opcodes.CHECKCAST, "java/lang/Number");
                    super.visitMethodInsn(Opcodes.INVOKEVIRTUAL, 
                        "java/lang/Number", sv.returnType.getClassName() + "Value", "()" + sv.returnType.getDescriptor()
                    );
                    super.visitInsn(sv.returnType.getOpcode(Opcodes.IRETURN));
                }
                return true;
            }

            @Override
            public void visitEnd() {
                super.visitEnd();
                if (body != null) {
                    if (generateBody()) {
                        // native method
                        super.visitMaxs(1, 0);
                    }
                    FindInClass.this.visitField(
                        Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, 
                        "$$fn$$" + name + "_" + found, 
                        "Lorg/apidesign/html/boot/spi/Fn;", 
                        null, null
                    );
                }
            }
            
            
            
            
        
            private final class FindInAnno extends AnnotationVisitor {
                private List args = new ArrayList();
                private String body;
                private boolean javacall = false;

                public FindInAnno() {
                    super(Opcodes.ASM4);
                }

                @Override
                public void visit(String name, Object value) {
                    if (name == null) {
                        args.add((String) value);
                        return;
                    }
                    if (name.equals("javacall")) { // NOI18N
                        javacall = (Boolean)value;
                        return;
                    }
                    assert name.equals("body");
                    body = (String) value;
                }

                @Override
                public AnnotationVisitor visitArray(String name) {
                    return this;
                }

                @Override
                public void visitEnd() {
                    if (body != null) {
                        if (javacall) {
                            body = FnUtils.callback(body);
                            args.add("vm");
                        }
                        generateJSBody(args, body);
                    }
                }
            }
        }
        
        private final class LoadResource extends AnnotationVisitor {
            public LoadResource() {
                super(Opcodes.ASM4);
            }
            
            @Override
            public void visit(String attrName, Object value)  {
                String relPath = (String) value;
                if (relPath.startsWith("/")) {
                    FnUtils.loadScript(JsClassLoader.this, relPath);
                } else {
                    int last = name.lastIndexOf('/');
                    String fullPath = name.substring(0, last + 1) + relPath;
                    FnUtils.loadScript(JsClassLoader.this, fullPath);
                }
            }
        }
    }
    
    private class ClassWriterEx extends ClassWriter {

        public ClassWriterEx(ClassReader classReader, int flags) {
            super(classReader, flags);
        }
        
        @Override
        protected String getCommonSuperClass(final String type1, final String type2) {
            Class c, d;
            ClassLoader classLoader = JsClassLoader.this;
            try {
                c = Class.forName(type1.replace('/', '.'), false, classLoader);
                d = Class.forName(type2.replace('/', '.'), false, classLoader);
            } catch (Exception e) {
                throw new RuntimeException(e.toString());
            }
            if (c.isAssignableFrom(d)) {
                return type1;
            }
            if (d.isAssignableFrom(c)) {
                return type2;
            }
            if (c.isInterface() || d.isInterface()) {
                return "java/lang/Object";
            } else {
                do {
                    c = c.getSuperclass();
                } while (!c.isAssignableFrom(d));
                return c.getName().replace('.', '/');
            }
        }        
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy