/* * This file is part of LanternServer, licensed under the MIT License (MIT). * * Copyright (c) LanternPowered <https://www.lanternpowered.org> * Copyright (c) SpongePowered <https://www.spongepowered.org> * 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 org.lanternpowered.server.data.persistence; import com.google.common.collect.HashMultimap; import com.google.common.collect.Multimap; import com.google.common.reflect.TypeToken; import org.lanternpowered.server.data.LanternDataManager; import org.spongepowered.api.CatalogType; import org.spongepowered.api.Sponge; import org.spongepowered.api.data.DataContainer; import org.spongepowered.api.data.DataQuery; import org.spongepowered.api.data.DataSerializable; import org.spongepowered.api.data.DataView; import org.spongepowered.api.data.MemoryDataContainer; import org.spongepowered.api.data.persistence.DataBuilder; import org.spongepowered.api.data.persistence.InvalidDataException; import java.lang.reflect.TypeVariable; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.stream.Collector; import java.util.stream.Collectors; public final class DataTypeSerializers { @SuppressWarnings("unchecked") public static void registerSerializers(LanternDataManager dataManager) { dataManager.registerTypeSerializer(new TypeToken<Multimap<?,?>>() {}, new MultimapSerializer()); dataManager.registerTypeSerializer(new TypeToken<Map<?,?>>() {}, new MapSerializer()); dataManager.registerTypeSerializer(new TypeToken<List<?>>() {}, new ListSerializer()); dataManager.registerTypeSerializer(new TypeToken<Set<?>>() {}, new SetSerializer()); dataManager.registerTypeSerializer((TypeToken) new TypeToken<Enum<?>>() {}, new EnumSerializer()); dataManager.registerTypeSerializer(TypeToken.of(CatalogType.class), new CatalogTypeSerializer()); dataManager.registerTypeSerializer(TypeToken.of(Number.class), new NumberSerializer()); dataManager.registerTypeSerializer(TypeToken.of(String.class), new StringSerializer()); dataManager.registerTypeSerializer(TypeToken.of(Boolean.class), new BooleanSerializer()); dataManager.registerTypeSerializer(TypeToken.of(DataSerializable.class), new DataSerializableSerializer()); } private static class DataSerializableSerializer implements DataViewTypeSerializer<DataSerializable> { @SuppressWarnings("unchecked") @Override public DataSerializable deserialize(TypeToken<?> type, DataTypeSerializerContext ctx, DataView data) throws InvalidDataException { DataBuilder<DataSerializable> dataBuilder = (DataBuilder<DataSerializable>) LanternDataManager.get() .getBuilder((Class<? extends DataSerializable>) type.getRawType()) .orElseThrow(() -> new IllegalStateException("Wasn't able to find a DataBuilder for the DataSerializable: " + type)); return dataBuilder.build(data).orElseThrow(() -> new InvalidDataException("Unable to deserializer the " + type)); } @Override public DataView serialize(TypeToken<?> type, DataTypeSerializerContext ctx, DataSerializable obj) throws InvalidDataException { return obj.toContainer(); } } private static class CatalogTypeSerializer implements DataTypeSerializer<CatalogType, String> { @SuppressWarnings("unchecked") @Override public CatalogType deserialize(TypeToken<?> type, DataTypeSerializerContext ctx, String data) throws InvalidDataException { Optional<CatalogType> catalogType = Sponge.getRegistry().getType((Class<CatalogType>) type.getRawType(), data); if (!catalogType.isPresent()) { throw new InvalidDataException("The catalog type " + data + " of type " + type.toString() + " is missing."); } return catalogType.get(); } @Override public String serialize(TypeToken<?> type, DataTypeSerializerContext ctx, CatalogType obj) throws InvalidDataException { return obj.getId(); } } private static class MultimapSerializer implements DataTypeSerializer<Multimap<?, ?>, List<DataView>> { private static final DataQuery KEY = DataQuery.of("K"); private static final DataQuery VALUE = DataQuery.of("V"); private static final DataQuery ENTRIES = DataQuery.of("E"); private final TypeVariable<Class<Multimap>> keyTypeVariable = Multimap.class.getTypeParameters()[0]; private final TypeVariable<Class<Multimap>> valueTypeVariable = Multimap.class.getTypeParameters()[1]; @SuppressWarnings("unchecked") @Override public Multimap<?, ?> deserialize(TypeToken<?> type, DataTypeSerializerContext ctx, List<DataView> entries) throws InvalidDataException { TypeToken<?> keyType = type.resolveType(this.keyTypeVariable); TypeToken<?> valueType = type.resolveType(this.valueTypeVariable); DataTypeSerializer keySerial = ctx.getSerializers().getTypeSerializer(keyType) .orElseThrow(() -> new IllegalStateException("Wasn't able to find a type serializer for: " + keyType.toString())); DataTypeSerializer valueSerial = ctx.getSerializers().getTypeSerializer(valueType) .orElseThrow(() -> new IllegalStateException("Wasn't able to find a type serializer for: " + valueType.toString())); Multimap map = HashMultimap.create(); for (DataView entry : entries) { Object key = keySerial.deserialize(type, ctx, entry.get(KEY) .orElseThrow(() -> new InvalidDataException("Entry is missing a key."))); if (!entry.contains(VALUE) && entry.contains(ENTRIES)) { throw new InvalidDataException("Entry is missing values."); } List<?> dataViews = entry.getList(ENTRIES).orElse(null); if (dataViews != null) { dataViews.forEach(o -> { Object value = valueSerial.deserialize(type, ctx, o); map.put(key, value); }); } else { Object value = valueSerial.deserialize(type, ctx, entry.get(VALUE).get()); map.put(key, value); } } return map; } @SuppressWarnings("unchecked") @Override public List<DataView> serialize(TypeToken<?> type, DataTypeSerializerContext ctx, Multimap<?, ?> obj) throws InvalidDataException { TypeToken<?> keyType = type.resolveType(this.keyTypeVariable); TypeToken<?> valueType = type.resolveType(this.valueTypeVariable); DataTypeSerializer keySerial = ctx.getSerializers().getTypeSerializer(keyType) .orElseThrow(() -> new IllegalStateException("Wasn't able to find a type serializer for: " + keyType.toString())); DataTypeSerializer valueSerial = ctx.getSerializers().getTypeSerializer(valueType) .orElseThrow(() -> new IllegalStateException("Wasn't able to find a type serializer for: " + valueType.toString())); List<DataView> dataViews = new ArrayList<>(); for (Object key : obj.keySet()) { DataContainer dataContainer = new MemoryDataContainer(); dataContainer.set(KEY, keySerial.serialize(keyType, ctx, key)); Collection<Object> values = ((Multimap) obj).get(key); if (values.size() == 1) { dataContainer.set(VALUE, valueSerial.serialize(valueType, ctx, values.iterator().next())); } else { dataContainer.set(ENTRIES, values.stream().map(v -> valueSerial.serialize(valueType, ctx, v)).collect(Collectors.toList())); } dataViews.add(dataContainer); } return dataViews; } } private static class MapSerializer implements DataTypeSerializer<Map<?, ?>, List<DataView>> { private static final DataQuery KEY = DataQuery.of("K"); private static final DataQuery VALUE = DataQuery.of("V"); private final TypeVariable<Class<Map>> keyTypeVariable = Map.class.getTypeParameters()[0]; private final TypeVariable<Class<Map>> valueTypeVariable = Map.class.getTypeParameters()[1]; @SuppressWarnings("unchecked") @Override public Map<?, ?> deserialize(TypeToken<?> type, DataTypeSerializerContext ctx, List<DataView> entries) throws InvalidDataException { TypeToken<?> keyType = type.resolveType(this.keyTypeVariable); TypeToken<?> valueType = type.resolveType(this.valueTypeVariable); DataTypeSerializer keySerial = ctx.getSerializers().getTypeSerializer(keyType) .orElseThrow(() -> new IllegalStateException("Wasn't able to find a type serializer for: " + keyType.toString())); DataTypeSerializer valueSerial = ctx.getSerializers().getTypeSerializer(valueType) .orElseThrow(() -> new IllegalStateException("Wasn't able to find a type serializer for: " + valueType.toString())); Map map = new HashMap<>(); for (DataView entry : entries) { Object key = keySerial.deserialize(type, ctx, entry.get(KEY) .orElseThrow(() -> new InvalidDataException("Entry is missing a key."))); Object value = valueSerial.deserialize(type, ctx, entry.get(VALUE) .orElseThrow(() -> new InvalidDataException("Entry is missing a value."))); map.put(key, value); } return map; } @SuppressWarnings("unchecked") @Override public List<DataView> serialize(TypeToken<?> type, DataTypeSerializerContext ctx, Map<?, ?> obj) throws InvalidDataException { TypeToken<?> keyType = type.resolveType(this.keyTypeVariable); TypeToken<?> valueType = type.resolveType(this.valueTypeVariable); DataTypeSerializer keySerial = ctx.getSerializers().getTypeSerializer(keyType) .orElseThrow(() -> new IllegalStateException("Wasn't able to find a type serializer for: " + keyType.toString())); DataTypeSerializer valueSerial = ctx.getSerializers().getTypeSerializer(valueType) .orElseThrow(() -> new IllegalStateException("Wasn't able to find a type serializer for: " + valueType.toString())); return obj.entrySet().stream().map(entry -> new MemoryDataContainer() .set(KEY, keySerial.serialize(keyType, ctx, entry.getKey())) .set(VALUE, valueSerial.serialize(valueType, ctx, entry.getValue()))).collect(Collectors.toList()); } } private static class ListSerializer implements DataTypeSerializer<List<?>, List<Object>> { private final TypeVariable<Class<List>> typeVariable = List.class.getTypeParameters()[0]; @SuppressWarnings("unchecked") @Override public List<?> deserialize(TypeToken<?> type, DataTypeSerializerContext ctx, List<Object> data) throws InvalidDataException { TypeToken<?> elementType = type.resolveType(this.typeVariable); DataTypeSerializer elementSerial = ctx.getSerializers().getTypeSerializer(elementType) .orElseThrow(() -> new IllegalStateException("Wasn't able to find a type serializer for: " + elementType.toString())); return (List) data.stream() .map(object -> elementSerial.deserialize(elementType, ctx, object)) .collect((Collector) Collectors.toList()); } @SuppressWarnings("unchecked") @Override public List<Object> serialize(TypeToken<?> type, DataTypeSerializerContext ctx, List<?> obj) throws InvalidDataException { TypeToken<?> elementType = type.resolveType(this.typeVariable); DataTypeSerializer elementSerial = ctx.getSerializers().getTypeSerializer(elementType) .orElseThrow(() -> new IllegalStateException("Wasn't able to find a type serializer for: " + elementType.toString())); return obj.stream().map(object -> elementSerial.serialize(elementType, ctx, object)).collect(Collectors.toList()); } } private static class SetSerializer implements DataTypeSerializer<Set<?>, List<Object>> { private final TypeVariable<Class<Set>> typeVariable = Set.class.getTypeParameters()[0]; @SuppressWarnings("unchecked") @Override public Set<?> deserialize(TypeToken<?> type, DataTypeSerializerContext ctx, List<Object> data) throws InvalidDataException { TypeToken<?> elementType = type.resolveType(this.typeVariable); DataTypeSerializer elementSerial = ctx.getSerializers().getTypeSerializer(elementType) .orElseThrow(() -> new IllegalStateException("Wasn't able to find a type serializer for: " + elementType.toString())); return (Set) data.stream() .map(object -> elementSerial.deserialize(elementType, ctx, object)) .collect((Collector) Collectors.toSet()); } @SuppressWarnings("unchecked") @Override public List<Object> serialize(TypeToken<?> type, DataTypeSerializerContext ctx, Set<?> obj) throws InvalidDataException { TypeToken<?> elementType = type.resolveType(this.typeVariable); DataTypeSerializer elementSerial = ctx.getSerializers().getTypeSerializer(elementType) .orElseThrow(() -> new IllegalStateException("Wasn't able to find a type serializer for: " + elementType.toString())); return obj.stream().map(object -> elementSerial.serialize(elementType, ctx, object)).collect(Collectors.toList()); } } private static class EnumSerializer implements DataTypeSerializer<Enum, String> { @SuppressWarnings("unchecked") @Override public Enum deserialize(TypeToken<?> type, DataTypeSerializerContext ctx, String data) throws InvalidDataException { // Enum values should be uppercase data = data.toUpperCase(); Enum ret; try { ret = Enum.valueOf(((TypeToken) type).getRawType().asSubclass(Enum.class), data); } catch (IllegalArgumentException e) { throw new InvalidDataException("Invalid enum constant, expected a value of enum " + type + ", got " + data); } return ret; } @Override public String serialize(TypeToken<?> type, DataTypeSerializerContext ctx, Enum obj) throws InvalidDataException { return obj.name(); } } private static class NumberSerializer implements DataTypeSerializer<Number, Object> { private static final DataQuery TYPE = DataQuery.of("Type"); private static final DataQuery VALUE = DataQuery.of("Value"); @Override public Number deserialize(TypeToken<?> type, DataTypeSerializerContext ctx, Object data) throws InvalidDataException { if (data instanceof DataView) { final DataView view = (DataView) data; if (view.contains(TYPE) && view.contains(VALUE)) { String numberType = view.getString(TYPE).get(); String value = view.getString(VALUE).get(); if (numberType.equals(BigDecimal.class.getSimpleName())) { return new BigDecimal(value); } else if (numberType.equals(BigInteger.class.getSimpleName())) { return new BigInteger(value); } else { throw new InvalidDataException("Unsupported number type: " + numberType); } } throw new InvalidDataException("Unsupported number format: " + view); } else if (data instanceof Number) { return (Number) data; } throw new InvalidDataException("Unsupported data type: " + data.getClass().getName()); } @Override public Object serialize(TypeToken<?> type, DataTypeSerializerContext ctx, Number obj) throws InvalidDataException { if (obj instanceof BigDecimal || obj instanceof BigInteger) { return new MemoryDataContainer() .set(TYPE, obj.getClass().getSimpleName()) .set(VALUE, obj.toString()); } return obj; } } private static class BooleanSerializer implements DataTypeSerializer<Boolean, Boolean> { @Override public Boolean deserialize(TypeToken<?> type, DataTypeSerializerContext ctx, Boolean data) throws InvalidDataException { return data; } @Override public Boolean serialize(TypeToken<?> type, DataTypeSerializerContext ctx, Boolean obj) throws InvalidDataException { return obj; } } private static class StringSerializer implements DataTypeSerializer<String, String> { @Override public String deserialize(TypeToken<?> type, DataTypeSerializerContext ctx, String data) throws InvalidDataException { return data; } @Override public String serialize(TypeToken<?> type, DataTypeSerializerContext ctx, String obj) throws InvalidDataException { return obj; } } }