/*
* Copyright 2002-2007 the original author or authors.
*
* 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.baidu.bjf.remoting.protobuf;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.baidu.bjf.remoting.protobuf.annotation.Protobuf;
import com.baidu.bjf.remoting.protobuf.utils.FieldInfo;
import com.baidu.bjf.remoting.protobuf.utils.FieldUtils;
import com.baidu.bjf.remoting.protobuf.utils.ProtobufProxyUtils;
import com.google.protobuf.ByteString;
import com.google.protobuf.CodedInputStream;
import com.google.protobuf.CodedOutputStream;
import com.google.protobuf.UninitializedMessageException;
import com.google.protobuf.WireFormat;
/**
* A reflective usage by java reflect utility tools
*
* @author xiemalin
* @since 1.1.0
*/
public class ReflectiveCodec<T> implements Codec<T> {
private Class<T> cls;
private Map<Integer, FieldInfo> orderFieldsMapping;
private List<FieldInfo> fieldInfos;
public ReflectiveCodec(Class<T> cls) {
this.cls = cls;
List<Field> fields = FieldUtils.findMatchedFields(cls, Protobuf.class);
if (fields.isEmpty()) {
throw new IllegalArgumentException("Invalid class [" + cls.getName() + "] no field use annotation @"
+ Protobuf.class.getName() + " at class " + cls.getName());
}
fieldInfos = ProtobufProxyUtils.processDefaultValue(fields);
orderFieldsMapping = new HashMap<Integer, FieldInfo>();
for (FieldInfo fieldInfo : fieldInfos) {
int tag = CodedConstant.makeTag(fieldInfo.getOrder(),
fieldInfo.getFieldType().getInternalFieldType().getWireType());
orderFieldsMapping.put(tag, fieldInfo);
}
}
/*
* (non-Javadoc)
*
* @see com.baidu.bjf.remoting.protobuf.Codec#encode(java.lang.Object)
*/
@Override
public byte[] encode(T t) throws IOException {
if (t == null) {
throw new RuntimeException("target object to encode is null.");
}
int size = size(t);
byte[] bytes = new byte[size];
CodedOutputStream out = CodedOutputStream.newInstance(bytes);
writeTo(t, out);
return bytes;
}
private int computeSize(FieldInfo fieldInfo, Object value) throws IOException {
FieldType fieldType = fieldInfo.getFieldType();
int size = 0;
if (value instanceof List) {
// if list
size = CodedConstant.computeListSize(fieldInfo.getOrder(), (List) value, fieldInfo.getFieldType(), true, null);
return size;
}
int order = fieldInfo.getOrder();
switch (fieldType) {
case DOUBLE:
size = CodedOutputStream.computeDoubleSize(order, (Double) value);
break;
case BYTES:
ByteString bytes = ByteString.copyFrom((byte[]) value);
size = CodedOutputStream.computeBytesSize(order, bytes);
break;
case STRING:
ByteString string = ByteString.copyFromUtf8(value.toString());
size = CodedOutputStream.computeBytesSize(order, string);
break;
case BOOL:
size = CodedOutputStream.computeBoolSize(order, (Boolean) value);
break;
case FIXED32:
size = CodedOutputStream.computeFixed32Size(order, (Integer) value);
break;
case SFIXED32:
size = CodedOutputStream.computeSFixed32Size(order, (Integer) value);
break;
case SINT32:
size = CodedOutputStream.computeSInt32Size(order, (Integer) value);
break;
case INT32:
size = CodedOutputStream.computeInt32Size(order, (Integer) value);
break;
case UINT32:
size = CodedOutputStream.computeUInt32Size(order, (Integer) value);
break;
case FIXED64:
size = CodedOutputStream.computeFixed64Size(order, (Long) value);
break;
case SFIXED64:
size = CodedOutputStream.computeSFixed64Size(order, (Long) value);
break;
case SINT64:
size = CodedOutputStream.computeSInt64Size(order, (Long) value);
break;
case INT64:
size = CodedOutputStream.computeInt64Size(order, (Long) value);
break;
case UINT64:
size = CodedOutputStream.computeUInt64Size(order, (Long) value);
break;
case ENUM:
int i;
i = getEnumValue(value);
size = CodedOutputStream.computeEnumSize(order, i);
break;
case FLOAT:
size = CodedOutputStream.computeFloatSize(order, (Float) value);
break;
case OBJECT:
Class c = value.getClass();
ReflectiveCodec codec = new ReflectiveCodec(c);
int objectSize = codec.size(value);
size = size + CodedOutputStream.computeRawVarint32Size(objectSize);
size = size + CodedOutputStream.computeTagSize(order);
size += objectSize;
break;
default:
throw new IOException("Unknown field type on field '" + fieldInfo.getField().getName() + "'");
}
return size;
}
private int getEnumValue(Object value) {
int i;
if (value instanceof EnumReadable) {
i = ((EnumReadable) value).value();
} else {
Enum e = (Enum) value;
i = e.ordinal();
}
return i;
}
/*
* (non-Javadoc)
*
* @see com.baidu.bjf.remoting.protobuf.Codec#decode(byte[])
*/
@Override
public T decode(byte[] bytes) throws IOException {
if (bytes == null) {
throw new IOException("byte array is null.");
}
CodedInputStream input = CodedInputStream.newInstance(bytes, 0, bytes.length);
return readFrom(input);
}
/*
* (non-Javadoc)
*
* @see com.baidu.bjf.remoting.protobuf.Codec#size(java.lang.Object)
*/
@Override
public int size(T t) throws IOException {
int size = 0;
for (FieldInfo fieldInfo : fieldInfos) {
Object value = FieldUtils.getField(t, fieldInfo.getField());
// to check required
if (value == null) {
if (fieldInfo.isRequired()) {
throw new UninitializedMessageException(CodedConstant.asList(fieldInfo.getField().getName()));
}
} else {
size += computeSize(fieldInfo, value);
}
}
return size;
}
/*
* (non-Javadoc)
*
* @see com.baidu.bjf.remoting.protobuf.Codec#writeTo(java.lang.Object,
* com.google.protobuf.CodedOutputStream)
*/
@Override
public void writeTo(T t, CodedOutputStream out) throws IOException {
for (FieldInfo fieldInfo : fieldInfos) {
Object value = FieldUtils.getField(t, fieldInfo.getField());
if (value != null) {
writeTo(fieldInfo, value, out);
}
}
}
private void writeTo(FieldInfo fieldInfo, Object value, CodedOutputStream out) throws IOException {
FieldType fieldType = fieldInfo.getFieldType();
int order = fieldInfo.getOrder();
if (value instanceof List) {
// if check list
CodedConstant.writeToList(out, order, fieldType, (List) value);
return;
}
switch (fieldType) {
case DOUBLE:
out.writeDouble(order, (Double) value);
break;
case BYTES:
ByteString bytes = ByteString.copyFrom((byte[]) value);
out.writeBytes(order, bytes);
break;
case STRING:
ByteString string = ByteString.copyFromUtf8(value.toString());
out.writeBytes(order, string);
break;
case BOOL:
out.writeBool(order, (Boolean) value);
break;
case FIXED32:
out.writeFixed32(order, (Integer) value);
break;
case SFIXED32:
out.writeSFixed32(order, (Integer) value);
break;
case SINT32:
out.writeSInt32(order, (Integer) value);
break;
case INT32:
out.writeInt32(order, (Integer) value);
break;
case UINT32:
out.writeUInt32(order, (Integer) value);
break;
case FIXED64:
out.writeFixed64(order, (Long) value);
break;
case SFIXED64:
out.writeSFixed64(order, (Long) value);
break;
case SINT64:
out.writeSInt64(order, (Long) value);
break;
case INT64:
out.writeInt64(order, (Long) value);
break;
case UINT64:
out.writeUInt64(order, (Long) value);
break;
case ENUM:
int i;
i = getEnumValue(value);
out.writeEnum(order, i);
break;
case FLOAT:
out.writeFloat(order, (Float) value);
break;
case OBJECT:
Class c = value.getClass();
ReflectiveCodec codec = new ReflectiveCodec(c);
out.writeRawVarint32(CodedConstant.makeTag(order, WireFormat.WIRETYPE_LENGTH_DELIMITED));
out.writeRawVarint32(codec.size(value));
codec.writeTo(value, out);
break;
default:
throw new IOException("Unknown field type on field '" + fieldInfo.getField().getName() + "'");
}
}
/*
* (non-Javadoc)
*
* @see com.baidu.bjf.remoting.protobuf.Codec#readFrom(com.google.protobuf.
* CodedInputStream)
*/
@Override
public T readFrom(CodedInputStream input) throws IOException {
T t;
try {
t = cls.newInstance();
} catch (InstantiationException e1) {
throw new IOException(e1.getMessage(), e1);
} catch (IllegalAccessException e1) {
throw new IOException(e1.getMessage(), e1);
}
try {
boolean done = false;
while (!done) {
int tag = input.readTag();
if (tag == 0) {
break;
}
FieldInfo fieldInfo = orderFieldsMapping.get(tag);
if (fieldInfo == null) {
input.skipField(tag);
// maybe new field added should be ignore
continue;
}
// check list type
Field field = fieldInfo.getField();
Class<?> c = fieldInfo.getField().getType();
if (List.class.isAssignableFrom(c)) {
List list = (List) FieldUtils.getField(t, field);
if (list == null) {
list = new ArrayList();
FieldUtils.setField(t, field, list);
}
list.add(readValue(input, fieldInfo));
continue;
}
FieldType fieldType = fieldInfo.getFieldType();
switch (fieldType) {
case DOUBLE:
FieldUtils.setField(t, field, input.readDouble());
break;
case BYTES:
FieldUtils.setField(t, field, input.readBytes().toByteArray());
break;
case STRING:
FieldUtils.setField(t, field, input.readString());
break;
case BOOL:
FieldUtils.setField(t, field, input.readBool());
break;
case FIXED32:
FieldUtils.setField(t, field, input.readFixed32());
break;
case SFIXED32:
FieldUtils.setField(t, field, input.readSFixed32());
break;
case SINT32:
FieldUtils.setField(t, field, input.readSInt32());
break;
case INT32:
FieldUtils.setField(t, field, input.readInt32());
break;
case UINT32:
FieldUtils.setField(t, field, input.readUInt32());
break;
case FIXED64:
FieldUtils.setField(t, field, input.readFixed64());
break;
case SFIXED64:
FieldUtils.setField(t, field, input.readSFixed64());
break;
case SINT64:
FieldUtils.setField(t, field, input.readSInt64());
break;
case INT64:
FieldUtils.setField(t, field, input.readInt64());
break;
case UINT64:
FieldUtils.setField(t, field, input.readUInt64());
break;
case ENUM:
Class<?> type = field.getType();
try {
Method method = type.getMethod("values");
Enum[] inter = (Enum[]) method.invoke(null, null);
if (Enum.class.isAssignableFrom(type)) {
Enum value = CodedConstant.getEnum(inter, input.readEnum());
FieldUtils.setField(t, fieldInfo.getField(), value);
}
} catch (Exception e) {
throw new IOException(e.getMessage(), e);
}
break;
case FLOAT:
FieldUtils.setField(t, field, input.readFloat());
break;
case OBJECT:
ReflectiveCodec codec = new ReflectiveCodec(c);
int length = input.readRawVarint32();
final int oldLimit = input.pushLimit(length);
Object o = codec.readFrom(input);
FieldUtils.setField(t, fieldInfo.getField(), o);
break;
default:
throw new IOException("Unknown field type on field '" + fieldInfo.getField().getName() + "'");
}
}
} catch (com.google.protobuf.InvalidProtocolBufferException e) {
throw e;
} catch (java.io.IOException e) {
throw e;
}
return t;
}
private Object readValue(CodedInputStream input, FieldInfo fieldInfo) throws IOException {
FieldType fieldType = fieldInfo.getFieldType();
switch (fieldType) {
case DOUBLE:
return input.readDouble();
case BYTES:
return input.readBytes().toByteArray();
case STRING:
return input.readString();
case BOOL:
return input.readBool();
case FIXED32:
return input.readFixed32();
case SFIXED32:
return input.readSFixed32();
case SINT32:
return input.readSInt32();
case INT32:
return input.readInt32();
case UINT32:
return input.readUInt32();
case FIXED64:
return input.readFixed64();
case SFIXED64:
return input.readSFixed64();
case SINT64:
return input.readSInt64();
case INT64:
return input.readInt64();
case UINT64:
return input.readUInt64();
case ENUM:
Class<?> type = null;
if(fieldInfo.isList()) {
type = fieldInfo.getGenericKeyType();
} else{
type = fieldInfo.getField().getType();
}
try {
Method method = type.getMethod("values");
Enum[] inter = (Enum[]) method.invoke(null, null);
if (Enum.class.isAssignableFrom(type)) {
Enum value = CodedConstant.getEnum(inter, input.readEnum());
return value;
}
} catch (Exception e) {
throw new IOException(e.getMessage(), e);
}
break;
case FLOAT:
return input.readFloat();
case OBJECT:
if(fieldInfo.isList()) {
Class<?> c = fieldInfo.getGenericKeyType();
ReflectiveCodec codec = new ReflectiveCodec(c);
int length = input.readRawVarint32();
final int oldLimit = input.pushLimit(length);
Object o = codec.readFrom(input);
input.checkLastTagWas(0);
input.popLimit(oldLimit);
return o;
}
Class<?> c = fieldInfo.getField().getType();
ReflectiveCodec codec = new ReflectiveCodec(c);
int length = input.readRawVarint32();
final int oldLimit = input.pushLimit(length);
Object o = codec.readFrom(input);
return o;
default:
throw new IOException("Unknown field type on field '" + fieldInfo.getField().getName() + "'");
}
return null;
}
}