Java tutorial
/* * 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(); } }