mirror of
https://github.com/kingston-csj/jforgame.git
synced 2026-10-29 03:26:35 +08:00
struct-codec增加MapCodec以支持字典字段
This commit is contained in:
@@ -254,11 +254,18 @@
|
||||
Container#inject()方法增加主键检查
|
||||
Container增加#validate(DataRepository dataRepository)方法,允许容器进行关联数据校验
|
||||
|
||||
### jforgame-codec-struct
|
||||
增加MapCodec,以支持字典字段
|
||||
|
||||
## V4.0.0 api变动!!(计划)
|
||||
|
||||
Container去掉#validate()无参方法
|
||||
|
||||
### jforgame-codec-struct
|
||||
|
||||
StructMessageCodec更名为BeanAutoCodec
|
||||
ProtobufMessageCodec更名为ProtobufCodec
|
||||
ArrayCodec/CollectionCodec容器元素支持继承关系
|
||||
|
||||
|
||||
|
||||
|
||||
+7
-1
@@ -7,13 +7,16 @@ import java.nio.ByteBuffer;
|
||||
* 数组属性序列化
|
||||
* 注:由于数组元素bean没有像Message一样注册id,
|
||||
* 因此数组的元素不能是父类或抽象类
|
||||
* 数组长度不能超过Short.MAX_VALUE,即最多65535
|
||||
*/
|
||||
public class ArrayCodec extends Codec {
|
||||
|
||||
@Override
|
||||
public Object decode(ByteBuffer in, Class<?> type, Class<?> wrapper) {
|
||||
int size = ByteBuffUtil.readShort(in);
|
||||
|
||||
if (size < 0) {
|
||||
throw new RuntimeException("Array size less than zero!");
|
||||
}
|
||||
Object array = ReflectUtil.newArray(type, wrapper, size);
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
@@ -32,6 +35,9 @@ public class ArrayCodec extends Codec {
|
||||
return;
|
||||
}
|
||||
int size = Array.getLength(value);
|
||||
if (size > Short.MAX_VALUE) {
|
||||
throw new RuntimeException("Collection size less than zero or exceed max short value!");
|
||||
}
|
||||
ByteBuffUtil.writeShort(out, (short) size);
|
||||
for (int i = 0; i < size; i++) {
|
||||
Object elem = Array.get(value, i);
|
||||
|
||||
+1
@@ -37,6 +37,7 @@ public abstract class Codec {
|
||||
register(String.class, new StringCodec());
|
||||
register(List.class, new CollectionCodec());
|
||||
register(Set.class, new CollectionCodec());
|
||||
register(Map.class, new MapCodec());
|
||||
register(Object[].class, new ArrayCodec());
|
||||
}
|
||||
|
||||
|
||||
+52
-46
@@ -9,58 +9,64 @@ import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
*
|
||||
* 集合属性序列化
|
||||
* 注:由于集合元素bean没有像Message一样注册id,
|
||||
* 因此集合的元素不能是父类或抽象类
|
||||
*/
|
||||
* 集合属性序列化
|
||||
* 注:由于集合元素bean没有像Message一样注册id,
|
||||
* 因此集合的元素不能是父类或抽象类
|
||||
* 集合长度不能超过Short.MAX_VALUE,即最多65535
|
||||
*/
|
||||
public class CollectionCodec extends Codec {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("all")
|
||||
public Object decode(ByteBuffer in, Class<?> type, Class<?> wrapper) {
|
||||
int size = ByteBuffUtil.readShort(in);
|
||||
int modifier = type.getModifiers();
|
||||
Collection<Object> result = null;
|
||||
@Override
|
||||
@SuppressWarnings("all")
|
||||
public Object decode(ByteBuffer in, Class<?> type, Class<?> wrapper) {
|
||||
int size = ByteBuffUtil.readShort(in);
|
||||
if (size < 0) {
|
||||
throw new RuntimeException("Collection size less than zero!");
|
||||
}
|
||||
int modifier = type.getModifiers();
|
||||
Collection<Object> result = null;
|
||||
|
||||
if (Modifier.isAbstract(modifier) || Modifier.isInterface(modifier)) {
|
||||
if (List.class.isAssignableFrom(type)) {
|
||||
result = new ArrayList<>();
|
||||
} else if (Set.class.isAssignableFrom(type)) {
|
||||
result = new HashSet<>();
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
result = (Collection)type.newInstance();
|
||||
}catch(Exception e) {
|
||||
result = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
if (Modifier.isAbstract(modifier) || Modifier.isInterface(modifier)) {
|
||||
if (List.class.isAssignableFrom(type)) {
|
||||
result = new ArrayList<>();
|
||||
} else if (Set.class.isAssignableFrom(type)) {
|
||||
result = new HashSet<>();
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
result = (Collection) type.newInstance();
|
||||
} catch (Exception e) {
|
||||
result = new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
||||
for (int i=0; i<size; i++) {
|
||||
Codec fieldCodec = getSerializer(wrapper);
|
||||
Object eleValue = fieldCodec.decode(in, wrapper, null);
|
||||
result.add(eleValue);
|
||||
}
|
||||
for (int i = 0; i < size; i++) {
|
||||
Codec fieldCodec = getSerializer(wrapper);
|
||||
Object eleValue = fieldCodec.decode(in, wrapper, null);
|
||||
result.add(eleValue);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuffer out, Object value, Class<?> wrapper) {
|
||||
if (value == null) {
|
||||
ByteBuffUtil.writeShort(out, (short)0);
|
||||
return;
|
||||
}
|
||||
Collection<Object> collection = (Collection)value;
|
||||
int size = collection.size();
|
||||
ByteBuffUtil.writeShort(out, (short)size);
|
||||
@Override
|
||||
public void encode(ByteBuffer out, Object value, Class<?> wrapper) {
|
||||
if (value == null) {
|
||||
ByteBuffUtil.writeShort(out, (short) 0);
|
||||
return;
|
||||
}
|
||||
Collection<Object> collection = (Collection) value;
|
||||
int size = collection.size();
|
||||
if (size > Short.MAX_VALUE) {
|
||||
throw new RuntimeException("Collection size less than zero or exceed max short value!");
|
||||
}
|
||||
ByteBuffUtil.writeShort(out, (short) size);
|
||||
|
||||
for (Object elem:collection) {
|
||||
Class<?> clazz = elem.getClass();
|
||||
Codec fieldCodec = getSerializer(clazz);
|
||||
fieldCodec.encode(out, elem, wrapper);
|
||||
}
|
||||
}
|
||||
for (Object elem : collection) {
|
||||
Class<?> clazz = elem.getClass();
|
||||
Codec fieldCodec = getSerializer(clazz);
|
||||
fieldCodec.encode(out, elem, wrapper);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+51
-41
@@ -2,53 +2,63 @@ package jforgame.codec.struct;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
|
||||
public class FieldCodecMeta {
|
||||
|
||||
private Field field;
|
||||
|
||||
private Class<?> type;
|
||||
|
||||
private Codec codec;
|
||||
/** collection类型里的元素 */
|
||||
private Class<?> wrapper;
|
||||
|
||||
public static FieldCodecMeta valueOf(Field field, Codec codec) {
|
||||
FieldCodecMeta meta = new FieldCodecMeta();
|
||||
meta.field = field;
|
||||
Class<?> type = field.getType();
|
||||
meta.type = type;
|
||||
meta.codec = codec;
|
||||
|
||||
if (Collection.class.isAssignableFrom(type)) {
|
||||
meta.wrapper = (Class<?>)((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0];
|
||||
} else if (type.isArray()) {
|
||||
meta.wrapper = type.getComponentType();
|
||||
}
|
||||
return meta;
|
||||
}
|
||||
|
||||
public Field getField() {
|
||||
return field;
|
||||
}
|
||||
private Field field;
|
||||
|
||||
public Class<?> getType() {
|
||||
return type;
|
||||
}
|
||||
private Class<?> type;
|
||||
|
||||
public Codec getCodec() {
|
||||
return codec;
|
||||
}
|
||||
private Codec codec;
|
||||
/**
|
||||
* collection类型里的元素
|
||||
*/
|
||||
private Class<?> wrapper;
|
||||
|
||||
public Class<?> getWrapper() {
|
||||
return wrapper;
|
||||
}
|
||||
public static FieldCodecMeta valueOf(Field field, Codec codec) {
|
||||
FieldCodecMeta meta = new FieldCodecMeta();
|
||||
meta.field = field;
|
||||
Class<?> type = field.getType();
|
||||
meta.type = type;
|
||||
meta.codec = codec;
|
||||
// 处理 Collection 类型(存储元素类型)
|
||||
if (Collection.class.isAssignableFrom(type)) {
|
||||
meta.wrapper = (Class<?>) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
|
||||
} else if (type.isArray()) {
|
||||
// 处理数组类型(存储元素类型)
|
||||
meta.wrapper = type.getComponentType();
|
||||
} // 处理 Map 类型(Key 固定为 String,只存储 Value 类型)
|
||||
else if (Map.class.isAssignableFrom(type)) {
|
||||
// 泛型参数:[0]是Key(忽略,强制为String),[1]是Value类型
|
||||
Type[] actualTypes = ((ParameterizedType) field.getGenericType()).getActualTypeArguments();
|
||||
meta.wrapper = (Class<?>) actualTypes[1]; // 只存 Value 类型
|
||||
}
|
||||
return meta;
|
||||
}
|
||||
|
||||
public Field getField() {
|
||||
return field;
|
||||
}
|
||||
|
||||
public Class<?> getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public Codec getCodec() {
|
||||
return codec;
|
||||
}
|
||||
|
||||
public Class<?> getWrapper() {
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FieldCodecMeta [field=" + field + ", type=" + type + ", serializer=" + codec + ", wrapper="
|
||||
+ wrapper + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "FieldCodecMeta [field=" + field + ", type=" + type + ", serializer=" + codec + ", wrapper="
|
||||
+ wrapper + "]";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
package jforgame.codec.struct;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Map 编解码器(Key 强制为 String 类型,后续大版本升级再做改造)
|
||||
*/
|
||||
public class MapCodec extends Codec {
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("all")
|
||||
public Object decode(ByteBuffer in, Class<?> type, Class<?> valueType) {
|
||||
// 读取 Map 大小(short 类型)
|
||||
int size = ByteBuffUtil.readShort(in);
|
||||
Map<String, Object> result = new HashMap<>(size);
|
||||
if (valueType == null) {
|
||||
throw new IllegalArgumentException("MapCodec: valueType is null");
|
||||
}
|
||||
|
||||
// Value 类型:优先用 wrapper 传入的类型,否则默认 Object
|
||||
Class<?> actualValueType = valueType != null ? valueType : Object.class;
|
||||
Codec valueCodec = getSerializer(actualValueType);
|
||||
|
||||
// 循环解码键值对(Key 固定为 String)
|
||||
for (int i = 0; i < size; i++) {
|
||||
// 解码 Key(强制用 StringCodec)
|
||||
String key = (String) getSerializer(String.class).decode(in, String.class, null);
|
||||
// 解码 Value(用指定的 Value 类型编解码器)
|
||||
Object value = valueCodec.decode(in, actualValueType, null);
|
||||
result.put(key, value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuffer out, Object target, Class<?> wrapper) {
|
||||
if (target == null) {
|
||||
// 空 Map 写入 size=0
|
||||
ByteBuffUtil.writeShort(out, (short) 0);
|
||||
return;
|
||||
}
|
||||
|
||||
Map<String, Object> map = (Map<String, Object>) target;
|
||||
int size = map.size();
|
||||
ByteBuffUtil.writeShort(out, (short) size);
|
||||
|
||||
// 循环编码键值对(Key 强制为 String)
|
||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
// 编码 Key
|
||||
getSerializer(String.class).encode(out, key, null);
|
||||
|
||||
// 编码 Value(根据实际类型获取编解码器)
|
||||
Class<?> valueType = value != null ? value.getClass() : Object.class;
|
||||
Codec valueCodec = getSerializer(valueType);
|
||||
valueCodec.encode(out, value, null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import jforgame.codec.MessageCodec;
|
||||
import jforgame.codec.struct.StructMessageCodec;
|
||||
import message.ItemVo;
|
||||
import message.PlayerBackpack;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestArrayMessage {
|
||||
|
||||
private MessageCodec messageCodec = new StructMessageCodec();
|
||||
|
||||
@Test
|
||||
public void test() throws Exception {
|
||||
PlayerBackpack backpack = new PlayerBackpack();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ItemVo vo = new ItemVo();
|
||||
vo.setItemId(i);
|
||||
vo.setCount(i);
|
||||
backpack.getItems().add(vo);
|
||||
}
|
||||
|
||||
byte[] encode = messageCodec.encode(backpack);
|
||||
|
||||
PlayerBackpack newMsg = (PlayerBackpack) messageCodec.decode(PlayerBackpack.class, encode);
|
||||
System.out.println(newMsg);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import jforgame.codec.MessageCodec;
|
||||
import jforgame.codec.struct.StructMessageCodec;
|
||||
import message.PlayerLevelUpMessage;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import jforgame.codec.MessageCodec;
|
||||
import jforgame.codec.struct.StructMessageCodec;
|
||||
import message.ItemVo;
|
||||
import message.PlayerBackpack2;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TestMapMessage {
|
||||
|
||||
private MessageCodec messageCodec = new StructMessageCodec();
|
||||
|
||||
@Test
|
||||
public void test() throws Exception {
|
||||
PlayerBackpack2 backpack = new PlayerBackpack2();
|
||||
for (int i = 0; i < 10; i++) {
|
||||
ItemVo vo = new ItemVo();
|
||||
vo.setUid("uid_" + i);
|
||||
vo.setItemId(i);
|
||||
vo.setCount(i);
|
||||
backpack.getItems().put(vo.getUid(), vo);
|
||||
}
|
||||
|
||||
byte[] encode = messageCodec.encode(backpack);
|
||||
|
||||
PlayerBackpack2 newMsg = (PlayerBackpack2) messageCodec.decode(PlayerBackpack2.class, encode);
|
||||
System.out.println(newMsg);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package message;
|
||||
|
||||
public class ItemVo {
|
||||
|
||||
private String uid;
|
||||
|
||||
private int itemId;
|
||||
|
||||
private int count;
|
||||
|
||||
public String getUid() {
|
||||
return uid;
|
||||
}
|
||||
|
||||
public void setUid(String uid) {
|
||||
this.uid = uid;
|
||||
}
|
||||
|
||||
public int getItemId() {
|
||||
return itemId;
|
||||
}
|
||||
|
||||
public void setItemId(int itemId) {
|
||||
this.itemId = itemId;
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return count;
|
||||
}
|
||||
|
||||
public void setCount(int count) {
|
||||
this.count = count;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package message;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
public class PlayerBackpack {
|
||||
|
||||
private List<ItemVo> items = new LinkedList<>();
|
||||
|
||||
public List<ItemVo> getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
public void setItems(List<ItemVo> items) {
|
||||
this.items = items;
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
package message;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class PlayerBackpack2 {
|
||||
|
||||
private Map<String, ItemVo> items = new HashMap<>();
|
||||
|
||||
|
||||
public Map<String, ItemVo> getItems() {
|
||||
return items;
|
||||
}
|
||||
|
||||
public void setItems(Map<String, ItemVo> items) {
|
||||
this.items = items;
|
||||
}
|
||||
}
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
package message;
|
||||
|
||||
public class PlayerLevelUpMessage extends PlayerMessage {
|
||||
|
||||
private int afterLevel;
|
||||
+2
@@ -1,3 +1,5 @@
|
||||
package message;
|
||||
|
||||
public class PlayerMessage {
|
||||
|
||||
private long playerId;
|
||||
Reference in New Issue
Block a user