com.google.gwtorm.protobuf.CodecGen.java Source code

Java tutorial

Introduction

Here is the source code for com.google.gwtorm.protobuf.CodecGen.java

Source

// Copyright 2009 Google Inc.
//
// 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 com.google.gwtorm.protobuf;

import com.google.gwtorm.client.Column;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.jdbc.gen.CodeGenSupport;
import com.google.gwtorm.jdbc.gen.GeneratedClassLoader;
import com.google.gwtorm.schema.ColumnModel;
import com.google.gwtorm.schema.Util;
import com.google.gwtorm.schema.java.JavaColumnModel;
import com.google.protobuf.ByteString;
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.CodedOutputStream;
import com.google.protobuf.WireFormat;

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 java.io.OutputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;

/** Generates {@link ProtobufCodec} implementations. */
class CodecGen<T> implements Opcodes {
    private static final Type string = Type.getType(String.class);
    private static final Type byteStringOutput = Type.getType(ByteString.Output.class);
    private static final Type byteString = Type.getType(ByteString.class);
    private static final Type object = Type.getType(Object.class);
    private static final Type codedOutputStream = Type.getType(CodedOutputStream.class);
    private static final Type codedInputStream = Type.getType(CodedInputStream.class);
    private final GeneratedClassLoader classLoader;
    private final Class<T> pojo;
    private final Type pojoType;

    private ClassWriter cw;
    private JavaColumnModel[] myFields;
    private String superTypeName;
    private String implClassName;
    private String implTypeName;

    public CodecGen(final GeneratedClassLoader loader, final Class<T> t) {
        classLoader = loader;
        pojo = t;
        pojoType = Type.getType(pojo);
    }

    public ProtobufCodec<T> create() throws OrmException {
        myFields = scanFields(pojo);

        init();
        implementConstructor();
        implementSizeof();
        implementEncode();
        implementDecode();
        cw.visitEnd();
        classLoader.defineClass(implClassName, cw.toByteArray());

        try {
            final Class<?> c = Class.forName(implClassName, true, classLoader);
            return cast(c.newInstance());
        } catch (InstantiationException e) {
            throw new OrmException("Cannot create new encoder", e);
        } catch (IllegalAccessException e) {
            throw new OrmException("Cannot create new encoder", e);
        } catch (ClassNotFoundException e) {
            throw new OrmException("Cannot create new encoder", e);
        }
    }

    private static JavaColumnModel[] scanFields(Class<?> in) throws OrmException {
        final Collection<JavaColumnModel> col = new ArrayList<JavaColumnModel>();
        while (in != null) {
            for (final Field f : in.getDeclaredFields()) {
                if (f.getAnnotation(Column.class) != null) {
                    col.add(new JavaColumnModel(f));
                }
            }
            in = in.getSuperclass();
        }
        return sort(col);
    }

    private static JavaColumnModel[] sort(final Collection<? extends ColumnModel> col) {
        JavaColumnModel[] out = col.toArray(new JavaColumnModel[col.size()]);
        Arrays.sort(out, new Comparator<JavaColumnModel>() {
            @Override
            public int compare(JavaColumnModel o1, JavaColumnModel o2) {
                return o1.getColumnID() - o2.getColumnID();
            }
        });
        return out;
    }

    @SuppressWarnings("unchecked")
    private static <T> ProtobufCodec<T> cast(final Object c) {
        return (ProtobufCodec<T>) c;
    }

    private void init() {
        superTypeName = Type.getInternalName(ProtobufCodec.class);
        implClassName = pojo.getName() + "_protobuf_" + Util.createRandomName();
        implTypeName = implClassName.replace('.', '/');

        cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
        cw.visit(V1_3, ACC_PUBLIC | ACC_FINAL | ACC_SUPER, implTypeName, null, superTypeName, new String[] {});
    }

    private void implementConstructor() {
        final String consName = "<init>";
        final String consDesc = Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {});
        final MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, consName, consDesc, null, null);
        mv.visitCode();

        mv.visitVarInsn(ALOAD, 0);
        mv.visitMethodInsn(INVOKESPECIAL, superTypeName, consName, consDesc);

        mv.visitInsn(RETURN);
        mv.visitMaxs(-1, -1);
        mv.visitEnd();
    }

    private void implementSizeof() throws OrmException {
        final MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "sizeof",
                Type.getMethodDescriptor(Type.INT_TYPE, new Type[] { object }), null, new String[] {});
        mv.visitCode();
        final SizeofCGS cgs = new SizeofCGS(mv);
        cgs.setEntityType(pojoType);

        mv.visitVarInsn(ALOAD, 1);
        mv.visitTypeInsn(CHECKCAST, pojoType.getInternalName());
        mv.visitVarInsn(ASTORE, 1);

        cgs.push(0);
        mv.visitVarInsn(ISTORE, cgs.sizeVar);
        sizeofMessage(myFields, mv, cgs);

        mv.visitVarInsn(ILOAD, cgs.sizeVar);
        mv.visitInsn(IRETURN);
        mv.visitMaxs(-1, -1);
        mv.visitEnd();
    }

    private static void sizeofMessage(final JavaColumnModel[] myFields, final MethodVisitor mv, final SizeofCGS cgs)
            throws OrmException {
        for (final JavaColumnModel f : myFields) {
            if (f.isNested()) {
                final Label end = new Label();
                cgs.setFieldReference(f);
                cgs.pushFieldValue();
                mv.visitJumpInsn(IFNULL, end);

                final int oldVar = cgs.sizeVar;
                final int msgVar = cgs.newLocal();
                cgs.sizeVar = msgVar;
                cgs.push(0);
                mv.visitVarInsn(ISTORE, cgs.sizeVar);

                sizeofMessage(sort(f.getNestedColumns()), mv, cgs);
                cgs.sizeVar = oldVar;

                cgs.push(f.getColumnID());
                cgs.inc("computeTagSize", Type.INT_TYPE);

                mv.visitVarInsn(ILOAD, msgVar);
                cgs.inc("computeRawVarint32Size", Type.INT_TYPE);

                mv.visitVarInsn(ILOAD, msgVar);
                cgs.inc();

                cgs.freeLocal(msgVar);
                mv.visitLabel(end);
            } else {
                sizeofScalar(mv, cgs, f);
            }
        }
    }

    private static void sizeofScalar(final MethodVisitor mv, final SizeofCGS cgs, final JavaColumnModel f)
            throws OrmException {
        cgs.setFieldReference(f);

        switch (Type.getType(f.getPrimitiveType()).getSort()) {
        case Type.BOOLEAN:
            cgs.push(f.getColumnID());
            cgs.pushFieldValue();
            cgs.inc("computeBoolSize", Type.INT_TYPE, Type.BOOLEAN_TYPE);
            break;

        case Type.CHAR:
            cgs.push(f.getColumnID());
            cgs.pushFieldValue();
            cgs.inc("computeUInt32Size", Type.INT_TYPE, Type.INT_TYPE);
            break;

        case Type.BYTE:
        case Type.SHORT:
        case Type.INT:
            cgs.push(f.getColumnID());
            cgs.pushFieldValue();
            cgs.inc("computeSInt32Size", Type.INT_TYPE, Type.INT_TYPE);
            break;

        case Type.FLOAT:
            cgs.push(f.getColumnID());
            cgs.pushFieldValue();
            cgs.inc("computeFloatSize", Type.INT_TYPE, Type.FLOAT_TYPE);
            break;

        case Type.DOUBLE:
            cgs.push(f.getColumnID());
            cgs.pushFieldValue();
            cgs.inc("computeDoubleSize", Type.INT_TYPE, Type.DOUBLE_TYPE);
            break;

        case Type.LONG:
            cgs.push(f.getColumnID());
            cgs.pushFieldValue();
            cgs.inc("computeSInt64", Type.INT_TYPE, Type.LONG_TYPE);
            break;

        case Type.ARRAY:
        case Type.OBJECT: {
            final Label end = new Label();
            cgs.pushFieldValue();
            mv.visitJumpInsn(IFNULL, end);

            if (f.getPrimitiveType() == byte[].class) {
                cgs.push(f.getColumnID());
                cgs.inc("computeTagSize", Type.INT_TYPE);

                cgs.pushFieldValue();
                mv.visitInsn(ARRAYLENGTH);
                cgs.inc("computeRawVarint32Size", Type.INT_TYPE);

                cgs.pushFieldValue();
                mv.visitInsn(ARRAYLENGTH);
                cgs.inc();

            } else if (f.getPrimitiveType() == String.class) {
                cgs.push(f.getColumnID());
                cgs.pushFieldValue();
                cgs.inc("computeStringSize", Type.INT_TYPE, string);

            } else if (f.getPrimitiveType() == java.sql.Timestamp.class
                    || f.getPrimitiveType() == java.util.Date.class
                    || f.getPrimitiveType() == java.sql.Date.class) {
                cgs.push(f.getColumnID());
                String tsType = Type.getType(f.getPrimitiveType()).getInternalName();
                mv.visitMethodInsn(INVOKEVIRTUAL, tsType, "getTime",
                        Type.getMethodDescriptor(Type.LONG_TYPE, new Type[] {}));
                cgs.inc("computeFixed64Size", Type.INT_TYPE, Type.LONG_TYPE);

            } else {
                throw new OrmException(
                        "Type " + f.getPrimitiveType() + " not supported for field " + f.getPathToFieldName());
            }
            mv.visitLabel(end);
            break;
        }

        default:
            throw new OrmException(
                    "Type " + f.getPrimitiveType() + " not supported for field " + f.getPathToFieldName());
        }
    }

    private void implementEncode() throws OrmException {
        final MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "encode",
                Type.getMethodDescriptor(byteString, new Type[] { object }), null, new String[] {});
        mv.visitCode();
        final EncodeCGS cgs = new EncodeCGS(mv);
        cgs.setEntityType(pojoType);

        mv.visitVarInsn(ALOAD, 1);
        mv.visitTypeInsn(CHECKCAST, pojoType.getInternalName());
        mv.visitVarInsn(ASTORE, 1);

        encodeMessage(myFields, mv, cgs);

        mv.visitInsn(ARETURN);
        mv.visitMaxs(-1, -1);
        mv.visitEnd();
    }

    private static void encodeMessage(final JavaColumnModel[] myFields, final MethodVisitor mv, final EncodeCGS cgs)
            throws OrmException {
        final int oldVar = cgs.codedOutputStreamVar;
        cgs.codedOutputStreamVar = cgs.newLocal();

        final int strVar = cgs.newLocal();
        mv.visitMethodInsn(INVOKESTATIC, byteString.getInternalName(), "newOutput",
                Type.getMethodDescriptor(byteStringOutput, new Type[] {}));
        mv.visitVarInsn(ASTORE, strVar);

        mv.visitVarInsn(ALOAD, strVar);
        mv.visitMethodInsn(INVOKESTATIC, codedOutputStream.getInternalName(), "newInstance",
                Type.getMethodDescriptor(codedOutputStream, new Type[] { Type.getType(OutputStream.class) }));
        mv.visitVarInsn(ASTORE, cgs.codedOutputStreamVar);

        for (final JavaColumnModel f : myFields) {
            if (f.isNested()) {
                final Label end = new Label();
                cgs.setFieldReference(f);
                cgs.pushFieldValue();
                mv.visitJumpInsn(IFNULL, end);

                final int v = cgs.newLocal();
                encodeMessage(sort(f.getNestedColumns()), mv, cgs);
                mv.visitVarInsn(ASTORE, v);

                mv.visitVarInsn(ALOAD, v);
                mv.visitMethodInsn(INVOKEVIRTUAL, byteString.getInternalName(), "size",
                        Type.getMethodDescriptor(Type.INT_TYPE, new Type[] {}));
                mv.visitJumpInsn(IFEQ, end);

                cgs.pushCodedOutputStream();
                cgs.push(f.getColumnID());
                mv.visitVarInsn(ALOAD, v);
                cgs.write("writeBytes", byteString);

                cgs.freeLocal(v);
                mv.visitLabel(end);
            } else {
                encodeScalar(mv, cgs, f);
            }
        }

        cgs.pushCodedOutputStream();
        mv.visitMethodInsn(INVOKEVIRTUAL, codedOutputStream.getInternalName(), "flush",
                Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {}));

        cgs.freeLocal(cgs.codedOutputStreamVar);
        cgs.codedOutputStreamVar = oldVar;

        mv.visitVarInsn(ALOAD, strVar);
        mv.visitMethodInsn(INVOKEVIRTUAL, byteStringOutput.getInternalName(), "toByteString",
                Type.getMethodDescriptor(byteString, new Type[] {}));
        cgs.freeLocal(strVar);
    }

    private static void encodeScalar(final MethodVisitor mv, final EncodeCGS cgs, final JavaColumnModel f)
            throws OrmException {
        cgs.setFieldReference(f);

        switch (Type.getType(f.getPrimitiveType()).getSort()) {
        case Type.BOOLEAN:
            cgs.pushCodedOutputStream();
            cgs.push(f.getColumnID());
            cgs.pushFieldValue();
            cgs.write("writeBool", Type.BOOLEAN_TYPE);
            break;

        case Type.CHAR:
            cgs.pushCodedOutputStream();
            cgs.push(f.getColumnID());
            cgs.pushFieldValue();
            cgs.write("writeUInt32", Type.INT_TYPE);
            break;

        case Type.BYTE:
        case Type.SHORT:
        case Type.INT:
            cgs.pushCodedOutputStream();
            cgs.push(f.getColumnID());
            cgs.pushFieldValue();
            cgs.write("writeSInt32", Type.INT_TYPE);
            break;

        case Type.FLOAT:
            cgs.pushCodedOutputStream();
            cgs.push(f.getColumnID());
            cgs.pushFieldValue();
            cgs.write("writeFloat", Type.FLOAT_TYPE);
            break;

        case Type.DOUBLE:
            cgs.pushCodedOutputStream();
            cgs.push(f.getColumnID());
            cgs.pushFieldValue();
            cgs.write("writeDouble", Type.DOUBLE_TYPE);
            break;

        case Type.LONG:
            cgs.pushCodedOutputStream();
            cgs.push(f.getColumnID());
            cgs.pushFieldValue();
            cgs.write("writeSInt64", Type.LONG_TYPE);
            break;

        case Type.ARRAY:
        case Type.OBJECT: {
            final Label end = new Label();
            cgs.pushFieldValue();
            mv.visitJumpInsn(IFNULL, end);

            if (f.getPrimitiveType() == byte[].class) {
                cgs.pushCodedOutputStream();
                cgs.push(f.getColumnID());
                cgs.push(WireFormat.FieldType.BYTES.getWireType());
                mv.visitMethodInsn(INVOKEVIRTUAL, codedOutputStream.getInternalName(), "writeTag",
                        Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { Type.INT_TYPE, Type.INT_TYPE }));

                cgs.pushCodedOutputStream();
                cgs.pushFieldValue();
                mv.visitInsn(ARRAYLENGTH);
                mv.visitMethodInsn(INVOKEVIRTUAL, codedOutputStream.getInternalName(), "writeRawVarint32",
                        Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { Type.INT_TYPE }));

                cgs.pushCodedOutputStream();
                cgs.pushFieldValue();
                mv.visitMethodInsn(INVOKEVIRTUAL, codedOutputStream.getInternalName(), "writeRawBytes",
                        Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { Type.getType(byte[].class) }));

            } else {
                cgs.pushCodedOutputStream();
                cgs.push(f.getColumnID());
                cgs.pushFieldValue();

                if (f.getPrimitiveType() == String.class) {
                    cgs.write("writeString", string);

                } else if (f.getPrimitiveType() == java.sql.Timestamp.class
                        || f.getPrimitiveType() == java.util.Date.class
                        || f.getPrimitiveType() == java.sql.Date.class) {
                    String tsType = Type.getType(f.getPrimitiveType()).getInternalName();
                    mv.visitMethodInsn(INVOKEVIRTUAL, tsType, "getTime",
                            Type.getMethodDescriptor(Type.LONG_TYPE, new Type[] {}));
                    cgs.write("writeFixed64", Type.LONG_TYPE);

                } else {
                    throw new OrmException(
                            "Type " + f.getPrimitiveType() + " not supported for field " + f.getPathToFieldName());
                }
            }
            mv.visitLabel(end);
            break;
        }

        default:
            throw new OrmException(
                    "Type " + f.getPrimitiveType() + " not supported for field " + f.getPathToFieldName());
        }
    }

    private void implementDecode() throws OrmException {
        final Type retType = object;
        final MethodVisitor mv = cw.visitMethod(ACC_PROTECTED, "decode",
                Type.getMethodDescriptor(retType, new Type[] { codedInputStream }), null, new String[] {});
        mv.visitCode();
        final DecodeCGS cgs = new DecodeCGS(mv);

        cgs.setEntityType(pojoType);

        mv.visitTypeInsn(NEW, pojoType.getInternalName());
        mv.visitInsn(DUP);
        mv.visitMethodInsn(INVOKESPECIAL, pojoType.getInternalName(), "<init>",
                Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {}));
        mv.visitVarInsn(ASTORE, cgs.objVar);

        final int tagVar = cgs.newLocal();
        decodeMessage(myFields, mv, cgs);

        cgs.pushEntity();
        mv.visitInsn(ARETURN);
        mv.visitMaxs(-1, -1);
        mv.visitEnd();
    }

    private static void decodeMessage(final JavaColumnModel[] myFields, final MethodVisitor mv, final DecodeCGS cgs)
            throws OrmException {
        final Label nextField = new Label();
        final Label end = new Label();
        mv.visitLabel(nextField);

        // while (!ci.isAtEnd) { ...
        cgs.call("readTag", Type.INT_TYPE);
        mv.visitInsn(DUP);
        mv.visitVarInsn(ISTORE, cgs.tagVar);

        cgs.push(3);
        mv.visitInsn(IUSHR);

        final Label badField = new Label();
        final int[] caseTags = new int[1 + myFields.length];
        final Label[] caseLabels = new Label[caseTags.length];

        caseTags[0] = 0;
        caseLabels[0] = new Label();

        int gaps = 0;
        for (int i = 1; i < caseTags.length; i++) {
            caseTags[i] = myFields[i - 1].getColumnID();
            caseLabels[i] = new Label();
            gaps += caseTags[i] - (caseTags[i - 1] + 1);
        }

        if (2 * gaps / 3 <= myFields.length) {
            final int min = 0;
            final int max = caseTags[caseTags.length - 1];
            final Label[] table = new Label[max + 1];
            Arrays.fill(table, badField);
            for (int idx = 0; idx < caseTags.length; idx++) {
                table[caseTags[idx]] = caseLabels[idx];
            }
            mv.visitTableSwitchInsn(min, max, badField, table);
        } else {
            mv.visitLookupSwitchInsn(badField, caseTags, caseLabels);
        }

        mv.visitLabel(caseLabels[0]);
        mv.visitJumpInsn(GOTO, end);

        for (int idx = 1; idx < caseTags.length; idx++) {
            final JavaColumnModel f = myFields[idx - 1];
            mv.visitLabel(caseLabels[idx]);
            if (f.isNested()) {
                final Label load = new Label();
                cgs.setFieldReference(f);
                cgs.pushFieldValue();
                mv.visitJumpInsn(IFNONNULL, load);
                cgs.fieldSetBegin();
                mv.visitTypeInsn(NEW, Type.getType(f.getNestedClass()).getInternalName());
                mv.visitInsn(DUP);
                mv.visitMethodInsn(INVOKESPECIAL, Type.getType(f.getNestedClass()).getInternalName(), "<init>",
                        Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {}));
                cgs.fieldSetEnd();

                // read the length, set a new limit, decode the message, validate
                // we stopped at the end of it as expected.
                //
                mv.visitLabel(load);
                final int limitVar = cgs.newLocal();
                cgs.pushCodedInputStream();
                cgs.call("readRawVarint32", Type.INT_TYPE);
                cgs.ncallInt("pushLimit", Type.INT_TYPE);
                mv.visitVarInsn(ISTORE, limitVar);

                decodeMessage(sort(f.getNestedColumns()), mv, cgs);

                cgs.pushCodedInputStream();
                mv.visitVarInsn(ILOAD, limitVar);
                cgs.ncallInt("popLimit", Type.VOID_TYPE);
                cgs.freeLocal(limitVar);

            } else {
                decodeScalar(mv, cgs, f);
            }
            mv.visitJumpInsn(GOTO, nextField);
        }

        // default:
        mv.visitLabel(badField);
        cgs.pushCodedInputStream();
        mv.visitVarInsn(ILOAD, cgs.tagVar);
        cgs.ncallInt("skipField", Type.BOOLEAN_TYPE);
        mv.visitInsn(POP);
        mv.visitJumpInsn(GOTO, nextField);

        mv.visitLabel(end);
        cgs.pushCodedInputStream();
        cgs.push(0);
        cgs.ncallInt("checkLastTagWas", Type.VOID_TYPE);
    }

    private static void decodeScalar(final MethodVisitor mv, final DecodeCGS cgs, final JavaColumnModel f)
            throws OrmException {
        cgs.setFieldReference(f);
        cgs.fieldSetBegin();
        switch (Type.getType(f.getPrimitiveType()).getSort()) {
        case Type.BOOLEAN:
            cgs.call("readBool", Type.BOOLEAN_TYPE);
            break;

        case Type.CHAR:
            cgs.call("readUInt32", Type.INT_TYPE);
            break;

        case Type.BYTE:
        case Type.SHORT:
        case Type.INT:
            cgs.call("readSInt32", Type.INT_TYPE);
            break;

        case Type.FLOAT:
            cgs.call("readFloat", Type.FLOAT_TYPE);
            break;

        case Type.DOUBLE:
            cgs.call("readDouble", Type.DOUBLE_TYPE);
            break;

        case Type.LONG:
            cgs.call("readSInt64", Type.LONG_TYPE);
            break;

        default:
            if (f.getPrimitiveType() == byte[].class) {
                cgs.call("readBytes", byteString);
                mv.visitMethodInsn(INVOKEVIRTUAL, byteString.getInternalName(), "toByteArray",
                        Type.getMethodDescriptor(Type.getType(byte[].class), new Type[] {}));

            } else if (f.getPrimitiveType() == String.class) {
                cgs.call("readString", string);

            } else if (f.getPrimitiveType() == java.sql.Timestamp.class
                    || f.getPrimitiveType() == java.util.Date.class
                    || f.getPrimitiveType() == java.sql.Date.class) {
                String tsType = Type.getType(f.getPrimitiveType()).getInternalName();
                mv.visitTypeInsn(NEW, tsType);
                mv.visitInsn(DUP);
                cgs.call("readFixed64", Type.LONG_TYPE);
                mv.visitMethodInsn(INVOKESPECIAL, tsType, "<init>",
                        Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {}));

            } else {
                throw new OrmException(
                        "Type " + f.getPrimitiveType() + " not supported for field " + f.getPathToFieldName());
            }
            break;
        }
        cgs.fieldSetEnd();
    }

    private static final class SizeofCGS extends CodeGenSupport {
        int sizeVar;

        private SizeofCGS(MethodVisitor method) {
            super(method);
            sizeVar = newLocal();
        }

        void inc(String name, Type... args) {
            mv.visitMethodInsn(INVOKESTATIC, codedOutputStream.getInternalName(), name,
                    Type.getMethodDescriptor(Type.INT_TYPE, args));
            inc();
        }

        void inc() {
            mv.visitVarInsn(ILOAD, sizeVar);
            mv.visitInsn(IADD);
            mv.visitVarInsn(ISTORE, sizeVar);
        }

        @Override
        public void pushEntity() {
            mv.visitVarInsn(ALOAD, 1);
        }
    }

    private static final class EncodeCGS extends CodeGenSupport {
        int codedOutputStreamVar;

        private EncodeCGS(MethodVisitor method) {
            super(method);
        }

        void pushCodedOutputStream() {
            mv.visitVarInsn(ALOAD, codedOutputStreamVar);
        }

        void write(String name, Type arg) {
            mv.visitMethodInsn(INVOKEVIRTUAL, codedOutputStream.getInternalName(), name,
                    Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] { Type.INT_TYPE, arg }));
        }

        @Override
        public void pushEntity() {
            mv.visitVarInsn(ALOAD, 1);
        }
    }

    private static final class DecodeCGS extends CodeGenSupport {
        final int codedInputStreamVar = 1;
        final int objVar;
        final int tagVar;

        private DecodeCGS(MethodVisitor method) {
            super(method);
            objVar = newLocal();
            tagVar = newLocal();
        }

        void pushCodedInputStream() {
            mv.visitVarInsn(ALOAD, codedInputStreamVar);
        }

        void call(String name, Type ret) {
            pushCodedInputStream();
            mv.visitMethodInsn(INVOKEVIRTUAL, codedInputStream.getInternalName(), name,
                    Type.getMethodDescriptor(ret, new Type[] {}));
        }

        void ncallInt(String name, Type ret) {
            mv.visitMethodInsn(INVOKEVIRTUAL, codedInputStream.getInternalName(), name,
                    Type.getMethodDescriptor(ret, new Type[] { Type.INT_TYPE }));
        }

        @Override
        public void pushEntity() {
            mv.visitVarInsn(ALOAD, objVar);
        }
    }
}