blob: a726ad6ac1dd1a71623a91ea6d82dc6a3b309091 [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.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.gwtorm.server.OrmException;
import com.google.protobuf.ByteString;
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.CodedOutputStream;
import com.google.protobuf.WireFormat;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.TreeSet;
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;
/** Generates {@link ProtobufCodec} implementations. */
class CodecGen<T> implements Opcodes {
private static final Type illegalStateException = Type.getType(IllegalStateException.class);
private static final Type collection = Type.getType(java.util.Collection.class);
private static final Type iterator = Type.getType(java.util.Iterator.class);
private static final Type string = Type.getType(String.class);
private static final Type enumType = Type.getType(Enum.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;
private Map<Class<?>, NestedCodec> nestedCodecs;
public CodecGen(final GeneratedClassLoader loader, final Class<T> t) {
classLoader = loader;
pojo = t;
pojoType = Type.getType(pojo);
nestedCodecs = new HashMap<>();
}
public ProtobufCodec<T> create() throws OrmException {
myFields = scanFields(pojo);
init();
implementNewInstanceObject();
implementNewInstanceSelf();
implementSizeofObject();
implementSizeofSelf();
implementEncodeObject();
implementEncodeSelf();
implementMergeFromObject();
implementMergeFromSelf();
implementCodecFields();
implementStaticInit();
implementConstructor();
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<>();
while (in != null) {
for (final Field f : JavaColumnModel.getDeclaredFields(in)) {
if (f.getAnnotation(Column.class) != null) {
col.add(new JavaColumnModel(f));
}
}
in = in.getSuperclass();
}
if (col.isEmpty()) {
throw new OrmException("Cannot create new encoder, no @Column fields found");
}
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 implementCodecFields() {
for (NestedCodec other : nestedCodecs.values()) {
cw.visitField(
ACC_PRIVATE | ACC_STATIC | ACC_FINAL,
other.field,
other.codecType.getDescriptor(),
null,
null)
.visitEnd();
}
}
private void implementStaticInit() {
final MethodVisitor mv =
cw.visitMethod(
ACC_PUBLIC | ACC_STATIC,
"<clinit>",
Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {}),
null,
null);
mv.visitCode();
for (NestedCodec other : nestedCodecs.values()) {
mv.visitTypeInsn(NEW, other.codecType.getInternalName());
mv.visitInsn(DUP);
mv.visitMethodInsn(
INVOKESPECIAL,
other.codecType.getInternalName(),
"<init>",
Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {}));
mv.visitFieldInsn(PUTSTATIC, implTypeName, other.field, other.codecType.getDescriptor());
}
mv.visitInsn(RETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
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 implementNewInstanceObject() {
final MethodVisitor mv =
cw.visitMethod(
ACC_PUBLIC,
"newInstance",
Type.getMethodDescriptor(object, new Type[] {}),
null,
new String[] {});
mv.visitCode();
mv.visitTypeInsn(NEW, pojoType.getInternalName());
mv.visitInsn(DUP);
mv.visitMethodInsn(
INVOKESPECIAL,
pojoType.getInternalName(),
"<init>",
Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {}));
mv.visitInsn(ARETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
private void implementNewInstanceSelf() {
final MethodVisitor mv =
cw.visitMethod(
ACC_PUBLIC,
"newInstance",
Type.getMethodDescriptor(pojoType, new Type[] {}),
null,
new String[] {});
mv.visitCode();
mv.visitTypeInsn(NEW, pojoType.getInternalName());
mv.visitInsn(DUP);
mv.visitMethodInsn(
INVOKESPECIAL,
pojoType.getInternalName(),
"<init>",
Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {}));
mv.visitInsn(ARETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
private void implementSizeofObject() {
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.sizeVar = cgs.newLocal();
cgs.setEntityType(pojoType);
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitTypeInsn(CHECKCAST, pojoType.getInternalName());
mv.visitMethodInsn(
INVOKEVIRTUAL,
implTypeName,
"sizeof",
Type.getMethodDescriptor(Type.INT_TYPE, new Type[] {pojoType}));
mv.visitInsn(IRETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
private void implementSizeofSelf() throws OrmException {
final MethodVisitor mv =
cw.visitMethod(
ACC_PUBLIC,
"sizeof",
Type.getMethodDescriptor(Type.INT_TYPE, new Type[] {pojoType}),
null,
new String[] {});
mv.visitCode();
final SizeofCGS cgs = new SizeofCGS(mv);
cgs.sizeVar = cgs.newLocal();
cgs.setEntityType(pojoType);
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 void sizeofMessage(
final JavaColumnModel[] myFields, final MethodVisitor mv, final SizeofCGS cgs)
throws OrmException {
for (final JavaColumnModel f : myFields) {
if (f.isNested()) {
final NestedCodec n = nestedFor(f);
final Label end = new Label();
cgs.setFieldReference(f);
cgs.pushFieldValue();
mv.visitJumpInsn(IFNULL, end);
final int msgSizeVar = cgs.newLocal();
mv.visitFieldInsn(GETSTATIC, implTypeName, n.field, n.codecType.getDescriptor());
cgs.pushFieldValue();
mv.visitMethodInsn(
INVOKEVIRTUAL,
n.codecType.getInternalName(),
"sizeof",
Type.getMethodDescriptor(Type.INT_TYPE, new Type[] {n.pojoType}));
mv.visitVarInsn(ISTORE, msgSizeVar);
cgs.preinc();
cgs.push(f.getColumnID());
cgs.doinc("computeTagSize", Type.INT_TYPE);
cgs.preinc();
mv.visitVarInsn(ILOAD, msgSizeVar);
cgs.doinc("computeRawVarint32Size", Type.INT_TYPE);
cgs.preinc();
mv.visitVarInsn(ILOAD, msgSizeVar);
cgs.doinc();
cgs.freeLocal(msgSizeVar);
mv.visitLabel(end);
} else if (f.isCollection()) {
sizeofCollection(f, mv, cgs);
} else {
sizeofScalar(mv, cgs, f);
}
}
}
@SuppressWarnings({"unchecked", "rawtypes"})
private NestedCodec nestedFor(JavaColumnModel f) {
Class clazz = f.getNestedClass();
NestedCodec n = nestedCodecs.get(clazz);
if (n == null) {
Class<? extends ProtobufCodec> codec = null;
Type type = Type.getType(clazz);
if (f.getField() != null) {
final CustomCodec cc = f.getField().getAnnotation(CustomCodec.class);
if (cc != null) {
codec = cc.value();
type = object;
}
}
if (codec == null) {
codec = CodecFactory.encoder(clazz).getClass();
}
n = new NestedCodec("codec" + f.getColumnID(), codec, type);
nestedCodecs.put(clazz, n);
}
return n;
}
private void sizeofCollection(
final JavaColumnModel f, final MethodVisitor mv, final SizeofCGS cgs) throws OrmException {
final int itr = cgs.newLocal();
final int val = cgs.newLocal();
final Class<?> valClazz = (Class<?>) f.getArgumentTypes()[0];
final Type valType = Type.getType(valClazz);
final JavaColumnModel col = collectionColumn(f, valClazz);
final SizeofCGS ng =
new SizeofCGS(mv) {
{
sizeVar = cgs.sizeVar;
setEntityType(valType);
}
@Override
public void pushEntity() {
mv.visitVarInsn(ALOAD, val);
}
@Override
protected void appendGetField(final ColumnModel c) {
if (c != col) {
super.appendGetField(c);
}
}
@Override
public int newLocal() {
return cgs.newLocal();
}
@Override
public void freeLocal(int index) {
cgs.freeLocal(index);
}
};
final Label end = new Label();
cgs.setFieldReference(f);
cgs.pushFieldValue();
mv.visitJumpInsn(IFNULL, end);
cgs.setFieldReference(f);
cgs.pushFieldValue();
mv.visitMethodInsn(
INVOKEINTERFACE,
collection.getInternalName(),
"iterator",
Type.getMethodDescriptor(iterator, new Type[] {}));
mv.visitVarInsn(ASTORE, itr);
final Label doloop = new Label();
mv.visitLabel(doloop);
mv.visitVarInsn(ALOAD, itr);
mv.visitMethodInsn(
INVOKEINTERFACE,
iterator.getInternalName(),
"hasNext",
Type.getMethodDescriptor(Type.BOOLEAN_TYPE, new Type[] {}));
mv.visitJumpInsn(IFEQ, end);
mv.visitVarInsn(ALOAD, itr);
mv.visitMethodInsn(
INVOKEINTERFACE,
iterator.getInternalName(),
"next",
Type.getMethodDescriptor(object, new Type[] {}));
mv.visitTypeInsn(CHECKCAST, valType.getInternalName());
mv.visitVarInsn(ASTORE, val);
sizeofMessage(new JavaColumnModel[] {col}, mv, ng);
mv.visitJumpInsn(GOTO, doloop);
mv.visitLabel(end);
cgs.freeLocal(itr);
cgs.freeLocal(val);
}
private JavaColumnModel collectionColumn(final JavaColumnModel f, final Class<?> valClazz)
throws OrmException {
return new JavaColumnModel( //
f.getField(), //
f.getPathToFieldName(), //
f.getColumnID(), //
valClazz);
}
private 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("computeInt32Size", 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("computeInt64Size", 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 if (f.getPrimitiveType().isEnum()) {
cgs.preinc();
cgs.push(f.getColumnID());
cgs.pushFieldValue();
mv.visitMethodInsn(
INVOKEVIRTUAL,
enumType.getInternalName(),
"ordinal", //
Type.getMethodDescriptor(Type.INT_TYPE, new Type[] {}));
cgs.doinc("computeEnumSize", Type.INT_TYPE, Type.INT_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 implementEncodeObject() {
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, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitTypeInsn(CHECKCAST, pojoType.getInternalName());
mv.visitVarInsn(ALOAD, 2);
mv.visitMethodInsn(
INVOKEVIRTUAL,
implTypeName,
"encode",
Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {pojoType, codedOutputStream}));
mv.visitInsn(RETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
private void implementEncodeSelf() throws OrmException {
final MethodVisitor mv =
cw.visitMethod(
ACC_PUBLIC,
"encode",
Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {pojoType, codedOutputStream}),
null,
new String[] {});
mv.visitCode();
final EncodeCGS cgs = new EncodeCGS(mv);
cgs.setEntityType(pojoType);
encodeMessage(myFields, mv, cgs);
mv.visitInsn(RETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
private void encodeMessage(
final JavaColumnModel[] myFields, final MethodVisitor mv, final EncodeCGS cgs)
throws OrmException {
for (final JavaColumnModel f : myFields) {
if (f.isNested()) {
final NestedCodec n = nestedFor(f);
final Label end = new Label();
cgs.setFieldReference(f);
cgs.pushFieldValue();
mv.visitJumpInsn(IFNULL, end);
final int msgSizeVar = cgs.newLocal();
mv.visitFieldInsn(GETSTATIC, implTypeName, n.field, n.codecType.getDescriptor());
cgs.pushFieldValue();
mv.visitMethodInsn(
INVOKEVIRTUAL,
n.codecType.getInternalName(),
"sizeof",
Type.getMethodDescriptor(Type.INT_TYPE, new Type[] {n.pojoType}));
mv.visitVarInsn(ISTORE, msgSizeVar);
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.pushCodedOutputStream();
mv.visitVarInsn(ILOAD, msgSizeVar);
mv.visitMethodInsn(
INVOKEVIRTUAL,
codedOutputStream.getInternalName(),
"writeRawVarint32",
Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {Type.INT_TYPE}));
mv.visitFieldInsn(GETSTATIC, implTypeName, n.field, n.codecType.getDescriptor());
cgs.pushFieldValue();
cgs.pushCodedOutputStream();
mv.visitMethodInsn(
INVOKEVIRTUAL,
n.codecType.getInternalName(),
"encode",
Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {n.pojoType, codedOutputStream}));
cgs.freeLocal(msgSizeVar);
mv.visitLabel(end);
} else if (f.isCollection()) {
encodeCollection(f, mv, cgs);
} else {
encodeScalar(mv, cgs, f);
}
}
}
private void encodeCollection(
final JavaColumnModel f, final MethodVisitor mv, final EncodeCGS cgs) throws OrmException {
final int itr = cgs.newLocal();
final int val = cgs.newLocal();
final Class<?> valClazz = (Class<?>) f.getArgumentTypes()[0];
final Type valType = Type.getType(valClazz);
final JavaColumnModel col = collectionColumn(f, valClazz);
final EncodeCGS ng =
new EncodeCGS(mv) {
{
sizeVar = cgs.sizeVar;
setEntityType(valType);
}
@Override
public void pushEntity() {
mv.visitVarInsn(ALOAD, val);
}
@Override
protected void appendGetField(final ColumnModel c) {
if (c != col) {
super.appendGetField(c);
}
}
@Override
public int newLocal() {
return cgs.newLocal();
}
@Override
public void freeLocal(int index) {
cgs.freeLocal(index);
}
};
final Label end = new Label();
cgs.setFieldReference(f);
cgs.pushFieldValue();
mv.visitJumpInsn(IFNULL, end);
cgs.setFieldReference(f);
cgs.pushFieldValue();
mv.visitMethodInsn(
INVOKEINTERFACE,
collection.getInternalName(),
"iterator",
Type.getMethodDescriptor(iterator, new Type[] {}));
mv.visitVarInsn(ASTORE, itr);
final Label doloop = new Label();
mv.visitLabel(doloop);
mv.visitVarInsn(ALOAD, itr);
mv.visitMethodInsn(
INVOKEINTERFACE,
iterator.getInternalName(),
"hasNext",
Type.getMethodDescriptor(Type.BOOLEAN_TYPE, new Type[] {}));
mv.visitJumpInsn(IFEQ, end);
mv.visitVarInsn(ALOAD, itr);
mv.visitMethodInsn(
INVOKEINTERFACE,
iterator.getInternalName(),
"next",
Type.getMethodDescriptor(object, new Type[] {}));
mv.visitTypeInsn(CHECKCAST, valType.getInternalName());
mv.visitVarInsn(ASTORE, val);
encodeMessage(new JavaColumnModel[] {col}, mv, ng);
mv.visitJumpInsn(GOTO, doloop);
mv.visitLabel(end);
cgs.freeLocal(itr);
cgs.freeLocal(val);
}
private 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("writeInt32", 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("writeInt64", 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 if (f.getPrimitiveType().isEnum()) {
mv.visitMethodInsn(
INVOKEVIRTUAL,
enumType.getInternalName(),
"ordinal", //
Type.getMethodDescriptor(Type.INT_TYPE, new Type[] {}));
cgs.write("writeEnum", Type.INT_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 implementMergeFromObject() {
final MethodVisitor mv =
cw.visitMethod(
ACC_PUBLIC,
"mergeFrom",
Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {codedInputStream, object}),
null,
new String[] {});
mv.visitCode();
mv.visitVarInsn(ALOAD, 0);
mv.visitVarInsn(ALOAD, 1);
mv.visitVarInsn(ALOAD, 2);
mv.visitTypeInsn(CHECKCAST, pojoType.getInternalName());
mv.visitMethodInsn(
INVOKEVIRTUAL,
implTypeName,
"mergeFrom",
Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {codedInputStream, pojoType}));
mv.visitInsn(RETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
private void implementMergeFromSelf() throws OrmException {
final MethodVisitor mv =
cw.visitMethod(
ACC_PUBLIC,
"mergeFrom",
Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {codedInputStream, pojoType}),
null,
new String[] {});
mv.visitCode();
final DecodeCGS cgs = new DecodeCGS(mv);
cgs.objVar = 2;
cgs.tagVar = cgs.newLocal();
cgs.setEntityType(pojoType);
decodeMessage(myFields, mv, cgs);
mv.visitInsn(RETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
private 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]);
decodeField(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 void decodeField(final MethodVisitor mv, final DecodeCGS cgs, final JavaColumnModel f)
throws OrmException {
if (f.isNested()) {
final NestedCodec n = nestedFor(f);
final Label load = new Label();
cgs.setFieldReference(f);
cgs.pushFieldValue();
mv.visitJumpInsn(IFNONNULL, load);
// Since the field isn't initialized, construct it
//
cgs.fieldSetBegin();
mv.visitFieldInsn(GETSTATIC, implTypeName, n.field, n.codecType.getDescriptor());
mv.visitMethodInsn(
INVOKEVIRTUAL,
n.codecType.getInternalName(),
"newInstance",
Type.getMethodDescriptor(n.pojoType, new Type[] {}));
if (object.equals(n.pojoType)) {
mv.visitTypeInsn(CHECKCAST, Type.getType(f.getNestedClass()).getInternalName());
}
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);
mv.visitFieldInsn(GETSTATIC, implTypeName, n.field, n.codecType.getDescriptor());
cgs.pushCodedInputStream();
cgs.pushFieldValue();
mv.visitMethodInsn(
INVOKEVIRTUAL,
n.codecType.getInternalName(),
"mergeFrom",
Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {codedInputStream, n.pojoType}));
cgs.pushCodedInputStream();
mv.visitVarInsn(ILOAD, limitVar);
cgs.ncallInt("popLimit", Type.VOID_TYPE);
cgs.freeLocal(limitVar);
} else if (f.isCollection()) {
decodeCollection(mv, cgs, f);
} else {
decodeScalar(mv, cgs, f);
}
}
private void decodeCollection(
final MethodVisitor mv, final DecodeCGS cgs, final JavaColumnModel f) throws OrmException {
final Class<?> valClazz = (Class<?>) f.getArgumentTypes()[0];
final Type valType = Type.getType(valClazz);
final JavaColumnModel col = collectionColumn(f, valClazz);
final DecodeCGS ng =
new DecodeCGS(mv) {
{
tagVar = cgs.tagVar;
setEntityType(valType);
}
@Override
public int newLocal() {
return cgs.newLocal();
}
@Override
public void freeLocal(int index) {
cgs.freeLocal(index);
}
@Override
protected void appendGetField(final ColumnModel c) {
if (c != col) {
super.appendGetField(c);
}
}
@Override
public void fieldSetBegin() {
if (col.isNested()) {
super.fieldSetBegin();
} else {
cgs.pushFieldValue();
}
}
@Override
public void fieldSetEnd() {
if (col.isNested()) {
super.fieldSetEnd();
} else {
mv.visitMethodInsn(
INVOKEINTERFACE,
collection.getInternalName(),
"add",
Type.getMethodDescriptor(Type.BOOLEAN_TYPE, new Type[] {object}));
mv.visitInsn(POP);
}
}
};
final Label notnull = new Label();
cgs.setFieldReference(f);
cgs.pushFieldValue();
mv.visitJumpInsn(IFNONNULL, notnull);
// If the field is null, try to initialize it based on its declared type.
// If we don't know what that is, we have to throw an exception instead.
//
final Type concreteType;
if (!f.getNestedClass().isInterface()
&& (f.getNestedClass().getModifiers() & Modifier.ABSTRACT) == 0) {
concreteType = Type.getType(f.getNestedClass());
} else if (f.getNestedClass().isAssignableFrom(ArrayList.class)) {
concreteType = Type.getType(ArrayList.class);
} else if (f.getNestedClass().isAssignableFrom(HashSet.class)) {
concreteType = Type.getType(HashSet.class);
} else if (f.getNestedClass().isAssignableFrom(TreeSet.class)) {
concreteType = Type.getType(TreeSet.class);
} else {
mv.visitTypeInsn(NEW, illegalStateException.getInternalName());
mv.visitInsn(DUP);
mv.visitLdcInsn("Field " + f.getPathToFieldName() + " not initialized");
mv.visitMethodInsn(
INVOKESPECIAL,
illegalStateException.getInternalName(),
"<init>",
Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {string}));
mv.visitInsn(ATHROW);
concreteType = null;
}
if (concreteType != null) {
cgs.fieldSetBegin();
mv.visitTypeInsn(NEW, concreteType.getInternalName());
mv.visitInsn(DUP);
mv.visitMethodInsn(
INVOKESPECIAL,
concreteType.getInternalName(),
"<init>",
Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {}));
cgs.fieldSetEnd();
}
mv.visitLabel(notnull);
if (col.isNested()) {
// If its nested, we have to build the object instance.
//
final NestedCodec n = nestedFor(col);
ng.objVar = cgs.newLocal();
mv.visitFieldInsn(GETSTATIC, implTypeName, n.field, n.codecType.getDescriptor());
mv.visitMethodInsn(
INVOKEVIRTUAL,
n.codecType.getInternalName(),
"newInstance",
Type.getMethodDescriptor(n.pojoType, new Type[] {}));
mv.visitVarInsn(ASTORE, ng.objVar);
// read the length, set a new limit, decode the message, validate
// we stopped at the end of it as expected.
//
final int limitVar = cgs.newLocal();
cgs.pushCodedInputStream();
cgs.call("readRawVarint32", Type.INT_TYPE);
cgs.ncallInt("pushLimit", Type.INT_TYPE);
mv.visitVarInsn(ISTORE, limitVar);
mv.visitFieldInsn(GETSTATIC, implTypeName, n.field, n.codecType.getDescriptor());
cgs.pushCodedInputStream();
mv.visitVarInsn(ALOAD, ng.objVar);
mv.visitMethodInsn(
INVOKEVIRTUAL,
n.codecType.getInternalName(),
"mergeFrom",
Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {codedInputStream, n.pojoType}));
cgs.pushCodedInputStream();
mv.visitVarInsn(ILOAD, limitVar);
cgs.ncallInt("popLimit", Type.VOID_TYPE);
cgs.freeLocal(limitVar);
cgs.pushFieldValue();
mv.visitVarInsn(ALOAD, ng.objVar);
mv.visitMethodInsn(
INVOKEINTERFACE,
collection.getInternalName(),
"add",
Type.getMethodDescriptor(Type.BOOLEAN_TYPE, new Type[] {object}));
mv.visitInsn(POP);
cgs.freeLocal(ng.objVar);
} else if (col.isCollection()) {
throw new OrmException(
"Cannot nest collection as member of another" + " collection: " + f.getPathToFieldName());
} else {
decodeScalar(mv, ng, col);
}
}
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("readInt32", 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("readInt64", 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 if (f.getPrimitiveType().isEnum()) {
Type et = Type.getType(f.getPrimitiveType());
mv.visitMethodInsn(
INVOKESTATIC,
et.getInternalName(),
"values",
Type.getMethodDescriptor(Type.getType("[" + et.getDescriptor()), new Type[] {}));
cgs.call("readEnum", Type.INT_TYPE);
mv.visitInsn(AALOAD);
} 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);
}
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 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 class DecodeCGS extends CodeGenSupport {
final int codedInputStreamVar = 1;
int objVar;
int tagVar;
DecodeCGS(MethodVisitor method) {
super(method);
}
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);
}
}
private static class NestedCodec {
final String field;
final Type codecType;
final Type pojoType;
NestedCodec(String field, Class<?> impl, Type pojoType) {
this.field = field;
this.codecType = Type.getType(impl);
this.pojoType = pojoType;
}
}
}