Update to configurate 4.2.0 (#12869)

This commit is contained in:
Jake Potrebic
2025-07-20 14:05:42 -07:00
committed by GitHub
parent a2d37f12fb
commit 617e5a46a1
36 changed files with 623 additions and 292 deletions

View File

@@ -154,8 +154,7 @@ dependencies {
implementation("io.netty:netty-codec-haproxy:4.1.118.Final") // Add support for proxy protocol implementation("io.netty:netty-codec-haproxy:4.1.118.Final") // Add support for proxy protocol
implementation("org.apache.logging.log4j:log4j-iostreams:2.24.1") implementation("org.apache.logging.log4j:log4j-iostreams:2.24.1")
implementation("org.ow2.asm:asm-commons:9.8") implementation("org.ow2.asm:asm-commons:9.8")
implementation("org.spongepowered:configurate-yaml:4.2.0-20250225.064233-199") implementation("org.spongepowered:configurate-yaml:4.2.0")
implementation("org.spongepowered:configurate-core:4.2.0-20250225.064233-204") // Pinned dependency of above pinned yaml snapshot.
// Deps that were previously in the API but have now been moved here for backwards compat, eventually to be removed // Deps that were previously in the API but have now been moved here for backwards compat, eventually to be removed
runtimeOnly("commons-lang:commons-lang:2.6") runtimeOnly("commons-lang:commons-lang:2.6")

View File

@@ -3,6 +3,7 @@ package io.papermc.paper.configuration;
import com.mojang.logging.LogUtils; import com.mojang.logging.LogUtils;
import io.papermc.paper.FeatureHooks; import io.papermc.paper.FeatureHooks;
import io.papermc.paper.configuration.constraint.Constraints; import io.papermc.paper.configuration.constraint.Constraints;
import io.papermc.paper.configuration.serializer.collection.map.WriteKeyBack;
import io.papermc.paper.configuration.type.number.DoubleOr; import io.papermc.paper.configuration.type.number.DoubleOr;
import io.papermc.paper.configuration.type.number.IntOr; import io.papermc.paper.configuration.type.number.IntOr;
import io.papermc.paper.util.sanitizer.ItemObfuscationBinding; import io.papermc.paper.util.sanitizer.ItemObfuscationBinding;
@@ -28,7 +29,7 @@ import java.util.Set;
@SuppressWarnings({"CanBeFinal", "FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic"}) @SuppressWarnings({"CanBeFinal", "FieldCanBeLocal", "FieldMayBeFinal", "NotNullFieldNotInitialized", "InnerClassMayBeStatic"})
public class GlobalConfiguration extends ConfigurationPart { public class GlobalConfiguration extends ConfigurationPart {
private static final Logger LOGGER = LogUtils.getLogger(); private static final Logger LOGGER = LogUtils.getLogger();
static final int CURRENT_VERSION = 29; // (when you change the version, change the comment, so it conflicts on rebases): <insert changes here> static final int CURRENT_VERSION = 30; // (when you change the version, change the comment, so it conflicts on rebases): upgrade packet to use ids
private static GlobalConfiguration instance; private static GlobalConfiguration instance;
public static boolean isFirstStart = false; public static boolean isFirstStart = false;
public static GlobalConfiguration get() { public static GlobalConfiguration get() {
@@ -274,7 +275,7 @@ public class GlobalConfiguration extends ConfigurationPart {
public class PacketLimiter extends ConfigurationPart { public class PacketLimiter extends ConfigurationPart {
public Component kickMessage = Component.translatable("disconnect.exceeded_packet_rate", NamedTextColor.RED); public Component kickMessage = Component.translatable("disconnect.exceeded_packet_rate", NamedTextColor.RED);
public PacketLimit allPackets = new PacketLimit(7.0, 500.0, PacketLimit.ViolateAction.KICK); public PacketLimit allPackets = new PacketLimit(7.0, 500.0, PacketLimit.ViolateAction.KICK);
public Map<Class<? extends Packet<?>>, PacketLimit> overrides = Map.of(ServerboundPlaceRecipePacket.class, new PacketLimit(4.0, 5.0, PacketLimit.ViolateAction.DROP)); public Map<@WriteKeyBack Class<? extends Packet<?>>, PacketLimit> overrides = Map.of(ServerboundPlaceRecipePacket.class, new PacketLimit(4.0, 5.0, PacketLimit.ViolateAction.DROP));
@ConfigSerializable @ConfigSerializable
public record PacketLimit(@Required double interval, @Required double maxPacketRate, ViolateAction action) { public record PacketLimit(@Required double interval, @Required double maxPacketRate, ViolateAction action) {

View File

@@ -5,21 +5,25 @@ import com.google.common.collect.Table;
import com.mojang.logging.LogUtils; import com.mojang.logging.LogUtils;
import io.leangen.geantyref.TypeToken; import io.leangen.geantyref.TypeToken;
import io.papermc.paper.configuration.legacy.RequiresSpigotInitialization; import io.papermc.paper.configuration.legacy.RequiresSpigotInitialization;
import io.papermc.paper.configuration.mapping.Definition;
import io.papermc.paper.configuration.mapping.FieldProcessor;
import io.papermc.paper.configuration.mapping.InnerClassFieldDiscoverer; import io.papermc.paper.configuration.mapping.InnerClassFieldDiscoverer;
import io.papermc.paper.configuration.mapping.MergeMap;
import io.papermc.paper.configuration.serializer.ComponentSerializer; import io.papermc.paper.configuration.serializer.ComponentSerializer;
import io.papermc.paper.configuration.serializer.EnumValueSerializer; import io.papermc.paper.configuration.serializer.EnumValueSerializer;
import io.papermc.paper.configuration.serializer.NbtPathSerializer; import io.papermc.paper.configuration.serializer.NbtPathSerializer;
import io.papermc.paper.configuration.serializer.PacketClassSerializer; import io.papermc.paper.configuration.serializer.ServerboundPacketClassSerializer;
import io.papermc.paper.configuration.serializer.ResourceLocationSerializer; import io.papermc.paper.configuration.serializer.ResourceLocationSerializer;
import io.papermc.paper.configuration.serializer.StringRepresentableSerializer; import io.papermc.paper.configuration.serializer.StringRepresentableSerializer;
import io.papermc.paper.configuration.serializer.collections.FastutilMapSerializer; import io.papermc.paper.configuration.serializer.collection.TableSerializer;
import io.papermc.paper.configuration.serializer.collections.MapSerializer; import io.papermc.paper.configuration.serializer.collection.map.FastutilMapSerializer;
import io.papermc.paper.configuration.serializer.collections.TableSerializer; import io.papermc.paper.configuration.serializer.collection.map.MapSerializer;
import io.papermc.paper.configuration.serializer.registry.RegistryHolderSerializer; import io.papermc.paper.configuration.serializer.registry.RegistryHolderSerializer;
import io.papermc.paper.configuration.serializer.registry.RegistryValueSerializer; import io.papermc.paper.configuration.serializer.registry.RegistryValueSerializer;
import io.papermc.paper.configuration.transformation.Transformations; import io.papermc.paper.configuration.transformation.Transformations;
import io.papermc.paper.configuration.transformation.global.LegacyPaperConfig; import io.papermc.paper.configuration.transformation.global.LegacyPaperConfig;
import io.papermc.paper.configuration.transformation.global.versioned.V29_LogIPs; import io.papermc.paper.configuration.transformation.global.versioned.V29_LogIPs;
import io.papermc.paper.configuration.transformation.global.versioned.V30_PacketIds;
import io.papermc.paper.configuration.transformation.world.FeatureSeedsGeneration; import io.papermc.paper.configuration.transformation.world.FeatureSeedsGeneration;
import io.papermc.paper.configuration.transformation.world.LegacyPaperWorldConfig; import io.papermc.paper.configuration.transformation.world.LegacyPaperWorldConfig;
import io.papermc.paper.configuration.transformation.world.versioned.V29_ZeroWorldHeight; import io.papermc.paper.configuration.transformation.world.versioned.V29_ZeroWorldHeight;
@@ -41,10 +45,12 @@ import it.unimi.dsi.fastutil.objects.Reference2ObjectMap;
import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.StandardCopyOption; import java.nio.file.StandardCopyOption;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
@@ -196,7 +202,7 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
} }
private static ObjectMapper.Factory.Builder defaultGlobalFactoryBuilder(ObjectMapper.Factory.Builder builder) { private static ObjectMapper.Factory.Builder defaultGlobalFactoryBuilder(ObjectMapper.Factory.Builder builder) {
return builder.addDiscoverer(InnerClassFieldDiscoverer.globalConfig()); return builder.addDiscoverer(InnerClassFieldDiscoverer.globalConfig(defaultFieldProcessors()));
} }
@Override @Override
@@ -209,7 +215,7 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
return options return options
.header(GLOBAL_HEADER) .header(GLOBAL_HEADER)
.serializers(builder -> builder .serializers(builder -> builder
.register(new PacketClassSerializer()) .register(new ServerboundPacketClassSerializer())
.register(new RegistryValueSerializer<>(new TypeToken<DataComponentType<?>>() {}, registryAccess, Registries.DATA_COMPONENT_TYPE, false)) .register(new RegistryValueSerializer<>(new TypeToken<DataComponentType<?>>() {}, registryAccess, Registries.DATA_COMPONENT_TYPE, false))
); );
} }
@@ -232,7 +238,7 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
return super.createWorldObjectMapperFactoryBuilder(contextMap) return super.createWorldObjectMapperFactoryBuilder(contextMap)
.addNodeResolver(new RequiresSpigotInitialization.Factory(contextMap.require(SPIGOT_WORLD_CONFIG_CONTEXT_KEY).get())) .addNodeResolver(new RequiresSpigotInitialization.Factory(contextMap.require(SPIGOT_WORLD_CONFIG_CONTEXT_KEY).get()))
.addNodeResolver(new NestedSetting.Factory()) .addNodeResolver(new NestedSetting.Factory())
.addDiscoverer(InnerClassFieldDiscoverer.worldConfig(createWorldConfigInstance(contextMap))); .addDiscoverer(InnerClassFieldDiscoverer.worldConfig(createWorldConfigInstance(contextMap), defaultFieldProcessors()));
} }
private static WorldConfiguration createWorldConfigInstance(ContextMap contextMap) { private static WorldConfiguration createWorldConfigInstance(ContextMap contextMap) {
@@ -291,6 +297,7 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
final ConfigurationTransformation.VersionedBuilder versionedBuilder = Transformations.versionedBuilder(); final ConfigurationTransformation.VersionedBuilder versionedBuilder = Transformations.versionedBuilder();
V29_LogIPs.apply(versionedBuilder); V29_LogIPs.apply(versionedBuilder);
V30_PacketIds.apply(versionedBuilder);
// ADD FUTURE VERSIONED TRANSFORMS TO versionedBuilder HERE // ADD FUTURE VERSIONED TRANSFORMS TO versionedBuilder HERE
versionedBuilder.build().apply(node); versionedBuilder.build().apply(node);
} }
@@ -334,6 +341,12 @@ public class PaperConfigurations extends Configurations<GlobalConfiguration, Wor
} }
} }
private static List<Definition<? extends Annotation, ?, ? extends FieldProcessor.Factory<?, ?>>> defaultFieldProcessors() {
return List.of(
MergeMap.DEFINITION
);
}
private static ContextMap createWorldContextMap(ServerLevel level) { private static ContextMap createWorldContextMap(ServerLevel level) {
return createWorldContextMap(level.levelStorageAccess.levelDirectory.path(), level.serverLevelData.getLevelName(), level.dimension().location(), level.spigotConfig, level.registryAccess(), level.getGameRules()); return createWorldContextMap(level.levelStorageAccess.levelDirectory.path(), level.serverLevelData.getLevelName(), level.dimension().location(), level.spigotConfig, level.registryAccess(), level.getGameRules());
} }

View File

@@ -8,7 +8,7 @@ import io.papermc.paper.configuration.legacy.MaxEntityCollisionsInitializer;
import io.papermc.paper.configuration.legacy.RequiresSpigotInitialization; import io.papermc.paper.configuration.legacy.RequiresSpigotInitialization;
import io.papermc.paper.configuration.mapping.MergeMap; import io.papermc.paper.configuration.mapping.MergeMap;
import io.papermc.paper.configuration.serializer.NbtPathSerializer; import io.papermc.paper.configuration.serializer.NbtPathSerializer;
import io.papermc.paper.configuration.serializer.collections.MapSerializer; import io.papermc.paper.configuration.serializer.collection.map.ThrowExceptions;
import io.papermc.paper.configuration.transformation.world.FeatureSeedsGeneration; import io.papermc.paper.configuration.transformation.world.FeatureSeedsGeneration;
import io.papermc.paper.configuration.type.BooleanOrDefault; import io.papermc.paper.configuration.type.BooleanOrDefault;
import io.papermc.paper.configuration.type.DespawnRange; import io.papermc.paper.configuration.type.DespawnRange;
@@ -193,15 +193,14 @@ public class WorldConfiguration extends ConfigurationPart {
} }
} }
@MapSerializer.ThrowExceptions public @ThrowExceptions Reference2ObjectMap<EntityType<?>, IntOr.Disabled> despawnTime = Util.make(new Reference2ObjectOpenHashMap<>(), map -> {
public Reference2ObjectMap<EntityType<?>, IntOr.Disabled> despawnTime = Util.make(new Reference2ObjectOpenHashMap<>(), map -> {
map.put(EntityType.SNOWBALL, IntOr.Disabled.DISABLED); map.put(EntityType.SNOWBALL, IntOr.Disabled.DISABLED);
map.put(EntityType.LLAMA_SPIT, IntOr.Disabled.DISABLED); map.put(EntityType.LLAMA_SPIT, IntOr.Disabled.DISABLED);
}); });
@PostProcess @PostProcess
public void precomputeDespawnDistances() throws SerializationException { public void precomputeDespawnDistances() throws SerializationException {
for (Map.Entry<MobCategory, DespawnRangePair> entry : this.despawnRanges.entrySet()) { for (final Map.Entry<MobCategory, DespawnRangePair> entry : this.despawnRanges.entrySet()) {
final MobCategory category = entry.getKey(); final MobCategory category = entry.getKey();
final DespawnRangePair range = entry.getValue(); final DespawnRangePair range = entry.getValue();
range.hard().preComputed(category.getDespawnDistance(), category.getSerializedName()); range.hard().preComputed(category.getDespawnDistance(), category.getSerializedName());

View File

@@ -0,0 +1,11 @@
package io.papermc.paper.configuration.mapping;
import io.leangen.geantyref.TypeToken;
import java.lang.annotation.Annotation;
public record Definition<A extends Annotation, T, F>(Class<A> annotation, TypeToken<T> type, F factory) {
public Definition(final Class<A> annotation, final Class<T> type, final F factory) {
this(annotation, TypeToken.get(type), factory);
}
}

View File

@@ -0,0 +1,16 @@
package io.papermc.paper.configuration.mapping;
import java.lang.reflect.AnnotatedType;
import org.jspecify.annotations.Nullable;
import org.spongepowered.configurate.serialize.SerializationException;
import static com.google.common.base.Preconditions.checkState;
public record DeserializedFieldInfo<V>(AnnotatedType fieldType, Object deserializedValue, @Nullable FieldProcessor<V> processor) {
@SuppressWarnings("unchecked")
public @Nullable V runProcessor(final @Nullable Object valueInField, final @Nullable Object deserializedValue) throws SerializationException {
checkState(this.processor != null, "processor is null");
return this.processor.process(this.fieldType, (V) deserializedValue, (V) valueInField);
}
}

View File

@@ -0,0 +1,16 @@
package io.papermc.paper.configuration.mapping;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedType;
import org.jspecify.annotations.Nullable;
import org.spongepowered.configurate.serialize.SerializationException;
public interface FieldProcessor<V> {
@Nullable V process(final AnnotatedType target, @Nullable V deserializedValue, @Nullable V valueInField) throws SerializationException;
interface Factory<A extends Annotation, T> {
FieldProcessor<T> make(A data, AnnotatedType annotatedType);
}
}

View File

@@ -1,52 +1,103 @@
package io.papermc.paper.configuration.mapping; package io.papermc.paper.configuration.mapping;
import io.leangen.geantyref.GenericTypeReflector;
import io.papermc.paper.configuration.ConfigurationPart; import io.papermc.paper.configuration.ConfigurationPart;
import io.papermc.paper.configuration.WorldConfiguration; import io.papermc.paper.configuration.WorldConfiguration;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedType; import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.SequencedMap;
import java.util.function.Supplier;
import org.jspecify.annotations.Nullable; import org.jspecify.annotations.Nullable;
import org.spongepowered.configurate.objectmapping.FieldDiscoverer; import org.spongepowered.configurate.objectmapping.FieldDiscoverer;
import org.spongepowered.configurate.serialize.SerializationException; import org.spongepowered.configurate.serialize.SerializationException;
import static io.leangen.geantyref.GenericTypeReflector.erase; import static io.leangen.geantyref.GenericTypeReflector.erase;
import static java.util.Objects.requireNonNullElseGet;
public final class InnerClassFieldDiscoverer implements FieldDiscoverer<Map<Field, Object>> { public final class InnerClassFieldDiscoverer implements FieldDiscoverer<Map<Field, DeserializedFieldInfo<?>>> {
private final InnerClassInstanceSupplier instanceSupplier; private final InnerClassInstanceSupplier instanceSupplier;
private final FieldDiscoverer<Map<Field, Object>> delegate; private final FieldDiscoverer<Map<Field, Object>> delegate;
private final Map<Class<? extends Annotation>, List<Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>>>> fieldProcessors;
@SuppressWarnings("unchecked") private InnerClassFieldDiscoverer(final InnerClassInstanceSupplier instanceSupplier, final FieldDiscoverer<Map<Field, Object>> delegate, final Map<Class<? extends Annotation>, List<Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>>>> fieldProcessors) {
public InnerClassFieldDiscoverer(final Map<Class<?>, Object> initialOverrides) { this.instanceSupplier = instanceSupplier;
this.instanceSupplier = new InnerClassInstanceSupplier(initialOverrides); this.delegate = delegate;
this.delegate = (FieldDiscoverer<Map<Field, Object>>) FieldDiscoverer.object(this.instanceSupplier); this.fieldProcessors = fieldProcessors;
} }
@SuppressWarnings({"unchecked", "rawtypes"})
@Override @Override
public @Nullable <V> InstanceFactory<Map<Field, Object>> discover(final AnnotatedType target, final FieldCollector<Map<Field, Object>, V> collector) throws SerializationException { public @Nullable <V> InstanceFactory<Map<Field, DeserializedFieldInfo<?>>> discover(final AnnotatedType target, final FieldCollector<Map<Field, DeserializedFieldInfo<?>>, V> collector) throws SerializationException {
final Class<?> clazz = erase(target.getType()); final Class<?> clazz = erase(target.getType());
if (ConfigurationPart.class.isAssignableFrom(clazz)) { if (ConfigurationPart.class.isAssignableFrom(clazz)) {
final FieldDiscoverer.@Nullable InstanceFactory<Map<Field, Object>> instanceFactoryDelegate = this.delegate.<V>discover(target, (name, type, annotations, deserializer, serializer) -> { // we don't care about the returned instance factory, we just need to call it to collect fields
if (!erase(type.getType()).equals(clazz.getEnclosingClass())) { // don't collect synth fields for inner classes final Object dummyInstance = this.delegate.<V>discover(target, (name, fieldType, container, deserializer, serializer) -> {
collector.accept(name, type, annotations, deserializer, serializer); if (!erase(fieldType.getType()).equals(clazz.getEnclosingClass())) { // don't collect synth fields for inner classes
FieldProcessor<?> processor = null;
processor: for (final Annotation annotation : container.getAnnotations()) {
final List<Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>>> definitions = this.fieldProcessors.get(annotation.annotationType());
if (definitions != null) {
for (final Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>> def : definitions) {
if (GenericTypeReflector.isSuperType(def.type().getType(), GenericTypeReflector.box(fieldType.getType()))) {
processor = ((FieldProcessor.Factory)def.factory()).make(annotation, fieldType);
break processor;
}
}
}
}
final FieldProcessor<?> finalProcessor = processor;
collector.accept(
name,
fieldType,
container,
(intermediate, newValue, implicitInitializer) -> {
// we only create this map to collect the field, it should only ever have 1 entry
final SequencedMap<Field, Object> map = new LinkedHashMap<>();
deserializer.accept(map, newValue, implicitInitializer);
final Object deserializedValue;
deserializedValue = requireNonNullElseGet(newValue, () -> new ImplicitProvider(implicitInitializer));
intermediate.put(map.firstEntry().getKey(), new DeserializedFieldInfo<>(fieldType, deserializedValue, finalProcessor));
},
serializer
);
} }
}); });
if (instanceFactoryDelegate instanceof MutableInstanceFactory<Map<Field, Object>> mutableInstanceFactoryDelegate) { if (dummyInstance == null) {
return new InnerClassInstanceFactory(this.instanceSupplier, mutableInstanceFactoryDelegate, target); return null;
} }
return new InnerClassInstanceFactory(this.instanceSupplier, target);
} }
return null; return null;
} }
public static FieldDiscoverer<?> worldConfig(WorldConfiguration worldConfiguration) { record ImplicitProvider(Supplier<Object> provider) {
}
@SuppressWarnings("unchecked")
private static InnerClassFieldDiscoverer create(final Map<Class<?>, Object> overrides, final List<Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>>> fieldProcessors) {
final Map<Class<? extends Annotation>, List<Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>>>> processors = new LinkedHashMap<>();
for (final Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>> definition : fieldProcessors) {
processors.computeIfAbsent(definition.annotation(), k -> new ArrayList<>()).add(definition);
}
final InnerClassInstanceSupplier instanceSupplier = new InnerClassInstanceSupplier(overrides);
return new InnerClassFieldDiscoverer(instanceSupplier, (FieldDiscoverer<Map<Field, Object>>) FieldDiscoverer.object(instanceSupplier), processors);
}
public static FieldDiscoverer<?> worldConfig(final WorldConfiguration worldConfiguration, final List<Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>>> fieldProcessors) {
final Map<Class<?>, Object> overrides = Map.of( final Map<Class<?>, Object> overrides = Map.of(
WorldConfiguration.class, worldConfiguration WorldConfiguration.class, worldConfiguration
); );
return new InnerClassFieldDiscoverer(overrides); return create(overrides, fieldProcessors);
} }
public static FieldDiscoverer<?> globalConfig() { public static FieldDiscoverer<?> globalConfig(final List<Definition<?, ?, ? extends FieldProcessor.Factory<?, ?>>> fieldProcessors) {
return new InnerClassFieldDiscoverer(Collections.emptyMap()); return create(Collections.emptyMap(), fieldProcessors);
} }
} }

View File

@@ -2,56 +2,68 @@ package io.papermc.paper.configuration.mapping;
import java.lang.reflect.AnnotatedType; import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Iterator; import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.function.Supplier;
import org.spongepowered.configurate.objectmapping.FieldDiscoverer; import org.spongepowered.configurate.objectmapping.FieldDiscoverer;
import org.spongepowered.configurate.serialize.SerializationException; import org.spongepowered.configurate.serialize.SerializationException;
import static io.leangen.geantyref.GenericTypeReflector.erase; import static io.leangen.geantyref.GenericTypeReflector.erase;
final class InnerClassInstanceFactory implements FieldDiscoverer.MutableInstanceFactory<Map<Field, Object>> { final class InnerClassInstanceFactory implements FieldDiscoverer.MutableInstanceFactory<Map<Field, DeserializedFieldInfo<?>>> {
private final InnerClassInstanceSupplier instanceSupplier; private final InnerClassInstanceSupplier instanceSupplier;
private final FieldDiscoverer.MutableInstanceFactory<Map<Field, Object>> fallback;
private final AnnotatedType targetType; private final AnnotatedType targetType;
InnerClassInstanceFactory(final InnerClassInstanceSupplier instanceSupplier, final FieldDiscoverer.MutableInstanceFactory<Map<Field, Object>> fallback, final AnnotatedType targetType) { InnerClassInstanceFactory(
final InnerClassInstanceSupplier instanceSupplier,
final AnnotatedType targetType
) {
this.instanceSupplier = instanceSupplier; this.instanceSupplier = instanceSupplier;
this.fallback = fallback;
this.targetType = targetType; this.targetType = targetType;
} }
@Override @Override
public Map<Field, Object> begin() { public Map<Field, DeserializedFieldInfo<?>> begin() {
return this.fallback.begin(); return new LinkedHashMap<>();
} }
@SuppressWarnings("unchecked")
@Override @Override
public void complete(final Object instance, final Map<Field, Object> intermediate) throws SerializationException { public void complete(final Object instance, final Map<Field, DeserializedFieldInfo<?>> intermediate) throws SerializationException {
final Iterator<Map.Entry<Field, Object>> iter = intermediate.entrySet().iterator(); // modeled off of native ObjectFieldDiscoverer
try { for (final Map.Entry<Field, DeserializedFieldInfo<?>> entry : intermediate.entrySet()) {
while (iter.hasNext()) { // manually merge any mergeable maps final boolean hasProcessor = entry.getValue().processor() != null;
Map.Entry<Field, Object> entry = iter.next(); try {
if (entry.getKey().isAnnotationPresent(MergeMap.class) && Map.class.isAssignableFrom(entry.getKey().getType()) && intermediate.get(entry.getKey()) instanceof Map<?, ?> map) { final Object valueInField = entry.getKey().get(instance);
iter.remove(); if (entry.getValue().deserializedValue() instanceof InnerClassFieldDiscoverer.ImplicitProvider(final Supplier<Object> provider)) {
Map<Object, Object> existingMap = (Map<Object, Object>) entry.getKey().get(instance); Object newFieldValue = provider.get();
if (existingMap != null) { if (hasProcessor) {
existingMap.putAll(map); newFieldValue = entry.getValue().runProcessor(valueInField, newFieldValue);
} else {
entry.getKey().set(instance, entry.getValue());
} }
if (newFieldValue != null) {
if (valueInField == null) {
entry.getKey().set(instance, newFieldValue);
}
}
} else {
Object newFieldValue = entry.getValue().deserializedValue();
if (hasProcessor) {
newFieldValue = entry.getValue().runProcessor(valueInField, newFieldValue);
}
entry.getKey().set(instance, newFieldValue);
} }
} catch (final IllegalAccessException e) {
throw new SerializationException(this.targetType.getType(), e);
} catch (final SerializationException ex) {
ex.initType(entry.getValue().fieldType());
} }
} catch (final IllegalAccessException e) {
throw new SerializationException(this.targetType.getType(), e);
} }
this.fallback.complete(instance, intermediate);
} }
@Override @Override
public Object complete(final Map<Field, Object> intermediate) throws SerializationException { public Object complete(final Map<Field, DeserializedFieldInfo<?>> intermediate) throws SerializationException {
final Object targetInstance = Objects.requireNonNull(this.instanceSupplier.instanceMap().get(erase(this.targetType.getType())), () -> this.targetType.getType() + " must already have an instance created"); final Object targetInstance = Objects.requireNonNull(this.instanceSupplier.instanceMap().get(erase(this.targetType.getType())), () -> this.targetType.getType() + " must already have an instance created");
this.complete(targetInstance, intermediate); this.complete(targetInstance, intermediate);
return targetInstance; return targetInstance;
@@ -59,6 +71,6 @@ final class InnerClassInstanceFactory implements FieldDiscoverer.MutableInstance
@Override @Override
public boolean canCreateInstances() { public boolean canCreateInstances() {
return this.fallback.canCreateInstances(); return true;
} }
} }

View File

@@ -57,7 +57,7 @@ final class InnerClassInstanceSupplier implements CheckedFunction<AnnotatedType,
final Object instance = instanceSupplier.get(); final Object instance = instanceSupplier.get();
this.instanceMap.put(type, instance); this.instanceMap.put(type, instance);
return () -> instance; return () -> instance;
} catch (ReflectiveOperationException e) { } catch (final ReflectiveOperationException e) {
throw new SerializationException(ConfigurationPart.class, target + " must be a valid ConfigurationPart", e); throw new SerializationException(ConfigurationPart.class, target + " must be a valid ConfigurationPart", e);
} }
} else { } else {

View File

@@ -1,11 +1,20 @@
package io.papermc.paper.configuration.mapping; package io.papermc.paper.configuration.mapping;
import com.google.common.collect.Sets;
import com.mojang.logging.LogUtils;
import io.papermc.paper.configuration.ConfigurationPart; import io.papermc.paper.configuration.ConfigurationPart;
import io.papermc.paper.configuration.serializer.collection.map.MapSerializer;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.ElementType; import java.lang.annotation.ElementType;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import java.lang.reflect.AnnotatedType;
import java.util.Map;
import java.util.Set;
import org.slf4j.Logger;
import static com.google.common.base.Preconditions.checkState;
/** /**
* For use in maps inside {@link ConfigurationPart}s that have default keys that shouldn't be removed by users * For use in maps inside {@link ConfigurationPart}s that have default keys that shouldn't be removed by users
@@ -17,4 +26,39 @@ import java.lang.annotation.Target;
@Target(ElementType.FIELD) @Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface MergeMap { public @interface MergeMap {
Logger LOGGER = LogUtils.getClassLogger();
Definition<MergeMap, Map<?, ?>, Factory> DEFINITION = new Definition<>(MergeMap.class, MapSerializer.TYPE, new Factory());
/**
* If marked as restricted, the field won't allow new keys beyond what
* is already in the field when deserializing.
*
* @return True if restricted
*/
boolean restricted() default true;
final class Factory implements FieldProcessor.Factory<MergeMap, Map<?, ?>> {
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public FieldProcessor<Map<?, ?>> make(final MergeMap data, final AnnotatedType annotatedType) {
return (target, deserializedValue, valueInField) -> {
if (valueInField != null && deserializedValue != null) {
if (data.restricted()) {
final Set<?> invalidKeys = Sets.difference(deserializedValue.keySet(), valueInField.keySet()).immutableCopy();
for (final Object invalidKey : invalidKeys) {
LOGGER.error("The key {} is not allowed to be added to the field {}", invalidKey, target);
}
((Map) deserializedValue).keySet().removeAll(invalidKeys);
}
((Map) valueInField).putAll(deserializedValue);
return valueInField;
} else {
checkState(!data.restricted() || valueInField != null, "If marked as restricted, field must have a value");
return deserializedValue;
}
};
}
}
} }

View File

@@ -1,26 +1,25 @@
package io.papermc.paper.configuration.serializer; package io.papermc.paper.configuration.serializer;
import java.lang.reflect.AnnotatedType;
import java.util.function.Predicate;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage; import net.kyori.adventure.text.minimessage.MiniMessage;
import org.spongepowered.configurate.serialize.ScalarSerializer; import org.spongepowered.configurate.serialize.ScalarSerializer;
import org.spongepowered.configurate.serialize.SerializationException; import org.spongepowered.configurate.serialize.SerializationException;
import java.lang.reflect.Type; public class ComponentSerializer extends ScalarSerializer.Annotated<Component> {
import java.util.function.Predicate;
public class ComponentSerializer extends ScalarSerializer<Component> {
public ComponentSerializer() { public ComponentSerializer() {
super(Component.class); super(Component.class);
} }
@Override @Override
public Component deserialize(Type type, Object obj) throws SerializationException { public Component deserialize(final AnnotatedType type, final Object obj) throws SerializationException {
return MiniMessage.miniMessage().deserialize(obj.toString()); return MiniMessage.miniMessage().deserialize(obj.toString());
} }
@Override @Override
protected Object serialize(Component component, Predicate<Class<?>> typeSupported) { protected Object serialize(final AnnotatedType type, final Component item, final Predicate<Class<?>> typeSupported) {
return MiniMessage.miniMessage().serialize(component); return MiniMessage.miniMessage().serialize(item);
} }
} }

View File

@@ -1,11 +1,10 @@
package io.papermc.paper.configuration.serializer; package io.papermc.paper.configuration.serializer;
import io.papermc.paper.configuration.type.EngineMode; import io.papermc.paper.configuration.type.EngineMode;
import org.spongepowered.configurate.serialize.ScalarSerializer;
import org.spongepowered.configurate.serialize.SerializationException;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.function.Predicate; import java.util.function.Predicate;
import org.spongepowered.configurate.serialize.ScalarSerializer;
import org.spongepowered.configurate.serialize.SerializationException;
public final class EngineModeSerializer extends ScalarSerializer<EngineMode> { public final class EngineModeSerializer extends ScalarSerializer<EngineMode> {
@@ -14,11 +13,11 @@ public final class EngineModeSerializer extends ScalarSerializer<EngineMode> {
} }
@Override @Override
public EngineMode deserialize(Type type, Object obj) throws SerializationException { public EngineMode deserialize(final Type type, final Object obj) throws SerializationException {
if (obj instanceof Integer id) { if (obj instanceof final Integer id) {
try { try {
return EngineMode.valueOf(id); return EngineMode.valueOf(id);
} catch (IllegalArgumentException e) { } catch (final IllegalArgumentException e) {
throw new SerializationException(id + " is not a valid id for type " + type + " for this node"); throw new SerializationException(id + " is not a valid id for type " + type + " for this node");
} }
} }
@@ -27,7 +26,7 @@ public final class EngineModeSerializer extends ScalarSerializer<EngineMode> {
} }
@Override @Override
protected Object serialize(EngineMode item, Predicate<Class<?>> typeSupported) { protected Object serialize(final EngineMode item, final Predicate<Class<?>> typeSupported) {
return item.getId(); return item.getId();
} }
} }

View File

@@ -2,7 +2,7 @@ package io.papermc.paper.configuration.serializer;
import com.mojang.logging.LogUtils; import com.mojang.logging.LogUtils;
import io.leangen.geantyref.TypeToken; import io.leangen.geantyref.TypeToken;
import java.lang.reflect.Type; import java.lang.reflect.AnnotatedType;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.function.Predicate; import java.util.function.Predicate;
@@ -17,7 +17,7 @@ import static io.leangen.geantyref.GenericTypeReflector.erase;
/** /**
* Enum serializer that lists options if fails and accepts `-` as `_`. * Enum serializer that lists options if fails and accepts `-` as `_`.
*/ */
public class EnumValueSerializer extends ScalarSerializer<Enum<?>> { public class EnumValueSerializer extends ScalarSerializer.Annotated<Enum<?>> {
private static final Logger LOGGER = LogUtils.getClassLogger(); private static final Logger LOGGER = LogUtils.getClassLogger();
@@ -27,23 +27,23 @@ public class EnumValueSerializer extends ScalarSerializer<Enum<?>> {
@SuppressWarnings({"rawtypes", "unchecked"}) @SuppressWarnings({"rawtypes", "unchecked"})
@Override @Override
public @Nullable Enum<?> deserialize(final Type type, final Object obj) throws SerializationException { public @Nullable Enum<?> deserialize(final AnnotatedType annotatedType, final Object obj) throws SerializationException {
final String enumConstant = obj.toString(); final String enumConstant = obj.toString();
final Class<? extends Enum> typeClass = erase(type).asSubclass(Enum.class); final Class<? extends Enum> typeClass = erase(annotatedType.getType()).asSubclass(Enum.class);
Enum<?> ret = EnumLookup.lookupEnum(typeClass, enumConstant); Enum<?> ret = EnumLookup.lookupEnum(typeClass, enumConstant);
if (ret == null) { if (ret == null) {
ret = EnumLookup.lookupEnum(typeClass, enumConstant.replace("-", "_")); ret = EnumLookup.lookupEnum(typeClass, enumConstant.replace("-", "_"));
} }
if (ret == null) { if (ret == null) {
boolean longer = typeClass.getEnumConstants().length > 10; final boolean longer = typeClass.getEnumConstants().length > 10;
List<String> options = Arrays.stream(typeClass.getEnumConstants()).limit(10L).map(Enum::name).toList(); final List<String> options = Arrays.stream(typeClass.getEnumConstants()).limit(10L).map(Enum::name).toList();
LOGGER.error("Invalid enum constant provided, expected one of [{}{}], but got {}", String.join(", ", options), longer ? ", ..." : "", enumConstant); LOGGER.error("Invalid enum constant provided, expected one of [{}{}], but got {}", String.join(", ", options), longer ? ", ..." : "", enumConstant);
} }
return ret; return ret;
} }
@Override @Override
public Object serialize(final Enum<?> item, final Predicate<Class<?>> typeSupported) { public Object serialize(final AnnotatedType type, final Enum<?> item, final Predicate<Class<?>> typeSupported) {
return item.name(); return item.name();
} }
} }

View File

@@ -3,7 +3,7 @@ package io.papermc.paper.configuration.serializer;
import com.destroystokyo.paper.util.SneakyThrow; import com.destroystokyo.paper.util.SneakyThrow;
import com.mojang.brigadier.StringReader; import com.mojang.brigadier.StringReader;
import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.CommandSyntaxException;
import java.lang.reflect.Type; import java.lang.reflect.AnnotatedType;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Predicate; import java.util.function.Predicate;
@@ -11,7 +11,7 @@ import net.minecraft.commands.arguments.NbtPathArgument;
import org.spongepowered.configurate.serialize.ScalarSerializer; import org.spongepowered.configurate.serialize.ScalarSerializer;
import org.spongepowered.configurate.serialize.SerializationException; import org.spongepowered.configurate.serialize.SerializationException;
public class NbtPathSerializer extends ScalarSerializer<NbtPathArgument.NbtPath> { public class NbtPathSerializer extends ScalarSerializer.Annotated<NbtPathArgument.NbtPath> {
public static final NbtPathSerializer SERIALIZER = new NbtPathSerializer(); public static final NbtPathSerializer SERIALIZER = new NbtPathSerializer();
private static final NbtPathArgument DUMMY_ARGUMENT = new NbtPathArgument(); private static final NbtPathArgument DUMMY_ARGUMENT = new NbtPathArgument();
@@ -20,23 +20,13 @@ public class NbtPathSerializer extends ScalarSerializer<NbtPathArgument.NbtPath>
super(NbtPathArgument.NbtPath.class); super(NbtPathArgument.NbtPath.class);
} }
@Override
public NbtPathArgument.NbtPath deserialize(final Type type, final Object obj) throws SerializationException {
return fromString(obj.toString());
}
@Override
protected Object serialize(final NbtPathArgument.NbtPath item, final Predicate<Class<?>> typeSupported) {
return item.toString();
}
public static List<NbtPathArgument.NbtPath> fromString(final List<String> tags) { public static List<NbtPathArgument.NbtPath> fromString(final List<String> tags) {
List<NbtPathArgument.NbtPath> paths = new ArrayList<>(); final List<NbtPathArgument.NbtPath> paths = new ArrayList<>();
try { try {
for (final String tag : tags) { for (final String tag : tags) {
paths.add(fromString(tag)); paths.add(fromString(tag));
} }
} catch (SerializationException ex) { } catch (final SerializationException ex) {
SneakyThrow.sneaky(ex); SneakyThrow.sneaky(ex);
} }
return List.copyOf(paths); return List.copyOf(paths);
@@ -45,8 +35,18 @@ public class NbtPathSerializer extends ScalarSerializer<NbtPathArgument.NbtPath>
private static NbtPathArgument.NbtPath fromString(final String tag) throws SerializationException { private static NbtPathArgument.NbtPath fromString(final String tag) throws SerializationException {
try { try {
return DUMMY_ARGUMENT.parse(new StringReader(tag)); return DUMMY_ARGUMENT.parse(new StringReader(tag));
} catch (CommandSyntaxException e) { } catch (final CommandSyntaxException e) {
throw new SerializationException(NbtPathArgument.NbtPath.class, e); throw new SerializationException(NbtPathArgument.NbtPath.class, e);
} }
} }
@Override
public NbtPathArgument.NbtPath deserialize(final AnnotatedType type, final Object obj) throws SerializationException {
return fromString(obj.toString());
}
@Override
protected Object serialize(final AnnotatedType type, final NbtPathArgument.NbtPath item, final Predicate<Class<?>> typeSupported) {
return item.toString();
}
} }

View File

@@ -1,86 +0,0 @@
package io.papermc.paper.configuration.serializer;
import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap;
import com.mojang.logging.LogUtils;
import io.leangen.geantyref.TypeToken;
import io.papermc.paper.configuration.serializer.collections.MapSerializer;
import io.papermc.paper.util.MappingEnvironment;
import io.papermc.paper.util.ObfHelper;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import net.minecraft.network.protocol.Packet;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.spongepowered.configurate.serialize.ScalarSerializer;
import org.spongepowered.configurate.serialize.SerializationException;
@SuppressWarnings("Convert2Diamond")
public final class PacketClassSerializer extends ScalarSerializer<Class<? extends Packet<?>>> implements MapSerializer.WriteBack {
private static final Logger LOGGER = LogUtils.getClassLogger();
private static final TypeToken<Class<? extends Packet<?>>> TYPE = new TypeToken<Class<? extends Packet<?>>>() {};
private static final List<String> SUBPACKAGES = List.of("game", "handshake", "login", "status");
private static final BiMap<String, String> MOJANG_TO_OBF;
static {
final ImmutableBiMap.Builder<String, String> builder = ImmutableBiMap.builder();
final @Nullable Map<String, ObfHelper.ClassMapping> classMappingMap = ObfHelper.INSTANCE.mappingsByMojangName();
if (classMappingMap != null) {
classMappingMap.forEach((mojMap, classMapping) -> {
if (mojMap.startsWith("net.minecraft.network.protocol.")) {
builder.put(classMapping.mojangName(), classMapping.obfName());
}
});
}
MOJANG_TO_OBF = builder.build();
}
public PacketClassSerializer() {
super(TYPE);
}
@SuppressWarnings("unchecked")
@Override
public Class<? extends Packet<?>> deserialize(final Type type, final Object obj) throws SerializationException {
Class<?> packetClass = null;
for (final String subpackage : SUBPACKAGES) {
final String fullClassName = "net.minecraft.network.protocol." + subpackage + "." + obj;
try {
packetClass = Class.forName(fullClassName);
break;
} catch (final ClassNotFoundException ex) {
final String spigotClassName = MOJANG_TO_OBF.get(fullClassName);
if (spigotClassName != null) {
try {
packetClass = Class.forName(spigotClassName);
} catch (final ClassNotFoundException ignore) {}
}
}
}
if (packetClass == null || !Packet.class.isAssignableFrom(packetClass)) {
throw new SerializationException("Could not deserialize a packet from " + obj);
}
return (Class<? extends Packet<?>>) packetClass;
}
@Override
protected @Nullable Object serialize(final Class<? extends Packet<?>> packetClass, final Predicate<Class<?>> typeSupported) {
final String name = packetClass.getName();
@Nullable String mojName = ObfHelper.INSTANCE.mappingsByMojangName() == null || !MappingEnvironment.reobf() ? name : MOJANG_TO_OBF.inverse().get(name); // if the mappings are null, running on moj-mapped server
if (mojName == null && MOJANG_TO_OBF.containsKey(name)) {
mojName = name;
}
if (mojName != null) {
int pos = mojName.lastIndexOf('.');
if (pos != -1 && pos != mojName.length() - 1) {
return mojName.substring(pos + 1);
}
}
LOGGER.error("Could not serialize {} into a mojang-mapped packet class name", packetClass);
return null;
}
}

View File

@@ -1,12 +1,13 @@
package io.papermc.paper.configuration.serializer; package io.papermc.paper.configuration.serializer;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.function.Predicate; import java.util.function.Predicate;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import org.spongepowered.configurate.serialize.ScalarSerializer; import org.spongepowered.configurate.serialize.ScalarSerializer;
import org.spongepowered.configurate.serialize.SerializationException; import org.spongepowered.configurate.serialize.SerializationException;
public class ResourceLocationSerializer extends ScalarSerializer<ResourceLocation> { public class ResourceLocationSerializer extends ScalarSerializer.Annotated<ResourceLocation> {
public static final ScalarSerializer<ResourceLocation> INSTANCE = new ResourceLocationSerializer(); public static final ScalarSerializer<ResourceLocation> INSTANCE = new ResourceLocationSerializer();
@@ -15,12 +16,12 @@ public class ResourceLocationSerializer extends ScalarSerializer<ResourceLocatio
} }
@Override @Override
public ResourceLocation deserialize(final Type type, final Object obj) throws SerializationException { public ResourceLocation deserialize(final AnnotatedType annotatedType, final Object obj) throws SerializationException {
return ResourceLocation.read(obj.toString()).getOrThrow(s -> new SerializationException(ResourceLocation.class, s)); return ResourceLocation.read(obj.toString()).getOrThrow(s -> new SerializationException(ResourceLocation.class, s));
} }
@Override @Override
protected Object serialize(final ResourceLocation item, final Predicate<Class<?>> typeSupported) { protected Object serialize(final AnnotatedType annotatedType, final ResourceLocation item, final Predicate<Class<?>> typeSupported) {
return item.toString(); return item.toString();
} }
} }

View File

@@ -0,0 +1,118 @@
package io.papermc.paper.configuration.serializer;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.mojang.logging.LogUtils;
import io.leangen.geantyref.GenericTypeReflector;
import io.leangen.geantyref.TypeToken;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.PacketFlow;
import net.minecraft.network.protocol.PacketType;
import net.minecraft.network.protocol.common.CommonPacketTypes;
import net.minecraft.network.protocol.configuration.ConfigurationPacketTypes;
import net.minecraft.network.protocol.cookie.CookiePacketTypes;
import net.minecraft.network.protocol.game.GamePacketTypes;
import net.minecraft.network.protocol.handshake.HandshakePacketTypes;
import net.minecraft.network.protocol.login.LoginPacketTypes;
import net.minecraft.network.protocol.ping.PingPacketTypes;
import net.minecraft.network.protocol.status.StatusPacketTypes;
import net.minecraft.resources.ResourceLocation;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
import org.spongepowered.configurate.serialize.ScalarSerializer;
import org.spongepowered.configurate.serialize.SerializationException;
@SuppressWarnings("Convert2Diamond")
public final class ServerboundPacketClassSerializer extends ScalarSerializer<Class<? extends Packet<?>>> {
private static final Logger LOGGER = LogUtils.getClassLogger();
private static final TypeToken<Class<? extends Packet<?>>> TYPE = new TypeToken<Class<? extends Packet<?>>>() {};
static final Set<Class<?>> PACKET_CLASS_HOLDERS = Set.of(
PingPacketTypes.class,
HandshakePacketTypes.class,
StatusPacketTypes.class,
LoginPacketTypes.class,
ConfigurationPacketTypes.class,
CommonPacketTypes.class,
CookiePacketTypes.class,
GamePacketTypes.class
);
private static final Map<ResourceLocation, PacketInfo> ID_TO_INFO;
private static final Map<Class<?>, PacketInfo> CLASS_TO_INFO;
static {
try {
final ImmutableMap.Builder<ResourceLocation, PacketInfo> idBuilder = ImmutableMap.builder();
final ImmutableMap.Builder<Class<?>, PacketInfo> classBuilder = ImmutableMap.builder();
for (final Class<?> holder : PACKET_CLASS_HOLDERS) {
for (final Field field : holder.getDeclaredFields()) {
if (!Modifier.isStatic(field.getModifiers()) || !Modifier.isFinal(field.getModifiers()) || !field.getType().equals(PacketType.class)) {
continue;
}
final Type genericType = field.getGenericType();
if (!(genericType instanceof final ParameterizedType parameterizedType)) {
throw new RuntimeException("Unexpected generic type: " + genericType);
}
final PacketType<?> type = (PacketType<?>) field.get(null);
if (type.flow() != PacketFlow.SERVERBOUND) {
// we only care about serverbound for rate limiting
continue;
}
final Class<?> packetClass = GenericTypeReflector.erase(parameterizedType.getActualTypeArguments()[0]);
final PacketInfo info = new PacketInfo(packetClass.asSubclass(Packet.class), type);
idBuilder.put(type.id(), info);
classBuilder.put(packetClass, info);
}
}
ID_TO_INFO = idBuilder.buildOrThrow();
CLASS_TO_INFO = classBuilder.buildOrThrow();
Preconditions.checkState(ID_TO_INFO.size() == 74, "Packet class map must have 74 entries");
} catch (final ReflectiveOperationException e) {
throw new RuntimeException("Could not create packet class map", e);
}
}
public ServerboundPacketClassSerializer() {
super(TYPE);
}
@Override
public Class<? extends Packet<?>> deserialize(final Type type, final Object obj) throws SerializationException {
final ResourceLocation location = ResourceLocation.tryParse(obj.toString());
if (location == null) {
throw new SerializationException(ResourceLocation.class, "Could not deserialize a packet from " + obj);
}
final PacketInfo info = ID_TO_INFO.get(location);
if (info == null) {
throw new SerializationException("Could not deserialize a packet from " + obj);
}
return info.packetClass();
}
@Override
protected @Nullable Object serialize(final Class<? extends Packet<?>> packetClass, final Predicate<Class<?>> typeSupported) {
final PacketInfo info = CLASS_TO_INFO.get(packetClass);
if (info == null) {
LOGGER.error("Could not serialize {} into a packet identifier", packetClass);
return null;
} else {
return info.type().id().toString();
}
}
@SuppressWarnings("rawtypes")
record PacketInfo(Class<? extends Packet> clazz, PacketType<?> type) {
@SuppressWarnings("unchecked")
public Class<? extends Packet<?>> packetClass() {
return (Class<? extends Packet<?>>) this.clazz;
}
}
}

View File

@@ -12,6 +12,7 @@ import org.spongepowered.configurate.serialize.ScalarSerializer;
import org.spongepowered.configurate.serialize.SerializationException; import org.spongepowered.configurate.serialize.SerializationException;
public final class StringRepresentableSerializer extends ScalarSerializer<StringRepresentable> { public final class StringRepresentableSerializer extends ScalarSerializer<StringRepresentable> {
private static final Map<Type, Function<String, StringRepresentable>> TYPES = Collections.synchronizedMap(Map.ofEntries( private static final Map<Type, Function<String, StringRepresentable>> TYPES = Collections.synchronizedMap(Map.ofEntries(
createEntry(MobCategory.class) createEntry(MobCategory.class)
)); ));
@@ -24,20 +25,22 @@ public final class StringRepresentableSerializer extends ScalarSerializer<String
return TYPES.containsKey(type); return TYPES.containsKey(type);
} }
private static <E extends Enum<E> & StringRepresentable> Map.Entry<Type, Function<String, @Nullable StringRepresentable>> createEntry(Class<E> type) { private static <E extends Enum<E> & StringRepresentable> Map.Entry<Type, Function<String, @Nullable StringRepresentable>> createEntry(final Class<E> type) {
return Map.entry(type, s -> { return Map.entry(
for (E value : type.getEnumConstants()) { type, s -> {
if (value.getSerializedName().equals(s)) { for (final E value : type.getEnumConstants()) {
return value; if (value.getSerializedName().equals(s)) {
return value;
}
} }
return null;
} }
return null; );
});
} }
@Override @Override
public StringRepresentable deserialize(Type type, Object obj) throws SerializationException { public StringRepresentable deserialize(final Type type, final Object obj) throws SerializationException {
Function<String, StringRepresentable> function = TYPES.get(type); final Function<String, StringRepresentable> function = TYPES.get(type);
if (function == null) { if (function == null) {
throw new SerializationException(type + " isn't registered"); throw new SerializationException(type + " isn't registered");
} }
@@ -45,7 +48,7 @@ public final class StringRepresentableSerializer extends ScalarSerializer<String
} }
@Override @Override
protected Object serialize(StringRepresentable item, Predicate<Class<?>> typeSupported) { protected Object serialize(final StringRepresentable item, final Predicate<Class<?>> typeSupported) {
return item.getSerializedName(); return item.getSerializedName();
} }
} }

View File

@@ -1,9 +1,11 @@
package io.papermc.paper.configuration.serializer.collections; package io.papermc.paper.configuration.serializer.collection;
import com.google.common.collect.HashBasedTable; import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableTable; import com.google.common.collect.ImmutableTable;
import com.google.common.collect.Table; import com.google.common.collect.Table;
import io.leangen.geantyref.TypeFactory; import io.leangen.geantyref.TypeFactory;
import java.lang.reflect.AnnotatedParameterizedType;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.Map; import java.util.Map;
@@ -15,33 +17,34 @@ import org.spongepowered.configurate.ConfigurationOptions;
import org.spongepowered.configurate.serialize.SerializationException; import org.spongepowered.configurate.serialize.SerializationException;
import org.spongepowered.configurate.serialize.TypeSerializer; import org.spongepowered.configurate.serialize.TypeSerializer;
public class TableSerializer implements TypeSerializer<Table<?, ?, ?>> { public class TableSerializer implements TypeSerializer.Annotated<Table<?, ?, ?>> {
private static final int ROW_TYPE_ARGUMENT_INDEX = 0; private static final int ROW_TYPE_ARGUMENT_INDEX = 0;
private static final int COLUMN_TYPE_ARGUMENT_INDEX = 1; private static final int COLUMN_TYPE_ARGUMENT_INDEX = 1;
private static final int VALUE_TYPE_ARGUMENT_INDEX = 2; private static final int VALUE_TYPE_ARGUMENT_INDEX = 2;
@Override @Override
public Table<?, ?, ?> deserialize(final Type type, final ConfigurationNode node) throws SerializationException { public Table<?, ?, ?> deserialize(final AnnotatedType type, final ConfigurationNode node) throws SerializationException {
final Table<?, ?, ?> table = HashBasedTable.create(); final Table<?, ?, ?> table = HashBasedTable.create();
if (!node.empty() && node.isMap()) { if (!node.empty() && node.isMap()) {
this.deserialize0(table, (ParameterizedType) type, node); this.deserialize0(table, (AnnotatedParameterizedType) type, node);
} }
return table; return table;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private <R, C, V> void deserialize0(final Table<R, C, V> table, final ParameterizedType type, final ConfigurationNode node) throws SerializationException { private <R, C, V> void deserialize0(final Table<R, C, V> table, final AnnotatedParameterizedType type, final ConfigurationNode node) throws SerializationException {
final Type rowType = type.getActualTypeArguments()[ROW_TYPE_ARGUMENT_INDEX]; final AnnotatedType rowType = type.getAnnotatedActualTypeArguments()[ROW_TYPE_ARGUMENT_INDEX];
final Type columnType = type.getActualTypeArguments()[COLUMN_TYPE_ARGUMENT_INDEX]; final AnnotatedType columnType = type.getAnnotatedActualTypeArguments()[COLUMN_TYPE_ARGUMENT_INDEX];
final Type valueType = type.getActualTypeArguments()[VALUE_TYPE_ARGUMENT_INDEX]; final AnnotatedType valueType = type.getAnnotatedActualTypeArguments()[VALUE_TYPE_ARGUMENT_INDEX];
final TypeSerializer<R> rowKeySerializer = (TypeSerializer<R>) node.options().serializers().get(rowType); final TypeSerializer<R> rowKeySerializer = (TypeSerializer<R>) node.options().serializers().get(rowType);
if (rowKeySerializer == null) { if (rowKeySerializer == null) {
throw new SerializationException("Could not find serializer for table row type " + rowType); throw new SerializationException("Could not find serializer for table row type " + rowType);
} }
final Type mapType = TypeFactory.parameterizedClass(Map.class, columnType, valueType); final ParameterizedType mapType = (ParameterizedType) TypeFactory.parameterizedClass(Map.class, columnType.getType(), valueType.getType());
final TypeSerializer<Map<C, V>> columnValueSerializer = (TypeSerializer<Map<C, V>>) node.options().serializers().get(mapType); final AnnotatedParameterizedType annotatedMapType = TypeFactory.parameterizedAnnotatedType(mapType, type.getAnnotations(), columnType.getAnnotations(), valueType.getAnnotations());
final TypeSerializer<Map<C, V>> columnValueSerializer = (TypeSerializer<Map<C, V>>) node.options().serializers().get(annotatedMapType);
if (columnValueSerializer == null) { if (columnValueSerializer == null) {
throw new SerializationException("Could not find serializer for table column-value map " + type); throw new SerializationException("Could not find serializer for table column-value map " + type);
} }
@@ -56,17 +59,21 @@ public class TableSerializer implements TypeSerializer<Table<?, ?, ?>> {
} }
@Override @Override
public void serialize(final Type type, final @Nullable Table<?, ?, ?> table, final ConfigurationNode node) throws SerializationException { public void serialize(
if (table != null) { final AnnotatedType type,
this.serialize0(table, (ParameterizedType) type, node); final @Nullable Table<?, ?, ?> obj,
final ConfigurationNode node
) throws SerializationException {
if (obj != null) {
this.serialize0(obj, (AnnotatedParameterizedType) type, node);
} }
} }
@SuppressWarnings({"rawtypes", "unchecked"}) @SuppressWarnings({"rawtypes", "unchecked"})
private <R, C, V> void serialize0(final Table<R, C, V> table, final ParameterizedType type, final ConfigurationNode node) throws SerializationException { private <R, C, V> void serialize0(final Table<R, C, V> table, final AnnotatedParameterizedType type, final ConfigurationNode node) throws SerializationException {
final Type rowType = type.getActualTypeArguments()[ROW_TYPE_ARGUMENT_INDEX]; final AnnotatedType rowType = type.getAnnotatedActualTypeArguments()[ROW_TYPE_ARGUMENT_INDEX];
final Type columnType = type.getActualTypeArguments()[COLUMN_TYPE_ARGUMENT_INDEX]; final AnnotatedType columnType = type.getAnnotatedActualTypeArguments()[COLUMN_TYPE_ARGUMENT_INDEX];
final Type valueType = type.getActualTypeArguments()[VALUE_TYPE_ARGUMENT_INDEX]; final AnnotatedType valueType = type.getAnnotatedActualTypeArguments()[VALUE_TYPE_ARGUMENT_INDEX];
final TypeSerializer rowKeySerializer = node.options().serializers().get(rowType); final TypeSerializer rowKeySerializer = node.options().serializers().get(rowType);
if (rowKeySerializer == null) { if (rowKeySerializer == null) {
@@ -77,12 +84,14 @@ public class TableSerializer implements TypeSerializer<Table<?, ?, ?>> {
for (final R key : table.rowKeySet()) { for (final R key : table.rowKeySet()) {
rowKeySerializer.serialize(rowType, key, rowKeyNode.set(key)); rowKeySerializer.serialize(rowType, key, rowKeyNode.set(key));
final Object keyObj = Objects.requireNonNull(rowKeyNode.raw()); final Object keyObj = Objects.requireNonNull(rowKeyNode.raw());
node.node(keyObj).set(TypeFactory.parameterizedClass(Map.class, columnType, valueType), table.row(key)); final ParameterizedType mapType = (ParameterizedType) TypeFactory.parameterizedClass(Map.class, columnType.getType(), valueType.getType());
final AnnotatedParameterizedType annotatedMapType = TypeFactory.parameterizedAnnotatedType(mapType, type.getAnnotations(), columnType.getAnnotations(), valueType.getAnnotations());
node.node(keyObj).set(annotatedMapType, table.row(key));
} }
} }
@Override @Override
public @Nullable Table<?, ?, ?> emptyValue(Type specificType, ConfigurationOptions options) { public @Nullable Table<?, ?, ?> emptyValue(final Type specificType, final ConfigurationOptions options) {
return ImmutableTable.of(); return ImmutableTable.of();
} }
} }

View File

@@ -1,8 +1,7 @@
package io.papermc.paper.configuration.serializer.collections; package io.papermc.paper.configuration.serializer.collection.map;
import io.leangen.geantyref.GenericTypeReflector; import io.leangen.geantyref.GenericTypeReflector;
import io.leangen.geantyref.TypeFactory; import io.leangen.geantyref.TypeFactory;
import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedParameterizedType; import java.lang.reflect.AnnotatedParameterizedType;
import java.lang.reflect.AnnotatedType; import java.lang.reflect.AnnotatedType;
import java.lang.reflect.ParameterizedType; import java.lang.reflect.ParameterizedType;

View File

@@ -1,11 +1,9 @@
package io.papermc.paper.configuration.serializer.collections; package io.papermc.paper.configuration.serializer.collection.map;
import com.mojang.logging.LogUtils; import com.mojang.logging.LogUtils;
import io.leangen.geantyref.TypeToken; import io.leangen.geantyref.TypeToken;
import java.lang.annotation.Retention; import java.lang.reflect.AnnotatedParameterizedType;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.AnnotatedType; import java.lang.reflect.AnnotatedType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
@@ -36,56 +34,54 @@ public class MapSerializer implements TypeSerializer.Annotated<Map<?, ?>> {
private final boolean clearInvalids; private final boolean clearInvalids;
private final TypeSerializer<Map<?, ?>> fallback; private final TypeSerializer<Map<?, ?>> fallback;
public MapSerializer(boolean clearInvalids) { public MapSerializer(final boolean clearInvalids) {
this.clearInvalids = clearInvalids; this.clearInvalids = clearInvalids;
this.fallback = requireNonNull(TypeSerializerCollection.defaults().get(TYPE), "Could not find default Map<?, ?> serializer"); this.fallback = requireNonNull(TypeSerializerCollection.defaults().get(TYPE), "Could not find default Map<?, ?> serializer");
} }
@Retention(RetentionPolicy.RUNTIME)
public @interface ThrowExceptions {}
@Override @Override
public Map<?, ?> deserialize(AnnotatedType annotatedType, ConfigurationNode node) throws SerializationException { public Map<?, ?> deserialize(final AnnotatedType annotatedType, final ConfigurationNode node) throws SerializationException {
if (annotatedType.isAnnotationPresent(ThrowExceptions.class)) { if (annotatedType.isAnnotationPresent(ThrowExceptions.class)) {
return this.fallback.deserialize(annotatedType, node); return this.fallback.deserialize(annotatedType, node);
} }
final Map<Object, Object> map = new LinkedHashMap<>(); final Map<Object, Object> map = new LinkedHashMap<>();
final Type type = annotatedType.getType(); final Type type = annotatedType.getType();
if (node.isMap()) { if (node.isMap()) {
if (!(type instanceof ParameterizedType parameterizedType)) { if (!(annotatedType instanceof final AnnotatedParameterizedType annotatedParameterizedType)) {
throw new SerializationException(type, "Raw types are not supported for collections"); throw new SerializationException(type, "Raw types are not supported for collections");
} }
if (parameterizedType.getActualTypeArguments().length != 2) { if (annotatedParameterizedType.getAnnotatedActualTypeArguments().length != 2) {
throw new SerializationException(type, "Map expected two type arguments!"); throw new SerializationException(type, "Map expected two type arguments!");
} }
final Type key = parameterizedType.getActualTypeArguments()[0]; final AnnotatedType key = annotatedParameterizedType.getAnnotatedActualTypeArguments()[0];
final Type value = parameterizedType.getActualTypeArguments()[1]; final AnnotatedType value = annotatedParameterizedType.getAnnotatedActualTypeArguments()[1];
final @Nullable TypeSerializer<?> keySerializer = node.options().serializers().get(key); final TypeSerializer<?> keySerializer = node.options().serializers().get(key);
final @Nullable TypeSerializer<?> valueSerializer = node.options().serializers().get(value); final TypeSerializer<?> valueSerializer = node.options().serializers().get(value);
if (keySerializer == null) { if (keySerializer == null) {
throw new SerializationException(type, "No type serializer available for key type " + key); throw new SerializationException(type, "No type serializer available for key type " + key);
} }
if (valueSerializer == null) { if (valueSerializer == null) {
throw new SerializationException(type, "No type serializer available for value type " + value); throw new SerializationException(type, "No type serializer available for value type " + value);
} }
final boolean writeKeyBack = key.isAnnotationPresent(WriteKeyBack.class);
final BasicConfigurationNode keyNode = BasicConfigurationNode.root(node.options()); final BasicConfigurationNode keyNode = BasicConfigurationNode.root(node.options());
final Set<Object> keysToClear = new HashSet<>(); final Set<Object> keysToClear = new HashSet<>();
for (Map.Entry<Object, ? extends ConfigurationNode> ent : node.childrenMap().entrySet()) { for (final Map.Entry<Object, ? extends ConfigurationNode> ent : node.childrenMap().entrySet()) {
final @Nullable Object deserializedKey = deserialize(key, keySerializer, "key", keyNode.set(ent.getKey()), node.path()); final Object deserializedKey = this.deserialize(key.getType(), keySerializer, "key", keyNode.set(ent.getKey()), node.path());
final @Nullable Object deserializedValue = deserialize(value, valueSerializer, "value", ent.getValue(), ent.getValue().path()); final Object deserializedValue = this.deserialize(value.getType(), valueSerializer, "value", ent.getValue(), ent.getValue().path());
if (deserializedKey == null || deserializedValue == null) { if (deserializedKey == null || deserializedValue == null) {
continue; continue;
} }
if (keySerializer instanceof WriteBack) { if (writeKeyBack) {
if (serialize(key, keySerializer, deserializedKey, "key", keyNode, node.path()) && !ent.getKey().equals(requireNonNull(keyNode.raw(), "Key must not be null!"))) { if (this.serialize(key.getType(), keySerializer, deserializedKey, "key", keyNode, node.path()) && !ent.getKey().equals(requireNonNull(keyNode.raw(), "Key must not be null!"))) {
keysToClear.add(ent.getKey()); keysToClear.add(ent.getKey());
} }
} }
map.put(deserializedKey, deserializedValue); map.put(deserializedKey, deserializedValue);
} }
if (keySerializer instanceof WriteBack) { // supports cleaning keys which deserialize to the same value if (writeKeyBack) { // supports cleaning keys which deserialize to the same value
for (Object keyToClear : keysToClear) { for (final Object keyToClear : keysToClear) {
node.node(keyToClear).raw(null); node.node(keyToClear).raw(null);
} }
} }
@@ -93,10 +89,10 @@ public class MapSerializer implements TypeSerializer.Annotated<Map<?, ?>> {
return map; return map;
} }
private @Nullable Object deserialize(Type type, TypeSerializer<?> serializer, String mapPart, ConfigurationNode node, NodePath path) { private @Nullable Object deserialize(final Type type, final TypeSerializer<?> serializer, final String mapPart, final ConfigurationNode node, final NodePath path) {
try { try {
return serializer.deserialize(type, node); return serializer.deserialize(type, node);
} catch (SerializationException ex) { } catch (final SerializationException ex) {
ex.initPath(node::path); ex.initPath(node::path);
LOGGER.error("Could not deserialize {} {} into {} at {}: {}", mapPart, node.raw(), type, path, ex.rawMessage()); LOGGER.error("Could not deserialize {} {} into {} at {}: {}", mapPart, node.raw(), type, path, ex.rawMessage());
} }
@@ -104,22 +100,22 @@ public class MapSerializer implements TypeSerializer.Annotated<Map<?, ?>> {
} }
@Override @Override
public void serialize(AnnotatedType annotatedType, @Nullable Map<?, ?> obj, ConfigurationNode node) throws SerializationException { public void serialize(final AnnotatedType annotatedType, final @Nullable Map<?, ?> obj, final ConfigurationNode node) throws SerializationException {
if (annotatedType.isAnnotationPresent(ThrowExceptions.class)) { if (annotatedType.isAnnotationPresent(ThrowExceptions.class)) {
this.fallback.serialize(annotatedType, obj, node); this.fallback.serialize(annotatedType, obj, node);
return; return;
} }
final Type type = annotatedType.getType(); final Type type = annotatedType.getType();
if (!(type instanceof ParameterizedType parameterizedType)) { if (!(annotatedType instanceof final AnnotatedParameterizedType annotatedParameterizedType)) {
throw new SerializationException(type, "Raw types are not supported for collections"); throw new SerializationException(type, "Raw types are not supported for collections");
} }
if (parameterizedType.getActualTypeArguments().length != 2) { if (annotatedParameterizedType.getAnnotatedActualTypeArguments().length != 2) {
throw new SerializationException(type, "Map expected two type arguments!"); throw new SerializationException(type, "Map expected two type arguments!");
} }
final Type key = parameterizedType.getActualTypeArguments()[0]; final AnnotatedType key = annotatedParameterizedType.getAnnotatedActualTypeArguments()[0];
final Type value = parameterizedType.getActualTypeArguments()[1]; final AnnotatedType value = annotatedParameterizedType.getAnnotatedActualTypeArguments()[1];
final @Nullable TypeSerializer<?> keySerializer = node.options().serializers().get(key); final TypeSerializer<?> keySerializer = node.options().serializers().get(key);
final @Nullable TypeSerializer<?> valueSerializer = node.options().serializers().get(value); final TypeSerializer<?> valueSerializer = node.options().serializers().get(value);
if (keySerializer == null) { if (keySerializer == null) {
throw new SerializationException(type, "No type serializer available for key type " + key); throw new SerializationException(type, "No type serializer available for key type " + key);
@@ -135,22 +131,22 @@ public class MapSerializer implements TypeSerializer.Annotated<Map<?, ?>> {
final Set<Object> unvisitedKeys; final Set<Object> unvisitedKeys;
if (node.empty()) { if (node.empty()) {
node.raw(Collections.emptyMap()); node.raw(Collections.emptyMap());
unvisitedKeys = Collections.emptySet(); unvisitedKeys = new HashSet<>();
} else { } else {
unvisitedKeys = new HashSet<>(node.childrenMap().keySet()); unvisitedKeys = new HashSet<>(node.childrenMap().keySet());
} }
final BasicConfigurationNode keyNode = BasicConfigurationNode.root(node.options()); final BasicConfigurationNode keyNode = BasicConfigurationNode.root(node.options());
for (Map.Entry<?, ?> ent : obj.entrySet()) { for (final Map.Entry<?, ?> ent : obj.entrySet()) {
if (!serialize(key, keySerializer, ent.getKey(), "key", keyNode, node.path())) { if (!this.serialize(key.getType(), keySerializer, ent.getKey(), "key", keyNode, node.path())) {
continue; continue;
} }
final Object keyObj = requireNonNull(keyNode.raw(), "Key must not be null!"); final Object keyObj = requireNonNull(keyNode.raw(), "Key must not be null!");
final ConfigurationNode child = node.node(keyObj); final ConfigurationNode child = node.node(keyObj);
serialize(value, valueSerializer, ent.getValue(), "value", child, child.path()); this.serialize(value.getType(), valueSerializer, ent.getValue(), "value", child, child.path());
unvisitedKeys.remove(keyObj); unvisitedKeys.remove(keyObj);
} }
if (this.clearInvalids) { if (this.clearInvalids) {
for (Object unusedChild : unvisitedKeys) { for (final Object unusedChild : unvisitedKeys) {
node.removeChild(unusedChild); node.removeChild(unusedChild);
} }
} }
@@ -158,11 +154,11 @@ public class MapSerializer implements TypeSerializer.Annotated<Map<?, ?>> {
} }
@SuppressWarnings({"rawtypes", "unchecked"}) @SuppressWarnings({"rawtypes", "unchecked"})
private boolean serialize(Type type, TypeSerializer serializer, Object object, String mapPart, ConfigurationNode node, NodePath path) { private boolean serialize(final Type type, final TypeSerializer serializer, final Object object, final String mapPart, final ConfigurationNode node, final NodePath path) {
try { try {
serializer.serialize(type, object, node); serializer.serialize(type, object, node);
return true; return true;
} catch (SerializationException ex) { } catch (final SerializationException ex) {
ex.initPath(node::path); ex.initPath(node::path);
LOGGER.error("Could not serialize {} {} from {} at {}: {}", mapPart, object, type, path, ex.rawMessage()); LOGGER.error("Could not serialize {} {} from {} at {}: {}", mapPart, object, type, path, ex.rawMessage());
} }
@@ -170,13 +166,11 @@ public class MapSerializer implements TypeSerializer.Annotated<Map<?, ?>> {
} }
@Override @Override
public @Nullable Map<?, ?> emptyValue(AnnotatedType specificType, ConfigurationOptions options) { public @Nullable Map<?, ?> emptyValue(final AnnotatedType specificType, final ConfigurationOptions options) {
if (specificType.isAnnotationPresent(ThrowExceptions.class)) { if (specificType.isAnnotationPresent(ThrowExceptions.class)) {
return this.fallback.emptyValue(specificType, options); return this.fallback.emptyValue(specificType, options);
} }
return new LinkedHashMap<>(); return new LinkedHashMap<>();
} }
public interface WriteBack { // marker interface
}
} }

View File

@@ -0,0 +1,10 @@
package io.papermc.paper.configuration.serializer.collection.map;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ThrowExceptions {}

View File

@@ -0,0 +1,10 @@
package io.papermc.paper.configuration.serializer.collection.map;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE_USE)
@Retention(RetentionPolicy.RUNTIME)
public @interface WriteKeyBack {}

View File

@@ -0,0 +1,4 @@
@NullMarked
package io.papermc.paper.configuration.serializer.collection.map;
import org.jspecify.annotations.NullMarked;

View File

@@ -0,0 +1,4 @@
@NullMarked
package io.papermc.paper.configuration.serializer.collection;
import org.jspecify.annotations.NullMarked;

View File

@@ -1,4 +0,0 @@
@NullMarked
package io.papermc.paper.configuration.serializer.collections;
import org.jspecify.annotations.NullMarked;

View File

@@ -1,6 +1,7 @@
package io.papermc.paper.configuration.serializer.registry; package io.papermc.paper.configuration.serializer.registry;
import io.leangen.geantyref.TypeToken; import io.leangen.geantyref.TypeToken;
import java.lang.reflect.AnnotatedType;
import java.lang.reflect.Type; import java.lang.reflect.Type;
import java.util.function.Predicate; import java.util.function.Predicate;
import net.minecraft.core.Registry; import net.minecraft.core.Registry;
@@ -10,20 +11,30 @@ import net.minecraft.resources.ResourceLocation;
import org.spongepowered.configurate.serialize.ScalarSerializer; import org.spongepowered.configurate.serialize.ScalarSerializer;
import org.spongepowered.configurate.serialize.SerializationException; import org.spongepowered.configurate.serialize.SerializationException;
abstract class RegistryEntrySerializer<T, R> extends ScalarSerializer<T> { abstract class RegistryEntrySerializer<T, R> extends ScalarSerializer.Annotated<T> {
private final RegistryAccess registryAccess; private final RegistryAccess registryAccess;
private final ResourceKey<? extends Registry<R>> registryKey; private final ResourceKey<? extends Registry<R>> registryKey;
private final boolean omitMinecraftNamespace; private final boolean omitMinecraftNamespace;
protected RegistryEntrySerializer(TypeToken<T> type, final RegistryAccess registryAccess, ResourceKey<? extends Registry<R>> registryKey, boolean omitMinecraftNamespace) { protected RegistryEntrySerializer(
final TypeToken<T> type,
final RegistryAccess registryAccess,
final ResourceKey<? extends Registry<R>> registryKey,
final boolean omitMinecraftNamespace
) {
super(type); super(type);
this.registryAccess = registryAccess; this.registryAccess = registryAccess;
this.registryKey = registryKey; this.registryKey = registryKey;
this.omitMinecraftNamespace = omitMinecraftNamespace; this.omitMinecraftNamespace = omitMinecraftNamespace;
} }
protected RegistryEntrySerializer(Class<T> type, final RegistryAccess registryAccess, ResourceKey<? extends Registry<R>> registryKey, boolean omitMinecraftNamespace) { protected RegistryEntrySerializer(
final Class<T> type,
final RegistryAccess registryAccess,
final ResourceKey<? extends Registry<R>> registryKey,
final boolean omitMinecraftNamespace
) {
super(type); super(type);
this.registryAccess = registryAccess; this.registryAccess = registryAccess;
this.registryKey = registryKey; this.registryKey = registryKey;
@@ -37,14 +48,14 @@ abstract class RegistryEntrySerializer<T, R> extends ScalarSerializer<T> {
protected abstract T convertFromResourceKey(ResourceKey<R> key) throws SerializationException; protected abstract T convertFromResourceKey(ResourceKey<R> key) throws SerializationException;
@Override @Override
public final T deserialize(Type type, Object obj) throws SerializationException { public final T deserialize(final AnnotatedType type, final Object obj) throws SerializationException {
return this.convertFromResourceKey(this.deserializeKey(obj)); return this.convertFromResourceKey(this.deserializeKey(obj));
} }
protected abstract ResourceKey<R> convertToResourceKey(T value); protected abstract ResourceKey<R> convertToResourceKey(T value);
@Override @Override
protected final Object serialize(T item, Predicate<Class<?>> typeSupported) { protected final Object serialize(final AnnotatedType type, final T item, final Predicate<Class<?>> typeSupported) {
final ResourceKey<R> key = this.convertToResourceKey(item); final ResourceKey<R> key = this.convertToResourceKey(item);
if (this.omitMinecraftNamespace && key.location().getNamespace().equals(ResourceLocation.DEFAULT_NAMESPACE)) { if (this.omitMinecraftNamespace && key.location().getNamespace().equals(ResourceLocation.DEFAULT_NAMESPACE)) {
return key.location().getPath(); return key.location().getPath();

View File

@@ -13,22 +13,32 @@ import org.spongepowered.configurate.serialize.SerializationException;
public final class RegistryHolderSerializer<T> extends RegistryEntrySerializer<Holder<T>, T> { public final class RegistryHolderSerializer<T> extends RegistryEntrySerializer<Holder<T>, T> {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public RegistryHolderSerializer(TypeToken<T> typeToken, final RegistryAccess registryAccess, ResourceKey<? extends Registry<T>> registryKey, boolean omitMinecraftNamespace) { public RegistryHolderSerializer(
final TypeToken<T> typeToken,
final RegistryAccess registryAccess,
final ResourceKey<? extends Registry<T>> registryKey,
final boolean omitMinecraftNamespace
) {
super((TypeToken<Holder<T>>) TypeToken.get(TypeFactory.parameterizedClass(Holder.class, typeToken.getType())), registryAccess, registryKey, omitMinecraftNamespace); super((TypeToken<Holder<T>>) TypeToken.get(TypeFactory.parameterizedClass(Holder.class, typeToken.getType())), registryAccess, registryKey, omitMinecraftNamespace);
} }
public RegistryHolderSerializer(Class<T> type, final RegistryAccess registryAccess, ResourceKey<? extends Registry<T>> registryKey, boolean omitMinecraftNamespace) { public RegistryHolderSerializer(
final Class<T> type,
final RegistryAccess registryAccess,
final ResourceKey<? extends Registry<T>> registryKey,
final boolean omitMinecraftNamespace
) {
this(TypeToken.get(type), registryAccess, registryKey, omitMinecraftNamespace); this(TypeToken.get(type), registryAccess, registryKey, omitMinecraftNamespace);
Preconditions.checkArgument(type.getTypeParameters().length == 0, "%s must have 0 type parameters", type); Preconditions.checkArgument(type.getTypeParameters().length == 0, "%s must have 0 type parameters", type);
} }
@Override @Override
protected Holder<T> convertFromResourceKey(ResourceKey<T> key) throws SerializationException { protected Holder<T> convertFromResourceKey(final ResourceKey<T> key) throws SerializationException {
return this.registry().get(key).orElseThrow(() -> new SerializationException("Missing holder in " + this.registry().key() + " with key " + key)); return this.registry().get(key).orElseThrow(() -> new SerializationException("Missing holder in " + this.registry().key() + " with key " + key));
} }
@Override @Override
protected ResourceKey<T> convertToResourceKey(Holder<T> value) { protected ResourceKey<T> convertToResourceKey(final Holder<T> value) {
return value.unwrap().map(Function.identity(), r -> this.registry().getResourceKey(r).orElseThrow()); return value.unwrap().map(Function.identity(), r -> this.registry().getResourceKey(r).orElseThrow());
} }
} }

View File

@@ -11,16 +11,26 @@ import org.spongepowered.configurate.serialize.SerializationException;
*/ */
public final class RegistryValueSerializer<T> extends RegistryEntrySerializer<T, T> { public final class RegistryValueSerializer<T> extends RegistryEntrySerializer<T, T> {
public RegistryValueSerializer(TypeToken<T> type, final RegistryAccess registryAccess, ResourceKey<? extends Registry<T>> registryKey, boolean omitMinecraftNamespace) { public RegistryValueSerializer(
final TypeToken<T> type,
final RegistryAccess registryAccess,
final ResourceKey<? extends Registry<T>> registryKey,
final boolean omitMinecraftNamespace
) {
super(type, registryAccess, registryKey, omitMinecraftNamespace); super(type, registryAccess, registryKey, omitMinecraftNamespace);
} }
public RegistryValueSerializer(Class<T> type, final RegistryAccess registryAccess, ResourceKey<? extends Registry<T>> registryKey, boolean omitMinecraftNamespace) { public RegistryValueSerializer(
final Class<T> type,
final RegistryAccess registryAccess,
final ResourceKey<? extends Registry<T>> registryKey,
final boolean omitMinecraftNamespace
) {
super(type, registryAccess, registryKey, omitMinecraftNamespace); super(type, registryAccess, registryKey, omitMinecraftNamespace);
} }
@Override @Override
protected T convertFromResourceKey(ResourceKey<T> key) throws SerializationException { protected T convertFromResourceKey(final ResourceKey<T> key) throws SerializationException {
final T value = this.registry().getValue(key); final T value = this.registry().getValue(key);
if (value == null) { if (value == null) {
throw new SerializationException("Missing value in " + this.registry() + " with key " + key.location()); throw new SerializationException("Missing value in " + this.registry() + " with key " + key.location());
@@ -29,7 +39,7 @@ public final class RegistryValueSerializer<T> extends RegistryEntrySerializer<T,
} }
@Override @Override
protected ResourceKey<T> convertToResourceKey(T value) { protected ResourceKey<T> convertToResourceKey(final T value) {
return this.registry().getResourceKey(value).orElseThrow(); return this.registry().getResourceKey(value).orElseThrow();
} }
} }

View File

@@ -0,0 +1,67 @@
package io.papermc.paper.configuration.transformation.global.versioned;
import com.google.common.collect.ImmutableMap;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.Map;
import java.util.Objects;
import net.minecraft.resources.ResourceLocation;
import org.jspecify.annotations.Nullable;
import org.spongepowered.configurate.ConfigurationNode;
import org.spongepowered.configurate.NodePath;
import org.spongepowered.configurate.transformation.ConfigurationTransformation;
import org.spongepowered.configurate.transformation.TransformAction;
import static java.util.Objects.requireNonNull;
import static org.spongepowered.configurate.NodePath.path;
public class V30_PacketIds implements TransformAction {
public static final V30_PacketIds INSTANCE = new V30_PacketIds();
private static final int VERSION = 30;
private static final Gson GSON = new Gson();
private static final Map<String, ResourceLocation> MOJANG_TO_ID;
static {
final ImmutableMap.Builder<String, ResourceLocation> builder2 = ImmutableMap.builder();
final InputStream input = V30_PacketIds.class.getResourceAsStream("/config-data/packet-limiter-upgrade-data.json");
if (input == null) {
throw new RuntimeException("Failed to load packet limiter upgrade data");
}
try (final Reader reader = new InputStreamReader(new BufferedInputStream(input))) {
final JsonArray array = GSON.fromJson(reader, JsonArray.class);
for (final JsonElement element : array) {
final JsonObject obj = element.getAsJsonObject();
builder2.put(obj.get("simple_class_name").getAsString(), ResourceLocation.parse(obj.get("id").getAsString()));
}
} catch (final IOException e) {
throw new RuntimeException("Failed to load packet limiter upgrade data", e);
}
MOJANG_TO_ID = builder2.build();
}
public static void apply(final ConfigurationTransformation.VersionedBuilder builder) {
builder.addVersion(VERSION, ConfigurationTransformation.builder().addAction(path("packet-limiter", "overrides", ConfigurationTransformation.WILDCARD_OBJECT), INSTANCE).build());
}
private V30_PacketIds() {
}
@Override
public Object @Nullable [] visitPath(final NodePath path, final ConfigurationNode value) {
final String oldClassName = path.get(path.size() - 1).toString();
if (MOJANG_TO_ID.containsKey(oldClassName)) {
return path.with(path.size() - 1, MOJANG_TO_ID.get(oldClassName).toString()).array();
}
return null;
}
}

View File

@@ -1,6 +1,6 @@
package io.papermc.paper.configuration.type; package io.papermc.paper.configuration.type;
import java.lang.reflect.Type; import java.lang.reflect.AnnotatedType;
import java.util.Locale; import java.util.Locale;
import java.util.function.Predicate; import java.util.function.Predicate;
import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.BooleanUtils;
@@ -9,38 +9,38 @@ import org.spongepowered.configurate.serialize.ScalarSerializer;
import org.spongepowered.configurate.serialize.SerializationException; import org.spongepowered.configurate.serialize.SerializationException;
public record BooleanOrDefault(@Nullable Boolean value) { public record BooleanOrDefault(@Nullable Boolean value) {
private static final String DEFAULT_VALUE = "default";
public static final BooleanOrDefault USE_DEFAULT = new BooleanOrDefault(null); public static final BooleanOrDefault USE_DEFAULT = new BooleanOrDefault(null);
public static final ScalarSerializer<BooleanOrDefault> SERIALIZER = new Serializer(); public static final ScalarSerializer<BooleanOrDefault> SERIALIZER = new Serializer();
private static final String DEFAULT_VALUE = "default";
public boolean or(boolean fallback) { public boolean or(final boolean fallback) {
return this.value == null ? fallback : this.value; return this.value == null ? fallback : this.value;
} }
private static final class Serializer extends ScalarSerializer<BooleanOrDefault> { private static final class Serializer extends ScalarSerializer.Annotated<BooleanOrDefault> {
Serializer() { Serializer() {
super(BooleanOrDefault.class); super(BooleanOrDefault.class);
} }
@Override @Override
public BooleanOrDefault deserialize(Type type, Object obj) throws SerializationException { public BooleanOrDefault deserialize(final AnnotatedType type, final Object obj) throws SerializationException {
if (obj instanceof String string) { if (obj instanceof final String string) {
if (DEFAULT_VALUE.equalsIgnoreCase(string)) { if (DEFAULT_VALUE.equalsIgnoreCase(string)) {
return USE_DEFAULT; return USE_DEFAULT;
} }
try { try {
return new BooleanOrDefault(BooleanUtils.toBoolean(string.toLowerCase(Locale.ROOT), "true", "false")); return new BooleanOrDefault(BooleanUtils.toBoolean(string.toLowerCase(Locale.ROOT), "true", "false"));
} catch (IllegalArgumentException ex) { } catch (final IllegalArgumentException ex) {
throw new SerializationException(BooleanOrDefault.class, obj + "(" + type + ") is not a boolean or '" + DEFAULT_VALUE + "'", ex); throw new SerializationException(BooleanOrDefault.class, obj + "(" + type + ") is not a boolean or '" + DEFAULT_VALUE + "'", ex);
} }
} else if (obj instanceof Boolean bool) { } else if (obj instanceof final Boolean bool) {
return new BooleanOrDefault(bool); return new BooleanOrDefault(bool);
} }
throw new SerializationException(BooleanOrDefault.class, obj + "(" + type + ") is not a boolean or '" + DEFAULT_VALUE + "'"); throw new SerializationException(BooleanOrDefault.class, obj + "(" + type + ") is not a boolean or '" + DEFAULT_VALUE + "'");
} }
@Override @Override
protected Object serialize(BooleanOrDefault item, Predicate<Class<?>> typeSupported) { protected Object serialize(final AnnotatedType type, final BooleanOrDefault item, final Predicate<Class<?>> typeSupported) {
final Boolean value = item.value; final Boolean value = item.value;
if (value != null) { if (value != null) {
return value.toString(); return value.toString();

View File

@@ -1,6 +1,7 @@
package io.papermc.paper.configuration.type.number; package io.papermc.paper.configuration.type.number;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import java.lang.reflect.AnnotatedType;
import java.util.OptionalDouble; import java.util.OptionalDouble;
import java.util.function.DoublePredicate; import java.util.function.DoublePredicate;
import java.util.function.Function; import java.util.function.Function;
@@ -20,17 +21,17 @@ public interface DoubleOr {
} }
record Default(OptionalDouble value) implements DoubleOr { record Default(OptionalDouble value) implements DoubleOr {
private static final String DEFAULT_VALUE = "default";
public static final Default USE_DEFAULT = new Default(OptionalDouble.empty()); public static final Default USE_DEFAULT = new Default(OptionalDouble.empty());
private static final String DEFAULT_VALUE = "default";
public static final ScalarSerializer<Default> SERIALIZER = new Serializer<>(Default.class, Default::new, DEFAULT_VALUE, USE_DEFAULT); public static final ScalarSerializer<Default> SERIALIZER = new Serializer<>(Default.class, Default::new, DEFAULT_VALUE, USE_DEFAULT);
} }
record Disabled(OptionalDouble value) implements DoubleOr { record Disabled(OptionalDouble value) implements DoubleOr {
private static final String DISABLED_VALUE = "disabled";
public static final Disabled DISABLED = new Disabled(OptionalDouble.empty()); public static final Disabled DISABLED = new Disabled(OptionalDouble.empty());
private static final String DISABLED_VALUE = "disabled";
public static final ScalarSerializer<Disabled> SERIALIZER = new Serializer<>(Disabled.class, Disabled::new, DISABLED_VALUE, DISABLED); public static final ScalarSerializer<Disabled> SERIALIZER = new Serializer<>(Disabled.class, Disabled::new, DISABLED_VALUE, DISABLED);
public boolean test(DoublePredicate predicate) { public boolean test(final DoublePredicate predicate) {
return this.value.isPresent() && predicate.test(this.value.getAsDouble()); return this.value.isPresent() && predicate.test(this.value.getAsDouble());
} }
@@ -40,12 +41,12 @@ public interface DoubleOr {
} }
final class Serializer<T extends DoubleOr> extends OptionalNumSerializer<T, OptionalDouble> { final class Serializer<T extends DoubleOr> extends OptionalNumSerializer<T, OptionalDouble> {
Serializer(final Class<T> classOfT, final Function<OptionalDouble, T> factory, String emptySerializedValue, T emptyValue) { Serializer(final Class<T> classOfT, final Function<OptionalDouble, T> factory, final String emptySerializedValue, final T emptyValue) {
super(classOfT, emptySerializedValue, emptyValue, OptionalDouble::empty, OptionalDouble::isEmpty, factory, double.class); super(classOfT, emptySerializedValue, emptyValue, OptionalDouble::empty, OptionalDouble::isEmpty, factory, double.class);
} }
@Override @Override
protected Object serialize(final T item, final Predicate<Class<?>> typeSupported) { protected Object serialize(final AnnotatedType type, final T item, final Predicate<Class<?>> typeSupported) {
final OptionalDouble value = item.value(); final OptionalDouble value = item.value();
if (value.isPresent()) { if (value.isPresent()) {
return value.getAsDouble(); return value.getAsDouble();

View File

@@ -2,6 +2,7 @@ package io.papermc.paper.configuration.type.number;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.mojang.logging.LogUtils; import com.mojang.logging.LogUtils;
import java.lang.reflect.AnnotatedType;
import java.util.OptionalInt; import java.util.OptionalInt;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.IntPredicate; import java.util.function.IntPredicate;
@@ -28,17 +29,17 @@ public interface IntOr {
} }
record Default(OptionalInt value) implements IntOr { record Default(OptionalInt value) implements IntOr {
private static final String DEFAULT_VALUE = "default";
public static final Default USE_DEFAULT = new Default(OptionalInt.empty()); public static final Default USE_DEFAULT = new Default(OptionalInt.empty());
private static final String DEFAULT_VALUE = "default";
public static final ScalarSerializer<Default> SERIALIZER = new Serializer<>(Default.class, Default::new, DEFAULT_VALUE, USE_DEFAULT); public static final ScalarSerializer<Default> SERIALIZER = new Serializer<>(Default.class, Default::new, DEFAULT_VALUE, USE_DEFAULT);
} }
record Disabled(OptionalInt value) implements IntOr { record Disabled(OptionalInt value) implements IntOr {
private static final String DISABLED_VALUE = "disabled";
public static final Disabled DISABLED = new Disabled(OptionalInt.empty()); public static final Disabled DISABLED = new Disabled(OptionalInt.empty());
private static final String DISABLED_VALUE = "disabled";
public static final ScalarSerializer<Disabled> SERIALIZER = new Serializer<>(Disabled.class, Disabled::new, DISABLED_VALUE, DISABLED); public static final ScalarSerializer<Disabled> SERIALIZER = new Serializer<>(Disabled.class, Disabled::new, DISABLED_VALUE, DISABLED);
public boolean test(IntPredicate predicate) { public boolean test(final IntPredicate predicate) {
return this.value.isPresent() && predicate.test(this.value.getAsInt()); return this.value.isPresent() && predicate.test(this.value.getAsInt());
} }
@@ -49,7 +50,7 @@ public interface IntOr {
final class Serializer<T extends IntOr> extends OptionalNumSerializer<T, OptionalInt> { final class Serializer<T extends IntOr> extends OptionalNumSerializer<T, OptionalInt> {
private Serializer(Class<T> classOfT, Function<OptionalInt, T> factory, String emptySerializedValue, T emptyValue) { private Serializer(final Class<T> classOfT, final Function<OptionalInt, T> factory, final String emptySerializedValue, final T emptyValue) {
super(classOfT, emptySerializedValue, emptyValue, OptionalInt::empty, OptionalInt::isEmpty, factory, int.class); super(classOfT, emptySerializedValue, emptyValue, OptionalInt::empty, OptionalInt::isEmpty, factory, int.class);
} }
@@ -73,7 +74,7 @@ public interface IntOr {
} }
@Override @Override
protected Object serialize(final T item, final Predicate<Class<?>> typeSupported) { protected Object serialize(final AnnotatedType type, final T item, final Predicate<Class<?>> typeSupported) {
final OptionalInt value = item.value(); final OptionalInt value = item.value();
if (value.isPresent()) { if (value.isPresent()) {
return value.getAsInt(); return value.getAsInt();

View File

@@ -17,7 +17,15 @@ public abstract class OptionalNumSerializer<T, O> extends ScalarSerializer.Annot
private final Function<O, T> factory; private final Function<O, T> factory;
private final Class<?> number; private final Class<?> number;
protected OptionalNumSerializer(final Class<T> classOfT, final String emptySerializedValue, final T emptyValue, final Supplier<O> empty, final Predicate<O> isEmpty, final Function<O, T> factory, final Class<?> number) { protected OptionalNumSerializer(
final Class<T> classOfT,
final String emptySerializedValue,
final T emptyValue,
final Supplier<O> empty,
final Predicate<O> isEmpty,
final Function<O, T> factory,
final Class<?> number
) {
super(classOfT); super(classOfT);
this.emptySerializedValue = emptySerializedValue; this.emptySerializedValue = emptySerializedValue;
this.emptyValue = emptyValue; this.emptyValue = emptyValue;
@@ -30,7 +38,7 @@ public abstract class OptionalNumSerializer<T, O> extends ScalarSerializer.Annot
@Override @Override
public final T deserialize(final AnnotatedType type, final Object obj) throws SerializationException { public final T deserialize(final AnnotatedType type, final Object obj) throws SerializationException {
final O value; final O value;
if (obj instanceof String string) { if (obj instanceof final String string) {
if (this.emptySerializedValue.equalsIgnoreCase(string)) { if (this.emptySerializedValue.equalsIgnoreCase(string)) {
value = this.empty.get(); value = this.empty.get();
} else if (NumberUtils.isParsable(string)) { } else if (NumberUtils.isParsable(string)) {
@@ -38,7 +46,7 @@ public abstract class OptionalNumSerializer<T, O> extends ScalarSerializer.Annot
} else { } else {
throw new SerializationException("%s (%s) is not a(n) %s or '%s'".formatted(obj, type, this.number.getSimpleName(), this.emptySerializedValue)); throw new SerializationException("%s (%s) is not a(n) %s or '%s'".formatted(obj, type, this.number.getSimpleName(), this.emptySerializedValue));
} }
} else if (obj instanceof Number num) { } else if (obj instanceof final Number num) {
value = this.full(num); value = this.full(num);
} else { } else {
throw new SerializationException("%s (%s) is not a(n) %s or '%s'".formatted(obj, type, this.number.getSimpleName(), this.emptySerializedValue)); throw new SerializationException("%s (%s) is not a(n) %s or '%s'".formatted(obj, type, this.number.getSimpleName(), this.emptySerializedValue));

File diff suppressed because one or more lines are too long