diff --git a/build.gradle b/build.gradle index 326321a..3523c1c 100644 --- a/build.gradle +++ b/build.gradle @@ -14,7 +14,7 @@ plugins { description = 'Jenjin Core IO API' group = 'com.jenjinstudios' -version = '0.1.0-alpha' +version = '0.2.0-alpha' // Core plugins apply plugin: 'java' diff --git a/src/main/java/com/jenjinstudios/io/annotations/MessageAdapter.java b/src/main/java/com/jenjinstudios/io/annotations/MessageAdapter.java index d382238..5c60287 100644 --- a/src/main/java/com/jenjinstudios/io/annotations/MessageAdapter.java +++ b/src/main/java/com/jenjinstudios/io/annotations/MessageAdapter.java @@ -19,5 +19,12 @@ * * @return The name of the class from which the Message should be serialized. */ - String adaptFrom(); + String adaptFrom() default ""; + + /** + * Specify the class to which the Message should be serialized. + * + * @return The name of the class to which the Message should be serialized. + */ + String adaptTo() default ""; } diff --git a/src/main/java/com/jenjinstudios/io/serialization/GsonMessageDeserializer.java b/src/main/java/com/jenjinstudios/io/serialization/GsonMessageDeserializer.java index 4879d0d..0b502be 100644 --- a/src/main/java/com/jenjinstudios/io/serialization/GsonMessageDeserializer.java +++ b/src/main/java/com/jenjinstudios/io/serialization/GsonMessageDeserializer.java @@ -21,7 +21,7 @@ public class GsonMessageDeserializer implements JsonDeserializer { private static final Logger LOGGER = LoggerFactory.getLogger(GsonMessageDeserializer.class); - private static final Map ADAPTED_CLASSES = new HashMap<>(10); + private static final Map ADAPT_FROM = new HashMap<>(10); static { LOGGER.debug("Scanning for message classes"); @@ -31,12 +31,15 @@ public class GsonMessageDeserializer implements JsonDeserializer LOGGER.debug("Registering message class: " + messageClass.getName()); Annotation annotation = messageClass.getAnnotation(MessageAdapter.class); if (annotation != null) { - String adaptFromClass = ((MessageAdapter) annotation).adaptFrom(); - LOGGER.debug("Registering adapter class: " + adaptFromClass); - ADAPTED_CLASSES.put(adaptFromClass, messageClass); + final MessageAdapter messageAdapter = (MessageAdapter) annotation; + String adaptFromClass = messageAdapter.adaptFrom(); + if (!adaptFromClass.isEmpty()) { + LOGGER.debug("Registering adapter class: " + adaptFromClass); + ADAPT_FROM.put(adaptFromClass, messageClass); + } } } - LOGGER.debug("Registered Message Classes: {}", ADAPTED_CLASSES); + LOGGER.debug("Registered \"adaptFrom\" Message Classes: {}", ADAPT_FROM); } @Override @@ -50,8 +53,8 @@ public Message deserialize(JsonElement json, Type typeOfT, JsonDeserializationCo } String className = classElement.getAsString(); Class lookupClass; - if (ADAPTED_CLASSES.containsKey(className)) { - lookupClass = ADAPTED_CLASSES.get(className); + if (ADAPT_FROM.containsKey(className)) { + lookupClass = ADAPT_FROM.get(className); } else { try { lookupClass = (Class) Class.forName(className); diff --git a/src/main/java/com/jenjinstudios/io/serialization/GsonMessageSerializer.java b/src/main/java/com/jenjinstudios/io/serialization/GsonMessageSerializer.java index f299709..02e771b 100644 --- a/src/main/java/com/jenjinstudios/io/serialization/GsonMessageSerializer.java +++ b/src/main/java/com/jenjinstudios/io/serialization/GsonMessageSerializer.java @@ -5,8 +5,16 @@ import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; import com.jenjinstudios.io.Message; +import com.jenjinstudios.io.annotations.MessageAdapter; +import org.reflections.Reflections; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.lang.annotation.Annotation; import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; /** * Used to serialize Messages with Gson. @@ -15,11 +23,42 @@ */ public class GsonMessageSerializer implements JsonSerializer { + private static final Logger LOGGER = LoggerFactory.getLogger(GsonMessageSerializer.class); + private static final Map ADAPT_TO = new HashMap<>(10); + + static { + LOGGER.debug("Scanning for message classes"); + Reflections reflections = new Reflections(""); + final Set> messageClasses = reflections.getSubTypesOf(Message.class); + for (Class messageClass : messageClasses) { + LOGGER.debug("Registering message class: " + messageClass.getName()); + Annotation annotation = messageClass.getAnnotation(MessageAdapter.class); + if (annotation != null) { + final MessageAdapter messageAdapter = (MessageAdapter) annotation; + String adaptTo = messageAdapter.adaptTo(); + if (!adaptTo.isEmpty()) { + LOGGER.debug("Registering adapter class: " + adaptTo); + try { + Class.forName(adaptTo); + ADAPT_TO.put(messageClass, adaptTo); + } catch (ClassNotFoundException e) { + LOGGER.warn("Encountered ClassNotFoundException when registering message adapter", e); + } + } + } + } + LOGGER.debug("Registered \"adaptTo\" Message Classes: {}", ADAPT_TO); + } + @Override public JsonElement serialize(Message src, Type typeOfSrc, JsonSerializationContext context) { - JsonElement fields = context.serialize(src, src.getClass()); + final Class srcClass = src.getClass(); + JsonElement fields = context.serialize(src, srcClass); JsonObject message = new JsonObject(); - message.addProperty("class", src.getClass().getName()); + + String className = ADAPT_TO.containsKey(srcClass) ? ADAPT_TO.get(srcClass) : srcClass.getName(); + + message.addProperty("class", className); message.add("fields", fields); return message; } diff --git a/src/test/groovy/com/jenjinstudios/io/serialization/AdaptToMessage.groovy b/src/test/groovy/com/jenjinstudios/io/serialization/AdaptToMessage.groovy new file mode 100644 index 0000000..8aa0e1d --- /dev/null +++ b/src/test/groovy/com/jenjinstudios/io/serialization/AdaptToMessage.groovy @@ -0,0 +1,20 @@ +package com.jenjinstudios.io.serialization + +import com.jenjinstudios.io.ExecutionContext +import com.jenjinstudios.io.Message +import com.jenjinstudios.io.annotations.MessageAdapter + +/** + * Used to test adapting messages. + * + * @author Caleb Brinkman + */ +@MessageAdapter(adaptTo = "com.jenjinstudios.io.serialization.TestMessage") +class AdaptToMessage implements Message{ + String name; + + @Override + public Message execute(ExecutionContext context) { + return null; + } +} diff --git a/src/test/groovy/com/jenjinstudios/io/serialization/GsonSerializerSpec.groovy b/src/test/groovy/com/jenjinstudios/io/serialization/GsonSerializerSpec.groovy index 8d846c1..1c178ff 100644 --- a/src/test/groovy/com/jenjinstudios/io/serialization/GsonSerializerSpec.groovy +++ b/src/test/groovy/com/jenjinstudios/io/serialization/GsonSerializerSpec.groovy @@ -22,4 +22,22 @@ public class GsonSerializerSpec extends Specification { then: json == expectedJson } + + def "GsonMessageSerializer should properly adapt message objects when serializing"() { + given: "An AdaptToMessage and a GsonBuilder" + def expectedJson = '{"class":"com.jenjinstudios.io.serialization.TestMessage","fields":{"name":"bar"}}'; + def message = new AdaptToMessage() + message.name = "bar" + def builder = new GsonBuilder() + + when: "The GsonMessageSerializer is registered" + builder.registerTypeAdapter(Message.class, new GsonMessageSerializer()) + def gson = builder.create(); + + and: "The message is serialized" + def json = gson.toJson(message, Message) + + then: "The message should be adapted from AdaptToMessage into TestMessage" + json == expectedJson + } } \ No newline at end of file