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:
		| @@ -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 <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; | ||||
|  | ||||
| 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() { | ||||
|   | ||||
| @@ -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 | ||||
|  * {@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 <K> the type of the "key" | ||||
|  * @param <V> the type of the "value" | ||||
|  */ | ||||
|   | ||||
		Reference in New Issue
	
	Block a user