migrated to jackson
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
package eu.jonahbauer.wizard.common.messages;
|
||||
|
||||
public class ParseException extends RuntimeException {
|
||||
public ParseException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
@@ -1,25 +1,23 @@
|
||||
package eu.jonahbauer.wizard.common.messages.client;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import eu.jonahbauer.wizard.common.messages.ParseException;
|
||||
import eu.jonahbauer.wizard.common.messages.player.PlayerMessage;
|
||||
import eu.jonahbauer.wizard.common.util.SealedClassTypeAdapterFactory;
|
||||
import eu.jonahbauer.wizard.common.util.SerializationUtil;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
@EqualsAndHashCode
|
||||
public abstract sealed class ClientMessage permits CreateSessionMessage, InteractionMessage, JoinSessionMessage, LeaveSessionMessage, ReadyMessage, RejoinMessage {
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.registerTypeAdapterFactory(SealedClassTypeAdapterFactory.of(ClientMessage.class, "Message"))
|
||||
.registerTypeAdapterFactory(SealedClassTypeAdapterFactory.of(PlayerMessage.class, "Message"))
|
||||
.create();
|
||||
private static final ObjectMapper MAPPER = SerializationUtil.newObjectMapper(ClientMessage.class, PlayerMessage.class);
|
||||
|
||||
public static ClientMessage parse(String json) throws JsonParseException {
|
||||
return GSON.fromJson(json, ClientMessage.class);
|
||||
public static ClientMessage parse(String json) throws ParseException {
|
||||
return SerializationUtil.parse(json, MAPPER, ClientMessage.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public String toString() {
|
||||
return GSON.toJson(this, ClientMessage.class);
|
||||
return MAPPER.writeValueAsString(this);
|
||||
}
|
||||
}
|
||||
|
@@ -1,23 +1,22 @@
|
||||
package eu.jonahbauer.wizard.common.messages.observer;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonParseException;
|
||||
import eu.jonahbauer.wizard.common.util.SealedClassTypeAdapterFactory;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import eu.jonahbauer.wizard.common.messages.ParseException;
|
||||
import eu.jonahbauer.wizard.common.util.SerializationUtil;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
@EqualsAndHashCode
|
||||
public abstract sealed class ObserverMessage permits CardMessage, HandMessage, PredictionMessage, ScoreMessage, StateMessage, TimeoutMessage, TrickMessage, TrumpMessage, UserInputMessage {
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.registerTypeAdapterFactory(SealedClassTypeAdapterFactory.of(ObserverMessage.class, "Message"))
|
||||
.create();
|
||||
private static final ObjectMapper MAPPER = SerializationUtil.newObjectMapper(ObserverMessage.class);
|
||||
|
||||
public static ObserverMessage parse(String json) throws JsonParseException {
|
||||
return GSON.fromJson(json, ObserverMessage.class);
|
||||
public static ObserverMessage parse(String json) throws ParseException {
|
||||
return SerializationUtil.parse(json, MAPPER, ObserverMessage.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public String toString() {
|
||||
return GSON.toJson(this, ObserverMessage.class);
|
||||
return MAPPER.writeValueAsString(this);
|
||||
}
|
||||
}
|
@@ -1,23 +1,22 @@
|
||||
package eu.jonahbauer.wizard.common.messages.player;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonParseException;
|
||||
import eu.jonahbauer.wizard.common.util.SealedClassTypeAdapterFactory;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import eu.jonahbauer.wizard.common.messages.ParseException;
|
||||
import eu.jonahbauer.wizard.common.util.SerializationUtil;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
@EqualsAndHashCode
|
||||
public abstract sealed class PlayerMessage permits ContinueMessage, JuggleMessage, PickTrumpMessage, PlayCardMessage, PredictMessage {
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.registerTypeAdapterFactory(SealedClassTypeAdapterFactory.of(PlayerMessage.class, "Message"))
|
||||
.create();
|
||||
private static final ObjectMapper MAPPER = SerializationUtil.newObjectMapper(PlayerMessage.class);
|
||||
|
||||
public static PlayerMessage parse(String json) throws JsonParseException {
|
||||
return GSON.fromJson(json, PlayerMessage.class);
|
||||
public static PlayerMessage parse(String json) throws ParseException {
|
||||
return SerializationUtil.parse(json, MAPPER, PlayerMessage.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public String toString() {
|
||||
return GSON.toJson(this);
|
||||
return MAPPER.writeValueAsString(this);
|
||||
}
|
||||
}
|
||||
|
@@ -1,25 +1,23 @@
|
||||
package eu.jonahbauer.wizard.common.messages.server;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import eu.jonahbauer.wizard.common.messages.ParseException;
|
||||
import eu.jonahbauer.wizard.common.messages.observer.ObserverMessage;
|
||||
import eu.jonahbauer.wizard.common.util.SealedClassTypeAdapterFactory;
|
||||
import eu.jonahbauer.wizard.common.util.SerializationUtil;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.SneakyThrows;
|
||||
|
||||
@EqualsAndHashCode
|
||||
public abstract sealed class ServerMessage permits AckMessage, GameMessage, KickVotedMessage, KickedMessage, NackMessage, PlayerLeftMessage, PlayerModifiedMessage, SessionJoinedMessage, SessionListMessage, SessionModifiedMessage, SessionRemovedMessage, StartingGameMessage {
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.registerTypeAdapterFactory(SealedClassTypeAdapterFactory.of(ServerMessage.class, "Message"))
|
||||
.registerTypeAdapterFactory(SealedClassTypeAdapterFactory.of(ObserverMessage.class, "Message"))
|
||||
.create();
|
||||
private static final ObjectMapper MAPPER = SerializationUtil.newObjectMapper(ServerMessage.class, ObserverMessage.class);
|
||||
|
||||
public static ServerMessage parse(String json) throws JsonParseException {
|
||||
return GSON.fromJson(json, ServerMessage.class);
|
||||
public static ServerMessage parse(String json) throws ParseException {
|
||||
return SerializationUtil.parse(json, MAPPER, ServerMessage.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SneakyThrows
|
||||
public String toString() {
|
||||
return GSON.toJson(this, ServerMessage.class);
|
||||
return MAPPER.writeValueAsString(this);
|
||||
}
|
||||
}
|
||||
|
@@ -1,62 +0,0 @@
|
||||
package eu.jonahbauer.wizard.common.util;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.TypeAdapterFactory;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.gson.typeadapters.RuntimeTypeAdapterFactory;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Locale;
|
||||
|
||||
public final class SealedClassTypeAdapterFactory<T> implements TypeAdapterFactory {
|
||||
private final RuntimeTypeAdapterFactory<T> factory;
|
||||
|
||||
public static <T> SealedClassTypeAdapterFactory<T> of(Class<T> clazz) {
|
||||
return new SealedClassTypeAdapterFactory<>(clazz, null);
|
||||
}
|
||||
|
||||
public static <T> SealedClassTypeAdapterFactory<T> of(Class<T> clazz, @Nullable String suffix) {
|
||||
return new SealedClassTypeAdapterFactory<>(clazz, suffix);
|
||||
}
|
||||
|
||||
private SealedClassTypeAdapterFactory(Class<T> clazz, @Nullable String suffix) {
|
||||
factory = RuntimeTypeAdapterFactory.of(clazz);
|
||||
register(clazz, suffix);
|
||||
}
|
||||
|
||||
private void register(Class<? extends T> clazz, String suffix) {
|
||||
for (Class<?> subclass : clazz.getPermittedSubclasses()) {
|
||||
int modifiers = subclass.getModifiers();
|
||||
if (Modifier.isFinal(modifiers) || subclass.isSealed() && !Modifier.isAbstract(modifiers)) {
|
||||
String name = subclass.getSimpleName();
|
||||
|
||||
// remove suffix
|
||||
if (suffix != null) {
|
||||
if (name.endsWith(suffix)) name = name.substring(0, name.length() - suffix.length());
|
||||
}
|
||||
|
||||
// transform camelCast to snake_case
|
||||
name = name.replaceAll("([a-z])([A-Z]+)", "$1_$2").toLowerCase(Locale.ROOT);
|
||||
|
||||
factory.registerSubtype(subclass.asSubclass(clazz), name);
|
||||
}
|
||||
if (subclass.isSealed()) {
|
||||
register(subclass.asSubclass(clazz), suffix);
|
||||
} else if (!Modifier.isFinal(modifiers)) {
|
||||
//subclass is non-sealed
|
||||
throw new IllegalArgumentException(
|
||||
"SealedClassTypeAdapterFactory does not support a type hierarchy that contains non-sealed classes. " +
|
||||
"Found non-sealed class " + subclass.getName()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public <S> TypeAdapter<S> create(Gson gson, TypeToken<S> type) {
|
||||
return factory.create(gson, type);
|
||||
}
|
||||
}
|
@@ -0,0 +1,87 @@
|
||||
package eu.jonahbauer.wizard.common.util;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.cfg.MapperConfig;
|
||||
import com.fasterxml.jackson.databind.introspect.*;
|
||||
import com.fasterxml.jackson.databind.jsontype.NamedType;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.module.paramnames.ParameterNamesModule;
|
||||
import eu.jonahbauer.wizard.common.messages.ParseException;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Locale;
|
||||
|
||||
@UtilityClass
|
||||
public class SerializationUtil {
|
||||
public static ObjectMapper newObjectMapper(Class<?>...classes) {
|
||||
var mapper = new ObjectMapper()
|
||||
.registerModule(new ParameterNamesModule(JsonCreator.Mode.PROPERTIES))
|
||||
.registerModule(new SimpleModule() {
|
||||
@Override
|
||||
public void setupModule(SetupContext context) {
|
||||
super.setupModule(context);
|
||||
for (Class<?> clazz : classes) {
|
||||
context.setMixInAnnotations(clazz, Mixin.class);
|
||||
}
|
||||
context.insertAnnotationIntrospector(new NopAnnotationIntrospector() {
|
||||
@Override
|
||||
public JsonCreator.Mode findCreatorAnnotation(MapperConfig<?> config, Annotated ann) {
|
||||
if (ann instanceof AnnotatedConstructor con) {
|
||||
var declaringClass = con.getDeclaringClass();
|
||||
for (Class<?> clazz : classes) {
|
||||
if (clazz.isAssignableFrom(declaringClass)) {
|
||||
return JsonCreator.Mode.PROPERTIES;
|
||||
}
|
||||
}
|
||||
}
|
||||
return super.findCreatorAnnotation(config, ann);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
for (Class<?> clazz : classes) {
|
||||
registerSubtypes(mapper, clazz);
|
||||
}
|
||||
|
||||
return mapper;
|
||||
}
|
||||
|
||||
private static void registerSubtypes(ObjectMapper objectMapper, Class<?> clazz) {
|
||||
if (!clazz.isSealed()) throw new IllegalArgumentException();
|
||||
|
||||
var suffix = "Message";
|
||||
for (Class<?> subclass : clazz.getPermittedSubclasses()) {
|
||||
int modifiers = subclass.getModifiers();
|
||||
|
||||
if (Modifier.isFinal(modifiers) || subclass.isSealed() && !Modifier.isAbstract(modifiers)) {
|
||||
var name = subclass.getSimpleName();
|
||||
if (name.endsWith(suffix)) name = name.substring(0, name.length() - suffix.length());
|
||||
name = name.replaceAll("([a-z])([A-Z]+)", "$1_$2").toLowerCase(Locale.ROOT);
|
||||
|
||||
objectMapper.registerSubtypes(new NamedType(subclass, name));
|
||||
}
|
||||
|
||||
if (subclass.isSealed()) {
|
||||
registerSubtypes(objectMapper, subclass);
|
||||
} else if (!Modifier.isFinal(modifiers)) {
|
||||
throw new IllegalArgumentException("Non-sealed classes are not supported.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> T parse(String json, ObjectMapper objectMapper, Class<T> clazz) throws ParseException {
|
||||
try {
|
||||
return objectMapper.readValue(json, clazz);
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new ParseException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
|
||||
private static class Mixin {}
|
||||
}
|
Reference in New Issue
Block a user