org.actorsguildframework.internal.codegenerator.BeanCreator.java Source code

Java tutorial

Introduction

Here is the source code for org.actorsguildframework.internal.codegenerator.BeanCreator.java

Source

/*
 *    Copyright 2008,2009 Tim Jansen
 *
 *  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.actorsguildframework.internal.codegenerator;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;

import org.actorsguildframework.Actor;
import org.actorsguildframework.ActorException;
import org.actorsguildframework.ConfigurationException;
import org.actorsguildframework.annotations.Bean;
import org.actorsguildframework.internal.BeanClassDescriptor;
import org.actorsguildframework.internal.BeanFactory;
import org.actorsguildframework.internal.BeanHelper;
import org.actorsguildframework.internal.PropertyDescriptor;
import org.actorsguildframework.internal.PropertyDescriptor.PropertySource;
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;

/**
 * BeanCreator is a singleton that creates bean implementation classes for all {@link Bean}
 * classes. It works system-wide, for all Controllers. 
 */
public final class BeanCreator {
    /**
     * The only instance of BeanCreator.
     */
    private static final BeanCreator instance = new BeanCreator();

    /**
     * The ActorProxyCreator.
     */
    private static final ActorProxyCreator actorCreator = ActorProxyCreator.getInstance();

    /**
     * Map of bean factories. Synchronize before accessing it!
     */
    private final HashMap<Class<?>, BeanFactory> beanFactories;

    /**
     * The version of the generated Java classes. 1.5 or 1.6, depending on the
     * Java version. 
     */
    private final static int codeVersion = System.getProperty("java.version").startsWith("1.5") ? Opcodes.V1_5
            : Opcodes.V1_6;

    /**
     * Private constructor.
     */
    private BeanCreator() {
        beanFactories = new HashMap<Class<?>, BeanFactory>();
    }

    /**
     * Returns the BeanCreator.
     * @return the BeanCreator
     */
    public static BeanCreator getInstance() {
        return instance;
    }

    /**
     * Returns a proxy factory for the given {@link Bean} class. 
     * @param beanClass the class of the Bean
     * @return the new factory
     * @throws ConfigurationException if the agent is not configured correctly
     */
    public BeanFactory getFactory(Class<?> beanClass) {
        synchronized (this) {
            BeanFactory apf = beanFactories.get(beanClass);
            if (apf != null)
                return apf;

            // TODO: don't create an instance while synchronized. Find a way to
            // block only requests for the same class.
            // Otherwise this class could unnecessarily slow down start-up of
            // programs using AG.

            if (beanClass.getAnnotation(Bean.class) == null)
                throw new ConfigurationException(
                        "The given class has no @Bean annotation (and is no actor, which are @Beans).");

            BeanFactory r;
            if (Actor.class.isAssignableFrom(beanClass))
                r = actorCreator.createFactory(beanClass);
            else
                r = createFactory(beanClass);
            beanFactories.put(beanClass, r);
            return r;
        }
    }

    /**
     * Creates a new factory.
     * @param beanClass
     * @return the factory
     * @throws ConfigurationException if the agent is not configured correctly
     */
    private BeanFactory createFactory(Class<?> beanClass) {
        // TODO: proxy creation should be parallelized. Use an Executor and futures.
        // At least one future for each generated class.
        // This would improve start-up time of AG apps.

        try {
            BeanClassDescriptor bcd = BeanClassDescriptor.create(beanClass);
            generateBeanClass(beanClass, bcd);
            return generateFactoryClass(beanClass, String.format("%s__BEAN", beanClass.getName()), bcd, false);
        } catch (NoSuchMethodException e) {
            throw new ActorException("Unexpected error while creating bean", e);
        }
    }

    /**
     * Creates and loads the bean's factory class.
     * @param beanClass the Bean class
     * @param generatedBeanClassName the name of the class that this factory will produce
     * @param bcd the bean class descriptor
     * @param synchronizeInitializers true to synchronize the initializer invocations (actors
     *       do this), false otherwise
     * @return the new factory
     */
    public static BeanFactory generateFactoryClass(Class<?> beanClass, String generatedBeanClassName,
            BeanClassDescriptor bcd, boolean synchronizeInitializers) {
        String className = String.format("%s__BEANFACTORY", beanClass.getName());
        String classNameInternal = className.replace('.', '/');

        String generatedBeanClassNameInternal = generatedBeanClassName.replace('.', '/');

        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        MethodVisitor mv;
        cw.visit(codeVersion, Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER + Opcodes.ACC_SYNTHETIC,
                classNameInternal, null, "java/lang/Object",
                new String[] { Type.getInternalName(BeanFactory.class) });

        cw.visitSource(null, null);

        {
            mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
            mv.visitCode();
            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitVarInsn(Opcodes.ALOAD, 0);
            mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
            mv.visitInsn(Opcodes.RETURN);
            Label l1 = new Label();
            mv.visitLabel(l1);
            mv.visitLocalVariable("this", "L" + classNameInternal + ";", null, l0, l1, 0);
            mv.visitMaxs(1, 1);
            mv.visitEnd();
        }
        {
            mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "createNewInstance",
                    "(Lorg/actorsguildframework/internal/Controller;Lorg/actorsguildframework/Props;)Ljava/lang/Object;",
                    null, null);
            mv.visitCode();
            final int initCount = bcd.getInitializerCount();
            Label tryStart = new Label();
            Label tryEnd = new Label();
            Label tryFinally = new Label();
            Label tryFinallyEnd = new Label();
            if (synchronizeInitializers && (initCount > 0)) {
                mv.visitTryCatchBlock(tryStart, tryEnd, tryFinally, null);
                mv.visitTryCatchBlock(tryFinally, tryFinallyEnd, tryFinally, null);
            }

            Label l0 = new Label();
            mv.visitLabel(l0);
            mv.visitTypeInsn(Opcodes.NEW, generatedBeanClassNameInternal);
            mv.visitInsn(Opcodes.DUP);
            mv.visitVarInsn(Opcodes.ALOAD, 1);
            mv.visitVarInsn(Opcodes.ALOAD, 2);
            mv.visitMethodInsn(Opcodes.INVOKESPECIAL, generatedBeanClassNameInternal, "<init>",
                    "(Lorg/actorsguildframework/internal/Controller;Lorg/actorsguildframework/Props;)V");

            if (synchronizeInitializers) {
                mv.visitInsn(Opcodes.DUP);
                mv.visitInsn(Opcodes.MONITORENTER);
                mv.visitLabel(tryStart);
            }

            for (int i = 0; i < initCount; i++) {
                Method m = bcd.getInitializers(i);
                mv.visitInsn(Opcodes.DUP);
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, generatedBeanClassNameInternal, m.getName(),
                        Type.getMethodDescriptor(m));
            }

            if (synchronizeInitializers) {
                if (initCount > 0) {
                    mv.visitInsn(Opcodes.DUP);
                    mv.visitInsn(Opcodes.MONITOREXIT);
                    mv.visitLabel(tryEnd);
                    mv.visitJumpInsn(Opcodes.GOTO, tryFinallyEnd);
                }
                mv.visitLabel(tryFinally);
                mv.visitInsn(Opcodes.DUP);
                mv.visitInsn(Opcodes.MONITOREXIT);
                mv.visitLabel(tryFinallyEnd);
            }

            mv.visitInsn(Opcodes.ARETURN);
            Label l1 = new Label();
            mv.visitLabel(l1);
            mv.visitLocalVariable("this", "L" + classNameInternal + ";", null, l0, l1, 0);
            mv.visitLocalVariable("controller", "Lorg/actorsguildframework/internal/Controller;", null, l0, l1, 1);
            mv.visitLocalVariable("props", "Lorg/actorsguildframework/Props;", null, l0, l1, 2);
            mv.visitLocalVariable("synchronizeInitializer", "Z", null, l0, l1, 3);
            mv.visitMaxs(4, 3);
            mv.visitEnd();
        }
        cw.visitEnd();

        Class<?> newClass = GenerationUtils.loadClass(className, cw.toByteArray());
        try {
            return (BeanFactory) newClass.newInstance();
        } catch (Exception e) {
            throw new ConfigurationException("Failure loading ActorProxyFactory", e);
        }
    }

    private final static String PROP_FIELD_NAME_TEMPLATE = "%s__BEAN_PROP";

    /**
     * Creates and loads the bean implementation class.
     * @param beanClass the bean class
     * @param bcd the BeanClassDescriptor to use
     * @throws ConfigurationException if the agent is not configured correctly
     */
    private static Class<?> generateBeanClass(Class<?> beanClass, BeanClassDescriptor bcd)
            throws NoSuchMethodException {

        String className = String.format("%s__BEAN", beanClass.getName());
        String classNameInternal = className.replace('.', '/');

        ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
        MethodVisitor mv;
        cw.visit(codeVersion, Opcodes.ACC_PUBLIC + Opcodes.ACC_FINAL + Opcodes.ACC_SUPER + Opcodes.ACC_SYNTHETIC,
                classNameInternal, null, Type.getInternalName(beanClass), new String[] {});

        cw.visitSource(null, null);

        // write @Prop fields
        writePropFields(bcd, cw);

        // static constructor
        {
            mv = cw.visitMethod(Opcodes.ACC_STATIC, "<clinit>", "()V", null, null);
            mv.visitCode();
            mv.visitInsn(Opcodes.RETURN);
            mv.visitMaxs(0, 0);
            mv.visitEnd();
        }

        // constructor(Controller, Props)
        writeConstructor(beanClass, bcd, classNameInternal, cw, null);

        // Write @Prop accessors
        writePropAccessors(bcd, classNameInternal, cw);

        cw.visitEnd();

        try {
            return (Class<?>) GenerationUtils.loadClass(className, cw.toByteArray());
        } catch (Exception e) {
            throw new ConfigurationException("Failure loading generated Bean", e);
        }
    }

    /**
     * Writes the accessor methods for @Prop generated properties.
     * @param bcd the class descriptor
     * @param classNameInternal the internal name of this class
     * @param cw the ClassWriter to write to
     */
    public static void writePropAccessors(BeanClassDescriptor bcd, String classNameInternal, ClassWriter cw) {
        String classNameDescriptor = "L" + classNameInternal + ";";

        MethodVisitor mv;
        for (int i = 0; i < bcd.getPropertyCount(); i++) {
            PropertyDescriptor pd = bcd.getProperty(i);
            if (!pd.getPropertySource().isGenerating())
                continue;

            {
                Type t = Type.getType(pd.getPropertyClass());
                Method orig = pd.getGetter();
                mv = cw.visitMethod(
                        GenerationUtils.convertAccessModifiers(orig.getModifiers())
                                + (bcd.isThreadSafe() ? Opcodes.ACC_SYNCHRONIZED : 0),
                        orig.getName(), Type.getMethodDescriptor(orig), GenericTypeHelper.getSignature(orig), null);

                mv.visitCode();
                Label l0 = new Label();
                mv.visitLabel(l0);
                mv.visitVarInsn(Opcodes.ALOAD, 0);
                mv.visitFieldInsn(Opcodes.GETFIELD, classNameInternal,
                        String.format(PROP_FIELD_NAME_TEMPLATE, pd.getName()),
                        Type.getDescriptor(pd.getPropertyClass()));
                mv.visitInsn(t.getOpcode(Opcodes.IRETURN));
                Label l1 = new Label();
                mv.visitLabel(l1);
                mv.visitLocalVariable("this", classNameDescriptor, null, l0, l1, 0);
                mv.visitMaxs(0, 0);
                mv.visitEnd();
            }

            if (pd.getAccess().isWritable()) {
                Type t = Type.getType(pd.getPropertyClass());
                Method orig = pd.getSetter();
                mv = cw.visitMethod(
                        GenerationUtils.convertAccessModifiers(orig.getModifiers())
                                + (bcd.isThreadSafe() ? Opcodes.ACC_SYNCHRONIZED : 0),
                        orig.getName(), Type.getMethodDescriptor(orig), GenericTypeHelper.getSignature(orig), null);

                mv.visitCode();
                Label l0 = new Label();
                mv.visitLabel(l0);
                mv.visitVarInsn(Opcodes.ALOAD, 0);
                mv.visitVarInsn(t.getOpcode(Opcodes.ILOAD), 1);
                mv.visitFieldInsn(Opcodes.PUTFIELD, classNameInternal,
                        String.format(PROP_FIELD_NAME_TEMPLATE, pd.getName()),
                        Type.getDescriptor(pd.getPropertyClass()));
                mv.visitInsn(Opcodes.RETURN);
                Label l1 = new Label();
                mv.visitLabel(l1);
                mv.visitLocalVariable("this", classNameDescriptor, null, l0, l1, 0);
                mv.visitLocalVariable("value", Type.getDescriptor(pd.getPropertyClass()), null, l0, l1, 1);
                mv.visitMaxs(0, 0);
                mv.visitEnd();
            }
        }
    }

    /**
     * Write the fields of @Prop properties.
     * @param bcd the class descriptor
     * @param cw the ClassWriter to write to
     */
    public static void writePropFields(BeanClassDescriptor bcd, ClassWriter cw) {
        for (int i = 0; i < bcd.getPropertyCount(); i++) {
            PropertyDescriptor pd = bcd.getProperty(i);
            if (pd.getPropertySource() != PropertySource.ABSTRACT_METHOD)
                continue;

            cw.visitField(Opcodes.ACC_PRIVATE + (pd.getAccess().isWritable() ? 0 : Opcodes.ACC_FINAL),
                    String.format(PROP_FIELD_NAME_TEMPLATE, pd.getName()),
                    Type.getDescriptor(pd.getPropertyClass()), GenericTypeHelper.getSignature(pd.getPropertyType()),
                    null).visitEnd();
        }
    }

    /**
     * Interface for classes that write only one snippet of code into a method.
     */
    public interface SnippetWriter {
        /**
         * Writes something into the given MethodVisitor.
         * @param mv the MethodVisitor to use
         */
        void write(MethodVisitor mv);
    }

    /**
     * Writes the bean constructor to the given ClassWriter.
     * @param beanClass the original bean class to extend
     * @param bcd the descriptor of the bean
     * @param classNameInternal the internal name of the new class
     * @param cw the ClassWriter to write to
     * @param snippetWriter if not null, this will be invoked to add a snippet
     *                      after the invocation of the super constructor
     */
    public static void writeConstructor(Class<?> beanClass, BeanClassDescriptor bcd, String classNameInternal,
            ClassWriter cw, SnippetWriter snippetWriter) {
        String classNameDescriptor = "L" + classNameInternal + ";";

        int localPropertySize = 0;
        ArrayList<PropertyDescriptor> localVarProperties = new ArrayList<PropertyDescriptor>();
        for (int i = 0; i < bcd.getPropertyCount(); i++) {
            PropertyDescriptor pd = bcd.getProperty(i);
            if (pd.getPropertySource().isGenerating() || (pd.getDefaultValue() != null)) {
                localVarProperties.add(pd);
                localPropertySize += Type.getType(pd.getPropertyClass()).getSize();
            }
        }

        final int locVarThis = 0;
        final int locVarController = 1;
        final int locVarProps = 2;
        final int locVarPropertiesOffset = 3;
        final int locVarP = 3 + localPropertySize;
        final int locVarK = 4 + localPropertySize;
        final int locVarV = 5 + localPropertySize;

        MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>",
                "(Lorg/actorsguildframework/internal/Controller;Lorg/actorsguildframework/Props;)V", null, null);
        mv.visitCode();
        Label lTry = new Label();
        Label lCatch = new Label();
        mv.visitTryCatchBlock(lTry, lCatch, lCatch, "java/lang/ClassCastException");

        Label lBegin = new Label();
        mv.visitLabel(lBegin);
        mv.visitVarInsn(Opcodes.ALOAD, locVarThis);
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(beanClass), "<init>", "()V");

        if (snippetWriter != null)
            snippetWriter.write(mv);

        Label lPropertyInit = new Label();
        mv.visitLabel(lPropertyInit);
        // load default values into the local variables for each property that must be set
        int varCount = 0;
        for (PropertyDescriptor pd : localVarProperties) {
            Type pt = Type.getType(pd.getPropertyClass());
            if (pd.getDefaultValue() != null)
                mv.visitFieldInsn(Opcodes.GETSTATIC, Type.getInternalName(pd.getDefaultValue().getDeclaringClass()),
                        pd.getDefaultValue().getName(), Type.getDescriptor(pd.getDefaultValue().getType()));
            else
                GenerationUtils.generateLoadDefault(mv, pd.getPropertyClass());
            mv.visitVarInsn(pt.getOpcode(Opcodes.ISTORE), locVarPropertiesOffset + varCount);
            varCount += pt.getSize();
        }

        // loop through the props argument's list
        mv.visitVarInsn(Opcodes.ALOAD, locVarProps);
        mv.visitVarInsn(Opcodes.ASTORE, locVarP);
        Label lWhile = new Label();
        Label lEndWhile = new Label();
        Label lWhileBody = new Label();
        mv.visitLabel(lWhile);
        mv.visitJumpInsn(Opcodes.GOTO, lEndWhile);
        mv.visitLabel(lWhileBody);

        mv.visitVarInsn(Opcodes.ALOAD, locVarP);
        mv.visitInsn(Opcodes.DUP);
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/actorsguildframework/Props", "getKey",
                "()Ljava/lang/String;");
        mv.visitVarInsn(Opcodes.ASTORE, locVarK);
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/actorsguildframework/Props", "getValue",
                "()Ljava/lang/Object;");
        mv.visitVarInsn(Opcodes.ASTORE, locVarV);

        mv.visitLabel(lTry);
        // write an if for each property
        Label lEndIf = new Label();
        varCount = 0;
        int ifCount = 0;
        for (int i = 0; i < bcd.getPropertyCount(); i++) {
            PropertyDescriptor pd = bcd.getProperty(i);
            boolean usesLocal = pd.getPropertySource().isGenerating() || (pd.getDefaultValue() != null);
            Class<?> propClass = pd.getPropertyClass();
            Type pt = Type.getType(propClass);
            mv.visitVarInsn(Opcodes.ALOAD, locVarK);
            mv.visitLdcInsn(pd.getName());
            mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/String", "equals", "(Ljava/lang/Object;)Z");
            Label lElse = new Label();
            mv.visitJumpInsn(Opcodes.IFEQ, lElse);

            if (!usesLocal)
                mv.visitVarInsn(Opcodes.ALOAD, locVarThis); // for setter invocation, load 'this'

            if (propClass.isPrimitive()) {
                mv.visitLdcInsn(pd.getName());
                mv.visitVarInsn(Opcodes.ALOAD, locVarV);
                mv.visitMethodInsn(Opcodes.INVOKESTATIC, Type.getInternalName(BeanHelper.class),
                        String.format("get%s%sFromPropValue",
                                propClass.getName().substring(0, 1).toUpperCase(Locale.US),
                                propClass.getName().substring(1)),
                        "(Ljava/lang/String;Ljava/lang/Object;)" + pt.getDescriptor());
            } else if (!propClass.equals(Object.class)) {
                mv.visitVarInsn(Opcodes.ALOAD, locVarV);
                mv.visitTypeInsn(Opcodes.CHECKCAST, pt.getInternalName());
            } else
                mv.visitVarInsn(Opcodes.ALOAD, locVarV);

            if (!usesLocal)
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, classNameInternal, pd.getSetter().getName(),
                        Type.getMethodDescriptor(pd.getSetter()));
            else
                mv.visitVarInsn(pt.getOpcode(Opcodes.ISTORE), varCount + locVarPropertiesOffset);

            mv.visitJumpInsn(Opcodes.GOTO, lEndIf);
            mv.visitLabel(lElse);

            ifCount++;
            if (usesLocal)
                varCount += pt.getSize();
        }

        // else (==> if not prop matched) throw IllegalArgumentException
        mv.visitTypeInsn(Opcodes.NEW, Type.getInternalName(IllegalArgumentException.class));
        mv.visitInsn(Opcodes.DUP);
        mv.visitLdcInsn("Unknown property \"%s\".");
        mv.visitInsn(Opcodes.ICONST_1);
        mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
        mv.visitInsn(Opcodes.DUP);
        mv.visitInsn(Opcodes.ICONST_0);
        mv.visitVarInsn(Opcodes.ALOAD, locVarK);
        mv.visitInsn(Opcodes.AASTORE);
        mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/String", "format",
                "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;");
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(IllegalArgumentException.class), "<init>",
                "(Ljava/lang/String;)V");
        mv.visitInsn(Opcodes.ATHROW);

        mv.visitLabel(lCatch);
        mv.visitInsn(Opcodes.POP); // pop the exception object (not needed)
        mv.visitTypeInsn(Opcodes.NEW, Type.getInternalName(IllegalArgumentException.class));
        mv.visitInsn(Opcodes.DUP);
        mv.visitLdcInsn("Incompatible type for property \"%s\". Got %s.");
        mv.visitInsn(Opcodes.ICONST_2);
        mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object");
        mv.visitInsn(Opcodes.DUP);
        mv.visitInsn(Opcodes.ICONST_0);
        mv.visitVarInsn(Opcodes.ALOAD, locVarK);
        mv.visitInsn(Opcodes.AASTORE);
        mv.visitInsn(Opcodes.DUP);
        mv.visitInsn(Opcodes.ICONST_1);
        mv.visitVarInsn(Opcodes.ALOAD, locVarV);
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Object", "getClass", "()Ljava/lang/Class;");
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/Class", "getName", "()Ljava/lang/String;");
        mv.visitInsn(Opcodes.AASTORE);
        mv.visitMethodInsn(Opcodes.INVOKESTATIC, "java/lang/String", "format",
                "(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;");
        mv.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(IllegalArgumentException.class), "<init>",
                "(Ljava/lang/String;)V");
        mv.visitInsn(Opcodes.ATHROW);

        mv.visitLabel(lEndIf);
        mv.visitVarInsn(Opcodes.ALOAD, locVarP);
        mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "org/actorsguildframework/Props", "tail",
                "()Lorg/actorsguildframework/Props;");
        mv.visitVarInsn(Opcodes.ASTORE, locVarP);

        mv.visitLabel(lEndWhile);
        mv.visitVarInsn(Opcodes.ALOAD, locVarP);
        mv.visitJumpInsn(Opcodes.IFNONNULL, lWhileBody);

        // write local variables back into properties 
        varCount = 0;
        for (PropertyDescriptor pd : localVarProperties) {
            Type pt = Type.getType(pd.getPropertyClass());
            mv.visitVarInsn(Opcodes.ALOAD, locVarThis);
            if (pd.getPropertySource() == PropertySource.ABSTRACT_METHOD) {
                mv.visitVarInsn(pt.getOpcode(Opcodes.ILOAD), locVarPropertiesOffset + varCount);
                mv.visitFieldInsn(Opcodes.PUTFIELD, classNameInternal,
                        String.format(PROP_FIELD_NAME_TEMPLATE, pd.getName()), pt.getDescriptor());
            } else if (pd.getPropertySource() == PropertySource.USER_WRITTEN) {
                mv.visitVarInsn(pt.getOpcode(Opcodes.ILOAD), locVarPropertiesOffset + varCount);
                mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, classNameInternal, pd.getSetter().getName(),
                        Type.getMethodDescriptor(pd.getSetter()));
            } else
                throw new RuntimeException("Internal error");
            varCount += pt.getSize();
        }

        // if bean is thread-safe, publish all writes now
        if (bcd.isThreadSafe()) {
            mv.visitVarInsn(Opcodes.ALOAD, locVarThis);
            mv.visitInsn(Opcodes.DUP);
            mv.visitInsn(Opcodes.MONITORENTER);
            mv.visitInsn(Opcodes.MONITOREXIT);
        }

        mv.visitInsn(Opcodes.RETURN);
        Label lEnd = new Label();
        mv.visitLabel(lEnd);

        mv.visitLocalVariable("this", classNameDescriptor, null, lBegin, lEnd, locVarThis);
        mv.visitLocalVariable("controller", "Lorg/actorsguildframework/internal/Controller;", null, lBegin, lEnd,
                locVarController);
        mv.visitLocalVariable("props", "Lorg/actorsguildframework/Props;", null, lBegin, lEnd, locVarProps);
        varCount = 0;
        for (PropertyDescriptor pd : localVarProperties) {
            Type pt = Type.getType(pd.getPropertyClass());
            mv.visitLocalVariable("__" + pd.getName(), pt.getDescriptor(),
                    GenericTypeHelper.getSignature(pd.getPropertyType()), lPropertyInit, lEnd,
                    locVarPropertiesOffset + varCount);
            varCount += pt.getSize();
        }
        mv.visitLocalVariable("p", "Lorg/actorsguildframework/Props;", null, lPropertyInit, lEnd, locVarP);
        mv.visitLocalVariable("k", "Ljava/lang/String;", null, lWhile, lEndWhile, locVarK);
        mv.visitLocalVariable("v", "Ljava/lang/Object;", null, lWhile, lEndWhile, locVarV);
        mv.visitMaxs(0, 0);
        mv.visitEnd();
    }

}