Code: Select all
public class Transform{
public Vector2 position;
public Quaternion rotation;
public Vector2 scale;
}
var serializer = new SFSObjectSerializer();
serializer.addSerializer(Transform.class, new ISerializer(){
void serialize(Transform){ // Custom Logic }
Transform deserialize(){ //Custom logic }
});
var packet = serializer.from(new Transform()); // By custom serializer
var nextPacket = serializer.from(new SuperCoolClass()); // Reflection, if attribute has its own serializer, then use that one instead of reflection
This would be very, very usefull... atleast i suffer from the current mechanic. I simply cant extend some of my third party classes to add my own custom toSFSObject(); methods. So i would love to plugin my own serializer instead of using OOP.
I actually came up with my own solution in an few hours, which is able to parse simple and complex class structures into ISFSObject hierarchys for sending them. And i also managed to implement custom serialization by using interfaces. My approach looks like this, there probably many bugs left or unsupported data types. But this is already working for some of my cases ( Note, deserialization not finished yet ) ...
Code: Select all
import com.smartfoxserver.v2.entities.data.*;
import org.apache.commons.lang3.ArrayUtils;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
/**
* A serializer that is able to serialize and deserialize almost every object by reflection or custom serializers.
* It makes use of {@link ISFSObject} for this process.
*/
public class NetworkSerializer {
/** Used to custom serialize certain classes and deserialize them back to entities **/
public interface ISerializer<T>{
ISFSObject serialize(NetworkSerializer serializer, T obj, Object meta);
T deserialize(NetworkSerializer serializer, ISFSObject packet, Object meta);
}
/** Stores all available serializers **/
private Map<Class, ISerializer> serializers = new HashMap<>();
/**
* Adds a new serializer.
* @param clazz The class we wanna serialize
* @param serializer The used serializer
* @param <T> The type
*/
public <T> void addSerializer(Class<T> clazz, ISerializer<T> serializer){ this.serializers.put(clazz, serializer); }
/**
* Removes an existing serializer.
* @param clazz The class we wanna delete the serializer for
* @param <T> The type
*/
public <T> void removeSerializer(Class<T> clazz){ this.serializers.remove(clazz); }
/**
* Returns an existing serializer or null.
* @param clazz The class we wanna get the serializer for
* @param <T> The type
* @return The existing serializer or null
*/
public <T> ISerializer getSerializer(Class<T> clazz){ return serializers.get(clazz); }
/**
* Serializes a passed object into a {@link ISFSObject}
* @param obj The object to serialize
* @return A packet which contains the serialized entity
*/
public ISFSObject toPacket(final Object obj) {
// Create packet and add ID/Classname
var clazz = obj.getClass();
var packet = new SFSObject();
packet.putUtfString("c", clazz.getCanonicalName());
// When theres a serialier use that one, otherwhise use reflection
var serializer = getSerializer(clazz);
if (serializer != null) {
// Serialize and merge the created object into the existing one
var entityPacket = serializer.serialize(this, obj, null);
merge(packet, entityPacket);
} else {
// Convert all attributes
var fields = clazz.getDeclaredFields();
for (var index = fields.length - 1; index >= 0; index--) {
var field = fields[index];
var modifier = field.getModifiers();
var isTransistent = Modifier.isTransient(modifier);
var isStatic = Modifier.isStatic(modifier);
// If not transistent, convert
if (!isTransistent && !isStatic) {
try {
field.setAccessible(true);
var fieldName = field.getName();
var fieldValue = field.get(obj);
var type = toType(fieldValue);
// Skip when theres a null value
if (type == SFSDataType.NULL) continue;
// Convert attribute either using reflection or using a custom serializer
var wrapped = wrap(type, fieldValue);
packet.put(fieldName, wrapped);
} catch (Exception e) { e.printStackTrace(); }
}
}
}
return packet;
}
// TODO : Deserialization of packets
public <T> T fromPacket(final ISFSObject packet){ return null; }
/**
* Checks the type of an object and returns the sfs-type. That one determines how it will be presented inside the packet
* @param value The object
* @return Its packet type
*/
public static SFSDataType toType(Object value) {
if (value == null) return SFSDataType.NULL;
else {
SFSDataType wrapper = null;
if (value instanceof Boolean) {
wrapper = SFSDataType.BOOL;
} else if (value instanceof Byte) {
wrapper = SFSDataType.BYTE;
} else if (value instanceof Short) {
wrapper = SFSDataType.SHORT;
} else if (value instanceof Integer) {
wrapper = SFSDataType.INT;
} else if (value instanceof Long) {
wrapper = SFSDataType.LONG;
} else if (value instanceof Float) {
wrapper = SFSDataType.FLOAT;
} else if (value instanceof Double) {
wrapper = SFSDataType.DOUBLE;
} else if (value instanceof String) {
wrapper = SFSDataType.UTF_STRING;
} else if(value.getClass().isArray()){
wrapper = SFSDataType.SFS_ARRAY;
} else {
wrapper = SFSDataType.SFS_OBJECT;
}
return wrapper;
}
}
/**
* Converts an array if its primitive to its boxed form.
* @param array The primitive/Object array to convert
* @return The converted, boxed array version
*/
public Object[] convertArray(Object array){
if(array instanceof boolean[]){
var boolArray = (boolean[])array;
return ArrayUtils.toObject(boolArray);
} else if(array instanceof byte[]){
var byteArray = (byte[])array;
return ArrayUtils.toObject(byteArray);
} else if(array instanceof short[]){
var shortArray = (short[])array;
return ArrayUtils.toObject(shortArray);
} else if(array instanceof int[]){
var intArray = (int[])array;
return ArrayUtils.toObject(intArray);
} else if(array instanceof long[]){
var longArray = (long[])array;
return ArrayUtils.toObject(longArray);
} else if(array instanceof float[]){
var floatArray = (float[])array;
return ArrayUtils.toObject(floatArray);
} else if(array instanceof double[]){
var floatArray = (double[])array;
return ArrayUtils.toObject(floatArray);
} else return (Object[]) array;
}
/**
* Wraps a object to a {@link SFSDataWrapper} to put it straight into {@link ISFSObject}'s.
* Smartfoxserver then takes care of the rest.
* @param value The object to wrap
* @return A wrapper for that object.
*/
public SFSDataWrapper wrap(Object value){
var type = toType(value);
return wrap(type, value);
}
/**
* Wraps a object to a {@link SFSDataWrapper} to put it straight into {@link ISFSObject}'s.
* Smartfoxserver then takes care of the rest.
* @param type The type
* @param value The object to wrap
* @return A wrapper for that object.
*/
public SFSDataWrapper wrap(SFSDataType type, Object value){
if(type == SFSDataType.NULL || value == null) return new SFSDataWrapper(SFSDataType.NULL, value);
switch (type){
case SFS_OBJECT:
// If theres a unknown type, create a packet and place it in the wrapper
var packet = toPacket(value);
return new SFSDataWrapper(type, packet);
case SFS_ARRAY:
// If theres an array, wrap its items one by one
var array = convertArray(value);
var arrayPacket = new SFSArray();
for(var index = 0; index < array.length; ++index) {
var item = array[index];
var itemType = toType(item);
var wrapped = wrap(itemType, item);
arrayPacket.add(wrapped);
}
return new SFSDataWrapper(type, arrayPacket);
default: return new SFSDataWrapper(type, value);
}
}
/**
* Merges two {@link ISFSObject}'s... overwrides possible keys.
* @param mergeIn The one that should be merged into
* @param toMerge The one that gets merged into the first one
*/
public static void merge(final ISFSObject mergeIn, final ISFSObject toMerge){
var iterator = toMerge.iterator();
while (iterator.hasNext()) {
var item = iterator.next();
mergeIn.put(item.getKey(), item.getValue());
}
}
}
Heres a little code example...
Code: Select all
public class CollectionSerializer implements NetworkSerializer.ISerializer<Collection> {
@Override
public ISFSObject serialize(NetworkSerializer serializer, Collection collection, Object meta) {
var collectionPacket = new SFSObject();
var itemsPacket = new SFSArray();
collectionPacket.putSFSArray("items", itemsPacket);
// Loop over all items, convert them using reflection/serializers and return them
for(var item : collection){
var wrapped = serializer.wrap(item);
itemsPacket.add(wrapped);
}
return collectionPacket;
}
@Override
public Collection deserialize(NetworkSerializer serializer, ISFSObject packet, Object meta) {
return null;
}
}
var test = new NetworkSerializer();
test.addSerializer(Collection.class, new CollectionSerializer());
var tesst = test.toPacket(new Transform()); // The tranform used above
Produces a ISFSObject looking like this :
ISFSObject
c : classPath
position : ISFSObject
c: Vector2 classpath
x
y
rotation : ...
Would be great if you could implement such custom serializers as some sort of plugins instead of using inheritance and OOP.
Feel free to use my code and i hope this helps someone.