From 0854352e80d61fcfa1651db4bc9fb6b65d86c2a5 Mon Sep 17 00:00:00 2001 From: Bukkit/Spigot Date: Fri, 22 Apr 2022 19:38:45 +1000 Subject: [PATCH] SPIGOT-6949: Configuration sections that are nested within Maps or Lists are not properly serialized. This broke with the configuration changes in ed8a152b3ae55c5f2b783729ed284d14010df169. This commit reverts one of the changes of this other commit so that nested configuration sections are serialized as Maps again. Although the types of these nested configuration sections are not preserved when reloading a configuration (they turn into Maps), their contents should at least be preserved, as it has been the case in earlier Bukkit versions. By: blablubbabc --- .../configuration/file/YamlRepresenter.java | 12 ++++ .../file/YamlConfigurationTest.java | 63 +++++++++++++++++++ 2 files changed, 75 insertions(+) diff --git a/paper-api/src/main/java/org/bukkit/configuration/file/YamlRepresenter.java b/paper-api/src/main/java/org/bukkit/configuration/file/YamlRepresenter.java index 20e9687647..4670f86681 100644 --- a/paper-api/src/main/java/org/bukkit/configuration/file/YamlRepresenter.java +++ b/paper-api/src/main/java/org/bukkit/configuration/file/YamlRepresenter.java @@ -2,6 +2,7 @@ package org.bukkit.configuration.file; import java.util.LinkedHashMap; import java.util.Map; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.serialization.ConfigurationSerializable; import org.bukkit.configuration.serialization.ConfigurationSerialization; import org.jetbrains.annotations.NotNull; @@ -11,12 +12,23 @@ import org.yaml.snakeyaml.representer.Representer; public class YamlRepresenter extends Representer { public YamlRepresenter() { + this.multiRepresenters.put(ConfigurationSection.class, new RepresentConfigurationSection()); this.multiRepresenters.put(ConfigurationSerializable.class, new RepresentConfigurationSerializable()); // SPIGOT-6234: We could just switch YamlConstructor to extend Constructor rather than SafeConstructor, however there is a very small risk of issues with plugins treating config as untrusted input // So instead we will just allow future plugins to have their enums extend ConfigurationSerializable this.multiRepresenters.remove(Enum.class); } + // SPIGOT-6949: Used by configuration sections that are nested within lists or maps. + private class RepresentConfigurationSection extends RepresentMap { + + @NotNull + @Override + public Node representData(@NotNull Object data) { + return super.representData(((ConfigurationSection) data).getValues(false)); + } + } + private class RepresentConfigurationSerializable extends RepresentMap { @NotNull diff --git a/paper-api/src/test/java/org/bukkit/configuration/file/YamlConfigurationTest.java b/paper-api/src/test/java/org/bukkit/configuration/file/YamlConfigurationTest.java index 3522baa0a2..194949d74a 100644 --- a/paper-api/src/test/java/org/bukkit/configuration/file/YamlConfigurationTest.java +++ b/paper-api/src/test/java/org/bukkit/configuration/file/YamlConfigurationTest.java @@ -3,8 +3,14 @@ package org.bukkit.configuration.file; import static org.junit.Assert.*; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.InvalidConfigurationException; +import org.bukkit.configuration.MemoryConfiguration; import org.junit.Test; public class YamlConfigurationTest extends FileConfigurationTest { @@ -201,4 +207,61 @@ public class YamlConfigurationTest extends FileConfigurationTest { + "'!!null': X\n"; assertEquals(expected, config.saveToString()); } + + // SPIGOT-6949 + @Test + public void testNestedConfigSections() throws InvalidConfigurationException { + YamlConfiguration config = getConfig(); + List configList = new ArrayList<>(); + + MemoryConfiguration nestedSection = new MemoryConfiguration(); + nestedSection.set("something", "value"); + configList.add(nestedSection); + + Map nestedMap = new HashMap<>(); + nestedMap.put("scalar", 10); + nestedMap.put("string", "something"); + + MemoryConfiguration nestedSection2 = new MemoryConfiguration(); + nestedSection2.set("embedded", "value"); + nestedMap.put("section", nestedSection2); + configList.add(nestedMap); + + config.set("list", configList); + + String serialized = config.saveToString(); + YamlConfiguration deserialized = new YamlConfiguration(); + deserialized.loadFromString(serialized); + + // The types of nested maps or configuration sections or configs might not be preserved, but + // their contents should be preserved: + assertEquals(convertSectionsToMaps(config), convertSectionsToMaps(deserialized)); + } + + // Recursively converts all configuration sections to Maps, including within any nested data + // structures such as Maps and Lists. + private Object convertSectionsToMaps(Object object) { + if (object instanceof ConfigurationSection) { + ConfigurationSection section = (ConfigurationSection) object; + Map values = section.getValues(false); + return convertSectionsToMaps(values); + } else if (object instanceof Map) { + Map map = (Map) object; // Might be immutable + Map newMap = new LinkedHashMap<>(); + for (Entry entry : map.entrySet()) { + newMap.put(entry.getKey(), convertSectionsToMaps(entry.getValue())); + } + return newMap; + } else if (object instanceof Iterable) { + // Any other type of Collection is converted to a list: + Iterable iterable = (Iterable) object; // Might be immutable + List newList = new ArrayList<>(); + for (Object element : iterable) { + newList.add(convertSectionsToMaps(element)); + } + return newList; + } else { + return object; + } + } }