Add serialization support for Bukkit's ConfigurationSerializable types

dev
Exlll 3 years ago
parent f70c44f159
commit 3ea01199db

@ -1,7 +1,8 @@
package de.exlll.configlib;
import java.util.HashMap;
import java.util.Map;
import java.lang.reflect.Type;
import java.util.*;
import java.util.function.Predicate;
import static de.exlll.configlib.Validator.requireNonNull;
@ -10,6 +11,7 @@ import static de.exlll.configlib.Validator.requireNonNull;
*/
class ConfigurationProperties {
private final Map<Class<?>, Serializer<?, ?>> serializersByType;
private final Map<Predicate<? super Type>, Serializer<?, ?>> serializersByCondition;
private final FieldFormatter formatter;
private final FieldFilter filter;
private final boolean outputNulls;
@ -24,6 +26,9 @@ class ConfigurationProperties {
*/
protected ConfigurationProperties(Builder<?> builder) {
this.serializersByType = Map.copyOf(builder.serializersByType);
this.serializersByCondition = Collections.unmodifiableMap(new LinkedHashMap<>(
builder.serializersByCondition
));
this.formatter = requireNonNull(builder.formatter, "field formatter");
this.filter = requireNonNull(builder.filter, "field filter");
this.outputNulls = builder.outputNulls;
@ -68,7 +73,8 @@ class ConfigurationProperties {
*/
public static abstract class Builder<B extends Builder<B>> {
private final Map<Class<?>, Serializer<?, ?>> serializersByType = new HashMap<>();
/* change setter JavaDoc if default values are changed */
private final Map<Predicate<? super Type>, Serializer<?, ?>> serializersByCondition =
new LinkedHashMap<>();
private FieldFormatter formatter = FieldFormatters.IDENTITY;
private FieldFilter filter = FieldFilters.DEFAULT;
private boolean outputNulls = false;
@ -79,6 +85,7 @@ class ConfigurationProperties {
protected Builder(ConfigurationProperties properties) {
this.serializersByType.putAll(properties.serializersByType);
this.serializersByCondition.putAll(properties.serializersByCondition);
this.formatter = properties.formatter;
this.filter = properties.filter;
this.outputNulls = properties.outputNulls;
@ -131,6 +138,29 @@ class ConfigurationProperties {
return getThis();
}
/**
* Adds a serializer for the condition. The serializer is selected when the condition
* evaluates to true. The {@code test} method of the condition object is invoked with
* the type of a field. Serializers added by this method take precedence over all other
* serializers expect the ones that were added for a specific type by the
* {@link #addSerializer(Class, Serializer)} method. The conditions are checked in the order
* in which they were added.
*
* @param condition the condition
* @param serializer the serializer
* @return this builder
* @throws NullPointerException if any argument is null
*/
final B addSerializerByCondition(
Predicate<? super Type> condition,
Serializer<?, ?> serializer
) {
requireNonNull(condition, "condition");
requireNonNull(serializer, "serializer");
serializersByCondition.put(condition, serializer);
return getThis();
}
/**
* Sets whether fields or collection elements whose value is null should be output
* while serializing the configuration.
@ -215,6 +245,18 @@ class ConfigurationProperties {
return serializersByType;
}
/**
* Returns an unmodifiable map of serializers by condition. The serializers returned by this
* method take precedence over any default serializers provided by this library expect the ones
* that were added for a specific type.
*
* @return serializers by condition
*/
final Map<Predicate<? super Type>, Serializer<?, ?>> getSerializersByCondition() {
return serializersByCondition;
}
/**
* Returns whether null values should be output.
*

@ -42,6 +42,9 @@ final class SerializerSelector {
}
public Serializer<?, ?> select(Type type) {
final Serializer<?, ?> custom = selectCustomSerializer(type);
if (custom != null)
return custom;
if (type instanceof Class<?> cls) {
return selectForClass(cls);
} else if (type instanceof ParameterizedType pType) {
@ -60,9 +63,19 @@ final class SerializerSelector {
throw new ConfigurationException(baseExceptionMessage(type));
}
private Serializer<?, ?> selectCustomSerializer(Type type) {
if (type instanceof Class<?> cls) {
if (properties.getSerializers().containsKey(cls)) // TODO move check out
return properties.getSerializers().get(cls);
}
for (var entry : properties.getSerializersByCondition().entrySet()) {
if (entry.getKey().test(type))
return entry.getValue();
}
return null;
}
private Serializer<?, ?> selectForClass(Class<?> cls) {
if (properties.getSerializers().containsKey(cls))
return properties.getSerializers().get(cls);
if (DEFAULT_SERIALIZERS.containsKey(cls))
return DEFAULT_SERIALIZERS.get(cls);
if (Reflect.isEnumType(cls)) {

@ -1,10 +1,13 @@
package de.exlll.configlib;
import de.exlll.configlib.Serializers.StringSerializer;
import org.junit.jupiter.api.Test;
import java.awt.Point;
import java.lang.reflect.Type;
import java.util.Locale;
import java.util.Map;
import java.util.function.Predicate;
import static de.exlll.configlib.TestUtils.assertThrowsNullPointerException;
import static org.hamcrest.MatcherAssert.assertThat;
@ -21,6 +24,7 @@ class ConfigurationPropertiesTest {
assertThat(properties.outputNulls(), is(false));
assertThat(properties.inputNulls(), is(false));
assertThat(properties.getSerializers().entrySet(), empty());
assertThat(properties.getSerializersByCondition().entrySet(), empty());
assertThat(properties.getFieldFormatter(), is(FieldFormatters.IDENTITY));
assertThat(properties.getFieldFilter(), is(FieldFilters.DEFAULT));
}
@ -30,9 +34,11 @@ class ConfigurationPropertiesTest {
FieldFormatter formatter = field -> field.getName().toLowerCase(Locale.ROOT);
FieldFilter filter = field -> field.getName().startsWith("f");
TestUtils.PointSerializer serializer = new TestUtils.PointSerializer();
Predicate<? super Type> predicate = type -> true;
ConfigurationProperties properties = ConfigurationProperties.newBuilder()
.addSerializer(Point.class, serializer)
.addSerializerByCondition(predicate, serializer)
.setFieldFormatter(formatter)
.setFieldFilter(filter)
.outputNulls(true)
@ -41,6 +47,7 @@ class ConfigurationPropertiesTest {
.build();
assertThat(properties.getSerializers(), is(Map.of(Point.class, serializer)));
assertThat(properties.getSerializersByCondition(), is(Map.of(predicate, serializer)));
assertThat(properties.outputNulls(), is(true));
assertThat(properties.inputNulls(), is(true));
assertThat(properties.serializeSetsAsLists(), is(false));
@ -53,9 +60,11 @@ class ConfigurationPropertiesTest {
FieldFormatter formatter = field -> field.getName().toLowerCase(Locale.ROOT);
FieldFilter filter = field -> field.getName().startsWith("f");
TestUtils.PointSerializer serializer = new TestUtils.PointSerializer();
Predicate<? super Type> predicate = type -> true;
ConfigurationProperties properties = ConfigurationProperties.newBuilder()
.addSerializer(Point.class, serializer)
.addSerializerByCondition(predicate, serializer)
.setFieldFormatter(formatter)
.setFieldFilter(filter)
.outputNulls(true)
@ -66,6 +75,7 @@ class ConfigurationPropertiesTest {
.build();
assertThat(properties.getSerializers(), is(Map.of(Point.class, serializer)));
assertThat(properties.getSerializersByCondition(), is(Map.of(predicate, serializer)));
assertThat(properties.outputNulls(), is(true));
assertThat(properties.inputNulls(), is(true));
assertThat(properties.serializeSetsAsLists(), is(false));
@ -76,11 +86,16 @@ class ConfigurationPropertiesTest {
@Test
void builderSerializersUnmodifiable() {
ConfigurationProperties properties = ConfigurationProperties.newBuilder().build();
Map<Class<?>, Serializer<?, ?>> map = properties.getSerializers();
var serializersByType = properties.getSerializers();
var serializersByCondition = properties.getSerializersByCondition();
assertThrows(
UnsupportedOperationException.class,
() -> map.put(Point.class, new TestUtils.PointSerializer())
() -> serializersByType.put(Point.class, new TestUtils.PointSerializer())
);
assertThrows(
UnsupportedOperationException.class,
() -> serializersByCondition.put(t -> true, new TestUtils.PointSerializer())
);
}
@ -104,9 +119,9 @@ class ConfigurationPropertiesTest {
}
@Test
void addSerializerRequiresNonNull() {
void addSerializerByTypeRequiresNonNull() {
assertThrowsNullPointerException(
() -> builder.addSerializer(null, new Serializers.StringSerializer()),
() -> builder.addSerializer(null, new StringSerializer()),
"serialized type"
);
@ -115,5 +130,18 @@ class ConfigurationPropertiesTest {
"serializer"
);
}
@Test
void addSerializerByConditionRequiresNonNull() {
assertThrowsNullPointerException(
() -> builder.addSerializerByCondition(null, new StringSerializer()),
"condition"
);
assertThrowsNullPointerException(
() -> builder.addSerializerByCondition(type -> true, null),
"serializer"
);
}
}
}

@ -178,7 +178,7 @@ class SerializerSelectorTest {
}
@Test
void selectSerializerCustomType() {
void selectSerializerByCustomType() {
var properties = ConfigurationProperties.newBuilder()
.addSerializer(Point.class, POINT_SERIALIZER)
.build();
@ -188,7 +188,7 @@ class SerializerSelectorTest {
}
@Test
void selectSerializerCustomSerializerTakesPrecedence() {
void selectSerializerByCustomTypeTakesPrecedence() {
var properties = ConfigurationProperties.newBuilder()
.addSerializer(BigInteger.class, CUSTOM_BIG_INTEGER_SERIALIZER)
.build();
@ -198,6 +198,41 @@ class SerializerSelectorTest {
assertThat(bigIntegerSerializer, sameInstance(CUSTOM_BIG_INTEGER_SERIALIZER));
}
@Test
void selectSerializerByCondition() {
var properties = ConfigurationProperties.newBuilder()
.addSerializerByCondition(t -> t == Point.class, POINT_SERIALIZER)
.build();
SerializerSelector selector = new SerializerSelector(properties);
var pointSerializer = selector.select(Point.class);
assertThat(pointSerializer, sameInstance(POINT_SERIALIZER));
}
@Test
void selectSerializerByConditionTakesPrecedence() {
var properties = ConfigurationProperties.newBuilder()
.addSerializerByCondition(t -> t == BigInteger.class, CUSTOM_BIG_INTEGER_SERIALIZER)
.build();
SerializerSelector selector = new SerializerSelector(properties);
var bigIntegerSerializer = selector.select(BigInteger.class);
assertThat(bigIntegerSerializer, instanceOf(TestUtils.CustomBigIntegerSerializer.class));
assertThat(bigIntegerSerializer, sameInstance(CUSTOM_BIG_INTEGER_SERIALIZER));
}
@Test
void selectSerializerByCustomTypeTakesPrecedenceOverCustomType() {
var serializer1 = IdentifiableSerializer.of(1);
var serializer2 = IdentifiableSerializer.of(2);
var properties = ConfigurationProperties.newBuilder()
.addSerializerByCondition(t -> t == int.class, serializer1)
.addSerializer(int.class, serializer2)
.build();
SerializerSelector selector = new SerializerSelector(properties);
var serializer = selector.select(int.class);
assertThat(serializer, instanceOf(IdentifiableSerializer.class));
assertThat(serializer, sameInstance(serializer2));
}
@Test
void selectSerializerList() {
class A {

@ -94,6 +94,28 @@ public final class TestUtils {
}
}
public static final class IdentifiableSerializer<S, T> implements Serializer<S, T> {
public int identifier;
public IdentifiableSerializer(int identifier) {
this.identifier = identifier;
}
public static IdentifiableSerializer<Integer, Integer> of(int identifier) {
return new IdentifiableSerializer<>(identifier);
}
@Override
public T serialize(S element) {
return null;
}
@Override
public S deserialize(T element) {
return null;
}
}
public static final class PointIdentitySerializer implements Serializer<Point, Point> {
@Override
public Point serialize(Point element) {

@ -0,0 +1,31 @@
package de.exlll.configlib;
import org.bukkit.configuration.file.YamlConstructor;
import org.bukkit.configuration.file.YamlRepresenter;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
final class BukkitConfigurationSerializableSerializer
implements Serializer<ConfigurationSerializable, String> {
static BukkitConfigurationSerializableSerializer DEFAULT =
new BukkitConfigurationSerializableSerializer();
private final Yaml yaml;
BukkitConfigurationSerializableSerializer() {
DumperOptions options = new DumperOptions();
options.setIndent(2);
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
this.yaml = new Yaml(new YamlConstructor(), new YamlRepresenter(), options);
}
@Override
public String serialize(ConfigurationSerializable element) {
return yaml.dump(element);
}
@Override
public ConfigurationSerializable deserialize(String element) {
return yaml.load(element);
}
}

@ -1,8 +1,31 @@
package de.exlll.configlib;
import org.bukkit.configuration.serialization.ConfigurationSerializable;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.java.JavaPlugin;
/**
* An empty plugin class that loads this library and its dependencies.
* The plugin class that loads this library and its dependencies.
*/
public final class ConfigLib extends JavaPlugin {}
public final class ConfigLib extends JavaPlugin {
/**
* A {@code YamlConfigurationProperties} object that provides serializers for several Bukkit
* classes like {@link ItemStack} and other {@link ConfigurationSerializable} types.
* <p>
* You can configure these properties further by creating a new builder using the
* {@code toBuilder()} method of this object.
*/
public static final YamlConfigurationProperties BUKKIT_DEFAULT_PROPERTIES =
initializeBukkitDefaultProperties();
private static YamlConfigurationProperties initializeBukkitDefaultProperties() {
return YamlConfigurationProperties
.newBuilder()
.addSerializerByCondition(
type -> type instanceof Class<?> cls &&
ConfigurationSerializable.class.isAssignableFrom(cls),
BukkitConfigurationSerializableSerializer.DEFAULT
)
.build();
}
}

Loading…
Cancel
Save