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.
This commit is contained in:
parent
f1ef4e1927
commit
b2f5770461
@ -1,23 +1,15 @@
|
|||||||
package fr.pandacube.lib.core.json;
|
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.ArrayList;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
import com.google.gson.TypeAdapter;
|
import com.google.gson.TypeAdapter;
|
||||||
import com.google.gson.TypeAdapterFactory;
|
import com.google.gson.TypeAdapterFactory;
|
||||||
|
import com.google.gson.internal.bind.ReflectiveTypeAdapterFactory;
|
||||||
import com.google.gson.reflect.TypeToken;
|
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
|
* 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 {
|
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 <T> TypeAdapter<T> create(Gson gson, TypeToken<T> type) {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Class<T> clazz = (Class<T>) type.getRawType();
|
|
||||||
if (!clazz.isRecord() || clazz == Record.class) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return new RecordTypeAdapter<>(gson, this, type);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class RecordTypeAdapter<T> extends TypeAdapter<T> {
|
|
||||||
private final Gson gson;
|
|
||||||
private final TypeAdapterFactory factory;
|
|
||||||
private final TypeToken<T> type;
|
|
||||||
|
|
||||||
public RecordTypeAdapter(Gson gson, TypeAdapterFactory factory, TypeToken<T> 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<T> clazz = (Class<T>) type.getRawType();
|
|
||||||
|
|
||||||
RecordComponent[] recordComponents = clazz.getRecordComponents();
|
|
||||||
Map<String, TypeToken<?>> typeMap = new HashMap<>();
|
|
||||||
for (RecordComponent recordComponent : recordComponents) {
|
|
||||||
typeMap.put(recordComponent.getName(), TypeToken.get(recordComponent.getGenericType()));
|
|
||||||
}
|
|
||||||
var argsMap = new HashMap<String, Object>();
|
|
||||||
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<T> 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -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<T> extends TypeAdapter<T> {
|
||||||
|
|
||||||
|
public static final TypeAdapterFactory FACTORY = new TypeAdapterFactory() {
|
||||||
|
@Override
|
||||||
|
public <TT> TypeAdapter<TT> create(Gson gson, TypeToken<TT> type) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Class<TT> clazz = (Class<TT>) 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<T> type;
|
||||||
|
|
||||||
|
public RecordTypeAdapter(Gson gson, TypeAdapterFactory factory, TypeToken<T> 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<T> clazz = (Class<T>) type.getRawType();
|
||||||
|
|
||||||
|
RecordComponent[] recordComponents = clazz.getRecordComponents();
|
||||||
|
Map<String, TypeToken<?>> typeMap = new HashMap<>();
|
||||||
|
for (RecordComponent recordComponent : recordComponents) {
|
||||||
|
typeMap.put(recordComponent.getName(), TypeToken.get(recordComponent.getGenericType()));
|
||||||
|
}
|
||||||
|
var argsMap = new HashMap<String, Object>();
|
||||||
|
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<T> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
package fr.pandacube.lib.paper;
|
package fr.pandacube.lib.paper;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.paper.json.PaperJson;
|
||||||
import fr.pandacube.lib.paper.modules.PerformanceAnalysisManager;
|
import fr.pandacube.lib.paper.modules.PerformanceAnalysisManager;
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
@ -9,6 +10,7 @@ public class PandaLibPaper {
|
|||||||
|
|
||||||
public static void onLoad(Plugin plugin) {
|
public static void onLoad(Plugin plugin) {
|
||||||
PandaLibPaper.plugin = plugin;
|
PandaLibPaper.plugin = plugin;
|
||||||
|
PaperJson.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void onEnable() {
|
public static void onEnable() {
|
||||||
|
@ -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<ItemStack>, JsonDeserializer<ItemStack> {
|
||||||
|
|
||||||
|
private static final TypeToken<ItemStack> ITEMSTACK_TYPE = TypeToken.get(ItemStack.class);
|
||||||
|
public static final TypeAdapterFactory FACTORY = TreeTypeAdapter.newFactoryWithMatchRawType(ITEMSTACK_TYPE, new ItemStackAdapter());
|
||||||
|
|
||||||
|
private static final TypeToken<Map<String, Object>> 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());
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
* 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.
|
* {@code backwardMap} that store the value to key mapping.
|
||||||
* All the keys and value are always unique in this bi-directional map.
|
* All the keys and value are always unique in this bi-directional map.
|
||||||
|
* This class is fully thread safe.
|
||||||
* @param <K> the type of the "key"
|
* @param <K> the type of the "key"
|
||||||
* @param <V> the type of the "value"
|
* @param <V> the type of the "value"
|
||||||
*/
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user