SPIGOT-6949: Configuration sections that are nested within Maps or Lists are not properly serialized.

This broke with the configuration changes in
ed8a152b3a.

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 <lukas@wirsindwir.de>
This commit is contained in:
Bukkit/Spigot 2022-04-22 19:38:45 +10:00
parent 212f79b449
commit 0854352e80
2 changed files with 75 additions and 0 deletions

View File

@ -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

View File

@ -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<Object> configList = new ArrayList<>();
MemoryConfiguration nestedSection = new MemoryConfiguration();
nestedSection.set("something", "value");
configList.add(nestedSection);
Map<String, Object> 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<String, Object> values = section.getValues(false);
return convertSectionsToMaps(values);
} else if (object instanceof Map) {
Map<?, ?> map = (Map<?, ?>) object; // Might be immutable
Map<Object, Object> 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<Object> newList = new ArrayList<>();
for (Object element : iterable) {
newList.add(convertSectionsToMaps(element));
}
return newList;
} else {
return object;
}
}
}