From b2f57704611cf82e7c4396a9bb0e9ffa81196a85 Mon Sep 17 00:00:00 2001 From: Marc Baloup Date: Sun, 12 Mar 2023 14:14:17 +0100 Subject: [PATCH] Improved Json record support (Gson 2.10 natively supports it) + Added ItemStack Json support - Extract RecordTypeAdapter to its own file + only use it if Gson library does not support it (it does since 2.10, but we are unsure of which version is actually used in paper/bungee/other) - new ItemStackAdapter to support Json (de)serializing of Bukkit ItemStack. --- .../java/fr/pandacube/lib/core/json/Json.java | 90 +++---------------- .../lib/core/json/RecordTypeAdapter.java | 88 ++++++++++++++++++ .../fr/pandacube/lib/paper/PandaLibPaper.java | 2 + .../lib/paper/json/ItemStackAdapter.java | 34 +++++++ .../pandacube/lib/paper/json/PaperJson.java | 16 ++++ .../java/fr/pandacube/lib/util/BiMap.java | 1 + 6 files changed, 151 insertions(+), 80 deletions(-) create mode 100644 pandalib-core/src/main/java/fr/pandacube/lib/core/json/RecordTypeAdapter.java create mode 100644 pandalib-paper/src/main/java/fr/pandacube/lib/paper/json/ItemStackAdapter.java create mode 100644 pandalib-paper/src/main/java/fr/pandacube/lib/paper/json/PaperJson.java diff --git a/pandalib-core/src/main/java/fr/pandacube/lib/core/json/Json.java b/pandalib-core/src/main/java/fr/pandacube/lib/core/json/Json.java index cebcea4..eee80b8 100644 --- a/pandalib-core/src/main/java/fr/pandacube/lib/core/json/Json.java +++ b/pandalib-core/src/main/java/fr/pandacube/lib/core/json/Json.java @@ -1,23 +1,15 @@ package fr.pandacube.lib.core.json; -import java.io.IOException; -import java.lang.reflect.Constructor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.RecordComponent; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.function.Function; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.TypeAdapter; import com.google.gson.TypeAdapterFactory; +import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory; import com.google.gson.reflect.TypeToken; -import com.google.gson.stream.JsonReader; -import com.google.gson.stream.JsonToken; -import com.google.gson.stream.JsonWriter; /** * Provides pre-instanciated {@link Gson} instances, all with support for Java records and additionnal @@ -98,81 +90,19 @@ public class Json { - + private static boolean hasGsonNativeRecordSupport() { + for (Class innerClasses : ReflectiveTypeAdapterFactory.class.getDeclaredClasses()) { + if (innerClasses.getSimpleName().equals("RecordAdapter")) + return true; + } + return false; + } static { - registerTypeAdapterFactory(new RecordAdapterFactory()); + if (!hasGsonNativeRecordSupport()) + registerTypeAdapterFactory(RecordTypeAdapter.FACTORY); } - // from https://github.com/google/gson/issues/1794#issuecomment-812964421 - private static class RecordAdapterFactory implements TypeAdapterFactory { - @Override - public TypeAdapter create(Gson gson, TypeToken type) { - @SuppressWarnings("unchecked") - Class clazz = (Class) type.getRawType(); - if (!clazz.isRecord() || clazz == Record.class) { - return null; - } - return new RecordTypeAdapter<>(gson, this, type); - } - } - - private static class RecordTypeAdapter extends TypeAdapter { - private final Gson gson; - private final TypeAdapterFactory factory; - private final TypeToken type; - - public RecordTypeAdapter(Gson gson, TypeAdapterFactory factory, TypeToken type) { - this.gson = gson; - this.factory = factory; - this.type = type; - } - - @Override - public void write(JsonWriter out, T value) throws IOException { - gson.getDelegateAdapter(factory, type).write(out, value); - } - - @Override - public T read(JsonReader reader) throws IOException { - if (reader.peek() == JsonToken.NULL) { - reader.nextNull(); - return null; - } else { - @SuppressWarnings("unchecked") - Class clazz = (Class) type.getRawType(); - - RecordComponent[] recordComponents = clazz.getRecordComponents(); - Map> typeMap = new HashMap<>(); - for (RecordComponent recordComponent : recordComponents) { - typeMap.put(recordComponent.getName(), TypeToken.get(recordComponent.getGenericType())); - } - var argsMap = new HashMap(); - reader.beginObject(); - while (reader.hasNext()) { - String name = reader.nextName(); - argsMap.put(name, gson.getAdapter(typeMap.get(name)).read(reader)); - } - reader.endObject(); - - var argTypes = new Class[recordComponents.length]; - var args = new Object[recordComponents.length]; - for (int i = 0; i < recordComponents.length; i++) { - argTypes[i] = recordComponents[i].getType(); - args[i] = argsMap.get(recordComponents[i].getName()); - } - Constructor constructor; - try { - constructor = clazz.getDeclaredConstructor(argTypes); - constructor.setAccessible(true); - return constructor.newInstance(args); - } catch (NoSuchMethodException | InstantiationException | SecurityException | IllegalAccessException - | IllegalArgumentException | InvocationTargetException e) { - throw new RuntimeException(e); - } - } - } - } } diff --git a/pandalib-core/src/main/java/fr/pandacube/lib/core/json/RecordTypeAdapter.java b/pandalib-core/src/main/java/fr/pandacube/lib/core/json/RecordTypeAdapter.java new file mode 100644 index 0000000..5cbbf2b --- /dev/null +++ b/pandalib-core/src/main/java/fr/pandacube/lib/core/json/RecordTypeAdapter.java @@ -0,0 +1,88 @@ +package fr.pandacube.lib.core.json; + +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.stream.JsonReader; +import com.google.gson.stream.JsonToken; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.RecordComponent; +import java.util.HashMap; +import java.util.Map; + +// from https://github.com/google/gson/issues/1794#issuecomment-812964421 +/* package */ class RecordTypeAdapter extends TypeAdapter { + + public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() { + @Override + public TypeAdapter create(Gson gson, TypeToken type) { + @SuppressWarnings("unchecked") + Class clazz = (Class) type.getRawType(); + if (!clazz.isRecord() || clazz == Record.class) { + return null; + } + return new RecordTypeAdapter<>(gson, this, type); + } + }; + + + private final Gson gson; + private final TypeAdapterFactory factory; + private final TypeToken type; + + public RecordTypeAdapter(Gson gson, TypeAdapterFactory factory, TypeToken type) { + this.gson = gson; + this.factory = factory; + this.type = type; + } + + @Override + public void write(JsonWriter out, T value) throws IOException { + gson.getDelegateAdapter(factory, type).write(out, value); + } + + @Override + public T read(JsonReader reader) throws IOException { + if (reader.peek() == JsonToken.NULL) { + reader.nextNull(); + return null; + } else { + @SuppressWarnings("unchecked") + Class clazz = (Class) type.getRawType(); + + RecordComponent[] recordComponents = clazz.getRecordComponents(); + Map> typeMap = new HashMap<>(); + for (RecordComponent recordComponent : recordComponents) { + typeMap.put(recordComponent.getName(), TypeToken.get(recordComponent.getGenericType())); + } + var argsMap = new HashMap(); + reader.beginObject(); + while (reader.hasNext()) { + String name = reader.nextName(); + argsMap.put(name, gson.getAdapter(typeMap.get(name)).read(reader)); + } + reader.endObject(); + + var argTypes = new Class[recordComponents.length]; + var args = new Object[recordComponents.length]; + for (int i = 0; i < recordComponents.length; i++) { + argTypes[i] = recordComponents[i].getType(); + args[i] = argsMap.get(recordComponents[i].getName()); + } + Constructor constructor; + try { + constructor = clazz.getDeclaredConstructor(argTypes); + constructor.setAccessible(true); + return constructor.newInstance(args); + } catch (NoSuchMethodException | InstantiationException | SecurityException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/PandaLibPaper.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/PandaLibPaper.java index a91158c..c55e61d 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/PandaLibPaper.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/PandaLibPaper.java @@ -1,5 +1,6 @@ package fr.pandacube.lib.paper; +import fr.pandacube.lib.paper.json.PaperJson; import fr.pandacube.lib.paper.modules.PerformanceAnalysisManager; import org.bukkit.plugin.Plugin; @@ -9,6 +10,7 @@ public class PandaLibPaper { public static void onLoad(Plugin plugin) { PandaLibPaper.plugin = plugin; + PaperJson.init(); } public static void onEnable() { diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/json/ItemStackAdapter.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/json/ItemStackAdapter.java new file mode 100644 index 0000000..5486359 --- /dev/null +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/json/ItemStackAdapter.java @@ -0,0 +1,34 @@ +package fr.pandacube.lib.paper.json; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import com.google.gson.TypeAdapterFactory; +import com.google.gson.internal.bind.TreeTypeAdapter; +import com.google.gson.reflect.TypeToken; +import org.bukkit.inventory.ItemStack; + +import java.lang.reflect.Type; +import java.util.Map; + +/* package */ class ItemStackAdapter implements JsonSerializer, JsonDeserializer { + + private static final TypeToken ITEMSTACK_TYPE = TypeToken.get(ItemStack.class); + public static final TypeAdapterFactory FACTORY = TreeTypeAdapter.newFactoryWithMatchRawType(ITEMSTACK_TYPE, new ItemStackAdapter()); + + private static final TypeToken> MAP_STR_OBJ_TYPE = new TypeToken<>() { }; + + + @Override + public ItemStack deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + return ItemStack.deserialize(context.deserialize(json, MAP_STR_OBJ_TYPE.getType())); + } + + @Override + public JsonElement serialize(ItemStack src, Type typeOfSrc, JsonSerializationContext context) { + return context.serialize(src.serialize(), MAP_STR_OBJ_TYPE.getType()); + } +} diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/json/PaperJson.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/json/PaperJson.java new file mode 100644 index 0000000..d1f1969 --- /dev/null +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/json/PaperJson.java @@ -0,0 +1,16 @@ +package fr.pandacube.lib.paper.json; + +import fr.pandacube.lib.core.json.Json; + +/** + * Utility class to register Json adapters related to paper API classes. + */ +public class PaperJson { + + /** + * Registers Json adapters related to paper API classes. + */ + public static void init() { + Json.registerTypeAdapterFactory(ItemStackAdapter.FACTORY); + } +} diff --git a/pandalib-util/src/main/java/fr/pandacube/lib/util/BiMap.java b/pandalib-util/src/main/java/fr/pandacube/lib/util/BiMap.java index d4fa491..7703baa 100644 --- a/pandalib-util/src/main/java/fr/pandacube/lib/util/BiMap.java +++ b/pandalib-util/src/main/java/fr/pandacube/lib/util/BiMap.java @@ -13,6 +13,7 @@ import java.util.function.Supplier; * A bi-direction map storing in a synchronized way a {@code forwardMap} that store the key to value mapping, and a * {@code backwardMap} that store the value to key mapping. * All the keys and value are always unique in this bi-directional map. + * This class is fully thread safe. * @param the type of the "key" * @param the type of the "value" */