org.unseen.proxy.impl.ProxyClassBuilder.java Source code

Java tutorial

Introduction

Here is the source code for org.unseen.proxy.impl.ProxyClassBuilder.java

Source

/**
 * Copyright (C) 2008 Todor Boev
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.unseen.proxy.impl;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.Lock;

import java.io.IOException;
import java.io.InputStream;

import org.objectweb.asm.*;
import org.unseen.proxy.gen.Proxy;
import org.unseen.proxy.gen.ProxyException;
import org.unseen.proxy.ref.Ref;

/**
 * @author Todor Boev
 * @version $Revision$
 */
public class ProxyClassBuilder implements Opcodes {
    private static final String PROXY_IFACE;
    private static final String PROXY_CONTROL;
    private static final String PROXY_CONTROL_DESC;

    private static final String REF_IFACE;
    private static final String REF_LOCK;
    private static final String REF_LOCK_DESC;
    private static final String REF_VAL;
    private static final String REF_VAL_DESC;

    static {
        try {
            /* Init the Proxy constants */
            PROXY_IFACE = toInternalName(Proxy.class);

            PROXY_CONTROL = Proxy.class.getMethod("proxyControl", new Class[0]).getName();
            PROXY_CONTROL_DESC = "()L" + toInternalName(Ref.class) + ";";

            /* Init the DynamicRef constants */
            REF_IFACE = toInternalName(Ref.class.getName());

            REF_LOCK = Ref.class.getMethod("lock", new Class[0]).getName();
            REF_LOCK_DESC = "()L" + toInternalName(Lock.class) + ";";

            REF_VAL = Ref.class.getMethod("val", new Class[0]).getName();
            REF_VAL_DESC = "()L" + toInternalName(Object.class) + ";";
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private static final String FIELD_DESC = "L" + REF_IFACE + ";";

    private final Set<String> visitedMethods;

    /**
     * 
     */
    private class MixinGenerator implements ClassVisitor {
        private final String ifName;
        private final String fieldName;

        public MixinGenerator(String ifName) {
            this.ifName = toInternalName(ifName);
            this.fieldName = toIdentifier(ifName);
        }

        /**
         * @param mv
         * @param fieldNo
         */
        public void generateConstructorCode(MethodVisitor mv, int fieldNo) {
            mv.visitVarInsn(ALOAD, 0);
            mv.visitVarInsn(ALOAD, fieldNo);
            mv.visitFieldInsn(PUTFIELD, implName, fieldName, FIELD_DESC);
        }

        /**
         * 
         */
        public void generateProxyControl() {
            MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, PROXY_CONTROL, PROXY_CONTROL_DESC, null, null);
            mv.visitCode();

            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(GETFIELD, implName, fieldName, FIELD_DESC);
            mv.visitInsn(ARETURN);

            mv.visitMaxs(0, 0);
            mv.visitEnd();
        }

        /**
         * 
         */
        public void generateHashCode() {
            generateMethod(ACC_PUBLIC, "hashCode", "()I", null, null);
        }

        /**
         * 
         */
        public void generateToString() {
            generateMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null);
        }

        /**
         * 
         */
        public void generateEquals() {
            visitedMethods.add("equals" + "(Ljava/lang/Object;)Z");

            MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "equals", "(Ljava/lang/Object;)Z", null, null);
            mv.visitCode();

            Label l0 = new Label();
            Label l1 = new Label();
            mv.visitTryCatchBlock(l0, l1, l1, null);
            Label l2 = new Label();
            Label l3 = new Label();
            mv.visitTryCatchBlock(l2, l3, l3, null);
            mv.visitInsn(ACONST_NULL);
            mv.visitVarInsn(ASTORE, 2);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(GETFIELD, implName, fieldName, FIELD_DESC);
            mv.visitMethodInsn(INVOKEINTERFACE, REF_IFACE, REF_LOCK, REF_LOCK_DESC);
            mv.visitMethodInsn(INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "lock", "()V");
            mv.visitLabel(l0);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(GETFIELD, implName, fieldName, FIELD_DESC);
            mv.visitMethodInsn(INVOKEINTERFACE, REF_IFACE, REF_VAL, REF_VAL_DESC);
            mv.visitVarInsn(ASTORE, 2);
            Label l4 = new Label();
            mv.visitJumpInsn(GOTO, l4);
            mv.visitLabel(l1);
            mv.visitVarInsn(ASTORE, 3);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(GETFIELD, implName, fieldName, FIELD_DESC);
            mv.visitMethodInsn(INVOKEINTERFACE, REF_IFACE, REF_LOCK, REF_LOCK_DESC);
            mv.visitMethodInsn(INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "unlock", "()V");
            mv.visitVarInsn(ALOAD, 3);
            mv.visitInsn(ATHROW);
            mv.visitLabel(l4);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(GETFIELD, implName, fieldName, FIELD_DESC);
            mv.visitMethodInsn(INVOKEINTERFACE, REF_IFACE, REF_LOCK, REF_LOCK_DESC);
            mv.visitMethodInsn(INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "unlock", "()V");
            mv.visitVarInsn(ALOAD, 1);
            mv.visitTypeInsn(INSTANCEOF, PROXY_IFACE);
            Label l5 = new Label();
            mv.visitJumpInsn(IFEQ, l5);
            mv.visitVarInsn(ALOAD, 1);
            mv.visitTypeInsn(CHECKCAST, PROXY_IFACE);
            mv.visitMethodInsn(INVOKEINTERFACE, PROXY_IFACE, PROXY_CONTROL, PROXY_CONTROL_DESC);
            mv.visitVarInsn(ASTORE, 3);
            mv.visitVarInsn(ALOAD, 3);
            mv.visitMethodInsn(INVOKEINTERFACE, REF_IFACE, REF_LOCK, REF_LOCK_DESC);
            mv.visitMethodInsn(INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "lock", "()V");
            mv.visitLabel(l2);
            mv.visitVarInsn(ALOAD, 3);
            mv.visitMethodInsn(INVOKEINTERFACE, REF_IFACE, REF_VAL, REF_VAL_DESC);
            mv.visitVarInsn(ASTORE, 1);
            Label l6 = new Label();
            mv.visitJumpInsn(GOTO, l6);
            mv.visitLabel(l3);
            mv.visitVarInsn(ASTORE, 4);
            mv.visitVarInsn(ALOAD, 3);
            mv.visitMethodInsn(INVOKEINTERFACE, REF_IFACE, REF_LOCK, REF_LOCK_DESC);
            mv.visitMethodInsn(INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "unlock", "()V");
            mv.visitVarInsn(ALOAD, 4);
            mv.visitInsn(ATHROW);
            mv.visitLabel(l6);
            mv.visitVarInsn(ALOAD, 3);
            mv.visitMethodInsn(INVOKEINTERFACE, REF_IFACE, REF_LOCK, REF_LOCK_DESC);
            mv.visitMethodInsn(INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "unlock", "()V");
            mv.visitLabel(l5);
            mv.visitVarInsn(ALOAD, 2);
            mv.visitVarInsn(ALOAD, 1);
            mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Object", "equals", "(Ljava/lang/Object;)Z");
            mv.visitInsn(IRETURN);

            mv.visitMaxs(0, 0);
            mv.visitEnd();
        }

        /**
         *  
         */
        public void generateMixin() {
            /* Generate the field */
            cv.visitField(ACC_PRIVATE + ACC_FINAL, fieldName, FIELD_DESC, null, null).visitEnd();

            /* Visit the interface */
            getReader(ifName).accept(this, null, ClassReader.SKIP_DEBUG);
        }

        public void visit(int ver, int acc, String name, String sig, String superN, String[] ifaces) {
            /*
             * Visit the super interfaces before the main interface. This yields a depth
             * first left to right traversal of the interface acyclic directed graph
             */
            for (int i = 0; i < ifaces.length; i++) {
                getReader(ifaces[i]).accept(this, null, ClassReader.SKIP_DEBUG);
            }
        }

        public void visitSource(String source, String debug) {
            return;
        }

        public void visitOuterClass(String owner, String name, String desc) {
            return;
        }

        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
            return null;
        }

        public void visitAttribute(Attribute attr) {
        }

        public void visitInnerClass(String name, String outerName, String innerName, int access) {
        }

        public FieldVisitor visitField(int access, String name, String desc, String sig, Object val) {
            return null;
        }

        public MethodVisitor visitMethod(int access, String name, String desc, String sig, String[] excs) {
            generateMethod(access, name, desc, sig, excs);
            return null;
        }

        public void visitEnd() {
        }

        private void generateMethod(int access, String name, String desc, String sig, String[] excs) {
            String methodSig = name + desc;
            if (visitedMethods.contains(methodSig)) {
                return;
            }

            MethodVisitor mv = cv.visitMethod(access & ~ACC_ABSTRACT, name, desc, sig, excs);
            mv.visitCode();

            Label l0 = new Label();
            Label l1 = new Label();
            mv.visitTryCatchBlock(l0, l1, l1, null);

            /* Lock */
            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(GETFIELD, implName, fieldName, FIELD_DESC);
            mv.visitMethodInsn(INVOKEINTERFACE, REF_IFACE, REF_LOCK, REF_LOCK_DESC);
            mv.visitMethodInsn(INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "lock", "()V");
            mv.visitLabel(l0);

            /* Dereference */
            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(GETFIELD, implName, fieldName, FIELD_DESC);
            mv.visitMethodInsn(INVOKEINTERFACE, REF_IFACE, REF_VAL, REF_VAL_DESC);
            mv.visitTypeInsn(CHECKCAST, ifName);

            /* Invoke */
            Type[] args = Type.getArgumentTypes(desc);
            for (int i = 0; i < args.length; i++) {
                mv.visitVarInsn(args[i].getOpcode(ILOAD), i + 1);
            }
            mv.visitMethodInsn(INVOKEINTERFACE, ifName, name, desc);

            Label l2 = new Label();
            mv.visitJumpInsn(GOTO, l2);

            /* Unlock and re-throw */
            mv.visitLabel(l1);
            mv.visitVarInsn(ASTORE, args.length + 1);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(GETFIELD, implName, fieldName, FIELD_DESC);
            mv.visitMethodInsn(INVOKEINTERFACE, REF_IFACE, REF_LOCK, REF_LOCK_DESC);
            mv.visitMethodInsn(INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "unlock", "()V");
            mv.visitVarInsn(ALOAD, args.length + 1);
            mv.visitInsn(ATHROW);

            /* Unlock and return */
            mv.visitLabel(l2);
            mv.visitVarInsn(ALOAD, 0);
            mv.visitFieldInsn(GETFIELD, implName, fieldName, FIELD_DESC);
            mv.visitMethodInsn(INVOKEINTERFACE, REF_IFACE, REF_LOCK, REF_LOCK_DESC);
            mv.visitMethodInsn(INVOKEINTERFACE, "java/util/concurrent/locks/Lock", "unlock", "()V");
            mv.visitInsn(Type.getReturnType(desc).getOpcode(IRETURN));

            mv.visitMaxs(0, 0);
            mv.visitEnd();

            visitedMethods.add(methodSig);
        }
    }

    private final String implName;
    private final ClassLoader loader;

    private final List<MixinGenerator> mixins;
    private final List<String> classSig;

    private String constrSig = "";

    private final ClassWriter cv;

    /**
     * @param implName
     * @param loader
     */
    public ProxyClassBuilder(String implName, ClassLoader loader) {
        this.implName = toInternalName(implName);
        this.loader = loader;

        this.mixins = new ArrayList<MixinGenerator>();
        this.classSig = new ArrayList<String>();
        this.visitedMethods = new HashSet<String>();

        this.cv = new ClassWriter(ClassWriter.COMPUTE_MAXS + ClassWriter.COMPUTE_FRAMES);
    }

    /**
     * @param ifName
     * @param dynamic
     */
    public void add(String ifName) {
        ifName = toInternalName(ifName);

        boolean first = mixins.size() == 0;

        MixinGenerator e = new MixinGenerator(ifName);
        mixins.add(e);
        classSig.add(ifName);

        /* Add the mixin to the constructor signature */
        constrSig += first ? "(" : "";
        constrSig += "L" + REF_IFACE + ";";
    }

    /**
     * @return
     */
    public byte[] generate() {
        visitedMethods.clear();

        classSig.add(PROXY_IFACE);
        constrSig += ")V";

        /* Write the class header */
        cv.visit(V1_5, ACC_PUBLIC + ACC_SUPER, implName, null, "java/lang/Object",
                classSig.toArray(new String[classSig.size()]));

        /* Start the constructor */
        MethodVisitor mv = cv.visitMethod(ACC_PUBLIC, "<init>", constrSig, null, null);
        mv.visitCode();
        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");

        /*
         * Add a field to hold the delegate followed by implementations of all
         * methods that use that field for delegation
         */
        for (int no = 0; no < mixins.size(); no++) {
            MixinGenerator e = mixins.get(no);

            /*
             * First mixin our overrides of object methods so that this is remembered
             * and later mixing does not add them again.
             */
            if (no == 0) {
                e.generateProxyControl();
                e.generateToString();
                e.generateHashCode();
                e.generateEquals();
            }

            e.generateConstructorCode(mv, no + 1);
            e.generateMixin();
        }

        /* Finish the constructor */
        mv.visitInsn(RETURN);
        mv.visitMaxs(0, 0);
        mv.visitEnd();

        cv.visitEnd();

        return cv.toByteArray();
    }

    /**
     * @param name
     * @return
     */
    private ClassReader getReader(String name) {
        try {
            String path = name + ".class";

            InputStream str = ClassLoader.getSystemResourceAsStream(path);

            if (str == null) {
                ClassLoader otherLoader = loader.loadClass(toClassName(name)).getClassLoader();
                str = otherLoader.getResourceAsStream(path);
            }

            return new ClassReader(str);
        } catch (IOException e) {
            throw new ProxyException(e);
        } catch (ClassNotFoundException e) {
            throw new ProxyException(e);
        }
    }

    /**
     * @param name
     * @return
     */
    private static String toInternalName(Class<?> cl) {
        return toInternalName(cl.getName());
    }

    /**
     * @param name
     * @return
     */
    private static String toInternalName(String name) {
        return name.replace('.', '/');
    }

    /**
     * @param name
     * @return
     */
    private static String toClassName(String name) {
        return name.replace('/', '.');
    }

    /**
     * @param name
     * @return
     */
    private static String toIdentifier(String name) {
        return name.replace('.', '_').replace('/', '_');
    }
}