mt.swift.Deserializer.java Source code

Java tutorial

Introduction

Here is the source code for mt.swift.Deserializer.java

Source

/**
 *  Copyright 2008 Martin Traverso
 *
 *  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 mt.swift;

import com.facebook.thrift.TException;
import com.facebook.thrift.protocol.TProtocol;
import mt.swift.model.BasicType;
import mt.swift.model.Field;
import mt.swift.model.ListType;
import mt.swift.model.MapType;
import mt.swift.model.SetType;
import mt.swift.model.StructureType;
import mt.swift.model.Type;
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 static org.objectweb.asm.Opcodes.*;
import org.objectweb.asm.util.CheckClassAdapter;
import org.objectweb.asm.util.TraceClassVisitor;

import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * A dynamic deserializer for Thrift structures.
 *
 * This class is capable of deserializing a Thrift-serialized stream into a Map or Javabean, according to a
 * user-specified Thrift schema definition.
 *
 */
public class Deserializer {
    private final SchemalessDeserializer schemalessDeserializer = new SchemalessDeserializer();

    private Map<String, StructureType> types = new ConcurrentHashMap<String, StructureType>();
    private Map<String, Class<?>> classes = new ConcurrentHashMap<String, Class<?>>();

    private Map<String, StructureDeserializer<?>> deserializers = new ConcurrentHashMap<String, StructureDeserializer<?>>();

    private boolean debug = false;

    public void setDebug(boolean debug) {
        this.debug = debug;
    }

    /**
     * Bind the given Thrift schema to the provided Javabean-compatible class
     *
     * @param type the thrift schema
     * @param clazz the class of the Javabean
     */
    public void bind(StructureType type, Class clazz) {
        types.put(type.getName(), type);
        classes.put(type.getName(), clazz);
    }

    /**
     * Bind the given Thrift schema to a generic Map.
     *
     * @param type the thrift schema
     */
    public void bindToMap(StructureType type) {
        types.put(type.getName(), type);
        classes.put(type.getName(), HashMap.class);
    }

    /**
     * Deserialize the specified structure from the given protocol
     *
     * @param name the name of the structure type to read from the protocol
     * @param protocol the protocol
     * @return a map or javabean corresponding to the deserialized object
     * @throws TException if an error occurs during deserialization
     */
    public <T> T deserialize(String name, TProtocol protocol) throws TException {
        StructureDeserializer<T> deserializer = (StructureDeserializer<T>) deserializers.get(name);
        Class<T> clazz = (Class<T>) classes.get(name);

        if (clazz == null) {
            deserializer = (StructureDeserializer<T>) schemalessDeserializer;
        }

        // construct deserializer
        if (deserializer == null) {
            StructureType type = types.get(name);
            deserializer = compileDeserializer(type, clazz);
            deserializers.put(name, deserializer);
        }

        return deserializer.deserialize(this, protocol);
    }

    private AtomicInteger sequence = new AtomicInteger();

    private <T> StructureDeserializer<T> compileDeserializer(StructureType type, Class<T> clazz) {
        ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES); // TODO: compute this ourselves?
        ClassVisitor writer = classWriter;

        if (debug) {
            writer = new CheckClassAdapter(classWriter);
            //         writer = new TraceClassVisitor(writer, new PrintWriter(System.out));
        }

        String targetClassName = Util.getInternalName(clazz);
        String deserializerClassName = "mt/swift/generated/Deserializer" + clazz.getSimpleName() + "_"
                + sequence.incrementAndGet();

        // class metadata
        writer.visit(V1_6, ACC_PUBLIC + ACC_SUPER, deserializerClassName,
                "Ljava/lang/Object;L" + Util.getInternalName(StructureDeserializer.class) + "<L" + targetClassName
                        + ";>;",
                "java/lang/Object", new String[] { Util.getInternalName(StructureDeserializer.class) });

        compileConstructor(writer);
        compileDeserializeMethod(type, writer, targetClassName, clazz);
        compileBridgeMethod(writer, targetClassName, deserializerClassName);

        writer.visitEnd();

        if (debug) {
            ClassReader reader = new ClassReader(classWriter.toByteArray());
            reader.accept(new TraceClassVisitor(new PrintWriter(System.out)), 0);
        }

        ByteArrayClassLoader loader = new ByteArrayClassLoader();
        try {
            return (StructureDeserializer<T>) loader
                    .defineClass(deserializerClassName.replace('/', '.'), classWriter.toByteArray()).newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        }
    }

    private void compileBridgeMethod(ClassVisitor writer, String targetClassName, String deserializerClassName) {
        // this method is needed to support the generics-based call 
        MethodVisitor syntheticMethodVisitor = writer.visitMethod(ACC_PUBLIC + ACC_BRIDGE + ACC_SYNTHETIC,
                "deserialize",
                "(L" + org.objectweb.asm.Type.getInternalName(Deserializer.class)
                        + ";Lcom/facebook/thrift/protocol/TProtocol;)Ljava/lang/Object;",
                null, new String[] { "com/facebook/thrift/TException" });
        syntheticMethodVisitor.visitCode();
        syntheticMethodVisitor.visitVarInsn(ALOAD, 0);
        syntheticMethodVisitor.visitVarInsn(ALOAD, 1);
        syntheticMethodVisitor.visitVarInsn(ALOAD, 2);
        syntheticMethodVisitor.visitMethodInsn(INVOKEVIRTUAL, deserializerClassName, "deserialize",
                "(L" + Util.getInternalName(Deserializer.class) + ";Lcom/facebook/thrift/protocol/TProtocol;)L"
                        + targetClassName + ";");
        syntheticMethodVisitor.visitInsn(ARETURN);
        syntheticMethodVisitor.visitMaxs(3, 3);
        syntheticMethodVisitor.visitEnd();
    }

    private void compileDeserializeMethod(StructureType type, ClassVisitor writer, String targetClassName,
            Class targetClass) {
        MethodVisitor methodVisitor = writer.visitMethod(
                ACC_PUBLIC, "deserialize", "(L" + Util.getInternalName(Deserializer.class)
                        + ";Lcom/facebook/thrift/protocol/TProtocol;)L" + targetClassName + ";",
                null, new String[] { "com/facebook/thrift/TException" });

        FrameRegisterManager context = new FrameRegisterManager();
        context.bindSlot("this", 0);
        context.bindSlot("deserializer", 1);
        context.bindSlot("protocol", 2);
        context.newSlot("target");
        context.newSlot("tfield");

        methodVisitor.visitCode();

        // <target> result = new <target>()
        methodVisitor.visitTypeInsn(NEW, targetClassName);
        methodVisitor.visitInsn(DUP);
        methodVisitor.visitMethodInsn(INVOKESPECIAL, targetClassName, "<init>", "()V");
        methodVisitor.visitVarInsn(ASTORE, context.getSlot("target"));

        // protocol.readStructBegin()
        methodVisitor.visitVarInsn(ALOAD, context.getSlot("protocol"));
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "com/facebook/thrift/protocol/TProtocol", "readStructBegin",
                "()Lcom/facebook/thrift/protocol/TStruct;");
        methodVisitor.visitInsn(POP); // discard return value

        // while (true)
        Label whileLabel = new Label();
        methodVisitor.visitLabel(whileLabel);

        // TField tfield = protocol.readFieldBegin()
        methodVisitor.visitVarInsn(ALOAD, context.getSlot("protocol"));
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "com/facebook/thrift/protocol/TProtocol", "readFieldBegin",
                "()Lcom/facebook/thrift/protocol/TField;");
        methodVisitor.visitVarInsn(ASTORE, context.getSlot("tfield"));

        // tfield.type
        methodVisitor.visitVarInsn(ALOAD, context.getSlot("tfield"));
        methodVisitor.visitFieldInsn(GETFIELD, "com/facebook/thrift/protocol/TField", "type", "B");

        methodVisitor.visitFieldInsn(GETSTATIC, "com/facebook/thrift/protocol/TType", "STOP", "B");

        // if (tfield.type == TType.STOP) { break; }
        Label endWhile = new Label();
        methodVisitor.visitJumpInsn(IF_ICMPEQ, endWhile);

        // tfield.id
        methodVisitor.visitVarInsn(ALOAD, context.getSlot("tfield"));
        methodVisitor.visitFieldInsn(GETFIELD, "com/facebook/thrift/protocol/TField", "id", "S");

        List<Field> fields = new ArrayList<Field>(type.getFields());
        int[] ids = new int[fields.size()];
        Label[] labels = new Label[fields.size()];
        for (int i = 0; i < fields.size(); ++i) {
            ids[i] = fields.get(i).getId();
            labels[i] = new Label();
        }

        Label fieldSkipped = new Label();

        methodVisitor.visitLookupSwitchInsn(fieldSkipped, ids, labels);

        for (int i = 0; i < fields.size(); ++i) {
            Field field = fields.get(i);

            methodVisitor.visitLabel(labels[i]);

            // if (tfield.type == ###)
            methodVisitor.visitVarInsn(ALOAD, context.getSlot("tfield"));
            methodVisitor.visitFieldInsn(GETFIELD, "com/facebook/thrift/protocol/TField", "type", "B");
            methodVisitor.visitIntInsn(BIPUSH, field.getType().getTType());
            methodVisitor.visitJumpInsn(IF_ICMPNE, fieldSkipped);

            methodVisitor.visitVarInsn(ALOAD, context.getSlot("target"));
            if (Map.class.isAssignableFrom(targetClass)) {
                methodVisitor.visitLdcInsn(field.getName());
                generateReadElement(methodVisitor, context, field.getType());
                generateAddToMap(targetClassName, methodVisitor, context, field);
            } else {
                generateReadElement(methodVisitor, context, field.getType());
                generateSetTargetField(targetClassName, methodVisitor, context, field);
            }

            methodVisitor.visitJumpInsn(GOTO, whileLabel);
        }

        methodVisitor.visitLabel(fieldSkipped);
        methodVisitor.visitVarInsn(ALOAD, context.getSlot("protocol"));
        methodVisitor.visitVarInsn(ALOAD, context.getSlot("tfield"));
        methodVisitor.visitFieldInsn(GETFIELD, "com/facebook/thrift/protocol/TField", "type", "B");
        methodVisitor.visitMethodInsn(INVOKESTATIC, "com/facebook/thrift/protocol/TProtocolUtil", "skip",
                "(Lcom/facebook/thrift/protocol/TProtocol;B)V");

        // end while
        methodVisitor.visitJumpInsn(GOTO, whileLabel);

        methodVisitor.visitLabel(endWhile);

        // protocol.readStructEnd()
        methodVisitor.visitVarInsn(ALOAD, context.getSlot("protocol"));
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "com/facebook/thrift/protocol/TProtocol", "readStructEnd",
                "()V");

        // return result
        methodVisitor.visitVarInsn(ALOAD, context.getSlot("target"));
        methodVisitor.visitInsn(ARETURN);

        methodVisitor.visitMaxs(1, 1); // TODO: what should these be?
        methodVisitor.visitEnd();
    }

    private void compileConstructor(ClassVisitor writer) {
        // constructor
        MethodVisitor constructorVisitor = writer.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
        constructorVisitor.visitCode();
        constructorVisitor.visitVarInsn(ALOAD, 0);
        constructorVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V");
        constructorVisitor.visitInsn(RETURN);
        constructorVisitor.visitMaxs(1, 1);
        constructorVisitor.visitEnd();
    }

    private void generateAddToMap(String targetClassName, MethodVisitor visitor, FrameRegisterManager context,
            Field field) {
        generateConvertToObject(visitor, field.getType());
        visitor.visitMethodInsn(INVOKEVIRTUAL, targetClassName, "put",
                "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
        visitor.visitInsn(POP);
    }

    // TODO: autoboxing support: setXXX(Integer) vs setXXX(int)
    private void generateSetTargetField(String targetClassName, MethodVisitor methodVisitor,
            FrameRegisterManager context, Field field) {
        String setter = "set" + Util.toCamelCase(field.getName());

        if (field.getType() == BasicType.BOOLEAN) {
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, targetClassName, setter, "(Z)V");
        } else if (field.getType() == BasicType.BYTE) {
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, targetClassName, setter, "(B)V");
        } else if (field.getType() == BasicType.I16) {
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, targetClassName, setter, "(S)V");
        } else if (field.getType() == BasicType.I32) {
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, targetClassName, setter, "(I)V");
        } else if (field.getType() == BasicType.I64) {
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, targetClassName, setter, "(J)V");
        } else if (field.getType() == BasicType.DOUBLE) {
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, targetClassName, setter, "(D)V");
        } else if (field.getType() == BasicType.BINARY) {
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, targetClassName, setter, "([B)V");
        } else if (field.getType() == BasicType.STRING) {
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, targetClassName, setter, "(Ljava/lang/String;)V");
        } else if (field.getType() instanceof StructureType) {
            Class childClass = classes.get(((StructureType) field.getType()).getName());
            methodVisitor.visitTypeInsn(CHECKCAST, Util.getInternalName(childClass));
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, targetClassName, setter,
                    "(L" + Util.getInternalName(childClass) + ";)V");
        } else if (field.getType() instanceof ListType) {
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, targetClassName, setter,
                    "(L" + Util.getInternalName(java.util.List.class) + ";)V");
        } else if (field.getType() instanceof SetType) {
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, targetClassName, setter,
                    "(L" + Util.getInternalName(java.util.Set.class) + ";)V");

        } else if (field.getType() instanceof MapType) {
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, targetClassName, setter,
                    "(L" + Util.getInternalName(java.util.Map.class) + ";)V");
        }

    }

    // leaves result in stack(0)
    private void generateReadList(MethodVisitor methodVisitor, FrameRegisterManager context, ListType listType) {
        // protocol.readListBegin()
        int tlistSizeLocal = context.newAnonymousSlot();
        int loopCounterLocal = context.newAnonymousSlot();

        methodVisitor.visitVarInsn(ALOAD, context.getSlot("protocol"));
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "com/facebook/thrift/protocol/TProtocol", "readListBegin",
                "()Lcom/facebook/thrift/protocol/TList;");
        methodVisitor.visitFieldInsn(GETFIELD, "com/facebook/thrift/protocol/TList", "size", "I");
        methodVisitor.visitVarInsn(ISTORE, tlistSizeLocal);

        // result = new ArrayList(tlist.size)
        methodVisitor.visitTypeInsn(NEW, "java/util/ArrayList");
        methodVisitor.visitInsn(DUP);
        methodVisitor.visitVarInsn(ILOAD, tlistSizeLocal);
        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/util/ArrayList", "<init>", "(I)V");

        // i = 0
        methodVisitor.visitInsn(ICONST_0);
        methodVisitor.visitVarInsn(ISTORE, loopCounterLocal); // #4 = loop counter

        Label done = new Label();
        Label loop = new Label();
        methodVisitor.visitLabel(loop);
        methodVisitor.visitVarInsn(ILOAD, loopCounterLocal);
        methodVisitor.visitVarInsn(ILOAD, tlistSizeLocal);
        methodVisitor.visitJumpInsn(IF_ICMPGE, done);

        methodVisitor.visitInsn(DUP); // ArrayList

        generateReadElement(methodVisitor, context, listType.getValueType());
        generateConvertToObject(methodVisitor, listType.getValueType());

        // entry is left on stack(0). Add to list
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/util/ArrayList", "add", "(Ljava/lang/Object;)Z");
        methodVisitor.visitInsn(POP);

        methodVisitor.visitIincInsn(loopCounterLocal, 1);
        methodVisitor.visitJumpInsn(GOTO, loop);

        methodVisitor.visitLabel(done);
        methodVisitor.visitVarInsn(ALOAD, context.getSlot("protocol"));
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "com/facebook/thrift/protocol/TProtocol", "readListEnd",
                "()V");

        context.release(tlistSizeLocal);
        context.release(loopCounterLocal);
    }

    private void generateConvertToObject(MethodVisitor methodVisitor, Type type) {
        if (type == BasicType.BOOLEAN) {
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;");
        } else if (type == BasicType.BYTE) {
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;");
        } else if (type == BasicType.I16) {
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;");
        } else if (type == BasicType.I32) {
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;");
        } else if (type == BasicType.I64) {
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;");
        } else if (type == BasicType.DOUBLE) {
            methodVisitor.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;");
        }
    }

    private void generateReadElement(MethodVisitor methodVisitor, FrameRegisterManager context, Type type) {
        if (type == BasicType.BOOLEAN) {
            methodVisitor.visitVarInsn(ALOAD, context.getSlot("protocol"));
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "com/facebook/thrift/protocol/TProtocol", "readBool",
                    "()Z");
        } else if (type == BasicType.BYTE) {
            methodVisitor.visitVarInsn(ALOAD, context.getSlot("protocol"));
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "com/facebook/thrift/protocol/TProtocol", "readByte",
                    "()B");
        } else if (type == BasicType.I16) {
            methodVisitor.visitVarInsn(ALOAD, context.getSlot("protocol"));
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "com/facebook/thrift/protocol/TProtocol", "readI16",
                    "()S");
        } else if (type == BasicType.I32) {
            methodVisitor.visitVarInsn(ALOAD, context.getSlot("protocol"));
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "com/facebook/thrift/protocol/TProtocol", "readI32",
                    "()I");
        } else if (type == BasicType.I64) {
            methodVisitor.visitVarInsn(ALOAD, context.getSlot("protocol"));
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "com/facebook/thrift/protocol/TProtocol", "readI64",
                    "()J");
        } else if (type == BasicType.DOUBLE) {
            methodVisitor.visitVarInsn(ALOAD, context.getSlot("protocol"));
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "com/facebook/thrift/protocol/TProtocol", "readDouble",
                    "()D");
        } else if (type == BasicType.BINARY) {
            methodVisitor.visitVarInsn(ALOAD, context.getSlot("protocol"));
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "com/facebook/thrift/protocol/TProtocol", "readBinary",
                    "()[B");
        } else if (type == BasicType.STRING) {
            methodVisitor.visitVarInsn(ALOAD, context.getSlot("protocol"));
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "com/facebook/thrift/protocol/TProtocol", "readString",
                    "()Ljava/lang/String;");
        } else if (type instanceof StructureType) {
            StructureType structureType = (StructureType) type;

            methodVisitor.visitVarInsn(ALOAD, context.getSlot("deserializer"));
            methodVisitor.visitLdcInsn(structureType.getName());
            methodVisitor.visitVarInsn(ALOAD, context.getSlot("protocol"));
            methodVisitor.visitMethodInsn(INVOKEVIRTUAL, Util.getInternalName(Deserializer.class), "deserialize",
                    "(Ljava/lang/String;Lcom/facebook/thrift/protocol/TProtocol;)Ljava/lang/Object;");
        } else if (type instanceof ListType) {
            ListType listType = (ListType) type;
            generateReadList(methodVisitor, context, listType);
        } else if (type instanceof SetType) {
            SetType setType = (SetType) type;
            generateReadSet(methodVisitor, context, setType);
        } else if (type instanceof MapType) {
            MapType mapType = (MapType) type;
            generateReadMap(methodVisitor, context, mapType);
        }
    }

    private void generateReadSet(MethodVisitor methodVisitor, FrameRegisterManager context, SetType type) {
        // protocol.readListBegin()
        int tsetSizeLocal = context.newAnonymousSlot();
        int loopCounterLocal = context.newAnonymousSlot();

        methodVisitor.visitVarInsn(ALOAD, context.getSlot("protocol"));
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "com/facebook/thrift/protocol/TProtocol", "readSetBegin",
                "()Lcom/facebook/thrift/protocol/TSet;");
        methodVisitor.visitFieldInsn(GETFIELD, "com/facebook/thrift/protocol/TSet", "size", "I");
        methodVisitor.visitVarInsn(ISTORE, tsetSizeLocal);

        // result = new ArrayList(tlist.size)
        methodVisitor.visitTypeInsn(NEW, "java/util/HashSet");
        methodVisitor.visitInsn(DUP);
        methodVisitor.visitVarInsn(ILOAD, tsetSizeLocal);
        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/util/HashSet", "<init>", "(I)V");

        // i = 0
        methodVisitor.visitInsn(ICONST_0);
        methodVisitor.visitVarInsn(ISTORE, loopCounterLocal); // #4 = loop counter

        Label done = new Label();
        Label loop = new Label();
        methodVisitor.visitLabel(loop);
        methodVisitor.visitVarInsn(ILOAD, loopCounterLocal);
        methodVisitor.visitVarInsn(ILOAD, tsetSizeLocal);
        methodVisitor.visitJumpInsn(IF_ICMPGE, done);

        methodVisitor.visitInsn(DUP); // ArrayList

        generateReadElement(methodVisitor, context, type.getValueType());
        generateConvertToObject(methodVisitor, type.getValueType());

        // entry is left on stack(0). Add to list
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/util/HashSet", "add", "(Ljava/lang/Object;)Z");
        methodVisitor.visitInsn(POP);

        methodVisitor.visitIincInsn(loopCounterLocal, 1);
        methodVisitor.visitJumpInsn(GOTO, loop);

        methodVisitor.visitLabel(done);
        methodVisitor.visitVarInsn(ALOAD, context.getSlot("protocol"));
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "com/facebook/thrift/protocol/TProtocol", "readSetEnd", "()V");

        context.release(tsetSizeLocal);
        context.release(loopCounterLocal);
    }

    private void generateReadMap(MethodVisitor methodVisitor, FrameRegisterManager context, MapType type) {
        // protocol.readListBegin()
        int tmapSizeLocal = context.newAnonymousSlot();
        int loopCounterLocal = context.newAnonymousSlot();

        methodVisitor.visitVarInsn(ALOAD, context.getSlot("protocol"));
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "com/facebook/thrift/protocol/TProtocol", "readMapBegin",
                "()Lcom/facebook/thrift/protocol/TMap;");
        methodVisitor.visitFieldInsn(GETFIELD, "com/facebook/thrift/protocol/TMap", "size", "I");
        methodVisitor.visitVarInsn(ISTORE, tmapSizeLocal);

        // result = new ArrayList(tlist.size)
        methodVisitor.visitTypeInsn(NEW, "java/util/LinkedHashMap");
        methodVisitor.visitInsn(DUP);
        methodVisitor.visitVarInsn(ILOAD, tmapSizeLocal);
        methodVisitor.visitInsn(ICONST_2); // allocate 2 * tmap.size to avoid rehashing while building map
        methodVisitor.visitInsn(IMUL);
        methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/util/LinkedHashMap", "<init>", "(I)V");

        // i = 0
        methodVisitor.visitInsn(ICONST_0);
        methodVisitor.visitVarInsn(ISTORE, loopCounterLocal); // #4 = loop counter

        Label done = new Label();
        Label loop = new Label();
        methodVisitor.visitLabel(loop);
        methodVisitor.visitVarInsn(ILOAD, loopCounterLocal);
        methodVisitor.visitVarInsn(ILOAD, tmapSizeLocal);
        methodVisitor.visitJumpInsn(IF_ICMPGE, done);

        methodVisitor.visitInsn(DUP); // Map

        generateReadElement(methodVisitor, context, type.getKeyType());
        generateConvertToObject(methodVisitor, type.getKeyType());
        generateReadElement(methodVisitor, context, type.getValueType());
        generateConvertToObject(methodVisitor, type.getValueType());

        // entry is left on stack(0). Add to list
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/util/LinkedHashMap", "put",
                "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;");
        methodVisitor.visitInsn(POP);

        methodVisitor.visitIincInsn(loopCounterLocal, 1);
        methodVisitor.visitJumpInsn(GOTO, loop);

        methodVisitor.visitLabel(done);
        methodVisitor.visitVarInsn(ALOAD, context.getSlot("protocol"));
        methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "com/facebook/thrift/protocol/TProtocol", "readMapEnd", "()V");

        context.release(tmapSizeLocal);
        context.release(loopCounterLocal);
    }

}