blob: f7effdeffa6bc5b4ab55392b9d4139b760d2a49c [file] [log] [blame]
// 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.schema.ColumnModel;
import com.google.gwtorm.schema.Util;
import com.google.gwtorm.schema.java.JavaColumnModel;
import com.google.gwtorm.server.CodeGenSupport;
import com.google.gwtorm.server.GeneratedClassLoader;
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.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.preinc();
cgs.push(f.getColumnID());
cgs.doinc("computeTagSize", Type.INT_TYPE);
cgs.preinc();
mv.visitVarInsn(ILOAD, msgVar);
cgs.doinc("computeRawVarint32Size", Type.INT_TYPE);
cgs.preinc();
mv.visitVarInsn(ILOAD, msgVar);
cgs.doinc();
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.preinc();
cgs.push(f.getColumnID());
cgs.pushFieldValue();
cgs.doinc("computeBoolSize", Type.INT_TYPE, Type.BOOLEAN_TYPE);
break;
case Type.CHAR:
cgs.preinc();
cgs.push(f.getColumnID());
cgs.pushFieldValue();
cgs.doinc("computeUInt32Size", Type.INT_TYPE, Type.INT_TYPE);
break;
case Type.BYTE:
case Type.SHORT:
case Type.INT:
cgs.preinc();
cgs.push(f.getColumnID());
cgs.pushFieldValue();
cgs.doinc("computeSInt32Size", Type.INT_TYPE, Type.INT_TYPE);
break;
case Type.FLOAT:
cgs.preinc();
cgs.push(f.getColumnID());
cgs.pushFieldValue();
cgs.doinc("computeFloatSize", Type.INT_TYPE, Type.FLOAT_TYPE);
break;
case Type.DOUBLE:
cgs.preinc();
cgs.push(f.getColumnID());
cgs.pushFieldValue();
cgs.doinc("computeDoubleSize", Type.INT_TYPE, Type.DOUBLE_TYPE);
break;
case Type.LONG:
cgs.preinc();
cgs.push(f.getColumnID());
cgs.pushFieldValue();
cgs.doinc("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.preinc();
cgs.push(f.getColumnID());
cgs.doinc("computeTagSize", Type.INT_TYPE);
cgs.preinc();
cgs.pushFieldValue();
mv.visitInsn(ARRAYLENGTH);
cgs.doinc("computeRawVarint32Size", Type.INT_TYPE);
cgs.preinc();
cgs.pushFieldValue();
mv.visitInsn(ARRAYLENGTH);
cgs.doinc();
} else if (f.getPrimitiveType() == String.class) {
cgs.preinc();
cgs.push(f.getColumnID());
cgs.pushFieldValue();
cgs.doinc("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.preinc();
cgs.push(f.getColumnID());
cgs.pushFieldValue();
String tsType = Type.getType(f.getPrimitiveType()).getInternalName();
mv.visitMethodInsn(INVOKEVIRTUAL, tsType, "getTime", Type
.getMethodDescriptor(Type.LONG_TYPE, new Type[] {}));
cgs.doinc("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(
Type.VOID_TYPE, new Type[] {object, codedOutputStream}), 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);
cgs.pushCodedOutputStream();
mv.visitMethodInsn(INVOKEVIRTUAL, codedOutputStream.getInternalName(),
"flush", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {}));
mv.visitInsn(RETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
private static void encodeMessage(final JavaColumnModel[] myFields,
final MethodVisitor mv, final EncodeCGS 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);
cgs.pushCodedOutputStream();
cgs.push(f.getColumnID());
cgs.push(WireFormat.FieldType.MESSAGE.getWireType());
mv.visitMethodInsn(INVOKEVIRTUAL, codedOutputStream.getInternalName(),
"writeTag", Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {
Type.INT_TYPE, Type.INT_TYPE}));
cgs.push(0);
mv.visitVarInsn(ISTORE, cgs.sizeVar);
sizeofMessage(sort(f.getNestedColumns()), mv, cgs);
cgs.pushCodedOutputStream();
mv.visitVarInsn(ILOAD, cgs.sizeVar);
mv.visitMethodInsn(INVOKEVIRTUAL, codedOutputStream.getInternalName(),
"writeRawVarint32", Type.getMethodDescriptor(Type.VOID_TYPE,
new Type[] {Type.INT_TYPE}));
encodeMessage(sort(f.getNestedColumns()), mv, cgs);
mv.visitLabel(end);
} else {
encodeScalar(mv, cgs, f);
}
}
}
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[] {Type.LONG_TYPE}));
} else {
throw new OrmException("Type " + f.getPrimitiveType()
+ " not supported for field " + f.getPathToFieldName());
}
break;
}
cgs.fieldSetEnd();
}
private static class SizeofCGS extends CodeGenSupport {
int sizeVar;
SizeofCGS(MethodVisitor method) {
super(method);
sizeVar = newLocal();
}
void doinc(String name, Type... args) {
mv.visitMethodInsn(INVOKESTATIC, codedOutputStream.getInternalName(),
name, Type.getMethodDescriptor(Type.INT_TYPE, args));
doinc();
}
void preinc() {
mv.visitVarInsn(ILOAD, sizeVar);
}
void doinc() {
mv.visitInsn(IADD);
mv.visitVarInsn(ISTORE, sizeVar);
}
@Override
public void pushEntity() {
mv.visitVarInsn(ALOAD, 1);
}
}
private static final class EncodeCGS extends SizeofCGS {
private EncodeCGS(MethodVisitor method) {
super(method);
}
void pushCodedOutputStream() {
mv.visitVarInsn(ALOAD, 2);
}
void write(String name, Type arg) {
mv.visitMethodInsn(INVOKEVIRTUAL, codedOutputStream.getInternalName(),
name, Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {
Type.INT_TYPE, arg}));
}
}
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);
}
}
}