diff --git a/rpgregions/src/main/java/me/lucko/helper/gson/GsonSerializable.java b/rpgregions/src/main/java/me/lucko/helper/gson/GsonSerializable.java new file mode 100644 index 0000000..e8301d3 --- /dev/null +++ b/rpgregions/src/main/java/me/lucko/helper/gson/GsonSerializable.java @@ -0,0 +1,118 @@ +/* + * This file is part of helper, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.helper.gson; + +import com.google.gson.JsonElement; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; + +/** + * An object which can be serialized to JSON. + * + *

Classes which implement this interface should also implement a static "deserialize" method, + * accepting {@link JsonElement} as the only parameter.

+ */ +public interface GsonSerializable { + + /** + * Deserializes a JsonElement to a GsonSerializable object. + * + * @param clazz the GsonSerializable class + * @param element the json element to deserialize + * @param the GsonSerializable type + * @return the deserialized object + * @throws IllegalStateException if the clazz does not have a deserialization method + */ + @Nonnull + static T deserialize(@Nonnull Class clazz, @Nonnull JsonElement element) { + Method deserializeMethod = getDeserializeMethod(clazz); + if (deserializeMethod == null) { + throw new IllegalStateException("Class does not have a deserialize method accessible."); + } + + try { + //noinspection unchecked + return (T) deserializeMethod.invoke(null, element); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException(e); + } + } + + /** + * Deserializes a JsonElement to a GsonSerializable object. + * + * @param clazz the GsonSerializable class + * @param element the json element to deserialize + * @return the deserialized object + * @throws IllegalStateException if the clazz does not have a deserialization method + */ + @Nonnull + static GsonSerializable deserializeRaw(@Nonnull Class clazz, @Nonnull JsonElement element) { + Class typeCastedClass = clazz.asSubclass(GsonSerializable.class); + return deserialize(typeCastedClass, element); + } + + /** + * Gets the deserialization method for a given class. + * + * @param clazz the class + * @return the deserialization method, if the class has one + */ + @Nullable + static Method getDeserializeMethod(@Nonnull Class clazz) { + if (!GsonSerializable.class.isAssignableFrom(clazz)) { + return null; + } + + Method deserializeMethod; + try { + //noinspection JavaReflectionMemberAccess + deserializeMethod = clazz.getDeclaredMethod("deserialize", JsonElement.class); + deserializeMethod.setAccessible(true); + } catch (Exception e) { + return null; + } + + if (!Modifier.isStatic(deserializeMethod.getModifiers())) { + return null; + } + + return deserializeMethod; + } + + /** + * Serializes the object to JSON + * + * @return a json form of this object + */ + @Nonnull + JsonElement serialize(); + +} diff --git a/rpgregions/src/main/java/me/lucko/helper/gson/JsonBuilder.java b/rpgregions/src/main/java/me/lucko/helper/gson/JsonBuilder.java new file mode 100644 index 0000000..a0c97d9 --- /dev/null +++ b/rpgregions/src/main/java/me/lucko/helper/gson/JsonBuilder.java @@ -0,0 +1,739 @@ +/* + * This file is part of helper, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.helper.gson; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +import javax.annotation.Nullable; +import java.util.Map; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collector; +import java.util.stream.Stream; + +/** + * Builder utilities for creating GSON Objects/Arrays. + */ +public final class JsonBuilder { + + /** + * Creates a new object builder + * + *

If copy is not true, the passed object will be mutated by the builder methods.

+ * + * @param object the object to base the new builder upon + * @param copy if the object should be deep copied, or just referenced. + * @return a new builder + */ + public static JsonObjectBuilder object(JsonObject object, boolean copy) { + Objects.requireNonNull(object, "object"); + + if (copy) { + return object().addAll(object, true); + } else { + return new JsonObjectBuilderImpl(object); + } + } + + /** + * Creates a new object builder, without copying the passed object. + * + *

Equivalent to calling {@link #object(JsonObject, boolean)} with copy = false.

+ * + * @param object the object to base the new builder upon + * @return a new builder + */ + public static JsonObjectBuilder object(JsonObject object) { + Objects.requireNonNull(object, "object"); + return object(object, false); + } + + /** + * Creates a new object builder, with no initial values + * + * @return a new builder + */ + public static JsonObjectBuilder object() { + return object(new JsonObject()); + } + + /** + * Creates a new array builder + * + *

If copy is not true, the passed array will be mutated by the builder methods.

+ * + * @param array the array to base the new builder upon + * @param copy if the array should be deep copied, or just referenced. + * @return a new builder + */ + public static JsonArrayBuilder array(JsonArray array, boolean copy) { + Objects.requireNonNull(array, "array"); + + if (copy) { + return array().addAll(array, true); + } else { + return new JsonArrayBuilderImpl(array); + } + } + + /** + * Creates a new array builder, without copying the passed array. + * + *

Equivalent to calling {@link #array(JsonArray, boolean)} with copy = false.

+ * + * @param array the array to base the new builder upon + * @return a new builder + */ + public static JsonArrayBuilder array(JsonArray array) { + Objects.requireNonNull(array, "array"); + return array(array, false); + } + + /** + * Creates a new array builder, with no initial values + * + * @return a new builder + */ + public static JsonArrayBuilder array() { + return array(new JsonArray()); + } + + /** + * Creates a JsonPrimitive from the given value. + * + *

If the value is null, {@link #nullValue()} is returned.

+ * + * @param value the value + * @return a json primitive for the value + */ + public static JsonElement primitive(@Nullable String value) { + return value == null ? nullValue() : new JsonPrimitive(value); + } + + /** + * Creates a JsonPrimitive from the given value. + * + *

If the value is null, {@link #nullValue()} is returned.

+ * + * @param value the value + * @return a json primitive for the value + */ + public static JsonElement primitive(@Nullable Number value) { + return value == null ? nullValue() : new JsonPrimitive(value); + } + + /** + * Creates a JsonPrimitive from the given value. + * + *

If the value is null, {@link #nullValue()} is returned.

+ * + * @param value the value + * @return a json primitive for the value + */ + public static JsonElement primitive(@Nullable Boolean value) { + return value == null ? nullValue() : new JsonPrimitive(value); + } + + /** + * Creates a JsonPrimitive from the given value. + * + *

If the value is null, {@link #nullValue()} is returned.

+ * + * @param value the value + * @return a json primitive for the value + */ + public static JsonElement primitive(@Nullable Character value) { + return value == null ? nullValue() : new JsonPrimitive(value); + } + + /** + * Returns an instance of {@link JsonNull}. + * + * @return a json null instance + */ + public static JsonNull nullValue() { + return JsonNull.INSTANCE; + } + + /** + * Creates a JsonPrimitive from the given value. + * + *

If the value is null, a {@link NullPointerException} will be thrown.

+ * + * @param value the value + * @return a json primitive for the value + * @throws NullPointerException if value is null + */ + public static JsonPrimitive primitiveNonNull(String value) { + Objects.requireNonNull(value, "value"); + return new JsonPrimitive(value); + } + + /** + * Creates a JsonPrimitive from the given value. + * + *

If the value is null, a {@link NullPointerException} will be thrown.

+ * + * @param value the value + * @return a json primitive for the value + * @throws NullPointerException if value is null + */ + public static JsonPrimitive primitiveNonNull(Number value) { + Objects.requireNonNull(value, "value"); + return new JsonPrimitive(value); + } + + /** + * Creates a JsonPrimitive from the given value. + * + *

If the value is null, a {@link NullPointerException} will be thrown.

+ * + * @param value the value + * @return a json primitive for the value + * @throws NullPointerException if value is null + */ + public static JsonPrimitive primitiveNonNull(Boolean value) { + Objects.requireNonNull(value, "value"); + return new JsonPrimitive(value); + } + + /** + * Creates a JsonPrimitive from the given value. + * + *

If the value is null, a {@link NullPointerException} will be thrown.

+ * + * @param value the value + * @return a json primitive for the value + * @throws NullPointerException if value is null + */ + public static JsonPrimitive primitiveNonNull(Character value) { + Objects.requireNonNull(value, "value"); + return new JsonPrimitive(value); + } + + /** + * Returns a collector which forms a JsonObject using the key and value mappers + * + * @param keyMapper the function to map from T to {@link String} + * @param valueMapper the function to map from T to {@link JsonElement} + * @param the type + * @return a new collector + */ + public static Collector collectToObject(Function keyMapper, Function valueMapper) { + return Collector.of( + JsonBuilder::object, + (r, t) -> r.add(keyMapper.apply(t), valueMapper.apply(t)), + (l, r) -> l.addAll(r.build()), + JsonObjectBuilder::build + ); + } + + /** + * Returns a collector which forms a JsonArray using the value mapper + * + * @param valueMapper the function to map from T to {@link JsonElement} + * @param the type + * @return a new collector + */ + public static Collector collectToArray(Function valueMapper) { + return Collector.of( + JsonBuilder::array, + (r, t) -> r.add(valueMapper.apply(t)), + (l, r) -> l.addAll(r.build()), + JsonArrayBuilder::build + ); + } + + /** + * Returns a collector which forms a JsonArray from JsonElements + * + * @return a new collector + */ + public static Collector collectToArray() { + return Collector.of( + JsonBuilder::array, + JsonArrayBuilder::add, + (l, r) -> l.addAll(r.build()), + JsonArrayBuilder::build + ); + } + + /** + * Returns a collector which forms a JsonArray from GsonSerializables + * + * @return a new collector + */ + public static Collector collectSerializablesToArray() { + return Collector.of( + JsonBuilder::array, + JsonArrayBuilder::add, + (l, r) -> l.addAll(r.build()), + JsonArrayBuilder::build + ); + } + + /** + * A {@link JsonObject} builder utility + */ + public interface JsonObjectBuilder extends BiConsumer, Consumer> { + + @Override + default void accept(Map.Entry entry) { + Objects.requireNonNull(entry, "entry"); + add(entry.getKey(), entry.getValue()); + } + + @Override + default void accept(String property, JsonElement value) { + add(property, value); + } + + JsonObjectBuilder add(String property, @Nullable JsonElement value, boolean copy); + + default JsonObjectBuilder add(String property, @Nullable JsonElement value) { + return add(property, value, false); + } + + default JsonObjectBuilder add(String property, @Nullable GsonSerializable serializable) { + return serializable == null ? add(property, nullValue()) : add(property, serializable.serialize()); + } + + default JsonObjectBuilder add(String property, @Nullable String value) { + return add(property, primitive(value)); + } + + default JsonObjectBuilder add(String property, @Nullable Number value) { + return add(property, primitive(value)); + } + + default JsonObjectBuilder add(String property, @Nullable Boolean value) { + return add(property, primitive(value)); + } + + default JsonObjectBuilder add(String property, @Nullable Character value) { + return add(property, primitive(value)); + } + + JsonObjectBuilder addIfAbsent(String property, @Nullable JsonElement value, boolean copy); + + default JsonObjectBuilder addIfAbsent(String property, @Nullable JsonElement value) { + return addIfAbsent(property, value, false); + } + + default JsonObjectBuilder addIfAbsent(String property, @Nullable GsonSerializable serializable) { + return serializable == null ? addIfAbsent(property, nullValue()) : addIfAbsent(property, serializable.serialize()); + } + + default JsonObjectBuilder addIfAbsent(String property, @Nullable String value) { + return addIfAbsent(property, primitive(value)); + } + + default JsonObjectBuilder addIfAbsent(String property, @Nullable Number value) { + return addIfAbsent(property, primitive(value)); + } + + default JsonObjectBuilder addIfAbsent(String property, @Nullable Boolean value) { + return addIfAbsent(property, primitive(value)); + } + + default JsonObjectBuilder addIfAbsent(String property, @Nullable Character value) { + return addIfAbsent(property, primitive(value)); + } + + default JsonObjectBuilder addAll(Iterable> iterable, boolean deepCopy) { + Objects.requireNonNull(iterable, "iterable"); + for (Map.Entry e : iterable) { + if (e == null || e.getKey() == null) { + continue; + } + add(e.getKey(), e.getValue(), deepCopy); + } + return this; + } + + default JsonObjectBuilder addAll(Iterable> iterable) { + return addAll(iterable, false); + } + + default JsonObjectBuilder addAll(Stream> stream, boolean deepCopy) { + Objects.requireNonNull(stream, "stream"); + stream.forEach(e -> { + if (e == null || e.getKey() == null) { + return; + } + add(e.getKey(), e.getValue(), deepCopy); + }); + return this; + } + + default JsonObjectBuilder addAll(Stream> stream) { + return addAll(stream, false); + } + + default JsonObjectBuilder addAll(JsonObject object, boolean deepCopy) { + Objects.requireNonNull(object, "object"); + return addAll(object.entrySet(), deepCopy); + } + + default JsonObjectBuilder addAll(JsonObject object) { + return addAll(object, false); + } + + default JsonObjectBuilder addAllSerializables(Iterable> iterable) { + Objects.requireNonNull(iterable, "iterable"); + for (Map.Entry e : iterable) { + if (e == null || e.getKey() == null) { + continue; + } + add(e.getKey(), e.getValue()); + } + return this; + } + + default JsonObjectBuilder addAllStrings(Iterable> iterable) { + Objects.requireNonNull(iterable, "iterable"); + for (Map.Entry e : iterable) { + if (e == null || e.getKey() == null) { + continue; + } + add(e.getKey(), e.getValue()); + } + return this; + } + + default JsonObjectBuilder addAllNumbers(Iterable> iterable) { + Objects.requireNonNull(iterable, "iterable"); + for (Map.Entry e : iterable) { + if (e == null || e.getKey() == null) { + continue; + } + add(e.getKey(), e.getValue()); + } + return this; + } + + default JsonObjectBuilder addAllBooleans(Iterable> iterable) { + Objects.requireNonNull(iterable, "iterable"); + for (Map.Entry e : iterable) { + if (e == null || e.getKey() == null) { + continue; + } + add(e.getKey(), e.getValue()); + } + return this; + } + + default JsonObjectBuilder addAllCharacters(Iterable> iterable) { + Objects.requireNonNull(iterable, "iterable"); + for (Map.Entry e : iterable) { + if (e == null || e.getKey() == null) { + continue; + } + add(e.getKey(), e.getValue()); + } + return this; + } + + default JsonObjectBuilder addAllIfAbsent(Iterable> iterable, boolean deepCopy) { + Objects.requireNonNull(iterable, "iterable"); + for (Map.Entry e : iterable) { + if (e == null || e.getKey() == null) { + continue; + } + addIfAbsent(e.getKey(), e.getValue(), deepCopy); + } + return this; + } + + default JsonObjectBuilder addAllIfAbsent(Iterable> iterable) { + return addAllIfAbsent(iterable, false); + } + + default JsonObjectBuilder addAllIfAbsent(Stream> stream, boolean deepCopy) { + Objects.requireNonNull(stream, "stream"); + stream.forEach(e -> { + if (e == null || e.getKey() == null) { + return; + } + addIfAbsent(e.getKey(), e.getValue(), deepCopy); + }); + return this; + } + + default JsonObjectBuilder addAllIfAbsent(Stream> stream) { + return addAllIfAbsent(stream, false); + } + + default JsonObjectBuilder addAllIfAbsent(JsonObject object, boolean deepCopy) { + Objects.requireNonNull(object, "object"); + return addAllIfAbsent(object.entrySet(), deepCopy); + } + + default JsonObjectBuilder addAllIfAbsent(JsonObject object) { + return addAllIfAbsent(object, false); + } + + default JsonObjectBuilder addAllSerializablesIfAbsent(Iterable> iterable) { + Objects.requireNonNull(iterable, "iterable"); + for (Map.Entry e : iterable) { + if (e == null || e.getKey() == null) { + continue; + } + addIfAbsent(e.getKey(), e.getValue()); + } + return this; + } + + default JsonObjectBuilder addAllStringsIfAbsent(Iterable> iterable) { + Objects.requireNonNull(iterable, "iterable"); + for (Map.Entry e : iterable) { + if (e == null || e.getKey() == null) { + continue; + } + addIfAbsent(e.getKey(), e.getValue()); + } + return this; + } + + default JsonObjectBuilder addAllNumbersIfAbsent(Iterable> iterable) { + Objects.requireNonNull(iterable, "iterable"); + for (Map.Entry e : iterable) { + if (e == null || e.getKey() == null) { + continue; + } + addIfAbsent(e.getKey(), e.getValue()); + } + return this; + } + + default JsonObjectBuilder addAllBooleansIfAbsent(Iterable> iterable) { + Objects.requireNonNull(iterable, "iterable"); + for (Map.Entry e : iterable) { + if (e == null || e.getKey() == null) { + continue; + } + addIfAbsent(e.getKey(), e.getValue()); + } + return this; + } + + default JsonObjectBuilder addAllCharactersIfAbsent(Iterable> iterable) { + Objects.requireNonNull(iterable, "iterable"); + for (Map.Entry e : iterable) { + if (e == null || e.getKey() == null) { + continue; + } + addIfAbsent(e.getKey(), e.getValue()); + } + return this; + } + + JsonObject build(); + + } + + /** + * A {@link JsonArray} builder utility + */ + public interface JsonArrayBuilder extends Consumer { + + @Override + default void accept(JsonElement value) { + add(value); + } + + JsonArrayBuilder add(@Nullable JsonElement value, boolean copy); + + default JsonArrayBuilder add(@Nullable JsonElement value) { + return add(value, false); + } + + default JsonArrayBuilder add(@Nullable GsonSerializable serializable) { + return serializable == null ? add(nullValue()) : add(serializable.serialize()); + } + + default JsonArrayBuilder add(@Nullable String value) { + return add(primitive(value)); + } + + default JsonArrayBuilder add(@Nullable Number value) { + return add(primitive(value)); + } + + default JsonArrayBuilder add(@Nullable Boolean value) { + return add(primitive(value)); + } + + default JsonArrayBuilder add(@Nullable Character value) { + return add(primitive(value)); + } + + default JsonArrayBuilder addAll(Iterable iterable, boolean copy) { + Objects.requireNonNull(iterable, "iterable"); + for (T e : iterable) { + add(e, copy); + } + return this; + } + + default JsonArrayBuilder addAll(Iterable iterable) { + return addAll(iterable, false); + } + + default JsonArrayBuilder addAll(Stream stream, boolean copy) { + Objects.requireNonNull(stream, "iterable"); + stream.forEach(e -> add(e, copy)); + return this; + } + + default JsonArrayBuilder addAll(Stream stream) { + return addAll(stream, false); + } + + default JsonArrayBuilder addSerializables(Iterable iterable) { + Objects.requireNonNull(iterable, "iterable"); + for (T e : iterable) { + add(e); + } + return this; + } + + default JsonArrayBuilder addStrings(Iterable iterable) { + Objects.requireNonNull(iterable, "iterable"); + for (String e : iterable) { + add(e); + } + return this; + } + + default JsonArrayBuilder addNumbers(Iterable iterable) { + Objects.requireNonNull(iterable, "iterable"); + for (T e : iterable) { + add(e); + } + return this; + } + + default JsonArrayBuilder addBooleans(Iterable iterable) { + Objects.requireNonNull(iterable, "iterable"); + for (Boolean e : iterable) { + add(e); + } + return this; + } + + default JsonArrayBuilder addCharacters(Iterable iterable) { + Objects.requireNonNull(iterable, "iterable"); + for (Character e : iterable) { + add(e); + } + return this; + } + + JsonArray build(); + + } + + private static final class JsonObjectBuilderImpl implements JsonObjectBuilder { + private final JsonObject handle; + + private JsonObjectBuilderImpl(JsonObject handle) { + this.handle = handle; + } + + @Override + public JsonObjectBuilder add(String property, @Nullable JsonElement value, boolean copy) { + Objects.requireNonNull(property, "property"); + if (value == null) { + value = nullValue(); + } + + if (copy && value.isJsonObject()) { + this.handle.add(property, object(value.getAsJsonObject(), true).build()); + } else if (copy && value.isJsonArray()) { + this.handle.add(property, array(value.getAsJsonArray(), true).build()); + } else { + this.handle.add(property, value); + } + return this; + } + + @Override + public JsonObjectBuilder addIfAbsent(String property, @Nullable JsonElement value, boolean copy) { + Objects.requireNonNull(property, "property"); + if (this.handle.has(property)) { + return this; + } + return add(property, value, copy); + } + + @Override + public JsonObject build() { + return this.handle; + } + } + + private static final class JsonArrayBuilderImpl implements JsonArrayBuilder { + private final JsonArray handle; + + private JsonArrayBuilderImpl(JsonArray handle) { + this.handle = handle; + } + + @Override + public JsonArrayBuilder add(@Nullable JsonElement value, boolean copy) { + if (value == null) { + value = nullValue(); + } + + if (copy && value.isJsonObject()) { + this.handle.add(object(value.getAsJsonObject(), true).build()); + } else if (copy && value.isJsonArray()) { + this.handle.add(array(value.getAsJsonArray(), true).build()); + } else { + this.handle.add(value); + } + + return this; + } + + @Override + public JsonArray build() { + return this.handle; + } + } + + private JsonBuilder() { + throw new UnsupportedOperationException("This class cannot be instantiated"); + } + +} diff --git a/rpgregions/src/main/java/me/lucko/helper/serialize/Base64Util.java b/rpgregions/src/main/java/me/lucko/helper/serialize/Base64Util.java new file mode 100644 index 0000000..502b504 --- /dev/null +++ b/rpgregions/src/main/java/me/lucko/helper/serialize/Base64Util.java @@ -0,0 +1,53 @@ +/* + * This file is part of helper, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.helper.serialize; + +import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder; + +import java.util.Base64; + +class Base64Util { + + public static String encode(byte[] buf) { + return Base64.getEncoder().encodeToString(buf); + } + + public static byte[] decode(String src) { + try { + return Base64.getDecoder().decode(src); + } catch (IllegalArgumentException e) { + // compat with the previously used base64 encoder + try { + return Base64Coder.decodeLines(src); + } catch (Exception ignored) { + throw e; + } + } + } + + private Base64Util() {} + +} diff --git a/rpgregions/src/main/java/me/lucko/helper/serialize/InventorySerialization.java b/rpgregions/src/main/java/me/lucko/helper/serialize/InventorySerialization.java new file mode 100644 index 0000000..0b789be --- /dev/null +++ b/rpgregions/src/main/java/me/lucko/helper/serialize/InventorySerialization.java @@ -0,0 +1,145 @@ +/* + * This file is part of helper, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.helper.serialize; + +import org.bukkit.Bukkit; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.util.io.BukkitObjectInputStream; +import org.bukkit.util.io.BukkitObjectOutputStream; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +public final class InventorySerialization { + + public static byte[] encodeItemStack(ItemStack item) { + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + try (BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream)) { + dataOutput.writeObject(item); + return outputStream.toByteArray(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static String encodeItemStackToString(ItemStack item) { + return Base64Util.encode(encodeItemStack(item)); + } + + public static ItemStack decodeItemStack(byte[] buf) { + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(buf)) { + try (BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream)) { + return (ItemStack) dataInput.readObject(); + } + } catch (ClassNotFoundException | IOException e) { + throw new RuntimeException(e); + } + } + + public static ItemStack decodeItemStack(String data) { + return decodeItemStack(Base64Util.decode(data)); + } + + public static byte[] encodeItemStacks(ItemStack[] items) { + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + try (BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream)) { + dataOutput.writeInt(items.length); + for (ItemStack item : items) { + dataOutput.writeObject(item); + } + return outputStream.toByteArray(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static String encodeItemStacksToString(ItemStack[] items) { + return Base64Util.encode(encodeItemStacks(items)); + } + + public static ItemStack[] decodeItemStacks(byte[] buf) { + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(buf)) { + try (BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream)) { + ItemStack[] items = new ItemStack[dataInput.readInt()]; + for (int i = 0; i < items.length; i++) { + items[i] = (ItemStack) dataInput.readObject(); + } + return items; + } + } catch (ClassNotFoundException | IOException e) { + throw new RuntimeException(e); + } + } + + public static ItemStack[] decodeItemStacks(String data) { + return decodeItemStacks(Base64Util.decode(data)); + } + + public static byte[] encodeInventory(Inventory inventory) { + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + try (BukkitObjectOutputStream dataOutput = new BukkitObjectOutputStream(outputStream)) { + dataOutput.writeInt(inventory.getSize()); + for (int i = 0; i < inventory.getSize(); i++) { + dataOutput.writeObject(inventory.getItem(i)); + } + return outputStream.toByteArray(); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static String encodeInventoryToString(Inventory inventory) { + return Base64Util.encode(encodeInventory(inventory)); + } + + public static Inventory decodeInventory(byte[] buf, String title) { + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(buf)) { + try (BukkitObjectInputStream dataInput = new BukkitObjectInputStream(inputStream)) { + Inventory inventory = Bukkit.getServer().createInventory(null, dataInput.readInt(), title); + for (int i = 0; i < inventory.getSize(); i++) { + inventory.setItem(i, (ItemStack) dataInput.readObject()); + } + return inventory; + } + } catch (ClassNotFoundException | IOException e) { + throw new RuntimeException(e); + } + } + + public static Inventory decodeInventory(String data, String title) { + return decodeInventory(Base64Util.decode(data), title); + } + + private InventorySerialization() { + throw new UnsupportedOperationException("This class cannot be instantiated"); + } + +} diff --git a/rpgregions/src/main/java/me/lucko/helper/serialize/Serializers.java b/rpgregions/src/main/java/me/lucko/helper/serialize/Serializers.java new file mode 100644 index 0000000..0a45779 --- /dev/null +++ b/rpgregions/src/main/java/me/lucko/helper/serialize/Serializers.java @@ -0,0 +1,70 @@ +/* + * This file is part of helper, licensed under the MIT License. + * + * Copyright (c) lucko (Luck) + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package me.lucko.helper.serialize; + +import com.google.common.base.Preconditions; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; +import me.lucko.helper.gson.JsonBuilder; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +/** + * Utility methods for converting ItemStacks and Inventories to and from JSON. + */ +public final class Serializers { + + public static JsonPrimitive serializeItemstack(ItemStack item) { + return JsonBuilder.primitiveNonNull(InventorySerialization.encodeItemStackToString(item)); + } + + public static ItemStack deserializeItemstack(JsonElement data) { + Preconditions.checkArgument(data.isJsonPrimitive()); + return InventorySerialization.decodeItemStack(data.getAsString()); + } + + public static JsonPrimitive serializeItemstacks(ItemStack[] items) { + return JsonBuilder.primitiveNonNull(InventorySerialization.encodeItemStacksToString(items)); + } + + public static JsonPrimitive serializeInventory(Inventory inventory) { + return JsonBuilder.primitiveNonNull(InventorySerialization.encodeInventoryToString(inventory)); + } + + public static ItemStack[] deserializeItemstacks(JsonElement data) { + Preconditions.checkArgument(data.isJsonPrimitive()); + return InventorySerialization.decodeItemStacks(data.getAsString()); + } + + public static Inventory deserializeInventory(JsonElement data, String title) { + Preconditions.checkArgument(data.isJsonPrimitive()); + return InventorySerialization.decodeInventory(data.getAsString(), title); + } + + private Serializers() { + throw new UnsupportedOperationException("This class cannot be instantiated"); + } +} diff --git a/rpgregions/src/main/java/net/islandearth/rpgregions/commands/RPGRegionsCommand.java b/rpgregions/src/main/java/net/islandearth/rpgregions/commands/RPGRegionsCommand.java index 2fb3a98..079994e 100644 --- a/rpgregions/src/main/java/net/islandearth/rpgregions/commands/RPGRegionsCommand.java +++ b/rpgregions/src/main/java/net/islandearth/rpgregions/commands/RPGRegionsCommand.java @@ -139,7 +139,6 @@ public class RPGRegionsCommand extends BaseCommand { sender.sendMessage(StringUtils.colour("&aSet name of region '" + region + "' to: " + regionName)); } - @Subcommand("remove") @CommandPermission("rpgregions.remove") @CommandCompletion("@regions") diff --git a/rpgregions/src/main/java/net/islandearth/rpgregions/gson/ItemStackAdapter.java b/rpgregions/src/main/java/net/islandearth/rpgregions/gson/ItemStackAdapter.java index 1942642..502c856 100644 --- a/rpgregions/src/main/java/net/islandearth/rpgregions/gson/ItemStackAdapter.java +++ b/rpgregions/src/main/java/net/islandearth/rpgregions/gson/ItemStackAdapter.java @@ -8,6 +8,8 @@ import com.google.gson.JsonElement; import com.google.gson.JsonSerializationContext; import com.google.gson.JsonSerializer; import com.google.gson.reflect.TypeToken; +import me.lucko.helper.serialize.Serializers; +import net.islandearth.rpgregions.api.RPGRegionsAPI; import org.bukkit.inventory.ItemStack; import java.lang.reflect.Type; @@ -24,11 +26,16 @@ public class ItemStackAdapter implements JsonSerializer, JsonDeserial @Override public ItemStack deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext context) { - return ItemStack.deserialize(gson.fromJson(jsonElement, new TypeToken>(){}.getType())); + try { + return Serializers.deserializeItemstack(jsonElement); + } catch (Exception e) { // Legacy data, load it as normal, when it's next saved it will be normal. + RPGRegionsAPI.getAPI().getLogger().warning("Trying to migrate legacy ItemStack..."); + return ItemStack.deserialize(gson.fromJson(jsonElement, new TypeToken>(){}.getType())); + } } @Override public JsonElement serialize(ItemStack itemStack, Type type, JsonSerializationContext context) { - return gson.toJsonTree(itemStack.serialize()); + return Serializers.serializeItemstack(itemStack); } } \ No newline at end of file