forked from public-mirrors/ConfigLib
parent
65555f7dd6
commit
7c4f630f7c
@ -1,21 +1,18 @@
|
||||
name: publish-on-master-push
|
||||
name: publish-on-dispatch
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
paths-ignore:
|
||||
- '**/README.md'
|
||||
on: [ 'workflow_dispatch' ]
|
||||
|
||||
jobs:
|
||||
build-and-publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 16
|
||||
uses: actions/setup-java@v2
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
- name: Setup JDK and Gradle
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '16'
|
||||
distribution: 'temurin'
|
||||
java-version: '17'
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew build
|
||||
- name: Archive test reports
|
@ -1,5 +0,0 @@
|
||||
language: java
|
||||
jdk:
|
||||
- oraclejdk8
|
||||
- oraclejdk9
|
||||
- oraclejdk10
|
@ -1,5 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
public class ConfigLib extends JavaPlugin {}
|
@ -1,56 +0,0 @@
|
||||
package de.exlll.configlib.configs.yaml;
|
||||
|
||||
import org.bukkit.configuration.file.YamlConstructor;
|
||||
import org.bukkit.configuration.file.YamlRepresenter;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* A {@code BukkitYamlConfiguration} is a specialized form of a
|
||||
* {@code YamlConfiguration} that uses better default values.
|
||||
*/
|
||||
public abstract class BukkitYamlConfiguration extends YamlConfiguration {
|
||||
protected BukkitYamlConfiguration(Path path, BukkitYamlProperties properties) {
|
||||
super(path, properties);
|
||||
}
|
||||
|
||||
protected BukkitYamlConfiguration(Path path) {
|
||||
this(path, BukkitYamlProperties.DEFAULT);
|
||||
}
|
||||
|
||||
public static class BukkitYamlProperties extends YamlProperties {
|
||||
public static final BukkitYamlProperties DEFAULT = builder().build();
|
||||
|
||||
private BukkitYamlProperties(Builder<?> builder) {
|
||||
super(builder);
|
||||
}
|
||||
|
||||
public static Builder<?> builder() {
|
||||
return new Builder() {
|
||||
@Override
|
||||
protected Builder<?> getThis() {
|
||||
return this;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static abstract class
|
||||
Builder<B extends BukkitYamlProperties.Builder<B>>
|
||||
extends YamlProperties.Builder<B> {
|
||||
|
||||
protected Builder() {
|
||||
setConstructor(new YamlConstructor());
|
||||
setRepresenter(new YamlRepresenter());
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new {@code BukkitYamlProperties} instance using the values set.
|
||||
*
|
||||
* @return new {@code BukkitYamlProperties} instance
|
||||
*/
|
||||
public BukkitYamlProperties build() {
|
||||
return new BukkitYamlProperties(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
name: ConfigLib
|
||||
author: Exlll
|
||||
|
||||
version: 2.2.0
|
||||
main: de.exlll.configlib.ConfigLib
|
@ -1,5 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
|
||||
public class ConfigLib extends Plugin {}
|
@ -1,5 +0,0 @@
|
||||
name: ConfigLib
|
||||
author: Exlll
|
||||
|
||||
version: 2.2.0
|
||||
main: de.exlll.configlib.ConfigLib
|
@ -1,85 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import de.exlll.configlib.annotation.Comment;
|
||||
|
||||
import java.lang.reflect.AnnotatedElement;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
|
||||
/**
|
||||
* Instances of this class contain all comments of a {@link Configuration} class
|
||||
* and its fields.
|
||||
*/
|
||||
public final class Comments {
|
||||
private final List<String> classComments;
|
||||
private final Map<String, List<String>> fieldComments;
|
||||
|
||||
private Comments(List<String> classComments,
|
||||
Map<String, List<String>> fieldComments) {
|
||||
this.classComments = classComments;
|
||||
this.fieldComments = fieldComments;
|
||||
}
|
||||
|
||||
static Comments ofClass(Class<?> cls) {
|
||||
List<String> classComments = getComments(cls);
|
||||
Map<String, List<String>> fieldComments = Arrays
|
||||
.stream(cls.getDeclaredFields())
|
||||
.filter(Comments::isCommented)
|
||||
.collect(toMap(Field::getName, Comments::getComments));
|
||||
return new Comments(classComments, fieldComments);
|
||||
}
|
||||
|
||||
private static boolean isCommented(AnnotatedElement element) {
|
||||
return element.isAnnotationPresent(Comment.class);
|
||||
}
|
||||
|
||||
private static List<String> getComments(AnnotatedElement element) {
|
||||
Comment comment = element.getAnnotation(Comment.class);
|
||||
return (comment != null)
|
||||
? Arrays.asList(comment.value())
|
||||
: Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the {@code Configuration} this {@code Comments} object belongs to
|
||||
* has class comments.
|
||||
*
|
||||
* @return true, if {@code Configuration} has class comments.
|
||||
*/
|
||||
public boolean hasClassComments() {
|
||||
return !classComments.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the {@code Configuration} this {@code Comments} object belongs to
|
||||
* has field comments.
|
||||
*
|
||||
* @return true, if {@code Configuration} has field comments.
|
||||
*/
|
||||
public boolean hasFieldComments() {
|
||||
return !fieldComments.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of class comments.
|
||||
*
|
||||
* @return list of class comments
|
||||
*/
|
||||
public List<String> getClassComments() {
|
||||
return classComments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns lists of field comments mapped by field name.
|
||||
*
|
||||
* @return lists of field comments by field name
|
||||
*/
|
||||
public Map<String, List<String>> getFieldComments() {
|
||||
return fieldComments;
|
||||
}
|
||||
}
|
@ -1,214 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import de.exlll.configlib.FieldMapper.MappingInfo;
|
||||
import de.exlll.configlib.filter.FieldFilter;
|
||||
import de.exlll.configlib.filter.FieldFilters;
|
||||
import de.exlll.configlib.format.FieldNameFormatter;
|
||||
import de.exlll.configlib.format.FieldNameFormatters;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Parent class of all configurations.
|
||||
* <p>
|
||||
* This class contains the most basic methods that every configuration needs.
|
||||
*
|
||||
* @param <C> type of the configuration
|
||||
*/
|
||||
public abstract class Configuration<C extends Configuration<C>> {
|
||||
/**
|
||||
* {@code Comments} object containing all class and field comments
|
||||
* of this configuration
|
||||
*/
|
||||
protected final Comments comments;
|
||||
private final Properties props;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code Configuration} object.
|
||||
*
|
||||
* @param properties {@code Properties} used to configure this configuration
|
||||
* @throws NullPointerException if {@code properties} is null
|
||||
*/
|
||||
protected Configuration(Properties properties) {
|
||||
this.props = Objects.requireNonNull(properties);
|
||||
this.comments = Comments.ofClass(getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves this {@code Configuration}.
|
||||
*
|
||||
* @throws ConfigurationException if any field is not properly configured
|
||||
* @throws ConfigurationStoreException if an I/O error occurred while loading
|
||||
* this configuration
|
||||
*/
|
||||
public final void save() {
|
||||
try {
|
||||
preSave();
|
||||
MappingInfo mappingInfo = MappingInfo.from(this);
|
||||
Map<String, Object> map = FieldMapper
|
||||
.instanceToMap(this, mappingInfo);
|
||||
getSource().saveConfiguration(getThis(), map);
|
||||
} catch (IOException e) {
|
||||
throw new ConfigurationStoreException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads this {@code Configuration}.
|
||||
*
|
||||
* @throws ConfigurationException if values cannot be converted back to their
|
||||
* original representation
|
||||
* @throws ConfigurationStoreException if an I/O error occurred while loading
|
||||
* this configuration
|
||||
*/
|
||||
public final void load() {
|
||||
try {
|
||||
Map<String, Object> map = getSource().loadConfiguration(getThis());
|
||||
MappingInfo mappingInfo = MappingInfo.from(this);
|
||||
FieldMapper.instanceFromMap(this, map, mappingInfo);
|
||||
postLoad();
|
||||
} catch (IOException e) {
|
||||
throw new ConfigurationStoreException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ConfigurationSource} used for saving and loading this
|
||||
* {@code Configuration}.
|
||||
*
|
||||
* @return {@code ConfigurationSource} used for saving and loading
|
||||
*/
|
||||
protected abstract ConfigurationSource<C> getSource();
|
||||
|
||||
/**
|
||||
* Returns this {@code Configuration}.
|
||||
*
|
||||
* @return this {@code Configuration}
|
||||
*/
|
||||
protected abstract C getThis();
|
||||
|
||||
/**
|
||||
* Hook that is executed right before this {@code Configuration} is saved.
|
||||
* <p>
|
||||
* The default implementation of this method does nothing.
|
||||
*/
|
||||
protected void preSave() {}
|
||||
|
||||
/**
|
||||
* Hook that is executed right after this {@code Configuration} has
|
||||
* successfully been loaded.
|
||||
* <p>
|
||||
* The default implementation of this method does nothing.
|
||||
*/
|
||||
protected void postLoad() {}
|
||||
|
||||
Properties getProperties() {
|
||||
return props;
|
||||
}
|
||||
|
||||
/**
|
||||
* Instances of a {@code Properties} class are used to configure different
|
||||
* aspects of a configuration.
|
||||
*/
|
||||
protected static class Properties {
|
||||
private final FieldNameFormatter formatter;
|
||||
private final FieldFilter filter;
|
||||
|
||||
/**
|
||||
* Constructs a new {@code Properties} object.
|
||||
*
|
||||
* @param builder {@code Builder} used for construction
|
||||
* @throws NullPointerException if {@code builder} is null
|
||||
*/
|
||||
protected Properties(Builder<?> builder) {
|
||||
this.formatter = builder.formatter;
|
||||
this.filter = builder.filter;
|
||||
}
|
||||
|
||||
static Builder<?> builder() {
|
||||
return new Builder() {
|
||||
@Override
|
||||
protected Builder<?> getThis() {
|
||||
return this;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code FieldNameFormatter} of a configuration.
|
||||
*
|
||||
* @return {@code FieldNameFormatter} of a configuration
|
||||
*/
|
||||
public final FieldNameFormatter getFormatter() {
|
||||
return formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code FieldFilter} of a configuration
|
||||
*
|
||||
* @return {@code FieldFilter} of a configuration
|
||||
*/
|
||||
public final FieldFilter getFilter() {
|
||||
return filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder classes are used for constructing {@code Properties}.
|
||||
*
|
||||
* @param <B> type of the builder
|
||||
*/
|
||||
protected static abstract class Builder<B extends Builder<B>> {
|
||||
private FieldNameFormatter formatter = FieldNameFormatters.IDENTITY;
|
||||
private FieldFilter filter = FieldFilters.DEFAULT;
|
||||
|
||||
protected Builder() {}
|
||||
|
||||
/**
|
||||
* Returns this {@code Builder}.
|
||||
*
|
||||
* @return this {@code Builder}
|
||||
*/
|
||||
protected abstract B getThis();
|
||||
|
||||
/**
|
||||
* Sets the {@link FieldNameFormatter} for a configuration.
|
||||
*
|
||||
* @param formatter formatter for configuration
|
||||
* @return this {@code Builder}
|
||||
* @throws NullPointerException if {@code formatter} is null
|
||||
*/
|
||||
public final B setFormatter(FieldNameFormatter formatter) {
|
||||
this.formatter = Objects.requireNonNull(formatter);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Composes the given {@link FieldFilter} with the
|
||||
* {@code FieldFilters.DEFAULT} instance and any other
|
||||
* previously added filters.
|
||||
* <p>
|
||||
* The added filter is not evaluated for a field if the field has
|
||||
* already been filtered or by some other {@code FieldFilter}.
|
||||
*
|
||||
* @param filter field filter that is added
|
||||
* @return this {@code Builder}
|
||||
* @throws NullPointerException if {@code filter} is null
|
||||
*/
|
||||
public final B addFilter(FieldFilter filter) {
|
||||
this.filter = this.filter.and(filter);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new {@code Properties} instance using the values set.
|
||||
*
|
||||
* @return new {@code Properties} instance
|
||||
*/
|
||||
public Properties build() {
|
||||
return new Properties(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Implementations of this class save and load {@code Map<String, Object>} maps that
|
||||
* represent converted configurations.
|
||||
*
|
||||
* @param <C> type of the configuration
|
||||
*/
|
||||
public interface ConfigurationSource<C extends Configuration<C>> {
|
||||
/**
|
||||
* Saves the given map.
|
||||
*
|
||||
* @param config the configuration that the {@code map} object represents
|
||||
* @param map map that is saved
|
||||
* @throws IOException if an I/O error occurs when saving the {@code map}
|
||||
*/
|
||||
void saveConfiguration(C config, Map<String, Object> map)
|
||||
throws IOException;
|
||||
|
||||
/**
|
||||
* Loads the map representing the given {@code Configuration}.
|
||||
*
|
||||
* @param config the configuration instance that requested the load
|
||||
* @return map representing the given {@code Configuration}
|
||||
* @throws IOException if an I/O error occurs when loading the map
|
||||
*/
|
||||
Map<String, Object> loadConfiguration(C config)
|
||||
throws IOException;
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
/**
|
||||
* Signals that an error occurred while storing or loading a configuration.
|
||||
*/
|
||||
public final class ConfigurationStoreException extends RuntimeException {
|
||||
public ConfigurationStoreException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
@ -1,231 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import de.exlll.configlib.FieldMapper.MappingInfo;
|
||||
import de.exlll.configlib.annotation.ElementType;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* Implementations of this interface convert field values to objects that can be
|
||||
* stored by a {@link ConfigurationSource}, and vice versa.
|
||||
* <p>
|
||||
* Implementations must have a no-args constructor.
|
||||
*
|
||||
* @param <S> the source type
|
||||
* @param <T> the target type
|
||||
*/
|
||||
public interface Converter<S, T> {
|
||||
/**
|
||||
* Converts a field value to an object that can be stored by a
|
||||
* {@code ConfigurationSource}.
|
||||
* <p>
|
||||
* If this method returns null, a {@code ConfigurationException} is thrown.
|
||||
*
|
||||
* @param element field value that is converted
|
||||
* @param info information about the current conversion step
|
||||
* @return converted field value
|
||||
*/
|
||||
T convertTo(S element, ConversionInfo info);
|
||||
|
||||
/**
|
||||
* Executes some action before the field value is converted.
|
||||
*
|
||||
* @param info information about the current conversion step
|
||||
*/
|
||||
default void preConvertTo(ConversionInfo info) {}
|
||||
|
||||
/**
|
||||
* Converts a converted field value back to its original representation.
|
||||
* <p>
|
||||
* If this method returns null, the default value assigned to the field will
|
||||
* be kept.
|
||||
*
|
||||
* @param element object that should be converted back
|
||||
* @param info information about the current conversion step
|
||||
* @return the element's original representation
|
||||
*/
|
||||
S convertFrom(T element, ConversionInfo info);
|
||||
|
||||
/**
|
||||
* Executes some action before the converted field value is converted back
|
||||
* to its original representation.
|
||||
*
|
||||
* @param info information about the current conversion step
|
||||
*/
|
||||
default void preConvertFrom(ConversionInfo info) {}
|
||||
|
||||
/**
|
||||
* Instances of this class contain information about the currently converted
|
||||
* configuration, configuration element, and the conversion step.
|
||||
*/
|
||||
final class ConversionInfo {
|
||||
private final MappingInfo mappingInfo;
|
||||
private final Field field;
|
||||
private final Object instance;
|
||||
private final Object value;
|
||||
private final Object mapValue;
|
||||
private final Class<?> fieldType;
|
||||
private final Class<?> valueType;
|
||||
private final Class<?> elementType;
|
||||
private final String fieldName;
|
||||
private final Configuration.Properties props;
|
||||
private final int nestingLevel;
|
||||
private int currentNestingLevel;
|
||||
|
||||
private ConversionInfo(
|
||||
Field field, Object instance, Object mapValue,
|
||||
MappingInfo mappingInfo
|
||||
) {
|
||||
this.mappingInfo = mappingInfo;
|
||||
this.field = field;
|
||||
this.instance = instance;
|
||||
this.value = Reflect.getValue(field, instance);
|
||||
this.mapValue = mapValue;
|
||||
this.fieldType = field.getType();
|
||||
this.valueType = value.getClass();
|
||||
this.fieldName = field.getName();
|
||||
this.props = mappingInfo.getProperties();
|
||||
this.elementType = elementType(field);
|
||||
this.nestingLevel = nestingLevel(field);
|
||||
}
|
||||
|
||||
private static Class<?> elementType(Field field) {
|
||||
if (field.isAnnotationPresent(ElementType.class)) {
|
||||
ElementType et = field.getAnnotation(ElementType.class);
|
||||
return et.value();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static int nestingLevel(Field field) {
|
||||
if (field.isAnnotationPresent(ElementType.class)) {
|
||||
ElementType et = field.getAnnotation(ElementType.class);
|
||||
return et.nestingLevel();
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
static ConversionInfo from(
|
||||
Field field, Object instance, MappingInfo mappingInfo
|
||||
) {
|
||||
return new ConversionInfo(field, instance, null, mappingInfo);
|
||||
}
|
||||
|
||||
static ConversionInfo from(
|
||||
Field field, Object instance, Object mapValue,
|
||||
MappingInfo mappingInfo
|
||||
) {
|
||||
return new ConversionInfo(field, instance, mapValue, mappingInfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the field all other values belong to.
|
||||
*
|
||||
* @return current field
|
||||
*/
|
||||
public Field getField() {
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the field name.
|
||||
*
|
||||
* @return current field name
|
||||
*/
|
||||
public String getFieldName() {
|
||||
return fieldName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object the field belongs to, i.e. the instance currently
|
||||
* converted.
|
||||
*
|
||||
* @return object the field belongs to
|
||||
*/
|
||||
public Object getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default value assigned to that field.
|
||||
*
|
||||
* @return default value assigned to field
|
||||
*/
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* When loading, returns the converted field value, otherwise returns null.
|
||||
*
|
||||
* @return converted field value or null
|
||||
*/
|
||||
public Object getMapValue() {
|
||||
return mapValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the field.
|
||||
*
|
||||
* @return field type
|
||||
*/
|
||||
public Class<?> getFieldType() {
|
||||
return fieldType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the default value assigned to the field.
|
||||
*
|
||||
* @return type default value assigned to field
|
||||
*/
|
||||
public Class<?> getValueType() {
|
||||
return valueType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@code Configuration.Properties} instance of the currently
|
||||
* converted configuration.
|
||||
*
|
||||
* @return properties of currently converted configuration
|
||||
*/
|
||||
public Configuration.Properties getProperties() {
|
||||
return props;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the {@code ElementType} annotation or null if the
|
||||
* field is not annotated with this annotation.
|
||||
*
|
||||
* @return value of the {@code ElementType} annotation or null
|
||||
*/
|
||||
public Class<?> getElementType() {
|
||||
return elementType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the field is annotated with the {@code ElementType}
|
||||
* annotation.
|
||||
*
|
||||
* @return true, if field is annotated with {@code ElementType}.
|
||||
*/
|
||||
public boolean hasElementType() {
|
||||
return elementType != null;
|
||||
}
|
||||
|
||||
int getNestingLevel() {
|
||||
return nestingLevel;
|
||||
}
|
||||
|
||||
int getCurrentNestingLevel() {
|
||||
return currentNestingLevel;
|
||||
}
|
||||
|
||||
void incCurrentNestingLevel() {
|
||||
currentNestingLevel++;
|
||||
}
|
||||
|
||||
MappingInfo getMappingInfo() {
|
||||
return this.mappingInfo;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,551 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import de.exlll.configlib.Converter.ConversionInfo;
|
||||
import de.exlll.configlib.annotation.Convert;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static de.exlll.configlib.Validator.*;
|
||||
import static java.util.stream.Collectors.*;
|
||||
|
||||
final class Converters {
|
||||
private static final Map<Class<? extends Converter<?, ?>>, Converter<?, ?>> cache
|
||||
= new WeakHashMap<>();
|
||||
static final IdentityConverter IDENTITY_CONVERTER
|
||||
= new IdentityConverter();
|
||||
static final SimpleTypeConverter SIMPLE_TYPE_CONVERTER
|
||||
= new SimpleTypeConverter();
|
||||
static final EnumConverter ENUM_CONVERTER
|
||||
= new EnumConverter();
|
||||
static final ListConverter LIST_CONVERTER
|
||||
= new ListConverter();
|
||||
static final SetConverter SET_CONVERTER
|
||||
= new SetConverter();
|
||||
static final MapConverter MAP_CONVERTER
|
||||
= new MapConverter();
|
||||
static final SimpleListConverter SIMPLE_LIST_CONVERTER
|
||||
= new SimpleListConverter();
|
||||
static final SimpleSetConverter SIMPLE_SET_CONVERTER
|
||||
= new SimpleSetConverter();
|
||||
static final SimpleMapConverter SIMPLE_MAP_CONVERTER
|
||||
= new SimpleMapConverter();
|
||||
static final ConfigurationElementConverter ELEMENT_CONVERTER
|
||||
= new ConfigurationElementConverter();
|
||||
|
||||
static Object convertTo(ConversionInfo info) {
|
||||
Converter<Object, Object> converter = selectConverter(
|
||||
info.getValueType(), info
|
||||
);
|
||||
converter.preConvertTo(info);
|
||||
return tryConvertTo(converter, info);
|
||||
}
|
||||
|
||||
private static Object tryConvertTo(
|
||||
Converter<Object, Object> converter, ConversionInfo info
|
||||
) {
|
||||
try {
|
||||
return converter.convertTo(info.getValue(), info);
|
||||
} catch (ClassCastException e) {
|
||||
String msg = "Converter '" + converter.getClass().getSimpleName() + "'" +
|
||||
" cannot convert value '" + info.getValue() + "' of field '" +
|
||||
info.getFieldName() + "' because it expects a different type.";
|
||||
throw new ConfigurationException(msg, e);
|
||||
}
|
||||
}
|
||||
|
||||
static Object convertFrom(ConversionInfo info) {
|
||||
Converter<Object, Object> converter = selectConverter(
|
||||
info.getValueType(), info
|
||||
);
|
||||
converter.preConvertFrom(info);
|
||||
return tryConvertFrom(converter, info);
|
||||
}
|
||||
|
||||
private static Object tryConvertFrom(
|
||||
Converter<Object, Object> converter, ConversionInfo info
|
||||
) {
|
||||
try {
|
||||
return converter.convertFrom(info.getMapValue(), info);
|
||||
} catch (ClassCastException | IllegalArgumentException e) {
|
||||
String msg = "The value for field '" + info.getFieldName() + "' with " +
|
||||
"type '" + getClsName(info.getFieldType()) + "' cannot " +
|
||||
"be converted back to its original representation because a " +
|
||||
"type mismatch occurred.";
|
||||
throw new ConfigurationException(msg, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getClsName(Class<?> cls) {
|
||||
return cls.getSimpleName();
|
||||
}
|
||||
|
||||
private static Converter<Object, Object> selectConverter(
|
||||
Class<?> valueType, ConversionInfo info
|
||||
) {
|
||||
Converter<?, ?> converter;
|
||||
if (Reflect.hasNoConvert(info.getField())) {
|
||||
converter = IDENTITY_CONVERTER;
|
||||
} else if (Reflect.hasConverter(info.getField())) {
|
||||
converter = instantiateConverter(info.getField());
|
||||
} else if (Reflect.isSimpleType(valueType)) {
|
||||
converter = SIMPLE_TYPE_CONVERTER;
|
||||
} else {
|
||||
converter = selectNonSimpleConverter(valueType, info);
|
||||
}
|
||||
return toObjectConverter(converter);
|
||||
}
|
||||
|
||||
private static Converter<Object, Object> selectNonSimpleConverter(
|
||||
Class<?> valueType, ConversionInfo info
|
||||
) {
|
||||
Converter<?, ?> converter;
|
||||
if (Reflect.isEnumType(valueType) ||
|
||||
/* type is a string when converting back */
|
||||
(valueType == String.class)) {
|
||||
converter = ENUM_CONVERTER;
|
||||
} else if (Reflect.isContainerType(valueType)) {
|
||||
converter = selectContainerConverter(valueType, info);
|
||||
} else {
|
||||
converter = ELEMENT_CONVERTER;
|
||||
}
|
||||
return toObjectConverter(converter);
|
||||
}
|
||||
|
||||
private static Converter<?, ?> instantiateConverter(Field field) {
|
||||
Convert convert = field.getAnnotation(Convert.class);
|
||||
return cache.computeIfAbsent(convert.value(), cls -> {
|
||||
checkConverterHasNoArgsConstructor(cls, field.getName());
|
||||
return Reflect.newInstance(cls);
|
||||
});
|
||||
}
|
||||
|
||||
private static Converter<?, ?> selectContainerConverter(
|
||||
Class<?> valueType, ConversionInfo info
|
||||
) {
|
||||
if (info.hasElementType()) {
|
||||
return selectElementTypeContainerConverter(valueType);
|
||||
} else {
|
||||
return selectSimpleContainerConverter(valueType);
|
||||
}
|
||||
}
|
||||
|
||||
private static Converter<?, ?> selectElementTypeContainerConverter(
|
||||
Class<?> valueType
|
||||
) {
|
||||
return selector(
|
||||
LIST_CONVERTER, SET_CONVERTER, MAP_CONVERTER
|
||||
).apply(valueType);
|
||||
}
|
||||
|
||||
private static Converter<?, ?> selectSimpleContainerConverter(
|
||||
Class<?> valueType
|
||||
) {
|
||||
return selector(
|
||||
SIMPLE_LIST_CONVERTER, SIMPLE_SET_CONVERTER, SIMPLE_MAP_CONVERTER
|
||||
).apply(valueType);
|
||||
}
|
||||
|
||||
static <R> Function<Class<?>, R> selector(R listValue, R setValue, R mapValue) {
|
||||
return containerClass -> {
|
||||
if (List.class.isAssignableFrom(containerClass)) {
|
||||
return listValue;
|
||||
} else if (Set.class.isAssignableFrom(containerClass)) {
|
||||
return setValue;
|
||||
} else {
|
||||
return mapValue;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static String selectContainerName(Class<?> containerType) {
|
||||
return selector("list", "set", "map").apply(containerType);
|
||||
}
|
||||
|
||||
private static Converter<Object, Object> toObjectConverter(
|
||||
Converter<?, ?> converter
|
||||
) {
|
||||
/* This cast may result in a ClassCastException when converting objects
|
||||
* back to their original representation. This happens if the type of the
|
||||
* converted object has changed for some reason (e.g. by a configuration
|
||||
* mistake). However, the ClassCastException is later caught and translated
|
||||
* to a ConfigurationException to give additional information about what
|
||||
* happened. */
|
||||
@SuppressWarnings("unchecked")
|
||||
Converter<Object, Object> c = (Converter<Object, Object>) converter;
|
||||
return c;
|
||||
}
|
||||
|
||||
private static final class SimpleListConverter
|
||||
implements Converter<List<?>, List<?>> {
|
||||
@Override
|
||||
public List<?> convertTo(List<?> element, ConversionInfo info) {
|
||||
return element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preConvertTo(ConversionInfo info) {
|
||||
checkContainerValuesNotNull(info);
|
||||
checkContainerValuesSimpleType(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<?> convertFrom(List<?> element, ConversionInfo info) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class SimpleSetConverter
|
||||
implements Converter<Set<?>, Set<?>> {
|
||||
@Override
|
||||
public Set<?> convertTo(Set<?> element, ConversionInfo info) {
|
||||
return element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preConvertTo(ConversionInfo info) {
|
||||
checkContainerValuesNotNull(info);
|
||||
checkContainerValuesSimpleType(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<?> convertFrom(Set<?> element, ConversionInfo info) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class SimpleMapConverter
|
||||
implements Converter<Map<?, ?>, Map<?, ?>> {
|
||||
@Override
|
||||
public Map<?, ?> convertTo(Map<?, ?> element, ConversionInfo info) {
|
||||
return element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preConvertTo(ConversionInfo info) {
|
||||
checkMapKeysAndValues(info);
|
||||
checkContainerValuesSimpleType(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<?, ?> convertFrom(Map<?, ?> element, ConversionInfo info) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ListConverter
|
||||
implements Converter<List<?>, List<?>> {
|
||||
@Override
|
||||
public List<?> convertTo(List<?> element, ConversionInfo info) {
|
||||
if (element.isEmpty()) {
|
||||
return element;
|
||||
}
|
||||
Object o = element.get(0);
|
||||
Function<Object, ?> f = createToConversionFunction(o, info);
|
||||
return element.stream().map(f).collect(toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preConvertTo(ConversionInfo info) {
|
||||
checkElementType(info);
|
||||
checkContainerValuesNotNull(info);
|
||||
checkContainerTypes(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<?> convertFrom(List<?> element, ConversionInfo info) {
|
||||
if (element.isEmpty()) {
|
||||
return element;
|
||||
}
|
||||
Object o = element.get(0);
|
||||
Function<Object, ?> f = createFromConversionFunction(o, info);
|
||||
return element.stream().map(f).collect(toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preConvertFrom(ConversionInfo info) {
|
||||
checkElementType(info);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class SetConverter
|
||||
implements Converter<Set<?>, Set<?>> {
|
||||
@Override
|
||||
public Set<?> convertTo(Set<?> element, ConversionInfo info) {
|
||||
if (element.isEmpty()) {
|
||||
return element;
|
||||
}
|
||||
Object o = element.iterator().next();
|
||||
Function<Object, ?> f = createToConversionFunction(o, info);
|
||||
return element.stream().map(f).collect(toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preConvertTo(ConversionInfo info) {
|
||||
checkElementType(info);
|
||||
checkContainerValuesNotNull(info);
|
||||
checkContainerTypes(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<?> convertFrom(Set<?> element, ConversionInfo info) {
|
||||
if (element.isEmpty()) {
|
||||
return element;
|
||||
}
|
||||
Object o = element.iterator().next();
|
||||
Function<Object, ?> f = createFromConversionFunction(o, info);
|
||||
return element.stream().map(f).collect(toSet());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preConvertFrom(ConversionInfo info) {
|
||||
checkElementType(info);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class MapConverter
|
||||
implements Converter<Map<?, ?>, Map<?, ?>> {
|
||||
@Override
|
||||
public Map<?, ?> convertTo(Map<?, ?> element, ConversionInfo info) {
|
||||
if (element.isEmpty()) {
|
||||
return element;
|
||||
}
|
||||
Object o = element.values().iterator().next();
|
||||
Function<Object, ?> cf = createToConversionFunction(o, info);
|
||||
Function<Map.Entry<?, ?>, ?> f = e -> cf.apply(e.getValue());
|
||||
return element.entrySet().stream().collect(toMap(Map.Entry::getKey, f));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preConvertTo(ConversionInfo info) {
|
||||
checkElementType(info);
|
||||
checkMapKeysAndValues(info);
|
||||
checkContainerTypes(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<?, ?> convertFrom(Map<?, ?> element, ConversionInfo info) {
|
||||
if (element.isEmpty()) {
|
||||
return element;
|
||||
}
|
||||
Object o = element.values().iterator().next();
|
||||
Function<Object, ?> cf = createFromConversionFunction(o, info);
|
||||
Function<Map.Entry<?, ?>, ?> f = e -> cf.apply(e.getValue());
|
||||
return element.entrySet().stream().collect(toMap(Map.Entry::getKey, f));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preConvertFrom(ConversionInfo info) {
|
||||
checkElementType(info);
|
||||
}
|
||||
}
|
||||
|
||||
private static Function<Object, ?> createToConversionFunction(
|
||||
Object element, ConversionInfo info
|
||||
) {
|
||||
checkNestingLevel(element, info);
|
||||
if (Reflect.isContainerType(element.getClass())) {
|
||||
info.incCurrentNestingLevel();
|
||||
}
|
||||
Converter<Object, ?> converter = selectNonSimpleConverter(
|
||||
element.getClass(), info
|
||||
);
|
||||
return o -> converter.convertTo(o, info);
|
||||
}
|
||||
|
||||
private static Function<Object, ?> createFromConversionFunction(
|
||||
Object element, ConversionInfo info
|
||||
) {
|
||||
boolean currentLevelSameAsExpected =
|
||||
info.getNestingLevel() == info.getCurrentNestingLevel();
|
||||
checkCurrentLevelSameAsExpectedRequiresMapOrString(
|
||||
currentLevelSameAsExpected, element, info
|
||||
);
|
||||
if ((element instanceof Map<?, ?>) && currentLevelSameAsExpected) {
|
||||
return o -> {
|
||||
Map<String, Object> map = toTypeMap(o, null);
|
||||
Object inst = Reflect.newInstance(info.getElementType());
|
||||
FieldMapper.instanceFromMap(inst, map, info.getMappingInfo());
|
||||
return inst;
|
||||
};
|
||||
} else if ((element instanceof String) && currentLevelSameAsExpected) {
|
||||
return createNonSimpleConverter(element, info);
|
||||
} else {
|
||||
info.incCurrentNestingLevel();
|
||||
return createNonSimpleConverter(element, info);
|
||||
}
|
||||
}
|
||||
|
||||
private static Function<Object, ?> createNonSimpleConverter(
|
||||
Object element, ConversionInfo info
|
||||
) {
|
||||
Converter<?, Object> converter = selectNonSimpleConverter(
|
||||
element.getClass(), info
|
||||
);
|
||||
return o -> converter.convertFrom(o, info);
|
||||
}
|
||||
|
||||
private static Map<String, Object> toTypeMap(Object value, String fn) {
|
||||
checkIsMap(value, fn);
|
||||
checkMapKeysAreStrings((Map<?, ?>) value, fn);
|
||||
|
||||
// The following cast won't fail because we just verified that
|
||||
// it's a Map<String, Object>.
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> map = (Map<String, Object>) value;
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
private static final class IdentityConverter
|
||||
implements Converter<Object, Object> {
|
||||
|
||||
@Override
|
||||
public Object convertTo(Object element, ConversionInfo info) {
|
||||
return element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convertFrom(Object element, ConversionInfo info) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class SimpleTypeConverter
|
||||
implements Converter<Object, Object> {
|
||||
|
||||
@Override
|
||||
public Object convertTo(Object element, ConversionInfo info) {
|
||||
return element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convertFrom(Object element, ConversionInfo info) {
|
||||
if (info.getFieldType() == element.getClass()) {
|
||||
return element;
|
||||
}
|
||||
if (element instanceof Number) {
|
||||
return convertNumber(info.getFieldType(), (Number) element);
|
||||
}
|
||||
if (element instanceof String) {
|
||||
return convertString((String) element);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
private Object convertNumber(Class<?> target, Number value) {
|
||||
if (target == byte.class || target == Byte.class) {
|
||||
return value.byteValue();
|
||||
} else if (target == short.class || target == Short.class) {
|
||||
return value.shortValue();
|
||||
} else if (target == int.class || target == Integer.class) {
|
||||
return value.intValue();
|
||||
} else if (target == long.class || target == Long.class) {
|
||||
return value.longValue();
|
||||
} else if (target == float.class || target == Float.class) {
|
||||
return value.floatValue();
|
||||
} else if (target == double.class || target == Double.class) {
|
||||
return value.doubleValue();
|
||||
} else {
|
||||
String msg = "Number '" + value + "' cannot be converted " +
|
||||
"to type '" + target + "'";
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private Object convertString(String s) {
|
||||
int length = s.length();
|
||||
if (length == 0) {
|
||||
String msg = "An empty string cannot be converted to a character.";
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
if (length > 1) {
|
||||
String msg = "String '" + s + "' is too long to " +
|
||||
"be converted to a character";
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
return s.charAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
private static final class EnumConverter
|
||||
implements Converter<Enum<?>, String> {
|
||||
|
||||
@Override
|
||||
public String convertTo(Enum<?> element, ConversionInfo info) {
|
||||
return element.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preConvertFrom(ConversionInfo info) {
|
||||
checkEnumValueIsString(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enum<?> convertFrom(String element, ConversionInfo info) {
|
||||
Class<? extends Enum> cls = getEnumClass(info);
|
||||
try {
|
||||
/* cast won't fail because we know that it's an enum */
|
||||
@SuppressWarnings("unchecked")
|
||||
Enum<?> enm = Enum.valueOf(cls, element);
|
||||
return enm;
|
||||
} catch (IllegalArgumentException e) {
|
||||
checkElementTypeIsEnumType(cls, info);
|
||||
String in = selectWord(info);
|
||||
String msg = "Cannot initialize " + in + " because there is no " +
|
||||
"enum constant '" + element + "'.\nValid constants are: " +
|
||||
Arrays.toString(cls.getEnumConstants());
|
||||
throw new IllegalArgumentException(msg, e);
|
||||
}
|
||||
}
|
||||
|
||||
private String selectWord(ConversionInfo info) {
|
||||
String fn = info.getFieldName();
|
||||
if (Reflect.isContainerType(info.getFieldType())) {
|
||||
String w = selectContainerName(info.getValueType());
|
||||
return "an enum element of " + w + " '" + fn + "'";
|
||||
}
|
||||
return "enum '" + fn + "' ";
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Class<? extends Enum> getEnumClass(ConversionInfo info) {
|
||||
/* this cast won't fail because this method is only called by a
|
||||
* Converter that converts enum types. */
|
||||
return (Class<? extends Enum>) (!info.hasElementType()
|
||||
? info.getValue().getClass()
|
||||
: info.getElementType());
|
||||
}
|
||||
}
|
||||
|
||||
private static final class ConfigurationElementConverter
|
||||
implements Converter<Object, Object> {
|
||||
|
||||
@Override
|
||||
public Object convertTo(Object element, ConversionInfo info) {
|
||||
return FieldMapper.instanceToMap(element, info.getMappingInfo());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preConvertTo(ConversionInfo info) {
|
||||
checkTypeIsConfigurationElement(info.getValueType(), info.getFieldName());
|
||||
checkTypeHasNoArgsConstructor(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convertFrom(Object element, ConversionInfo info) {
|
||||
checkElementIsConvertibleToConfigurationElement(element, info);
|
||||
Object newInstance = Reflect.newInstance(info.getValueType());
|
||||
Map<String, Object> typeMap = toTypeMap(element, info.getFieldName());
|
||||
FieldMapper.instanceFromMap(newInstance, typeMap, info.getMappingInfo());
|
||||
return newInstance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preConvertFrom(ConversionInfo info) {
|
||||
checkTypeHasNoArgsConstructor(info);
|
||||
checkTypeIsConfigurationElement(info.getValueType(), info.getFieldName());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,120 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import de.exlll.configlib.Converter.ConversionInfo;
|
||||
import de.exlll.configlib.annotation.Format;
|
||||
import de.exlll.configlib.filter.FieldFilter;
|
||||
import de.exlll.configlib.format.FieldNameFormatter;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static de.exlll.configlib.Validator.*;
|
||||
|
||||
enum FieldMapper {
|
||||
;
|
||||
|
||||
static Map<String, Object> instanceToMap(Object inst, MappingInfo mappingInfo) {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
Configuration.Properties props = mappingInfo.getProperties();
|
||||
FieldFilter filter = props.getFilter();
|
||||
for (Field field : filter.filterDeclaredFieldsOf(inst.getClass())) {
|
||||
Object val = toConvertibleObject(field, inst, mappingInfo);
|
||||
FieldNameFormatter fnf = selectFormatter(mappingInfo);
|
||||
String fn = fnf.fromFieldName(field.getName());
|
||||
map.put(fn, val);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private static Object toConvertibleObject(
|
||||
Field field, Object instance, MappingInfo mappingInfo
|
||||
) {
|
||||
checkDefaultValueNull(field, instance);
|
||||
ConversionInfo info = ConversionInfo.from(field, instance, mappingInfo);
|
||||
checkFieldWithElementTypeIsContainer(info);
|
||||
Object converted = Converters.convertTo(info);
|
||||
checkConverterNotReturnsNull(converted, info);
|
||||
return converted;
|
||||
}
|
||||
|
||||
static void instanceFromMap(
|
||||
Object inst, Map<String, Object> instMap, MappingInfo mappingInfo
|
||||
) {
|
||||
FieldFilter filter = mappingInfo.getProperties().getFilter();
|
||||
for (Field field : filter.filterDeclaredFieldsOf(inst.getClass())) {
|
||||
FieldNameFormatter fnf = selectFormatter(mappingInfo);
|
||||
String fn = fnf.fromFieldName(field.getName());
|
||||
Object mapValue = instMap.get(fn);
|
||||
if (mapValue != null) {
|
||||
fromConvertedObject(field, inst, mapValue, mappingInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void fromConvertedObject(
|
||||
Field field, Object instance, Object mapValue,
|
||||
MappingInfo mappingInfo
|
||||
) {
|
||||
checkDefaultValueNull(field, instance);
|
||||
ConversionInfo info = ConversionInfo.from(
|
||||
field, instance, mapValue, mappingInfo
|
||||
);
|
||||
checkFieldWithElementTypeIsContainer(info);
|
||||
Object convert = Converters.convertFrom(info);
|
||||
|
||||
if (convert == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Reflect.isContainerType(info.getFieldType())) {
|
||||
checkFieldTypeAssignableFrom(convert.getClass(), info);
|
||||
}
|
||||
|
||||
Reflect.setValue(field, instance, convert);
|
||||
}
|
||||
|
||||
private static void checkDefaultValueNull(Field field, Object instance) {
|
||||
Object val = Reflect.getValue(field, instance);
|
||||
checkNotNull(val, field.getName());
|
||||
}
|
||||
|
||||
static FieldNameFormatter selectFormatter(MappingInfo info) {
|
||||
Configuration<?> configuration = info.getConfiguration();
|
||||
Configuration.Properties props = info.getProperties();
|
||||
if ((configuration != null) &&
|
||||
Reflect.hasFormatter(configuration.getClass())) {
|
||||
Format format = configuration.getClass()
|
||||
.getAnnotation(Format.class);
|
||||
return (format.formatterClass() != FieldNameFormatter.class)
|
||||
? Reflect.newInstance(format.formatterClass())
|
||||
: format.value();
|
||||
}
|
||||
return props.getFormatter();
|
||||
}
|
||||
|
||||
static final class MappingInfo {
|
||||
private final Configuration<?> configuration;
|
||||
private final Configuration.Properties properties;
|
||||
|
||||
MappingInfo(
|
||||
Configuration<?> configuration,
|
||||
Configuration.Properties properties
|
||||
) {
|
||||
this.configuration = configuration;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
Configuration<?> getConfiguration() {
|
||||
return configuration;
|
||||
}
|
||||
|
||||
Configuration.Properties getProperties() {
|
||||
return properties;
|
||||
}
|
||||
|
||||
static MappingInfo from(Configuration<?> configuration) {
|
||||
return new MappingInfo(configuration, configuration.getProperties());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import de.exlll.configlib.annotation.ConfigurationElement;
|
||||
import de.exlll.configlib.annotation.Convert;
|
||||
import de.exlll.configlib.annotation.Format;
|
||||
import de.exlll.configlib.annotation.NoConvert;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.*;
|
||||
|
||||
enum Reflect {
|
||||
;
|
||||
private static final Set<Class<?>> SIMPLE_TYPES = new HashSet<>(Arrays.asList(
|
||||
Boolean.class,
|
||||
Byte.class,
|
||||
Character.class,
|
||||
Short.class,
|
||||
Integer.class,
|
||||
Long.class,
|
||||
Float.class,
|
||||
Double.class,
|
||||
String.class
|
||||
));
|
||||
|
||||
static boolean isSimpleType(Class<?> cls) {
|
||||
return cls.isPrimitive() || SIMPLE_TYPES.contains(cls);
|
||||
}
|
||||
|
||||
static boolean isContainerType(Class<?> cls) {
|
||||
return List.class.isAssignableFrom(cls) ||
|
||||
Set.class.isAssignableFrom(cls) ||
|
||||
Map.class.isAssignableFrom(cls);
|
||||
}
|
||||
|
||||
static boolean isEnumType(Class<?> cls) {
|
||||
return cls.isEnum();
|
||||
}
|
||||
|
||||
static <T> T newInstance(Class<T> cls) {
|
||||
try {
|
||||
Constructor<T> constructor = cls.getDeclaredConstructor();
|
||||
constructor.setAccessible(true);
|
||||
return constructor.newInstance();
|
||||
} catch (NoSuchMethodException e) {
|
||||
String msg = "Class " + cls.getSimpleName() + " doesn't have a " +
|
||||
"no-args constructor.";
|
||||
throw new ConfigurationException(msg, e);
|
||||
} catch (IllegalAccessException e) {
|
||||
/* This exception should not be thrown because
|
||||
* we set the field to be accessible. */
|
||||
String msg = "No-args constructor of class " + cls.getSimpleName() +
|
||||
" not accessible.";
|
||||
throw new ConfigurationException(msg, e);
|
||||
} catch (InstantiationException e) {
|
||||
String msg = "Class " + cls.getSimpleName() + " not instantiable.";
|
||||
throw new ConfigurationException(msg, e);
|
||||
} catch (InvocationTargetException e) {
|
||||
String msg = "Constructor of class " + cls.getSimpleName() +
|
||||
" has thrown an exception.";
|
||||
throw new ConfigurationException(msg, e);
|
||||
}
|
||||
}
|
||||
|
||||
static Object getValue(Field field, Object inst) {
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
return field.get(inst);
|
||||
} catch (IllegalAccessException e) {
|
||||
/* This exception should not be thrown because
|
||||
* we set the field to be accessible. */
|
||||
String msg = "Illegal access of field '" + field + "' " +
|
||||
"on object " + inst + ".";
|
||||
throw new ConfigurationException(msg, e);
|
||||
}
|
||||
}
|
||||
|
||||
static void setValue(Field field, Object inst, Object value) {
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
field.set(inst, value);
|
||||
} catch (IllegalAccessException e) {
|
||||
String msg = "Illegal access of field '" + field + "' " +
|
||||
"on object " + inst + ".";
|
||||
throw new ConfigurationException(msg, e);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean hasConverter(Field field) {
|
||||
return field.isAnnotationPresent(Convert.class);
|
||||
}
|
||||
|
||||
static boolean hasNoConvert(Field field) {
|
||||
return field.isAnnotationPresent(NoConvert.class);
|
||||
}
|
||||
|
||||
static boolean hasFormatter(Class<?> cls) {
|
||||
return cls.isAnnotationPresent(Format.class);
|
||||
}
|
||||
|
||||
static boolean isConfigurationElement(Class<?> cls) {
|
||||
return cls.isAnnotationPresent(ConfigurationElement.class);
|
||||
}
|
||||
|
||||
static boolean hasNoArgConstructor(Class<?> cls) {
|
||||
return Arrays.stream(cls.getDeclaredConstructors())
|
||||
.anyMatch(c -> c.getParameterCount() == 0);
|
||||
}
|
||||
}
|
@ -1,357 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import de.exlll.configlib.Converter.ConversionInfo;
|
||||
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
final class Validator {
|
||||
|
||||
static void checkNotNull(Object o, String fn) {
|
||||
if (o == null) {
|
||||
String msg = "The value of field '" + fn + "' is null.\n" +
|
||||
"Please assign a non-null default value or remove this field.";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
static void checkContainerTypes(ConversionInfo info) {
|
||||
Object value = info.getValue();
|
||||
Collection<?> collection = toCollection(value);
|
||||
checkCollectionTypes(collection, info);
|
||||
}
|
||||
|
||||
private static void checkCollectionTypes(
|
||||
Collection<?> collection, ConversionInfo info
|
||||
) {
|
||||
for (Object element : collection) {
|
||||
if (Reflect.isContainerType(element.getClass())) {
|
||||
Collection<?> container = toCollection(element);
|
||||
checkCollectionTypes(container, info);
|
||||
} else {
|
||||
checkCollectionType(element, info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkCollectionType(Object element, ConversionInfo info) {
|
||||
Class<?> cls = element.getClass();
|
||||
if (cls != info.getElementType()) {
|
||||
String cNameField = selectContainerNameField(info);
|
||||
String cValues = selectContainerValues(info);
|
||||
String msg = "The type of " + cNameField + " doesn't match the " +
|
||||
"type indicated by the ElementType annotation.\n" +
|
||||
"Required type: '" + getClsName(info.getElementType()) +
|
||||
"'\tActual type: '" + getClsName(cls) +
|
||||
"'\n" + cValues;
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private static String selectContainerValues(ConversionInfo info) {
|
||||
Object value = info.getValue();
|
||||
return Converters.selector(
|
||||
"All elements: " + value,
|
||||
"All elements: " + value,
|
||||
"All entries: " + value
|
||||
).apply(info.getValueType());
|
||||
}
|
||||
|
||||
private static String selectContainerNameField(ConversionInfo info) {
|
||||
String fieldName = info.getFieldName();
|
||||
return Converters.selector(
|
||||
"an element of list '" + fieldName + "'",
|
||||
"an element of set '" + fieldName + "'",
|
||||
"a value of map '" + fieldName + "'"
|
||||
).apply(info.getValueType());
|
||||
}
|
||||
|
||||
private static Collection<?> toCollection(Object container) {
|
||||
if (container instanceof List<?> || container instanceof Set<?>) {
|
||||
return (Collection<?>) container;
|
||||
} else {
|
||||
Map<?, ?> map = (Map<?, ?>) container;
|
||||
return map.values();
|
||||
}
|
||||
}
|
||||
|
||||
static void checkMapKeysAndValues(ConversionInfo info) {
|
||||
checkMapKeysSimple((Map<?, ?>) info.getValue(), info.getFieldName());
|
||||
checkContainerValuesNotNull(info);
|
||||
}
|
||||
|
||||
private static void checkMapKeysSimple(Map<?, ?> map, String fn) {
|
||||
for (Object o : map.keySet()) {
|
||||
if (!Reflect.isSimpleType(o.getClass())) {
|
||||
String msg = "The keys of map '" + fn + "' must be simple types.";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void checkContainerValuesNotNull(ConversionInfo info) {
|
||||
Collection<?> collection = toCollection(info.getValue());
|
||||
checkCollectionValuesNotNull(collection, info);
|
||||
}
|
||||
|
||||
private static void checkCollectionValuesNotNull(
|
||||
Collection<?> col, ConversionInfo info
|
||||
) {
|
||||
for (Object element : col) {
|
||||
checkCollectionValueNotNull(element, info);
|
||||
if (Reflect.isContainerType(element.getClass())) {
|
||||
Collection<?> container = toCollection(element);
|
||||
checkCollectionValuesNotNull(container, info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkCollectionValueNotNull(
|
||||
Object element, ConversionInfo info
|
||||
) {
|
||||
if (element == null) {
|
||||
String cnf = selectContainerNameField(info)
|
||||
.replaceFirst("a", "A");
|
||||
String msg = cnf + " is null.\n" +
|
||||
"Please either remove or replace this element." +
|
||||
"\n" + selectContainerValues(info);
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
static void checkContainerValuesSimpleType(ConversionInfo info) {
|
||||
Collection<?> collection = toCollection(info.getValue());
|
||||
checkCollectionValuesSimpleType(collection, info);
|
||||
}
|
||||
|
||||
private static void checkCollectionValuesSimpleType(
|
||||
Collection<?> collection, ConversionInfo info
|
||||
) {
|
||||
for (Object element : collection) {
|
||||
if (Reflect.isContainerType(element.getClass())) {
|
||||
Collection<?> elements = toCollection(element);
|
||||
checkCollectionValuesSimpleType(elements, info);
|
||||
} else {
|
||||
checkCollectionValueSimpleType(element, info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkCollectionValueSimpleType(
|
||||
Object element, ConversionInfo info
|
||||
) {
|
||||
if (!Reflect.isSimpleType(element.getClass())) {
|
||||
String cn = Converters.selectContainerName(info.getValueType());
|
||||
String cnf = selectContainerNameField(info);
|
||||
String fieldName = info.getFieldName();
|
||||
String msg = "The type of " + cnf + " is not a simple type but " + cn +
|
||||
" '" + fieldName + "' is missing the ElementType annotation." +
|
||||
"\n" + selectContainerValues(info);
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
static void checkTypeIsConfigurationElement(Class<?> cls, String fn) {
|
||||
if (!Reflect.isConfigurationElement(cls)) {
|
||||
String msg = "Type '" + getClsName(cls) + "' of field '" +
|
||||
fn + "' is not annotated as a configuration element.";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getClsName(Class<?> cls) {
|
||||
String clsName = cls.getSimpleName();
|
||||
if (clsName.equals("")) {
|
||||
clsName = cls.getName();
|
||||
}
|
||||
return clsName;
|
||||
}
|
||||
|
||||
static void checkIsMap(Object value, String fn) {
|
||||
Class<?> cls = value.getClass();
|
||||
if (!Map.class.isAssignableFrom(cls)) {
|
||||
String msg = "Initializing field '" + fn + "' requires a " +
|
||||
"Map<String, Object> but the given object is not a map.\n" +
|
||||
"Type: '" + cls.getSimpleName() + "'\tValue: '" + value + "'";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
static void checkMapKeysAreStrings(Map<?, ?> map, String fn) {
|
||||
for (Map.Entry<?, ?> entry : map.entrySet()) {
|
||||
Object key = entry.getKey();
|
||||
if ((key == null) || (key.getClass() != String.class)) {
|
||||
String msg = "Initializing field '" + fn + "' requires a " +
|
||||
"Map<String, Object> but the given map contains " +
|
||||
"non-string keys.\nAll entries: " + map;
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void checkElementType(ConversionInfo info) {
|
||||
Class<?> elementType = info.getElementType();
|
||||
if (!elementType.isEnum())
|
||||
checkElementTypeIsConfigurationElement(info);
|
||||
checkElementTypeIsConcrete(info);
|
||||
if (!elementType.isEnum())
|
||||
checkElementTypeHasNoArgsConstructor(info);
|
||||
}
|
||||
|
||||
static void checkFieldWithElementTypeIsContainer(ConversionInfo info) {
|
||||
boolean isContainer = Reflect.isContainerType(info.getValueType());
|
||||
if (info.hasElementType() && !isContainer) {
|
||||
String msg = "Field '" + info.getFieldName() + "' is annotated with " +
|
||||
"the ElementType annotation but is not a List, Set or Map.";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkElementTypeIsConfigurationElement(ConversionInfo info) {
|
||||
Class<?> elementType = info.getElementType();
|
||||
if (!Reflect.isConfigurationElement(elementType)) {
|
||||
String msg = "The element type '" + getClsName(elementType) + "'" +
|
||||
" of field '" + info.getFieldName() + "' is not a " +
|
||||
"configuration element.";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private static void checkElementTypeIsConcrete(ConversionInfo info) {
|
||||
Class<?> elementType = info.getElementType();
|
||||
|
||||
String msg = getType(elementType);
|
||||
if (msg != null) {
|
||||
msg = "The element type of field '" + info.getFieldName() + "' must " +
|
||||
"be a concrete class but type '" +
|
||||
getClsName(elementType) + "' is " + msg;
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private static String getType(Class<?> cls) {
|
||||
String msg = null;
|
||||
|
||||
if (cls.isInterface()) {
|
||||
msg = "an interface.";
|
||||
} else if (cls.isPrimitive()) {
|
||||
msg = "primitive.";
|
||||
} else if (cls.isArray()) {
|
||||
msg = "an array.";
|
||||
} else if (Modifier.isAbstract(cls.getModifiers())) {
|
||||
msg = "an abstract class.";
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
|
||||
private static void checkElementTypeHasNoArgsConstructor(ConversionInfo info) {
|
||||
Class<?> elementType = info.getElementType();
|
||||
if (!Reflect.hasNoArgConstructor(elementType)) {
|
||||
String msg = "The element type '" + elementType.getSimpleName() + "'" +
|
||||
" of field '" + info.getFieldName() + "' doesn't have " +
|
||||
"a no-args constructor.";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
static void checkTypeHasNoArgsConstructor(ConversionInfo info) {
|
||||
Class<?> valueType = info.getValueType();
|
||||
if (!Reflect.hasNoArgConstructor(valueType)) {
|
||||
String msg = "Type '" + getClsName(valueType) + "' of field '" +
|
||||
info.getFieldName() + "' doesn't have a no-args constructor.";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
static void checkConverterHasNoArgsConstructor(Class<?> converterClass, String fn) {
|
||||
if (!Reflect.hasNoArgConstructor(converterClass)) {
|
||||
String msg = "Converter '" + converterClass.getSimpleName() + "' used " +
|
||||
"on field '" + fn + "' doesn't have a no-args constructor.";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
static void checkEnumValueIsString(ConversionInfo info) {
|
||||
Object val = info.getMapValue();
|
||||
if (!(val instanceof String)) {
|
||||
String sn = val.getClass().getSimpleName();
|
||||
String msg = "Initializing enum '" + info.getFieldName() + "' " +
|
||||
"requires a string but '" + val + "' is of type '" + sn + "'.";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
static void checkFieldTypeAssignableFrom(Class<?> type, ConversionInfo info) {
|
||||
Class<?> fieldType = info.getFieldType();
|
||||
if (!fieldType.isAssignableFrom(type)) {
|
||||
String msg = "Can not set field '" + info.getFieldName() + "' with " +
|
||||
"type '" + getClsName(fieldType) + "' to '" +
|
||||
getClsName(type) + "'.";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
static void checkElementIsConvertibleToConfigurationElement(
|
||||
Object element, ConversionInfo info
|
||||
) {
|
||||
Class<?> eClass = element.getClass();
|
||||
if (Reflect.isContainerType(info.getFieldType()) &&
|
||||
!Map.class.isAssignableFrom(eClass)) {
|
||||
String msg = "Initializing field '" + info.getFieldName() + "' " +
|
||||
"requires objects of type Map<String, Object> but element " +
|
||||
"'" + element + "' is of type '" + getClsName(eClass) + "'.";
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
static void checkNestingLevel(Object element, ConversionInfo info) {
|
||||
if (!Reflect.isContainerType(element.getClass())) {
|
||||
if (info.getNestingLevel() != info.getCurrentNestingLevel()) {
|
||||
String msg = "Field '" + info.getFieldName() + "' of class " +
|
||||
"'" + getClsName(info.getInstance().getClass()) + "' " +
|
||||
"has a nesting level of " + info.getNestingLevel() +
|
||||
" but the first object of type '" +
|
||||
getClsName(info.getElementType()) + "' was found on " +
|
||||
"level " + info.getCurrentNestingLevel() + ".";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void checkCurrentLevelSameAsExpectedRequiresMapOrString(
|
||||
boolean currentLevelSameAsExpected,
|
||||
Object element, ConversionInfo info
|
||||
) {
|
||||
boolean isMapOrString = (element instanceof Map<?, ?>) ||
|
||||
(element instanceof String);
|
||||
if (currentLevelSameAsExpected && !isMapOrString) {
|
||||
Class<?> cls = info.getInstance().getClass();
|
||||
String msg = "Field '" + info.getFieldName() + "' of class '" +
|
||||
getClsName(cls) + "' has a nesting level" +
|
||||
" of " + info.getNestingLevel() + " but element '" + element +
|
||||
"' of type '" + getClsName(element.getClass()) + "' cannot be " +
|
||||
"converted to '" + getClsName(info.getElementType()) + "'.";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
static void checkElementTypeIsEnumType(Class<?> type, ConversionInfo info) {
|
||||
if (!Reflect.isEnumType(type)) {
|
||||
String msg = "Element type '" + getClsName(type) + "' of field " +
|
||||
"'" + info.getFieldName() + "' is not an enum type.";
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
static void checkConverterNotReturnsNull(Object converted, ConversionInfo info) {
|
||||
if (converted == null) {
|
||||
String msg = "Failed to convert value '" + info.getValue() + "' of " +
|
||||
"field '" + info.getFieldName() + "' because the converter " +
|
||||
"returned null.";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
package de.exlll.configlib.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Indicates that the annotated element is saved together with explanatory
|
||||
* comments describing it.
|
||||
* <p>
|
||||
* For {@link de.exlll.configlib.configs.yaml.YamlConfiguration YamlConfiguration}s:
|
||||
* <ul>
|
||||
* <li>
|
||||
* If this annotation is used on a class, the comments returned by the
|
||||
* {@link #value()} method are saved at the beginning of the configuration file.
|
||||
* </li>
|
||||
* <li>
|
||||
* If this annotation is used on a field, the comments are saved above the field name.
|
||||
* </li>
|
||||
* </ul>
|
||||
*/
|
||||
@Target({ElementType.FIELD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Comment {
|
||||
/**
|
||||
* Returns the comments of the annotated type or field.
|
||||
*
|
||||
* @return class or field comments
|
||||
*/
|
||||
String[] value();
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
package de.exlll.configlib.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Enables conversion of instances of the annotated type.
|
||||
* <p>
|
||||
* {@code ConfigurationElement}s must have a no-args constructor.
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ConfigurationElement {}
|
@ -1,18 +0,0 @@
|
||||
package de.exlll.configlib.annotation;
|
||||
|
||||
import de.exlll.configlib.Converter;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Indicates that a custom conversion mechanism is used to convert the
|
||||
* annotated field.
|
||||
*/
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Convert {
|
||||
Class<? extends Converter<?, ?>> value();
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
package de.exlll.configlib.annotation;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Indicates the type of elements a {@code Collection} or {@code Map} contains.
|
||||
* <p>
|
||||
* This annotation must only be used if a {@code Collection} or {@code Map} contains
|
||||
* elements whose type is not simple. Note that {@code Map} keys can only be of some
|
||||
* simple type.
|
||||
* <p>
|
||||
* If collections are nested, the {@code nestingLevel} must be set. Examples:
|
||||
* <ul>
|
||||
* <li>nestingLevel 1: {@code List<List<T>>}</li>
|
||||
* <li>nestingLevel 1: {@code List<Set<T>>}</li>
|
||||
* <li>nestingLevel 1: {@code List<Map<String, T>>}</li>
|
||||
* <li>nestingLevel 2: {@code List<List<List<T>>>}</li>
|
||||
* <li>nestingLevel 2: {@code List<Set<List<T>>>}</li>
|
||||
* <li>nestingLevel 2: {@code List<List<Map<String, T>>>}</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Target(java.lang.annotation.ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ElementType {
|
||||
/**
|
||||
* Returns the type of elements a {@code Collection} or {@code Map} contains.
|
||||
*
|
||||
* @return type of elements.
|
||||
*/
|
||||
Class<?> value();
|
||||
|
||||
/**
|
||||
* Returns the nesting level
|
||||
*
|
||||
* @return nesting level
|
||||
*/
|
||||
int nestingLevel() default 0;
|
||||
}
|
@ -1,38 +0,0 @@
|
||||
package de.exlll.configlib.annotation;
|
||||
|
||||
import de.exlll.configlib.format.FieldNameFormatter;
|
||||
import de.exlll.configlib.format.FieldNameFormatters;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Indicates that a specific {@code FieldNameFormatter} is used. If a
|
||||
* {@link #formatterClass()} is specified, the {@code FieldNameFormatters}
|
||||
* returned by {@link #value()} is ignored. The {@code formatterClass} must
|
||||
* be instantiable and must have a no-args constructor.
|
||||
* <p>
|
||||
* This annotation takes precedence over the value set in properties object.
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Format {
|
||||
/**
|
||||
* Returns the {@code FieldNameFormatters} instance to be for formatting.
|
||||
* The return value of this method is ignored if a {@code formatterClass()}
|
||||
* is set.
|
||||
*
|
||||
* @return {@code FieldNameFormatters} instance
|
||||
*/
|
||||
FieldNameFormatters value() default FieldNameFormatters.IDENTITY;
|
||||
|
||||
/**
|
||||
* Returns the class of a {@code FieldNameFormatter} implementation.
|
||||
* The class must be instantiable and must have a no-args constructor.
|
||||
*
|
||||
* @return class of {@code FieldNameFormatter} implementation
|
||||
*/
|
||||
Class<? extends FieldNameFormatter> formatterClass() default FieldNameFormatter.class;
|
||||
}
|
@ -1,17 +0,0 @@
|
||||
package de.exlll.configlib.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Indicates that the annotated field should not be converted but instead used as is.
|
||||
* <p>
|
||||
* This may be useful if the configuration knows how to (de-)serialize
|
||||
* instances of that type. For example, a {@code BukkitYamlConfiguration}
|
||||
* knows how to serialize {@code ItemStack} instances.
|
||||
*/
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface NoConvert {}
|
@ -1,76 +0,0 @@
|
||||
package de.exlll.configlib.configs.yaml;
|
||||
|
||||
import de.exlll.configlib.Comments;
|
||||
import de.exlll.configlib.format.FieldNameFormatter;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
|
||||
final class YamlComments {
|
||||
private final Comments comments;
|
||||
|
||||
YamlComments(Comments comments) {
|
||||
this.comments = comments;
|
||||
}
|
||||
|
||||
String classCommentsAsString() {
|
||||
List<String> classComments = comments.getClassComments();
|
||||
return commentListToString(classComments);
|
||||
}
|
||||
|
||||
Map<String, String> fieldCommentAsStrings(FieldNameFormatter formatter) {
|
||||
Map<String, List<String>> fieldComments = comments.getFieldComments();
|
||||
return fieldComments.entrySet().stream()
|
||||
.map(e -> toFormattedStringCommentEntry(e, formatter))
|
||||
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
private Map.Entry<String, String> toFormattedStringCommentEntry(
|
||||
Map.Entry<String, List<String>> entry, FieldNameFormatter formatter
|
||||
) {
|
||||
String fieldComments = commentListToString(entry.getValue());
|
||||
String formattedKey = formatter.fromFieldName(entry.getKey());
|
||||
return new MapEntry<>(formattedKey, fieldComments);
|
||||
}
|
||||
|
||||
private String commentListToString(List<String> comments) {
|
||||
return comments.stream()
|
||||
.map(this::toCommentLine)
|
||||
.collect(joining("\n"));
|
||||
}
|
||||
|
||||
private String toCommentLine(String comment) {
|
||||
return comment.isEmpty() ? "" : "# " + comment;
|
||||
}
|
||||
|
||||
private static final class MapEntry<K, V> implements Map.Entry<K, V> {
|
||||
private final K key;
|
||||
private V value;
|
||||
|
||||
public MapEntry(K key, V value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public K getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V setValue(V value) {
|
||||
V old = this.value;
|
||||
this.value = value;
|
||||
return old;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,212 +0,0 @@
|
||||
package de.exlll.configlib.configs.yaml;
|
||||
|
||||
import de.exlll.configlib.Comments;
|
||||
import de.exlll.configlib.Configuration;
|
||||
import de.exlll.configlib.ConfigurationSource;
|
||||
import de.exlll.configlib.ConfigurationStoreException;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.DumperOptions.FlowStyle;
|
||||
import org.yaml.snakeyaml.constructor.BaseConstructor;
|
||||
import org.yaml.snakeyaml.constructor.Constructor;
|
||||
import org.yaml.snakeyaml.representer.Representer;
|
||||
import org.yaml.snakeyaml.resolver.Resolver;
|
||||
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public abstract class YamlConfiguration extends Configuration<YamlConfiguration> {
|
||||
private final YamlSource source;
|
||||
|
||||
protected YamlConfiguration(Path path, YamlProperties properties) {
|
||||
super(properties);
|
||||
this.source = new YamlSource(path, properties);
|
||||
}
|
||||
|
||||
protected YamlConfiguration(Path path) {
|
||||
this(path, YamlProperties.DEFAULT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final ConfigurationSource<YamlConfiguration> getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected final YamlConfiguration getThis() {
|
||||
return this;
|
||||
}
|
||||
|
||||
public final void loadAndSave() {
|
||||
try {
|
||||
load();
|
||||
save();
|
||||
} catch (ConfigurationStoreException e) {
|
||||
if (e.getCause() instanceof NoSuchFileException) {
|
||||
postLoad();
|
||||
save();
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Comments getComments() {
|
||||
return comments;
|
||||
}
|
||||
|
||||
public static class YamlProperties extends Properties {
|
||||
public static final YamlProperties DEFAULT = builder().build();
|
||||
private final List<String> prependedComments;
|
||||
private final List<String> appendedComments;
|
||||
private final BaseConstructor constructor;
|
||||
private final Representer representer;
|
||||
private final DumperOptions options;
|
||||
private final Resolver resolver;
|
||||
|
||||
protected YamlProperties(Builder<?> builder) {
|
||||
super(builder);
|
||||
this.prependedComments = builder.prependedComments;
|
||||
this.appendedComments = builder.appendedComments;
|
||||
this.constructor = builder.constructor;
|
||||
this.representer = builder.representer;
|
||||
this.options = builder.options;
|
||||
this.resolver = builder.resolver;
|
||||
}
|
||||
|
||||
public static Builder<?> builder() {
|
||||
return new Builder() {
|
||||
@Override
|
||||
protected Builder<?> getThis() {
|
||||
return this;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public final List<String> getPrependedComments() {
|
||||
return prependedComments;
|
||||
}
|
||||
|
||||
public final List<String> getAppendedComments() {
|
||||
return appendedComments;
|
||||
}
|
||||
|
||||
public final BaseConstructor getConstructor() {
|
||||
return constructor;
|
||||
}
|
||||
|
||||
public final Representer getRepresenter() {
|
||||
return representer;
|
||||
}
|
||||
|
||||
public final DumperOptions getOptions() {
|
||||
return options;
|
||||
}
|
||||
|
||||
public final Resolver getResolver() {
|
||||
return resolver;
|
||||
}
|
||||
|
||||
public static abstract class Builder<B extends Builder<B>>
|
||||
extends Properties.Builder<B> {
|
||||
private List<String> prependedComments = Collections.emptyList();
|
||||
private List<String> appendedComments = Collections.emptyList();
|
||||
private BaseConstructor constructor = new Constructor();
|
||||
private Representer representer = new Representer();
|
||||
private DumperOptions options = new DumperOptions();
|
||||
private Resolver resolver = new Resolver();
|
||||
|
||||
protected Builder() {
|
||||
options.setIndent(2);
|
||||
options.setDefaultFlowStyle(FlowStyle.BLOCK);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the comments prepended to a configuration.
|
||||
*
|
||||
* @param prependedComments List of comments that are prepended
|
||||
* @return this {@code Builder}
|
||||
* @throws NullPointerException if {@code prependedComments ist null}
|
||||
*/
|
||||
public final B setPrependedComments(List<String> prependedComments) {
|
||||
this.prependedComments = Objects.requireNonNull(prependedComments);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the comments appended to a configuration.
|
||||
*
|
||||
* @param appendedComments List of comments that are appended
|
||||
* @return this {@code Builder}
|
||||
* @throws NullPointerException if {@code appendedComments ist null}
|
||||
*/
|
||||
public final B setAppendedComments(List<String> appendedComments) {
|
||||
this.appendedComments = Objects.requireNonNull(appendedComments);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link BaseConstructor} used by the underlying YAML-parser.
|
||||
*
|
||||
* @param constructor {@code BaseConstructor} used by YAML-parser.
|
||||
* @return this {@code Builder}
|
||||
* @throws NullPointerException if {@code constructor ist null}
|
||||
* @see <a href="https://bitbucket.org/asomov/snakeyaml/wiki/Documentation">snakeyaml-Documentation</a>
|
||||
*/
|
||||
public final B setConstructor(BaseConstructor constructor) {
|
||||
this.constructor = Objects.requireNonNull(constructor);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Representer} used by the underlying YAML-parser.
|
||||
*
|
||||
* @param representer {@code Representer} used by YAML-parser.
|
||||
* @return this {@code Builder}
|
||||
* @throws NullPointerException if {@code representer ist null}
|
||||
* @see <a href="https://bitbucket.org/asomov/snakeyaml/wiki/Documentation">snakeyaml-Documentation</a>
|
||||
*/
|
||||
public final B setRepresenter(Representer representer) {
|
||||
this.representer = Objects.requireNonNull(representer);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link DumperOptions} used by the underlying YAML-parser.
|
||||
*
|
||||
* @param options {@code DumperOptions} used by YAML-parser.
|
||||
* @return this {@code Builder}
|
||||
* @throws NullPointerException if {@code options ist null}
|
||||
* @see <a href="https://bitbucket.org/asomov/snakeyaml/wiki/Documentation">snakeyaml-Documentation</a>
|
||||
*/
|
||||
public final B setOptions(DumperOptions options) {
|
||||
this.options = Objects.requireNonNull(options);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link Resolver} used by the underlying YAML-parser.
|
||||
*
|
||||
* @param resolver {@code Resolver} used by YAML-parser.
|
||||
* @return this {@code Builder}
|
||||
* @throws NullPointerException if {@code resolver ist null}
|
||||
* @see <a href="https://bitbucket.org/asomov/snakeyaml/wiki/Documentation">snakeyaml-Documentation</a>
|
||||
*/
|
||||
public final B setResolver(Resolver resolver) {
|
||||
this.resolver = Objects.requireNonNull(resolver);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new {@code YamlProperties} instance using the values set.
|
||||
*
|
||||
* @return new {@code YamlProperties} instance
|
||||
*/
|
||||
public YamlProperties build() {
|
||||
return new YamlProperties(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,138 +0,0 @@
|
||||
package de.exlll.configlib.configs.yaml;
|
||||
|
||||
import de.exlll.configlib.Comments;
|
||||
import de.exlll.configlib.ConfigurationSource;
|
||||
import de.exlll.configlib.configs.yaml.YamlConfiguration.YamlProperties;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static java.util.stream.Collectors.joining;
|
||||
|
||||
final class YamlSource implements ConfigurationSource<YamlConfiguration> {
|
||||
private final Path configPath;
|
||||
private final YamlProperties props;
|
||||
private final Yaml yaml;
|
||||
|
||||
public YamlSource(Path configPath, YamlProperties props) {
|
||||
this.configPath = Objects.requireNonNull(configPath);
|
||||
this.props = props;
|
||||
this.yaml = new Yaml(
|
||||
props.getConstructor(), props.getRepresenter(),
|
||||
props.getOptions(), props.getResolver()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveConfiguration(YamlConfiguration config, Map<String, Object> map)
|
||||
throws IOException {
|
||||
createParentDirectories();
|
||||
CommentAdder adder = new CommentAdder(
|
||||
yaml.dump(map), config.getComments(), props
|
||||
);
|
||||
String commentedDump = adder.getCommentedDump();
|
||||
Files.write(configPath, commentedDump.getBytes());
|
||||
}
|
||||
|
||||
private void createParentDirectories() throws IOException {
|
||||
Path parentDir = configPath.getParent();
|
||||
if (!Files.isDirectory(parentDir)) {
|
||||
Files.createDirectories(parentDir);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> loadConfiguration(YamlConfiguration config)
|
||||
throws IOException {
|
||||
String cfg = readConfig();
|
||||
return yaml.load(cfg);
|
||||
}
|
||||
|
||||
private String readConfig() throws IOException {
|
||||
return Files.lines(configPath).collect(joining("\n"));
|
||||
}
|
||||
|
||||
private static final class CommentAdder {
|
||||
private static final Pattern PREFIX_PATTERN = Pattern.compile("^\\w+:.*");
|
||||
private final String dump;
|
||||
private final Comments comments;
|
||||
private final YamlComments yamlComments;
|
||||
private final YamlProperties props;
|
||||
private final StringBuilder builder;
|
||||
|
||||
private CommentAdder(String dump, Comments comments,
|
||||
YamlProperties props
|
||||
) {
|
||||
this.dump = dump;
|
||||
this.props = props;
|
||||
this.comments = comments;
|
||||
this.yamlComments = new YamlComments(comments);
|
||||
this.builder = new StringBuilder(dump.length());
|
||||
}
|
||||
|
||||
public String getCommentedDump() {
|
||||
addComments(props.getPrependedComments());
|
||||
addClassComments();
|
||||
addFieldComments();
|
||||
addComments(props.getAppendedComments());
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private void addComments(List<String> comments) {
|
||||
for (String comment : comments) {
|
||||
if (!comment.isEmpty()) {
|
||||
builder.append("# ").append(comment);
|
||||
}
|
||||
builder.append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
private void addClassComments() {
|
||||
if (comments.hasClassComments()) {
|
||||
builder.append(yamlComments.classCommentsAsString());
|
||||
builder.append("\n");
|
||||
}
|
||||
}
|
||||
|
||||
private void addFieldComments() {
|
||||
if (comments.hasFieldComments()) {
|
||||
List<String> dumpLines = Arrays.asList(dump.split("\n"));
|
||||
addDumpLines(dumpLines);
|
||||
} else {
|
||||
builder.append(dump);
|
||||
}
|
||||
}
|
||||
|
||||
private void addDumpLines(List<String> dumpLines) {
|
||||
for (String dumpLine : dumpLines) {
|
||||
Matcher m = PREFIX_PATTERN.matcher(dumpLine);
|
||||
if (m.matches()) {
|
||||
addFieldComment(dumpLine);
|
||||
}
|
||||
builder.append(dumpLine).append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
private void addFieldComment(String dumpLine) {
|
||||
Map<String, String> map = yamlComments.fieldCommentAsStrings(
|
||||
props.getFormatter()
|
||||
);
|
||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||
String prefix = entry.getKey() + ":";
|
||||
if (dumpLine.startsWith(prefix)) {
|
||||
builder.append(entry.getValue()).append('\n');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package de.exlll.configlib.filter;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface FieldFilter extends Predicate<Field> {
|
||||
|
||||
@Override
|
||||
default FieldFilter and(Predicate<? super Field> other) {
|
||||
Objects.requireNonNull(other);
|
||||
return (t) -> test(t) && other.test(t);
|
||||
}
|
||||
|
||||
default List<? extends Field> filterDeclaredFieldsOf(Class<?> cls) {
|
||||
Field[] fields = cls.getDeclaredFields();
|
||||
return Arrays.stream(fields)
|
||||
.filter(this)
|
||||
.collect(toList());
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package de.exlll.configlib.filter;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
public enum FieldFilters implements FieldFilter {
|
||||
DEFAULT {
|
||||
@Override
|
||||
public boolean test(Field field) {
|
||||
if (field.isSynthetic()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int mods = field.getModifiers();
|
||||
return !(Modifier.isFinal(mods) ||
|
||||
Modifier.isStatic(mods) ||
|
||||
Modifier.isTransient(mods));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package de.exlll.configlib.format;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface FieldNameFormatter extends Function<String, String> {
|
||||
String fromFieldName(String fieldName);
|
||||
|
||||
@Override
|
||||
default String apply(String s) {
|
||||
return fromFieldName(s);
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
package de.exlll.configlib.format;
|
||||
|
||||
public enum FieldNameFormatters implements FieldNameFormatter {
|
||||
/**
|
||||
* Represents a {@code FieldNameFormatter} that doesn't actually format the
|
||||
* field name but instead returns it.
|
||||
*/
|
||||
IDENTITY {
|
||||
@Override
|
||||
public String fromFieldName(String fn) {
|
||||
return fn;
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Represents a {@code FieldNameFormatter} that transforms <i>camelCase</i> to
|
||||
* <i>lower_underscore</i>.
|
||||
* <p>
|
||||
* For example, <i>myPrivateField</i> becomes <i>my_private_field</i>.
|
||||
*/
|
||||
LOWER_UNDERSCORE {
|
||||
@Override
|
||||
public String fromFieldName(String fn) {
|
||||
StringBuilder builder = new StringBuilder(fn.length());
|
||||
for (char c : fn.toCharArray()) {
|
||||
if (Character.isLowerCase(c)) {
|
||||
builder.append(c);
|
||||
} else if (Character.isUpperCase(c)) {
|
||||
c = Character.toLowerCase(c);
|
||||
builder.append('_').append(c);
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Represents a {@code FieldNameFormatter} that transforms <i>camelCase</i> to
|
||||
* <i>UPPER_UNDERSCORE</i>.
|
||||
* <p>
|
||||
* For example, <i>myPrivateField</i> becomes <i>MY_PRIVATE_FIELD</i>.
|
||||
*/
|
||||
UPPER_UNDERSCORE {
|
||||
@Override
|
||||
public String fromFieldName(String fieldName) {
|
||||
StringBuilder builder = new StringBuilder(fieldName.length());
|
||||
for (char c : fieldName.toCharArray()) {
|
||||
if (Character.isLowerCase(c)) {
|
||||
builder.append(Character.toUpperCase(c));
|
||||
} else if (Character.isUpperCase(c)) {
|
||||
builder.append('_').append(c);
|
||||
}
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,54 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import de.exlll.configlib.annotation.Comment;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static de.exlll.configlib.util.CollectionFactory.listOf;
|
||||
import static de.exlll.configlib.util.CollectionFactory.mapOf;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class CommentsTest {
|
||||
|
||||
@Test
|
||||
void classCommentsAdded() {
|
||||
class A {}
|
||||
|
||||
@Comment("B")
|
||||
class B {}
|
||||
|
||||
@Comment({"C", "D"})
|
||||
class C {}
|
||||
|
||||
Comments comments = Comments.ofClass(A.class);
|
||||
assertThat(comments.getClassComments(), empty());
|
||||
assertThat(comments.getFieldComments().entrySet(), empty());
|
||||
|
||||
comments = Comments.ofClass(B.class);
|
||||
assertThat(comments.getClassComments(), is(listOf("B")));
|
||||
assertThat(comments.getFieldComments().entrySet(), empty());
|
||||
|
||||
comments = Comments.ofClass(C.class);
|
||||
assertThat(comments.getClassComments(), is(listOf("C", "D")));
|
||||
assertThat(comments.getFieldComments().entrySet(), empty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void fieldCommentsAdded() {
|
||||
class A {
|
||||
int a;
|
||||
@Comment("b")
|
||||
int b;
|
||||
@Comment({"c", "d"})
|
||||
int c;
|
||||
}
|
||||
|
||||
Comments comments = Comments.ofClass(A.class);
|
||||
assertThat(comments.getClassComments(), empty());
|
||||
assertThat(comments.getFieldComments(), is(mapOf(
|
||||
"b", listOf("b"),
|
||||
"c", listOf("c", "d")
|
||||
)));
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import de.exlll.configlib.configs.mem.InSharedMemoryConfiguration;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
class ConfigurationTest {
|
||||
private static class TestHook extends InSharedMemoryConfiguration {
|
||||
protected TestHook() { super(Properties.builder().build()); }
|
||||
}
|
||||
|
||||
@Test
|
||||
void configExecutesPreSaveHook() {
|
||||
class A extends TestHook {
|
||||
int i = 0;
|
||||
|
||||
@Override
|
||||
protected void preSave() { i++; }
|
||||
}
|
||||
A save = new A();
|
||||
save.save();
|
||||
assertThat(save.i, is(1));
|
||||
|
||||
A load = new A();
|
||||
load.load();
|
||||
assertThat(load.i, is(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void configExecutesPostLoadHook() {
|
||||
class A extends TestHook {
|
||||
int i = 0;
|
||||
|
||||
@Override
|
||||
protected void postLoad() { i++; }
|
||||
}
|
||||
A save = new A();
|
||||
save.save();
|
||||
assertThat(save.i, is(0));
|
||||
|
||||
A load = new A();
|
||||
load.load();
|
||||
assertThat(load.i, is(1));
|
||||
}
|
||||
}
|
@ -1,392 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import de.exlll.configlib.annotation.Convert;
|
||||
import de.exlll.configlib.annotation.ElementType;
|
||||
import de.exlll.configlib.classes.TestSubClass;
|
||||
import de.exlll.configlib.classes.TestSubClassConverter;
|
||||
import org.hamcrest.MatcherAssert;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static de.exlll.configlib.FieldMapperHelpers.*;
|
||||
import static de.exlll.configlib.util.CollectionFactory.listOf;
|
||||
import static de.exlll.configlib.util.CollectionFactory.mapOf;
|
||||
import static de.exlll.configlib.util.CollectionFactory.setOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
@SuppressWarnings({"unused", "ThrowableNotThrown"})
|
||||
public class FieldMapperConverterTest {
|
||||
|
||||
private static class Point2D {
|
||||
protected int x = 1;
|
||||
protected int y = 2;
|
||||
|
||||
private Point2D() {}
|
||||
|
||||
protected Point2D(int x, int y) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
private static Point2D of(int x, int y) {
|
||||
return new Point2D(x, y);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class PointToListConverter
|
||||
implements Converter<Point2D, List<Integer>> {
|
||||
@Override
|
||||
public List<Integer> convertTo(Point2D element, ConversionInfo info) {
|
||||
return listOf(element.x, element.y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point2D convertFrom(List<Integer> element, ConversionInfo info) {
|
||||
Point2D point = new Point2D();
|
||||
point.x = element.get(0);
|
||||
point.y = element.get(1);
|
||||
return point;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class PointToMapConverter
|
||||
implements Converter<Point2D, Map<String, String>> {
|
||||
@Override
|
||||
public Map<String, String> convertTo(Point2D element, ConversionInfo info) {
|
||||
int x = element.x;
|
||||
int y = element.y;
|
||||
return mapOf("p", x + ":" + y);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point2D convertFrom(Map<String, String> element, ConversionInfo info) {
|
||||
String p = element.get("p");
|
||||
String[] split = p.split(":");
|
||||
return Point2D.of(Integer.valueOf(split[0]), Integer.valueOf(split[1]));
|
||||
}
|
||||
}
|
||||
|
||||
private static final class IntToStringConverter
|
||||
implements Converter<Integer, String> {
|
||||
|
||||
@Override
|
||||
public String convertTo(Integer element, ConversionInfo info) {
|
||||
return element.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer convertFrom(String element, ConversionInfo info) {
|
||||
return Integer.valueOf(element);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapRequiresNoArgsConverter() {
|
||||
class A {
|
||||
@Convert(MultiArgsConverter.class)
|
||||
int i;
|
||||
}
|
||||
class B {
|
||||
@Convert(SubConverter.class)
|
||||
int i;
|
||||
}
|
||||
class C {
|
||||
@Convert(EnumConverter.class)
|
||||
int i;
|
||||
}
|
||||
String msg = "Converter 'MultiArgsConverter' used on field 'i' doesn't " +
|
||||
"have a no-args constructor.";
|
||||
assertItmCfgExceptionMessage(new A(), msg);
|
||||
msg = "Converter 'SubConverter' used on field 'i' doesn't " +
|
||||
"have a no-args constructor.";
|
||||
assertItmCfgExceptionMessage(new B(), msg);
|
||||
msg = "Converter 'EnumConverter' used on field 'i' doesn't " +
|
||||
"have a no-args constructor.";
|
||||
assertItmCfgExceptionMessage(new C(), msg);
|
||||
}
|
||||
|
||||
private static final class MultiArgsConverter
|
||||
implements Converter<Object, Object> {
|
||||
private MultiArgsConverter(int i) {}
|
||||
|
||||
@Override
|
||||
public Object convertTo(Object element, ConversionInfo info) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convertFrom(Object element, ConversionInfo info) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static final class NullConverter
|
||||
implements Converter<String, String> {
|
||||
|
||||
@Override
|
||||
public String convertTo(String element, ConversionInfo info) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String convertFrom(String element, ConversionInfo info) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapThrowsExceptionIfConverterReturnsNull() {
|
||||
class A {
|
||||
@Convert(NullConverter.class)
|
||||
String s = "string";
|
||||
}
|
||||
String msg = "Failed to convert value 'string' of field 's' because " +
|
||||
"the converter returned null.";
|
||||
assertItmCfgExceptionMessage(new A(), msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapKeepsDefaultValueIfConverterReturnsNull() {
|
||||
class A {
|
||||
@Convert(NullConverter.class)
|
||||
String s = "string";
|
||||
}
|
||||
Map<String, Object> map = mapOf("s", "value");
|
||||
A a = instanceFromMap(new A(), map);
|
||||
assertThat(a.s, is("string"));
|
||||
}
|
||||
|
||||
private interface SubConverter extends Converter<Object, Object> {}
|
||||
|
||||
private enum EnumConverter implements Converter<Object, Object> {
|
||||
;
|
||||
|
||||
@Override
|
||||
public Object convertTo(Object element, ConversionInfo info) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convertFrom(Object element, ConversionInfo info) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapUsesConverterForSimpleTypes() {
|
||||
class A {
|
||||
@Convert(IntToStringConverter.class)
|
||||
int i = 1;
|
||||
}
|
||||
Map<String, Object> map = instanceToMap(new A());
|
||||
assertThat(map.get("i"), is("1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapUsesConverterForSimpleTypes() {
|
||||
class A {
|
||||
@Convert(IntToStringConverter.class)
|
||||
int i = 1;
|
||||
}
|
||||
A i = instanceFromMap(new A(), mapOf("i", "10"));
|
||||
assertThat(i.i, is(10));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapConvertsCustomTypesUsingConverters() {
|
||||
class A {
|
||||
@Convert(PointToListConverter.class)
|
||||
Point2D p1 = new Point2D();
|
||||
@Convert(PointToMapConverter.class)
|
||||
Point2D p2 = new Point2D();
|
||||
}
|
||||
Map<String, Object> map = instanceToMap(new A());
|
||||
assertThat(map.get("p1"), is(listOf(1, 2)));
|
||||
assertThat(map.get("p2"), is(mapOf("p", "1:2")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapConvertsCustomTypesUsingConverters() {
|
||||
class A {
|
||||
@Convert(PointToListConverter.class)
|
||||
Point2D p1 = new Point2D();
|
||||
@Convert(PointToMapConverter.class)
|
||||
Point2D p2 = new Point2D();
|
||||
}
|
||||
Map<String, Object> map = mapOf(
|
||||
"p1", listOf(10, 11),
|
||||
"p2", mapOf("p", "11:12")
|
||||
);
|
||||
A i = instanceFromMap(new A(), map);
|
||||
assertThat(i.p1.x, is(10));
|
||||
assertThat(i.p1.y, is(11));
|
||||
|
||||
assertThat(i.p2.x, is(11));
|
||||
assertThat(i.p2.y, is(12));
|
||||
}
|
||||
|
||||
private static final class CountingConverter
|
||||
implements Converter<Object, Object> {
|
||||
static int instanceCount;
|
||||
|
||||
public CountingConverter() {
|
||||
instanceCount++;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convertTo(Object element, ConversionInfo info) {
|
||||
return element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object convertFrom(Object element, ConversionInfo info) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertersUseCache() {
|
||||
class A {
|
||||
@Convert(CountingConverter.class)
|
||||
Point2D a = new Point2D();
|
||||
@Convert(CountingConverter.class)
|
||||
Point2D b = new Point2D();
|
||||
}
|
||||
Map<String, Object> map = instanceToMap(new A());
|
||||
assertThat(CountingConverter.instanceCount, is(1));
|
||||
instanceFromMap(new A(), map);
|
||||
assertThat(CountingConverter.instanceCount, is(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapCatchesClassCastException() {
|
||||
class A {
|
||||
@Convert(TestSubClassConverter.class)
|
||||
String s = "string";
|
||||
}
|
||||
String msg = "Converter 'TestSubClassConverter' cannot convert value " +
|
||||
"'string' of field 's' because it expects a different type.";
|
||||
assertItmCfgExceptionMessage(new A(), msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapCatchesClassCastExceptionOfCustomClasses() {
|
||||
class A {
|
||||
@Convert(TestSubClassConverter.class)
|
||||
TestSubClass a = new TestSubClass();
|
||||
}
|
||||
Map<String, Object> map = mapOf(
|
||||
"a", 1
|
||||
);
|
||||
String msg = "The value for field 'a' with type 'TestSubClass' " +
|
||||
"cannot be converted back to its original representation because " +
|
||||
"a type mismatch occurred.";
|
||||
assertIfmCfgExceptionMessage(new A(), map, msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapCatchesClassCastExceptionOfChars() {
|
||||
class C {
|
||||
char c;
|
||||
}
|
||||
class D {
|
||||
char d;
|
||||
}
|
||||
Map<String, Object> map = mapOf(
|
||||
"c", "", "d", "12"
|
||||
);
|
||||
String msg = "The value for field 'c' with type 'char' " +
|
||||
"cannot be converted back to its original representation because " +
|
||||
"a type mismatch occurred.";
|
||||
assertIfmCfgExceptionMessage(new C(), map, msg);
|
||||
|
||||
msg = "The value for field 'd' with type 'char' " +
|
||||
"cannot be converted back to its original representation because " +
|
||||
"a type mismatch occurred.";
|
||||
assertIfmCfgExceptionMessage(new D(), map, msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapCatchesClassCastExceptionOfStrings() {
|
||||
class B {
|
||||
String b = "string";
|
||||
}
|
||||
Map<String, Object> map = mapOf(
|
||||
"b", 2
|
||||
);
|
||||
String msg = "The value for field 'b' with type 'String' " +
|
||||
"cannot be converted back to its original representation because " +
|
||||
"a type mismatch occurred.";
|
||||
assertIfmCfgExceptionMessage(new B(), map, msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapCatchesClassCastExceptionOfUnknownEnumConstants() {
|
||||
class A {
|
||||
LocalTestEnum e = LocalTestEnum.T;
|
||||
}
|
||||
Map<String, Object> map = mapOf(
|
||||
"e", "V"
|
||||
);
|
||||
String msg = "The value for field 'e' with type 'LocalTestEnum' " +
|
||||
"cannot be converted back to its original representation because " +
|
||||
"a type mismatch occurred.";
|
||||
assertIfmCfgExceptionMessage(new A(), map, msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapCatchesClassCastExceptionOfUnknownEnumConstantsInLists() {
|
||||
class A {
|
||||
@ElementType(value = LocalTestEnum.class, nestingLevel = 1)
|
||||
List<List<LocalTestEnum>> l = listOf();
|
||||
}
|
||||
Map<String, Object> map = mapOf(
|
||||
"l", listOf(listOf("Q", "V"))
|
||||
);
|
||||
ConfigurationException ex = assertIfmThrowsCfgException(new A(), map);
|
||||
Throwable cause = ex.getCause();
|
||||
|
||||
String msg = "Cannot initialize an enum element of list 'l' because there " +
|
||||
"is no enum constant 'Q'.\nValid constants are: [S, T]";
|
||||
MatcherAssert.assertThat(cause.getMessage(), is(msg));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapCatchesClassCastExceptionOfUnknownEnumConstantsInSets() {
|
||||
class A {
|
||||
@ElementType(value = LocalTestEnum.class, nestingLevel = 1)
|
||||
Set<List<LocalTestEnum>> s = setOf();
|
||||
}
|
||||
Map<String, Object> map = mapOf(
|
||||
"s", setOf(listOf("Q", "V"))
|
||||
);
|
||||
ConfigurationException ex = assertIfmThrowsCfgException(new A(), map);
|
||||
Throwable cause = ex.getCause();
|
||||
|
||||
String msg = "Cannot initialize an enum element of set 's' because there " +
|
||||
"is no enum constant 'Q'.\nValid constants are: [S, T]";
|
||||
MatcherAssert.assertThat(cause.getMessage(), is(msg));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapCatchesClassCastExceptionOfUnknownEnumConstantsInMaps() {
|
||||
class A {
|
||||
@ElementType(value = LocalTestEnum.class, nestingLevel = 1)
|
||||
Map<Integer, List<LocalTestEnum>> m = mapOf();
|
||||
}
|
||||
Map<String, Object> map = mapOf(
|
||||
"m", mapOf(1, listOf("Q", "V"))
|
||||
);
|
||||
ConfigurationException ex = assertIfmThrowsCfgException(new A(), map);
|
||||
Throwable cause = ex.getCause();
|
||||
|
||||
String msg = "Cannot initialize an enum element of map 'm' because there " +
|
||||
"is no enum constant 'Q'.\nValid constants are: [S, T]";
|
||||
MatcherAssert.assertThat(cause.getMessage(), is(msg));
|
||||
}
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import de.exlll.configlib.FieldMapper.MappingInfo;
|
||||
import de.exlll.configlib.annotation.ConfigurationElement;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static de.exlll.configlib.configs.yaml.YamlConfiguration.YamlProperties.DEFAULT;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public class FieldMapperHelpers {
|
||||
@ConfigurationElement
|
||||
interface LocalTestInterface {}
|
||||
|
||||
@ConfigurationElement
|
||||
static class LocalTestInterfaceImpl implements LocalTestInterface {}
|
||||
|
||||
@ConfigurationElement
|
||||
static abstract class LocalTestAbstractClass {}
|
||||
|
||||
@ConfigurationElement
|
||||
static class LocalTestAbstractClassImpl extends LocalTestAbstractClass {}
|
||||
|
||||
@ConfigurationElement
|
||||
enum LocalTestEnum {
|
||||
S, T
|
||||
}
|
||||
|
||||
static class Sub1 {}
|
||||
|
||||
@ConfigurationElement
|
||||
static class Sub2 {}
|
||||
|
||||
@ConfigurationElement
|
||||
static class Sub3 {
|
||||
public Sub3(int x) {}
|
||||
}
|
||||
|
||||
public static void assertItmCfgExceptionMessage(Object o, String msg) {
|
||||
ConfigurationException ex = assertItmThrowsCfgException(o);
|
||||
assertThat(ex.getMessage(), is(msg));
|
||||
}
|
||||
|
||||
public static void assertIfmCfgExceptionMessage(
|
||||
Object o, Map<String, Object> map, String msg
|
||||
) {
|
||||
ConfigurationException ex = assertIfmThrowsCfgException(o, map);
|
||||
assertThat(ex.getMessage(), is(msg));
|
||||
}
|
||||
|
||||
public static ConfigurationException assertItmThrowsCfgException(Object o) {
|
||||
return assertThrows(
|
||||
ConfigurationException.class,
|
||||
() -> instanceToMap(o)
|
||||
);
|
||||
}
|
||||
|
||||
public static ConfigurationException assertIfmThrowsCfgException(
|
||||
Object o, Map<String, Object> map
|
||||
) {
|
||||
return assertThrows(
|
||||
ConfigurationException.class,
|
||||
() -> instanceFromMap(o, map)
|
||||
);
|
||||
}
|
||||
|
||||
public static Map<String, Object> instanceToMap(Object o) {
|
||||
return instanceToMap(o, DEFAULT);
|
||||
}
|
||||
|
||||
public static Map<String, Object> instanceToMap(
|
||||
Object o, Configuration.Properties props
|
||||
) {
|
||||
MappingInfo mappingInfo = new MappingInfo(null, props);
|
||||
return FieldMapper.instanceToMap(o, mappingInfo);
|
||||
}
|
||||
|
||||
public static <T> T instanceFromMap(T o, Map<String, Object> map) {
|
||||
return instanceFromMap(o, map, DEFAULT);
|
||||
}
|
||||
|
||||
public static <T> T instanceFromMap(
|
||||
T o, Map<String, Object> map, Configuration.Properties props
|
||||
) {
|
||||
MappingInfo mappingInfo = new MappingInfo(null, props);
|
||||
FieldMapper.instanceFromMap(o, map, mappingInfo);
|
||||
return o;
|
||||
}
|
||||
}
|
@ -1,592 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import de.exlll.configlib.Converter.ConversionInfo;
|
||||
import de.exlll.configlib.FieldMapper.MappingInfo;
|
||||
import de.exlll.configlib.annotation.ElementType;
|
||||
import de.exlll.configlib.annotation.Format;
|
||||
import de.exlll.configlib.annotation.NoConvert;
|
||||
import de.exlll.configlib.classes.TestClass;
|
||||
import de.exlll.configlib.classes.TestSubClass;
|
||||
import de.exlll.configlib.classes.TestSubClassConverter;
|
||||
import de.exlll.configlib.configs.mem.InSharedMemoryConfiguration;
|
||||
import de.exlll.configlib.configs.yaml.YamlConfiguration.YamlProperties;
|
||||
import de.exlll.configlib.format.FieldNameFormatter;
|
||||
import de.exlll.configlib.format.FieldNameFormatters;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static de.exlll.configlib.Converters.ENUM_CONVERTER;
|
||||
import static de.exlll.configlib.Converters.SIMPLE_TYPE_CONVERTER;
|
||||
import static de.exlll.configlib.FieldMapperHelpers.*;
|
||||
import static de.exlll.configlib.util.CollectionFactory.*;
|
||||
import static java.util.stream.Collectors.*;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertAll;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
@SuppressWarnings({"unused", "ThrowableNotThrown"})
|
||||
class FieldMapperTest {
|
||||
private static final TestClass t = TestClass.TEST_VALUES;
|
||||
private static final Configuration.Properties DEFAULT =
|
||||
Configuration.Properties.builder().build();
|
||||
private static final YamlProperties WITH_UPPER_FORMATTER = YamlProperties
|
||||
.builder()
|
||||
.setFormatter(FieldNameFormatters.UPPER_UNDERSCORE)
|
||||
.build();
|
||||
private static final Map<String, Object> map = instanceToMap(
|
||||
TestClass.TEST_VALUES, DEFAULT
|
||||
);
|
||||
|
||||
private TestClass tmp;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
tmp = new TestClass();
|
||||
MappingInfo mappingInfo = MappingInfo.from(tmp);
|
||||
FieldMapper.instanceFromMap(tmp, map, mappingInfo);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapSetsSimpleTypes() {
|
||||
assertAll(
|
||||
() -> assertThat(tmp.getPrimBool(), is(t.getPrimBool())),
|
||||
() -> assertThat(tmp.getRefBool(), is(t.getRefBool())),
|
||||
() -> assertThat(tmp.getPrimByte(), is(t.getPrimByte())),
|
||||
() -> assertThat(tmp.getRefByte(), is(t.getRefByte())),
|
||||
() -> assertThat(tmp.getPrimChar(), is(t.getPrimChar())),
|
||||
() -> assertThat(tmp.getRefChar(), is(t.getRefChar())),
|
||||
() -> assertThat(tmp.getPrimShort(), is(t.getPrimShort())),
|
||||
() -> assertThat(tmp.getRefShort(), is(t.getRefShort())),
|
||||
() -> assertThat(tmp.getPrimInt(), is(t.getPrimInt())),
|
||||
() -> assertThat(tmp.getRefInt(), is(t.getRefInt())),
|
||||
() -> assertThat(tmp.getPrimLong(), is(t.getPrimLong())),
|
||||
() -> assertThat(tmp.getRefLong(), is(t.getRefLong())),
|
||||
() -> assertThat(tmp.getPrimFloat(), is(t.getPrimFloat())),
|
||||
() -> assertThat(tmp.getRefFloat(), is(t.getRefFloat())),
|
||||
() -> assertThat(tmp.getPrimDouble(), is(t.getPrimDouble())),
|
||||
() -> assertThat(tmp.getRefDouble(), is(t.getRefDouble())),
|
||||
() -> assertThat(tmp.getString(), is(t.getString()))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapSetsEnums() {
|
||||
assertThat(tmp.getE1(), is(t.getE1()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapSetsContainersOfSimpleTypes() {
|
||||
assertAll(
|
||||
() -> assertThat(tmp.getInts(), is(t.getInts())),
|
||||
() -> assertThat(tmp.getStrings(), is(t.getStrings())),
|
||||
() -> assertThat(tmp.getDoubleByBool(), is(t.getDoubleByBool()))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapsConvertsMapsToTypes() {
|
||||
assertThat(tmp.getSubClass(), is(t.getSubClass()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapsConvertsExcludedClasses() {
|
||||
assertThat(tmp.getExcludedClass(), is(t.getExcludedClass()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapsConvertsContainersOfMaps() {
|
||||
assertThat(tmp.getSubClassList(), is(t.getSubClassList()));
|
||||
assertThat(tmp.getSubClassSet(), is(t.getSubClassSet()));
|
||||
assertThat(tmp.getSubClassMap(), is(t.getSubClassMap()));
|
||||
assertThat(tmp.getSubClassListsList(), is(t.getSubClassListsList()));
|
||||
assertThat(tmp.getSubClassSetsSet(), is(t.getSubClassSetsSet()));
|
||||
assertThat(tmp.getSubClassMapsMap(), is(t.getSubClassMapsMap()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapDoesNotSetFinalStaticOrTransientFields() {
|
||||
Map<String, Object> map = mapOf(
|
||||
"staticFinalInt", 10,
|
||||
"staticInt", 10,
|
||||
"finalInt", 10,
|
||||
"transientInt", 10
|
||||
);
|
||||
TestClass cls = instanceFromMap(new TestClass(), map);
|
||||
assertThat(TestClass.getStaticFinalInt(), is(1));
|
||||
assertThat(TestClass.getStaticInt(), is(2));
|
||||
assertThat(cls.getFinalInt(), is(3));
|
||||
assertThat(cls.getTransientInt(), is(4));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapConvertsAllFields() {
|
||||
assertThat(tmp, is(t));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapThrowsExceptionIfEnumConstantDoesNotExist() {
|
||||
class A {
|
||||
LocalTestEnum t = LocalTestEnum.T;
|
||||
}
|
||||
Map<String, Object> map = mapOf(
|
||||
"t", "R"
|
||||
);
|
||||
|
||||
String msg = "Cannot initialize enum 't' because there is no enum " +
|
||||
"constant 'R'.\nValid constants are: [S, T]";
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> ((Converter<Enum<?>, String>) ENUM_CONVERTER)
|
||||
.convertFrom("R", newInfo("t", new A())),
|
||||
msg
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapIgnoresNullValues() {
|
||||
class A {
|
||||
TestSubClass c = new TestSubClass();
|
||||
}
|
||||
Map<String, Object> map = mapOf("c", mapOf("primInt", 20));
|
||||
|
||||
A a = new A();
|
||||
assertThat(a.c.getPrimInt(), is(0));
|
||||
assertThat(a.c.getString(), is(""));
|
||||
instanceFromMap(a, map);
|
||||
assertThat(a.c.getPrimInt(), is(20));
|
||||
assertThat(a.c.getString(), is(""));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapSetsField() {
|
||||
TestClass ins = TestClass.TEST_VALUES;
|
||||
TestClass def = new TestClass();
|
||||
assertThat(ins, is(not(def)));
|
||||
instanceFromMap(def, map);
|
||||
assertThat(ins, is(def));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapCreatesConcreteInstances() {
|
||||
class A {
|
||||
LocalTestInterface l = new LocalTestInterfaceImpl();
|
||||
}
|
||||
class B {
|
||||
LocalTestAbstractClass l = new LocalTestAbstractClassImpl();
|
||||
}
|
||||
instanceFromMap(new A(), mapOf("l", mapOf()));
|
||||
instanceFromMap(new B(), mapOf("l", mapOf()));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void instanceToMapUsesFieldNameFormatter() {
|
||||
Configuration.Properties.Builder builder =
|
||||
Configuration.Properties.builder();
|
||||
Map<String, Object> map = instanceToMap(
|
||||
TestClass.TEST_VALUES,
|
||||
builder.setFormatter(FieldNameFormatters.LOWER_UNDERSCORE).build()
|
||||
);
|
||||
assertThat(map.get("primBool"), nullValue());
|
||||
assertThat(map.get("prim_bool"), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapContainsAllFields() {
|
||||
assertThat(map.size(), is(34));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapDoesNotContainFinalStaticOrTransientFields() {
|
||||
assertAll(
|
||||
() -> assertThat(map.get("staticFinalInt"), is(nullValue())),
|
||||
() -> assertThat(map.get("staticInt"), is(nullValue())),
|
||||
() -> assertThat(map.get("finalInt"), is(nullValue())),
|
||||
() -> assertThat(map.get("transientInt"), is(nullValue()))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapContainsAllSimpleFields() {
|
||||
assertAll(
|
||||
() -> assertThat(map.get("primBool"), is(t.getPrimBool())),
|
||||
() -> assertThat(map.get("refBool"), is(t.getRefBool())),
|
||||
() -> assertThat(map.get("primByte"), is(t.getPrimByte())),
|
||||
() -> assertThat(map.get("refByte"), is(t.getRefByte())),
|
||||
() -> assertThat(map.get("primChar"), is(t.getPrimChar())),
|
||||
() -> assertThat(map.get("refChar"), is(t.getRefChar())),
|
||||
() -> assertThat(map.get("primShort"), is(t.getPrimShort())),
|
||||
() -> assertThat(map.get("refShort"), is(t.getRefShort())),
|
||||
() -> assertThat(map.get("primInt"), is(t.getPrimInt())),
|
||||
() -> assertThat(map.get("refInt"), is(t.getRefInt())),
|
||||
() -> assertThat(map.get("primLong"), is(t.getPrimLong())),
|
||||
() -> assertThat(map.get("refLong"), is(t.getRefLong())),
|
||||
() -> assertThat(map.get("primFloat"), is(t.getPrimFloat())),
|
||||
() -> assertThat(map.get("refFloat"), is(t.getRefFloat())),
|
||||
() -> assertThat(map.get("primDouble"), is(t.getPrimDouble())),
|
||||
() -> assertThat(map.get("refDouble"), is(t.getRefDouble())),
|
||||
() -> assertThat(map.get("string"), is(t.getString()))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapContainsAllContainersOfSimpleTypes() {
|
||||
assertAll(
|
||||
() -> assertThat(map.get("ints"), is(t.getInts())),
|
||||
() -> assertThat(map.get("strings"), is(t.getStrings())),
|
||||
() -> assertThat(map.get("doubleByBool"), is(t.getDoubleByBool()))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapConvertsTypesToMaps() {
|
||||
assertThat(map.get("subClass"), is(t.getSubClass().asMap()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapConvertsExcludedClasses() {
|
||||
assertThat(map.get("excludedClass"), is(t.getExcludedClass()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapConvertsEnumsContainersToStringContainers() {
|
||||
class A {
|
||||
@ElementType(LocalTestEnum.class)
|
||||
List<LocalTestEnum> el = listOf(LocalTestEnum.S, LocalTestEnum.T);
|
||||
@ElementType(LocalTestEnum.class)
|
||||
Set<LocalTestEnum> es = setOf(LocalTestEnum.S, LocalTestEnum.T);
|
||||
@ElementType(LocalTestEnum.class)
|
||||
Map<String, LocalTestEnum> em = mapOf(
|
||||
"1", LocalTestEnum.S, "2", LocalTestEnum.T
|
||||
);
|
||||
}
|
||||
Map<String, Object> map = instanceToMap(new A());
|
||||
assertThat(map.get("el"), is(listOf("S", "T")));
|
||||
assertThat(map.get("es"), is(setOf("S", "T")));
|
||||
assertThat(map.get("em"), is(mapOf("1", "S", "2", "T")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapConvertsStringContainersToEnumContainers() {
|
||||
class A {
|
||||
@ElementType(LocalTestEnum.class)
|
||||
List<LocalTestEnum> el = listOf();
|
||||
@ElementType(LocalTestEnum.class)
|
||||
Set<LocalTestEnum> es = setOf();
|
||||
@ElementType(LocalTestEnum.class)
|
||||
Map<String, LocalTestEnum> em = mapOf();
|
||||
}
|
||||
Map<String, Object> map = mapOf(
|
||||
"el", listOf("S", "T"),
|
||||
"es", setOf("S", "T"),
|
||||
"em", mapOf("1", "S", "2", "T")
|
||||
);
|
||||
A a = instanceFromMap(new A(), map);
|
||||
assertThat(a.el, is(listOf(LocalTestEnum.S, LocalTestEnum.T)));
|
||||
assertThat(a.es, is(setOf(LocalTestEnum.S, LocalTestEnum.T)));
|
||||
assertThat(a.em, is(mapOf(
|
||||
"1", LocalTestEnum.S, "2", LocalTestEnum.T
|
||||
)));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void instanceToMapConvertsContainerElementsToMaps() {
|
||||
List<Map<String, Object>> subClassList = t.getSubClassList().stream()
|
||||
.map(TestSubClass::asMap)
|
||||
.collect(toList());
|
||||
Set<Map<String, Object>> subClassSet = t.getSubClassSet().stream()
|
||||
.map(TestSubClass::asMap)
|
||||
.collect(toSet());
|
||||
Map<String, Map<String, Object>> subClassMap = t.getSubClassMap()
|
||||
.entrySet().stream()
|
||||
.map(e -> mapEntry(e.getKey(), e.getValue().asMap()))
|
||||
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
assertAll(
|
||||
() -> assertThat(map.get("subClassSet"), is(subClassSet)),
|
||||
() -> assertThat(map.get("subClassMap"), is(subClassMap)),
|
||||
() -> assertThat(map.get("subClassList"), is(subClassList))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapContainsNestedContainersOfSimpleTypes() {
|
||||
assertAll(
|
||||
() -> assertThat(map.get("listsList"), is(t.getListsList())),
|
||||
() -> assertThat(map.get("setsSet"), is(t.getSetsSet())),
|
||||
() -> assertThat(map.get("mapsMap"), is(t.getMapsMap()))
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapContainsNestedContainersOfCustomTypes() {
|
||||
List<List<Map<String, Object>>> lists = t.getSubClassListsList()
|
||||
.stream().map(list -> list.stream()
|
||||
.map(TestSubClass::asMap)
|
||||
.collect(toList()))
|
||||
.collect(toList());
|
||||
Set<Set<Map<String, Object>>> sets = t.getSubClassSetsSet()
|
||||
.stream().map(set -> set.stream()
|
||||
.map(TestSubClass::asMap)
|
||||
.collect(toSet()))
|
||||
.collect(toSet());
|
||||
Function<Map<String, TestSubClass>, Map<String, Map<String, Object>>> f =
|
||||
map -> map.entrySet().stream().collect(
|
||||
toMap(Map.Entry::getKey, e -> e.getValue().asMap())
|
||||
);
|
||||
Map<Integer, Map<String, Map<String, Object>>> m = t.getSubClassMapsMap()
|
||||
.entrySet().stream()
|
||||
.map(e -> mapEntry(e.getKey(), f.apply(e.getValue())))
|
||||
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
|
||||
assertThat(map.get("subClassListsList"), is(lists));
|
||||
assertThat(map.get("subClassSetsSet"), is(sets));
|
||||
assertThat(map.get("subClassMapsMap"), is(m));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapContainsEnums() {
|
||||
assertThat(map.get("e1"), is(t.getE1().toString()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapContainsEnumLists() {
|
||||
List<String> list = t.getEnums().stream()
|
||||
.map(Enum::toString)
|
||||
.collect(toList());
|
||||
assertThat(map.get("enums"), is(list));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void instanceToMapConvertsConvertTypes() {
|
||||
String s = new TestSubClassConverter()
|
||||
.convertTo(t.getConverterSubClass(), null);
|
||||
assertThat(map.get("converterSubClass"), is(s));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapCreatesLinkedHashMap() {
|
||||
assertThat(instanceToMap(new Object()), instanceOf(LinkedHashMap.class));
|
||||
}
|
||||
|
||||
private static Converter<Object, Object> converter = SIMPLE_TYPE_CONVERTER;
|
||||
|
||||
private static ConversionInfo newInfo(String fieldName, Object o) {
|
||||
Field field = null;
|
||||
try {
|
||||
field = o.getClass().getDeclaredField(fieldName);
|
||||
} catch (NoSuchFieldException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
MappingInfo mappingInfo = new MappingInfo(null, WITH_UPPER_FORMATTER);
|
||||
return ConversionInfo.from(field, o, mappingInfo);
|
||||
}
|
||||
|
||||
private static ConversionInfo newInfo(String fieldName) {
|
||||
return newInfo(fieldName, new TestClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
void typeConverterReturnsInstanceIfClassesMatch() {
|
||||
//noinspection RedundantStringConstructorCall
|
||||
String s = new String("123");
|
||||
Object val = converter.convertFrom(s, newInfo("string"));
|
||||
assertThat(val, sameInstance(s));
|
||||
}
|
||||
|
||||
@Test
|
||||
void typeConverterConvertsStringToCharacter() {
|
||||
String s = "1";
|
||||
Object vc = converter.convertFrom(s, newInfo("primChar"));
|
||||
Object vd = converter.convertFrom(s, newInfo("refChar"));
|
||||
|
||||
assertThat(vc, instanceOf(Character.class));
|
||||
assertThat(vd, instanceOf(Character.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void typeConverterChecksInvalidStrings() {
|
||||
String msg = "An empty string cannot be converted to a character.";
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> converter.convertFrom("", newInfo("refChar")),
|
||||
msg
|
||||
);
|
||||
|
||||
String value = "123";
|
||||
msg = "String '" + value + "' is too long to be converted to a character";
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> converter.convertFrom(value, newInfo("refChar")),
|
||||
msg
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void typeConverterConvertsNumbers() {
|
||||
Number[] numbers = {
|
||||
(byte) 1, (short) 2, 3, (long) 4,
|
||||
(float) 5, (double) 6, 4L, 5f, 6d
|
||||
};
|
||||
String[] classes = {
|
||||
"refByte", "refShort", "refInt",
|
||||
"primLong", "refFloat", "refDouble"
|
||||
};
|
||||
|
||||
for (String cls : classes) {
|
||||
for (Number number : numbers) {
|
||||
ConversionInfo info = newInfo(cls);
|
||||
Object converted = converter.convertFrom(number, info);
|
||||
assertThat(converted, instanceOf(info.getFieldType()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void typeConverterChecksInvalidNumbers() {
|
||||
String msg = "Number '1' cannot be converted to type 'String'";
|
||||
assertThrows(
|
||||
IllegalArgumentException.class,
|
||||
() -> converter.convertFrom(1, newInfo("string")),
|
||||
msg
|
||||
);
|
||||
}
|
||||
|
||||
private static final class ExcludedClass {}
|
||||
|
||||
@Test
|
||||
void instanceToMapSetsAutoConvertedInstancesAsIs() {
|
||||
class A {
|
||||
@NoConvert
|
||||
ExcludedClass ex = new ExcludedClass();
|
||||
}
|
||||
A a = new A();
|
||||
Map<String, Object> map = instanceToMap(a);
|
||||
assertThat(map.get("ex"), sameInstance(a.ex));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapSetsAutoConvertedInstancesAsIs() {
|
||||
class A {
|
||||
@NoConvert
|
||||
ExcludedClass ex = new ExcludedClass();
|
||||
}
|
||||
|
||||
ExcludedClass cls = new ExcludedClass();
|
||||
Map<String, Object> map = mapOf("ex", cls);
|
||||
A a = instanceFromMap(new A(), map);
|
||||
assertThat(a.ex, sameInstance(cls));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fieldMapperUsesFiltersAdded() {
|
||||
class A {
|
||||
private int a = 1;
|
||||
private int b = 2;
|
||||
private int c = 3;
|
||||
private transient int d = 4;
|
||||
private final int e = 5;
|
||||
}
|
||||
Configuration.Properties props = Configuration.Properties.builder()
|
||||
.addFilter(field -> !field.getName().equals("a"))
|
||||
.addFilter(field -> !field.getName().equals("c"))
|
||||
.build();
|
||||
|
||||
Map<String, Object> map = instanceToMap(new A(), props);
|
||||
assertThat(map.size(), is(1));
|
||||
assertThat(map, is(mapOf("b", 2)));
|
||||
|
||||
map = mapOf("a", -1, "b", -2, "c", -3, "d", -4, "e", -5);
|
||||
A a = instanceFromMap(new A(), map, props);
|
||||
assertThat(a.a, is(1));
|
||||
assertThat(a.b, is(-2));
|
||||
assertThat(a.c, is(3));
|
||||
assertThat(a.d, is(4));
|
||||
assertThat(a.e, is(5));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectFieldNameFormatterReturnsPropertiesIfNoFormatAnnotation() {
|
||||
class A extends InSharedMemoryConfiguration {
|
||||
protected A() { super(WITH_UPPER_FORMATTER); }
|
||||
}
|
||||
A a = new A();
|
||||
FieldNameFormatter fnf = FieldMapper.selectFormatter(MappingInfo.from(a));
|
||||
assertThat(fnf, sameInstance(FieldNameFormatters.UPPER_UNDERSCORE));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectFieldNameFormatterReturnsIfFormatAnnotation() {
|
||||
@Format(FieldNameFormatters.LOWER_UNDERSCORE)
|
||||
class A extends InSharedMemoryConfiguration {
|
||||
protected A() { super(WITH_UPPER_FORMATTER); }
|
||||
}
|
||||
@Format(formatterClass = MyFormatter.class)
|
||||
class B extends InSharedMemoryConfiguration {
|
||||
protected B() { super(WITH_UPPER_FORMATTER); }
|
||||
}
|
||||
A a = new A();
|
||||
FieldNameFormatter fnf = FieldMapper.selectFormatter(MappingInfo.from(a));
|
||||
assertThat(fnf, sameInstance(FieldNameFormatters.LOWER_UNDERSCORE));
|
||||
|
||||
B b = new B();
|
||||
fnf = FieldMapper.selectFormatter(MappingInfo.from(b));
|
||||
assertThat(fnf, instanceOf(MyFormatter.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void formatterClassTakesPrecedence() {
|
||||
@Format(
|
||||
value = FieldNameFormatters.LOWER_UNDERSCORE,
|
||||
formatterClass = MyFormatter.class
|
||||
)
|
||||
class A extends InSharedMemoryConfiguration {
|
||||
protected A() { super(WITH_UPPER_FORMATTER); }
|
||||
}
|
||||
A a = new A();
|
||||
FieldNameFormatter fnf = FieldMapper.selectFormatter(MappingInfo.from(a));
|
||||
assertThat(fnf, instanceOf(MyFormatter.class));
|
||||
}
|
||||
|
||||
private static final class MyFormatter implements FieldNameFormatter {
|
||||
@Override
|
||||
public String fromFieldName(String fieldName) {
|
||||
return fieldName;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void fieldMapperUsesSelectFieldNameFormatter() {
|
||||
@Format(FieldNameFormatters.LOWER_UNDERSCORE)
|
||||
class A extends InSharedMemoryConfiguration {
|
||||
private int abc = 1;
|
||||
private int eFg = 2;
|
||||
private int hIJ = 3;
|
||||
|
||||
protected A() { super(WITH_UPPER_FORMATTER); }
|
||||
}
|
||||
|
||||
A a = new A();
|
||||
MappingInfo mappingInfo = MappingInfo.from(a);
|
||||
Map<String, Object> map = FieldMapper.instanceToMap(a, mappingInfo);
|
||||
|
||||
assertThat(map.get("abc"), is(1));
|
||||
assertThat(map.get("e_fg"), is(2));
|
||||
assertThat(map.get("h_i_j"), is(3));
|
||||
|
||||
map = mapOf("abc", 4, "e_fg", 5, "h_i_j", 6);
|
||||
FieldMapper.instanceFromMap(a, map, mappingInfo);
|
||||
assertThat(a.abc, is(4));
|
||||
assertThat(a.eFg, is(5));
|
||||
assertThat(a.hIJ, is(6));
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import de.exlll.configlib.format.FieldNameFormatter;
|
||||
import de.exlll.configlib.format.FieldNameFormatters;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
class FieldNameFormattersTest {
|
||||
@Test
|
||||
void identityReturnsSameName() {
|
||||
FieldNameFormatter formatter = FieldNameFormatters.IDENTITY;
|
||||
|
||||
assertThat(formatter.fromFieldName("fieldName"), is("fieldName"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void lowerUnderscoreConvertsFromAndToCamelCase() {
|
||||
FieldNameFormatter formatter = FieldNameFormatters.LOWER_UNDERSCORE;
|
||||
|
||||
assertThat(formatter.fromFieldName("fieldNameFormat"), is("field_name_format"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void upperUnderscoreConvertsFromAndToCamelCase() {
|
||||
FieldNameFormatter formatter = FieldNameFormatters.UPPER_UNDERSCORE;
|
||||
|
||||
assertThat(formatter.fromFieldName("fieldNameFormat"), is("FIELD_NAME_FORMAT"));
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import de.exlll.configlib.classes.TestClass;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static de.exlll.configlib.util.CollectionFactory.setOf;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
class ReflectTest {
|
||||
private static final Set<Class<?>> ALL_SIMPLE_TYPES = setOf(
|
||||
boolean.class, Boolean.class,
|
||||
byte.class, Byte.class,
|
||||
char.class, Character.class,
|
||||
short.class, Short.class,
|
||||
int.class, Integer.class,
|
||||
long.class, Long.class,
|
||||
float.class, Float.class,
|
||||
double.class, Double.class,
|
||||
String.class
|
||||
);
|
||||
|
||||
@Test
|
||||
void isSimpleType() {
|
||||
for (Class<?> cls : ALL_SIMPLE_TYPES) {
|
||||
assertThat(Reflect.isSimpleType(cls), is(true));
|
||||
}
|
||||
assertThat(Reflect.isSimpleType(Object.class), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isContainerType() {
|
||||
assertThat(Reflect.isContainerType(Object.class), is(false));
|
||||
assertThat(Reflect.isContainerType(HashMap.class), is(true));
|
||||
assertThat(Reflect.isContainerType(HashSet.class), is(true));
|
||||
assertThat(Reflect.isContainerType(ArrayList.class), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getValue() throws NoSuchFieldException {
|
||||
TestClass testClass = TestClass.TEST_VALUES;
|
||||
Field f1 = TestClass.class.getDeclaredField("string");
|
||||
Field f2 = TestClass.class.getDeclaredField("primLong");
|
||||
Field f3 = TestClass.class.getDeclaredField("staticFinalInt");
|
||||
|
||||
Object value = Reflect.getValue(f1, testClass);
|
||||
assertThat(value, is(testClass.getString()));
|
||||
|
||||
value = Reflect.getValue(f2, testClass);
|
||||
assertThat(value, is(testClass.getPrimLong()));
|
||||
|
||||
value = Reflect.getValue(f3, testClass);
|
||||
assertThat(value, is(TestClass.getStaticFinalInt()));
|
||||
}
|
||||
}
|
@ -1,667 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import de.exlll.configlib.annotation.ConfigurationElement;
|
||||
import de.exlll.configlib.annotation.ElementType;
|
||||
import de.exlll.configlib.classes.TestSubClass;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentSkipListSet;
|
||||
import java.util.concurrent.CopyOnWriteArrayList;
|
||||
|
||||
import static de.exlll.configlib.FieldMapperHelpers.*;
|
||||
import static de.exlll.configlib.util.CollectionFactory.*;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class ValidatorTest {
|
||||
@Test
|
||||
void instanceFromMapRequiresMapToInitializeCustomClass() {
|
||||
class A {
|
||||
TestSubClass c = new TestSubClass();
|
||||
}
|
||||
|
||||
Map<String, Object> map = mapOf(
|
||||
"c", "s"
|
||||
);
|
||||
String msg = "Initializing field 'c' requires a Map<String, Object> " +
|
||||
"but the given object is not a map." +
|
||||
"\nType: 'String'\tValue: 's'";
|
||||
assertIfmCfgExceptionMessage(new A(), map, msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapChecksEnumValuesAreString() {
|
||||
class A {
|
||||
LocalTestEnum t = LocalTestEnum.T;
|
||||
}
|
||||
Map<String, Object> map = mapOf(
|
||||
"t", 1
|
||||
);
|
||||
String msg = "Initializing enum 't' requires a string but '1' is of " +
|
||||
"type 'Integer'.";
|
||||
assertIfmCfgExceptionMessage(new A(), map, msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapRequiresMapWithStringsAsKeys() {
|
||||
class A {
|
||||
TestSubClass c = new TestSubClass();
|
||||
}
|
||||
|
||||
Map<String, Object> map = mapOf(
|
||||
"c", mapOf(1, 200, "string", "s")
|
||||
);
|
||||
String msg = "Initializing field 'c' requires a Map<String, Object> " +
|
||||
"but the given map contains non-string keys." +
|
||||
"\nAll entries: " + map.get("c");
|
||||
assertIfmCfgExceptionMessage(new A(), map, msg);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void instanceToMapRequiresNonNullMapKeys() {
|
||||
class A {
|
||||
TestSubClass c = new TestSubClass();
|
||||
}
|
||||
Map<String, Object> m1 = new HashMap<>();
|
||||
m1.put(null, "null");
|
||||
Map<String, Object> m2 = mapOf("c", m1);
|
||||
String msg = "Initializing field 'c' requires a Map<String, Object> " +
|
||||
"but the given map contains non-string keys." +
|
||||
"\nAll entries: {null=null}";
|
||||
assertIfmCfgExceptionMessage(new A(), m2, msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapRequiresCustomClassToHaveNoArgsConstructors() {
|
||||
class A {
|
||||
Sub3 s = new Sub3(1);
|
||||
}
|
||||
Map<String, Object> map = mapOf("s", mapOf());
|
||||
String msg = "Type 'Sub3' of field 's' doesn't have a no-args constructor.";
|
||||
assertIfmCfgExceptionMessage(new A(), map, msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapRequiresCustomClassToBeConfigurationElements() {
|
||||
class A {
|
||||
Sub1 s = new Sub1();
|
||||
}
|
||||
Map<String, Object> map = mapOf("s", mapOf());
|
||||
String msg = "Type 'Sub1' of field 's' is not annotated " +
|
||||
"as a configuration element.";
|
||||
assertIfmCfgExceptionMessage(new A(), map, msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapChecksThatContainerTypesMatch() {
|
||||
class A {
|
||||
CopyOnWriteArrayList<?> l = new CopyOnWriteArrayList<>();
|
||||
}
|
||||
class B {
|
||||
ConcurrentSkipListSet<?> s = new ConcurrentSkipListSet<>();
|
||||
}
|
||||
class C {
|
||||
ConcurrentHashMap<?, ?> m = new ConcurrentHashMap<>();
|
||||
}
|
||||
Map<String, Object> m = mapOf("l", listOf("s"));
|
||||
String msg = "Can not set field 'l' with type 'CopyOnWriteArrayList' " +
|
||||
"to 'ArrayList'.";
|
||||
assertIfmCfgExceptionMessage(new A(), m, msg);
|
||||
|
||||
m = mapOf("s", setOf("s"));
|
||||
msg = "Can not set field 's' with type 'ConcurrentSkipListSet' " +
|
||||
"to 'LinkedHashSet'.";
|
||||
assertIfmCfgExceptionMessage(new B(), m, msg);
|
||||
|
||||
m = mapOf("m", mapOf(1, "s"));
|
||||
msg = "Can not set field 'm' with type 'ConcurrentHashMap' " +
|
||||
"to 'LinkedHashMap'.";
|
||||
assertIfmCfgExceptionMessage(new C(), m, msg);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapThrowsExceptionIfDefaultValueIsNull() {
|
||||
class A {
|
||||
String string;
|
||||
}
|
||||
String msg = "The value of field 'string' is null.\n" +
|
||||
"Please assign a non-null default value or remove this field.";
|
||||
assertItmCfgExceptionMessage(new A(), msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapThrowsExceptionIfDefaultValueIsNull() {
|
||||
class A {
|
||||
String string;
|
||||
}
|
||||
Map<String, Object> map = mapOf("string", "s");
|
||||
String msg = "The value of field 'string' is null.\n" +
|
||||
"Please assign a non-null default value or remove this field.";
|
||||
assertIfmCfgExceptionMessage(new A(), map, msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapRequiresListsWithoutElementTypeToContainSimpleTypes() {
|
||||
class A {
|
||||
List<TestSubClass> l = new ArrayList<>(listOf(
|
||||
TestSubClass.TEST_VALUES
|
||||
));
|
||||
}
|
||||
class B {
|
||||
List<Set<Map<Integer, TestSubClass>>> l = new ArrayList<>(listOf(
|
||||
setOf(mapOf(1, TestSubClass.TEST_VALUES))
|
||||
));
|
||||
}
|
||||
|
||||
String asString = TestSubClass.TEST_VALUES.toString();
|
||||
|
||||
A a = new A();
|
||||
String msg = "The type of an element of list 'l' is not a simple type " +
|
||||
"but list 'l' is missing the ElementType annotation.\n" +
|
||||
"All elements: [" + asString + "]";
|
||||
assertItmCfgExceptionMessage(a, msg);
|
||||
|
||||
B b = new B();
|
||||
msg = "The type of an element of list 'l' is not a simple type " +
|
||||
"but list 'l' is missing the ElementType annotation.\n" +
|
||||
"All elements: [[{1=" + asString + "}]]";
|
||||
assertItmCfgExceptionMessage(b, msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapRequiresSetsWithoutElementTypeToContainSimpleTypes() {
|
||||
class A {
|
||||
Set<TestSubClass> s = new HashSet<>(setOf(
|
||||
TestSubClass.TEST_VALUES
|
||||
));
|
||||
}
|
||||
class B {
|
||||
Set<List<Map<Integer, TestSubClass>>> s = new HashSet<>(setOf(
|
||||
listOf(mapOf(1, TestSubClass.TEST_VALUES))
|
||||
));
|
||||
}
|
||||
|
||||
String asString = TestSubClass.TEST_VALUES.toString();
|
||||
|
||||
A a = new A();
|
||||
String msg = "The type of an element of set 's' is not a simple type " +
|
||||
"but set 's' is missing the ElementType annotation.\n" +
|
||||
"All elements: [" + asString + "]";
|
||||
assertItmCfgExceptionMessage(a, msg);
|
||||
|
||||
B b = new B();
|
||||
msg = "The type of an element of set 's' is not a simple type " +
|
||||
"but set 's' is missing the ElementType annotation.\n" +
|
||||
"All elements: [[{1=" + asString + "}]]";
|
||||
assertItmCfgExceptionMessage(b, msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapRequiresMapsWithoutElementTypeToContainSimpleTypes() {
|
||||
class A {
|
||||
Map<Integer, TestSubClass> m = new HashMap<>(mapOf(
|
||||
1, TestSubClass.TEST_VALUES
|
||||
));
|
||||
}
|
||||
class B {
|
||||
Map<Integer, Set<List<TestSubClass>>> m = new HashMap<>(mapOf(
|
||||
1, setOf(listOf(TestSubClass.TEST_VALUES))
|
||||
));
|
||||
}
|
||||
|
||||
String asString = TestSubClass.TEST_VALUES.toString();
|
||||
|
||||
A a = new A();
|
||||
String msg = "The type of a value of map 'm' is not a simple type " +
|
||||
"but map 'm' is missing the ElementType annotation.\n" +
|
||||
"All entries: {1=" + asString + "}";
|
||||
assertItmCfgExceptionMessage(a, msg);
|
||||
|
||||
B b = new B();
|
||||
msg = "The type of a value of map 'm' is not a simple type " +
|
||||
"but map 'm' is missing the ElementType annotation.\n" +
|
||||
"All entries: {1=[[" + asString + "]]}";
|
||||
assertItmCfgExceptionMessage(b, msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapRequiresNonNullListElements() {
|
||||
class A {
|
||||
@ElementType(TestSubClass.class)
|
||||
List<TestSubClass> l1 = new ArrayList<>();
|
||||
List<Integer> l2 = new ArrayList<>();
|
||||
}
|
||||
A a = new A();
|
||||
a.l1.add(null);
|
||||
a.l2.add(null);
|
||||
|
||||
String msg = "An element of list 'l1' is null.\n" +
|
||||
"Please either remove or replace this element.\n" +
|
||||
"All elements: [null]";
|
||||
assertItmCfgExceptionMessage(a, msg);
|
||||
|
||||
a.l1.clear();
|
||||
msg = "An element of list 'l2' is null.\n" +
|
||||
"Please either remove or replace this element.\n" +
|
||||
"All elements: [null]";
|
||||
assertItmCfgExceptionMessage(a, msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapRequiresNonNullListElementsRecursively() {
|
||||
class A {
|
||||
@ElementType(TestSubClass.class)
|
||||
List<List<TestSubClass>> bla = new ArrayList<>();
|
||||
}
|
||||
A o = new A();
|
||||
o.bla.add(new ArrayList<>());
|
||||
o.bla.get(0).add(null);
|
||||
String msg = "An element of list 'bla' is null.\n" +
|
||||
"Please either remove or replace this element.\n" +
|
||||
"All elements: [[null]]";
|
||||
assertItmCfgExceptionMessage(o, msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapRequiresNonNullSetElements() {
|
||||
class A {
|
||||
@ElementType(TestSubClass.class)
|
||||
Set<TestSubClass> s1 = new HashSet<>();
|
||||
Set<Integer> s2 = new HashSet<>();
|
||||
}
|
||||
A a = new A();
|
||||
a.s1.add(null);
|
||||
a.s2.add(null);
|
||||
|
||||
String msg = "An element of set 's1' is null.\n" +
|
||||
"Please either remove or replace this element.\n" +
|
||||
"All elements: [null]";
|
||||
assertItmCfgExceptionMessage(a, msg);
|
||||
|
||||
a.s1.clear();
|
||||
msg = "An element of set 's2' is null.\n" +
|
||||
"Please either remove or replace this element.\n" +
|
||||
"All elements: [null]";
|
||||
assertItmCfgExceptionMessage(a, msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapRequiresNonNullSetElementsRecursively() {
|
||||
class A {
|
||||
@ElementType(TestSubClass.class)
|
||||
Set<List<TestSubClass>> bla = new HashSet<>();
|
||||
}
|
||||
A o = new A();
|
||||
o.bla.add(new ArrayList<>());
|
||||
o.bla.iterator().next().add(null);
|
||||
String msg = "An element of set 'bla' is null.\n" +
|
||||
"Please either remove or replace this element.\n" +
|
||||
"All elements: [[null]]";
|
||||
assertItmCfgExceptionMessage(o, msg);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void instanceToMapRequiresNonNullMapValues() {
|
||||
class A {
|
||||
@ElementType(TestSubClass.class)
|
||||
Map<Integer, TestSubClass> m1 = new HashMap<>();
|
||||
Map<Integer, TestSubClass> m2 = new HashMap<>();
|
||||
}
|
||||
A a = new A();
|
||||
a.m1.put(1, null);
|
||||
a.m2.put(1, null);
|
||||
|
||||
String msg = "A value of map 'm1' is null.\n" +
|
||||
"Please either remove or replace this element.\n" +
|
||||
"All entries: {1=null}";
|
||||
assertItmCfgExceptionMessage(a, msg);
|
||||
|
||||
a.m1.clear();
|
||||
msg = "A value of map 'm2' is null.\n" +
|
||||
"Please either remove or replace this element.\n" +
|
||||
"All entries: {1=null}";
|
||||
assertItmCfgExceptionMessage(a, msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapRequiresNonNullMapValuesRecursively() {
|
||||
class A {
|
||||
@ElementType(TestSubClass.class)
|
||||
Map<Integer, List<TestSubClass>> bla = new HashMap<>();
|
||||
}
|
||||
A o = new A();
|
||||
o.bla.put(1, new ArrayList<>());
|
||||
o.bla.get(1).add(null);
|
||||
String msg = "A value of map 'bla' is null.\n" +
|
||||
"Please either remove or replace this element.\n" +
|
||||
"All entries: {1=[null]}";
|
||||
assertItmCfgExceptionMessage(o, msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapRequiresSimpleMapKeys() {
|
||||
class A {
|
||||
Map<TestSubClass, Integer> m = new HashMap<>();
|
||||
}
|
||||
A a = new A();
|
||||
a.m.put(TestSubClass.TEST_VALUES, 1);
|
||||
|
||||
String msg = "The keys of map 'm' must be simple types.";
|
||||
assertItmCfgExceptionMessage(a, msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapRequiresContainerTypesToMatchElementType() {
|
||||
class A {
|
||||
@ElementType(TestSubClass.class)
|
||||
List<Integer> l = new ArrayList<>();
|
||||
@ElementType(TestSubClass.class)
|
||||
Set<Integer> s = new HashSet<>();
|
||||
@ElementType(TestSubClass.class)
|
||||
Map<Integer, Integer> m = new HashMap<>();
|
||||
}
|
||||
A a = new A();
|
||||
a.l.add(1);
|
||||
a.s.add(1);
|
||||
a.m.put(1, 1);
|
||||
|
||||
String msg = "The type of an element of list 'l' doesn't match the " +
|
||||
"type indicated by the ElementType annotation.\n" +
|
||||
"Required type: 'TestSubClass'\tActual type: 'Integer'\n" +
|
||||
"All elements: [1]";
|
||||
assertItmCfgExceptionMessage(a, msg);
|
||||
|
||||
a.l.clear();
|
||||
msg = "The type of an element of set 's' doesn't match the " +
|
||||
"type indicated by the ElementType annotation.\n" +
|
||||
"Required type: 'TestSubClass'\tActual type: 'Integer'\n" +
|
||||
"All elements: [1]";
|
||||
assertItmCfgExceptionMessage(a, msg);
|
||||
|
||||
a.s.clear();
|
||||
msg = "The type of a value of map 'm' doesn't match the " +
|
||||
"type indicated by the ElementType annotation.\n" +
|
||||
"Required type: 'TestSubClass'\tActual type: 'Integer'\n" +
|
||||
"All entries: {1=1}";
|
||||
assertItmCfgExceptionMessage(a, msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapRequiresCustomClassesToBeConfigurationElements() {
|
||||
class A {
|
||||
Sub1 s = new Sub1();
|
||||
}
|
||||
class B {
|
||||
Sub2 s = new Sub2();
|
||||
}
|
||||
|
||||
Map<String, Object> map = mapOf("s", Collections.emptyMap());
|
||||
|
||||
assertThat(instanceToMap(new B()), is(map));
|
||||
|
||||
String msg = "Type 'Sub1' of field 's' is not annotated " +
|
||||
"as a configuration element.";
|
||||
assertItmCfgExceptionMessage(new A(), msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapRequiresElementTypesToBeConcreteType() {
|
||||
class A {
|
||||
@ElementType(LocalTestInterface.class)
|
||||
List<LocalTestInterface> l = new ArrayList<>();
|
||||
}
|
||||
class B {
|
||||
@ElementType(LocalTestAbstractClass.class)
|
||||
List<LocalTestAbstractClass> l = new ArrayList<>();
|
||||
}
|
||||
class C {
|
||||
@ElementType(int.class)
|
||||
List<LocalTestAbstractClass> l = new ArrayList<>();
|
||||
}
|
||||
class D {
|
||||
@ElementType(TestSubClass[].class)
|
||||
List<TestSubClass[]> l = new ArrayList<>();
|
||||
}
|
||||
class E {
|
||||
@ElementType(LocalTestEnum.class)
|
||||
List<LocalTestEnum> l = new ArrayList<>();
|
||||
}
|
||||
Map<String, Object> m = mapOf("l", listOf());
|
||||
|
||||
String msg = "The element type of field 'l' must be a concrete class " +
|
||||
"but type 'LocalTestInterface' is an interface.";
|
||||
assertItmCfgExceptionMessage(new A(), msg);
|
||||
assertIfmCfgExceptionMessage(new A(), m, msg);
|
||||
|
||||
msg = "The element type of field 'l' must be a concrete class " +
|
||||
"but type 'LocalTestAbstractClass' is an abstract class.";
|
||||
assertItmCfgExceptionMessage(new B(), msg);
|
||||
assertIfmCfgExceptionMessage(new B(), m, msg);
|
||||
|
||||
msg = "The element type 'int' of field 'l' is not a configuration element.";
|
||||
assertItmCfgExceptionMessage(new C(), msg);
|
||||
assertIfmCfgExceptionMessage(new C(), m, msg);
|
||||
|
||||
msg = "The element type 'TestSubClass[]' of field 'l' is " +
|
||||
"not a configuration element.";
|
||||
assertItmCfgExceptionMessage(new D(), msg);
|
||||
assertIfmCfgExceptionMessage(new D(), m, msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapRequiresConfigurationElementsToHaveNoArgsConstructors() {
|
||||
@ConfigurationElement
|
||||
class Sub {
|
||||
Sub(int n) {}
|
||||
}
|
||||
|
||||
class A {
|
||||
Sub s = new Sub(2);
|
||||
}
|
||||
|
||||
String msg = "Type 'Sub' of field 's' doesn't have a no-args constructor.";
|
||||
assertItmCfgExceptionMessage(new A(), msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapRequiresElementTypesToBeConfigurationElements() {
|
||||
class A {
|
||||
@ElementType(String.class)
|
||||
List<String> l = new ArrayList<>();
|
||||
}
|
||||
String msg = "The element type 'String' of field 'l' is not a " +
|
||||
"configuration element.";
|
||||
assertItmCfgExceptionMessage(new A(), msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapRequiresElementTypesToHaveNoArgsConstructors() {
|
||||
class A {
|
||||
@ElementType(Sub3.class)
|
||||
List<Sub3> list = new ArrayList<>();
|
||||
}
|
||||
String msg = "The element type 'Sub3' of field 'list' " +
|
||||
"doesn't have a no-args constructor.";
|
||||
assertItmCfgExceptionMessage(new A(), msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToAndFromMapRequireFieldsWithElementTypeToBeContainers() {
|
||||
class A {
|
||||
@ElementType(String.class)
|
||||
String s = "";
|
||||
}
|
||||
String msg = "Field 's' is annotated with the ElementType annotation but " +
|
||||
"is not a List, Set or Map.";
|
||||
assertItmCfgExceptionMessage(new A(), msg);
|
||||
assertIfmCfgExceptionMessage(new A(), mapOf("s", ""), msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapsRequiresElementTypeToBeEnumType() {
|
||||
class A {
|
||||
@ElementType(value = TestSubClass.class, nestingLevel = 1)
|
||||
List<List<TestSubClass>> l = listOf();
|
||||
}
|
||||
Map<String, Object> map = mapOf(
|
||||
"l", listOf(listOf("Q", "V"))
|
||||
);
|
||||
ConfigurationException ex = assertIfmThrowsCfgException(new A(), map);
|
||||
Throwable cause = ex.getCause();
|
||||
|
||||
String msg = "Element type 'TestSubClass' of field 'l' is not an enum type.";
|
||||
assertThat(cause.getMessage(), is(msg));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapElementConverterRequiresObjectsOfTypeMapStringObject() {
|
||||
class A {
|
||||
@ElementType(value = TestSubClass.class, nestingLevel = 1)
|
||||
List<List<TestSubClass>> l = listOf();
|
||||
}
|
||||
Map<String, Object> map = mapOf(
|
||||
"l", listOf(listOf(1, 2))
|
||||
);
|
||||
String msg = "Field 'l' of class 'A' has a nesting level of 1 but element " +
|
||||
"'1' of type 'Integer' cannot be converted to 'TestSubClass'.";
|
||||
assertIfmCfgExceptionMessage(new A(), map, msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapRequiresCorrectNestingLevelForLists() {
|
||||
TestSubClass testValues = TestSubClass.TEST_VALUES;
|
||||
class A {
|
||||
@ElementType(TestSubClass.class)
|
||||
List<List<TestSubClass>> l1 = listOf();
|
||||
|
||||
@ElementType(TestSubClass.class)
|
||||
List<List<TestSubClass>> l2 = listOf(listOf(testValues));
|
||||
}
|
||||
class B {
|
||||
@ElementType(value = TestSubClass.class, nestingLevel = 1)
|
||||
List<List<List<TestSubClass>>> l = listOf(listOf(listOf(testValues)));
|
||||
}
|
||||
class C {
|
||||
@ElementType(value = TestSubClass.class, nestingLevel = 3)
|
||||
List<List<List<TestSubClass>>> l = listOf(listOf(listOf(testValues)));
|
||||
}
|
||||
class D {
|
||||
@ElementType(value = TestSubClass.class, nestingLevel = 1)
|
||||
List<List<TestSubClass>> l = listOf(listOf(
|
||||
TestSubClass.of(11, "11"), TestSubClass.of(12, "12"),
|
||||
TestSubClass.of(13, "13"), TestSubClass.of(14, "14")
|
||||
));
|
||||
}
|
||||
|
||||
String msg = "Field 'l2' of class 'A' has a nesting level of 0 but the " +
|
||||
"first object of type 'TestSubClass' was found on level 1.";
|
||||
assertItmCfgExceptionMessage(new A(), msg);
|
||||
|
||||
msg = "Field 'l' of class 'B' has a nesting level of 1 but the " +
|
||||
"first object of type 'TestSubClass' was found on level 2.";
|
||||
assertItmCfgExceptionMessage(new B(), msg);
|
||||
|
||||
msg = "Field 'l' of class 'C' has a nesting level of 3 but the " +
|
||||
"first object of type 'TestSubClass' was found on level 2.";
|
||||
assertItmCfgExceptionMessage(new C(), msg);
|
||||
|
||||
Map<String, Object> map = instanceToMap(new D());
|
||||
D d = instanceFromMap(new D(), map);
|
||||
assertThat(d.l, is(new D().l));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void instanceToMapRequiresCorrectNestingLevelForMaps() {
|
||||
TestSubClass testValues = TestSubClass.TEST_VALUES;
|
||||
class A {
|
||||
@ElementType(TestSubClass.class)
|
||||
Map<String, Map<String, TestSubClass>> m1 = mapOf();
|
||||
|
||||
@ElementType(TestSubClass.class)
|
||||
Map<Integer, Map<String, TestSubClass>> m2 = mapOf(
|
||||
1, mapOf("1", TestSubClass.of(11, "11")),
|
||||
2, mapOf("2", TestSubClass.of(12, "12"))
|
||||
);
|
||||
}
|
||||
class B {
|
||||
@ElementType(value = TestSubClass.class, nestingLevel = 1)
|
||||
Map<String, Map<String, Map<String, TestSubClass>>> m = mapOf(
|
||||
"1", mapOf("2", mapOf("3", testValues)),
|
||||
"1", mapOf("2", mapOf("3", testValues))
|
||||
);
|
||||
}
|
||||
class C {
|
||||
@ElementType(value = TestSubClass.class, nestingLevel = 3)
|
||||
Map<String, Map<String, Map<String, TestSubClass>>> m = mapOf(
|
||||
"1", mapOf("2", mapOf("3", testValues)),
|
||||
"1", mapOf("2", mapOf("3", testValues))
|
||||
);
|
||||
}
|
||||
class D {
|
||||
@ElementType(value = TestSubClass.class, nestingLevel = 1)
|
||||
Map<Integer, Map<String, TestSubClass>> m = mapOf(
|
||||
1, mapOf("1", TestSubClass.of(11, "11")),
|
||||
2, mapOf("2", TestSubClass.of(12, "12")),
|
||||
3, mapOf("3", TestSubClass.of(13, "13")),
|
||||
4, mapOf("4", TestSubClass.of(14, "14"))
|
||||
);
|
||||
}
|
||||
|
||||
String msg = "Field 'm2' of class 'A' has a nesting level of 0 but the " +
|
||||
"first object of type 'TestSubClass' was found on level 1.";
|
||||
assertItmCfgExceptionMessage(new A(), msg);
|
||||
|
||||
msg = "Field 'm' of class 'B' has a nesting level of 1 but the " +
|
||||
"first object of type 'TestSubClass' was found on level 2.";
|
||||
assertItmCfgExceptionMessage(new B(), msg);
|
||||
|
||||
msg = "Field 'm' of class 'C' has a nesting level of 3 but the " +
|
||||
"first object of type 'TestSubClass' was found on level 2.";
|
||||
assertItmCfgExceptionMessage(new C(), msg);
|
||||
|
||||
Map<String, Object> map = instanceToMap(new D());
|
||||
D d = instanceFromMap(new D(), map);
|
||||
assertThat(d.m, is(new D().m));
|
||||
}
|
||||
|
||||
/* The case that the nestingLevel is set to high cannot properly be detected. */
|
||||
@Test
|
||||
void instanceFromMapRequiresCorrectNestingLevelForLists() {
|
||||
class A {
|
||||
@ElementType(TestSubClass.class)
|
||||
List<TestSubClass> l = listOf();
|
||||
}
|
||||
class B {
|
||||
@ElementType(LocalTestEnum.class)
|
||||
List<LocalTestEnum> l = listOf();
|
||||
}
|
||||
class C {
|
||||
@ElementType(TestSubClass.class)
|
||||
List<List<TestSubClass>> l = listOf();
|
||||
}
|
||||
class D {
|
||||
@ElementType(LocalTestEnum.class)
|
||||
List<List<LocalTestEnum>> l = listOf();
|
||||
}
|
||||
Map<String, Object> m = TestSubClass.TEST_VALUES.asMap();
|
||||
instanceFromMap(new A(), mapOf("l", listOf(m)));
|
||||
instanceFromMap(new B(), mapOf("l", listOf("S", "T")));
|
||||
|
||||
String elementAsString = m.toString();
|
||||
String msg = "Field 'l' of class 'C' has a nesting level of 0 but element '[" +
|
||||
elementAsString + "]' of type 'ArrayList' cannot be converted " +
|
||||
"to 'TestSubClass'.";
|
||||
assertIfmCfgExceptionMessage(new C(), mapOf("l", listOf(listOf(m))), msg);
|
||||
|
||||
msg = "Field 'l' of class 'D' has a nesting level of 0 but element '[S, T]' of type " +
|
||||
"'ArrayList' cannot be converted to 'LocalTestEnum'.";
|
||||
assertIfmCfgExceptionMessage(new D(), mapOf("l", listOf(listOf("S", "T"))), msg);
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package de.exlll.configlib.classes;
|
||||
|
||||
public final class ClassWithFinalStaticTransientField {
|
||||
private final int i = 0;
|
||||
private static int j;
|
||||
private transient int k;
|
||||
}
|
@ -1,482 +0,0 @@
|
||||
package de.exlll.configlib.classes;
|
||||
|
||||
import de.exlll.configlib.annotation.Comment;
|
||||
import de.exlll.configlib.annotation.Convert;
|
||||
import de.exlll.configlib.annotation.ElementType;
|
||||
import de.exlll.configlib.annotation.NoConvert;
|
||||
import de.exlll.configlib.configs.yaml.YamlConfiguration;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
|
||||
import static de.exlll.configlib.util.CollectionFactory.*;
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
@Comment({"A", "", "B", "C"})
|
||||
public final class TestClass extends YamlConfiguration {
|
||||
public static final TestClass TEST_VALUES;
|
||||
|
||||
public enum TestEnum {
|
||||
DEFAULT, NON_DEFAULT
|
||||
}
|
||||
|
||||
static {
|
||||
TEST_VALUES = new TestClass();
|
||||
TEST_VALUES.primBool = true;
|
||||
TEST_VALUES.refBool = true;
|
||||
TEST_VALUES.primByte = 1;
|
||||
TEST_VALUES.refByte = 2;
|
||||
TEST_VALUES.primChar = 'c';
|
||||
TEST_VALUES.refChar = 'd';
|
||||
TEST_VALUES.primShort = 3;
|
||||
TEST_VALUES.refShort = 4;
|
||||
TEST_VALUES.primInt = 5;
|
||||
TEST_VALUES.refInt = 6;
|
||||
TEST_VALUES.primLong = 7;
|
||||
TEST_VALUES.refLong = 8L;
|
||||
TEST_VALUES.primFloat = 9.0f;
|
||||
TEST_VALUES.refFloat = 10.0f;
|
||||
TEST_VALUES.primDouble = 11.0;
|
||||
TEST_VALUES.refDouble = 12.0;
|
||||
TEST_VALUES.string = "string";
|
||||
/* other types */
|
||||
TEST_VALUES.subClass = TestSubClass.TEST_VALUES;
|
||||
/* containers of simple types */
|
||||
TEST_VALUES.ints = setOf(1, 2, 3);
|
||||
TEST_VALUES.strings = listOf("a", "b", "c");
|
||||
TEST_VALUES.doubleByBool = mapOf(true, 1.0, false, 2.0);
|
||||
/* containers of other types */
|
||||
TEST_VALUES.subClassSet = setOf(
|
||||
TestSubClass.of(1, "1"),
|
||||
TestSubClass.of(2, "2")
|
||||
);
|
||||
TEST_VALUES.subClassList = listOf(
|
||||
TestSubClass.of(3, "3"),
|
||||
TestSubClass.of(4, "4")
|
||||
);
|
||||
TEST_VALUES.subClassMap = mapOf(
|
||||
"5", TestSubClass.of(5, "5"),
|
||||
"6", TestSubClass.of(6, "6")
|
||||
);
|
||||
/* nested containers of simple types */
|
||||
TEST_VALUES.listsList = listOf(
|
||||
listOf(1, 2), listOf(3, 4)
|
||||
);
|
||||
TEST_VALUES.setsSet = setOf(
|
||||
setOf("a", "b"), setOf("c", "d")
|
||||
);
|
||||
TEST_VALUES.mapsMap = mapOf(
|
||||
1, mapOf("1", 1), 2, mapOf("2", 2)
|
||||
);
|
||||
/* nested containers of custom types */
|
||||
TEST_VALUES.subClassListsList = listOf(
|
||||
listOf(TestSubClass.of(7, "7"), TestSubClass.of(8, "8"))
|
||||
);
|
||||
TEST_VALUES.subClassSetsSet = setOf(setOf(
|
||||
TestSubClass.of(9, "9"), TestSubClass.of(10, "10")
|
||||
));
|
||||
TEST_VALUES.subClassMapsMap = mapOf(
|
||||
1, mapOf("1", TestSubClass.of(11, "11")),
|
||||
2, mapOf("2", TestSubClass.of(12, "12"))
|
||||
);
|
||||
TEST_VALUES.e1 = TestEnum.NON_DEFAULT;
|
||||
TEST_VALUES.enums = listOf(TestEnum.DEFAULT, TestEnum.NON_DEFAULT);
|
||||
TEST_VALUES.converterSubClass = TestSubClass.of(13, "13");
|
||||
TEST_VALUES.excludedClass = TestExcludedClass.TEST_VALUES;
|
||||
}
|
||||
|
||||
/* not converted */
|
||||
private static final int staticFinalInt = 1;
|
||||
private static int staticInt = 2;
|
||||
private final int finalInt = 3;
|
||||
private transient int transientInt = 4;
|
||||
/* simple types */
|
||||
@Comment({"A"})
|
||||
private boolean primBool;
|
||||
@Comment({"B", "C"})
|
||||
private Boolean refBool = false;
|
||||
@Comment({"D", "", "E"})
|
||||
private byte primByte;
|
||||
private Byte refByte = 0;
|
||||
@Comment("F")
|
||||
private char primChar;
|
||||
@Comment({"", "G"})
|
||||
private Character refChar = '\0';
|
||||
private short primShort;
|
||||
private Short refShort = 0;
|
||||
private int primInt;
|
||||
private Integer refInt = 0;
|
||||
private long primLong;
|
||||
private Long refLong = 0L;
|
||||
private float primFloat;
|
||||
private Float refFloat = 0F;
|
||||
private double primDouble;
|
||||
private Double refDouble = 0.0;
|
||||
private String string = "";
|
||||
/* other types */
|
||||
private TestSubClass subClass = new TestSubClass();
|
||||
/* containers of simple types */
|
||||
private Set<Integer> ints = new HashSet<>();
|
||||
private List<String> strings = new ArrayList<>();
|
||||
private Map<Boolean, Double> doubleByBool = new HashMap<>();
|
||||
/* containers of other types */
|
||||
@ElementType(TestSubClass.class)
|
||||
private Set<TestSubClass> subClassSet = new HashSet<>();
|
||||
@ElementType(TestSubClass.class)
|
||||
private List<TestSubClass> subClassList = new ArrayList<>();
|
||||
@ElementType(TestSubClass.class)
|
||||
private Map<String, TestSubClass> subClassMap = new HashMap<>();
|
||||
/* nested containers of simple types */
|
||||
private List<List<Integer>> listsList = new ArrayList<>();
|
||||
private Set<Set<String>> setsSet = new HashSet<>();
|
||||
private Map<Integer, Map<String, Integer>> mapsMap = new HashMap<>();
|
||||
/* nested containers of custom types */
|
||||
@ElementType(value = TestSubClass.class, nestingLevel = 1)
|
||||
private List<List<TestSubClass>> subClassListsList = new ArrayList<>();
|
||||
@ElementType(value = TestSubClass.class, nestingLevel = 1)
|
||||
private Set<Set<TestSubClass>> subClassSetsSet = new HashSet<>();
|
||||
@ElementType(value = TestSubClass.class, nestingLevel = 1)
|
||||
private Map<Integer, Map<String, TestSubClass>> subClassMapsMap
|
||||
= new HashMap<>();
|
||||
private TestEnum e1 = TestEnum.DEFAULT;
|
||||
@ElementType(TestEnum.class)
|
||||
private List<TestEnum> enums = new ArrayList<>();
|
||||
@Convert(TestSubClassConverter.class)
|
||||
private TestSubClass converterSubClass = new TestSubClass();
|
||||
@NoConvert
|
||||
private TestExcludedClass excludedClass = new TestExcludedClass();
|
||||
|
||||
public TestClass(Path path, YamlProperties properties) {
|
||||
super(path, properties);
|
||||
}
|
||||
|
||||
public TestClass(Path path) {
|
||||
super(path);
|
||||
}
|
||||
|
||||
public TestClass() {
|
||||
this(Paths.get(""), YamlProperties.DEFAULT);
|
||||
}
|
||||
|
||||
public TestClass(Path configPath, TestClass other) {
|
||||
this(configPath);
|
||||
this.transientInt = other.transientInt;
|
||||
this.primBool = other.primBool;
|
||||
this.refBool = other.refBool;
|
||||
this.primByte = other.primByte;
|
||||
this.refByte = other.refByte;
|
||||
this.primChar = other.primChar;
|
||||
this.refChar = other.refChar;
|
||||
this.primShort = other.primShort;
|
||||
this.refShort = other.refShort;
|
||||
this.primInt = other.primInt;
|
||||
this.refInt = other.refInt;
|
||||
this.primLong = other.primLong;
|
||||
this.refLong = other.refLong;
|
||||
this.primFloat = other.primFloat;
|
||||
this.refFloat = other.refFloat;
|
||||
this.primDouble = other.primDouble;
|
||||
this.refDouble = other.refDouble;
|
||||
this.string = other.string;
|
||||
this.subClass = other.subClass;
|
||||
this.ints = other.ints;
|
||||
this.strings = other.strings;
|
||||
this.doubleByBool = other.doubleByBool;
|
||||
this.subClassSet = other.subClassSet;
|
||||
this.subClassList = other.subClassList;
|
||||
this.subClassMap = other.subClassMap;
|
||||
this.listsList = other.listsList;
|
||||
this.setsSet = other.setsSet;
|
||||
this.mapsMap = other.mapsMap;
|
||||
this.subClassListsList = other.subClassListsList;
|
||||
this.subClassSetsSet = other.subClassSetsSet;
|
||||
this.subClassMapsMap = other.subClassMapsMap;
|
||||
this.e1 = other.e1;
|
||||
this.enums = other.enums;
|
||||
this.converterSubClass = other.converterSubClass;
|
||||
this.excludedClass = other.excludedClass;
|
||||
}
|
||||
|
||||
public static int getStaticFinalInt() {
|
||||
return staticFinalInt;
|
||||
}
|
||||
|
||||
public static int getStaticInt() {
|
||||
return staticInt;
|
||||
}
|
||||
|
||||
public int getFinalInt() {
|
||||
return finalInt;
|
||||
}
|
||||
|
||||
public int getTransientInt() {
|
||||
return transientInt;
|
||||
}
|
||||
|
||||
public boolean getPrimBool() {
|
||||
return primBool;
|
||||
}
|
||||
|
||||
public Boolean getRefBool() {
|
||||
return refBool;
|
||||
}
|
||||
|
||||
public byte getPrimByte() {
|
||||
return primByte;
|
||||
}
|
||||
|
||||
public Byte getRefByte() {
|
||||
return refByte;
|
||||
}
|
||||
|
||||
public char getPrimChar() {
|
||||
return primChar;
|
||||
}
|
||||
|
||||
public Character getRefChar() {
|
||||
return refChar;
|
||||
}
|
||||
|
||||
public short getPrimShort() {
|
||||
return primShort;
|
||||
}
|
||||
|
||||
public Short getRefShort() {
|
||||
return refShort;
|
||||
}
|
||||
|
||||
public int getPrimInt() {
|
||||
return primInt;
|
||||
}
|
||||
|
||||
public Integer getRefInt() {
|
||||
return refInt;
|
||||
}
|
||||
|
||||
public long getPrimLong() {
|
||||
return primLong;
|
||||
}
|
||||
|
||||
public Long getRefLong() {
|
||||
return refLong;
|
||||
}
|
||||
|
||||
public float getPrimFloat() {
|
||||
return primFloat;
|
||||
}
|
||||
|
||||
public Float getRefFloat() {
|
||||
return refFloat;
|
||||
}
|
||||
|
||||
public double getPrimDouble() {
|
||||
return primDouble;
|
||||
}
|
||||
|
||||
public Double getRefDouble() {
|
||||
return refDouble;
|
||||
}
|
||||
|
||||
public String getString() {
|
||||
return string;
|
||||
}
|
||||
|
||||
public TestSubClass getSubClass() {
|
||||
return subClass;
|
||||
}
|
||||
|
||||
public Set<Integer> getInts() {
|
||||
return ints;
|
||||
}
|
||||
|
||||
public List<String> getStrings() {
|
||||
return strings;
|
||||
}
|
||||
|
||||
public Map<Boolean, Double> getDoubleByBool() {
|
||||
return doubleByBool;
|
||||
}
|
||||
|
||||
public Set<TestSubClass> getSubClassSet() {
|
||||
return subClassSet;
|
||||
}
|
||||
|
||||
public List<TestSubClass> getSubClassList() {
|
||||
return subClassList;
|
||||
}
|
||||
|
||||
public Map<String, TestSubClass> getSubClassMap() {
|
||||
return subClassMap;
|
||||
}
|
||||
|
||||
public List<List<Integer>> getListsList() {
|
||||
return listsList;
|
||||
}
|
||||
|
||||
public Set<Set<String>> getSetsSet() {
|
||||
return setsSet;
|
||||
}
|
||||
|
||||
public Map<Integer, Map<String, Integer>> getMapsMap() {
|
||||
return mapsMap;
|
||||
}
|
||||
|
||||
public List<List<TestSubClass>> getSubClassListsList() {
|
||||
return subClassListsList;
|
||||
}
|
||||
|
||||
public Set<Set<TestSubClass>> getSubClassSetsSet() {
|
||||
return subClassSetsSet;
|
||||
}
|
||||
|
||||
public Map<Integer, Map<String, TestSubClass>> getSubClassMapsMap() {
|
||||
return subClassMapsMap;
|
||||
}
|
||||
|
||||
public TestEnum getE1() {
|
||||
return e1;
|
||||
}
|
||||
|
||||
public List<TestEnum> getEnums() {
|
||||
return enums;
|
||||
}
|
||||
|
||||
public TestSubClass getConverterSubClass() {
|
||||
return converterSubClass;
|
||||
}
|
||||
|
||||
public TestExcludedClass getExcludedClass() {
|
||||
return excludedClass;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof TestClass)) return false;
|
||||
|
||||
TestClass testClass = (TestClass) o;
|
||||
|
||||
if (finalInt != testClass.finalInt) return false;
|
||||
if (transientInt != testClass.transientInt) return false;
|
||||
if (primBool != testClass.primBool) return false;
|
||||
if (primByte != testClass.primByte) return false;
|
||||
if (primChar != testClass.primChar) return false;
|
||||
if (primShort != testClass.primShort) return false;
|
||||
if (primInt != testClass.primInt) return false;
|
||||
if (primLong != testClass.primLong) return false;
|
||||
if (Float.compare(testClass.primFloat, primFloat) != 0) return false;
|
||||
if (Double.compare(testClass.primDouble, primDouble) != 0) return false;
|
||||
if (!refBool.equals(testClass.refBool)) return false;
|
||||
if (!refByte.equals(testClass.refByte)) return false;
|
||||
if (!refChar.equals(testClass.refChar)) return false;
|
||||
if (!refShort.equals(testClass.refShort)) return false;
|
||||
if (!refInt.equals(testClass.refInt)) return false;
|
||||
if (!refLong.equals(testClass.refLong)) return false;
|
||||
if (!refFloat.equals(testClass.refFloat)) return false;
|
||||
if (!refDouble.equals(testClass.refDouble)) return false;
|
||||
if (!string.equals(testClass.string)) return false;
|
||||
if (!subClass.equals(testClass.subClass)) return false;
|
||||
if (!ints.equals(testClass.ints)) return false;
|
||||
if (!strings.equals(testClass.strings)) return false;
|
||||
if (!doubleByBool.equals(testClass.doubleByBool)) return false;
|
||||
if (!subClassSet.equals(testClass.subClassSet)) return false;
|
||||
if (!subClassList.equals(testClass.subClassList)) return false;
|
||||
if (!subClassMap.equals(testClass.subClassMap)) return false;
|
||||
if (!listsList.equals(testClass.listsList)) return false;
|
||||
if (!setsSet.equals(testClass.setsSet)) return false;
|
||||
if (!mapsMap.equals(testClass.mapsMap)) return false;
|
||||
if (!subClassListsList.equals(testClass.subClassListsList)) return false;
|
||||
if (!subClassSetsSet.equals(testClass.subClassSetsSet)) return false;
|
||||
if (e1 != testClass.e1) return false;
|
||||
if (!enums.equals(testClass.enums)) return false;
|
||||
if (!converterSubClass.equals(testClass.converterSubClass)) return false;
|
||||
if (!excludedClass.equals(testClass.excludedClass)) return false;
|
||||
return subClassMapsMap.equals(testClass.subClassMapsMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result;
|
||||
long temp;
|
||||
result = finalInt;
|
||||
result = 31 * result + transientInt;
|
||||
result = 31 * result + (primBool ? 1 : 0);
|
||||
result = 31 * result + refBool.hashCode();
|
||||
result = 31 * result + (int) primByte;
|
||||
result = 31 * result + refByte.hashCode();
|
||||
result = 31 * result + (int) primChar;
|
||||
result = 31 * result + refChar.hashCode();
|
||||
result = 31 * result + (int) primShort;
|
||||
result = 31 * result + refShort.hashCode();
|
||||
result = 31 * result + primInt;
|
||||
result = 31 * result + refInt.hashCode();
|
||||
result = 31 * result + (int) (primLong ^ (primLong >>> 32));
|
||||
result = 31 * result + refLong.hashCode();
|
||||
result = 31 * result + (primFloat != +0.0f ? Float.floatToIntBits(
|
||||
primFloat) : 0);
|
||||
result = 31 * result + refFloat.hashCode();
|
||||
temp = Double.doubleToLongBits(primDouble);
|
||||
result = 31 * result + (int) (temp ^ (temp >>> 32));
|
||||
result = 31 * result + refDouble.hashCode();
|
||||
result = 31 * result + string.hashCode();
|
||||
result = 31 * result + subClass.hashCode();
|
||||
result = 31 * result + ints.hashCode();
|
||||
result = 31 * result + strings.hashCode();
|
||||
result = 31 * result + doubleByBool.hashCode();
|
||||
result = 31 * result + subClassSet.hashCode();
|
||||
result = 31 * result + subClassList.hashCode();
|
||||
result = 31 * result + subClassMap.hashCode();
|
||||
result = 31 * result + listsList.hashCode();
|
||||
result = 31 * result + setsSet.hashCode();
|
||||
result = 31 * result + mapsMap.hashCode();
|
||||
result = 31 * result + subClassListsList.hashCode();
|
||||
result = 31 * result + subClassSetsSet.hashCode();
|
||||
result = 31 * result + subClassMapsMap.hashCode();
|
||||
result = 31 * result + e1.hashCode();
|
||||
result = 31 * result + enums.hashCode();
|
||||
result = 31 * result + converterSubClass.hashCode();
|
||||
result = 31 * result + excludedClass.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TestClass{" +
|
||||
"\nprimBool=" + primBool +
|
||||
",\nrefBool=" + refBool +
|
||||
",\nprimByte=" + primByte +
|
||||
",\nrefByte=" + refByte +
|
||||
",\nprimChar=" + primChar +
|
||||
",\nrefChar=" + refChar +
|
||||
",\nprimShort=" + primShort +
|
||||
",\nrefShort=" + refShort +
|
||||
",\nprimInt=" + primInt +
|
||||
",\nrefInt=" + refInt +
|
||||
",\nprimLong=" + primLong +
|
||||
",\nrefLong=" + refLong +
|
||||
",\nprimFloat=" + primFloat +
|
||||
",\nrefFloat=" + refFloat +
|
||||
",\nprimDouble=" + primDouble +
|
||||
",\nrefDouble=" + refDouble +
|
||||
",\nstring='" + string + '\'' +
|
||||
",\nsubClass=" + subClass +
|
||||
",\nints=" + ints +
|
||||
",\nstrings=" + strings +
|
||||
",\ndoubleByBool=" + doubleByBool +
|
||||
",\nsubClassSet=" + subClassSet +
|
||||
",\nsubClassList=" + subClassList +
|
||||
",\nsubClassMap=" + subClassMap +
|
||||
",\nlistsList=" + listsList +
|
||||
",\nsetsSet=" + setsSet +
|
||||
",\nmapsMap=" + mapsMap +
|
||||
",\nsubClassListsList=" + subClassListsList +
|
||||
",\nsubClassSetsSet=" + subClassSetsSet +
|
||||
",\nsubClassMapsMap=" + subClassMapsMap +
|
||||
",\ne1=" + e1 +
|
||||
",\nenums=" + enums +
|
||||
",\nconverterSubClass=" + converterSubClass +
|
||||
",\nexcludedClass=" + excludedClass +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -1,55 +0,0 @@
|
||||
package de.exlll.configlib.classes;
|
||||
|
||||
public class TestExcludedClass {
|
||||
public static final TestExcludedClass TEST_VALUES;
|
||||
private int primInt;
|
||||
private String string = "";
|
||||
|
||||
static {
|
||||
TEST_VALUES = new TestExcludedClass();
|
||||
TEST_VALUES.primInt = 1;
|
||||
TEST_VALUES.string = "string";
|
||||
}
|
||||
|
||||
public int getPrimInt() {
|
||||
return primInt;
|
||||
}
|
||||
|
||||
public String getString() {
|
||||
return string;
|
||||
}
|
||||
|
||||
public void setPrimInt(int primInt) {
|
||||
this.primInt = primInt;
|
||||
}
|
||||
|
||||
public void setString(String string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
TestExcludedClass that = (TestExcludedClass) o;
|
||||
|
||||
if (primInt != that.primInt) return false;
|
||||
return string.equals(that.string);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = primInt;
|
||||
result = 31 * result + string.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TestExcludedClass{" +
|
||||
"primInt=" + primInt +
|
||||
", string='" + string + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -1,149 +0,0 @@
|
||||
package de.exlll.configlib.classes;
|
||||
|
||||
import de.exlll.configlib.annotation.ConfigurationElement;
|
||||
import de.exlll.configlib.annotation.ElementType;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static de.exlll.configlib.util.CollectionFactory.*;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
import static java.util.stream.Collectors.toSet;
|
||||
|
||||
@SuppressWarnings("FieldCanBeLocal")
|
||||
@ConfigurationElement
|
||||
public final class TestSubClass {
|
||||
public static final TestSubClass TEST_VALUES;
|
||||
|
||||
static {
|
||||
TEST_VALUES = new TestSubClass();
|
||||
TEST_VALUES.primInt = 1;
|
||||
TEST_VALUES.string = "string";
|
||||
TEST_VALUES.list = listOf("list");
|
||||
TEST_VALUES.set = setOf("set");
|
||||
TEST_VALUES.map = mapOf("map", 1);
|
||||
TEST_VALUES.testSubSubClass = TestSubSubClass.of(14, "14");
|
||||
TEST_VALUES.subClassList = listOf(
|
||||
TestSubSubClass.of(15, "15"), TestSubSubClass.of(16, "16")
|
||||
);
|
||||
TEST_VALUES.subClassSet = setOf(
|
||||
TestSubSubClass.of(17, "17"), TestSubSubClass.of(18, "18")
|
||||
);
|
||||
TEST_VALUES.subClassMap = mapOf("map", TestSubSubClass.of(19, "19"));
|
||||
}
|
||||
|
||||
private final int finalInt = 1;
|
||||
private int primInt;
|
||||
private String string = "";
|
||||
private List<String> list = Collections.emptyList();
|
||||
private Set<String> set = Collections.emptySet();
|
||||
private Map<String, Integer> map = Collections.emptyMap();
|
||||
private TestSubSubClass testSubSubClass = new TestSubSubClass();
|
||||
@ElementType(TestSubSubClass.class)
|
||||
private List<TestSubSubClass> subClassList = Collections.emptyList();
|
||||
@ElementType(TestSubSubClass.class)
|
||||
private Set<TestSubSubClass> subClassSet = Collections.emptySet();
|
||||
@ElementType(TestSubSubClass.class)
|
||||
private Map<String, TestSubSubClass> subClassMap = Collections.emptyMap();
|
||||
|
||||
public static TestSubClass of(int primInt, String string) {
|
||||
TestSubClass cls = new TestSubClass();
|
||||
cls.primInt = primInt;
|
||||
cls.string = string;
|
||||
cls.list = listOf(string);
|
||||
cls.set = setOf(string);
|
||||
cls.map = mapOf(string, primInt);
|
||||
cls.testSubSubClass = TestSubSubClass.of(primInt, string);
|
||||
cls.subClassList = listOf(
|
||||
TestSubSubClass.of(primInt * 100, string)
|
||||
);
|
||||
cls.subClassSet = setOf(
|
||||
TestSubSubClass.of(primInt * 101, string)
|
||||
);
|
||||
cls.subClassMap = mapOf(string, TestSubSubClass.of(primInt * 102, string));
|
||||
return cls;
|
||||
}
|
||||
|
||||
public Map<String, Object> asMap() {
|
||||
Map<String, Object> asMap = mapOf("primInt", primInt, "string", string);
|
||||
asMap.put("list", list);
|
||||
asMap.put("set", set);
|
||||
asMap.put("map", map);
|
||||
asMap.put("testSubSubClass", testSubSubClass.asMap());
|
||||
asMap.put(
|
||||
"subClassList", subClassList.stream()
|
||||
.map(TestSubSubClass::asMap)
|
||||
.collect(toList())
|
||||
);
|
||||
asMap.put(
|
||||
"subClassSet", subClassSet.stream()
|
||||
.map(TestSubSubClass::asMap)
|
||||
.collect(toSet())
|
||||
);
|
||||
asMap.put(
|
||||
"subClassMap", subClassMap.entrySet().stream()
|
||||
.map(e -> mapEntry(e.getKey(), e.getValue().asMap()))
|
||||
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue))
|
||||
);
|
||||
return asMap;
|
||||
}
|
||||
|
||||
public int getPrimInt() {
|
||||
return primInt;
|
||||
}
|
||||
|
||||
public String getString() {
|
||||
return string;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TestSubClass{" +
|
||||
"primInt=" + primInt +
|
||||
", string='" + string + '\'' +
|
||||
", list=" + list +
|
||||
", set=" + set +
|
||||
", map=" + map +
|
||||
", testSubSubClass=" + testSubSubClass +
|
||||
", subClassList=" + subClassList +
|
||||
", subClassSet=" + subClassSet +
|
||||
", subClassMap=" + subClassMap +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
TestSubClass that = (TestSubClass) o;
|
||||
|
||||
if (primInt != that.primInt) return false;
|
||||
if (!string.equals(that.string)) return false;
|
||||
if (!list.equals(that.list)) return false;
|
||||
if (!set.equals(that.set)) return false;
|
||||
if (!map.equals(that.map)) return false;
|
||||
if (!testSubSubClass.equals(that.testSubSubClass)) return false;
|
||||
if (!subClassList.equals(that.subClassList)) return false;
|
||||
if (!subClassSet.equals(that.subClassSet)) return false;
|
||||
return subClassMap.equals(that.subClassMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = finalInt;
|
||||
result = 31 * result + primInt;
|
||||
result = 31 * result + string.hashCode();
|
||||
result = 31 * result + list.hashCode();
|
||||
result = 31 * result + set.hashCode();
|
||||
result = 31 * result + map.hashCode();
|
||||
result = 31 * result + testSubSubClass.hashCode();
|
||||
result = 31 * result + subClassList.hashCode();
|
||||
result = 31 * result + subClassSet.hashCode();
|
||||
result = 31 * result + subClassMap.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package de.exlll.configlib.classes;
|
||||
|
||||
import de.exlll.configlib.Converter;
|
||||
|
||||
public final class TestSubClassConverter
|
||||
implements Converter<TestSubClass, String> {
|
||||
|
||||
@Override
|
||||
public String convertTo(TestSubClass element, ConversionInfo info) {
|
||||
return element.getPrimInt() + ":" + element.getString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public TestSubClass convertFrom(String element, ConversionInfo info) {
|
||||
String[] split = element.split(":");
|
||||
return TestSubClass.of(Integer.parseInt(split[0]), split[1]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TestSubClassConverter";
|
||||
}
|
||||
}
|
@ -1,83 +0,0 @@
|
||||
package de.exlll.configlib.classes;
|
||||
|
||||
import de.exlll.configlib.annotation.ConfigurationElement;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static de.exlll.configlib.util.CollectionFactory.listOf;
|
||||
import static de.exlll.configlib.util.CollectionFactory.mapOf;
|
||||
import static de.exlll.configlib.util.CollectionFactory.setOf;
|
||||
|
||||
@ConfigurationElement
|
||||
public final class TestSubSubClass {
|
||||
public static final TestSubSubClass TEST_VALUES;
|
||||
|
||||
static {
|
||||
TEST_VALUES = new TestSubSubClass();
|
||||
TEST_VALUES.primInt = 1;
|
||||
TEST_VALUES.string = "string";
|
||||
TEST_VALUES.list = listOf("list");
|
||||
TEST_VALUES.set = setOf("set");
|
||||
TEST_VALUES.map = mapOf("map", 1);
|
||||
}
|
||||
|
||||
private int primInt;
|
||||
private String string = "";
|
||||
private List<String> list = listOf();
|
||||
private Set<String> set = setOf();
|
||||
private Map<String, Integer> map = mapOf();
|
||||
|
||||
public static TestSubSubClass of(int primInt, String string) {
|
||||
TestSubSubClass cls = new TestSubSubClass();
|
||||
cls.primInt = primInt;
|
||||
cls.string = string;
|
||||
String concat = string + string;
|
||||
cls.list = listOf(concat);
|
||||
cls.set = setOf(concat);
|
||||
cls.map = mapOf(concat, primInt);
|
||||
return cls;
|
||||
}
|
||||
|
||||
public Map<String, Object> asMap() {
|
||||
Map<String, Object> asMap = mapOf("primInt", primInt, "string", string);
|
||||
asMap.put("list", list);
|
||||
asMap.put("set", set);
|
||||
asMap.put("map", map);
|
||||
return asMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TestSubSubClass{" +
|
||||
"primInt=" + primInt +
|
||||
", string='" + string + '\'' +
|
||||
", list=" + list +
|
||||
", set=" + set +
|
||||
", map=" + map +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
TestSubSubClass that = (TestSubSubClass) o;
|
||||
|
||||
if (primInt != that.primInt) return false;
|
||||
if (!string.equals(that.string)) return false;
|
||||
if (!list.equals(that.list)) return false;
|
||||
if (!set.equals(that.set)) return false;
|
||||
return map.equals(that.map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = primInt;
|
||||
result = 31 * result + string.hashCode();
|
||||
result = 31 * result + list.hashCode();
|
||||
result = 31 * result + set.hashCode();
|
||||
result = 31 * result + map.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
@ -1,45 +0,0 @@
|
||||
package de.exlll.configlib.configs.mem;
|
||||
|
||||
import de.exlll.configlib.Configuration;
|
||||
import de.exlll.configlib.ConfigurationSource;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public class InSharedMemoryConfiguration
|
||||
extends Configuration<InSharedMemoryConfiguration> {
|
||||
private final InSharedMemorySource source = new InSharedMemorySource();
|
||||
|
||||
protected InSharedMemoryConfiguration(Properties properties) {
|
||||
super(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ConfigurationSource<InSharedMemoryConfiguration> getSource() {
|
||||
return source;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected InSharedMemoryConfiguration getThis() {
|
||||
return this;
|
||||
}
|
||||
|
||||
private static final class InSharedMemorySource implements
|
||||
ConfigurationSource<InSharedMemoryConfiguration> {
|
||||
private static Map<String, Object> map;
|
||||
|
||||
@Override
|
||||
public Map<String, Object> loadConfiguration(
|
||||
InSharedMemoryConfiguration config
|
||||
) {
|
||||
return Objects.requireNonNull(map);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveConfiguration(
|
||||
InSharedMemoryConfiguration config, Map<String, Object> map
|
||||
) {
|
||||
InSharedMemorySource.map = map;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,941 +0,0 @@
|
||||
package de.exlll.configlib.configs.yaml;
|
||||
|
||||
import com.google.common.jimfs.Jimfs;
|
||||
import de.exlll.configlib.Configuration;
|
||||
import de.exlll.configlib.annotation.Comment;
|
||||
import de.exlll.configlib.classes.TestClass;
|
||||
import de.exlll.configlib.configs.yaml.YamlConfiguration.YamlProperties;
|
||||
import de.exlll.configlib.format.FieldNameFormatters;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static de.exlll.configlib.util.CollectionFactory.listOf;
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
class YamlConfigurationTest {
|
||||
private FileSystem fileSystem;
|
||||
private Path testPath, configPath;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
fileSystem = Jimfs.newFileSystem();
|
||||
testPath = fileSystem.getPath("/a/b/test.yml");
|
||||
configPath = fileSystem.getPath("/a/b/config.yml");
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws IOException {
|
||||
fileSystem.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadAndSaveExecutesPostLoadHook() {
|
||||
class A extends YamlConfiguration {
|
||||
int i = 0;
|
||||
|
||||
protected A() { super(configPath, YamlProperties.DEFAULT); }
|
||||
|
||||
@Override
|
||||
protected void postLoad() {
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
A a = new A();
|
||||
a.loadAndSave();
|
||||
assertThat(a.i, is(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadAndSaveSavesConfiguration() {
|
||||
YamlConfiguration configuration = new TestClass(
|
||||
configPath, TestClass.TEST_VALUES
|
||||
);
|
||||
configuration.loadAndSave();
|
||||
assertThat(Files.exists(configPath), is(true));
|
||||
|
||||
YamlConfiguration load = new TestClass(configPath);
|
||||
load.load();
|
||||
assertThat(load, is(TestClass.TEST_VALUES));
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadAndSaveLoadsConfiguration() {
|
||||
new TestClass(configPath, TestClass.TEST_VALUES).save();
|
||||
|
||||
YamlConfiguration configuration = new TestClass(configPath);
|
||||
configuration.loadAndSave();
|
||||
assertThat(configuration, is(TestClass.TEST_VALUES));
|
||||
assertThat(Files.exists(configPath), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadLoadsConfig() {
|
||||
setupConfigPath();
|
||||
Configuration configuration = new TestClass(configPath);
|
||||
assertThat(configuration, is(not(TestClass.TEST_VALUES)));
|
||||
configuration.load();
|
||||
assertThat(configuration, is((TestClass.TEST_VALUES)));
|
||||
}
|
||||
|
||||
private void setupConfigPath() {
|
||||
Configuration configuration = new TestClass(
|
||||
configPath, TestClass.TEST_VALUES
|
||||
);
|
||||
configuration.save();
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadThrowsExceptionIfTypesDoNotMatch() {
|
||||
Configuration configuration = new TestClass(configPath);
|
||||
configuration.save();
|
||||
assertThrows(IllegalArgumentException.class, configuration::load);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveCreatesConfig() {
|
||||
assertThat(Files.exists(testPath), is(false));
|
||||
Configuration configuration = new TestClass(testPath);
|
||||
configuration.save();
|
||||
assertThat(Files.exists(testPath), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveDumpsYaml() throws IOException {
|
||||
Configuration configuration = new TestClass(
|
||||
testPath, TestClass.TEST_VALUES
|
||||
);
|
||||
configuration.save();
|
||||
assertThat(readConfig(testPath), is(TEST_CLASS_YML));
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveDumpsPrependedAndAppendedComments() throws IOException {
|
||||
class A extends YamlConfiguration {
|
||||
int i;
|
||||
|
||||
protected A(YamlProperties properties) {
|
||||
super(testPath, properties);
|
||||
}
|
||||
}
|
||||
YamlProperties properties = YamlProperties.builder()
|
||||
.setPrependedComments(listOf("AB", "", "CD"))
|
||||
.setAppendedComments(listOf("AB", "", "CD"))
|
||||
.build();
|
||||
new A(properties).save();
|
||||
assertThat(readConfig(testPath), is(PRE_AND_APPENDED_COMMENTS_YML));
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveDumpsClassComments() throws IOException {
|
||||
@Comment({"1", "", "2"})
|
||||
class A extends YamlConfiguration {
|
||||
@Comment("a")
|
||||
private int a = 1;
|
||||
@Comment({"b", "x"})
|
||||
private int b = 2;
|
||||
@Comment({"c", "", "y"})
|
||||
private int c = 3;
|
||||
private int d = 4;
|
||||
|
||||
protected A() { super(testPath); }
|
||||
}
|
||||
new A().save();
|
||||
assertThat(readConfig(testPath), is(CLASS_COMMENTS_YML));
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveDumpsFieldComments() throws IOException {
|
||||
class A extends YamlConfiguration {
|
||||
@Comment("a")
|
||||
private int a = 1;
|
||||
@Comment({"b", "x"})
|
||||
private int b = 2;
|
||||
@Comment({"c", "", "y"})
|
||||
private int c = 3;
|
||||
private int d = 4;
|
||||
|
||||
protected A() { super(testPath); }
|
||||
}
|
||||
new A().save();
|
||||
assertThat(readConfig(testPath), is(FIELD_COMMENTS_YML));
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveDumpsFormattedFieldComments() throws IOException {
|
||||
class A extends YamlConfiguration {
|
||||
@Comment("aB")
|
||||
private int aB = 1;
|
||||
@Comment({"cD", "dC"})
|
||||
private int cD = 2;
|
||||
|
||||
protected A(YamlProperties properties) {
|
||||
super(testPath, properties);
|
||||
}
|
||||
}
|
||||
|
||||
YamlProperties properties = YamlProperties.builder()
|
||||
.setFormatter(FieldNameFormatters.LOWER_UNDERSCORE)
|
||||
.build();
|
||||
|
||||
new A(properties).save();
|
||||
assertThat(readConfig(testPath), is(FORMATTED_FIELD_COMMENTS_YML));
|
||||
}
|
||||
|
||||
private String readConfig(Path path) throws IOException {
|
||||
return Files.lines(path).collect(joining("\n"));
|
||||
}
|
||||
|
||||
private static final String PRE_AND_APPENDED_COMMENTS_YML = "# AB\n" +
|
||||
"\n" +
|
||||
"# CD\n" +
|
||||
"i: 0\n" +
|
||||
"# AB\n" +
|
||||
"\n" +
|
||||
"# CD";
|
||||
|
||||
private static final String FIELD_COMMENTS_YML = "# a\n" +
|
||||
"a: 1\n" +
|
||||
"# b\n" +
|
||||
"# x\n" +
|
||||
"b: 2\n" +
|
||||
"# c\n" +
|
||||
"\n" +
|
||||
"# y\n" +
|
||||
"c: 3\n" +
|
||||
"d: 4";
|
||||
|
||||
private static final String FORMATTED_FIELD_COMMENTS_YML = "# aB\n" +
|
||||
"a_b: 1\n" +
|
||||
"# cD\n" +
|
||||
"# dC\n" +
|
||||
"c_d: 2";
|
||||
|
||||
private static final String CLASS_COMMENTS_YML = "# 1\n" +
|
||||
"\n" +
|
||||
"# 2\n" +
|
||||
"# a\n" +
|
||||
"a: 1\n" +
|
||||
"# b\n" +
|
||||
"# x\n" +
|
||||
"b: 2\n" +
|
||||
"# c\n" +
|
||||
"\n" +
|
||||
"# y\n" +
|
||||
"c: 3\n" +
|
||||
"d: 4";
|
||||
|
||||
private static final String TEST_CLASS_YML = "# A\n" +
|
||||
"\n" +
|
||||
"# B\n" +
|
||||
"# C\n" +
|
||||
"# A\n" +
|
||||
"primBool: true\n" +
|
||||
"# B\n" +
|
||||
"# C\n" +
|
||||
"refBool: true\n" +
|
||||
"# D\n" +
|
||||
"\n" +
|
||||
"# E\n" +
|
||||
"primByte: 1\n" +
|
||||
"refByte: 2\n" +
|
||||
"# F\n" +
|
||||
"primChar: c\n" +
|
||||
"\n" +
|
||||
"# G\n" +
|
||||
"refChar: d\n" +
|
||||
"primShort: 3\n" +
|
||||
"refShort: 4\n" +
|
||||
"primInt: 5\n" +
|
||||
"refInt: 6\n" +
|
||||
"primLong: 7\n" +
|
||||
"refLong: 8\n" +
|
||||
"primFloat: 9.0\n" +
|
||||
"refFloat: 10.0\n" +
|
||||
"primDouble: 11.0\n" +
|
||||
"refDouble: 12.0\n" +
|
||||
"string: string\n" +
|
||||
"subClass:\n" +
|
||||
" primInt: 1\n" +
|
||||
" string: string\n" +
|
||||
" list:\n" +
|
||||
" - list\n" +
|
||||
" set: !!set\n" +
|
||||
" set: null\n" +
|
||||
" map:\n" +
|
||||
" map: 1\n" +
|
||||
" testSubSubClass:\n" +
|
||||
" primInt: 14\n" +
|
||||
" string: '14'\n" +
|
||||
" list:\n" +
|
||||
" - '1414'\n" +
|
||||
" set: !!set\n" +
|
||||
" '1414': null\n" +
|
||||
" map:\n" +
|
||||
" '1414': 14\n" +
|
||||
" subClassList:\n" +
|
||||
" - primInt: 15\n" +
|
||||
" string: '15'\n" +
|
||||
" list:\n" +
|
||||
" - '1515'\n" +
|
||||
" set: !!set\n" +
|
||||
" '1515': null\n" +
|
||||
" map:\n" +
|
||||
" '1515': 15\n" +
|
||||
" - primInt: 16\n" +
|
||||
" string: '16'\n" +
|
||||
" list:\n" +
|
||||
" - '1616'\n" +
|
||||
" set: !!set\n" +
|
||||
" '1616': null\n" +
|
||||
" map:\n" +
|
||||
" '1616': 16\n" +
|
||||
" subClassSet: !!set\n" +
|
||||
" ? primInt: 18\n" +
|
||||
" string: '18'\n" +
|
||||
" list:\n" +
|
||||
" - '1818'\n" +
|
||||
" set: !!set\n" +
|
||||
" '1818': null\n" +
|
||||
" map:\n" +
|
||||
" '1818': 18\n" +
|
||||
" : null\n" +
|
||||
" ? primInt: 17\n" +
|
||||
" string: '17'\n" +
|
||||
" list:\n" +
|
||||
" - '1717'\n" +
|
||||
" set: !!set\n" +
|
||||
" '1717': null\n" +
|
||||
" map:\n" +
|
||||
" '1717': 17\n" +
|
||||
" : null\n" +
|
||||
" subClassMap:\n" +
|
||||
" map:\n" +
|
||||
" primInt: 19\n" +
|
||||
" string: '19'\n" +
|
||||
" list:\n" +
|
||||
" - '1919'\n" +
|
||||
" set: !!set\n" +
|
||||
" '1919': null\n" +
|
||||
" map:\n" +
|
||||
" '1919': 19\n" +
|
||||
"ints: !!set\n" +
|
||||
" 1: null\n" +
|
||||
" 2: null\n" +
|
||||
" 3: null\n" +
|
||||
"strings:\n" +
|
||||
"- a\n" +
|
||||
"- b\n" +
|
||||
"- c\n" +
|
||||
"doubleByBool:\n" +
|
||||
" true: 1.0\n" +
|
||||
" false: 2.0\n" +
|
||||
"subClassSet: !!set\n" +
|
||||
" ? primInt: 1\n" +
|
||||
" string: '1'\n" +
|
||||
" list:\n" +
|
||||
" - '1'\n" +
|
||||
" set: !!set\n" +
|
||||
" '1': null\n" +
|
||||
" map:\n" +
|
||||
" '1': 1\n" +
|
||||
" testSubSubClass:\n" +
|
||||
" primInt: 1\n" +
|
||||
" string: '1'\n" +
|
||||
" list:\n" +
|
||||
" - '11'\n" +
|
||||
" set: !!set\n" +
|
||||
" '11': null\n" +
|
||||
" map:\n" +
|
||||
" '11': 1\n" +
|
||||
" subClassList:\n" +
|
||||
" - primInt: 100\n" +
|
||||
" string: '1'\n" +
|
||||
" list:\n" +
|
||||
" - '11'\n" +
|
||||
" set: !!set\n" +
|
||||
" '11': null\n" +
|
||||
" map:\n" +
|
||||
" '11': 100\n" +
|
||||
" subClassSet: !!set\n" +
|
||||
" ? primInt: 101\n" +
|
||||
" string: '1'\n" +
|
||||
" list:\n" +
|
||||
" - '11'\n" +
|
||||
" set: !!set\n" +
|
||||
" '11': null\n" +
|
||||
" map:\n" +
|
||||
" '11': 101\n" +
|
||||
" : null\n" +
|
||||
" subClassMap:\n" +
|
||||
" '1':\n" +
|
||||
" primInt: 102\n" +
|
||||
" string: '1'\n" +
|
||||
" list:\n" +
|
||||
" - '11'\n" +
|
||||
" set: !!set\n" +
|
||||
" '11': null\n" +
|
||||
" map:\n" +
|
||||
" '11': 102\n" +
|
||||
" : null\n" +
|
||||
" ? primInt: 2\n" +
|
||||
" string: '2'\n" +
|
||||
" list:\n" +
|
||||
" - '2'\n" +
|
||||
" set: !!set\n" +
|
||||
" '2': null\n" +
|
||||
" map:\n" +
|
||||
" '2': 2\n" +
|
||||
" testSubSubClass:\n" +
|
||||
" primInt: 2\n" +
|
||||
" string: '2'\n" +
|
||||
" list:\n" +
|
||||
" - '22'\n" +
|
||||
" set: !!set\n" +
|
||||
" '22': null\n" +
|
||||
" map:\n" +
|
||||
" '22': 2\n" +
|
||||
" subClassList:\n" +
|
||||
" - primInt: 200\n" +
|
||||
" string: '2'\n" +
|
||||
" list:\n" +
|
||||
" - '22'\n" +
|
||||
" set: !!set\n" +
|
||||
" '22': null\n" +
|
||||
" map:\n" +
|
||||
" '22': 200\n" +
|
||||
" subClassSet: !!set\n" +
|
||||
" ? primInt: 202\n" +
|
||||
" string: '2'\n" +
|
||||
" list:\n" +
|
||||
" - '22'\n" +
|
||||
" set: !!set\n" +
|
||||
" '22': null\n" +
|
||||
" map:\n" +
|
||||
" '22': 202\n" +
|
||||
" : null\n" +
|
||||
" subClassMap:\n" +
|
||||
" '2':\n" +
|
||||
" primInt: 204\n" +
|
||||
" string: '2'\n" +
|
||||
" list:\n" +
|
||||
" - '22'\n" +
|
||||
" set: !!set\n" +
|
||||
" '22': null\n" +
|
||||
" map:\n" +
|
||||
" '22': 204\n" +
|
||||
" : null\n" +
|
||||
"subClassList:\n" +
|
||||
"- primInt: 3\n" +
|
||||
" string: '3'\n" +
|
||||
" list:\n" +
|
||||
" - '3'\n" +
|
||||
" set: !!set\n" +
|
||||
" '3': null\n" +
|
||||
" map:\n" +
|
||||
" '3': 3\n" +
|
||||
" testSubSubClass:\n" +
|
||||
" primInt: 3\n" +
|
||||
" string: '3'\n" +
|
||||
" list:\n" +
|
||||
" - '33'\n" +
|
||||
" set: !!set\n" +
|
||||
" '33': null\n" +
|
||||
" map:\n" +
|
||||
" '33': 3\n" +
|
||||
" subClassList:\n" +
|
||||
" - primInt: 300\n" +
|
||||
" string: '3'\n" +
|
||||
" list:\n" +
|
||||
" - '33'\n" +
|
||||
" set: !!set\n" +
|
||||
" '33': null\n" +
|
||||
" map:\n" +
|
||||
" '33': 300\n" +
|
||||
" subClassSet: !!set\n" +
|
||||
" ? primInt: 303\n" +
|
||||
" string: '3'\n" +
|
||||
" list:\n" +
|
||||
" - '33'\n" +
|
||||
" set: !!set\n" +
|
||||
" '33': null\n" +
|
||||
" map:\n" +
|
||||
" '33': 303\n" +
|
||||
" : null\n" +
|
||||
" subClassMap:\n" +
|
||||
" '3':\n" +
|
||||
" primInt: 306\n" +
|
||||
" string: '3'\n" +
|
||||
" list:\n" +
|
||||
" - '33'\n" +
|
||||
" set: !!set\n" +
|
||||
" '33': null\n" +
|
||||
" map:\n" +
|
||||
" '33': 306\n" +
|
||||
"- primInt: 4\n" +
|
||||
" string: '4'\n" +
|
||||
" list:\n" +
|
||||
" - '4'\n" +
|
||||
" set: !!set\n" +
|
||||
" '4': null\n" +
|
||||
" map:\n" +
|
||||
" '4': 4\n" +
|
||||
" testSubSubClass:\n" +
|
||||
" primInt: 4\n" +
|
||||
" string: '4'\n" +
|
||||
" list:\n" +
|
||||
" - '44'\n" +
|
||||
" set: !!set\n" +
|
||||
" '44': null\n" +
|
||||
" map:\n" +
|
||||
" '44': 4\n" +
|
||||
" subClassList:\n" +
|
||||
" - primInt: 400\n" +
|
||||
" string: '4'\n" +
|
||||
" list:\n" +
|
||||
" - '44'\n" +
|
||||
" set: !!set\n" +
|
||||
" '44': null\n" +
|
||||
" map:\n" +
|
||||
" '44': 400\n" +
|
||||
" subClassSet: !!set\n" +
|
||||
" ? primInt: 404\n" +
|
||||
" string: '4'\n" +
|
||||
" list:\n" +
|
||||
" - '44'\n" +
|
||||
" set: !!set\n" +
|
||||
" '44': null\n" +
|
||||
" map:\n" +
|
||||
" '44': 404\n" +
|
||||
" : null\n" +
|
||||
" subClassMap:\n" +
|
||||
" '4':\n" +
|
||||
" primInt: 408\n" +
|
||||
" string: '4'\n" +
|
||||
" list:\n" +
|
||||
" - '44'\n" +
|
||||
" set: !!set\n" +
|
||||
" '44': null\n" +
|
||||
" map:\n" +
|
||||
" '44': 408\n" +
|
||||
"subClassMap:\n" +
|
||||
" '5':\n" +
|
||||
" primInt: 5\n" +
|
||||
" string: '5'\n" +
|
||||
" list:\n" +
|
||||
" - '5'\n" +
|
||||
" set: !!set\n" +
|
||||
" '5': null\n" +
|
||||
" map:\n" +
|
||||
" '5': 5\n" +
|
||||
" testSubSubClass:\n" +
|
||||
" primInt: 5\n" +
|
||||
" string: '5'\n" +
|
||||
" list:\n" +
|
||||
" - '55'\n" +
|
||||
" set: !!set\n" +
|
||||
" '55': null\n" +
|
||||
" map:\n" +
|
||||
" '55': 5\n" +
|
||||
" subClassList:\n" +
|
||||
" - primInt: 500\n" +
|
||||
" string: '5'\n" +
|
||||
" list:\n" +
|
||||
" - '55'\n" +
|
||||
" set: !!set\n" +
|
||||
" '55': null\n" +
|
||||
" map:\n" +
|
||||
" '55': 500\n" +
|
||||
" subClassSet: !!set\n" +
|
||||
" ? primInt: 505\n" +
|
||||
" string: '5'\n" +
|
||||
" list:\n" +
|
||||
" - '55'\n" +
|
||||
" set: !!set\n" +
|
||||
" '55': null\n" +
|
||||
" map:\n" +
|
||||
" '55': 505\n" +
|
||||
" : null\n" +
|
||||
" subClassMap:\n" +
|
||||
" '5':\n" +
|
||||
" primInt: 510\n" +
|
||||
" string: '5'\n" +
|
||||
" list:\n" +
|
||||
" - '55'\n" +
|
||||
" set: !!set\n" +
|
||||
" '55': null\n" +
|
||||
" map:\n" +
|
||||
" '55': 510\n" +
|
||||
" '6':\n" +
|
||||
" primInt: 6\n" +
|
||||
" string: '6'\n" +
|
||||
" list:\n" +
|
||||
" - '6'\n" +
|
||||
" set: !!set\n" +
|
||||
" '6': null\n" +
|
||||
" map:\n" +
|
||||
" '6': 6\n" +
|
||||
" testSubSubClass:\n" +
|
||||
" primInt: 6\n" +
|
||||
" string: '6'\n" +
|
||||
" list:\n" +
|
||||
" - '66'\n" +
|
||||
" set: !!set\n" +
|
||||
" '66': null\n" +
|
||||
" map:\n" +
|
||||
" '66': 6\n" +
|
||||
" subClassList:\n" +
|
||||
" - primInt: 600\n" +
|
||||
" string: '6'\n" +
|
||||
" list:\n" +
|
||||
" - '66'\n" +
|
||||
" set: !!set\n" +
|
||||
" '66': null\n" +
|
||||
" map:\n" +
|
||||
" '66': 600\n" +
|
||||
" subClassSet: !!set\n" +
|
||||
" ? primInt: 606\n" +
|
||||
" string: '6'\n" +
|
||||
" list:\n" +
|
||||
" - '66'\n" +
|
||||
" set: !!set\n" +
|
||||
" '66': null\n" +
|
||||
" map:\n" +
|
||||
" '66': 606\n" +
|
||||
" : null\n" +
|
||||
" subClassMap:\n" +
|
||||
" '6':\n" +
|
||||
" primInt: 612\n" +
|
||||
" string: '6'\n" +
|
||||
" list:\n" +
|
||||
" - '66'\n" +
|
||||
" set: !!set\n" +
|
||||
" '66': null\n" +
|
||||
" map:\n" +
|
||||
" '66': 612\n" +
|
||||
"listsList:\n" +
|
||||
"- - 1\n" +
|
||||
" - 2\n" +
|
||||
"- - 3\n" +
|
||||
" - 4\n" +
|
||||
"setsSet: !!set\n" +
|
||||
" ? !!set\n" +
|
||||
" a: null\n" +
|
||||
" b: null\n" +
|
||||
" : null\n" +
|
||||
" ? !!set\n" +
|
||||
" c: null\n" +
|
||||
" d: null\n" +
|
||||
" : null\n" +
|
||||
"mapsMap:\n" +
|
||||
" 1:\n" +
|
||||
" '1': 1\n" +
|
||||
" 2:\n" +
|
||||
" '2': 2\n" +
|
||||
"subClassListsList:\n" +
|
||||
"- - primInt: 7\n" +
|
||||
" string: '7'\n" +
|
||||
" list:\n" +
|
||||
" - '7'\n" +
|
||||
" set: !!set\n" +
|
||||
" '7': null\n" +
|
||||
" map:\n" +
|
||||
" '7': 7\n" +
|
||||
" testSubSubClass:\n" +
|
||||
" primInt: 7\n" +
|
||||
" string: '7'\n" +
|
||||
" list:\n" +
|
||||
" - '77'\n" +
|
||||
" set: !!set\n" +
|
||||
" '77': null\n" +
|
||||
" map:\n" +
|
||||
" '77': 7\n" +
|
||||
" subClassList:\n" +
|
||||
" - primInt: 700\n" +
|
||||
" string: '7'\n" +
|
||||
" list:\n" +
|
||||
" - '77'\n" +
|
||||
" set: !!set\n" +
|
||||
" '77': null\n" +
|
||||
" map:\n" +
|
||||
" '77': 700\n" +
|
||||
" subClassSet: !!set\n" +
|
||||
" ? primInt: 707\n" +
|
||||
" string: '7'\n" +
|
||||
" list:\n" +
|
||||
" - '77'\n" +
|
||||
" set: !!set\n" +
|
||||
" '77': null\n" +
|
||||
" map:\n" +
|
||||
" '77': 707\n" +
|
||||
" : null\n" +
|
||||
" subClassMap:\n" +
|
||||
" '7':\n" +
|
||||
" primInt: 714\n" +
|
||||
" string: '7'\n" +
|
||||
" list:\n" +
|
||||
" - '77'\n" +
|
||||
" set: !!set\n" +
|
||||
" '77': null\n" +
|
||||
" map:\n" +
|
||||
" '77': 714\n" +
|
||||
" - primInt: 8\n" +
|
||||
" string: '8'\n" +
|
||||
" list:\n" +
|
||||
" - '8'\n" +
|
||||
" set: !!set\n" +
|
||||
" '8': null\n" +
|
||||
" map:\n" +
|
||||
" '8': 8\n" +
|
||||
" testSubSubClass:\n" +
|
||||
" primInt: 8\n" +
|
||||
" string: '8'\n" +
|
||||
" list:\n" +
|
||||
" - '88'\n" +
|
||||
" set: !!set\n" +
|
||||
" '88': null\n" +
|
||||
" map:\n" +
|
||||
" '88': 8\n" +
|
||||
" subClassList:\n" +
|
||||
" - primInt: 800\n" +
|
||||
" string: '8'\n" +
|
||||
" list:\n" +
|
||||
" - '88'\n" +
|
||||
" set: !!set\n" +
|
||||
" '88': null\n" +
|
||||
" map:\n" +
|
||||
" '88': 800\n" +
|
||||
" subClassSet: !!set\n" +
|
||||
" ? primInt: 808\n" +
|
||||
" string: '8'\n" +
|
||||
" list:\n" +
|
||||
" - '88'\n" +
|
||||
" set: !!set\n" +
|
||||
" '88': null\n" +
|
||||
" map:\n" +
|
||||
" '88': 808\n" +
|
||||
" : null\n" +
|
||||
" subClassMap:\n" +
|
||||
" '8':\n" +
|
||||
" primInt: 816\n" +
|
||||
" string: '8'\n" +
|
||||
" list:\n" +
|
||||
" - '88'\n" +
|
||||
" set: !!set\n" +
|
||||
" '88': null\n" +
|
||||
" map:\n" +
|
||||
" '88': 816\n" +
|
||||
"subClassSetsSet: !!set\n" +
|
||||
" ? !!set\n" +
|
||||
" ? primInt: 10\n" +
|
||||
" string: '10'\n" +
|
||||
" list:\n" +
|
||||
" - '10'\n" +
|
||||
" set: !!set\n" +
|
||||
" '10': null\n" +
|
||||
" map:\n" +
|
||||
" '10': 10\n" +
|
||||
" testSubSubClass:\n" +
|
||||
" primInt: 10\n" +
|
||||
" string: '10'\n" +
|
||||
" list:\n" +
|
||||
" - '1010'\n" +
|
||||
" set: !!set\n" +
|
||||
" '1010': null\n" +
|
||||
" map:\n" +
|
||||
" '1010': 10\n" +
|
||||
" subClassList:\n" +
|
||||
" - primInt: 1000\n" +
|
||||
" string: '10'\n" +
|
||||
" list:\n" +
|
||||
" - '1010'\n" +
|
||||
" set: !!set\n" +
|
||||
" '1010': null\n" +
|
||||
" map:\n" +
|
||||
" '1010': 1000\n" +
|
||||
" subClassSet: !!set\n" +
|
||||
" ? primInt: 1010\n" +
|
||||
" string: '10'\n" +
|
||||
" list:\n" +
|
||||
" - '1010'\n" +
|
||||
" set: !!set\n" +
|
||||
" '1010': null\n" +
|
||||
" map:\n" +
|
||||
" '1010': 1010\n" +
|
||||
" : null\n" +
|
||||
" subClassMap:\n" +
|
||||
" '10':\n" +
|
||||
" primInt: 1020\n" +
|
||||
" string: '10'\n" +
|
||||
" list:\n" +
|
||||
" - '1010'\n" +
|
||||
" set: !!set\n" +
|
||||
" '1010': null\n" +
|
||||
" map:\n" +
|
||||
" '1010': 1020\n" +
|
||||
" : null\n" +
|
||||
" ? primInt: 9\n" +
|
||||
" string: '9'\n" +
|
||||
" list:\n" +
|
||||
" - '9'\n" +
|
||||
" set: !!set\n" +
|
||||
" '9': null\n" +
|
||||
" map:\n" +
|
||||
" '9': 9\n" +
|
||||
" testSubSubClass:\n" +
|
||||
" primInt: 9\n" +
|
||||
" string: '9'\n" +
|
||||
" list:\n" +
|
||||
" - '99'\n" +
|
||||
" set: !!set\n" +
|
||||
" '99': null\n" +
|
||||
" map:\n" +
|
||||
" '99': 9\n" +
|
||||
" subClassList:\n" +
|
||||
" - primInt: 900\n" +
|
||||
" string: '9'\n" +
|
||||
" list:\n" +
|
||||
" - '99'\n" +
|
||||
" set: !!set\n" +
|
||||
" '99': null\n" +
|
||||
" map:\n" +
|
||||
" '99': 900\n" +
|
||||
" subClassSet: !!set\n" +
|
||||
" ? primInt: 909\n" +
|
||||
" string: '9'\n" +
|
||||
" list:\n" +
|
||||
" - '99'\n" +
|
||||
" set: !!set\n" +
|
||||
" '99': null\n" +
|
||||
" map:\n" +
|
||||
" '99': 909\n" +
|
||||
" : null\n" +
|
||||
" subClassMap:\n" +
|
||||
" '9':\n" +
|
||||
" primInt: 918\n" +
|
||||
" string: '9'\n" +
|
||||
" list:\n" +
|
||||
" - '99'\n" +
|
||||
" set: !!set\n" +
|
||||
" '99': null\n" +
|
||||
" map:\n" +
|
||||
" '99': 918\n" +
|
||||
" : null\n" +
|
||||
" : null\n" +
|
||||
"subClassMapsMap:\n" +
|
||||
" 1:\n" +
|
||||
" '1':\n" +
|
||||
" primInt: 11\n" +
|
||||
" string: '11'\n" +
|
||||
" list:\n" +
|
||||
" - '11'\n" +
|
||||
" set: !!set\n" +
|
||||
" '11': null\n" +
|
||||
" map:\n" +
|
||||
" '11': 11\n" +
|
||||
" testSubSubClass:\n" +
|
||||
" primInt: 11\n" +
|
||||
" string: '11'\n" +
|
||||
" list:\n" +
|
||||
" - '1111'\n" +
|
||||
" set: !!set\n" +
|
||||
" '1111': null\n" +
|
||||
" map:\n" +
|
||||
" '1111': 11\n" +
|
||||
" subClassList:\n" +
|
||||
" - primInt: 1100\n" +
|
||||
" string: '11'\n" +
|
||||
" list:\n" +
|
||||
" - '1111'\n" +
|
||||
" set: !!set\n" +
|
||||
" '1111': null\n" +
|
||||
" map:\n" +
|
||||
" '1111': 1100\n" +
|
||||
" subClassSet: !!set\n" +
|
||||
" ? primInt: 1111\n" +
|
||||
" string: '11'\n" +
|
||||
" list:\n" +
|
||||
" - '1111'\n" +
|
||||
" set: !!set\n" +
|
||||
" '1111': null\n" +
|
||||
" map:\n" +
|
||||
" '1111': 1111\n" +
|
||||
" : null\n" +
|
||||
" subClassMap:\n" +
|
||||
" '11':\n" +
|
||||
" primInt: 1122\n" +
|
||||
" string: '11'\n" +
|
||||
" list:\n" +
|
||||
" - '1111'\n" +
|
||||
" set: !!set\n" +
|
||||
" '1111': null\n" +
|
||||
" map:\n" +
|
||||
" '1111': 1122\n" +
|
||||
" 2:\n" +
|
||||
" '2':\n" +
|
||||
" primInt: 12\n" +
|
||||
" string: '12'\n" +
|
||||
" list:\n" +
|
||||
" - '12'\n" +
|
||||
" set: !!set\n" +
|
||||
" '12': null\n" +
|
||||
" map:\n" +
|
||||
" '12': 12\n" +
|
||||
" testSubSubClass:\n" +
|
||||
" primInt: 12\n" +
|
||||
" string: '12'\n" +
|
||||
" list:\n" +
|
||||
" - '1212'\n" +
|
||||
" set: !!set\n" +
|
||||
" '1212': null\n" +
|
||||
" map:\n" +
|
||||
" '1212': 12\n" +
|
||||
" subClassList:\n" +
|
||||
" - primInt: 1200\n" +
|
||||
" string: '12'\n" +
|
||||
" list:\n" +
|
||||
" - '1212'\n" +
|
||||
" set: !!set\n" +
|
||||
" '1212': null\n" +
|
||||
" map:\n" +
|
||||
" '1212': 1200\n" +
|
||||
" subClassSet: !!set\n" +
|
||||
" ? primInt: 1212\n" +
|
||||
" string: '12'\n" +
|
||||
" list:\n" +
|
||||
" - '1212'\n" +
|
||||
" set: !!set\n" +
|
||||
" '1212': null\n" +
|
||||
" map:\n" +
|
||||
" '1212': 1212\n" +
|
||||
" : null\n" +
|
||||
" subClassMap:\n" +
|
||||
" '12':\n" +
|
||||
" primInt: 1224\n" +
|
||||
" string: '12'\n" +
|
||||
" list:\n" +
|
||||
" - '1212'\n" +
|
||||
" set: !!set\n" +
|
||||
" '1212': null\n" +
|
||||
" map:\n" +
|
||||
" '1212': 1224\n" +
|
||||
"e1: NON_DEFAULT\n" +
|
||||
"enums:\n" +
|
||||
"- DEFAULT\n" +
|
||||
"- NON_DEFAULT\n" +
|
||||
"converterSubClass: '13:13'\n" +
|
||||
"excludedClass: !!de.exlll.configlib.classes.TestExcludedClass\n" +
|
||||
" primInt: 1\n" +
|
||||
" string: string";
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
package de.exlll.configlib.configs.yaml;
|
||||
|
||||
import com.google.common.jimfs.Jimfs;
|
||||
import de.exlll.configlib.classes.TestClass;
|
||||
import de.exlll.configlib.configs.yaml.YamlConfiguration.YamlProperties;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static de.exlll.configlib.util.CollectionFactory.mapOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
class YamlSourceTest {
|
||||
private FileSystem fileSystem;
|
||||
private Path configPath;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
fileSystem = Jimfs.newFileSystem();
|
||||
configPath = fileSystem.getPath("/a/b/config.yml");
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws IOException {
|
||||
fileSystem.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void yamlSourceCreatesDirectories() throws IOException {
|
||||
YamlSource source = new YamlSource(configPath, YamlProperties.DEFAULT);
|
||||
Path parentDir = configPath.getParent();
|
||||
assertThat(Files.exists(parentDir), is(false));
|
||||
source.saveConfiguration(new TestClass(configPath), mapOf());
|
||||
assertThat(Files.exists(parentDir), is(true));
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
package de.exlll.configlib.filter;
|
||||
|
||||
import de.exlll.configlib.classes.ClassWithFinalStaticTransientField;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.List;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
class FieldFilterTest {
|
||||
private static final FieldFilter filter = FieldFilters.DEFAULT;
|
||||
private static final Class<ClassWithFinalStaticTransientField> CWFSTF =
|
||||
ClassWithFinalStaticTransientField.class;
|
||||
|
||||
@Test
|
||||
void filteredFieldsFiltersFields() throws NoSuchFieldException {
|
||||
List<? extends Field> fields = filter.filterDeclaredFieldsOf(CWFSTF);
|
||||
assertThat(fields.size(), is(0));
|
||||
|
||||
class A {
|
||||
private int i;
|
||||
private final int j = 0;
|
||||
private transient int k;
|
||||
}
|
||||
fields = filter.filterDeclaredFieldsOf(A.class);
|
||||
assertThat(fields.size(), is(1));
|
||||
assertThat(fields.get(0), is(A.class.getDeclaredField("i")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultFilterFiltersSyntheticFields() {
|
||||
for (Field field : ClassWithSyntheticField.class.getDeclaredFields()) {
|
||||
assertThat(field.isSynthetic(), is(true));
|
||||
assertThat(filter.test(field), is(false));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultFilterFiltersFinalStaticTransientFields()
|
||||
throws NoSuchFieldException {
|
||||
Field field = CWFSTF.getDeclaredField("i");
|
||||
assertThat(Modifier.isFinal(field.getModifiers()), is(true));
|
||||
assertThat(filter.test(field), is(false));
|
||||
|
||||
field = CWFSTF.getDeclaredField("j");
|
||||
assertThat(Modifier.isStatic(field.getModifiers()), is(true));
|
||||
assertThat(filter.test(field), is(false));
|
||||
|
||||
field = CWFSTF.getDeclaredField("k");
|
||||
assertThat(Modifier.isTransient(field.getModifiers()), is(true));
|
||||
assertThat(filter.test(field), is(false));
|
||||
}
|
||||
|
||||
private final class ClassWithSyntheticField {}
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
package de.exlll.configlib.util;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
public final class CollectionFactory {
|
||||
|
||||
private CollectionFactory() { throw new AssertionError(); }
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> List<T> listOf(T... values) {
|
||||
return Arrays.asList(values);
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <T> Set<T> setOf(T... values) {
|
||||
return new LinkedHashSet<>(Arrays.asList(values));
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> mapOf() {
|
||||
return new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> mapOf(K k, V v) {
|
||||
HashMap<K, V> map = new LinkedHashMap<>();
|
||||
map.put(k, v);
|
||||
return map;
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> mapOf(K k1, V v1, K k2, V v2) {
|
||||
Map<K, V> map = mapOf(k1, v1);
|
||||
map.put(k2, v2);
|
||||
return map;
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> mapOf(K k1, V v1, K k2, V v2, K k3, V v3) {
|
||||
Map<K, V> map = mapOf(k1, v1, k2, v2);
|
||||
map.put(k3, v3);
|
||||
return map;
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> mapOf(
|
||||
K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4
|
||||
) {
|
||||
Map<K, V> map = mapOf(k1, v1, k2, v2, k3, v3);
|
||||
map.put(k4, v4);
|
||||
return map;
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> mapOf(
|
||||
K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4, K k5, V v5
|
||||
) {
|
||||
Map<K, V> map = mapOf(k1, v1, k2, v2, k3, v3, k4, v4);
|
||||
map.put(k5, v5);
|
||||
return map;
|
||||
}
|
||||
|
||||
public static <K, V> Map.Entry<K, V> mapEntry(K k, V v) {
|
||||
return new MapEntry<>(k, v);
|
||||
}
|
||||
|
||||
private static final class MapEntry<K, V> implements Map.Entry<K, V> {
|
||||
private final K key;
|
||||
private V value;
|
||||
|
||||
public MapEntry(K key, V value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public K getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public V setValue(V value) {
|
||||
V old = this.value;
|
||||
this.value = value;
|
||||
return old;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,509 +1,585 @@
|
||||
# ConfigLib v2
|
||||
# ConfigLib
|
||||
|
||||
**A Bukkit and BungeeCord library for storing and loading configurations**
|
||||
**A Minecraft library for mapping objects to YAML configuration files.**
|
||||
|
||||
This library facilitates creating, saving and loading configurations by reflectively converting configuration
|
||||
instances to serializable `Map`s which can be transformed to different representations (e.g. YAML) before being
|
||||
stored to files or other storage systems.
|
||||
This library facilitates creating, saving, loading, updating, and documenting YAML configuration
|
||||
files. It does so by automatically mapping instances of configuration classes to serializable maps
|
||||
which are then transformed into YAML and saved to some specified file.
|
||||
|
||||
Currently this library only supports storing configurations as YAML. However, users may provide their own
|
||||
storage systems.
|
||||
## Features
|
||||
|
||||
For a step-by-step tutorial see: [Tutorial](https://github.com/Exlll/ConfigLib/wiki/Tutorial)
|
||||
* Automatic creation, saving, loading, and updating of configuration files
|
||||
* Support for all primitive types, their wrapper types, and Strings
|
||||
* Support for `BigInteger`, `BigDecimal`, `LocalDate`, `LocalTime`, and `LocalDateTime`
|
||||
* Support for (nested) lists, sets, arrays, and maps
|
||||
* Support for enums and POJOs (+ inheritance!)
|
||||
* Option to exclude fields from being converted
|
||||
* Option to add explanatory comments by annotating fields
|
||||
* Option to format field names before conversion
|
||||
* Option to customize null handling
|
||||
* Option to provide custom serializers
|
||||
* ...and a few more!
|
||||
|
||||
## Features
|
||||
* automatic creation, saving, loading and updating of configurations
|
||||
* (_YAML_) automatic creation of files and directories
|
||||
* support for all primitive types, their wrapper types and `String`s
|
||||
* support for (nested) `List`s, `Set`s and `Map`s
|
||||
* support for `Enum`s and POJOs
|
||||
* option to add explanatory comments by annotating classes and their fields
|
||||
* option to provide custom configuration sources
|
||||
* option to exclude fields from being converted
|
||||
* option to provide custom conversion mechanisms
|
||||
* option to format field names before conversion
|
||||
* option to execute action before/after loading/saving the configuration
|
||||
* (_YAML_) option to change the style of the configuration file
|
||||
* (_YAML_) option to prepend/append text (e.g. color codes)
|
||||
## Usage example
|
||||
|
||||
## General information
|
||||
#### Supported types
|
||||
By default, the following types are converted automatically:
|
||||
- simple types, i.e. primitive types, their wrapper types and `String`s
|
||||
- `Enum`s
|
||||
- any type that is annotated as a `ConfigurationElement`
|
||||
- (nested) `List`s, `Set`s and `Map`s of all the above (e.g. `List<SomeType>`, `Map<String, List<SomeEnum>>`)
|
||||
- only simple types can be `Map` keys
|
||||
|
||||
For fields whose types are not any of the above, you have two other options:
|
||||
* Add a custom `Converter` that converts the field's value to any of the above types and back from it.
|
||||
* If the underlying storage system can handle the type, exclude the field from being converted.
|
||||
|
||||
#### Null values
|
||||
This library does _not_ support `null` values. All non-primitive fields (e.g. `Integer`, `String`, `List`, `Enum`s)
|
||||
must be assigned non-null default values.
|
||||
|
||||
#### Adding or removing configuration options
|
||||
This library supports adding or removing configuration options by simply adding new fields to or removing old fields
|
||||
from the configuration class. The next time the `save` or `loadAndSave` method is called, the changes will be saved.
|
||||
|
||||
#### Changing the type of configuration options
|
||||
Changing the type of configuration options is **_not_** supported. **Don't do that.** This may lead to
|
||||
`ClassCastException`s when loading or accessing the field. This is especially important for generic fields.
|
||||
For example, you should never change a `List<String>` to a `List<Integer>`.
|
||||
|
||||
If you need the type of an option to change, add a new field with a different name and the desired type and then
|
||||
remove the old one.
|
||||
|
||||
#### Subclassing configurations
|
||||
Currently, subclassing configurations is not supported. If you have an instance of class `B` where `B` is a
|
||||
subclass of `A` and `A` is a subclass of `YamlConfiguration` and you save or load that instance, then only the
|
||||
fields of class `B` will be saved or loaded, respectively.
|
||||
|
||||
## How-to (_YAML_)
|
||||
|
||||
For a step-by-step tutorial see: [Tutorial](https://github.com/Exlll/ConfigLib/wiki/Tutorial)
|
||||
|
||||
#### Creating configurations
|
||||
To create a YAML configuration, create a new class and extend `YamlConfiguration`. If you write a Bukkit plugin,
|
||||
you can alternatively extend `BukkitYamlConfiguration` which is a subclass of `YamlConfiguration` and can
|
||||
properly convert Bukkit classes like `Inventory` and `ItemStack` to YAML.
|
||||
|
||||
#### Instantiating configurations
|
||||
* To instantiate a `YamlConfiguration`, you need to pass a `Path` and optionally a `YamlConfiguration.YamlProperties`
|
||||
object to its constructor.
|
||||
* To instantiate a `BukkitYamlConfiguration`, you need to pass a `Path` and optionally a
|
||||
`BukkitYamlConfiguration.BukkitYamlProperties` object to its constructor.
|
||||
|
||||
If you don't pass a `(Bukkit-)YamlProperties` object, the `(Bukkit-)YamlProperties.DEFAULT` instance will be used.
|
||||
|
||||
#### Instantiating (Bukkit-)YamlProperties
|
||||
To instantiate a new `(Bukkit-)YamlProperties` object, call `(Bukkit-)YamlProperties.builder()`,
|
||||
configure the builder and then call its `build()` method.
|
||||
|
||||
Note: The `BukkitYamlProperties` is a subclass of `YamlProperties` but doesn't add any new methods to it.
|
||||
Its sole purpose is to provide more appropriate defaults to the underlying YAML parser.
|
||||
|
||||
#### Saving and loading configurations
|
||||
Instances of your configuration class have a `load`, `save` and `loadAndSave` method:
|
||||
- `load` first tries to load the configuration file and then updates the values of all fields of the configuration
|
||||
instance with the values it read from the file.
|
||||
* If the file contains an entry that doesn't have a corresponding field, the entry is ignored.
|
||||
* If the instance contains a field for which no entry was found, the default value you assigned to that field is kept.
|
||||
- `save` first converts the configuration instance with its current values to YAML and then tries to dump that YAML
|
||||
to a configuration file.
|
||||
* The configuration file is completely overwritten. This means any entries it contains are lost afterwards.
|
||||
- `loadAndSave` is a convenience method that first calls `load` and then `save`.
|
||||
* If the file doesn't exist, the configuration instance keeps its default values. Otherwise, the values are
|
||||
updated with the values read from the file.
|
||||
* Subsequently the instance is saved so that the values of any newly added fields are also added
|
||||
to configuration file.
|
||||
|
||||
#### Adding and removing fields
|
||||
Adding and removing fields is supported. However, changing the type of field is not.
|
||||
|
||||
For example, you can change the following `YamlConfiguration`
|
||||
```java
|
||||
class MyConfiguration extends YamlConfiguration {
|
||||
private String s = "1";
|
||||
private double d = 4.2;
|
||||
// ...
|
||||
}
|
||||
```
|
||||
to this:
|
||||
```java
|
||||
class MyConfiguration extends YamlConfiguration {
|
||||
private String s = "2";
|
||||
private int i = 1;
|
||||
// ...
|
||||
}
|
||||
```
|
||||
But you are not allowed to change the type of the variable `d` to `int` (or any other type).
|
||||
This section contains a short usage example to get you started. The whole range of features is
|
||||
discussed in the following sections. Information on how to import this library is located at the end
|
||||
of this documentation.
|
||||
|
||||
#### Simple, enum and custom types
|
||||
The following types are simple types (remember that `null` values are not allowed):
|
||||
|
||||
```java
|
||||
class MyConfiguration extends YamlConfiguration {
|
||||
private boolean primBool;
|
||||
private Boolean refBool = false;
|
||||
private byte primByte;
|
||||
private Byte refByte = 0;
|
||||
private char primChar;
|
||||
private Character refChar = '\0';
|
||||
private short primShort;
|
||||
private Short refShort = 0;
|
||||
private int primInt;
|
||||
private Integer refInt = 0;
|
||||
private long primLong;
|
||||
private Long refLong = 0L;
|
||||
private float primFloat;
|
||||
private Float refFloat = 0F;
|
||||
private double primDouble;
|
||||
private Double refDouble = 0.0;
|
||||
private String string = "";
|
||||
// ...
|
||||
}
|
||||
```
|
||||
public final class Example {
|
||||
// To create a configuration annotate the class with @Configuration and make sure that
|
||||
// it has a no-args constructor. That's it! Now you can add fields to it which can all
|
||||
// be private; setters are not required!
|
||||
@Configuration
|
||||
public static class BaseConfiguration {
|
||||
private String host = "127.0.0.1";
|
||||
private int port = 1234;
|
||||
// The library supports lists, sets, and maps.
|
||||
private Set<String> blockedAddresses = Set.of();
|
||||
// Fields can be ignored by making them final, transient, static or by
|
||||
// annotating them with @Ignore.
|
||||
private final double ignoreMe = 3.14;
|
||||
}
|
||||
|
||||
Enums are supported:
|
||||
// This class does not need to be annotated with @Configuration because it
|
||||
// extends a class which already is!
|
||||
public static final class UserConfiguration extends BaseConfiguration {
|
||||
// You can add comments with the @Comment annotation. Each string in the comment
|
||||
// array is written (as a comment) on a new line.
|
||||
@Comment({"The admin user has full access.", "Choose a proper password!"})
|
||||
User admin = new User("root", "toor"); // The User class is a @Configuration!
|
||||
List<User> blockedUsers = List.of(
|
||||
new User("user1", null), // null values are supported
|
||||
new User("user2", null)
|
||||
);
|
||||
}
|
||||
|
||||
```java
|
||||
class MyConfiguration extends YamlConfiguration {
|
||||
private Material material = Material.AIR;
|
||||
//...
|
||||
}
|
||||
```
|
||||
@Configuration
|
||||
public static final class User {
|
||||
private String username;
|
||||
@Comment("Please choose a strong password.")
|
||||
private String password;
|
||||
|
||||
Custom classes are supported if they are annotated as `ConfigurationElement`s and if they have a no-args constructor.
|
||||
Custom classes can have fields whose values are also instances of custom classes.
|
||||
// Configuration classes require a no-args constructor.
|
||||
// The constructor can be private though.
|
||||
private User() {}
|
||||
|
||||
```java
|
||||
@ConfigurationElement
|
||||
class MyCustomClass1 {/* fields etc.*/}
|
||||
public User(String username, String password) {/* initialize */}
|
||||
}
|
||||
|
||||
@ConfigurationElement
|
||||
class MyCustomClass2 {
|
||||
private MyCustomClass1 cls1 = new MyCustomClass1();
|
||||
// ...
|
||||
}
|
||||
public static void main(String[] args) {
|
||||
final var props = YamlConfigurationProperties.newBuilder().build();
|
||||
final var store = new YamlConfigurationStore<>(UserConfiguration.class, props);
|
||||
final var configFile = Paths.get("/tmp/config.yml");
|
||||
|
||||
// Save a new instance to the configuration file
|
||||
store.save(new UserConfiguration(), configFile);
|
||||
|
||||
class MyConfiguration extends YamlConfiguration {
|
||||
private MyCustomClass2 cls2 = new MyCustomClass2();
|
||||
// ...
|
||||
// Load a new instance from the configuration file
|
||||
UserConfiguration configuration = store.load(configFile);
|
||||
System.out.println(configuration.admin.username);
|
||||
System.out.println(configuration.blockedUsers);
|
||||
|
||||
// Modify and save the configuration file
|
||||
configuration.blockedUsers.add(new User("user3", "pass3"));
|
||||
store.save(configuration, configFile);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### `List`s, `Set`s, `Map`s
|
||||
Lists, sets and maps of simple types can be used as is and don't need any special treatment.
|
||||
```java
|
||||
class MyConfiguration extends YamlConfiguration {
|
||||
private Set<Integer> ints = new HashSet<>();
|
||||
private List<String> strings = new ArrayList<>();
|
||||
private Map<Boolean, Double> doubleByBool = new HashMap<>();
|
||||
// ...
|
||||
}
|
||||
By running the above code, a new YAML configuration is created at `/tmp/config.yml`. Its content
|
||||
looks like this:
|
||||
|
||||
```yaml
|
||||
host: 127.0.0.1
|
||||
port: 1234
|
||||
blockedAddresses: [ ]
|
||||
# The admin user has full access.
|
||||
# Choose a proper password!
|
||||
admin:
|
||||
username: root
|
||||
# Please choose a strong password.
|
||||
password: toor
|
||||
blockedUsers:
|
||||
- username: user1
|
||||
- username: user2
|
||||
- username: user3
|
||||
password: pass3
|
||||
```
|
||||
|
||||
Note: Even though sets are supported, their YAML-representation is 'pretty ugly', so it's better to use lists instead.
|
||||
If you need set behavior, you can internally use lists and convert them to sets using the `preSave/postLoad`-hooks.
|
||||
Two things are noticeable here:
|
||||
|
||||
1. Not every user in the `blockedUsers` list has a `password` mapping. This is because null values
|
||||
are not output by default. That behavior can be changed by the builder.
|
||||
2. The password of the user with username `user3` that has no comment. This is due to limitations of
|
||||
the YAML library. Configurations in lists, sets, or maps cannot have their comments printed.
|
||||
|
||||
## General information
|
||||
|
||||
In the following sections the term _configuration type_ refers to any non-generic class that is
|
||||
directly or indirectly (i.e. through subclassing) annotated with
|
||||
`@de.exlll.configlib.Configuration`. Accordingly, the term _configuration_ refers to an instance of
|
||||
such a type.
|
||||
|
||||
### Declaring configuration types
|
||||
|
||||
To declare a configuration type, annotate a class with `@Configuration` and make sure that it has a
|
||||
no-args constructor. The no-args constructor can be set `private`. Inner classes (i.e. the ones that
|
||||
are nested but not `static`) have an implicit synthetic constructor with at least one argument and
|
||||
are therefore not supported.
|
||||
|
||||
Add fields to your class whose type is any of the supported types listed in the next section. You
|
||||
should initialize all fields of reference types with non-null default values, though you can leave
|
||||
them null. Handling of null values is discussed in one of the sections further below.
|
||||
|
||||
Lists, sets and maps that contain other types (e.g. custom types or enums) must use the `@ElementType` annotation.
|
||||
Only simple types can be used as map keys.
|
||||
### Supported types
|
||||
|
||||
A configuration type may only contain fields of the following types:
|
||||
|
||||
| Type class | Types |
|
||||
|------------------------|--------------------------------------------------------------------|
|
||||
| Boolean types | `boolean`, and `Boolean` |
|
||||
| Integer types | `byte`, `short`, `int`, `long`, and their respective wrapper types |
|
||||
| Floating point types | `float`, `double`, and their respective wrapper types |
|
||||
| Characters and strings | `char`, `Character`, `String` |
|
||||
| Big numeric types | `BigInteger`, `BigDecimal` |
|
||||
| Time related types | `LocalTime`, `LocalDate`, `LocalDateTime` |
|
||||
| Enums | Any Java enum |
|
||||
| Configurations | Any configuration type |
|
||||
| Collections | (Nested) Lists, sets, maps*, or arrays of previously listed types |
|
||||
|
||||
(*) Map keys cannot be in the `Configurations` or `Collections` type class.
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Examples of supported types</summary>
|
||||
|
||||
The following class contains examples of types that this library supports:
|
||||
|
||||
```java
|
||||
@ConfigurationElement
|
||||
class MyCustomClass {/* fields etc.*/}
|
||||
|
||||
class MyConfiguration extends YamlConfiguration {
|
||||
@ElementType(Material.class)
|
||||
private List<Material> materials = new ArrayList<>();
|
||||
|
||||
@ElementType(MyCustomClass.class)
|
||||
private Set<MyCustomClass> customClasses = new HashSet<>();
|
||||
|
||||
@ElementType(MyCustomClass.class)
|
||||
private Map<String, MyCustomClass> customClassesMap = new HashMap<>();
|
||||
// ...
|
||||
public final class SupportedTypes {
|
||||
boolean supported;
|
||||
Character supported;
|
||||
ExampleEnum supported; // where 'ExampleEnum' is some Java enum type
|
||||
ExampleConf supported; // where 'ExampleConf' is another configuration type
|
||||
java.awt.Point supported; // only if a custom serializer is registered
|
||||
/* collection types */
|
||||
List<BigInteger> supported;
|
||||
Set<Double> supported;
|
||||
LocalDate[] supported;
|
||||
Map<ExampleEnum, ExampleConf> supported;
|
||||
/* nested collection types */
|
||||
List<Map<ExampleEnum, LocalDate>> supported;
|
||||
int[][] supported;
|
||||
Map<Integer, List<Map<Short, Set<ExampleConf>>>> supported;
|
||||
}
|
||||
```
|
||||
|
||||
Lists, sets and maps can be nested. If nested collections contain custom types, you must specify the
|
||||
nesting level using the `@ElementType` annotation. Examples:
|
||||
</details>
|
||||
|
||||
* `List<T>` requires a nesting level of 0, which is the default value, so you don't have to set it
|
||||
* `List<List<T>>` requires a nesting level of 1
|
||||
* `List<List<List<T>>>` requires a nesting level of 2
|
||||
* `List<Map<String, T>>` requires a nesting level of 1
|
||||
* `List<Map<String, Map<String, T>>>` requires a nesting level of 2
|
||||
<details>
|
||||
<summary>Examples of unsupported types</summary>
|
||||
|
||||
The following class contains examples of types that this library does (and will) not support:
|
||||
|
||||
```java
|
||||
@ConfigurationElement
|
||||
class MyCustomClass {/* fields etc.*/}
|
||||
|
||||
class MyConfiguration extends YamlConfiguration {
|
||||
private List<List<Integer>> listsList = new ArrayList<>();
|
||||
private Set<Set<String>> setsSet = new HashSet<>();
|
||||
private Map<Integer, Map<String, Integer>> mapsMap = new HashMap<>();
|
||||
|
||||
@ElementType(value = MyCustomClass.class, nestingLevel = 1)
|
||||
private List<List<MyCustomClass>> customClassListsList = new ArrayList<>();
|
||||
|
||||
@ElementType(value = MyCustomClass.class, nestingLevel = 1)
|
||||
private Set<Set<MyCustomClass>> customClassSetsSet = new HashSet<>();
|
||||
|
||||
@ElementType(value = MyCustomClass.class, nestingLevel = 1)
|
||||
private Map<Integer, Map<String, MyCustomClass>> customClassMapsMap = new HashMap<>();
|
||||
// ...
|
||||
public final class UnsupportedTypes<T> {
|
||||
Map<Point, String> unsupported; // invalid map key
|
||||
Map<List<String>, String> unsupported; // invalid map key
|
||||
Box<String> unsupported; // custom parameterized type
|
||||
List<? extends String> unsupported; // wildcard type
|
||||
List<?> unsupported; // wildcard type
|
||||
List<?>[] unsupported; // wildcard type
|
||||
T unsupported; // type variable
|
||||
List unsupported; // raw type
|
||||
List[] unsupported; // raw type
|
||||
List<String>[] unsupported; // generic array type
|
||||
Set<Integer>[] unsupported; // generic array type
|
||||
Map<Byte, Byte>[] unsupported; // generic array type
|
||||
}
|
||||
```
|
||||
#### Adding comments
|
||||
You can add comments to a configuration class or a its field by using the `@Comment` annotation.
|
||||
Class comments are saved at the beginning of a configuration file.
|
||||
|
||||
```java
|
||||
@Comment({"A", "", "B"})
|
||||
class MyConfiguration extends YamlConfiguration {
|
||||
@Comment("the x")
|
||||
private int x;
|
||||
@Comment({"", "the y"})
|
||||
private int y;
|
||||
// ...
|
||||
</details>
|
||||
|
||||
### Loading and saving configurations
|
||||
|
||||
There are two ways to load and save configurations. Which way you choose depends on your liking.
|
||||
Both ways have three methods in common:
|
||||
|
||||
* `save` saves a configuration to a file
|
||||
* `load` creates a new configuration instance and populates it with values taken from a file
|
||||
* `update` is a combination of `load` and `save` and the method you'd usually want to use: it takes
|
||||
care of creating the configuration file if it does not exist and updates it otherwise to reflect
|
||||
changes to (the fields of) the configuration type.
|
||||
|
||||
<details>
|
||||
<summary>Example of <code>update</code> behavior when configuration file exists</summary>
|
||||
|
||||
Let's say you have the following configuration type:
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public final class C {
|
||||
int i = 10;
|
||||
int j = 11;
|
||||
}
|
||||
```
|
||||
Empty strings are represented as newlines (i.e. lines that don't start with '# ').
|
||||
|
||||
#### Executing pre-save and post-load actions
|
||||
To execute pre-save and post-load actions, override `preSave()` and `postLoad()`, respectively.
|
||||
```java
|
||||
class MyConfiguration extends YamlConfiguration {
|
||||
@Override
|
||||
protected void preSave(){ /* do something ... */}
|
||||
|
||||
@Override
|
||||
protected void postLoad(){ /* do something ... */}
|
||||
// ...
|
||||
}
|
||||
... and a YAML configuration file that contains:
|
||||
|
||||
```yaml
|
||||
i: 20
|
||||
k: 30
|
||||
```
|
||||
|
||||
#### Excluding fields from being converted
|
||||
To exclude fields from being converted, annotate them with the `@NoConvert` annotation. This may be useful if the
|
||||
configuration knows how to (de-)serialize instances of that type. For example, a `BukkitYamlConfiguration` knows how
|
||||
to serialize `ItemStack` instances.
|
||||
Now, when you use one of the methods below to call `update` for that configuration type and file,
|
||||
the configuration instance that `update` returns will have its `i` variable initialized to `20`
|
||||
and its `j` variable will have its default of `11`. After the operation, the configuration file will
|
||||
contain:
|
||||
|
||||
```java
|
||||
class MyConfiguration extends BukkitYamlConfiguration {
|
||||
@NoConvert
|
||||
private ItemStack itemStack = new ItemStack(Material.STONE, 1);
|
||||
// ...
|
||||
}
|
||||
```yaml
|
||||
i: 20
|
||||
j: 11
|
||||
```
|
||||
|
||||
#### Changing configuration properties
|
||||
To change the properties of a configuration, use the properties builder object.
|
||||
</details>
|
||||
|
||||
##### Formatting field names
|
||||
To format field names before conversion, configure the properties builder to use a custom `FieldNameFormatter`.
|
||||
You can either define your own `FieldNameFormatter` or use one from the `FieldNameFormatters` enum.
|
||||
To exemplify the usage of these three methods we assume for the following sections that you have
|
||||
implemented the configuration type below and have access to some regular `java.nio.file.Path`
|
||||
object `configurationFile`.
|
||||
|
||||
```java
|
||||
YamlProperties properties = YamlProperties.builder()
|
||||
.setFormatter(FieldNameFormatters.LOWER_UNDERSCORE)
|
||||
// ...
|
||||
.build();
|
||||
```java
|
||||
@Configuration
|
||||
public final class Config { /* some fields */ }
|
||||
```
|
||||
|
||||
Alternatively, you can annotate your `Configuration` class with the `@Format` annotation. The `FieldNameFormatter`
|
||||
returned by this annotation takes precedence over the `FieldNameFormatter` returned by a `Properties` object.
|
||||
#### Way 1
|
||||
|
||||
```java
|
||||
@Format(FieldNameFormatters.UPPER_UNDERSCORE)
|
||||
class MyConfiguration extends YamlConfiguration {
|
||||
// ...
|
||||
}
|
||||
The first way is to create a configuration store and use it directly to save, load, or update
|
||||
configurations.
|
||||
|
||||
```java
|
||||
YamlConfigurationProperties properties = YamlConfigurationProperties.newBuilder().build();
|
||||
YamlConfigurationStore<Config> store = new YamlConfigurationStore<>(Config.class, properties);
|
||||
|
||||
Config config1 = store.load(configurationFile);
|
||||
store.save(config1, configurationFile);
|
||||
Config config2 = store.update(configurationFile);
|
||||
```
|
||||
|
||||
Note: You should neither remove nor replace a formatter with one that has a different formatting style because this
|
||||
could break existing configurations.
|
||||
#### Way 2
|
||||
|
||||
##### (_YAML_) Prepending/appending text
|
||||
To prepend or append comments to a configuration file, use the `setPrependedComments` and `setAppendedComments` methods,
|
||||
respectively.
|
||||
The second way is to use the static methods from the `Configurations` class.
|
||||
|
||||
```java
|
||||
YamlProperties properties = YamlProperties.builder()
|
||||
.setPrependedComments(Arrays.asList("A", "B"))
|
||||
.setAppendedComments(Arrays.asList("C", "D"))
|
||||
// ...
|
||||
.build();
|
||||
```java
|
||||
Config config1 = Configurations.loadYamlConfiguration(configurationFile, Config.class);
|
||||
Configurations.saveYamlConfiguration(configurationFile, Config.class, config1);
|
||||
Config config2 = Configurations.updateYamlConfiguration(configurationFile, Config.class);
|
||||
```
|
||||
|
||||
##### (_YAML_) Changing the style of the configuration file
|
||||
To change the configuration style, use the `setConstructor`, `setRepresenter`, `setOptions` and `setResolver` methods.
|
||||
These methods change the behavior of the underlying YAML-parser.
|
||||
See [snakeyaml-Documentation](https://bitbucket.org/asomov/snakeyaml/wiki/Documentation).
|
||||
Each of these methods has two additional overloads: One that takes a properties object and another
|
||||
that lets you configure a properties object builder. For example, the overloads for the
|
||||
`loadYamlConfiguration` method are:
|
||||
|
||||
```java
|
||||
// overload 1
|
||||
YamlConfigurationProperties properties = YamlConfigurationProperties.newBuilder()
|
||||
.inputNulls(true)
|
||||
.outputNulls(false)
|
||||
.build();
|
||||
Config c1 = Configurations.loadYamlConfiguration(configurationFile, Config.class, properties);
|
||||
|
||||
// overload 2
|
||||
Config c2 = Configurations.loadYamlConfiguration(
|
||||
configurationFile,
|
||||
Config.class,
|
||||
builder -> builder.inputNulls(true).outputNulls(false)
|
||||
);
|
||||
```
|
||||
|
||||
```java
|
||||
YamlProperties properties = YamlProperties.builder()
|
||||
.setConstructor(...)
|
||||
.setRepresenter(/* */)
|
||||
.setOptions(/* */)
|
||||
.setResolver(/* */)
|
||||
// ...
|
||||
.build();
|
||||
#### Configuration properties
|
||||
|
||||
Instances of the `ConfigurationProperties` class allow customization of how configurations are
|
||||
stored and loaded. To create such an instance, instantiate a new builder using
|
||||
the `YamlConfigurationProperties.newBuilder()` method, configure it, and finally call its `build()`
|
||||
method. Check out the several methods of the builder to see which configuration options are
|
||||
available.
|
||||
|
||||
#### Ignoring and filtering fields
|
||||
|
||||
Fields that are `final`, `static`, `transient` or annotated with `@Ignore` are neither serialized
|
||||
nor updated during deserialization. You can filter out additional fields by providing an instance of
|
||||
`FieldFilter` to the configuration properties.
|
||||
|
||||
#### Handling of `null` values
|
||||
|
||||
Configuration properties let you configure how `null` values are handled when serializing and
|
||||
deserializing a configuration:
|
||||
|
||||
* By setting `outputNulls` to false, fields and collection elements that are null are not output.
|
||||
Any comments that belong to such fields are also not written.
|
||||
* By setting `inputNulls` to false, fields and collection elements that are null are not input. That
|
||||
means that fields will keep their default values.
|
||||
|
||||
<details>
|
||||
<summary>Example <code>null</code> handling configuration</summary>
|
||||
|
||||
The following code forbids null values to be output but allows null values to be input. By default,
|
||||
both are forbidden which makes the call to `outputNulls` in this case redundant.
|
||||
|
||||
```java
|
||||
YamlConfigurationProperties.newBuilder()
|
||||
.outputNulls(false)
|
||||
.inputNulls(true)
|
||||
.build();
|
||||
```
|
||||
|
||||
Note: Changing the configuration style may break adding comments using the `@Comment` annotation.
|
||||
</details>
|
||||
|
||||
##### Adding field filters
|
||||
If your configuration has a lot of fields and you want to exclude some of these fields without
|
||||
making them final, static or transient, you can configure your properties object to use additional
|
||||
`FieldFilter`s. A `FieldFilter` filters the fields of a configuration class by a specified criterion.
|
||||
#### Field formatting
|
||||
|
||||
For example, if you only want to include fields whose names don't start with _ignore_, you would add
|
||||
the following filter:
|
||||
You can define how fields are formatted by configuring the configuration properties with a custom
|
||||
formatter. Field formatters are implementations of the `FieldFormatter` interface. You can implement
|
||||
this interface yourself or use one of the several formatters this library provides. These
|
||||
pre-defined formatters can be found in the `FieldFormatters` class.
|
||||
|
||||
```java
|
||||
YamlProperties properties = YamlProperties.builder()
|
||||
.addFilter(field -> !field.getName().startsWith("ignore"))
|
||||
// ...
|
||||
.build();
|
||||
<details>
|
||||
<summary>Example <code>FieldFormatter</code> configuration</summary>
|
||||
|
||||
The following code formats fields using the `IDENTITY` formatter (which is the default).
|
||||
|
||||
```java
|
||||
YamlConfigurationProperties.newBuilder()
|
||||
.setFieldFormatter(FieldFormatters.IDENTITY)
|
||||
.build();
|
||||
```
|
||||
|
||||
Note: A filter is not evaluated for a field if the field has already been filtered or by some
|
||||
other `FieldFilter`.
|
||||
</details>
|
||||
|
||||
#### Adding custom converters
|
||||
Any field can be converted using a custom converter. This can be useful if you don't like the default
|
||||
conversion mechanism or if you have classes that cannot be annotated as `ConfigurationElement`s
|
||||
(e.g. because they are not under your control).
|
||||
### Subclassing
|
||||
|
||||
To create a new converter, you have to implement the `Converter<F, T>` interface where `F` represents
|
||||
the type of the field and `T` represents the type of the value to which the field value is converted.
|
||||
Subclassing of configurations types is supported. Subclasses of configuration types don't need to be
|
||||
annotated with `@Configuration`. When a configuration is written, the fields of parent classes
|
||||
are written before the fields of the child in a top to bottom manner. Parent configurations can
|
||||
be `abstract`.
|
||||
|
||||
```java
|
||||
import java.awt.Point;
|
||||
#### Shadowing of fields
|
||||
|
||||
class PointStringConverter implements Converter<Point, String> {
|
||||
@Override
|
||||
public String convertTo(Point element, ConversionInfo info) {
|
||||
return element.x + ":" + element.y;
|
||||
}
|
||||
Shadowing of fields refers to the situation where a subclass of configuration has a field that has
|
||||
the same name as a field in one of its super classes. Shadowing of fields is currently not
|
||||
supported. (This restriction might easily be lifted. If you need this feature, please open an issue
|
||||
and describe how to handle name clashes.)
|
||||
|
||||
@Override
|
||||
public Point convertFrom(String element, ConversionInfo info) {
|
||||
String[] coordinates = element.split(":");
|
||||
int x = Integer.parseInt(coordinates[0]);
|
||||
int y = Integer.parseInt(coordinates[1]);
|
||||
return new Point(x, y);
|
||||
}
|
||||
### Comments
|
||||
|
||||
The fields of a configuration can be annotated with the `@Comment` annotation. This annotation takes
|
||||
an array of strings. Each of these strings is written onto a new line as a comment. Empty strings
|
||||
are written as newlines.
|
||||
|
||||
<details>
|
||||
<summary>Example of <code>@Comment</code> usage </summary>
|
||||
|
||||
Serializing the following configuration as YAML ...
|
||||
|
||||
```java
|
||||
@Configuration
|
||||
public final class ExampleConfiguration {
|
||||
@Comment({"Hello", "", " ", "World"})
|
||||
private String commentedField = "commented field";
|
||||
}
|
||||
```
|
||||
|
||||
To use your custom converter, pass its class to the `@Convert` annotation.
|
||||
```java
|
||||
class MyConfiguration extends YamlConfiguration {
|
||||
@Convert(PointStringConverter.class)
|
||||
private Point point = new Point(2, 3);
|
||||
//...
|
||||
}
|
||||
... results in the YAML file shown below:
|
||||
|
||||
```yaml
|
||||
# Hello
|
||||
|
||||
#
|
||||
# World
|
||||
commentedField: commented field
|
||||
```
|
||||
|
||||
Note: Only a single converter instance is created which is cached.
|
||||
</details>
|
||||
|
||||
## Example
|
||||
If a configuration type _C_ that defines comments is used (as a field) within another configuration
|
||||
type, the comments of _C_ are written with the proper indentation. However, if instances of _C_ are
|
||||
stored inside a collection, their comments are not printed when the collection is written.
|
||||
|
||||
For a step-by-step tutorial of a more complex example see:
|
||||
[Tutorial](https://github.com/Exlll/ConfigLib/wiki/Tutorial)
|
||||
### Recursive type definitions
|
||||
|
||||
Recursive type definitions are currently not allowed but might be supported in a future version if
|
||||
this feature is requested.
|
||||
|
||||
<details>
|
||||
<summary>Examples of recursive type definitions</summary>
|
||||
|
||||
Neither direct nor indirect recursive type definitions are supported.
|
||||
|
||||
```java
|
||||
import de.exlll.configlib.annotation.Comment;
|
||||
import de.exlll.configlib.annotation.ConfigurationElement;
|
||||
import de.exlll.configlib.configs.yaml.BukkitYamlConfiguration;
|
||||
import de.exlll.configlib.configs.yaml.BukkitYamlConfiguration.BukkitYamlProperties;
|
||||
import de.exlll.configlib.format.FieldNameFormatters;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@ConfigurationElement
|
||||
class Credentials {
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
// ConfigurationElements must have a no-args constructor
|
||||
Credentials() {
|
||||
this("", ""); // default values must be non-null
|
||||
public final class RecursiveTypDefinitions {
|
||||
// Direct recursive definition
|
||||
@Configuration
|
||||
static final class R {
|
||||
R r;
|
||||
}
|
||||
|
||||
Credentials(String username, String password) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
// Indirect recursive definition
|
||||
@Configuration
|
||||
static final class R1 {
|
||||
R2 r2;
|
||||
}
|
||||
|
||||
String getUsername() { return username; }
|
||||
@Configuration
|
||||
static final class R2 {
|
||||
R1 r1;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Type conversion and custom serializers
|
||||
|
||||
Before instances of the types listed in the [supported types](#supported-types) section can be
|
||||
stored, they need to be converted into serializable types (i.e. into types the underlying YAML
|
||||
library knows how to handle). The conversion happens according to the following table:
|
||||
|
||||
| Source type | Target type |
|
||||
|------------------------|------------------|
|
||||
| Boolean types | `Boolean` |
|
||||
| Integer types | `Long` |
|
||||
| Floating point types | `Double` |
|
||||
| Characters and strings | `String` |
|
||||
| Big numeric types | `String` |
|
||||
| Time related types | `String` |
|
||||
| Enums | `String` |
|
||||
| Configurations | `Map<String, ?>` |
|
||||
| `Set<S>` | `List<T>`* |
|
||||
| `List<S>` | `List<T>` |
|
||||
| `S[]` | `List<T>` |
|
||||
| `Map<S1, S2>` | `Map<T1, T2>` |
|
||||
|
||||
(*) By default, sets are serialized as lists. This can be changed through the configuration
|
||||
properties. This also means that `Set`s are valid target types.
|
||||
|
||||
#### Serializer selection
|
||||
|
||||
To convert the value of a field `F` with (source) type `S` into a serializable value of some
|
||||
target type, a serializer has to be selected. Serializers are instances of
|
||||
the `de.exlll.configlib.Serializer` interface and are selected based on `S`. Put differently,
|
||||
serializers are always selected based on the compile-time type of `F` and never on the runtime type
|
||||
of its value.
|
||||
|
||||
This distinction makes a difference (and might lead to confusion) when you have fields whose type is
|
||||
a configuration type or a collection of some configuration type, and you extend that configuration
|
||||
type. Concretely, assume you have and written two configuration types `A` and `B`
|
||||
where `B extends A`. Then, if you use `A a = new B()` in your main configuration, only the fields of
|
||||
a `A` will be stored when you save your main configuration. That is because the serializer of
|
||||
field `a` was selected based on the compile-time type of `a` which is `A` and not `B`. The same
|
||||
happens if you have a `List<A>` and put instances of `B` (or some other subclass of `A`) in it.
|
||||
|
||||
#### Custom serializers
|
||||
|
||||
If you want to add support for a type whose class is not annotated with `@Configuration`, you can
|
||||
register a custom serializer. Serializers are instances of the `de.exlll.configlib.Serializer`
|
||||
interface. When implementing that interface you have to make sure that you convert your source type
|
||||
into one of the valid target types listed in the table above. The serializer then has to be
|
||||
registered through a `ConfigurationProperties` object.
|
||||
|
||||
<details>
|
||||
<summary>Example <code>Serializer</code> implementation</summary>
|
||||
|
||||
The following `Serializer` serializes instances of `java.awt.Point` into strings.
|
||||
|
||||
@Comment("MAIN-DB CONFIG")
|
||||
class DatabaseConfig extends BukkitYamlConfiguration {
|
||||
private String host = "localhost";
|
||||
@Comment("must be greater than 1024")
|
||||
private int port = 3306;
|
||||
private Credentials adminAccount = new Credentials("admin", "123");
|
||||
private List<String> blockedUsers = Arrays.asList("root", "john");
|
||||
|
||||
/* You can use the other constructor instead which uses the
|
||||
* BukkitYamlProperties.DEFAULT instance. */
|
||||
DatabaseConfig(Path path, BukkitYamlProperties properties) {
|
||||
super(path, properties);
|
||||
```java
|
||||
public final class PointSerializer implements Serializer<Point, String> {
|
||||
@Override
|
||||
public String serialize(Point element) {
|
||||
return element.x + ":" + element.y;
|
||||
}
|
||||
|
||||
Credentials getAdminAccount() { return adminAccount; }
|
||||
@Override
|
||||
public Point deserialize(String element) {
|
||||
String[] parts = element.split(":");
|
||||
int x = Integer.parseInt(parts[0]);
|
||||
int y = Integer.parseInt(parts[1]);
|
||||
return new Point(x, y);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
public final class DatabasePlugin extends JavaPlugin {
|
||||
@Override
|
||||
public void onEnable() {
|
||||
/* Creating a properties object is not necessary if the other
|
||||
* DatabaseConfig constructor is used. */
|
||||
BukkitYamlProperties props = BukkitYamlProperties.builder()
|
||||
.setPrependedComments(Arrays.asList("Author: Pete", "Version: 1.0"))
|
||||
.setFormatter(FieldNameFormatters.LOWER_UNDERSCORE)
|
||||
.build();
|
||||
</details>
|
||||
|
||||
Path configPath = new File(getDataFolder(), "config.yml").toPath();
|
||||
Custom serializers takes precedence over the serializers provided by this library.
|
||||
|
||||
DatabaseConfig config = new DatabaseConfig(configPath, props);
|
||||
config.loadAndSave();
|
||||
### Changing the type of fields
|
||||
|
||||
Changing the type of fields is not supported. If you change the type of one of your fields but your
|
||||
configuration file still contains a value of the old type, a type mismatch will occur when reading
|
||||
that file. Instead, remove the old field and add a new one with a different name.
|
||||
|
||||
System.out.println(config.getAdminAccount().getUsername());
|
||||
}
|
||||
}
|
||||
```
|
||||
## Import
|
||||
|
||||
**INFO:** I'm currently looking for an easier way for you to import this library that does not
|
||||
require authentication with GitHub. Please check
|
||||
this [issue](https://github.com/Exlll/ConfigLib/issues/12) if you have authentication problems.
|
||||
|
||||
To use this library, import it into your project with either Maven or Gradle as shown in the two
|
||||
sections below. This library has additional dependencies (namely, a YAML parser) which are not
|
||||
included in the artifact you import.
|
||||
|
||||
This repository provides plugin versions of this library which bundle all its dependencies, so you
|
||||
don't have to worry about them. Also, these versions make it easier for you to update this library
|
||||
if you have written multiple plugins that use it.
|
||||
|
||||
The plugin versions can be downloaded from
|
||||
the [releases page](https://github.com/Exlll/ConfigLib/releases) where you can identify them by
|
||||
their `-paper-`, `-waterfall-`, and `-velocity-` infix and `-all` suffix. Other than that, the
|
||||
plugin versions currently don't add any additional functionality. If you use these versions, don't
|
||||
forget to add them as a dependency in the `plugin.yml` (for Paper and Waterfall) or to the
|
||||
dependencies array (for Velocity) of your own plugin.
|
||||
|
||||
Alternatively, if you don't want to use an extra plugin, you can shade the `-core` version and the
|
||||
YAML parser yourself.
|
||||
|
||||
#### Maven
|
||||
```xml
|
||||
|
||||
```xml
|
||||
<repository>
|
||||
<id>de.exlll</id>
|
||||
<url>https://maven.pkg.github.com/Exlll/ConfigLib</url>
|
||||
</repository>
|
||||
|
||||
<!-- for Bukkit plugins -->
|
||||
<dependency>
|
||||
<groupId>de.exlll</groupId>
|
||||
<artifactId>configlib-bukkit</artifactId>
|
||||
<version>2.2.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- for Bungee plugins -->
|
||||
<dependency>
|
||||
<groupId>de.exlll</groupId>
|
||||
<artifactId>configlib-bungee</artifactId>
|
||||
<version>2.2.0</version>
|
||||
<artifactId>configlib-core</artifactId>
|
||||
<version>3.0.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
#### Gradle
|
||||
|
||||
```groovy
|
||||
repositories { maven { url 'https://maven.pkg.github.com/Exlll/ConfigLib' } }
|
||||
|
||||
dependencies {
|
||||
// for Bukkit plugins
|
||||
implementation group: 'de.exlll', name: 'configlib-bukkit', version: '2.2.0'
|
||||
// for Bungee plugins
|
||||
implementation group: 'de.exlll', name: 'configlib-bungee', version: '2.2.0'
|
||||
}
|
||||
dependencies { implementation 'de.exlll:configlib-core:3.0.0' }
|
||||
```
|
||||
|
||||
```kotlin
|
||||
repositories { maven { url = uri("https://maven.pkg.github.com/Exlll/ConfigLib") } }
|
||||
|
||||
dependencies {
|
||||
// for Bukkit plugins
|
||||
implementation("de.exlll:configlib-bukkit:2.2.0")
|
||||
// for Bungee plugins
|
||||
implementation("de.exlll:configlib-bungee:2.2.0")
|
||||
}
|
||||
```
|
||||
dependencies { implementation("de.exlll:configlib-core:3.0.0") }
|
||||
```
|
||||
|
||||
## Future work
|
||||
|
||||
This section contains ideas for upcoming features. If you want any of these to happen any time soon,
|
||||
please [open an issue](https://github.com/Exlll/ConfigLib/issues/new) where we can discuss the
|
||||
details.
|
||||
|
||||
- Optional fields
|
||||
- Post load / pre save hooks
|
||||
- TOML support
|
||||
- Change the order of fields in parent/child class scenarios
|
||||
- Recursive definitions
|
@ -1,77 +1,4 @@
|
||||
plugins {
|
||||
java
|
||||
idea
|
||||
`maven-publish`
|
||||
}
|
||||
|
||||
allprojects {
|
||||
group = "de.exlll"
|
||||
version = "2.2.0"
|
||||
}
|
||||
|
||||
subprojects {
|
||||
apply(plugin = "java")
|
||||
apply(plugin = "idea")
|
||||
apply(plugin = "maven-publish")
|
||||
|
||||
java.sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
java.targetCompatibility = JavaVersion.VERSION_1_8
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.0.3")
|
||||
testImplementation("org.junit.platform:junit-platform-runner:1.0.3")
|
||||
testImplementation("org.junit.platform:junit-platform-suite-api:1.0.3")
|
||||
testImplementation("org.hamcrest:hamcrest-all:1.3")
|
||||
testImplementation("com.google.jimfs:jimfs:1.1")
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
name = "GitHubPackages"
|
||||
url = uri("https://maven.pkg.github.com/Exlll/ConfigLib")
|
||||
credentials {
|
||||
username = System.getenv("GITHUB_ACTOR")
|
||||
password = System.getenv("GITHUB_TOKEN")
|
||||
}
|
||||
}
|
||||
publications {
|
||||
register<MavenPublication>("gpr") {
|
||||
from(components["java"])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
project(":configlib-core") {
|
||||
dependencies {
|
||||
implementation("org.yaml:snakeyaml:1.20")
|
||||
}
|
||||
}
|
||||
project(":configlib-bukkit") {
|
||||
repositories {
|
||||
maven(url = "https://hub.spigotmc.org/nexus/content/repositories/snapshots/")
|
||||
}
|
||||
dependencies {
|
||||
implementation(project(":configlib-core"))
|
||||
implementation("org.bukkit:bukkit:1.12.2-R0.1-SNAPSHOT")
|
||||
}
|
||||
|
||||
tasks.jar { from(project(":configlib-core").sourceSets["main"].output) }
|
||||
}
|
||||
project(":configlib-bungee") {
|
||||
repositories {
|
||||
maven(url = "https://oss.sonatype.org/content/repositories/snapshots")
|
||||
}
|
||||
dependencies {
|
||||
implementation(project(":configlib-core"))
|
||||
implementation("net.md-5:bungeecord-api:1.12-SNAPSHOT")
|
||||
}
|
||||
|
||||
tasks.jar { from(project(":configlib-core").sourceSets["main"].output) }
|
||||
version = "3.0.0"
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
plugins {
|
||||
`kotlin-dsl`
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
gradlePluginPortal()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("gradle.plugin.com.github.johnrengelman:shadow:7.1.2")
|
||||
}
|
@ -0,0 +1,91 @@
|
||||
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
|
||||
|
||||
plugins {
|
||||
java
|
||||
idea
|
||||
`maven-publish`
|
||||
id("com.github.johnrengelman.shadow")
|
||||
}
|
||||
|
||||
val shade = configurations.create("shade")
|
||||
|
||||
configurations {
|
||||
compileClasspath.get().extendsFrom(shade)
|
||||
}
|
||||
|
||||
val shadowJarTask = tasks.getByName<ShadowJar>("shadowJar") {
|
||||
configurations = listOf(shade)
|
||||
relocate("org.snakeyaml.engine", "de.exlll.configlib.org.snakeyaml.engine")
|
||||
}
|
||||
|
||||
val coreProjectCheckTask = project(":configlib-core").tasks.getByName("check");
|
||||
|
||||
shadowJarTask.dependsOn(
|
||||
tasks.named("check"),
|
||||
coreProjectCheckTask
|
||||
)
|
||||
|
||||
tasks.getByName("build").dependsOn(coreProjectCheckTask)
|
||||
|
||||
java {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
|
||||
withJavadocJar()
|
||||
withSourcesJar()
|
||||
}
|
||||
|
||||
tasks.getByName<Test>("test") {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven(url = "https://papermc.io/repo/repository/maven-public/")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.2")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-params:5.8.2")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.8.2")
|
||||
testImplementation("org.junit.platform:junit-platform-runner:1.8.2")
|
||||
testImplementation("org.junit.platform:junit-platform-suite-api:1.8.2")
|
||||
testImplementation("org.mockito:mockito-inline:4.2.0")
|
||||
testImplementation("org.mockito:mockito-junit-jupiter:4.2.0")
|
||||
testImplementation("org.hamcrest:hamcrest-all:1.3")
|
||||
testImplementation("com.google.jimfs:jimfs:1.2")
|
||||
}
|
||||
|
||||
val javaComponent = components["java"] as AdhocComponentWithVariants
|
||||
javaComponent.withVariantsFromConfiguration(configurations["shadowRuntimeElements"]) {
|
||||
skip()
|
||||
}
|
||||
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
name = "GitHubPackages"
|
||||
url = uri("https://maven.pkg.github.com/Exlll/ConfigLib")
|
||||
credentials {
|
||||
username = System.getenv("GITHUB_ACTOR")
|
||||
password = System.getenv("GITHUB_TOKEN")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val moduleId = project.name.split("-")[1].toLowerCase()
|
||||
val publicationName = moduleId.capitalize()
|
||||
|
||||
publications {
|
||||
register<MavenPublication>(publicationName) {
|
||||
from(components["java"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
idea {
|
||||
module {
|
||||
isDownloadJavadoc = true
|
||||
isDownloadSources = true
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
plugins {
|
||||
`java-config`
|
||||
}
|
||||
|
||||
dependencies {
|
||||
shade("org.snakeyaml:snakeyaml-engine:2.3")
|
||||
implementation("org.snakeyaml:snakeyaml-engine:2.3")
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Indicates that the annotated element is saved together with explanatory
|
||||
* comments describing it.
|
||||
*/
|
||||
@Target({ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Comment {
|
||||
/**
|
||||
* Returns the comments of the annotated field.
|
||||
*
|
||||
* @return field comments
|
||||
*/
|
||||
String[] value();
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Holds the comments of a field as well as a list of field names. The list of
|
||||
* field names contains the names of all fields which led to this field from the
|
||||
* root of the configuration object. For example, for the following situation the
|
||||
* {@code CommentNode} of the field {@code fn2} would hold {@code comments} and
|
||||
* {@code fieldNames} lists that contain the values {@code ["Hello World"]} and
|
||||
* {@code ["fn0", "fn1", "fn2"]}, respectively.
|
||||
* <pre>
|
||||
* class A {
|
||||
* B fn0 = new B();
|
||||
* }
|
||||
* class B {
|
||||
* C fn1 = new C();
|
||||
* }
|
||||
* class C {
|
||||
* {@code @Comment("Hello world")}
|
||||
* int fn2;
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param comments
|
||||
* @param fieldNames
|
||||
*/
|
||||
record CommentNode(List<String> comments, List<String> fieldNames) {}
|
||||
|
@ -0,0 +1,83 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
|
||||
final class CommentNodeExtractor {
|
||||
private final FieldFilter fieldFilter;
|
||||
private final FieldFormatter fieldFormatter;
|
||||
private final boolean outputNull;
|
||||
|
||||
CommentNodeExtractor(ConfigurationProperties properties) {
|
||||
this.fieldFilter = Validator.requireNonNull(properties.getFieldFilter(), "field filter");
|
||||
this.fieldFormatter = Validator.requireNonNull(properties.getFieldFormatter(), "field formatter");
|
||||
this.outputNull = properties.outputNulls();
|
||||
}
|
||||
|
||||
private record State(Iterator<Field> iterator, Object configuration) {}
|
||||
|
||||
/**
|
||||
* Extracts {@code CommentNode}s of the given configuration in a DFS manner.
|
||||
* The nodes are returned in the order in which they were found.
|
||||
*
|
||||
* @param configuration the configuration from which the nodes are extracted
|
||||
* @return the nodes in the order in which they are found
|
||||
* @throws IllegalArgumentException if {@code configuration} is not a configuration
|
||||
* @throws NullPointerException if {@code configuration} is null
|
||||
*/
|
||||
public Queue<CommentNode> extractCommentNodes(final Object configuration) {
|
||||
Validator.requireConfiguration(configuration.getClass());
|
||||
final Queue<CommentNode> result = new ArrayDeque<>();
|
||||
final var fnameStack = new ArrayDeque<>(List.of(""));
|
||||
final var stateStack = new ArrayDeque<>(List.of(
|
||||
new State(configurationFields(configuration), configuration)
|
||||
));
|
||||
|
||||
State state;
|
||||
while (!stateStack.isEmpty()) {
|
||||
state = stateStack.removeLast();
|
||||
fnameStack.removeLast();
|
||||
|
||||
while (state.iterator.hasNext()) {
|
||||
final var field = state.iterator.next();
|
||||
final var value = Reflect.getValue(field, state.configuration);
|
||||
|
||||
if ((value == null) && !outputNull)
|
||||
continue;
|
||||
|
||||
final var commentNode = createNodeIfFieldHasComment(field, fnameStack);
|
||||
commentNode.ifPresent(result::add);
|
||||
|
||||
if ((value == null) || !Reflect.isConfiguration(field.getType()))
|
||||
continue;
|
||||
|
||||
stateStack.addLast(new State(state.iterator, state.configuration));
|
||||
fnameStack.addLast(fieldFormatter.format(field));
|
||||
state = new State(configurationFields(value), value);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Optional<CommentNode> createNodeIfFieldHasComment(
|
||||
Field field,
|
||||
Deque<String> fileNameStack
|
||||
) {
|
||||
if (field.isAnnotationPresent(Comment.class)) {
|
||||
final var comments = field.getAnnotation(Comment.class).value();
|
||||
final var fieldName = fieldFormatter.format(field);
|
||||
final var fieldNames = new ArrayList<>(fileNameStack.stream().toList());
|
||||
fieldNames.add(fieldName);
|
||||
final var result = new CommentNode(Arrays.asList(comments), fieldNames);
|
||||
return Optional.of(result);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private Iterator<Field> configurationFields(Object configuration) {
|
||||
return FieldExtractors.CONFIGURATION.extract(configuration.getClass())
|
||||
.filter(fieldFilter)
|
||||
.iterator();
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.lang.annotation.*;
|
||||
|
||||
/**
|
||||
* Indicates the annotated type is a configuration.
|
||||
* <p>
|
||||
* Configuration classes must have a no-args constructor.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.TYPE)
|
||||
@Inherited
|
||||
public @interface Configuration {}
|
@ -1,9 +1,7 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
/**
|
||||
* Signals that an error occurred during the (de-)serialization of a configuration.
|
||||
* <p>
|
||||
* The cause of this exception is most likely some misconfiguration.
|
||||
* Signals that an error occurred during the serialization or deserialization of a configuration.
|
||||
*/
|
||||
public final class ConfigurationException extends RuntimeException {
|
||||
ConfigurationException(String message) {
|
@ -0,0 +1,223 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static de.exlll.configlib.Validator.requireNonNull;
|
||||
|
||||
/**
|
||||
* A collection of values used to configure the serialization of configurations.
|
||||
*/
|
||||
class ConfigurationProperties {
|
||||
private final Map<Class<?>, Serializer<?, ?>> serializersByType;
|
||||
private final FieldFormatter formatter;
|
||||
private final FieldFilter filter;
|
||||
private final boolean outputNulls;
|
||||
private final boolean inputNulls;
|
||||
private final boolean serializeSetsAsLists;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of this class with values taken from the given builder.
|
||||
*
|
||||
* @param builder the builder used to initialize the fields of this class
|
||||
* @throws NullPointerException if the builder or any of its values is null
|
||||
*/
|
||||
protected ConfigurationProperties(Builder<?> builder) {
|
||||
this.serializersByType = Map.copyOf(builder.serializersByType);
|
||||
this.formatter = requireNonNull(builder.formatter, "field formatter");
|
||||
this.filter = requireNonNull(builder.filter, "field filter");
|
||||
this.outputNulls = builder.outputNulls;
|
||||
this.inputNulls = builder.inputNulls;
|
||||
this.serializeSetsAsLists = builder.serializeSetsAsLists;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code Builder} with default values.
|
||||
*
|
||||
* @return newly constructed {@code Builder}
|
||||
*/
|
||||
public static Builder<?> newBuilder() {
|
||||
return new BuilderImpl();
|
||||
}
|
||||
|
||||
private static final class BuilderImpl extends Builder<BuilderImpl> {
|
||||
@Override
|
||||
protected BuilderImpl getThis() {return this;}
|
||||
|
||||
@Override
|
||||
public ConfigurationProperties build() {return new ConfigurationProperties(this);}
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder class for constructing {@code ConfigurationProperties}.
|
||||
*
|
||||
* @param <B> the type of builder
|
||||
*/
|
||||
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 FieldFormatter formatter = FieldFormatters.IDENTITY;
|
||||
private FieldFilter filter = FieldFilters.DEFAULT;
|
||||
private boolean outputNulls = false;
|
||||
private boolean inputNulls = false;
|
||||
private boolean serializeSetsAsLists = true;
|
||||
|
||||
protected Builder() {}
|
||||
|
||||
/**
|
||||
* Sets the field filter. The given filter is applied in addition to and
|
||||
* after the default filter.
|
||||
*
|
||||
* @param filter the filter
|
||||
* @return this builder
|
||||
* @throws NullPointerException if {@code filter} is null
|
||||
*/
|
||||
public final B setFieldFilter(FieldFilter filter) {
|
||||
this.filter = requireNonNull(filter, "field filter");
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the field formatter.
|
||||
* <p>
|
||||
* The default value is a formatter that returns the name of the field.
|
||||
*
|
||||
* @param formatter the formatter
|
||||
* @return this builder
|
||||
* @throws NullPointerException if {@code formatter} is null
|
||||
*/
|
||||
public final B setFieldFormatter(FieldFormatter formatter) {
|
||||
this.formatter = requireNonNull(formatter, "field formatter");
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a serializer for the given type. If this library already provides a serializer
|
||||
* for the given type (e.g. {@code BigInteger}, {@code LocalDate}, etc.) the serializer
|
||||
* added by this method takes precedence.
|
||||
*
|
||||
* @param serializedType the class of the type that is serialized
|
||||
* @param serializer the serializer
|
||||
* @param <T> the type that is serialized
|
||||
* @return this builder
|
||||
* @throws NullPointerException if any argument is null
|
||||
*/
|
||||
public final <T> B addSerializer(Class<T> serializedType, Serializer<T, ?> serializer) {
|
||||
requireNonNull(serializedType, "serialized type");
|
||||
requireNonNull(serializer, "serializer");
|
||||
serializersByType.put(serializedType, serializer);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether fields or collection elements whose value is null should be output
|
||||
* while serializing the configuration.
|
||||
* <p>
|
||||
* The default value is {@code false}.
|
||||
*
|
||||
* @param outputNulls whether to output null values
|
||||
* @return this builder
|
||||
*/
|
||||
public final B outputNulls(boolean outputNulls) {
|
||||
this.outputNulls = outputNulls;
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether fields or collection elements should allow null values to bet set
|
||||
* while deserializing the configuration.
|
||||
* <p>
|
||||
* The default value is {@code false}.
|
||||
*
|
||||
* @param inputNulls whether to input null values
|
||||
* @return this builder
|
||||
*/
|
||||
public final B inputNulls(boolean inputNulls) {
|
||||
this.inputNulls = inputNulls;
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether sets should be serialized as lists.
|
||||
* <p>
|
||||
* The default value is {@code true}.
|
||||
*
|
||||
* @param serializeSetsAsLists whether to serialize sets as lists
|
||||
* @return this builder
|
||||
*/
|
||||
final B serializeSetsAsLists(boolean serializeSetsAsLists) {
|
||||
this.serializeSetsAsLists = serializeSetsAsLists;
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a {@code ConfigurationProperties} instance.
|
||||
*
|
||||
* @return newly constructed {@code ConfigurationProperties}
|
||||
*/
|
||||
public abstract ConfigurationProperties build();
|
||||
|
||||
/**
|
||||
* Returns this builder.
|
||||
*
|
||||
* @return this builder
|
||||
*/
|
||||
protected abstract B getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the field filter used to filter the fields of a configuration.
|
||||
*
|
||||
* @return the field filter
|
||||
*/
|
||||
public final FieldFilter getFieldFilter() {
|
||||
return filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the field formatter used to format the fields of a configuration.
|
||||
*
|
||||
* @return the formatter
|
||||
*/
|
||||
public final FieldFormatter getFieldFormatter() {
|
||||
return formatter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable map of serializers by type. The serializers returned by this
|
||||
* method take precedence over any default serializers provided by this library.
|
||||
*
|
||||
* @return serializers by type
|
||||
*/
|
||||
public final Map<Class<?>, Serializer<?, ?>> getSerializers() {
|
||||
return serializersByType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether null values should be output.
|
||||
*
|
||||
* @return whether to output null values
|
||||
*/
|
||||
public final boolean outputNulls() {
|
||||
return outputNulls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether null values should be allowed as input.
|
||||
*
|
||||
* @return whether to input null values
|
||||
*/
|
||||
public final boolean inputNulls() {
|
||||
return inputNulls;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether sets should be serialized as lists.
|
||||
*
|
||||
* @return whether to serialize sets as lists
|
||||
*/
|
||||
final boolean serializeSetsAsLists() {
|
||||
return serializeSetsAsLists;
|
||||
}
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
final class ConfigurationSerializer<T> implements Serializer<T, Map<?, ?>> {
|
||||
private final Class<T> configurationType;
|
||||
private final ConfigurationProperties properties;
|
||||
private final Map<String, Serializer<?, ?>> serializers;
|
||||
|
||||
ConfigurationSerializer(Class<T> configurationType, ConfigurationProperties properties) {
|
||||
this.configurationType = Validator.requireConfiguration(configurationType);
|
||||
this.properties = properties;
|
||||
this.serializers = buildSerializerMap();
|
||||
requireSerializableFields();
|
||||
}
|
||||
|
||||
private void requireSerializableFields() {
|
||||
if (serializers.isEmpty()) {
|
||||
String msg = "Configuration class '" + configurationType.getSimpleName() + "' " +
|
||||
"does not contain any (de-)serializable fields.";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<?, ?> serialize(T element) {
|
||||
final Map<String, Object> result = new LinkedHashMap<>();
|
||||
|
||||
for (final Field field : filterFields()) {
|
||||
final Object fieldValue = Reflect.getValue(field, element);
|
||||
|
||||
if ((fieldValue == null) && !properties.outputNulls())
|
||||
continue;
|
||||
|
||||
final Object serializedValue = serialize(field, fieldValue);
|
||||
|
||||
final String formattedField = properties.getFieldFormatter().format(field);
|
||||
result.put(formattedField, serializedValue);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private Object serialize(Field field, Object fieldValue) {
|
||||
// The following cast won't cause a ClassCastException because we select the
|
||||
// serializers based on the field type.
|
||||
@SuppressWarnings("unchecked")
|
||||
final Serializer<Object, Object> serializer =
|
||||
(Serializer<Object, Object>) serializers.get(field.getName());
|
||||
|
||||
return (fieldValue != null) ? serializer.serialize(fieldValue) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T deserialize(Map<?, ?> element) {
|
||||
final T result = Reflect.newInstance(configurationType);
|
||||
for (final Field field : filterFields()) {
|
||||
final String formattedField = properties.getFieldFormatter().format(field);
|
||||
|
||||
if (!element.containsKey(formattedField))
|
||||
continue;
|
||||
|
||||
final Object value = element.get(formattedField);
|
||||
|
||||
if (value == null && properties.inputNulls()) {
|
||||
requireNonPrimitiveFieldType(field);
|
||||
Reflect.setValue(field, result, null);
|
||||
} else if (value != null) {
|
||||
final Object deserialized = deserialize(field, value);
|
||||
Reflect.setValue(field, result, deserialized);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Object deserialize(Field field, Object value) {
|
||||
// This unchecked cast leads to an exception if the type of the object which
|
||||
// is deserialized is not a subtype of the type the deserializer expects.
|
||||
@SuppressWarnings("unchecked")
|
||||
final Serializer<Object, Object> serializer =
|
||||
(Serializer<Object, Object>) serializers.get(field.getName());
|
||||
|
||||
final Object deserialized;
|
||||
try {
|
||||
deserialized = serializer.deserialize(value);
|
||||
} catch (ClassCastException e) {
|
||||
String msg = baseDeserializeExceptionMessage(field, value) + "\n" +
|
||||
"The type of the object to be deserialized does not " +
|
||||
"match the type the deserializer expects.";
|
||||
throw new ConfigurationException(msg, e);
|
||||
} catch (RuntimeException e) {
|
||||
String msg = baseDeserializeExceptionMessage(field, value);
|
||||
throw new ConfigurationException(msg, e);
|
||||
}
|
||||
return deserialized;
|
||||
}
|
||||
|
||||
private static String baseDeserializeExceptionMessage(Field field, Object value) {
|
||||
return "Deserialization of value '" + value + "' with type " + value.getClass() + " " +
|
||||
"for field " + field + " failed.";
|
||||
}
|
||||
|
||||
private static void requireNonPrimitiveFieldType(Field field) {
|
||||
if (field.getType().isPrimitive()) {
|
||||
String msg = "Cannot set " + field + " to null value.\n" +
|
||||
"Primitive types cannot be assigned null.";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private List<Field> filterFields() {
|
||||
return FieldExtractors.CONFIGURATION.extract(configurationType)
|
||||
.filter(properties.getFieldFilter())
|
||||
.toList();
|
||||
}
|
||||
|
||||
Map<String, Serializer<?, ?>> buildSerializerMap() {
|
||||
SerializerSelector selector = new SerializerSelector(properties);
|
||||
try {
|
||||
return filterFields().stream()
|
||||
.collect(Collectors.toMap(
|
||||
Field::getName,
|
||||
field -> selector.select(field.getGenericType()),
|
||||
(serializer1, serializer2) -> serializer2
|
||||
));
|
||||
} catch (StackOverflowError error) {
|
||||
String msg = "Recursive type definitions are not supported.";
|
||||
throw new ConfigurationException(msg, error);
|
||||
}
|
||||
}
|
||||
|
||||
Class<T> getConfigurationType() {
|
||||
return configurationType;
|
||||
}
|
||||
}
|
@ -0,0 +1,236 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* This class contains convenience methods for loading, saving, and updating configurations.
|
||||
*/
|
||||
public final class Configurations {
|
||||
private Configurations() {}
|
||||
|
||||
/**
|
||||
* Loads a configuration of the given type from the specified YAML file using a
|
||||
* {@code YamlConfigurationProperties} object with default values.
|
||||
*
|
||||
* @param configurationFile the file the configuration is loaded from
|
||||
* @param configurationType the type of configuration
|
||||
* @param <T> the configuration type
|
||||
* @return a newly created configuration initialized with values taken from the configuration file
|
||||
* @throws ConfigurationException if the configuration cannot be deserialized
|
||||
* @throws IllegalArgumentException if the file does not exist or is not a regular file
|
||||
* @throws NullPointerException if any parameter is null
|
||||
* @throws RuntimeException if reading the configuration throws an exception
|
||||
* @see YamlConfigurationStore#load(Path)
|
||||
*/
|
||||
public static <T> T loadYamlConfiguration(
|
||||
Path configurationFile,
|
||||
Class<T> configurationType
|
||||
) {
|
||||
final var properties = YamlConfigurationProperties.newBuilder().build();
|
||||
return loadYamlConfiguration(configurationFile, configurationType, properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a configuration of the given type from the specified YAML file using a
|
||||
* {@code YamlConfigurationProperties} object that is built by a builder. The builder is
|
||||
* initialized with default values and can be configured by the {@code propertiesConfigurer}.
|
||||
*
|
||||
* @param configurationFile the file the configuration is loaded from
|
||||
* @param configurationType the type of configuration
|
||||
* @param propertiesConfigurer the consumer used to configure the builder
|
||||
* @param <T> the configuration type
|
||||
* @return a newly created configuration initialized with values taken from the configuration file
|
||||
* @throws ConfigurationException if the configuration cannot be deserialized
|
||||
* @throws IllegalArgumentException if the file does not exist or is not a regular file
|
||||
* @throws NullPointerException if any parameter is null
|
||||
* @throws RuntimeException if reading the configuration throws an exception
|
||||
* @see YamlConfigurationStore#load(Path)
|
||||
*/
|
||||
public static <T> T loadYamlConfiguration(
|
||||
Path configurationFile,
|
||||
Class<T> configurationType,
|
||||
Consumer<YamlConfigurationProperties.Builder<?>> propertiesConfigurer
|
||||
) {
|
||||
final var builder = YamlConfigurationProperties.newBuilder();
|
||||
propertiesConfigurer.accept(builder);
|
||||
return loadYamlConfiguration(configurationFile, configurationType, builder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a configuration of the given type from the specified YAML file using the given
|
||||
* {@code YamlConfigurationProperties} object.
|
||||
*
|
||||
* @param configurationFile the file the configuration is loaded from
|
||||
* @param configurationType the type of configuration
|
||||
* @param properties the configuration properties
|
||||
* @param <T> the configuration type
|
||||
* @return a newly created configuration initialized with values taken from the configuration file
|
||||
* @throws ConfigurationException if the configuration cannot be deserialized
|
||||
* @throws IllegalArgumentException if the file does not exist or is not a regular file
|
||||
* @throws NullPointerException if any parameter is null
|
||||
* @throws RuntimeException if reading the configuration throws an exception
|
||||
* @see YamlConfigurationStore#load(Path)
|
||||
*/
|
||||
public static <T> T loadYamlConfiguration(
|
||||
Path configurationFile,
|
||||
Class<T> configurationType,
|
||||
YamlConfigurationProperties properties
|
||||
) {
|
||||
final var store = new YamlConfigurationStore<>(configurationType, properties);
|
||||
return store.load(configurationFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a YAML configuration file with a configuration of the given type using a
|
||||
* {@code YamlConfigurationProperties} object with default values.
|
||||
* <p>
|
||||
* See {@link YamlConfigurationStore#save(Object, Path)} for an explanation of how the update is
|
||||
* done.
|
||||
*
|
||||
* @param configurationFile the configuration file that is updated
|
||||
* @param configurationType the type of configuration
|
||||
* @param <T> the configuration type
|
||||
* @return a newly created configuration initialized with values taken from the configuration file
|
||||
* @throws ConfigurationException if the configuration cannot be deserialized
|
||||
* @throws NullPointerException if any argument is null
|
||||
* @throws RuntimeException if loading or saving the configuration throws an exception
|
||||
* @see YamlConfigurationStore#update(Path)
|
||||
*/
|
||||
public static <T> T updateYamlConfiguration(
|
||||
Path configurationFile,
|
||||
Class<T> configurationType
|
||||
) {
|
||||
final var properties = YamlConfigurationProperties.newBuilder().build();
|
||||
return updateYamlConfiguration(configurationFile, configurationType, properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a YAML configuration file with a configuration of the given type using a
|
||||
* {@code YamlConfigurationProperties} object that is built by a builder. The builder is
|
||||
* initialized with default values and can be configured by the {@code propertiesConfigurer}.
|
||||
* <p>
|
||||
* See {@link YamlConfigurationStore#save(Object, Path)} for an explanation of how the update is
|
||||
* done.
|
||||
*
|
||||
* @param configurationFile the configuration file that is updated
|
||||
* @param configurationType the type of configuration
|
||||
* @param propertiesConfigurer the consumer used to configure the builder
|
||||
* @param <T> the configuration type
|
||||
* @return a newly created configuration initialized with values taken from the configuration file
|
||||
* @throws ConfigurationException if the configuration cannot be deserialized
|
||||
* @throws NullPointerException if any argument is null
|
||||
* @throws RuntimeException if loading or saving the configuration throws an exception
|
||||
* @see YamlConfigurationStore#update(Path)
|
||||
*/
|
||||
public static <T> T updateYamlConfiguration(
|
||||
Path configurationFile,
|
||||
Class<T> configurationType,
|
||||
Consumer<YamlConfigurationProperties.Builder<?>> propertiesConfigurer
|
||||
) {
|
||||
final var builder = YamlConfigurationProperties.newBuilder();
|
||||
propertiesConfigurer.accept(builder);
|
||||
return updateYamlConfiguration(configurationFile, configurationType, builder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a YAML configuration file with a configuration of the given type using the given
|
||||
* {@code YamlConfigurationProperties} object.
|
||||
* <p>
|
||||
* See {@link YamlConfigurationStore#save(Object, Path)} for an explanation of how the update is
|
||||
* done.
|
||||
*
|
||||
* @param configurationFile the configuration file that is updated
|
||||
* @param configurationType the type of configuration
|
||||
* @param properties the configuration properties
|
||||
* @param <T> the configuration type
|
||||
* @return a newly created configuration initialized with values taken from the configuration file
|
||||
* @throws ConfigurationException if the configuration cannot be deserialized
|
||||
* @throws NullPointerException if any argument is null
|
||||
* @throws RuntimeException if loading or saving the configuration throws an exception
|
||||
* @see YamlConfigurationStore#update(Path)
|
||||
*/
|
||||
public static <T> T updateYamlConfiguration(
|
||||
Path configurationFile,
|
||||
Class<T> configurationType,
|
||||
YamlConfigurationProperties properties
|
||||
) {
|
||||
final var store = new YamlConfigurationStore<>(configurationType, properties);
|
||||
return store.update(configurationFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a configuration of the given type to the specified YAML file using a
|
||||
* {@code YamlConfigurationProperties} object with default values.
|
||||
*
|
||||
* @param configuration the configuration that is saved
|
||||
* @param configurationType the type of configuration
|
||||
* @param configurationFile the file the configuration is saved to
|
||||
* @param <T> the configuration type
|
||||
* @throws ConfigurationException if the configuration contains invalid values or
|
||||
* cannot be serialized
|
||||
* @throws NullPointerException if any argument is null
|
||||
* @throws RuntimeException if writing the configuration throws an exception
|
||||
* @see YamlConfigurationStore#save(Object, Path)
|
||||
*/
|
||||
public static <T> void saveYamlConfiguration(
|
||||
Path configurationFile,
|
||||
Class<T> configurationType,
|
||||
T configuration
|
||||
) {
|
||||
final var properties = YamlConfigurationProperties.newBuilder().build();
|
||||
saveYamlConfiguration(configurationFile, configurationType, configuration, properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a configuration of the given type to the specified YAML file using a
|
||||
* {@code YamlConfigurationProperties} object that is built by a builder. The builder is
|
||||
* initialized with default values and can be configured by the {@code propertiesConfigurer}.
|
||||
*
|
||||
* @param configuration the configuration that is saved
|
||||
* @param configurationType the type of configuration
|
||||
* @param configurationFile the file the configuration is saved to
|
||||
* @param propertiesConfigurer the consumer used to configure the builder
|
||||
* @param <T> the configuration type
|
||||
* @throws ConfigurationException if the configuration contains invalid values or
|
||||
* cannot be serialized
|
||||
* @throws NullPointerException if any argument is null
|
||||
* @throws RuntimeException if writing the configuration throws an exception
|
||||
* @see YamlConfigurationStore#save(Object, Path)
|
||||
*/
|
||||
public static <T> void saveYamlConfiguration(
|
||||
Path configurationFile,
|
||||
Class<T> configurationType,
|
||||
T configuration,
|
||||
Consumer<YamlConfigurationProperties.Builder<?>> propertiesConfigurer
|
||||
) {
|
||||
final var builder = YamlConfigurationProperties.newBuilder();
|
||||
propertiesConfigurer.accept(builder);
|
||||
saveYamlConfiguration(configurationFile, configurationType, configuration, builder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves a configuration of the given type to the specified YAML file using the given
|
||||
* {@code YamlConfigurationProperties} object.
|
||||
*
|
||||
* @param configuration the configuration that is saved
|
||||
* @param configurationType the type of configuration
|
||||
* @param configurationFile the file the configuration is saved to
|
||||
* @param properties the configuration properties
|
||||
* @param <T> the configuration type
|
||||
* @throws ConfigurationException if the configuration contains invalid values or
|
||||
* cannot be serialized
|
||||
* @throws NullPointerException if any argument is null
|
||||
* @throws RuntimeException if writing the configuration throws an exception
|
||||
* @see YamlConfigurationStore#save(Object, Path)
|
||||
*/
|
||||
public static <T> void saveYamlConfiguration(
|
||||
Path configurationFile,
|
||||
Class<T> configurationType,
|
||||
T configuration,
|
||||
YamlConfigurationProperties properties
|
||||
) {
|
||||
final var store = new YamlConfigurationStore<>(configurationType, properties);
|
||||
store.save(configuration, configurationFile);
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* Implementations of this interface extract the fields of classes.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface FieldExtractor extends Function<Class<?>, Stream<Field>> {
|
||||
/**
|
||||
* Extracts the fields of a class.
|
||||
*
|
||||
* @param cls the class
|
||||
* @return a stream of fields
|
||||
* @throws NullPointerException if {@code cls} is null
|
||||
*/
|
||||
Stream<Field> extract(Class<?> cls);
|
||||
|
||||
@Override
|
||||
default Stream<Field> apply(Class<?> cls) {
|
||||
return extract(cls);
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.*;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
enum FieldExtractors implements FieldExtractor {
|
||||
/**
|
||||
* Extracts the declared fields of the given configuration and all its super classes up to the
|
||||
* first class that is not a configuration.
|
||||
* <p>
|
||||
* The order of the fields is reversed such that the field of super classes are listed first.
|
||||
*/
|
||||
CONFIGURATION {
|
||||
@Override
|
||||
public Stream<Field> extract(Class<?> cls) {
|
||||
Validator.requireNonNull(cls, "configuration class");
|
||||
Validator.requireConfiguration(cls);
|
||||
|
||||
List<Class<?>> classes = extractClassesWhile(cls, Reflect::isConfiguration);
|
||||
List<Field> fields = classes.stream()
|
||||
.flatMap(c -> Arrays.stream(c.getDeclaredFields()))
|
||||
.filter(FieldFilters.DEFAULT)
|
||||
.toList();
|
||||
requireNoShadowing(fields);
|
||||
return fields.stream();
|
||||
}
|
||||
};
|
||||
|
||||
private static void requireNoShadowing(List<Field> fields) {
|
||||
Map<String, Class<?>> map = new LinkedHashMap<>();
|
||||
|
||||
for (Field field : fields) {
|
||||
var fieldName = field.getName();
|
||||
var fieldClass = field.getDeclaringClass();
|
||||
|
||||
if (map.containsKey(fieldName)) {
|
||||
Class<?> superClass = map.get(fieldName);
|
||||
String msg = "Shadowing of fields is not supported. Field '" + fieldName + "' " +
|
||||
"of class " + fieldClass.getSimpleName() + " shadows field '" +
|
||||
fieldName + "' of class " + superClass.getSimpleName() + ".";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
map.put(fieldName, fieldClass);
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Class<?>> extractClassesWhile(Class<?> cls, Predicate<Class<?>> condition) {
|
||||
List<Class<?>> classes = new ArrayList<>();
|
||||
Class<?> current = cls;
|
||||
while (condition.test(current)) {
|
||||
classes.add(current);
|
||||
current = current.getSuperclass();
|
||||
}
|
||||
Collections.reverse(classes); // we want the fields of the super classes to come first
|
||||
return classes;
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Implementations of this interface test fields for specific conditions.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface FieldFilter extends Predicate<Field> {
|
||||
@Override
|
||||
default FieldFilter and(Predicate<? super Field> other) {
|
||||
return field -> test(field) && other.test(field);
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
enum FieldFilters implements FieldFilter {
|
||||
/**
|
||||
* The default {@code FieldFilter} that rejects fields that are final, static,
|
||||
* synthetic, transient, or annotated with {@code @Ignore}.
|
||||
*/
|
||||
DEFAULT {
|
||||
@Override
|
||||
public boolean test(Field field) {
|
||||
Validator.requireNonNull(field, "field");
|
||||
|
||||
if (field.isSynthetic())
|
||||
return false;
|
||||
if (Reflect.isIgnored(field))
|
||||
return false;
|
||||
|
||||
int modifiers = field.getModifiers();
|
||||
return !Modifier.isFinal(modifiers) &&
|
||||
!Modifier.isStatic(modifiers) &&
|
||||
!Modifier.isTransient(modifiers);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Implementations of this interface format the names of fields.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface FieldFormatter extends Function<Field, String> {
|
||||
/**
|
||||
* Formats the name of the given field.
|
||||
*
|
||||
* @param field the field
|
||||
* @return formatted field name
|
||||
* @throws NullPointerException if {@code field} is null
|
||||
*/
|
||||
String format(Field field);
|
||||
|
||||
@Override
|
||||
default String apply(Field field) {
|
||||
return format(field);
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
/**
|
||||
* This class contains instances of ready-to-use {@code FieldFormatter}s.
|
||||
*/
|
||||
public enum FieldFormatters implements FieldFormatter {
|
||||
/**
|
||||
* A {@code FieldFormatter} that returns the name of the field.
|
||||
*/
|
||||
IDENTITY {
|
||||
@Override
|
||||
public String format(Field field) {
|
||||
return field.getName();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* A {@code FieldFormatter} that transforms <i>camelCase</i> to
|
||||
* <i>lower_underscore</i>.
|
||||
* <p>
|
||||
* For example, <i>myPrivateField</i> becomes <i>my_private_field</i>.
|
||||
*/
|
||||
LOWER_UNDERSCORE {
|
||||
@Override
|
||||
public String format(Field field) {
|
||||
String fn = field.getName();
|
||||
StringBuilder builder = new StringBuilder(fn.length());
|
||||
for (char c : fn.toCharArray()) {
|
||||
if (Character.isUpperCase(c)) {
|
||||
char lower = Character.toLowerCase(c);
|
||||
builder.append('_').append(lower);
|
||||
} else builder.append(c);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
},
|
||||
/**
|
||||
* A {@code FieldFormatter} that transforms <i>camelCase</i> to
|
||||
* <i>UPPER_UNDERSCORE</i>.
|
||||
* <p>
|
||||
* For example, <i>myPrivateField</i> becomes <i>MY_PRIVATE_FIELD</i>.
|
||||
*/
|
||||
UPPER_UNDERSCORE {
|
||||
@Override
|
||||
public String format(Field field) {
|
||||
String fn = field.getName();
|
||||
StringBuilder builder = new StringBuilder(fn.length());
|
||||
for (char c : fn.toCharArray()) {
|
||||
if (Character.isLowerCase(c)) {
|
||||
builder.append(Character.toUpperCase(c));
|
||||
} else if (Character.isUpperCase(c)) {
|
||||
builder.append('_').append(c);
|
||||
} else builder.append(c);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,137 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
/**
|
||||
* An extension of the {@code ConfigurationProperties} class that allows configuring properties
|
||||
* that are more specific to files.
|
||||
*/
|
||||
public class FileConfigurationProperties extends ConfigurationProperties {
|
||||
private final String header;
|
||||
private final String footer;
|
||||
private final boolean createParentDirectories;
|
||||
|
||||
/**
|
||||
* Constructs a new instance of this class with values taken from the given builder.
|
||||
*
|
||||
* @param builder the builder used to initialize the fields of this class
|
||||
* @throws NullPointerException if the builder or any of its values is null
|
||||
*/
|
||||
protected FileConfigurationProperties(Builder<?> builder) {
|
||||
super(builder);
|
||||
this.header = builder.header;
|
||||
this.footer = builder.footer;
|
||||
this.createParentDirectories = builder.createParentDirectories;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code Builder} with default values.
|
||||
*
|
||||
* @return newly constructed {@code Builder}
|
||||
*/
|
||||
public static Builder<?> newBuilder() {
|
||||
return new BuilderImpl();
|
||||
}
|
||||
|
||||
private static final class BuilderImpl extends Builder<BuilderImpl> {
|
||||
@Override
|
||||
protected BuilderImpl getThis() {return this;}
|
||||
|
||||
@Override
|
||||
public FileConfigurationProperties build() {return new FileConfigurationProperties(this);}
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder class for constructing {@code FileConfigurationProperties}.
|
||||
*
|
||||
* @param <B> the type of builder
|
||||
*/
|
||||
public static abstract class Builder<B extends Builder<B>>
|
||||
extends ConfigurationProperties.Builder<B> {
|
||||
private String header = null;
|
||||
private String footer = null;
|
||||
private boolean createParentDirectories = true;
|
||||
|
||||
/**
|
||||
* A constructor that can be overridden by subclasses.
|
||||
*/
|
||||
protected Builder() {}
|
||||
|
||||
/**
|
||||
* Sets the header. The header is written as a comment before the actual configuration.
|
||||
* If the header is set to null (the default), nothing is written.
|
||||
*
|
||||
* @param header the header
|
||||
* @return this builder
|
||||
*/
|
||||
public final B header(String header) {
|
||||
this.header = header;
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the footer. The footer is written as a comment after the actual configuration.
|
||||
* If the footer is set to null (the default), nothing is written.
|
||||
*
|
||||
* @param footer the footer
|
||||
* @return this builder
|
||||
*/
|
||||
public final B footer(String footer) {
|
||||
this.footer = footer;
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether parent directories of a configuration file should be created.
|
||||
* <p>
|
||||
* The default value is {@code true}.
|
||||
*
|
||||
* @param createParentDirectories whether to create parent directories
|
||||
* @return this builder
|
||||
*/
|
||||
public final B createParentDirectories(boolean createParentDirectories) {
|
||||
this.createParentDirectories = createParentDirectories;
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a {@code ConfigurationProperties} instance.
|
||||
*
|
||||
* @return newly constructed {@code ConfigurationProperties}
|
||||
*/
|
||||
public abstract FileConfigurationProperties build();
|
||||
|
||||
/**
|
||||
* Returns this builder.
|
||||
*
|
||||
* @return this builder
|
||||
*/
|
||||
protected abstract B getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the header.
|
||||
*
|
||||
* @return the header
|
||||
*/
|
||||
public final String getHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the footer.
|
||||
*
|
||||
* @return the footer
|
||||
*/
|
||||
public final String getFooter() {
|
||||
return footer;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns whether to create parent directories.
|
||||
*
|
||||
* @return whether to create parent directories
|
||||
*/
|
||||
public final boolean createParentDirectories() {
|
||||
return createParentDirectories;
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Instances of this class save and load configurations using files.
|
||||
*
|
||||
* @param <T> the configuration type
|
||||
*/
|
||||
public interface FileConfigurationStore<T> {
|
||||
/**
|
||||
* Saves a configuration instance to the given file.
|
||||
*
|
||||
* @param configuration the configuration
|
||||
* @param configurationFile the file the configuration is saved to
|
||||
* @throws ConfigurationException if the configuration contains invalid values or
|
||||
* cannot be serialized
|
||||
* @throws NullPointerException if any argument is null
|
||||
* @throws RuntimeException if writing the configuration throws an exception
|
||||
*/
|
||||
void save(T configuration, Path configurationFile);
|
||||
|
||||
/**
|
||||
* Loads a configuration from the given file.
|
||||
*
|
||||
* @param configurationFile the file the configuration is loaded from
|
||||
* @return a newly created configuration initialized with values taken from the configuration file
|
||||
* @throws ConfigurationException if the configuration cannot be deserialized
|
||||
* @throws IllegalArgumentException if the file does not exist or is not a regular file
|
||||
* @throws NullPointerException if {@code configurationFile} is null
|
||||
* @throws RuntimeException if reading the configuration throws an exception
|
||||
*/
|
||||
T load(Path configurationFile);
|
||||
|
||||
/**
|
||||
* Updates the configuration file. If the file does not exist, it is created and populated
|
||||
* with the default values with which the fields of the configuration have been initialized.
|
||||
* Otherwise, a new configuration instance is created, initialized with the values taken from
|
||||
* the configuration file, and immediately saved to reflect possible changes of the
|
||||
* configuration type.
|
||||
*
|
||||
* @param configurationFile the configuration file that is updated
|
||||
* @return a newly created configuration initialized with values taken from the configuration file
|
||||
* @throws ConfigurationException if the configuration cannot be deserialized
|
||||
* @throws NullPointerException if {@code configurationFile} is null
|
||||
* @throws RuntimeException if loading or saving the configuration throws an exception
|
||||
*/
|
||||
T update(Path configurationFile);
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Indicates that the annotated field should not be serialized.
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface Ignore {}
|
@ -0,0 +1,110 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
final class Reflect {
|
||||
private Reflect() {}
|
||||
|
||||
static <T> T newInstance(Class<T> cls) {
|
||||
try {
|
||||
Constructor<T> constructor = cls.getDeclaredConstructor();
|
||||
constructor.setAccessible(true);
|
||||
return constructor.newInstance();
|
||||
} catch (NoSuchMethodException e) {
|
||||
String msg = "Class " + cls.getSimpleName() + " doesn't have a " +
|
||||
"no-args constructor.";
|
||||
throw new RuntimeException(msg, e);
|
||||
} catch (IllegalAccessException e) {
|
||||
/* This exception should not be thrown because
|
||||
* we set the constructor to be accessible. */
|
||||
String msg = "No-args constructor of class " + cls.getSimpleName() +
|
||||
" not accessible.";
|
||||
throw new RuntimeException(msg, e);
|
||||
} catch (InstantiationException e) {
|
||||
String msg = "Class " + cls.getSimpleName() + " is not instantiable.";
|
||||
throw new RuntimeException(msg, e);
|
||||
} catch (InvocationTargetException e) {
|
||||
String msg = "Constructor of class " + cls.getSimpleName() + " threw an exception.";
|
||||
throw new RuntimeException(msg, e);
|
||||
}
|
||||
}
|
||||
|
||||
static <T> T[] newArray(Class<T> componentType, int length) {
|
||||
// The following cast won't fail because we just created an array of that type
|
||||
@SuppressWarnings("unchecked")
|
||||
T[] array = (T[]) Array.newInstance(componentType, length);
|
||||
return array;
|
||||
}
|
||||
|
||||
static Object getValue(Field field, Object instance) {
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
return field.get(instance);
|
||||
} catch (IllegalAccessException e) {
|
||||
/* This exception should not be thrown because
|
||||
* we set the field to be accessible. */
|
||||
String msg = "Illegal access of field '" + field + "' " +
|
||||
"on object " + instance + ".";
|
||||
throw new RuntimeException(msg, e);
|
||||
}
|
||||
}
|
||||
|
||||
static void setValue(Field field, Object instance, Object value) {
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
field.set(instance, value);
|
||||
} catch (IllegalAccessException e) {
|
||||
/* This exception should not be thrown because
|
||||
* we set the field to be accessible. */
|
||||
String msg = "Illegal access of field '" + field + "' " +
|
||||
"on object " + instance + ".";
|
||||
throw new RuntimeException(msg, e);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean isIntegerType(Class<?> cls) {
|
||||
return (cls == byte.class) || (cls == Byte.class) ||
|
||||
(cls == short.class) || (cls == Short.class) ||
|
||||
(cls == int.class) || (cls == Integer.class) ||
|
||||
(cls == long.class) || (cls == Long.class);
|
||||
}
|
||||
|
||||
static boolean isFloatingPointType(Class<?> cls) {
|
||||
return (cls == float.class) || (cls == Float.class) ||
|
||||
(cls == double.class) || (cls == Double.class);
|
||||
}
|
||||
|
||||
static boolean isEnumType(Class<?> cls) {
|
||||
return cls.isEnum();
|
||||
}
|
||||
|
||||
static boolean isArrayType(Class<?> cls) {
|
||||
return cls.isArray();
|
||||
}
|
||||
|
||||
static boolean isListType(Class<?> cls) {
|
||||
return List.class.isAssignableFrom(cls);
|
||||
}
|
||||
|
||||
static boolean isSetType(Class<?> cls) {
|
||||
return Set.class.isAssignableFrom(cls);
|
||||
}
|
||||
|
||||
static boolean isMapType(Class<?> cls) {
|
||||
return Map.class.isAssignableFrom(cls);
|
||||
}
|
||||
|
||||
static boolean isConfiguration(Class<?> cls) {
|
||||
return cls.getAnnotation(Configuration.class) != null;
|
||||
}
|
||||
|
||||
static boolean isIgnored(Field field) {
|
||||
return field.getAnnotation(Ignore.class) != null;
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
/**
|
||||
* Implementations of this interface convert instances of type {@code T1} to a serializable type
|
||||
* {@code T2} and vice versa.
|
||||
* <p>
|
||||
* Which types {@code T2} are serializable depends on the underlying storage system. Currently,
|
||||
* all storage systems support the following types:
|
||||
* <ul>
|
||||
* <li>{@code Boolean}</li>
|
||||
* <li>{@code Long}</li>
|
||||
* <li>{@code Double}</li>
|
||||
* <li>{@code String}</li>
|
||||
* <li>(Nested) {@code List}s of the other types</li>
|
||||
* <li>(Nested) {@code Set}s of the other types</li>
|
||||
* <li>(Nested) {@code Map}s of the other types</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* That means that if you want to support all currently available configuration store formats,
|
||||
* your {@code Serializer} implementation should convert an object of type {@code T1} into one
|
||||
* of the seven types listed above.
|
||||
*
|
||||
* @param <T1> the type of the objects that should be serialized
|
||||
* @param <T2> the serializable type
|
||||
*/
|
||||
public interface Serializer<T1, T2> {
|
||||
/**
|
||||
* Serializes an element of type {@code T1} into an element of type {@code T2}.
|
||||
*
|
||||
* @param element the element of type {@code T1} that is serialized
|
||||
* @return the serialized element of type {@code T2}
|
||||
*/
|
||||
T2 serialize(T1 element);
|
||||
|
||||
/**
|
||||
* Deserializes an element of type {@code T2} into an element of type {@code T1}.
|
||||
*
|
||||
* @param element the element of type {@code T2} that is deserialized
|
||||
* @return the deserialized element of type {@code T1}
|
||||
*/
|
||||
T1 deserialize(T2 element);
|
||||
}
|
@ -0,0 +1,145 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import de.exlll.configlib.Serializers.*;
|
||||
|
||||
import java.lang.reflect.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.util.Map;
|
||||
|
||||
final class SerializerSelector {
|
||||
private static final Map<Class<?>, Serializer<?, ?>> DEFAULT_SERIALIZERS = Map.ofEntries(
|
||||
Map.entry(boolean.class, new BooleanSerializer()),
|
||||
Map.entry(Boolean.class, new BooleanSerializer()),
|
||||
Map.entry(byte.class, new NumberSerializer(byte.class)),
|
||||
Map.entry(Byte.class, new NumberSerializer(Byte.class)),
|
||||
Map.entry(short.class, new NumberSerializer(short.class)),
|
||||
Map.entry(Short.class, new NumberSerializer(Short.class)),
|
||||
Map.entry(int.class, new NumberSerializer(int.class)),
|
||||
Map.entry(Integer.class, new NumberSerializer(Integer.class)),
|
||||
Map.entry(long.class, new NumberSerializer(long.class)),
|
||||
Map.entry(Long.class, new NumberSerializer(Long.class)),
|
||||
Map.entry(float.class, new NumberSerializer(float.class)),
|
||||
Map.entry(Float.class, new NumberSerializer(Float.class)),
|
||||
Map.entry(double.class, new NumberSerializer(double.class)),
|
||||
Map.entry(Double.class, new NumberSerializer(Double.class)),
|
||||
Map.entry(char.class, new CharacterSerializer()),
|
||||
Map.entry(Character.class, new CharacterSerializer()),
|
||||
Map.entry(String.class, new StringSerializer()),
|
||||
Map.entry(BigInteger.class, new BigIntegerSerializer()),
|
||||
Map.entry(BigDecimal.class, new BigDecimalSerializer()),
|
||||
Map.entry(LocalDate.class, new LocalDateSerializer()),
|
||||
Map.entry(LocalTime.class, new LocalTimeSerializer()),
|
||||
Map.entry(LocalDateTime.class, new LocalDateTimeSerializer())
|
||||
);
|
||||
private final ConfigurationProperties properties;
|
||||
|
||||
public SerializerSelector(ConfigurationProperties properties) {
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
public Serializer<?, ?> select(Type type) {
|
||||
if (type instanceof Class<?> cls) {
|
||||
return selectForClass(cls);
|
||||
} else if (type instanceof ParameterizedType pType) {
|
||||
return selectForParameterizedType(pType);
|
||||
} else if (type instanceof WildcardType) {
|
||||
String msg = baseExceptionMessage(type) + "Wildcard types cannot be serialized.";
|
||||
throw new ConfigurationException(msg);
|
||||
} else if (type instanceof GenericArrayType) {
|
||||
String msg = baseExceptionMessage(type) + "Generic array types cannot be serialized.";
|
||||
throw new ConfigurationException(msg);
|
||||
} else if (type instanceof TypeVariable<?>) {
|
||||
String msg = baseExceptionMessage(type) + "Type variables cannot be serialized.";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
// should not happen as we covered all possible types
|
||||
throw new ConfigurationException(baseExceptionMessage(type));
|
||||
}
|
||||
|
||||
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)) {
|
||||
// The following cast won't fail because we just checked that it's an enum.
|
||||
@SuppressWarnings("unchecked")
|
||||
var enumCls = (Class<? extends Enum<?>>) cls;
|
||||
return new Serializers.EnumSerializer(enumCls);
|
||||
}
|
||||
if (Reflect.isArrayType(cls))
|
||||
return selectForArray(cls.getComponentType());
|
||||
if (Reflect.isConfiguration(cls))
|
||||
return new ConfigurationSerializer<>(cls, properties);
|
||||
|
||||
String msg = "Missing serializer for type " + cls + ".\n" +
|
||||
"Either annotate the type with @Configuration or provide a custom " +
|
||||
"serializer by adding it to the properties.";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
|
||||
private Serializer<?, ?> selectForArray(Class<?> elementType) {
|
||||
if (elementType == boolean.class) {
|
||||
return new PrimitiveBooleanArraySerializer();
|
||||
} else if (elementType == char.class) {
|
||||
return new PrimitiveCharacterArraySerializer();
|
||||
} else if (elementType == byte.class) {
|
||||
return new PrimitiveByteArraySerializer();
|
||||
} else if (elementType == short.class) {
|
||||
return new PrimitiveShortArraySerializer();
|
||||
} else if (elementType == int.class) {
|
||||
return new PrimitiveIntegerArraySerializer();
|
||||
} else if (elementType == long.class) {
|
||||
return new PrimitiveLongArraySerializer();
|
||||
} else if (elementType == float.class) {
|
||||
return new PrimitiveFloatArraySerializer();
|
||||
} else if (elementType == double.class) {
|
||||
return new PrimitiveDoubleArraySerializer();
|
||||
}
|
||||
var elementSerializer = select(elementType);
|
||||
var inputNulls = properties.inputNulls();
|
||||
var outputNulls = properties.outputNulls();
|
||||
return new ArraySerializer<>(elementType, elementSerializer, outputNulls, inputNulls);
|
||||
}
|
||||
|
||||
private Serializer<?, ?> selectForParameterizedType(ParameterizedType type) {
|
||||
// the raw type returned by Java is always a class
|
||||
final var rawType = (Class<?>) type.getRawType();
|
||||
final var typeArgs = type.getActualTypeArguments();
|
||||
final var inputNulls = properties.inputNulls();
|
||||
final var outputNulls = properties.outputNulls();
|
||||
|
||||
if (Reflect.isListType(rawType)) {
|
||||
var elementSerializer = select(typeArgs[0]);
|
||||
return new ListSerializer<>(elementSerializer, outputNulls, inputNulls);
|
||||
} else if (Reflect.isSetType(rawType)) {
|
||||
var elementSerializer = select(typeArgs[0]);
|
||||
return properties.serializeSetsAsLists()
|
||||
? new SetAsListSerializer<>(elementSerializer, outputNulls, inputNulls)
|
||||
: new SetSerializer<>(elementSerializer, outputNulls, inputNulls);
|
||||
} else if (Reflect.isMapType(rawType)) {
|
||||
if ((typeArgs[0] instanceof Class<?> cls) &&
|
||||
(DEFAULT_SERIALIZERS.containsKey(cls) ||
|
||||
Reflect.isEnumType(cls))) {
|
||||
var keySerializer = select(typeArgs[0]);
|
||||
var valSerializer = select(typeArgs[1]);
|
||||
return new MapSerializer<>(keySerializer, valSerializer, outputNulls, inputNulls);
|
||||
}
|
||||
String msg = baseExceptionMessage(type) +
|
||||
"Map keys can only be of simple or enum type.";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
|
||||
String msg = baseExceptionMessage(type) +
|
||||
"Parameterized types other than lists, sets, and maps cannot be serialized.";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
|
||||
private String baseExceptionMessage(Type type) {
|
||||
return "Cannot select serializer for type '" + type + "'.\n";
|
||||
}
|
||||
}
|
@ -0,0 +1,621 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.*;
|
||||
import java.util.function.IntFunction;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.IntStream;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
final class Serializers {
|
||||
private Serializers() {}
|
||||
|
||||
static final class BooleanSerializer implements Serializer<Boolean, Boolean> {
|
||||
@Override
|
||||
public Boolean serialize(Boolean element) {
|
||||
return element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean deserialize(Boolean element) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a primitive number or its wrapper type to another number type according to the
|
||||
* following rules:
|
||||
* <ul>
|
||||
* <li>
|
||||
* The {@code serialize} method converts all four integer types to {@code Long}, and
|
||||
* both floating point types to {@code Double}.
|
||||
* </li>
|
||||
* <li>
|
||||
* The {@code deserialize} method converts a number from any supported type to the
|
||||
* requested type if the value of the number fits in the range.
|
||||
* </li>
|
||||
* </ul>
|
||||
*/
|
||||
static final class NumberSerializer implements Serializer<Number, Number> {
|
||||
private final Class<? extends Number> cls;
|
||||
|
||||
public NumberSerializer(Class<? extends Number> cls) {
|
||||
this.cls = Validator.requireNonNull(cls, "number class");
|
||||
Validator.requirePrimitiveOrWrapperNumberType(cls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number serialize(Number element) {
|
||||
if (Reflect.isIntegerType(cls))
|
||||
return element.longValue();
|
||||
if (Reflect.isFloatingPointType(cls))
|
||||
return element.doubleValue();
|
||||
String msg = "Invalid element '" + element + "' with type " + element.getClass();
|
||||
throw new ConfigurationException(msg); // should not happen, types checked in ctor
|
||||
}
|
||||
|
||||
@Override
|
||||
public Number deserialize(Number element) {
|
||||
if (Reflect.isIntegerType(element.getClass())) {
|
||||
return deserializeFromIntegerType(element);
|
||||
}
|
||||
if (Reflect.isFloatingPointType(element.getClass())) {
|
||||
return deserializeFromFloatingPointType(element);
|
||||
}
|
||||
String clsName = element.getClass().getSimpleName();
|
||||
String msg = "Cannot deserialize element '" + element + "' of type " + clsName + ".\n" +
|
||||
"This serializer only supports primitive number types and their wrapper types.";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
|
||||
private Number deserializeFromFloatingPointType(Number element) {
|
||||
double value = element.doubleValue();
|
||||
|
||||
if (cls == float.class || cls == Float.class) {
|
||||
if (Double.isNaN(value))
|
||||
return Float.NaN;
|
||||
if (value == Double.POSITIVE_INFINITY)
|
||||
return Float.POSITIVE_INFINITY;
|
||||
if (value == Double.NEGATIVE_INFINITY)
|
||||
return Float.NEGATIVE_INFINITY;
|
||||
if (value != 0.0)
|
||||
requireFloatingPointInRange(value);
|
||||
return element.floatValue();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private Number deserializeFromIntegerType(Number element) {
|
||||
long value = element.longValue();
|
||||
|
||||
if (cls == byte.class || cls == Byte.class) {
|
||||
requireIntegerInRange(value, Byte.MIN_VALUE, Byte.MAX_VALUE);
|
||||
return element.byteValue();
|
||||
}
|
||||
|
||||
if (cls == short.class || cls == Short.class) {
|
||||
requireIntegerInRange(value, Short.MIN_VALUE, Short.MAX_VALUE);
|
||||
return element.shortValue();
|
||||
}
|
||||
|
||||
if (cls == int.class || cls == Integer.class) {
|
||||
requireIntegerInRange(value, Integer.MIN_VALUE, Integer.MAX_VALUE);
|
||||
return element.intValue();
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private void requireIntegerInRange(long value, long low, long high) {
|
||||
if (value < low || value > high) {
|
||||
String msg = baseExceptionMessage(value) + "It does not fit into the range " +
|
||||
"of valid values [" + low + ", " + high + "].";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private void requireFloatingPointInRange(double value) {
|
||||
final String clsName = cls.getSimpleName();
|
||||
if ((value > -Float.MIN_VALUE) && (value < Float.MIN_VALUE)) {
|
||||
String msg = baseExceptionMessage(value) + "It is smaller than the smallest " +
|
||||
"possible " + clsName + " value.";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
if ((value < -Float.MAX_VALUE) || (value > Float.MAX_VALUE)) {
|
||||
String msg = baseExceptionMessage(value) + "It is larger than the largest " +
|
||||
"possible " + clsName + " value.";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends Number> String baseExceptionMessage(T value) {
|
||||
String clsName = cls.getSimpleName();
|
||||
return "Number " + value + " cannot be converted to type " + clsName + ". ";
|
||||
}
|
||||
|
||||
public Class<? extends Number> getNumberClass() {
|
||||
return cls;
|
||||
}
|
||||
}
|
||||
|
||||
static final class StringSerializer implements Serializer<String, String> {
|
||||
@Override
|
||||
public String serialize(String element) {
|
||||
return element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String deserialize(String element) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
static final class CharacterSerializer implements Serializer<Character, String> {
|
||||
@Override
|
||||
public String serialize(Character element) {
|
||||
return element.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Character deserialize(String element) {
|
||||
int length = element.length();
|
||||
|
||||
if (length == 0) {
|
||||
String msg = "An empty string cannot be converted to a character.";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
if (length > 1) {
|
||||
String msg = "String '" + element + "' is too long to be converted " +
|
||||
"to a character.";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
|
||||
return element.charAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
static final class BigIntegerSerializer implements Serializer<BigInteger, String> {
|
||||
@Override
|
||||
public String serialize(BigInteger element) {
|
||||
return element.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger deserialize(String element) {
|
||||
return new BigInteger(element);
|
||||
}
|
||||
}
|
||||
|
||||
static final class BigDecimalSerializer implements Serializer<BigDecimal, String> {
|
||||
@Override
|
||||
public String serialize(BigDecimal element) {
|
||||
return element.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigDecimal deserialize(String element) {
|
||||
return new BigDecimal(element);
|
||||
}
|
||||
}
|
||||
|
||||
static final class LocalTimeSerializer implements Serializer<LocalTime, String> {
|
||||
@Override
|
||||
public String serialize(LocalTime element) {
|
||||
return element.truncatedTo(ChronoUnit.SECONDS).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalTime deserialize(String element) {
|
||||
return LocalTime.parse(element);
|
||||
}
|
||||
}
|
||||
|
||||
static final class LocalDateSerializer implements Serializer<LocalDate, String> {
|
||||
@Override
|
||||
public String serialize(LocalDate element) {
|
||||
return element.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate deserialize(String element) {
|
||||
return LocalDate.parse(element);
|
||||
}
|
||||
}
|
||||
|
||||
static final class LocalDateTimeSerializer implements Serializer<LocalDateTime, String> {
|
||||
@Override
|
||||
public String serialize(LocalDateTime element) {
|
||||
return element.truncatedTo(ChronoUnit.SECONDS).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDateTime deserialize(String element) {
|
||||
return LocalDateTime.parse(element);
|
||||
}
|
||||
}
|
||||
|
||||
static final class EnumSerializer implements Serializer<Enum<?>, String> {
|
||||
private final Class<? extends Enum<?>> cls;
|
||||
|
||||
public EnumSerializer(Class<? extends Enum<?>> cls) {
|
||||
this.cls = Validator.requireNonNull(cls, "enum class");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String serialize(Enum<?> element) {
|
||||
return element.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Enum<?> deserialize(String element) {
|
||||
for (Enum<?> constant : cls.getEnumConstants()) {
|
||||
if (constant.name().equals(element)) {
|
||||
return constant;
|
||||
}
|
||||
}
|
||||
String msg = createExceptionMessage(element);
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
|
||||
private String createExceptionMessage(String element) {
|
||||
String enums = Arrays.stream(cls.getEnumConstants())
|
||||
.map(Enum::name)
|
||||
.collect(Collectors.joining(", ", "[", "]"));
|
||||
return "Enum class " + cls.getSimpleName() + " does not contain enum '" +
|
||||
element + "'. Valid values are: " + enums;
|
||||
}
|
||||
|
||||
public Class<? extends Enum<?>> getEnumCls() {
|
||||
return cls;
|
||||
}
|
||||
}
|
||||
|
||||
static class CollectionSerializer<S, T, L extends Collection<S>, R extends Collection<T>>
|
||||
implements Serializer<L, R> {
|
||||
private final Serializer<S, T> serializer;
|
||||
private final boolean outputNulls;
|
||||
private final boolean inputNulls;
|
||||
private final Supplier<L> lSupplier;
|
||||
private final Supplier<R> rSupplier;
|
||||
|
||||
public CollectionSerializer(
|
||||
Serializer<S, T> serializer,
|
||||
boolean outputNulls,
|
||||
boolean inputNulls,
|
||||
Supplier<L> lSupplier,
|
||||
Supplier<R> rSupplier
|
||||
) {
|
||||
this.serializer = Validator.requireNonNull(serializer, "element serializer");
|
||||
this.outputNulls = outputNulls;
|
||||
this.inputNulls = inputNulls;
|
||||
this.lSupplier = lSupplier;
|
||||
this.rSupplier = rSupplier;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final R serialize(L element) {
|
||||
final Stream<T> stream = outputNulls
|
||||
? element.stream().map(s -> s == null ? null : serializer.serialize(s))
|
||||
: element.stream().filter(Objects::nonNull).map(serializer::serialize);
|
||||
return stream.collect(Collectors.toCollection(rSupplier));
|
||||
}
|
||||
|
||||
@Override
|
||||
public final L deserialize(R element) {
|
||||
final Stream<S> stream = inputNulls
|
||||
? element.stream().map(t -> t == null ? null : serializer.deserialize(t))
|
||||
: element.stream().filter(Objects::nonNull).map(serializer::deserialize);
|
||||
return stream.collect(Collectors.toCollection(lSupplier));
|
||||
}
|
||||
|
||||
public final Serializer<S, T> getElementSerializer() {
|
||||
return serializer;
|
||||
}
|
||||
}
|
||||
|
||||
static final class ListSerializer<S, T> extends CollectionSerializer<S, T, List<S>, List<T>> {
|
||||
public ListSerializer(Serializer<S, T> serializer, boolean outputNulls, boolean inputNulls) {
|
||||
super(serializer, outputNulls, inputNulls, ArrayList::new, ArrayList::new);
|
||||
}
|
||||
}
|
||||
|
||||
static final class SetSerializer<S, T> extends CollectionSerializer<S, T, Set<S>, Set<T>> {
|
||||
public SetSerializer(Serializer<S, T> serializer, boolean outputNulls, boolean inputNulls) {
|
||||
super(serializer, outputNulls, inputNulls, HashSet::new, LinkedHashSet::new);
|
||||
}
|
||||
}
|
||||
|
||||
static final class SetAsListSerializer<S, T> extends CollectionSerializer<S, T, Set<S>, List<T>> {
|
||||
public SetAsListSerializer(Serializer<S, T> serializer, boolean outputNulls, boolean inputNulls) {
|
||||
super(serializer, outputNulls, inputNulls, HashSet::new, ArrayList::new);
|
||||
}
|
||||
}
|
||||
|
||||
static final class MapSerializer<S1, T1, S2, T2> implements Serializer<Map<S1, S2>, Map<T1, T2>> {
|
||||
private final Serializer<S1, T1> keySerializer;
|
||||
private final Serializer<S2, T2> valSerializer;
|
||||
private final boolean outputNulls;
|
||||
private final boolean inputNulls;
|
||||
|
||||
public MapSerializer(
|
||||
Serializer<S1, T1> keySerializer,
|
||||
Serializer<S2, T2> valSerializer,
|
||||
boolean outputNulls,
|
||||
boolean inputNulls
|
||||
) {
|
||||
this.keySerializer = Validator.requireNonNull(keySerializer, "key serializer");
|
||||
this.valSerializer = Validator.requireNonNull(valSerializer, "value serializer");
|
||||
this.outputNulls = outputNulls;
|
||||
this.inputNulls = inputNulls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<T1, T2> serialize(Map<S1, S2> element) {
|
||||
// cannot work with Collectors.toMap as is doesn't allow null values
|
||||
final Map<T1, T2> result = new LinkedHashMap<>();
|
||||
for (final Map.Entry<S1, S2> entry : element.entrySet()) {
|
||||
if (!outputNulls && isEntryNull(entry))
|
||||
continue;
|
||||
var s1key = entry.getKey();
|
||||
var s2val = entry.getValue();
|
||||
var t1key = (s1key == null) ? null : keySerializer.serialize(s1key);
|
||||
var t2val = (s2val == null) ? null : valSerializer.serialize(s2val);
|
||||
result.put(t1key, t2val);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<S1, S2> deserialize(Map<T1, T2> element) {
|
||||
// cannot work with Collectors.toMap as is doesn't allow null values
|
||||
final Map<S1, S2> result = new LinkedHashMap<>();
|
||||
for (final Map.Entry<T1, T2> entry : element.entrySet()) {
|
||||
if (!inputNulls && isEntryNull(entry))
|
||||
continue;
|
||||
var t1key = entry.getKey();
|
||||
var t2val = entry.getValue();
|
||||
var s1key = (t1key == null) ? null : keySerializer.deserialize(t1key);
|
||||
var s2val = (t2val == null) ? null : valSerializer.deserialize(t2val);
|
||||
result.put(s1key, s2val);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static boolean isEntryNull(Map.Entry<?, ?> entry) {
|
||||
return (entry == null) || (entry.getKey() == null) || (entry.getValue() == null);
|
||||
}
|
||||
|
||||
public Serializer<S1, T1> getKeySerializer() {
|
||||
return keySerializer;
|
||||
}
|
||||
|
||||
public Serializer<S2, T2> getValueSerializer() {
|
||||
return valSerializer;
|
||||
}
|
||||
}
|
||||
|
||||
static final class ArraySerializer<T1, T2> implements Serializer<T1[], List<T2>> {
|
||||
private final Class<?> componentType;
|
||||
private final Serializer<T1, T2> serializer;
|
||||
private final boolean outputNulls;
|
||||
private final boolean inputNulls;
|
||||
|
||||
public ArraySerializer(
|
||||
Class<?> componentType,
|
||||
Serializer<T1, T2> serializer,
|
||||
boolean outputNulls,
|
||||
boolean inputNulls
|
||||
) {
|
||||
this.componentType = Validator.requireNonNull(componentType, "component type");
|
||||
this.serializer = Validator.requireNonNull(serializer, "element serializer");
|
||||
this.outputNulls = outputNulls;
|
||||
this.inputNulls = inputNulls;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T2> serialize(T1[] element) {
|
||||
final Stream<T2> stream = outputNulls
|
||||
? Arrays.stream(element).map(s -> s == null ? null : serializer.serialize(s))
|
||||
: Arrays.stream(element).filter(Objects::nonNull).map(serializer::serialize);
|
||||
return stream.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T1[] deserialize(List<T2> element) {
|
||||
final Stream<T1> stream = inputNulls
|
||||
? element.stream().map(t -> t == null ? null : serializer.deserialize(t))
|
||||
: element.stream().filter(Objects::nonNull).map(serializer::deserialize);
|
||||
// The following cast won't fail because we choose the elementSerializer based
|
||||
// on the componentType.
|
||||
@SuppressWarnings("unchecked")
|
||||
IntFunction<T1[]> f = value -> (T1[]) Reflect.newArray(componentType, value);
|
||||
return stream.toArray(f);
|
||||
}
|
||||
|
||||
public Class<?> getComponentType() {
|
||||
return componentType;
|
||||
}
|
||||
|
||||
public Serializer<T1, T2> getElementSerializer() {
|
||||
return serializer;
|
||||
}
|
||||
}
|
||||
|
||||
static final class PrimitiveBooleanArraySerializer implements Serializer<Object, List<Boolean>> {
|
||||
@Override
|
||||
public List<Boolean> serialize(Object element) {
|
||||
final boolean[] array = (boolean[]) element;
|
||||
return IntStream.range(0, array.length).mapToObj(i -> array[i]).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object deserialize(List<Boolean> element) {
|
||||
final boolean[] array = new boolean[element.size()];
|
||||
for (int i = 0; i < element.size(); i++)
|
||||
array[i] = Validator.requireNonNullArrayElement(element.get(i), "boolean", i);
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
static final class PrimitiveCharacterArraySerializer
|
||||
implements Serializer<Object, List<String>> {
|
||||
private static final CharacterSerializer serializer = new CharacterSerializer();
|
||||
|
||||
@Override
|
||||
public List<String> serialize(Object element) {
|
||||
final char[] array = (char[]) element;
|
||||
return IntStream.range(0, array.length)
|
||||
.mapToObj(i -> serializer.serialize(array[i]))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object deserialize(List<String> element) {
|
||||
final char[] array = new char[element.size()];
|
||||
for (int i = 0; i < element.size(); i++) {
|
||||
String character = Validator.requireNonNullArrayElement(element.get(i), "char", i);
|
||||
array[i] = serializer.deserialize(character);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
static final class PrimitiveByteArraySerializer
|
||||
implements Serializer<Object, List<Number>> {
|
||||
private static final NumberSerializer serializer = new NumberSerializer(byte.class);
|
||||
|
||||
@Override
|
||||
public List<Number> serialize(Object element) {
|
||||
final byte[] array = (byte[]) element;
|
||||
return IntStream.range(0, array.length).
|
||||
mapToObj(i -> serializer.serialize(array[i]))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object deserialize(List<Number> element) {
|
||||
final byte[] array = new byte[element.size()];
|
||||
for (int i = 0; i < element.size(); i++) {
|
||||
Number number = Validator.requireNonNullArrayElement(element.get(i), "byte", i);
|
||||
array[i] = (byte) serializer.deserialize(number);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
static final class PrimitiveShortArraySerializer
|
||||
implements Serializer<Object, List<Number>> {
|
||||
private static final NumberSerializer serializer = new NumberSerializer(short.class);
|
||||
|
||||
@Override
|
||||
public List<Number> serialize(Object element) {
|
||||
final short[] array = (short[]) element;
|
||||
return IntStream.range(0, array.length)
|
||||
.mapToObj(i -> serializer.serialize(array[i]))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object deserialize(List<Number> element) {
|
||||
final short[] array = new short[element.size()];
|
||||
for (int i = 0; i < element.size(); i++) {
|
||||
Number number = Validator.requireNonNullArrayElement(element.get(i), "short", i);
|
||||
array[i] = (short) serializer.deserialize(number);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
static final class PrimitiveIntegerArraySerializer
|
||||
implements Serializer<Object, List<Number>> {
|
||||
private static final NumberSerializer serializer = new NumberSerializer(int.class);
|
||||
|
||||
@Override
|
||||
public List<Number> serialize(Object element) {
|
||||
final int[] array = (int[]) element;
|
||||
return Arrays.stream(array).mapToObj(serializer::serialize).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object deserialize(List<Number> element) {
|
||||
final int[] array = new int[element.size()];
|
||||
for (int i = 0; i < element.size(); i++) {
|
||||
Number number = Validator.requireNonNullArrayElement(element.get(i), "int", i);
|
||||
array[i] = (int) serializer.deserialize(number);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
static final class PrimitiveLongArraySerializer
|
||||
implements Serializer<Object, List<Number>> {
|
||||
private static final NumberSerializer serializer = new NumberSerializer(long.class);
|
||||
|
||||
@Override
|
||||
public List<Number> serialize(Object element) {
|
||||
final long[] array = (long[]) element;
|
||||
return Arrays.stream(array).mapToObj(serializer::serialize).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object deserialize(List<Number> element) {
|
||||
final long[] array = new long[element.size()];
|
||||
for (int i = 0; i < element.size(); i++) {
|
||||
Number number = Validator.requireNonNullArrayElement(element.get(i), "long", i);
|
||||
array[i] = (long) serializer.deserialize(number);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
static final class PrimitiveFloatArraySerializer
|
||||
implements Serializer<Object, List<Number>> {
|
||||
private static final NumberSerializer serializer = new NumberSerializer(float.class);
|
||||
|
||||
@Override
|
||||
public List<Number> serialize(Object element) {
|
||||
final float[] array = (float[]) element;
|
||||
return IntStream.range(0, array.length)
|
||||
.mapToObj(i -> serializer.serialize(array[i]))
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object deserialize(List<Number> element) {
|
||||
final float[] array = new float[element.size()];
|
||||
for (int i = 0; i < element.size(); i++) {
|
||||
Number number = Validator.requireNonNullArrayElement(element.get(i), "float", i);
|
||||
array[i] = (float) serializer.deserialize(number);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
}
|
||||
|
||||
static final class PrimitiveDoubleArraySerializer
|
||||
implements Serializer<Object, List<Number>> {
|
||||
private static final NumberSerializer serializer = new NumberSerializer(double.class);
|
||||
|
||||
@Override
|
||||
public List<Number> serialize(Object element) {
|
||||
final double[] array = (double[]) element;
|
||||
return Arrays.stream(array).mapToObj(serializer::serialize).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object deserialize(List<Number> element) {
|
||||
final double[] array = new double[element.size()];
|
||||
for (int i = 0; i < element.size(); i++) {
|
||||
Number number = Validator.requireNonNullArrayElement(element.get(i), "double", i);
|
||||
array[i] = (double) serializer.deserialize(number);
|
||||
}
|
||||
return array;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
final class Validator {
|
||||
private Validator() {}
|
||||
|
||||
static <T> T requireNonNull(T object, String argumentName) {
|
||||
String msg = "The " + argumentName + " must not be null.";
|
||||
return Objects.requireNonNull(object, msg);
|
||||
}
|
||||
|
||||
static <T> T requireNonNullArrayElement(T element, String type, int index) {
|
||||
if (element == null) {
|
||||
String msg = "The " + type + " element at index " + index + " must not be null.";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
static <T> Class<T> requireConfiguration(Class<T> cls) {
|
||||
if (!Reflect.isConfiguration(cls)) {
|
||||
String msg = "Class '" + cls.getSimpleName() + "' must be a configuration.";
|
||||
throw new ConfigurationException(msg);
|
||||
}
|
||||
return cls;
|
||||
}
|
||||
|
||||
static void requirePrimitiveOrWrapperNumberType(Class<?> cls) {
|
||||
if (!Reflect.isIntegerType(cls) && !Reflect.isFloatingPointType(cls)) {
|
||||
String msg = "Class " + cls.getSimpleName() + " is not a byte, short, int, long, " +
|
||||
"float, double, or a wrapper type of one of the primitive number types.";
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
/**
|
||||
* An extension of the {@code FileConfigurationProperties} class that allows configuring properties
|
||||
* that are more specific to YAML files.
|
||||
*/
|
||||
public final class YamlConfigurationProperties extends FileConfigurationProperties {
|
||||
/**
|
||||
* Constructs a new instance of this class with values taken from the given builder.
|
||||
*
|
||||
* @param builder the builder used to initialize the fields of this class
|
||||
* @throws NullPointerException if the builder or any of its values is null
|
||||
*/
|
||||
public YamlConfigurationProperties(Builder<?> builder) {
|
||||
super(builder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@code Builder} with default values.
|
||||
*
|
||||
* @return newly constructed {@code Builder}
|
||||
*/
|
||||
public static Builder<?> newBuilder() {
|
||||
return new BuilderImpl();
|
||||
}
|
||||
|
||||
private static final class BuilderImpl extends Builder<BuilderImpl> {
|
||||
@Override
|
||||
protected BuilderImpl getThis() {return this;}
|
||||
|
||||
@Override
|
||||
public YamlConfigurationProperties build() {return new YamlConfigurationProperties(this);}
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder class for constructing {@code YamlConfigurationProperties}.
|
||||
*
|
||||
* @param <B> the type of builder
|
||||
*/
|
||||
public static abstract class Builder<B extends Builder<B>>
|
||||
extends FileConfigurationProperties.Builder<B> {
|
||||
|
||||
/**
|
||||
* A constructor that can be overridden by subclasses.
|
||||
*/
|
||||
protected Builder() {}
|
||||
|
||||
/**
|
||||
* Builds a {@code ConfigurationProperties} instance.
|
||||
*
|
||||
* @return newly constructed {@code ConfigurationProperties}
|
||||
*/
|
||||
public abstract YamlConfigurationProperties build();
|
||||
|
||||
/**
|
||||
* Returns this builder.
|
||||
*
|
||||
* @return this builder
|
||||
*/
|
||||
protected abstract B getThis();
|
||||
}
|
||||
}
|
@ -0,0 +1,286 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import org.snakeyaml.engine.v2.api.Dump;
|
||||
import org.snakeyaml.engine.v2.api.DumpSettings;
|
||||
import org.snakeyaml.engine.v2.api.Load;
|
||||
import org.snakeyaml.engine.v2.api.LoadSettings;
|
||||
import org.snakeyaml.engine.v2.common.FlowStyle;
|
||||
import org.snakeyaml.engine.v2.exceptions.YamlEngineException;
|
||||
import org.snakeyaml.engine.v2.nodes.Node;
|
||||
import org.snakeyaml.engine.v2.nodes.Tag;
|
||||
import org.snakeyaml.engine.v2.representer.StandardRepresenter;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
|
||||
import static de.exlll.configlib.Validator.requireNonNull;
|
||||
|
||||
/**
|
||||
* A configuration store that saves and loads configurations as YAML text files.
|
||||
*
|
||||
* @param <T> the configuration type
|
||||
*/
|
||||
public final class YamlConfigurationStore<T> implements FileConfigurationStore<T> {
|
||||
private static final Dump YAML_DUMPER = newYamlDumper();
|
||||
private static final Load YAML_LOADER = newYamlLoader();
|
||||
private final Class<T> configurationType;
|
||||
private final YamlConfigurationProperties properties;
|
||||
private final ConfigurationSerializer<T> serializer;
|
||||
private final CommentNodeExtractor extractor;
|
||||
|
||||
/**
|
||||
* Constructs a new store.
|
||||
*
|
||||
* @param configurationType the type of configuration
|
||||
* @param properties the properties
|
||||
* @throws NullPointerException if any argument is null
|
||||
*/
|
||||
public YamlConfigurationStore(Class<T> configurationType, YamlConfigurationProperties properties) {
|
||||
this.configurationType = requireNonNull(configurationType, "configuration type");
|
||||
this.properties = requireNonNull(properties, "properties");
|
||||
this.serializer = new ConfigurationSerializer<>(configurationType, properties);
|
||||
this.extractor = new CommentNodeExtractor(properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void save(T configuration, Path configurationFile) {
|
||||
tryCreateParentDirectories(configurationFile);
|
||||
var extractedCommentNodes = extractor.extractCommentNodes(configuration);
|
||||
var yamlFileWriter = new YamlFileWriter(configurationFile, properties);
|
||||
var dumpedYaml = tryDump(configuration);
|
||||
yamlFileWriter.writeYaml(dumpedYaml, extractedCommentNodes);
|
||||
}
|
||||
|
||||
private void tryCreateParentDirectories(Path configurationFile) {
|
||||
Path parent = configurationFile.getParent();
|
||||
if (!Files.exists(parent) && properties.createParentDirectories()) {
|
||||
try {
|
||||
Files.createDirectories(parent);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String tryDump(T configuration) {
|
||||
final Map<?, ?> serializedConfiguration = serializer.serialize(configuration);
|
||||
try {
|
||||
return YAML_DUMPER.dumpToString(serializedConfiguration);
|
||||
} catch (YamlEngineException e) {
|
||||
String msg = "The given configuration could not be converted into YAML. \n" +
|
||||
"Do all custom serializers produce valid target types?";
|
||||
throw new ConfigurationException(msg, e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public T load(Path configurationFile) {
|
||||
try (var reader = Files.newBufferedReader(configurationFile)) {
|
||||
var yaml = YAML_LOADER.loadFromReader(reader);
|
||||
var conf = requireConfiguration(yaml, configurationFile);
|
||||
return serializer.deserialize(conf);
|
||||
} catch (YamlEngineException e) {
|
||||
String msg = "The configuration file at %s does not contain valid YAML.";
|
||||
throw new ConfigurationException(msg.formatted(configurationFile), e);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<?, ?> requireConfiguration(Object yaml, Path configurationFile) {
|
||||
if (yaml == null) {
|
||||
String msg = "The configuration file at %s is empty or only contains null.";
|
||||
throw new ConfigurationException(msg.formatted(configurationFile));
|
||||
}
|
||||
|
||||
if (!(yaml instanceof Map<?, ?>)) {
|
||||
String msg = "The contents of the YAML file at %s do not represent a configuration. " +
|
||||
"A valid configuration file contains a YAML map but instead a " +
|
||||
"'" + yaml.getClass() + "' was found.";
|
||||
throw new ConfigurationException(msg.formatted(configurationFile));
|
||||
}
|
||||
|
||||
return (Map<?, ?>) yaml;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T update(Path configurationFile) {
|
||||
if (Files.exists(configurationFile)) {
|
||||
T configuration = load(configurationFile);
|
||||
save(configuration, configurationFile);
|
||||
return configuration;
|
||||
}
|
||||
T configuration = Reflect.newInstance(configurationType);
|
||||
save(configuration, configurationFile);
|
||||
return configuration;
|
||||
}
|
||||
|
||||
static Dump newYamlDumper() {
|
||||
DumpSettings settings = DumpSettings.builder()
|
||||
.setDefaultFlowStyle(FlowStyle.BLOCK)
|
||||
.setIndent(2)
|
||||
.build();
|
||||
return new Dump(settings, new YamlConfigurationRepresenter(settings));
|
||||
}
|
||||
|
||||
static Load newYamlLoader() {
|
||||
LoadSettings settings = LoadSettings.builder().build();
|
||||
return new Load(settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* A writer that writes YAML to a file.
|
||||
*/
|
||||
static final class YamlFileWriter {
|
||||
private final Path configurationFile;
|
||||
private final YamlConfigurationProperties properties;
|
||||
private BufferedWriter writer;
|
||||
|
||||
YamlFileWriter(Path configurationFile, YamlConfigurationProperties properties) {
|
||||
this.configurationFile = requireNonNull(configurationFile, "configuration file");
|
||||
this.properties = requireNonNull(properties, "configuration properties");
|
||||
}
|
||||
|
||||
public void writeYaml(String yaml, Queue<CommentNode> nodes) {
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(configurationFile)) {
|
||||
this.writer = writer;
|
||||
writeHeader();
|
||||
writeContent(yaml, nodes);
|
||||
writeFooter();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
} finally {
|
||||
this.writer = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void writeHeader() throws IOException {
|
||||
if (properties.getHeader() != null) {
|
||||
writeAsComment(properties.getHeader());
|
||||
writer.newLine();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeFooter() throws IOException {
|
||||
if (properties.getFooter() != null) {
|
||||
writer.newLine();
|
||||
writeAsComment(properties.getFooter());
|
||||
}
|
||||
}
|
||||
|
||||
private void writeAsComment(String comment) throws IOException {
|
||||
String[] lines = comment.split("\n");
|
||||
writeComments(Arrays.asList(lines), 0);
|
||||
}
|
||||
|
||||
private void writeComments(List<String> comments, int indentLevel) throws IOException {
|
||||
String indent = " ".repeat(indentLevel);
|
||||
for (String comment : comments) {
|
||||
if (comment.isEmpty()) {
|
||||
writer.newLine();
|
||||
continue;
|
||||
}
|
||||
String line = indent + "# " + comment;
|
||||
writeLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeLine(String line) throws IOException {
|
||||
writer.write(line);
|
||||
writer.newLine();
|
||||
}
|
||||
|
||||
private void writeContent(String yaml, Queue<CommentNode> nodes) throws IOException {
|
||||
if (nodes.isEmpty()) {
|
||||
writer.write(yaml);
|
||||
} else {
|
||||
writeCommentedYaml(yaml, nodes);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeCommentedYaml(String yaml, Queue<CommentNode> nodes)
|
||||
throws IOException {
|
||||
/*
|
||||
* The following algorithm is necessary since no Java YAML library seems
|
||||
* to properly support comments, at least not the way I want them.
|
||||
*
|
||||
* The algorithm writes YAML line by line and keeps track of the current
|
||||
* context with the help of fieldNames lists which come from the nodes in
|
||||
* the 'nodes' queue. The 'nodes' queue contains nodes in the order in
|
||||
* which the fields were read, which happened in DFS manner and with fields
|
||||
* of a parent class being read before the fields of the child. That order
|
||||
* ultimately represents the order in which the YAML file is structured.
|
||||
*/
|
||||
var node = nodes.poll();
|
||||
var currentIndentLevel = 0;
|
||||
|
||||
for (final String line : yaml.split("\n")) {
|
||||
if (node == null) {
|
||||
writeLine(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
final var fieldNames = node.fieldNames();
|
||||
final var indent = " ".repeat(currentIndentLevel);
|
||||
|
||||
final var lineStart = indent + fieldNames.get(currentIndentLevel) + ":";
|
||||
if (!line.startsWith(lineStart)) {
|
||||
writeLine(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
final var commentIndentLevel = fieldNames.size() - 1;
|
||||
if (currentIndentLevel++ == commentIndentLevel) {
|
||||
writeComments(node.comments(), commentIndentLevel);
|
||||
if ((node = nodes.poll()) != null) {
|
||||
currentIndentLevel = lengthCommonPrefix(node.fieldNames(), fieldNames);
|
||||
}
|
||||
}
|
||||
|
||||
writeLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
static int lengthCommonPrefix(List<String> l1, List<String> l2) {
|
||||
final int maxLen = Math.min(l1.size(), l2.size());
|
||||
int result = 0;
|
||||
for (int i = 0; i < maxLen; i++) {
|
||||
String s1 = l1.get(i);
|
||||
String s2 = l2.get(i);
|
||||
if (s1.equals(s2))
|
||||
result++;
|
||||
else return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A custom representer that prevents aliasing.
|
||||
*/
|
||||
static final class YamlConfigurationRepresenter extends StandardRepresenter {
|
||||
public YamlConfigurationRepresenter(DumpSettings settings) {
|
||||
super(settings);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Node representSequence(Tag tag, Iterable<?> sequence, FlowStyle flowStyle) {
|
||||
Node node = super.representSequence(tag, sequence, flowStyle);
|
||||
representedObjects.clear();
|
||||
return node;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Node representMapping(Tag tag, Map<?, ?> mapping, FlowStyle flowStyle) {
|
||||
Node node = super.representMapping(tag, mapping, flowStyle);
|
||||
representedObjects.clear();
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,322 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.Set;
|
||||
|
||||
import static de.exlll.configlib.TestUtils.assertThrowsConfigurationException;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@SuppressWarnings({"unused", "FieldMayBeFinal"})
|
||||
class CommentNodeExtractorTest {
|
||||
private static final ConfigurationProperties PROPERTIES = ConfigurationProperties.newBuilder()
|
||||
.outputNulls(true)
|
||||
.build();
|
||||
|
||||
private final CommentNodeExtractor EXTRACTOR = new CommentNodeExtractor(PROPERTIES);
|
||||
|
||||
@Test
|
||||
void requiresConfiguration() {
|
||||
assertThrowsConfigurationException(
|
||||
() -> EXTRACTOR.extractCommentNodes(new Object()),
|
||||
"Class 'Object' must be a configuration."
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractSingleComment1() {
|
||||
@Configuration
|
||||
class A {
|
||||
@Comment("Hello")
|
||||
int i;
|
||||
}
|
||||
|
||||
Queue<CommentNode> nodes = EXTRACTOR.extractCommentNodes(new A());
|
||||
assertEquals(cn(List.of("Hello"), "i"), nodes.poll());
|
||||
assertTrue(nodes.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractSingleComment2() {
|
||||
@Configuration
|
||||
class A {
|
||||
@Comment({"Hello", "World"})
|
||||
int i;
|
||||
}
|
||||
|
||||
Queue<CommentNode> nodes = EXTRACTOR.extractCommentNodes(new A());
|
||||
assertEquals(cn(List.of("Hello", "World"), "i"), nodes.poll());
|
||||
assertTrue(nodes.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractMultipleComments() {
|
||||
@Configuration
|
||||
class A {
|
||||
@Comment("Hello")
|
||||
int i;
|
||||
int j;
|
||||
@Comment("World")
|
||||
int k;
|
||||
}
|
||||
|
||||
Queue<CommentNode> nodes = EXTRACTOR.extractCommentNodes(new A());
|
||||
assertEquals(cn(List.of("Hello"), "i"), nodes.poll());
|
||||
assertEquals(cn(List.of("World"), "k"), nodes.poll());
|
||||
assertTrue(nodes.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractNestedComment1() {
|
||||
@Configuration
|
||||
class A {
|
||||
@Comment("Hello")
|
||||
int i;
|
||||
}
|
||||
@Configuration
|
||||
class B {
|
||||
A a = new A();
|
||||
}
|
||||
Queue<CommentNode> nodes = EXTRACTOR.extractCommentNodes(new B());
|
||||
assertEquals(cn(List.of("Hello"), "a", "i"), nodes.poll());
|
||||
assertTrue(nodes.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractNestedComment2() {
|
||||
@Configuration
|
||||
class A {
|
||||
@Comment("Hello")
|
||||
int i;
|
||||
int j;
|
||||
@Comment("World")
|
||||
int k;
|
||||
}
|
||||
@Configuration
|
||||
class B {
|
||||
A a = new A();
|
||||
}
|
||||
Queue<CommentNode> nodes = EXTRACTOR.extractCommentNodes(new B());
|
||||
assertEquals(cn(List.of("Hello"), "a", "i"), nodes.poll());
|
||||
assertEquals(cn(List.of("World"), "a", "k"), nodes.poll());
|
||||
assertTrue(nodes.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractNestedComment3() {
|
||||
@Configuration
|
||||
class A {
|
||||
@Comment("Hello")
|
||||
int i;
|
||||
int j;
|
||||
@Comment("World")
|
||||
int k;
|
||||
}
|
||||
@Configuration
|
||||
class B {
|
||||
@Comment("Hello")
|
||||
A a1 = new A();
|
||||
@Comment("World")
|
||||
A a2 = new A();
|
||||
}
|
||||
@Configuration
|
||||
class C {
|
||||
@Comment("Hello")
|
||||
B b = new B();
|
||||
@Comment("World")
|
||||
A a = new A();
|
||||
}
|
||||
@Configuration
|
||||
class D {
|
||||
C c = new C();
|
||||
}
|
||||
Queue<CommentNode> nodes = EXTRACTOR.extractCommentNodes(new D());
|
||||
assertEquals(cn(List.of("Hello"), "c", "b"), nodes.poll());
|
||||
assertEquals(cn(List.of("Hello"), "c", "b", "a1"), nodes.poll());
|
||||
assertEquals(cn(List.of("Hello"), "c", "b", "a1", "i"), nodes.poll());
|
||||
assertEquals(cn(List.of("World"), "c", "b", "a1", "k"), nodes.poll());
|
||||
assertEquals(cn(List.of("World"), "c", "b", "a2"), nodes.poll());
|
||||
assertEquals(cn(List.of("Hello"), "c", "b", "a2", "i"), nodes.poll());
|
||||
assertEquals(cn(List.of("World"), "c", "b", "a2", "k"), nodes.poll());
|
||||
assertEquals(cn(List.of("World"), "c", "a"), nodes.poll());
|
||||
assertEquals(cn(List.of("Hello"), "c", "a", "i"), nodes.poll());
|
||||
assertEquals(cn(List.of("World"), "c", "a", "k"), nodes.poll());
|
||||
assertTrue(nodes.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractEmptyClass() {
|
||||
@Configuration
|
||||
class A {}
|
||||
Queue<CommentNode> nodes = EXTRACTOR.extractCommentNodes(new A());
|
||||
assertTrue(nodes.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractNestedOnlyIfNotNull() {
|
||||
@Configuration
|
||||
class A {
|
||||
@Comment("Hello")
|
||||
int i;
|
||||
}
|
||||
@Configuration
|
||||
class B {
|
||||
A a = null;
|
||||
}
|
||||
@Configuration
|
||||
class C {
|
||||
@Comment("Hello")
|
||||
A a = null;
|
||||
}
|
||||
Queue<CommentNode> nodes1 = EXTRACTOR.extractCommentNodes(new B());
|
||||
assertTrue(nodes1.isEmpty());
|
||||
|
||||
Queue<CommentNode> nodes2 = EXTRACTOR.extractCommentNodes(new C());
|
||||
assertEquals(cn(List.of("Hello"), "a"), nodes2.poll());
|
||||
assertTrue(nodes2.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractIgnoresCommentIfFieldNullAndOutputNull1() {
|
||||
@Configuration
|
||||
class A {
|
||||
@Comment("Hello")
|
||||
int i;
|
||||
}
|
||||
@Configuration
|
||||
class B {
|
||||
A a = null;
|
||||
}
|
||||
@Configuration
|
||||
class C {
|
||||
@Comment("Hello")
|
||||
A a = null;
|
||||
}
|
||||
ConfigurationProperties properties = ConfigurationProperties.newBuilder()
|
||||
.outputNulls(false)
|
||||
.build();
|
||||
CommentNodeExtractor extractor = new CommentNodeExtractor(properties);
|
||||
|
||||
Queue<CommentNode> nodes1 = extractor.extractCommentNodes(new B());
|
||||
assertTrue(nodes1.isEmpty());
|
||||
|
||||
Queue<CommentNode> nodes2 = extractor.extractCommentNodes(new C());
|
||||
assertTrue(nodes2.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractIgnoresCommentIfFieldNullAndOutputNull2() {
|
||||
@Configuration
|
||||
class A {
|
||||
@Comment("Hello")
|
||||
String s1 = null;
|
||||
@Comment("World")
|
||||
String s2 = "";
|
||||
}
|
||||
ConfigurationProperties properties = ConfigurationProperties.newBuilder()
|
||||
.outputNulls(false)
|
||||
.build();
|
||||
CommentNodeExtractor extractor = new CommentNodeExtractor(properties);
|
||||
Queue<CommentNode> nodes = extractor.extractCommentNodes(new A());
|
||||
assertEquals(cn(List.of("World"), "s2"), nodes.poll());
|
||||
assertTrue(nodes.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractAppliesFormatterAndFilter() {
|
||||
@Configuration
|
||||
class A {
|
||||
@Comment("Hello")
|
||||
int i;
|
||||
int j;
|
||||
@Comment("World")
|
||||
int k;
|
||||
}
|
||||
@Configuration
|
||||
class B {
|
||||
@Comment("Hello")
|
||||
A a1 = new A();
|
||||
@Comment("World")
|
||||
A a2 = new A();
|
||||
}
|
||||
@Configuration
|
||||
class C {
|
||||
@Comment("Hello")
|
||||
B b = new B();
|
||||
@Comment("World")
|
||||
A a = new A();
|
||||
}
|
||||
|
||||
ConfigurationProperties properties = ConfigurationProperties.newBuilder()
|
||||
.setFieldFilter(field -> {
|
||||
String name = field.getName();
|
||||
return !name.equals("a1") && !name.equals("a");
|
||||
})
|
||||
.setFieldFormatter(FieldFormatters.UPPER_UNDERSCORE)
|
||||
.build();
|
||||
|
||||
CommentNodeExtractor extractor = new CommentNodeExtractor(properties);
|
||||
Queue<CommentNode> nodes = extractor.extractCommentNodes(new C());
|
||||
assertEquals(cn(List.of("Hello"), "B"), nodes.poll());
|
||||
assertEquals(cn(List.of("World"), "B", "A2"), nodes.poll());
|
||||
assertEquals(cn(List.of("Hello"), "B", "A2", "I"), nodes.poll());
|
||||
assertEquals(cn(List.of("World"), "B", "A2", "K"), nodes.poll());
|
||||
assertTrue(nodes.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractIgnoresCommentsForConfigurationsInCollections() {
|
||||
@Configuration
|
||||
class A {
|
||||
@Comment("Yes")
|
||||
int i;
|
||||
}
|
||||
@Configuration
|
||||
class B {
|
||||
Set<A> setA;
|
||||
List<A> listA;
|
||||
Map<Integer, A> mapIntegerA;
|
||||
}
|
||||
Queue<CommentNode> nodes = EXTRACTOR.extractCommentNodes(new B());
|
||||
assertTrue(nodes.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractCommentsFromParentClasses() {
|
||||
@Configuration
|
||||
class A1 {
|
||||
@Comment({"int", "i"})
|
||||
int i;
|
||||
}
|
||||
@Configuration
|
||||
class A2 extends A1 {
|
||||
@Comment({"int", "j"})
|
||||
int j;
|
||||
}
|
||||
@Configuration
|
||||
class B1 {
|
||||
@Comment({"A2", "a2"})
|
||||
A2 a2 = new A2();
|
||||
}
|
||||
@Configuration
|
||||
class B2 extends B1 {
|
||||
@Comment({"A1", "a1"})
|
||||
A1 a1 = new A1();
|
||||
}
|
||||
Queue<CommentNode> nodes = EXTRACTOR.extractCommentNodes(new B2());
|
||||
assertEquals(cn(List.of("A2", "a2"), "a2"), nodes.poll());
|
||||
assertEquals(cn(List.of("int", "i"), "a2", "i"), nodes.poll());
|
||||
assertEquals(cn(List.of("int", "j"), "a2", "j"), nodes.poll());
|
||||
assertEquals(cn(List.of("A1", "a1"), "a1"), nodes.poll());
|
||||
assertEquals(cn(List.of("int", "i"), "a1", "i"), nodes.poll());
|
||||
assertTrue(nodes.isEmpty());
|
||||
}
|
||||
|
||||
private static CommentNode cn(List<String> comments, String... fieldNames) {
|
||||
return new CommentNode(comments, List.of(fieldNames));
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
import static de.exlll.configlib.TestUtils.assertThrowsNullPointerException;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class ConfigurationPropertiesTest {
|
||||
|
||||
@Test
|
||||
void builderDefaultValues() {
|
||||
ConfigurationProperties properties = ConfigurationProperties.newBuilder().build();
|
||||
|
||||
assertThat(properties.serializeSetsAsLists(), is(true));
|
||||
assertThat(properties.outputNulls(), is(false));
|
||||
assertThat(properties.inputNulls(), is(false));
|
||||
assertThat(properties.getSerializers().entrySet(), empty());
|
||||
assertThat(properties.getFieldFormatter(), is(FieldFormatters.IDENTITY));
|
||||
assertThat(properties.getFieldFilter(), is(FieldFilters.DEFAULT));
|
||||
}
|
||||
|
||||
@Test
|
||||
void builderCopiesValues() {
|
||||
FieldFormatter formatter = field -> field.getName().toLowerCase(Locale.ROOT);
|
||||
FieldFilter filter = field -> field.getName().startsWith("f");
|
||||
TestUtils.PointSerializer serializer = new TestUtils.PointSerializer();
|
||||
|
||||
ConfigurationProperties properties = ConfigurationProperties.newBuilder()
|
||||
.addSerializer(Point.class, serializer)
|
||||
.setFieldFormatter(formatter)
|
||||
.setFieldFilter(filter)
|
||||
.outputNulls(true)
|
||||
.inputNulls(true)
|
||||
.serializeSetsAsLists(false)
|
||||
.build();
|
||||
|
||||
assertThat(properties.getSerializers(), is(Map.of(Point.class, serializer)));
|
||||
assertThat(properties.outputNulls(), is(true));
|
||||
assertThat(properties.inputNulls(), is(true));
|
||||
assertThat(properties.serializeSetsAsLists(), is(false));
|
||||
assertThat(properties.getFieldFormatter(), sameInstance(formatter));
|
||||
assertThat(properties.getFieldFilter(), sameInstance(filter));
|
||||
}
|
||||
|
||||
@Test
|
||||
void builderSerializersUnmodifiable() {
|
||||
ConfigurationProperties properties = ConfigurationProperties.newBuilder().build();
|
||||
Map<Class<?>, Serializer<?, ?>> map = properties.getSerializers();
|
||||
|
||||
assertThrows(
|
||||
UnsupportedOperationException.class,
|
||||
() -> map.put(Point.class, new TestUtils.PointSerializer())
|
||||
);
|
||||
}
|
||||
|
||||
public static final class BuilderTest {
|
||||
private static final ConfigurationProperties.Builder<?> builder = ConfigurationProperties.newBuilder();
|
||||
|
||||
@Test
|
||||
void setFieldFilterRequiresNonNull() {
|
||||
assertThrowsNullPointerException(
|
||||
() -> builder.setFieldFilter(null),
|
||||
"field filter"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void setFieldFormatterRequiresNonNull() {
|
||||
assertThrowsNullPointerException(
|
||||
() -> builder.setFieldFormatter(null),
|
||||
"field formatter"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void addSerializerRequiresNonNull() {
|
||||
assertThrowsNullPointerException(
|
||||
() -> builder.addSerializer(null, new Serializers.StringSerializer()),
|
||||
"serialized type"
|
||||
);
|
||||
|
||||
assertThrowsNullPointerException(
|
||||
() -> builder.addSerializer(String.class, null),
|
||||
"serializer"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,317 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import de.exlll.configlib.ConfigurationProperties.Builder;
|
||||
import de.exlll.configlib.Serializers.*;
|
||||
import de.exlll.configlib.configurations.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static de.exlll.configlib.TestUtils.*;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
@SuppressWarnings("FieldMayBeFinal")
|
||||
class ConfigurationSerializerTest {
|
||||
private static <T> ConfigurationSerializer<T> newSerializer(Class<T> cls) {
|
||||
return newSerializer(cls, builder -> builder);
|
||||
}
|
||||
|
||||
private static <T> ConfigurationSerializer<T> newSerializer(
|
||||
Class<T> cls,
|
||||
Function<Builder<?>, Builder<?>> propertiesConfigurer
|
||||
) {
|
||||
var builder = ConfigurationProperties.newBuilder()
|
||||
.addSerializer(Point.class, TestUtils.POINT_SERIALIZER);
|
||||
builder.addSerializer(Point.class, TestUtils.POINT_SERIALIZER);
|
||||
builder = propertiesConfigurer.apply(builder);
|
||||
return new ConfigurationSerializer<>(cls, builder.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildSerializerMapFiltersFields() {
|
||||
Map<String, Serializer<?, ?>> serializers = newSerializer(ExampleConfigurationA2.class)
|
||||
.buildSerializerMap();
|
||||
|
||||
assertThat(serializers.get("a1_staticFinalInt"), nullValue());
|
||||
assertThat(serializers.get("a1_staticInt"), nullValue());
|
||||
assertThat(serializers.get("a1_finalInt"), nullValue());
|
||||
assertThat(serializers.get("a1_transientInt"), nullValue());
|
||||
assertThat(serializers.get("a1_ignoredInt"), nullValue());
|
||||
assertThat(serializers.get("a1_ignoredString"), nullValue());
|
||||
assertThat(serializers.get("a1_ignoredListString"), nullValue());
|
||||
|
||||
assertThat(serializers.get("a2_staticFinalInt"), nullValue());
|
||||
assertThat(serializers.get("a2_staticInt"), nullValue());
|
||||
assertThat(serializers.get("a2_finalInt"), nullValue());
|
||||
assertThat(serializers.get("a2_transientInt"), nullValue());
|
||||
assertThat(serializers.get("a2_ignoredInt"), nullValue());
|
||||
assertThat(serializers.get("a2_ignoredString"), nullValue());
|
||||
assertThat(serializers.get("a2_ignoredListString"), nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildSerializerMapIgnoresFormatter() {
|
||||
Map<String, Serializer<?, ?>> serializers = newSerializer(
|
||||
ExampleConfigurationA2.class,
|
||||
props -> props.setFieldFormatter(FieldFormatters.UPPER_UNDERSCORE)
|
||||
).buildSerializerMap();
|
||||
|
||||
assertThat(serializers.get("A2_PRIM_BOOL"), nullValue());
|
||||
assertThat(serializers.get("a2_primBool"), instanceOf(BooleanSerializer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildSerializerMap() {
|
||||
Map<String, Serializer<?, ?>> serializers = newSerializer(ExampleConfigurationA2.class)
|
||||
.buildSerializerMap();
|
||||
assertThat(serializers.get("a2_primBool"), instanceOf(BooleanSerializer.class));
|
||||
assertThat(serializers.get("a2_refChar"), instanceOf(CharacterSerializer.class));
|
||||
assertThat(serializers.get("a2_string"), instanceOf(StringSerializer.class));
|
||||
assertThat(serializers.get("a2_Enm"), instanceOf(EnumSerializer.class));
|
||||
|
||||
ConfigurationSerializer<?> serializerB1 =
|
||||
(ConfigurationSerializer<?>) serializers.get("a2_b1");
|
||||
ConfigurationSerializer<?> serializerB2 =
|
||||
(ConfigurationSerializer<?>) serializers.get("a2_b2");
|
||||
|
||||
assertThat(serializerB1.getConfigurationType(), equalTo(ExampleConfigurationB1.class));
|
||||
assertThat(serializerB2.getConfigurationType(), equalTo(ExampleConfigurationB2.class));
|
||||
|
||||
ListSerializer<?, ?> serializerList =
|
||||
(ListSerializer<?, ?>) serializers.get("a2_listByte");
|
||||
ArraySerializer<?, ?> serializerArray =
|
||||
(ArraySerializer<?, ?>) serializers.get("a2_arrayString");
|
||||
SetAsListSerializer<?, ?> serializerSet =
|
||||
(SetAsListSerializer<?, ?>) serializers.get("a2_setBigInteger");
|
||||
MapSerializer<?, ?, ?, ?> serializerMap =
|
||||
(MapSerializer<?, ?, ?, ?>) serializers.get("a2_mapLocalTimeLocalTime");
|
||||
|
||||
assertThat(
|
||||
serializers.get("a2_arrayPrimDouble"),
|
||||
instanceOf(PrimitiveDoubleArraySerializer.class)
|
||||
);
|
||||
|
||||
assertThat(serializerList.getElementSerializer(), instanceOf(NumberSerializer.class));
|
||||
assertThat(serializerArray.getElementSerializer(), instanceOf(StringSerializer.class));
|
||||
assertThat(serializerSet.getElementSerializer(), instanceOf(BigIntegerSerializer.class));
|
||||
assertThat(serializerMap.getKeySerializer(), instanceOf(LocalTimeSerializer.class));
|
||||
assertThat(serializerMap.getValueSerializer(), instanceOf(LocalTimeSerializer.class));
|
||||
|
||||
assertThat(serializers.get("a2_point"), sameInstance(TestUtils.POINT_SERIALIZER));
|
||||
}
|
||||
|
||||
@Test
|
||||
void serializeAppliesFormatter() {
|
||||
@Configuration
|
||||
class A {
|
||||
int value1 = 1;
|
||||
int someValue2 = 2;
|
||||
}
|
||||
ConfigurationSerializer<A> serializer = newSerializer(
|
||||
A.class,
|
||||
builder -> builder.setFieldFormatter(FieldFormatters.UPPER_UNDERSCORE)
|
||||
);
|
||||
Map<?, ?> map = serializer.serialize(new A());
|
||||
assertThat(map.remove("VALUE1"), is(1L));
|
||||
assertThat(map.remove("SOME_VALUE2"), is(2L));
|
||||
assertTrue(map.isEmpty());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
private static final class B1 {
|
||||
int value1 = 1;
|
||||
int someValue2 = 2;
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeAppliesFormatter() {
|
||||
ConfigurationSerializer<B1> serializer = newSerializer(
|
||||
B1.class,
|
||||
builder -> builder.setFieldFormatter(FieldFormatters.UPPER_UNDERSCORE)
|
||||
);
|
||||
Map<String, ?> map = Map.of(
|
||||
"value1", 3,
|
||||
"someValue2", 4,
|
||||
"VALUE1", 5,
|
||||
"SOME_VALUE2", 6
|
||||
);
|
||||
B1 a = serializer.deserialize(map);
|
||||
assertThat(a.value1, is(5));
|
||||
assertThat(a.someValue2, is(6));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
private static final class B2 {
|
||||
boolean f1;
|
||||
char f2;
|
||||
byte f3;
|
||||
short f4;
|
||||
int f5;
|
||||
long f6;
|
||||
float f7;
|
||||
double f8;
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeNullForPrimitiveFields() {
|
||||
ConfigurationSerializer<B2> serializer = newSerializer(
|
||||
B2.class,
|
||||
builder -> builder.inputNulls(true)
|
||||
);
|
||||
for (int i = 1; i <= 8; i++) {
|
||||
String fieldName = "f" + i;
|
||||
Map<String, Object> map = asMap(fieldName, null);
|
||||
assertThrowsConfigurationException(
|
||||
() -> serializer.deserialize(map),
|
||||
"Cannot set " + getField(B2.class, fieldName) + " to null value.\n" +
|
||||
"Primitive types cannot be assigned null."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
private static final class B3 {
|
||||
String s = "";
|
||||
List<List<String>> l = List.of();
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeInvalidType() {
|
||||
ConfigurationSerializer<B3> serializer = newSerializer(B3.class);
|
||||
assertThrowsConfigurationException(
|
||||
() -> serializer.deserialize(Map.of("s", (byte) 3)),
|
||||
"Deserialization of value '3' with type class java.lang.Byte for field " +
|
||||
"java.lang.String de.exlll.configlib.ConfigurationSerializerTest$B3.s " +
|
||||
"failed.\nThe type of the object to be deserialized does not match the type " +
|
||||
"the deserializer expects."
|
||||
);
|
||||
assertThrowsConfigurationException(
|
||||
() -> serializer.deserialize(Map.of("l", List.of(List.of(3)))),
|
||||
"Deserialization of value '[[3]]' with type class " +
|
||||
"java.util.ImmutableCollections$List12 for field java.util.List " +
|
||||
"de.exlll.configlib.ConfigurationSerializerTest$B3.l failed.\n" +
|
||||
"The type of the object to be deserialized does not match the type the " +
|
||||
"deserializer expects."
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@Configuration
|
||||
private static final class B4 {
|
||||
private static final ExampleConfigurationB1 B4_NULL_B1 =
|
||||
ExampleInitializer.newExampleConfigurationB1_1();
|
||||
private static final List<String> B4_NULL_LIST = List.of();
|
||||
private static final Double[] B4_NULL_ARRAY = new Double[0];
|
||||
private static final Set<ExampleConfigurationB2> B4_NULL_SET = Set.of();
|
||||
private static final Map<LocalDate, BigDecimal> B4_NULL_MAP = Map.of();
|
||||
private static final Point B4_NULL_POINT = new Point(0, 0);
|
||||
|
||||
Integer nullInteger = 1;
|
||||
String nullString = "";
|
||||
ExampleEnum nullEnm = ExampleEnum.A;
|
||||
ExampleConfigurationB1 nullB1 = B4_NULL_B1;
|
||||
List<String> nullList = B4_NULL_LIST;
|
||||
Double[] nullArray = B4_NULL_ARRAY;
|
||||
Set<ExampleConfigurationB2> nullSet = B4_NULL_SET;
|
||||
Map<LocalDate, BigDecimal> nullMap = B4_NULL_MAP;
|
||||
Point nullPoint = B4_NULL_POINT;
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeWithoutNullDoesNotOverrideInitializedFields() {
|
||||
final Map<String, ?> map = entriesAsMap(
|
||||
entry("nullInteger", null),
|
||||
entry("nullString", null),
|
||||
entry("nullEnm", null),
|
||||
entry("nullB1", null),
|
||||
entry("nullList", null),
|
||||
entry("nullArray", null),
|
||||
entry("nullSet", null),
|
||||
entry("nullMap", null),
|
||||
entry("nullPoint", null)
|
||||
);
|
||||
ConfigurationSerializer<B4> serializer = newSerializer(B4.class);
|
||||
B4 config = serializer.deserialize(map);
|
||||
|
||||
assertEquals(1, config.nullInteger);
|
||||
assertEquals("", config.nullString);
|
||||
assertEquals(ExampleEnum.A, config.nullEnm);
|
||||
assertSame(B4.B4_NULL_B1, config.nullB1);
|
||||
assertSame(B4.B4_NULL_LIST, config.nullList);
|
||||
assertSame(B4.B4_NULL_ARRAY, config.nullArray);
|
||||
assertSame(B4.B4_NULL_SET, config.nullSet);
|
||||
assertSame(B4.B4_NULL_MAP, config.nullMap);
|
||||
assertSame(B4.B4_NULL_POINT, config.nullPoint);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
private static final class B5 {
|
||||
@Ignore
|
||||
private int ignored = 1;
|
||||
}
|
||||
|
||||
@Test
|
||||
void ctorRequiresConfigurationWithFields() {
|
||||
assertThrowsConfigurationException(
|
||||
() -> newSerializer(B5.class),
|
||||
"Configuration class 'B5' does not contain any (de-)serializable fields."
|
||||
);
|
||||
}
|
||||
|
||||
private static final class B6 {
|
||||
@Ignore
|
||||
private int ignored = 1;
|
||||
}
|
||||
|
||||
@Test
|
||||
void ctorRequiresConfiguration() {
|
||||
assertThrowsConfigurationException(
|
||||
() -> newSerializer(B6.class),
|
||||
"Class 'B6' must be a configuration."
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void buildSerializerMapPreventsRecursiveDefinitions() {
|
||||
assertThrowsConfigurationException(
|
||||
() -> newSerializer(R1.class),
|
||||
"Recursive type definitions are not supported."
|
||||
);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static final class R1 {
|
||||
R2 r2;
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static final class R2 {
|
||||
R1 r1;
|
||||
}
|
||||
|
||||
@Test
|
||||
void serializeTypeWithAbstractParent() {
|
||||
ConfigurationSerializer<B8> serializer = newSerializer(B8.class);
|
||||
Map<?, ?> serialize = serializer.serialize(new B8());
|
||||
B8 deserialize = serializer.deserialize(serialize);
|
||||
assertEquals(1, deserialize.i);
|
||||
assertEquals(2, deserialize.j);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static abstract class B7 {
|
||||
int i = 1;
|
||||
}
|
||||
|
||||
static final class B8 extends B7 {
|
||||
int j = 2;
|
||||
}
|
||||
}
|
@ -0,0 +1,147 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import com.google.common.jimfs.Jimfs;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class ConfigurationsTest {
|
||||
private static final FieldFilter includeI = field -> field.getName().equals("i");
|
||||
private final FileSystem fs = Jimfs.newFileSystem();
|
||||
private final Path yamlFile = fs.getPath("/tmp/config.yml");
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException {
|
||||
Files.createDirectories(yamlFile.getParent());
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws IOException {
|
||||
fs.close();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
private static final class Config {
|
||||
int i = 10;
|
||||
int j = 11;
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveYamlConfiguration1() {
|
||||
Config configuration = new Config();
|
||||
|
||||
Configurations.saveYamlConfiguration(yamlFile, Config.class, configuration);
|
||||
assertEquals("i: 10\nj: 11", TestUtils.readFile(yamlFile));
|
||||
|
||||
configuration.i = 20;
|
||||
Configurations.saveYamlConfiguration(yamlFile, Config.class, configuration);
|
||||
assertEquals("i: 20\nj: 11", TestUtils.readFile(yamlFile));
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveYamlConfiguration2() {
|
||||
Config configuration = new Config();
|
||||
|
||||
Configurations.saveYamlConfiguration(
|
||||
yamlFile, Config.class, configuration,
|
||||
builder -> builder.setFieldFilter(includeI)
|
||||
);
|
||||
assertEquals("i: 10", TestUtils.readFile(yamlFile));
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveYamlConfiguration3() {
|
||||
Config configuration = new Config();
|
||||
|
||||
Configurations.saveYamlConfiguration(
|
||||
yamlFile, Config.class, configuration,
|
||||
YamlConfigurationProperties.newBuilder().setFieldFilter(includeI).build()
|
||||
);
|
||||
assertEquals("i: 10", TestUtils.readFile(yamlFile));
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadYamlConfiguration1() {
|
||||
writeString("i: 20\nk: 30");
|
||||
Config config = Configurations.loadYamlConfiguration(yamlFile, Config.class);
|
||||
assertConfigEquals(config, 20, 11);
|
||||
|
||||
writeString("i: 20\nj: 30");
|
||||
config = Configurations.loadYamlConfiguration(yamlFile, Config.class);
|
||||
assertConfigEquals(config, 20, 30);
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadYamlConfiguration2() {
|
||||
writeString("i: 20\nj: 30");
|
||||
Config config = Configurations.loadYamlConfiguration(
|
||||
yamlFile, Config.class,
|
||||
builder -> builder.setFieldFilter(includeI)
|
||||
);
|
||||
assertConfigEquals(config, 20, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadYamlConfiguration3() {
|
||||
writeString("i: 20\nj: 30");
|
||||
|
||||
Config config = Configurations.loadYamlConfiguration(
|
||||
yamlFile, Config.class,
|
||||
YamlConfigurationProperties.newBuilder().setFieldFilter(includeI).build()
|
||||
);
|
||||
|
||||
assertConfigEquals(config, 20, 11);
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateYamlConfiguration1() {
|
||||
Config config = Configurations.updateYamlConfiguration(yamlFile, Config.class);
|
||||
assertConfigEquals(config, 10, 11);
|
||||
assertEquals("i: 10\nj: 11", TestUtils.readFile(yamlFile));
|
||||
|
||||
writeString("i: 20\nk: 30");
|
||||
config = Configurations.updateYamlConfiguration(yamlFile, Config.class);
|
||||
assertConfigEquals(config, 20, 11);
|
||||
assertEquals("i: 20\nj: 11", TestUtils.readFile(yamlFile));
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateYamlConfiguration2() {
|
||||
Config config = Configurations.updateYamlConfiguration(
|
||||
yamlFile, Config.class,
|
||||
builder -> builder.setFieldFilter(includeI)
|
||||
);
|
||||
assertConfigEquals(config, 10, 11);
|
||||
assertEquals("i: 10", TestUtils.readFile(yamlFile));
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateYamlConfiguration3() {
|
||||
Config config = Configurations.updateYamlConfiguration(
|
||||
yamlFile, Config.class,
|
||||
YamlConfigurationProperties.newBuilder().setFieldFilter(includeI).build()
|
||||
);
|
||||
assertConfigEquals(config, 10, 11);
|
||||
assertEquals("i: 10", TestUtils.readFile(yamlFile));
|
||||
}
|
||||
|
||||
private static void assertConfigEquals(Config config, int i, int j) {
|
||||
assertEquals(i, config.i);
|
||||
assertEquals(j, config.j);
|
||||
}
|
||||
|
||||
private void writeString(String string) {
|
||||
try {
|
||||
Files.writeString(yamlFile, string);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,673 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import com.google.common.jimfs.Jimfs;
|
||||
import de.exlll.configlib.configurations.*;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static de.exlll.configlib.configurations.ExampleConfigurationsSerialized.*;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class ExampleConfigurationTests {
|
||||
private static final ConfigurationProperties PROPERTIES_ALLOW_NULL = ConfigurationProperties.newBuilder()
|
||||
.addSerializer(Point.class, TestUtils.POINT_SERIALIZER)
|
||||
.outputNulls(true)
|
||||
.inputNulls(true)
|
||||
.build();
|
||||
private static final ConfigurationProperties PROPERTIES_DENY_NULL = ConfigurationProperties.newBuilder()
|
||||
.addSerializer(Point.class, TestUtils.POINT_SERIALIZER)
|
||||
.outputNulls(false)
|
||||
.inputNulls(false)
|
||||
.build();
|
||||
|
||||
private final FileSystem fs = Jimfs.newFileSystem();
|
||||
private final Path yamlFile = fs.getPath("/tmp/config.yml");
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException {
|
||||
Files.createDirectories(yamlFile.getParent());
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws IOException {
|
||||
fs.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void serializeExampleConfigurationB1() {
|
||||
ConfigurationSerializer<ExampleConfigurationB1> serializer =
|
||||
new ConfigurationSerializer<>(ExampleConfigurationB1.class, PROPERTIES_ALLOW_NULL);
|
||||
|
||||
ExampleConfigurationB1 config1 = ExampleInitializer.newExampleConfigurationB1_1();
|
||||
Map<?, ?> serialized1 = serializer.serialize(config1);
|
||||
assertEquals(EXAMPLE_CONFIGURATION_B1_1, serialized1);
|
||||
|
||||
ExampleConfigurationB1 config2 = ExampleInitializer.newExampleConfigurationB1_2();
|
||||
Map<?, ?> serialized2 = serializer.serialize(config2);
|
||||
assertEquals(EXAMPLE_CONFIGURATION_B1_2, serialized2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeExampleConfigurationB1() {
|
||||
ConfigurationSerializer<ExampleConfigurationB1> serializer =
|
||||
new ConfigurationSerializer<>(ExampleConfigurationB1.class, PROPERTIES_ALLOW_NULL);
|
||||
|
||||
assertExampleConfigurationsB1Equal(
|
||||
serializer.deserialize(EXAMPLE_CONFIGURATION_B1_1),
|
||||
ExampleInitializer.newExampleConfigurationB1_1()
|
||||
);
|
||||
|
||||
assertExampleConfigurationsB1Equal(
|
||||
serializer.deserialize(EXAMPLE_CONFIGURATION_B1_2),
|
||||
ExampleInitializer.newExampleConfigurationB1_2()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void serializeExampleConfigurationB2() {
|
||||
ConfigurationSerializer<ExampleConfigurationB2> serializer =
|
||||
new ConfigurationSerializer<>(ExampleConfigurationB2.class, PROPERTIES_ALLOW_NULL);
|
||||
|
||||
ExampleConfigurationB2 config1 = ExampleInitializer.newExampleConfigurationB2_1();
|
||||
Map<?, ?> serialized1 = serializer.serialize(config1);
|
||||
assertEquals(EXAMPLE_CONFIGURATION_B2_1, serialized1);
|
||||
|
||||
ExampleConfigurationB2 config2 = ExampleInitializer.newExampleConfigurationB2_2();
|
||||
Map<?, ?> serialized2 = serializer.serialize(config2);
|
||||
assertEquals(EXAMPLE_CONFIGURATION_B2_2, serialized2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeExampleConfigurationB2() {
|
||||
ConfigurationSerializer<ExampleConfigurationB2> serializer =
|
||||
new ConfigurationSerializer<>(ExampleConfigurationB2.class, PROPERTIES_ALLOW_NULL);
|
||||
|
||||
assertExampleConfigurationsB2Equal(
|
||||
serializer.deserialize(EXAMPLE_CONFIGURATION_B2_1),
|
||||
ExampleInitializer.newExampleConfigurationB2_1()
|
||||
);
|
||||
|
||||
assertExampleConfigurationsB2Equal(
|
||||
serializer.deserialize(EXAMPLE_CONFIGURATION_B2_2),
|
||||
ExampleInitializer.newExampleConfigurationB2_2()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void serializeExampleConfigurationA1() {
|
||||
ConfigurationSerializer<ExampleConfigurationA1> serializer =
|
||||
new ConfigurationSerializer<>(ExampleConfigurationA1.class, PROPERTIES_ALLOW_NULL);
|
||||
|
||||
ExampleConfigurationA1 config = ExampleInitializer.newExampleConfigurationA2();
|
||||
Map<?, ?> serialized = serializer.serialize(config);
|
||||
assertEquals(EXAMPLE_CONFIGURATION_A1, serialized);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeExampleConfigurationA1() {
|
||||
ConfigurationSerializer<ExampleConfigurationA1> serializer =
|
||||
new ConfigurationSerializer<>(ExampleConfigurationA1.class, PROPERTIES_ALLOW_NULL);
|
||||
|
||||
assertExampleConfigurationsA1Equal(
|
||||
serializer.deserialize(EXAMPLE_CONFIGURATION_A1),
|
||||
ExampleInitializer.newExampleConfigurationA2()
|
||||
);
|
||||
|
||||
assertExampleConfigurationsA1Equal(
|
||||
serializer.deserialize(EXAMPLE_CONFIGURATION_A2),
|
||||
ExampleInitializer.newExampleConfigurationA2()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void serializeExampleConfigurationA2() {
|
||||
ConfigurationSerializer<ExampleConfigurationA2> serializer =
|
||||
new ConfigurationSerializer<>(ExampleConfigurationA2.class, PROPERTIES_ALLOW_NULL);
|
||||
|
||||
ExampleConfigurationA2 config = ExampleInitializer.newExampleConfigurationA2();
|
||||
Map<?, ?> serialized = serializer.serialize(config);
|
||||
assertEquals(EXAMPLE_CONFIGURATION_A2, serialized);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeExampleConfigurationA2() {
|
||||
ConfigurationSerializer<ExampleConfigurationA2> serializer =
|
||||
new ConfigurationSerializer<>(ExampleConfigurationA2.class, PROPERTIES_ALLOW_NULL);
|
||||
|
||||
assertExampleConfigurationsA2Equal(
|
||||
serializer.deserialize(EXAMPLE_CONFIGURATION_A2),
|
||||
ExampleInitializer.newExampleConfigurationA2()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void serializeExampleConfigurationsNullsWithNullCollectionElements() {
|
||||
ConfigurationSerializer<ExampleConfigurationNulls> serializer =
|
||||
new ConfigurationSerializer<>(ExampleConfigurationNulls.class, PROPERTIES_ALLOW_NULL);
|
||||
|
||||
ExampleConfigurationNulls config1 = ExampleInitializer
|
||||
.newExampleConfigurationNullsWithNullCollectionElements1();
|
||||
Map<?, ?> serialized1 = serializer.serialize(config1);
|
||||
assertEquals(EXAMPLE_CONFIGURATION_NULLS_WITH_1, serialized1);
|
||||
|
||||
ExampleConfigurationNulls config2 = ExampleInitializer
|
||||
.newExampleConfigurationNullsWithNullCollectionElements2();
|
||||
Map<?, ?> serialized2 = serializer.serialize(config2);
|
||||
assertEquals(EXAMPLE_CONFIGURATION_NULLS_WITH_2, serialized2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeExampleConfigurationNullsWithNullCollectionElements() {
|
||||
ConfigurationSerializer<ExampleConfigurationNulls> serializer =
|
||||
new ConfigurationSerializer<>(ExampleConfigurationNulls.class, PROPERTIES_ALLOW_NULL);
|
||||
|
||||
assertExampleConfigurationsNullsEqual(
|
||||
serializer.deserialize(EXAMPLE_CONFIGURATION_NULLS_WITH_1),
|
||||
ExampleInitializer.newExampleConfigurationNullsWithNullCollectionElements1()
|
||||
);
|
||||
|
||||
assertExampleConfigurationsNullsEqual(
|
||||
serializer.deserialize(EXAMPLE_CONFIGURATION_NULLS_WITH_2),
|
||||
ExampleInitializer.newExampleConfigurationNullsWithNullCollectionElements2()
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void serializeExampleConfigurationsNullsWithoutNullCollectionElements() {
|
||||
ConfigurationSerializer<ExampleConfigurationNulls> serializer =
|
||||
new ConfigurationSerializer<>(ExampleConfigurationNulls.class, PROPERTIES_DENY_NULL);
|
||||
|
||||
ExampleConfigurationNulls config1 = ExampleInitializer
|
||||
.newExampleConfigurationNullsWithoutNullCollectionElements1();
|
||||
Map<?, ?> serialized1 = serializer.serialize(config1);
|
||||
assertEquals(EXAMPLE_CONFIGURATION_NULLS_WITHOUT_1, serialized1);
|
||||
|
||||
ExampleConfigurationNulls config2 = ExampleInitializer
|
||||
.newExampleConfigurationNullsWithoutNullCollectionElements2();
|
||||
Map<?, ?> serialized2 = serializer.serialize(config2);
|
||||
assertEquals(EXAMPLE_CONFIGURATION_NULLS_WITHOUT_2, serialized2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void deserializeExampleConfigurationNullsWithoutNullCollectionElements() {
|
||||
ConfigurationSerializer<ExampleConfigurationNulls> serializer =
|
||||
new ConfigurationSerializer<>(ExampleConfigurationNulls.class, PROPERTIES_DENY_NULL);
|
||||
|
||||
assertExampleConfigurationsNullsEqual(
|
||||
ExampleInitializer.newExampleConfigurationNullsWithoutNullCollectionElements1(),
|
||||
serializer.deserialize(EXAMPLE_CONFIGURATION_NULLS_WITHOUT_1)
|
||||
);
|
||||
|
||||
assertExampleConfigurationsNullsEqual(
|
||||
ExampleInitializer.newExampleConfigurationNullsWithoutNullCollectionElements2(),
|
||||
serializer.deserialize(EXAMPLE_CONFIGURATION_NULLS_WITHOUT_2)
|
||||
);
|
||||
}
|
||||
|
||||
private static void assertExampleConfigurationsNullsEqual(
|
||||
ExampleConfigurationNulls expected,
|
||||
ExampleConfigurationNulls actual
|
||||
) {
|
||||
assertEquals(expected.getNullInteger(), actual.getNullInteger());
|
||||
assertEquals(expected.getNullString(), actual.getNullString());
|
||||
assertEquals(expected.getNullEnm(), actual.getNullEnm());
|
||||
assertEquals(expected.getNullB1(), actual.getNullB1());
|
||||
assertEquals(expected.getNullList(), actual.getNullList());
|
||||
assertEquals(expected.getNullArray(), actual.getNullArray());
|
||||
assertEquals(expected.getNullSet(), actual.getNullSet());
|
||||
assertEquals(expected.getNullMap(), actual.getNullMap());
|
||||
assertEquals(expected.getNullPoint(), actual.getNullPoint());
|
||||
assertEquals(expected.getListNullString(), actual.getListNullString());
|
||||
assertArrayEquals(expected.getArrayNullDouble(), actual.getArrayNullDouble());
|
||||
assertEquals(expected.getSetNullInteger(), actual.getSetNullInteger());
|
||||
assertEquals(expected.getMapNullEnmKey(), actual.getMapNullEnmKey());
|
||||
assertEquals(expected.getMapNullBigIntegerValue(), actual.getMapNullBigIntegerValue());
|
||||
assertEquals(expected, actual);
|
||||
}
|
||||
|
||||
private static void assertExampleConfigurationsA1Equal(
|
||||
ExampleConfigurationA1 a1_1,
|
||||
ExampleConfigurationA1 a1_2
|
||||
) {
|
||||
assertThat(ExampleConfigurationA1.getA1_staticFinalInt(), is(1));
|
||||
assertThat(ExampleConfigurationA1.getA1_staticInt(), is(2));
|
||||
|
||||
assertThat(a1_1.getA1_finalInt(), is(a1_2.getA1_finalInt()));
|
||||
assertThat(a1_1.getA1_transientInt(), is(a1_2.getA1_transientInt()));
|
||||
assertThat(a1_1.getA1_ignoredInt(), is(a1_2.getA1_ignoredInt()));
|
||||
assertThat(a1_1.getA1_ignoredString(), is(a1_2.getA1_ignoredString()));
|
||||
assertThat(a1_1.getA1_ignoredListString(), is(a1_2.getA1_ignoredListString()));
|
||||
assertThat(a1_1.isA1_primBool(), is(a1_2.isA1_primBool()));
|
||||
assertThat(a1_1.getA1_primChar(), is(a1_2.getA1_primChar()));
|
||||
assertThat(a1_1.getA1_primByte(), is(a1_2.getA1_primByte()));
|
||||
assertThat(a1_1.getA1_primShort(), is(a1_2.getA1_primShort()));
|
||||
assertThat(a1_1.getA1_primInt(), is(a1_2.getA1_primInt()));
|
||||
assertThat(a1_1.getA1_primLong(), is(a1_2.getA1_primLong()));
|
||||
assertThat(a1_1.getA1_primFloat(), is(a1_2.getA1_primFloat()));
|
||||
assertThat(a1_1.getA1_primDouble(), is(a1_2.getA1_primDouble()));
|
||||
assertThat(a1_1.getA1_refBool(), is(a1_2.getA1_refBool()));
|
||||
assertThat(a1_1.getA1_refChar(), is(a1_2.getA1_refChar()));
|
||||
assertThat(a1_1.getA1_refByte(), is(a1_2.getA1_refByte()));
|
||||
assertThat(a1_1.getA1_refShort(), is(a1_2.getA1_refShort()));
|
||||
assertThat(a1_1.getA1_refInt(), is(a1_2.getA1_refInt()));
|
||||
assertThat(a1_1.getA1_refLong(), is(a1_2.getA1_refLong()));
|
||||
assertThat(a1_1.getA1_refFloat(), is(a1_2.getA1_refFloat()));
|
||||
assertThat(a1_1.getA1_refDouble(), is(a1_2.getA1_refDouble()));
|
||||
assertThat(a1_1.getA1_string(), is(a1_2.getA1_string()));
|
||||
assertThat(a1_1.getA1_bigInteger(), is(a1_2.getA1_bigInteger()));
|
||||
assertThat(a1_1.getA1_bigDecimal(), is(a1_2.getA1_bigDecimal()));
|
||||
assertThat(a1_1.getA1_localDate(), is(a1_2.getA1_localDate()));
|
||||
assertThat(a1_1.getA1_localTime(), is(a1_2.getA1_localTime()));
|
||||
assertThat(a1_1.getA1_localDateTime(), is(a1_2.getA1_localDateTime()));
|
||||
assertThat(a1_1.getA1_Enm(), is(a1_2.getA1_Enm()));
|
||||
assertThat(a1_1.getA1_b1(), is(a1_2.getA1_b1()));
|
||||
assertThat(a1_1.getA1_b2(), is(a1_2.getA1_b2()));
|
||||
assertThat(a1_1.getA1_listBoolean(), is(a1_2.getA1_listBoolean()));
|
||||
assertThat(a1_1.getA1_listChar(), is(a1_2.getA1_listChar()));
|
||||
assertThat(a1_1.getA1_listByte(), is(a1_2.getA1_listByte()));
|
||||
assertThat(a1_1.getA1_listShort(), is(a1_2.getA1_listShort()));
|
||||
assertThat(a1_1.getA1_listInteger(), is(a1_2.getA1_listInteger()));
|
||||
assertThat(a1_1.getA1_listLong(), is(a1_2.getA1_listLong()));
|
||||
assertThat(a1_1.getA1_listFloat(), is(a1_2.getA1_listFloat()));
|
||||
assertThat(a1_1.getA1_listDouble(), is(a1_2.getA1_listDouble()));
|
||||
assertThat(a1_1.getA1_listString(), is(a1_2.getA1_listString()));
|
||||
assertThat(a1_1.getA1_listBigInteger(), is(a1_2.getA1_listBigInteger()));
|
||||
assertThat(a1_1.getA1_listBigDecimal(), is(a1_2.getA1_listBigDecimal()));
|
||||
assertThat(a1_1.getA1_listLocalDate(), is(a1_2.getA1_listLocalDate()));
|
||||
assertThat(a1_1.getA1_listLocalTime(), is(a1_2.getA1_listLocalTime()));
|
||||
assertThat(a1_1.getA1_listLocalDateTime(), is(a1_2.getA1_listLocalDateTime()));
|
||||
assertThat(a1_1.getA1_listEnm(), is(a1_2.getA1_listEnm()));
|
||||
assertThat(a1_1.getA1_listB1(), is(a1_2.getA1_listB1()));
|
||||
assertThat(a1_1.getA1_listB2(), is(a1_2.getA1_listB2()));
|
||||
assertThat(a1_1.getA1_arrayPrimBoolean(), is(a1_2.getA1_arrayPrimBoolean()));
|
||||
assertThat(a1_1.getA1_arrayPrimChar(), is(a1_2.getA1_arrayPrimChar()));
|
||||
assertThat(a1_1.getA1_arrayPrimByte(), is(a1_2.getA1_arrayPrimByte()));
|
||||
assertThat(a1_1.getA1_arrayPrimShort(), is(a1_2.getA1_arrayPrimShort()));
|
||||
assertThat(a1_1.getA1_arrayPrimInteger(), is(a1_2.getA1_arrayPrimInteger()));
|
||||
assertThat(a1_1.getA1_arrayPrimLong(), is(a1_2.getA1_arrayPrimLong()));
|
||||
assertThat(a1_1.getA1_arrayPrimFloat(), is(a1_2.getA1_arrayPrimFloat()));
|
||||
assertThat(a1_1.getA1_arrayPrimDouble(), is(a1_2.getA1_arrayPrimDouble()));
|
||||
assertThat(a1_1.getA1_arrayBoolean(), is(a1_2.getA1_arrayBoolean()));
|
||||
assertThat(a1_1.getA1_arrayChar(), is(a1_2.getA1_arrayChar()));
|
||||
assertThat(a1_1.getA1_arrayByte(), is(a1_2.getA1_arrayByte()));
|
||||
assertThat(a1_1.getA1_arrayShort(), is(a1_2.getA1_arrayShort()));
|
||||
assertThat(a1_1.getA1_arrayInteger(), is(a1_2.getA1_arrayInteger()));
|
||||
assertThat(a1_1.getA1_arrayLong(), is(a1_2.getA1_arrayLong()));
|
||||
assertThat(a1_1.getA1_arrayFloat(), is(a1_2.getA1_arrayFloat()));
|
||||
assertThat(a1_1.getA1_arrayDouble(), is(a1_2.getA1_arrayDouble()));
|
||||
assertThat(a1_1.getA1_arrayString(), is(a1_2.getA1_arrayString()));
|
||||
assertThat(a1_1.getA1_arrayBigInteger(), is(a1_2.getA1_arrayBigInteger()));
|
||||
assertThat(a1_1.getA1_arrayBigDecimal(), is(a1_2.getA1_arrayBigDecimal()));
|
||||
assertThat(a1_1.getA1_arrayLocalDate(), is(a1_2.getA1_arrayLocalDate()));
|
||||
assertThat(a1_1.getA1_arrayLocalTime(), is(a1_2.getA1_arrayLocalTime()));
|
||||
assertThat(a1_1.getA1_arrayLocalDateTime(), is(a1_2.getA1_arrayLocalDateTime()));
|
||||
assertThat(a1_1.getA1_arrayEnm(), is(a1_2.getA1_arrayEnm()));
|
||||
assertThat(a1_1.getA1_arrayB1(), is(a1_2.getA1_arrayB1()));
|
||||
assertThat(a1_1.getA1_arrayB2(), is(a1_2.getA1_arrayB2()));
|
||||
assertThat(a1_1.getA1_setBoolean(), is(a1_2.getA1_setBoolean()));
|
||||
assertThat(a1_1.getA1_setChar(), is(a1_2.getA1_setChar()));
|
||||
assertThat(a1_1.getA1_setByte(), is(a1_2.getA1_setByte()));
|
||||
assertThat(a1_1.getA1_setShort(), is(a1_2.getA1_setShort()));
|
||||
assertThat(a1_1.getA1_setInteger(), is(a1_2.getA1_setInteger()));
|
||||
assertThat(a1_1.getA1_setLong(), is(a1_2.getA1_setLong()));
|
||||
assertThat(a1_1.getA1_setFloat(), is(a1_2.getA1_setFloat()));
|
||||
assertThat(a1_1.getA1_setDouble(), is(a1_2.getA1_setDouble()));
|
||||
assertThat(a1_1.getA1_setString(), is(a1_2.getA1_setString()));
|
||||
assertThat(a1_1.getA1_setBigInteger(), is(a1_2.getA1_setBigInteger()));
|
||||
assertThat(a1_1.getA1_setBigDecimal(), is(a1_2.getA1_setBigDecimal()));
|
||||
assertThat(a1_1.getA1_setLocalDate(), is(a1_2.getA1_setLocalDate()));
|
||||
assertThat(a1_1.getA1_setLocalTime(), is(a1_2.getA1_setLocalTime()));
|
||||
assertThat(a1_1.getA1_setLocalDateTime(), is(a1_2.getA1_setLocalDateTime()));
|
||||
assertThat(a1_1.getA1_setEnm(), is(a1_2.getA1_setEnm()));
|
||||
assertThat(a1_1.getA1_setB1(), is(a1_2.getA1_setB1()));
|
||||
assertThat(a1_1.getA1_setB2(), is(a1_2.getA1_setB2()));
|
||||
assertThat(a1_1.getA1_mapBooleanBoolean(), is(a1_2.getA1_mapBooleanBoolean()));
|
||||
assertThat(a1_1.getA1_mapCharChar(), is(a1_2.getA1_mapCharChar()));
|
||||
assertThat(a1_1.getA1_mapByteByte(), is(a1_2.getA1_mapByteByte()));
|
||||
assertThat(a1_1.getA1_mapShortShort(), is(a1_2.getA1_mapShortShort()));
|
||||
assertThat(a1_1.getA1_mapIntegerInteger(), is(a1_2.getA1_mapIntegerInteger()));
|
||||
assertThat(a1_1.getA1_mapLongLong(), is(a1_2.getA1_mapLongLong()));
|
||||
assertThat(a1_1.getA1_mapFloatFloat(), is(a1_2.getA1_mapFloatFloat()));
|
||||
assertThat(a1_1.getA1_mapDoubleDouble(), is(a1_2.getA1_mapDoubleDouble()));
|
||||
assertThat(a1_1.getA1_mapStringString(), is(a1_2.getA1_mapStringString()));
|
||||
assertThat(a1_1.getA1_mapBigIntegerBigInteger(), is(a1_2.getA1_mapBigIntegerBigInteger()));
|
||||
assertThat(a1_1.getA1_mapBigDecimalBigDecimal(), is(a1_2.getA1_mapBigDecimalBigDecimal()));
|
||||
assertThat(a1_1.getA1_mapLocalDateLocalDate(), is(a1_2.getA1_mapLocalDateLocalDate()));
|
||||
assertThat(a1_1.getA1_mapLocalTimeLocalTime(), is(a1_2.getA1_mapLocalTimeLocalTime()));
|
||||
assertThat(a1_1.getA1_mapLocalDateTimeLocalDateTime(), is(a1_2.getA1_mapLocalDateTimeLocalDateTime()));
|
||||
assertThat(a1_1.getA1_mapEnmEnm(), is(a1_2.getA1_mapEnmEnm()));
|
||||
assertThat(a1_1.getA1_mapIntegerB1(), is(a1_2.getA1_mapIntegerB1()));
|
||||
assertThat(a1_1.getA1_mapEnmB2(), is(a1_2.getA1_mapEnmB2()));
|
||||
assertThat(a1_1.getA1_listEmpty(), is(a1_2.getA1_listEmpty()));
|
||||
assertThat(a1_1.getA1_arrayEmpty(), is(a1_2.getA1_arrayEmpty()));
|
||||
assertThat(a1_1.getA1_setEmpty(), is(a1_2.getA1_setEmpty()));
|
||||
assertThat(a1_1.getA1_mapEmpty(), is(a1_2.getA1_mapEmpty()));
|
||||
assertThat(a1_1.getA1_listListByte(), is(a1_2.getA1_listListByte()));
|
||||
assertDeepEquals(a1_1.getA1_listArrayFloat(), a1_2.getA1_listArrayFloat(), ArrayList::new);
|
||||
assertThat(a1_1.getA1_listSetString(), is(a1_2.getA1_listSetString()));
|
||||
assertThat(a1_1.getA1_listMapEnmLocalDate(), is(a1_2.getA1_listMapEnmLocalDate()));
|
||||
assertThat(a1_1.getA1_setSetShort(), is(a1_2.getA1_setSetShort()));
|
||||
assertDeepEquals(a1_1.getA1_setArrayDouble(), a1_2.getA1_setArrayDouble(), HashSet::new);
|
||||
assertThat(a1_1.getA1_setListString(), is(a1_2.getA1_setListString()));
|
||||
assertThat(a1_1.getA1_setMapEnmLocalTime(), is(a1_2.getA1_setMapEnmLocalTime()));
|
||||
assertThat(a1_1.getA1_mapIntegerMapLongBoolean(), is(a1_2.getA1_mapIntegerMapLongBoolean()));
|
||||
assertThat(a1_1.getA1_mapStringListB1(), is(a1_2.getA1_mapStringListB1()));
|
||||
assertEquals(a1_2.getA1_mapBigIntegerArrayBigDecimal().keySet(), a1_1.getA1_mapBigIntegerArrayBigDecimal().keySet());
|
||||
assertDeepEquals(a1_2.getA1_mapBigIntegerArrayBigDecimal().values(), a1_2.getA1_mapBigIntegerArrayBigDecimal().values(), HashSet::new);
|
||||
assertThat(a1_1.getA1_mapEnmSetB2(), is(a1_2.getA1_mapEnmSetB2()));
|
||||
assertThat(a1_1.getA1_mapIntegerListMapShortSetB2(), is(a1_2.getA1_mapIntegerListMapShortSetB2()));
|
||||
assertThat(a1_1.getA1_arrayArrayPrimBoolean(), is(a1_2.getA1_arrayArrayPrimBoolean()));
|
||||
assertThat(a1_1.getA1_arrayArrayPrimChar(), is(a1_2.getA1_arrayArrayPrimChar()));
|
||||
assertThat(a1_1.getA1_arrayArrayPrimByte(), is(a1_2.getA1_arrayArrayPrimByte()));
|
||||
assertThat(a1_1.getA1_arrayArrayPrimShort(), is(a1_2.getA1_arrayArrayPrimShort()));
|
||||
assertThat(a1_1.getA1_arrayArrayPrimInteger(), is(a1_2.getA1_arrayArrayPrimInteger()));
|
||||
assertThat(a1_1.getA1_arrayArrayPrimLong(), is(a1_2.getA1_arrayArrayPrimLong()));
|
||||
assertThat(a1_1.getA1_arrayArrayPrimFloat(), is(a1_2.getA1_arrayArrayPrimFloat()));
|
||||
assertThat(a1_1.getA1_arrayArrayPrimDouble(), is(a1_2.getA1_arrayArrayPrimDouble()));
|
||||
assertThat(a1_1.getA1_arrayArrayBoolean(), is(a1_2.getA1_arrayArrayBoolean()));
|
||||
assertThat(a1_1.getA1_arrayArrayChar(), is(a1_2.getA1_arrayArrayChar()));
|
||||
assertThat(a1_1.getA1_arrayArrayByte(), is(a1_2.getA1_arrayArrayByte()));
|
||||
assertThat(a1_1.getA1_arrayArrayShort(), is(a1_2.getA1_arrayArrayShort()));
|
||||
assertThat(a1_1.getA1_arrayArrayInteger(), is(a1_2.getA1_arrayArrayInteger()));
|
||||
assertThat(a1_1.getA1_arrayArrayLong(), is(a1_2.getA1_arrayArrayLong()));
|
||||
assertThat(a1_1.getA1_arrayArrayFloat(), is(a1_2.getA1_arrayArrayFloat()));
|
||||
assertThat(a1_1.getA1_arrayArrayDouble(), is(a1_2.getA1_arrayArrayDouble()));
|
||||
assertThat(a1_1.getA1_arrayArrayString(), is(a1_2.getA1_arrayArrayString()));
|
||||
assertThat(a1_1.getA1_arrayArrayBigInteger(), is(a1_2.getA1_arrayArrayBigInteger()));
|
||||
assertThat(a1_1.getA1_arrayArrayBigDecimal(), is(a1_2.getA1_arrayArrayBigDecimal()));
|
||||
assertThat(a1_1.getA1_arrayArrayLocalDate(), is(a1_2.getA1_arrayArrayLocalDate()));
|
||||
assertThat(a1_1.getA1_arrayArrayLocalTime(), is(a1_2.getA1_arrayArrayLocalTime()));
|
||||
assertThat(a1_1.getA1_arrayArrayLocalDateTime(), is(a1_2.getA1_arrayArrayLocalDateTime()));
|
||||
assertThat(a1_1.getA1_arrayArrayEnm(), is(a1_2.getA1_arrayArrayEnm()));
|
||||
assertThat(a1_1.getA1_arrayArrayB1(), is(a1_2.getA1_arrayArrayB1()));
|
||||
assertThat(a1_1.getA1_arrayArrayB2(), is(a1_2.getA1_arrayArrayB2()));
|
||||
assertThat(a1_1.getA1_point(), is(a1_2.getA1_point()));
|
||||
assertThat(a1_1.getA1_listPoint(), is(a1_2.getA1_listPoint()));
|
||||
assertThat(a1_1.getA1_arrayPoint(), is(a1_2.getA1_arrayPoint()));
|
||||
assertThat(a1_1.getA1_setPoint(), is(a1_2.getA1_setPoint()));
|
||||
assertThat(a1_1.getA1_mapEnmListPoint(), is(a1_2.getA1_mapEnmListPoint()));
|
||||
}
|
||||
|
||||
static void assertExampleConfigurationsA2Equal(
|
||||
ExampleConfigurationA2 a2_1,
|
||||
ExampleConfigurationA2 a2_2
|
||||
) {
|
||||
assertThat(ExampleConfigurationA2.getA1_staticFinalInt(), is(1));
|
||||
assertThat(ExampleConfigurationA2.getA1_staticInt(), is(2));
|
||||
|
||||
assertExampleConfigurationsA1Equal(a2_1, a2_2);
|
||||
|
||||
assertThat(a2_1.getA2_finalInt(), is(a2_2.getA2_finalInt()));
|
||||
assertThat(a2_1.getA2_transientInt(), is(a2_2.getA2_transientInt()));
|
||||
assertThat(a2_1.getA2_ignoredInt(), is(a2_2.getA2_ignoredInt()));
|
||||
assertThat(a2_1.getA2_ignoredString(), is(a2_2.getA2_ignoredString()));
|
||||
assertThat(a2_1.getA2_ignoredListString(), is(a2_2.getA2_ignoredListString()));
|
||||
assertThat(a2_1.isA2_primBool(), is(a2_2.isA2_primBool()));
|
||||
assertThat(a2_1.getA2_primChar(), is(a2_2.getA2_primChar()));
|
||||
assertThat(a2_1.getA2_primByte(), is(a2_2.getA2_primByte()));
|
||||
assertThat(a2_1.getA2_primShort(), is(a2_2.getA2_primShort()));
|
||||
assertThat(a2_1.getA2_primInt(), is(a2_2.getA2_primInt()));
|
||||
assertThat(a2_1.getA2_primLong(), is(a2_2.getA2_primLong()));
|
||||
assertThat(a2_1.getA2_primFloat(), is(a2_2.getA2_primFloat()));
|
||||
assertThat(a2_1.getA2_primDouble(), is(a2_2.getA2_primDouble()));
|
||||
assertThat(a2_1.getA2_refBool(), is(a2_2.getA2_refBool()));
|
||||
assertThat(a2_1.getA2_refChar(), is(a2_2.getA2_refChar()));
|
||||
assertThat(a2_1.getA2_refByte(), is(a2_2.getA2_refByte()));
|
||||
assertThat(a2_1.getA2_refShort(), is(a2_2.getA2_refShort()));
|
||||
assertThat(a2_1.getA2_refInt(), is(a2_2.getA2_refInt()));
|
||||
assertThat(a2_1.getA2_refLong(), is(a2_2.getA2_refLong()));
|
||||
assertThat(a2_1.getA2_refFloat(), is(a2_2.getA2_refFloat()));
|
||||
assertThat(a2_1.getA2_refDouble(), is(a2_2.getA2_refDouble()));
|
||||
assertThat(a2_1.getA2_string(), is(a2_2.getA2_string()));
|
||||
assertThat(a2_1.getA2_bigInteger(), is(a2_2.getA2_bigInteger()));
|
||||
assertThat(a2_1.getA2_bigDecimal(), is(a2_2.getA2_bigDecimal()));
|
||||
assertThat(a2_1.getA2_localDate(), is(a2_2.getA2_localDate()));
|
||||
assertThat(a2_1.getA2_localTime(), is(a2_2.getA2_localTime()));
|
||||
assertThat(a2_1.getA2_localDateTime(), is(a2_2.getA2_localDateTime()));
|
||||
assertThat(a2_1.getA2_Enm(), is(a2_2.getA2_Enm()));
|
||||
assertThat(a2_1.getA2_b1(), is(a2_2.getA2_b1()));
|
||||
assertThat(a2_1.getA2_b2(), is(a2_2.getA2_b2()));
|
||||
assertThat(a2_1.getA2_listBoolean(), is(a2_2.getA2_listBoolean()));
|
||||
assertThat(a2_1.getA2_listChar(), is(a2_2.getA2_listChar()));
|
||||
assertThat(a2_1.getA2_listByte(), is(a2_2.getA2_listByte()));
|
||||
assertThat(a2_1.getA2_listShort(), is(a2_2.getA2_listShort()));
|
||||
assertThat(a2_1.getA2_listInteger(), is(a2_2.getA2_listInteger()));
|
||||
assertThat(a2_1.getA2_listLong(), is(a2_2.getA2_listLong()));
|
||||
assertThat(a2_1.getA2_listFloat(), is(a2_2.getA2_listFloat()));
|
||||
assertThat(a2_1.getA2_listDouble(), is(a2_2.getA2_listDouble()));
|
||||
assertThat(a2_1.getA2_listString(), is(a2_2.getA2_listString()));
|
||||
assertThat(a2_1.getA2_listBigInteger(), is(a2_2.getA2_listBigInteger()));
|
||||
assertThat(a2_1.getA2_listBigDecimal(), is(a2_2.getA2_listBigDecimal()));
|
||||
assertThat(a2_1.getA2_listLocalDate(), is(a2_2.getA2_listLocalDate()));
|
||||
assertThat(a2_1.getA2_listLocalTime(), is(a2_2.getA2_listLocalTime()));
|
||||
assertThat(a2_1.getA2_listLocalDateTime(), is(a2_2.getA2_listLocalDateTime()));
|
||||
assertThat(a2_1.getA2_listEnm(), is(a2_2.getA2_listEnm()));
|
||||
assertThat(a2_1.getA2_listB1(), is(a2_2.getA2_listB1()));
|
||||
assertThat(a2_1.getA2_listB2(), is(a2_2.getA2_listB2()));
|
||||
assertThat(a2_1.getA2_arrayPrimBoolean(), is(a2_2.getA2_arrayPrimBoolean()));
|
||||
assertThat(a2_1.getA2_arrayPrimChar(), is(a2_2.getA2_arrayPrimChar()));
|
||||
assertThat(a2_1.getA2_arrayPrimByte(), is(a2_2.getA2_arrayPrimByte()));
|
||||
assertThat(a2_1.getA2_arrayPrimShort(), is(a2_2.getA2_arrayPrimShort()));
|
||||
assertThat(a2_1.getA2_arrayPrimInteger(), is(a2_2.getA2_arrayPrimInteger()));
|
||||
assertThat(a2_1.getA2_arrayPrimLong(), is(a2_2.getA2_arrayPrimLong()));
|
||||
assertThat(a2_1.getA2_arrayPrimFloat(), is(a2_2.getA2_arrayPrimFloat()));
|
||||
assertThat(a2_1.getA2_arrayPrimDouble(), is(a2_2.getA2_arrayPrimDouble()));
|
||||
assertThat(a2_1.getA2_arrayBoolean(), is(a2_2.getA2_arrayBoolean()));
|
||||
assertThat(a2_1.getA2_arrayChar(), is(a2_2.getA2_arrayChar()));
|
||||
assertThat(a2_1.getA2_arrayByte(), is(a2_2.getA2_arrayByte()));
|
||||
assertThat(a2_1.getA2_arrayShort(), is(a2_2.getA2_arrayShort()));
|
||||
assertThat(a2_1.getA2_arrayInteger(), is(a2_2.getA2_arrayInteger()));
|
||||
assertThat(a2_1.getA2_arrayLong(), is(a2_2.getA2_arrayLong()));
|
||||
assertThat(a2_1.getA2_arrayFloat(), is(a2_2.getA2_arrayFloat()));
|
||||
assertThat(a2_1.getA2_arrayDouble(), is(a2_2.getA2_arrayDouble()));
|
||||
assertThat(a2_1.getA2_arrayString(), is(a2_2.getA2_arrayString()));
|
||||
assertThat(a2_1.getA2_arrayBigInteger(), is(a2_2.getA2_arrayBigInteger()));
|
||||
assertThat(a2_1.getA2_arrayBigDecimal(), is(a2_2.getA2_arrayBigDecimal()));
|
||||
assertThat(a2_1.getA2_arrayLocalDate(), is(a2_2.getA2_arrayLocalDate()));
|
||||
assertThat(a2_1.getA2_arrayLocalTime(), is(a2_2.getA2_arrayLocalTime()));
|
||||
assertThat(a2_1.getA2_arrayLocalDateTime(), is(a2_2.getA2_arrayLocalDateTime()));
|
||||
assertThat(a2_1.getA2_arrayEnm(), is(a2_2.getA2_arrayEnm()));
|
||||
assertThat(a2_1.getA2_arrayB1(), is(a2_2.getA2_arrayB1()));
|
||||
assertThat(a2_1.getA2_arrayB2(), is(a2_2.getA2_arrayB2()));
|
||||
assertThat(a2_1.getA2_setBoolean(), is(a2_2.getA2_setBoolean()));
|
||||
assertThat(a2_1.getA2_setChar(), is(a2_2.getA2_setChar()));
|
||||
assertThat(a2_1.getA2_setByte(), is(a2_2.getA2_setByte()));
|
||||
assertThat(a2_1.getA2_setShort(), is(a2_2.getA2_setShort()));
|
||||
assertThat(a2_1.getA2_setInteger(), is(a2_2.getA2_setInteger()));
|
||||
assertThat(a2_1.getA2_setLong(), is(a2_2.getA2_setLong()));
|
||||
assertThat(a2_1.getA2_setFloat(), is(a2_2.getA2_setFloat()));
|
||||
assertThat(a2_1.getA2_setDouble(), is(a2_2.getA2_setDouble()));
|
||||
assertThat(a2_1.getA2_setString(), is(a2_2.getA2_setString()));
|
||||
assertThat(a2_1.getA2_setBigInteger(), is(a2_2.getA2_setBigInteger()));
|
||||
assertThat(a2_1.getA2_setBigDecimal(), is(a2_2.getA2_setBigDecimal()));
|
||||
assertThat(a2_1.getA2_setLocalDate(), is(a2_2.getA2_setLocalDate()));
|
||||
assertThat(a2_1.getA2_setLocalTime(), is(a2_2.getA2_setLocalTime()));
|
||||
assertThat(a2_1.getA2_setLocalDateTime(), is(a2_2.getA2_setLocalDateTime()));
|
||||
assertThat(a2_1.getA2_setEnm(), is(a2_2.getA2_setEnm()));
|
||||
assertThat(a2_1.getA2_setB1(), is(a2_2.getA2_setB1()));
|
||||
assertThat(a2_1.getA2_setB2(), is(a2_2.getA2_setB2()));
|
||||
assertThat(a2_1.getA2_mapBooleanBoolean(), is(a2_2.getA2_mapBooleanBoolean()));
|
||||
assertThat(a2_1.getA2_mapCharChar(), is(a2_2.getA2_mapCharChar()));
|
||||
assertThat(a2_1.getA2_mapByteByte(), is(a2_2.getA2_mapByteByte()));
|
||||
assertThat(a2_1.getA2_mapShortShort(), is(a2_2.getA2_mapShortShort()));
|
||||
assertThat(a2_1.getA2_mapIntegerInteger(), is(a2_2.getA2_mapIntegerInteger()));
|
||||
assertThat(a2_1.getA2_mapLongLong(), is(a2_2.getA2_mapLongLong()));
|
||||
assertThat(a2_1.getA2_mapFloatFloat(), is(a2_2.getA2_mapFloatFloat()));
|
||||
assertThat(a2_1.getA2_mapDoubleDouble(), is(a2_2.getA2_mapDoubleDouble()));
|
||||
assertThat(a2_1.getA2_mapStringString(), is(a2_2.getA2_mapStringString()));
|
||||
assertThat(a2_1.getA2_mapBigIntegerBigInteger(), is(a2_2.getA2_mapBigIntegerBigInteger()));
|
||||
assertThat(a2_1.getA2_mapBigDecimalBigDecimal(), is(a2_2.getA2_mapBigDecimalBigDecimal()));
|
||||
assertThat(a2_1.getA2_mapLocalDateLocalDate(), is(a2_2.getA2_mapLocalDateLocalDate()));
|
||||
assertThat(a2_1.getA2_mapLocalTimeLocalTime(), is(a2_2.getA2_mapLocalTimeLocalTime()));
|
||||
assertThat(a2_1.getA2_mapLocalDateTimeLocalDateTime(), is(a2_2.getA2_mapLocalDateTimeLocalDateTime()));
|
||||
assertThat(a2_1.getA2_mapEnmEnm(), is(a2_2.getA2_mapEnmEnm()));
|
||||
assertThat(a2_1.getA2_mapIntegerB1(), is(a2_2.getA2_mapIntegerB1()));
|
||||
assertThat(a2_1.getA2_mapEnmB2(), is(a2_2.getA2_mapEnmB2()));
|
||||
assertThat(a2_1.getA2_listEmpty(), is(a2_2.getA2_listEmpty()));
|
||||
assertThat(a2_1.getA2_arrayEmpty(), is(a2_2.getA2_arrayEmpty()));
|
||||
assertThat(a2_1.getA2_setEmpty(), is(a2_2.getA2_setEmpty()));
|
||||
assertThat(a2_1.getA2_mapEmpty(), is(a2_2.getA2_mapEmpty()));
|
||||
assertThat(a2_1.getA2_listListByte(), is(a2_2.getA2_listListByte()));
|
||||
assertDeepEquals(a2_1.getA2_listArrayFloat(), a2_2.getA2_listArrayFloat(), ArrayList::new);
|
||||
assertThat(a2_1.getA2_listSetString(), is(a2_2.getA2_listSetString()));
|
||||
assertThat(a2_1.getA2_listMapEnmLocalDate(), is(a2_2.getA2_listMapEnmLocalDate()));
|
||||
assertThat(a2_1.getA2_setSetShort(), is(a2_2.getA2_setSetShort()));
|
||||
assertDeepEquals(a2_1.getA2_setArrayDouble(), a2_2.getA2_setArrayDouble(), HashSet::new);
|
||||
assertThat(a2_1.getA2_setListString(), is(a2_2.getA2_setListString()));
|
||||
assertThat(a2_1.getA2_setMapEnmLocalTime(), is(a2_2.getA2_setMapEnmLocalTime()));
|
||||
assertThat(a2_1.getA2_mapIntegerMapLongBoolean(), is(a2_2.getA2_mapIntegerMapLongBoolean()));
|
||||
assertThat(a2_1.getA2_mapStringListB1(), is(a2_2.getA2_mapStringListB1()));
|
||||
assertEquals(a2_2.getA2_mapBigIntegerArrayBigDecimal().keySet(), a2_1.getA2_mapBigIntegerArrayBigDecimal().keySet());
|
||||
assertDeepEquals(a2_2.getA2_mapBigIntegerArrayBigDecimal().values(), a2_2.getA2_mapBigIntegerArrayBigDecimal().values(), HashSet::new);
|
||||
assertThat(a2_1.getA2_mapEnmSetB2(), is(a2_2.getA2_mapEnmSetB2()));
|
||||
assertThat(a2_1.getA2_mapIntegerListMapShortSetB2(), is(a2_2.getA2_mapIntegerListMapShortSetB2()));
|
||||
assertThat(a2_1.getA2_arrayArrayPrimBoolean(), is(a2_2.getA2_arrayArrayPrimBoolean()));
|
||||
assertThat(a2_1.getA2_arrayArrayPrimChar(), is(a2_2.getA2_arrayArrayPrimChar()));
|
||||
assertThat(a2_1.getA2_arrayArrayPrimByte(), is(a2_2.getA2_arrayArrayPrimByte()));
|
||||
assertThat(a2_1.getA2_arrayArrayPrimShort(), is(a2_2.getA2_arrayArrayPrimShort()));
|
||||
assertThat(a2_1.getA2_arrayArrayPrimInteger(), is(a2_2.getA2_arrayArrayPrimInteger()));
|
||||
assertThat(a2_1.getA2_arrayArrayPrimLong(), is(a2_2.getA2_arrayArrayPrimLong()));
|
||||
assertThat(a2_1.getA2_arrayArrayPrimFloat(), is(a2_2.getA2_arrayArrayPrimFloat()));
|
||||
assertThat(a2_1.getA2_arrayArrayPrimDouble(), is(a2_2.getA2_arrayArrayPrimDouble()));
|
||||
assertThat(a2_1.getA2_arrayArrayBoolean(), is(a2_2.getA2_arrayArrayBoolean()));
|
||||
assertThat(a2_1.getA2_arrayArrayChar(), is(a2_2.getA2_arrayArrayChar()));
|
||||
assertThat(a2_1.getA2_arrayArrayByte(), is(a2_2.getA2_arrayArrayByte()));
|
||||
assertThat(a2_1.getA2_arrayArrayShort(), is(a2_2.getA2_arrayArrayShort()));
|
||||
assertThat(a2_1.getA2_arrayArrayInteger(), is(a2_2.getA2_arrayArrayInteger()));
|
||||
assertThat(a2_1.getA2_arrayArrayLong(), is(a2_2.getA2_arrayArrayLong()));
|
||||
assertThat(a2_1.getA2_arrayArrayFloat(), is(a2_2.getA2_arrayArrayFloat()));
|
||||
assertThat(a2_1.getA2_arrayArrayDouble(), is(a2_2.getA2_arrayArrayDouble()));
|
||||
assertThat(a2_1.getA2_arrayArrayString(), is(a2_2.getA2_arrayArrayString()));
|
||||
assertThat(a2_1.getA2_arrayArrayBigInteger(), is(a2_2.getA2_arrayArrayBigInteger()));
|
||||
assertThat(a2_1.getA2_arrayArrayBigDecimal(), is(a2_2.getA2_arrayArrayBigDecimal()));
|
||||
assertThat(a2_1.getA2_arrayArrayLocalDate(), is(a2_2.getA2_arrayArrayLocalDate()));
|
||||
assertThat(a2_1.getA2_arrayArrayLocalTime(), is(a2_2.getA2_arrayArrayLocalTime()));
|
||||
assertThat(a2_1.getA2_arrayArrayLocalDateTime(), is(a2_2.getA2_arrayArrayLocalDateTime()));
|
||||
assertThat(a2_1.getA2_arrayArrayEnm(), is(a2_2.getA2_arrayArrayEnm()));
|
||||
assertThat(a2_1.getA2_arrayArrayB1(), is(a2_2.getA2_arrayArrayB1()));
|
||||
assertThat(a2_1.getA2_arrayArrayB2(), is(a2_2.getA2_arrayArrayB2()));
|
||||
assertThat(a2_1.getA2_point(), is(a2_2.getA2_point()));
|
||||
assertThat(a2_1.getA2_listPoint(), is(a2_2.getA2_listPoint()));
|
||||
assertThat(a2_1.getA2_arrayPoint(), is(a2_2.getA2_arrayPoint()));
|
||||
assertThat(a2_1.getA2_setPoint(), is(a2_2.getA2_setPoint()));
|
||||
assertThat(a2_1.getA2_mapEnmListPoint(), is(a2_2.getA2_mapEnmListPoint()));
|
||||
}
|
||||
|
||||
|
||||
private static void assertExampleConfigurationsB1Equal(
|
||||
ExampleConfigurationB1 b1_1,
|
||||
ExampleConfigurationB1 b1_2
|
||||
) {
|
||||
assertThat(ExampleConfigurationB1.getB1_staticFinalInt(), is(1));
|
||||
assertThat(ExampleConfigurationB1.getB1_staticInt(), is(2));
|
||||
|
||||
assertThat(b1_1.getB1_finalInt(), is(b1_2.getB1_finalInt()));
|
||||
assertThat(b1_1.getB1_transientInt(), is(b1_2.getB1_transientInt()));
|
||||
assertThat(b1_1.getB1_ignoredInt(), is(b1_2.getB1_ignoredInt()));
|
||||
assertThat(b1_1.getB1_ignoredString(), is(b1_2.getB1_ignoredString()));
|
||||
assertThat(b1_1.getB1_ignoredListString(), is(b1_2.getB1_ignoredListString()));
|
||||
assertThat(b1_1.isB1_primBool(), is(b1_2.isB1_primBool()));
|
||||
assertThat(b1_1.getB1_refChar(), is(b1_2.getB1_refChar()));
|
||||
assertThat(b1_1.getB1_string(), is(b1_2.getB1_string()));
|
||||
assertThat(b1_1.getB1_listByte(), is(b1_2.getB1_listByte()));
|
||||
assertThat(b1_1.getB1_arrayShort(), is(b1_2.getB1_arrayShort()));
|
||||
assertThat(b1_1.getB1_setInteger(), is(b1_2.getB1_setInteger()));
|
||||
assertThat(b1_1.getB1_listEmpty(), is(b1_2.getB1_listEmpty()));
|
||||
assertThat(b1_1.getB1_mapLongLong(), is(b1_2.getB1_mapLongLong()));
|
||||
assertThat(b1_1.getB1_listListByte(), is(b1_2.getB1_listListByte()));
|
||||
assertThat(b1_1.getB1_point(), is(b1_2.getB1_point()));
|
||||
assertEquals(b1_2, b1_1);
|
||||
}
|
||||
|
||||
private static void assertExampleConfigurationsB2Equal(
|
||||
ExampleConfigurationB2 b2_1,
|
||||
ExampleConfigurationB2 b2_2
|
||||
) {
|
||||
assertExampleConfigurationsB1Equal(b2_1, b2_2);
|
||||
|
||||
assertThat(ExampleConfigurationB2.getB1_staticFinalInt(), is(1));
|
||||
assertThat(ExampleConfigurationB2.getB1_staticInt(), is(2));
|
||||
|
||||
assertThat(b2_1.getB2_finalInt(), is(b2_2.getB2_finalInt()));
|
||||
assertThat(b2_1.getB2_transientInt(), is(b2_2.getB2_transientInt()));
|
||||
assertThat(b2_1.getB2_ignoredInt(), is(b2_2.getB2_ignoredInt()));
|
||||
assertThat(b2_1.getB2_ignoredString(), is(b2_2.getB2_ignoredString()));
|
||||
assertThat(b2_1.getB2_ignoredListString(), is(b2_2.getB2_ignoredListString()));
|
||||
assertThat(b2_1.getB2_primChar(), is(b2_2.getB2_primChar()));
|
||||
assertThat(b2_1.getB2_refBool(), is(b2_2.getB2_refBool()));
|
||||
assertThat(b2_1.getB2_bigInteger(), is(b2_2.getB2_bigInteger()));
|
||||
assertThat(b2_1.getB2_listShort(), is(b2_2.getB2_listShort()));
|
||||
assertThat(b2_1.getB2_arrayInteger(), is(b2_2.getB2_arrayInteger()));
|
||||
assertThat(b2_1.getB2_setLong(), is(b2_2.getB2_setLong()));
|
||||
assertThat(b2_1.getB2_arrayEmpty(), is(b2_2.getB2_arrayEmpty()));
|
||||
assertThat(b2_1.getB2_mapFloatFloat(), is(b2_2.getB2_mapFloatFloat()));
|
||||
assertDeepEquals(b2_1.getB2_setArrayDouble(), b2_2.getB2_setArrayDouble(), LinkedHashSet::new);
|
||||
assertThat(b2_1.getB2_listPoint(), is(b2_2.getB2_listPoint()));
|
||||
assertEquals(b2_2, b2_1);
|
||||
}
|
||||
|
||||
private static <T, C extends Collection<T[]>> void assertDeepEquals(
|
||||
C collection1,
|
||||
C collection2,
|
||||
Supplier<Collection<List<T>>> collectionFactory
|
||||
) {
|
||||
Collection<List<T>> c1 = collection1.stream().map(Arrays::asList)
|
||||
.collect(Collectors.toCollection(collectionFactory));
|
||||
Collection<List<T>> c2 = collection2.stream().map(Arrays::asList)
|
||||
.collect(Collectors.toCollection(collectionFactory));
|
||||
assertEquals(c2, c1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void yamlStoreSavesAndLoadsExampleConfigurationA2() {
|
||||
var properties = YamlConfigurationProperties.newBuilder()
|
||||
.addSerializer(Point.class, TestUtils.POINT_SERIALIZER)
|
||||
.build();
|
||||
var store = new YamlConfigurationStore<>(ExampleConfigurationA2.class, properties);
|
||||
ExampleConfigurationA2 cfg1 = ExampleInitializer.newExampleConfigurationA2();
|
||||
store.save(cfg1, yamlFile);
|
||||
ExampleConfigurationA2 cfg2 = store.load(yamlFile);
|
||||
assertExampleConfigurationsA2Equal(cfg1, cfg2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void yamlStoreSavesAndLoadsExampleConfigurationNullsWithNullCollectionElements1() {
|
||||
var properties = YamlConfigurationProperties.newBuilder()
|
||||
.addSerializer(Point.class, TestUtils.POINT_SERIALIZER)
|
||||
.outputNulls(true)
|
||||
.inputNulls(true)
|
||||
.build();
|
||||
var store = new YamlConfigurationStore<>(ExampleConfigurationNulls.class, properties);
|
||||
ExampleConfigurationNulls cfg1 = ExampleInitializer
|
||||
.newExampleConfigurationNullsWithNullCollectionElements1();
|
||||
store.save(cfg1, yamlFile);
|
||||
ExampleConfigurationNulls cfg2 = store.load(yamlFile);
|
||||
assertExampleConfigurationsNullsEqual(cfg1, cfg2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void yamlStoreSavesAndLoadsExampleConfigurationNullsWithoutNullCollectionElements1() {
|
||||
var properties = YamlConfigurationProperties.newBuilder()
|
||||
.addSerializer(Point.class, TestUtils.POINT_SERIALIZER)
|
||||
.build();
|
||||
var store = new YamlConfigurationStore<>(ExampleConfigurationNulls.class, properties);
|
||||
ExampleConfigurationNulls cfg1 = ExampleInitializer
|
||||
.newExampleConfigurationNullsWithoutNullCollectionElements1();
|
||||
store.save(cfg1, yamlFile);
|
||||
ExampleConfigurationNulls cfg2 = store.load(yamlFile);
|
||||
assertExampleConfigurationsNullsEqual(cfg1, cfg2);
|
||||
}
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class FieldExtractorTest {
|
||||
@Test
|
||||
void applyCallsExtract() {
|
||||
FieldExtractor extractor = mock(FieldExtractor.class, CALLS_REAL_METHODS);
|
||||
|
||||
extractor.apply(Object.class);
|
||||
|
||||
verify(extractor).extract(Object.class);
|
||||
}
|
||||
}
|
@ -0,0 +1,102 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.List;
|
||||
|
||||
import static de.exlll.configlib.TestUtils.*;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
class FieldExtractorsTest {
|
||||
@Test
|
||||
void extractConfigurationRequiresNonNull() {
|
||||
assertThrowsNullPointerException(
|
||||
() -> FieldExtractors.CONFIGURATION.extract(null),
|
||||
"configuration class"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractConfigurationRequiresConfiguration() {
|
||||
assertThrowsConfigurationException(
|
||||
() -> FieldExtractors.CONFIGURATION.extract(getClass()),
|
||||
"Class 'FieldExtractorsTest' must be a configuration."
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractConfigurationDirectly() {
|
||||
@Configuration
|
||||
class A {
|
||||
int i;
|
||||
}
|
||||
|
||||
FieldExtractor extractor = FieldExtractors.CONFIGURATION;
|
||||
|
||||
List<Field> actual = extractor.extract(A.class).toList();
|
||||
List<Field> expected = List.of(
|
||||
getField(A.class, "i")
|
||||
);
|
||||
assertThat(actual, is(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractConfigurationIndirectly() {
|
||||
@Configuration
|
||||
class A {
|
||||
int i;
|
||||
}
|
||||
class B extends A {
|
||||
int j;
|
||||
}
|
||||
class C extends B {
|
||||
int k;
|
||||
}
|
||||
|
||||
FieldExtractor extractor = FieldExtractors.CONFIGURATION;
|
||||
|
||||
List<Field> actual = extractor.extract(C.class).toList();
|
||||
List<Field> expected = List.of(
|
||||
getField(A.class, "i"),
|
||||
getField(B.class, "j"),
|
||||
getField(C.class, "k")
|
||||
);
|
||||
assertThat(actual, is(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractConfigurationRequiresNoShadowing() {
|
||||
@Configuration
|
||||
class A {
|
||||
int i;
|
||||
}
|
||||
class B extends A {
|
||||
int i;
|
||||
}
|
||||
assertThrowsConfigurationException(
|
||||
() -> FieldExtractors.CONFIGURATION.extract(B.class),
|
||||
"Shadowing of fields is not supported. Field 'i' of class B " +
|
||||
"shadows field 'i' of class A."
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void extractConfigurationAppliesDefaultFieldFilter() {
|
||||
class A {
|
||||
@Configuration
|
||||
class B {
|
||||
private final int i = 1;
|
||||
@Ignore
|
||||
private int j;
|
||||
private static int k;
|
||||
private transient int l;
|
||||
private int m;
|
||||
}
|
||||
}
|
||||
List<Field> actual = FieldExtractors.CONFIGURATION.extract(A.B.class).toList();
|
||||
List<Field> expected = List.of(getField(A.B.class, "m"));
|
||||
assertThat(actual, is(expected));
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static de.exlll.configlib.TestUtils.assertThrowsNullPointerException;
|
||||
import static de.exlll.configlib.TestUtils.getField;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
class FieldFiltersTest {
|
||||
@Test
|
||||
void filterDefaultRequiresNonNullStream() {
|
||||
assertThrowsNullPointerException(
|
||||
() -> FieldFilters.DEFAULT.test(null),
|
||||
"field"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void filterDefault() {
|
||||
class A {
|
||||
class B {
|
||||
private final int i = 1;
|
||||
@Ignore
|
||||
private int j;
|
||||
private static int k;
|
||||
private transient int l;
|
||||
private int m;
|
||||
}
|
||||
}
|
||||
List<Field> actual = Arrays.stream(A.B.class.getDeclaredFields())
|
||||
.filter(FieldFilters.DEFAULT)
|
||||
.toList();
|
||||
List<Field> expected = List.of(getField(A.B.class, "m"));
|
||||
assertThat(actual, is(expected));
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
class FieldFormatterTest {
|
||||
@Test
|
||||
void applyCallsFormat() {
|
||||
class A {
|
||||
int i;
|
||||
}
|
||||
FieldFormatter formatter = mock(FieldFormatter.class, CALLS_REAL_METHODS);
|
||||
|
||||
Field field = TestUtils.getField(A.class, "i");
|
||||
formatter.apply(field);
|
||||
|
||||
verify(formatter).format(field);
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import static de.exlll.configlib.TestUtils.getField;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
|
||||
class FieldFormattersTest {
|
||||
private static class A {
|
||||
String lowercase;
|
||||
String camelCase;
|
||||
String withNumber123;
|
||||
String with123Number;
|
||||
String with_$;
|
||||
}
|
||||
|
||||
private static final Field f1 = getField(A.class, "lowercase");
|
||||
private static final Field f2 = getField(A.class, "camelCase");
|
||||
private static final Field f3 = getField(A.class, "withNumber123");
|
||||
private static final Field f4 = getField(A.class, "with123Number");
|
||||
private static final Field f5 = getField(A.class, "with_$");
|
||||
|
||||
@Test
|
||||
void formatIdentity() {
|
||||
FieldFormatters formatter = FieldFormatters.IDENTITY;
|
||||
|
||||
assertThat(formatter.format(f1), is("lowercase"));
|
||||
assertThat(formatter.format(f2), is("camelCase"));
|
||||
assertThat(formatter.format(f3), is("withNumber123"));
|
||||
assertThat(formatter.format(f4), is("with123Number"));
|
||||
assertThat(formatter.format(f5), is("with_$"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void formatLowerUnderscore() {
|
||||
FieldFormatters formatter = FieldFormatters.LOWER_UNDERSCORE;
|
||||
|
||||
assertThat(formatter.format(f1), is("lowercase"));
|
||||
assertThat(formatter.format(f2), is("camel_case"));
|
||||
assertThat(formatter.format(f3), is("with_number123"));
|
||||
assertThat(formatter.format(f4), is("with123_number"));
|
||||
assertThat(formatter.format(f5), is("with_$"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void formatUpperUnderscore() {
|
||||
FieldFormatters formatter = FieldFormatters.UPPER_UNDERSCORE;
|
||||
|
||||
assertThat(formatter.format(f1), is("LOWERCASE"));
|
||||
assertThat(formatter.format(f2), is("CAMEL_CASE"));
|
||||
assertThat(formatter.format(f3), is("WITH_NUMBER123"));
|
||||
assertThat(formatter.format(f4), is("WITH123_NUMBER"));
|
||||
assertThat(formatter.format(f5), is("WITH_$"));
|
||||
}
|
||||
}
|
@ -0,0 +1,27 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class FileConfigurationPropertiesTest {
|
||||
@Test
|
||||
void builderDefaultValues() {
|
||||
FileConfigurationProperties properties = FileConfigurationProperties.newBuilder().build();
|
||||
assertNull(properties.getHeader());
|
||||
assertNull(properties.getFooter());
|
||||
assertTrue(properties.createParentDirectories());
|
||||
}
|
||||
|
||||
@Test
|
||||
void builderCopiesValues() {
|
||||
FileConfigurationProperties properties = FileConfigurationProperties.newBuilder()
|
||||
.header("THE HEADER")
|
||||
.footer("THE FOOTER")
|
||||
.createParentDirectories(false)
|
||||
.build();
|
||||
assertEquals("THE HEADER", properties.getHeader());
|
||||
assertEquals("THE FOOTER", properties.getFooter());
|
||||
assertFalse(properties.createParentDirectories());
|
||||
}
|
||||
}
|
@ -0,0 +1,217 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
||||
import static de.exlll.configlib.TestUtils.assertThrowsRuntimeException;
|
||||
import static de.exlll.configlib.TestUtils.getField;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
|
||||
class ReflectTest {
|
||||
|
||||
static class B1 {
|
||||
B1(int i) {}
|
||||
}
|
||||
|
||||
@Test
|
||||
void newInstanceRequiresNoArgsCtor() {
|
||||
assertThrowsRuntimeException(
|
||||
() -> Reflect.newInstance(B1.class),
|
||||
"Class B1 doesn't have a no-args constructor."
|
||||
);
|
||||
}
|
||||
|
||||
static abstract class B2 {}
|
||||
|
||||
@Test
|
||||
void newInstanceRequiresConcreteClass() {
|
||||
assertThrowsRuntimeException(
|
||||
() -> Reflect.newInstance(B2.class),
|
||||
"Class B2 is not instantiable."
|
||||
);
|
||||
}
|
||||
|
||||
static class B3 {
|
||||
B3() {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void newInstanceRequiresNonThrowingCtor() {
|
||||
assertThrowsRuntimeException(
|
||||
() -> Reflect.newInstance(B3.class),
|
||||
"Constructor of class B3 threw an exception."
|
||||
);
|
||||
}
|
||||
|
||||
static class B4 {
|
||||
int i = 10;
|
||||
}
|
||||
|
||||
@Test
|
||||
void newInstance() {
|
||||
B4 inst = Reflect.newInstance(B4.class);
|
||||
assertThat(inst, notNullValue());
|
||||
assertThat(inst.i, is(10));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(ints = {0, 1, 2})
|
||||
void newArray(int arrayLength) {
|
||||
String[] strings = Reflect.newArray(String.class, arrayLength);
|
||||
assertThat(strings.length, is(arrayLength));
|
||||
|
||||
int[][] ints = Reflect.newArray(int[].class, arrayLength);
|
||||
assertThat(ints.length, is(arrayLength));
|
||||
}
|
||||
|
||||
@Test
|
||||
void getValue() {
|
||||
class A {
|
||||
private int i = 10;
|
||||
}
|
||||
int value = (int) Reflect.getValue(getField(A.class, "i"), new A());
|
||||
assertThat(value, is(10));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setValue() {
|
||||
class A {
|
||||
private int i = 10;
|
||||
}
|
||||
A a = new A();
|
||||
Reflect.setValue(getField(A.class, "i"), a, 20);
|
||||
assertThat(a.i, is(20));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setValueDoesNotSetFinalField() {
|
||||
class A {
|
||||
private final int i = 10;
|
||||
}
|
||||
A a = new A();
|
||||
Reflect.setValue(getField(A.class, "i"), a, 20);
|
||||
assertThat(a.i, is(10));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isIntegerType() {
|
||||
assertThat(Reflect.isIntegerType(byte.class), is(true));
|
||||
assertThat(Reflect.isIntegerType(Byte.class), is(true));
|
||||
assertThat(Reflect.isIntegerType(short.class), is(true));
|
||||
assertThat(Reflect.isIntegerType(Short.class), is(true));
|
||||
assertThat(Reflect.isIntegerType(int.class), is(true));
|
||||
assertThat(Reflect.isIntegerType(Integer.class), is(true));
|
||||
assertThat(Reflect.isIntegerType(long.class), is(true));
|
||||
assertThat(Reflect.isIntegerType(Long.class), is(true));
|
||||
assertThat(Reflect.isIntegerType(float.class), is(false));
|
||||
assertThat(Reflect.isIntegerType(Float.class), is(false));
|
||||
assertThat(Reflect.isIntegerType(double.class), is(false));
|
||||
assertThat(Reflect.isIntegerType(Double.class), is(false));
|
||||
assertThat(Reflect.isIntegerType(String.class), is(false));
|
||||
assertThat(Reflect.isIntegerType(Object.class), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isFloatingPointType() {
|
||||
assertThat(Reflect.isFloatingPointType(byte.class), is(false));
|
||||
assertThat(Reflect.isFloatingPointType(Byte.class), is(false));
|
||||
assertThat(Reflect.isFloatingPointType(short.class), is(false));
|
||||
assertThat(Reflect.isFloatingPointType(Short.class), is(false));
|
||||
assertThat(Reflect.isFloatingPointType(int.class), is(false));
|
||||
assertThat(Reflect.isFloatingPointType(Integer.class), is(false));
|
||||
assertThat(Reflect.isFloatingPointType(long.class), is(false));
|
||||
assertThat(Reflect.isFloatingPointType(Long.class), is(false));
|
||||
assertThat(Reflect.isFloatingPointType(float.class), is(true));
|
||||
assertThat(Reflect.isFloatingPointType(Float.class), is(true));
|
||||
assertThat(Reflect.isFloatingPointType(double.class), is(true));
|
||||
assertThat(Reflect.isFloatingPointType(Double.class), is(true));
|
||||
assertThat(Reflect.isFloatingPointType(String.class), is(false));
|
||||
assertThat(Reflect.isFloatingPointType(Object.class), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isEnumType() {
|
||||
enum A {}
|
||||
class B {}
|
||||
assertThat(Reflect.isEnumType(A.class), is(true));
|
||||
assertThat(Reflect.isEnumType(B.class), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isArrayType() {
|
||||
enum A {}
|
||||
class B {}
|
||||
assertThat(Reflect.isArrayType(A.class), is(false));
|
||||
assertThat(Reflect.isArrayType(B.class), is(false));
|
||||
assertThat(Reflect.isArrayType(int.class), is(false));
|
||||
assertThat(Reflect.isArrayType(A[].class), is(true));
|
||||
assertThat(Reflect.isArrayType(B[].class), is(true));
|
||||
assertThat(Reflect.isArrayType(int[].class), is(true));
|
||||
assertThat(Reflect.isArrayType(A[][].class), is(true));
|
||||
assertThat(Reflect.isArrayType(B[][].class), is(true));
|
||||
assertThat(Reflect.isArrayType(int[][].class), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isListType() {
|
||||
assertThat(Reflect.isListType(Object.class), is(false));
|
||||
assertThat(Reflect.isListType(HashMap.class), is(false));
|
||||
assertThat(Reflect.isListType(HashSet.class), is(false));
|
||||
assertThat(Reflect.isListType(ArrayList.class), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isSetType() {
|
||||
assertThat(Reflect.isSetType(Object.class), is(false));
|
||||
assertThat(Reflect.isSetType(HashMap.class), is(false));
|
||||
assertThat(Reflect.isSetType(HashSet.class), is(true));
|
||||
assertThat(Reflect.isSetType(ArrayList.class), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isMapType() {
|
||||
assertThat(Reflect.isMapType(Object.class), is(false));
|
||||
assertThat(Reflect.isMapType(HashMap.class), is(true));
|
||||
assertThat(Reflect.isMapType(HashSet.class), is(false));
|
||||
assertThat(Reflect.isMapType(ArrayList.class), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isConfiguration() {
|
||||
class A {}
|
||||
class B extends A {}
|
||||
@Configuration
|
||||
class C {}
|
||||
class D extends C {}
|
||||
|
||||
assertThat(Reflect.isConfiguration(A.class), is(false));
|
||||
assertThat(Reflect.isConfiguration(B.class), is(false));
|
||||
assertThat(Reflect.isConfiguration(C.class), is(true));
|
||||
assertThat(Reflect.isConfiguration(D.class), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isIgnored() {
|
||||
class A {
|
||||
@Ignore
|
||||
private int a;
|
||||
private int b;
|
||||
}
|
||||
|
||||
Field fieldA = getField(A.class, "a");
|
||||
Field fieldB = getField(A.class, "b");
|
||||
|
||||
assertThat(Reflect.isIgnored(fieldA), is(true));
|
||||
assertThat(Reflect.isIgnored(fieldB), is(false));
|
||||
}
|
||||
}
|
@ -0,0 +1,362 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import de.exlll.configlib.Serializers.*;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static de.exlll.configlib.TestUtils.*;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
class SerializerSelectorTest {
|
||||
private static final SerializerSelector SELECTOR = new SerializerSelector(
|
||||
ConfigurationProperties.newBuilder().build()
|
||||
);
|
||||
|
||||
private static Type getGenericType(Class<?> cls, String fieldName) {
|
||||
Field ls = getField(cls, fieldName);
|
||||
return ls.getGenericType();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(classes = {boolean.class, Boolean.class})
|
||||
void selectSerializerBoolean(Class<?> cls) {
|
||||
Serializer<?, ?> serializer = SELECTOR.select(cls);
|
||||
assertThat(serializer, instanceOf(BooleanSerializer.class));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(classes = {
|
||||
byte.class, Byte.class, short.class, Short.class,
|
||||
int.class, Integer.class, long.class, Long.class,
|
||||
float.class, Float.class, double.class, Double.class
|
||||
})
|
||||
void selectSerializerNumber(Class<?> cls) {
|
||||
NumberSerializer serializer = (NumberSerializer) SELECTOR.select(cls);
|
||||
assertThat(serializer.getNumberClass(), equalTo(cls));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(classes = {char.class, Character.class})
|
||||
void selectSerializerChar(Class<?> cls) {
|
||||
Serializer<?, ?> serializer = SELECTOR.select(cls);
|
||||
assertThat(serializer, instanceOf(CharacterSerializer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerString() {
|
||||
Serializer<?, ?> serializer = SELECTOR.select(String.class);
|
||||
assertThat(serializer, instanceOf(StringSerializer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerBigInteger() {
|
||||
Serializer<?, ?> serializer = SELECTOR.select(BigInteger.class);
|
||||
assertThat(serializer, instanceOf(BigIntegerSerializer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerBigDecimal() {
|
||||
Serializer<?, ?> serializer = SELECTOR.select(BigDecimal.class);
|
||||
assertThat(serializer, instanceOf(BigDecimalSerializer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerLocalDate() {
|
||||
Serializer<?, ?> serializer = SELECTOR.select(LocalDate.class);
|
||||
assertThat(serializer, instanceOf(LocalDateSerializer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerLocalTime() {
|
||||
Serializer<?, ?> serializer = SELECTOR.select(LocalTime.class);
|
||||
assertThat(serializer, instanceOf(LocalTimeSerializer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerLocalDateTime() {
|
||||
Serializer<?, ?> serializer = SELECTOR.select(LocalDateTime.class);
|
||||
assertThat(serializer, instanceOf(LocalDateTimeSerializer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerEnum() {
|
||||
enum E {}
|
||||
EnumSerializer serializer = (EnumSerializer) SELECTOR.select(E.class);
|
||||
assertThat(serializer.getEnumCls(), equalTo(E.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerArray() {
|
||||
var serializer = (ArraySerializer<?, ?>) SELECTOR.select(String[][].class);
|
||||
assertThat(serializer.getComponentType(), equalTo(String[].class));
|
||||
|
||||
var elementSerializer = (ArraySerializer<?, ?>) serializer.getElementSerializer();
|
||||
assertThat(elementSerializer.getComponentType(), equalTo(String.class));
|
||||
assertThat(elementSerializer.getElementSerializer(), instanceOf(StringSerializer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerPrimitiveBooleanArray() {
|
||||
Serializer<?, ?> serializer = SELECTOR.select(boolean[].class);
|
||||
assertThat(serializer, instanceOf(PrimitiveBooleanArraySerializer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerPrimitiveCharacterArray() {
|
||||
Serializer<?, ?> serializer = SELECTOR.select(char[].class);
|
||||
assertThat(serializer, instanceOf(PrimitiveCharacterArraySerializer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerPrimitiveByteArray() {
|
||||
Serializer<?, ?> serializer = SELECTOR.select(byte[].class);
|
||||
assertThat(serializer, instanceOf(PrimitiveByteArraySerializer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerPrimitiveShortArray() {
|
||||
Serializer<?, ?> serializer = SELECTOR.select(short[].class);
|
||||
assertThat(serializer, instanceOf(PrimitiveShortArraySerializer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerPrimitiveIntegerArray() {
|
||||
Serializer<?, ?> serializer = SELECTOR.select(int[].class);
|
||||
assertThat(serializer, instanceOf(PrimitiveIntegerArraySerializer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerPrimitiveLongArray() {
|
||||
Serializer<?, ?> serializer = SELECTOR.select(long[].class);
|
||||
assertThat(serializer, instanceOf(PrimitiveLongArraySerializer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerPrimitiveFloatArray() {
|
||||
Serializer<?, ?> serializer = SELECTOR.select(float[].class);
|
||||
assertThat(serializer, instanceOf(PrimitiveFloatArraySerializer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerPrimitiveDoubleArray() {
|
||||
Serializer<?, ?> serializer = SELECTOR.select(double[].class);
|
||||
assertThat(serializer, instanceOf(PrimitiveDoubleArraySerializer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerConfiguration() {
|
||||
@Configuration
|
||||
class A<T> {
|
||||
int i;
|
||||
}
|
||||
var serializer = (ConfigurationSerializer<?>) SELECTOR.select(A.class);
|
||||
assertThat(serializer.getConfigurationType(), equalTo(A.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerMissingType() {
|
||||
assertThrowsConfigurationException(
|
||||
() -> SELECTOR.select(Object.class),
|
||||
"Missing serializer for type class java.lang.Object.\nEither annotate the type with " +
|
||||
"@Configuration or provide a custom serializer by adding it to the properties."
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerCustomType() {
|
||||
var properties = ConfigurationProperties.newBuilder()
|
||||
.addSerializer(Point.class, POINT_SERIALIZER)
|
||||
.build();
|
||||
SerializerSelector selector = new SerializerSelector(properties);
|
||||
var pointSerializer = selector.select(Point.class);
|
||||
assertThat(pointSerializer, sameInstance(POINT_SERIALIZER));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerCustomSerializerTakesPrecedence() {
|
||||
var properties = ConfigurationProperties.newBuilder()
|
||||
.addSerializer(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 selectSerializerList() {
|
||||
class A {
|
||||
List<String> ls;
|
||||
}
|
||||
var serializer = (ListSerializer<?, ?>) SELECTOR.select(getGenericType(A.class, "ls"));
|
||||
assertThat(serializer.getElementSerializer(), instanceOf(StringSerializer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerListNested() {
|
||||
class A {
|
||||
List<List<String>> lls;
|
||||
}
|
||||
var serializer = (ListSerializer<?, ?>) SELECTOR.select(getGenericType(A.class, "lls"));
|
||||
var elementSerializer = (ListSerializer<?, ?>) serializer.getElementSerializer();
|
||||
assertThat(elementSerializer.getElementSerializer(), instanceOf(StringSerializer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerSetsAsSets() {
|
||||
class A {
|
||||
Set<String> ss;
|
||||
}
|
||||
SerializerSelector selector = new SerializerSelector(
|
||||
ConfigurationProperties.newBuilder().serializeSetsAsLists(false).build()
|
||||
);
|
||||
var serializer = (SetSerializer<?, ?>) selector.select(getGenericType(A.class, "ss"));
|
||||
assertThat(serializer.getElementSerializer(), instanceOf(StringSerializer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerSetsAsLists() {
|
||||
class A {
|
||||
Set<String> ss;
|
||||
}
|
||||
var serializer = (SetAsListSerializer<?, ?>) SELECTOR.select(getGenericType(A.class, "ss"));
|
||||
assertThat(serializer.getElementSerializer(), instanceOf(StringSerializer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerMap() {
|
||||
class A {
|
||||
Map<Integer, String> mis;
|
||||
}
|
||||
var serializer = (MapSerializer<?, ?, ?, ?>) SELECTOR.select(getGenericType(A.class, "mis"));
|
||||
var numberSerializer = (NumberSerializer) serializer.getKeySerializer();
|
||||
assertThat(numberSerializer.getNumberClass(), equalTo(Integer.class));
|
||||
assertThat(serializer.getValueSerializer(), instanceOf(StringSerializer.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerMapNested() {
|
||||
enum E {}
|
||||
class A {
|
||||
Map<E, Set<List<E>>> mesle;
|
||||
}
|
||||
var serializer = (MapSerializer<?, ?, ?, ?>) SELECTOR.select(getGenericType(A.class, "mesle"));
|
||||
|
||||
var keySerializer = (EnumSerializer) serializer.getKeySerializer();
|
||||
assertThat(keySerializer.getEnumCls(), equalTo(E.class));
|
||||
|
||||
var valSerializer = (SetAsListSerializer<?, ?>) serializer.getValueSerializer();
|
||||
var listSerializer = (ListSerializer<?, ?>) valSerializer.getElementSerializer();
|
||||
var enumSerializer = (EnumSerializer) listSerializer.getElementSerializer();
|
||||
assertThat(enumSerializer.getEnumCls(), equalTo(E.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerMapInvalidKeyType1() {
|
||||
class A {
|
||||
Map<List<String>, String> mlss;
|
||||
}
|
||||
Type type = getGenericType(A.class, "mlss");
|
||||
assertThrowsConfigurationException(
|
||||
() -> SELECTOR.select(type),
|
||||
"Cannot select serializer for type '" + type + "'.\nMap keys can only be " +
|
||||
"of simple or enum type."
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerMapInvalidKeyType2() {
|
||||
class A {
|
||||
Map<Point, String> mps;
|
||||
}
|
||||
Type type = getGenericType(A.class, "mps");
|
||||
assertThrowsConfigurationException(
|
||||
() -> SELECTOR.select(type),
|
||||
"Cannot select serializer for type '" + type + "'.\nMap keys can only be " +
|
||||
"of simple or enum type."
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerOtherParameterizedType() {
|
||||
class Box<T> {}
|
||||
class A {
|
||||
Box<String> box;
|
||||
}
|
||||
Type type = getGenericType(A.class, "box");
|
||||
assertThrowsConfigurationException(
|
||||
() -> SELECTOR.select(type),
|
||||
"Cannot select serializer for type '" + type + "'.\nParameterized " +
|
||||
"types other than lists, sets, and maps cannot be serialized."
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerGenericArrayType() {
|
||||
class A {
|
||||
List<?>[] ga;
|
||||
}
|
||||
Type type = getGenericType(A.class, "ga");
|
||||
assertThrowsConfigurationException(
|
||||
() -> SELECTOR.select(type),
|
||||
"Cannot select serializer for type 'java.util.List<?>[]'.\n" +
|
||||
"Generic array types cannot be serialized."
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerBoundedWildcardType() {
|
||||
class A {
|
||||
List<? extends String> les;
|
||||
}
|
||||
ParameterizedType ptype = (ParameterizedType) getGenericType(A.class, "les");
|
||||
Type type = ptype.getActualTypeArguments()[0];
|
||||
assertThrowsConfigurationException(
|
||||
() -> SELECTOR.select(type),
|
||||
"Cannot select serializer for type '? extends java.lang.String'.\n" +
|
||||
"Wildcard types cannot be serialized."
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerWildcardType() {
|
||||
class A {
|
||||
List<?> lw;
|
||||
}
|
||||
ParameterizedType ptype = (ParameterizedType) getGenericType(A.class, "lw");
|
||||
Type type = ptype.getActualTypeArguments()[0];
|
||||
assertThrowsConfigurationException(
|
||||
() -> SELECTOR.select(type),
|
||||
"Cannot select serializer for type '?'.\n" +
|
||||
"Wildcard types cannot be serialized."
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectSerializerTypeVariable() {
|
||||
class A<T> {
|
||||
T t;
|
||||
}
|
||||
Type type = getGenericType(A.class, "t");
|
||||
assertThrowsConfigurationException(
|
||||
() -> SELECTOR.select(type),
|
||||
"Cannot select serializer for type 'T'.\n" +
|
||||
"Type variables cannot be serialized."
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,908 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import de.exlll.configlib.Serializers.MapSerializer;
|
||||
import de.exlll.configlib.Serializers.NumberSerializer;
|
||||
import de.exlll.configlib.Serializers.SetAsListSerializer;
|
||||
import de.exlll.configlib.Serializers.SetSerializer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.EnumSource;
|
||||
import org.junit.jupiter.params.provider.ValueSource;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.Month;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static de.exlll.configlib.TestUtils.*;
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
|
||||
class SerializersTest {
|
||||
@Test
|
||||
void booleanSerializer() {
|
||||
Serializer<Boolean, Boolean> serializer = new Serializers.BooleanSerializer();
|
||||
|
||||
Boolean element = true;
|
||||
|
||||
Object serialized = serializer.serialize(element);
|
||||
assertThat(serialized, sameInstance(element));
|
||||
|
||||
Object deserialized = serializer.deserialize(element);
|
||||
assertThat(deserialized, sameInstance(element));
|
||||
}
|
||||
|
||||
@Test
|
||||
void numberSerializerRequiresValidNumberType() {
|
||||
assertThrowsNullPointerException(
|
||||
() -> new NumberSerializer(null),
|
||||
"number class"
|
||||
);
|
||||
assertThrowsIllegalArgumentException(
|
||||
() -> new NumberSerializer(BigInteger.class),
|
||||
"Class BigInteger is not a byte, short, int, long, float, double, or a " +
|
||||
"wrapper type of one of the primitive number types."
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void numberSerializerDeserializeInvalidType() {
|
||||
NumberSerializer serializer = new NumberSerializer(int.class);
|
||||
|
||||
assertThrowsConfigurationException(
|
||||
() -> serializer.deserialize(BigInteger.ONE),
|
||||
"Cannot deserialize element '1' of type BigInteger.\n" +
|
||||
"This serializer only supports primitive number types and their wrapper types."
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(classes = {
|
||||
byte.class, Byte.class,
|
||||
short.class, Short.class,
|
||||
int.class, Integer.class,
|
||||
long.class, Long.class
|
||||
})
|
||||
void numberSerializerSerializeIntegerType(Class<? extends Number> cls) {
|
||||
NumberSerializer serializer = new NumberSerializer(cls);
|
||||
|
||||
int[] numbers = {Integer.MIN_VALUE, 0, Integer.MAX_VALUE};
|
||||
|
||||
for (int number : numbers) {
|
||||
Number serialized = serializer.serialize(number);
|
||||
assertThat(serialized, instanceOf(Long.class));
|
||||
assertThat(serialized, is((long) number));
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(classes = {
|
||||
float.class, Float.class,
|
||||
double.class, Double.class
|
||||
})
|
||||
void numberSerializerSerializeFloatingPointType(Class<? extends Number> cls) {
|
||||
NumberSerializer serializer = new NumberSerializer(cls);
|
||||
|
||||
float[] numbers = {-Float.MAX_VALUE, -Float.MIN_VALUE, 0, Float.MIN_VALUE, Float.MAX_VALUE};
|
||||
|
||||
for (float number : numbers) {
|
||||
Number serialized = serializer.serialize(number);
|
||||
assertThat(serialized, instanceOf(Double.class));
|
||||
assertThat(serialized, is((double) number));
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(classes = {
|
||||
byte.class, Byte.class,
|
||||
short.class, Short.class,
|
||||
int.class, Integer.class,
|
||||
long.class, Long.class
|
||||
})
|
||||
void numberSerializerDeserializeByteToIntegerType(Class<? extends Number> cls) {
|
||||
NumberSerializer serializer = new NumberSerializer(cls);
|
||||
|
||||
byte[] numbers = {Byte.MIN_VALUE, 0, Byte.MAX_VALUE};
|
||||
|
||||
for (byte number : numbers) {
|
||||
Number deserialized = serializer.deserialize(number);
|
||||
assertThat(deserialized, instanceOf(cls));
|
||||
assertThat(deserialized.longValue(), is((long) number));
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(classes = {
|
||||
short.class, Short.class,
|
||||
int.class, Integer.class,
|
||||
long.class, Long.class
|
||||
})
|
||||
void numberSerializerDeserializeShortToIntegerType(Class<? extends Number> cls) {
|
||||
NumberSerializer serializer = new NumberSerializer(cls);
|
||||
|
||||
short[] numbers = {Short.MIN_VALUE, 0, Short.MAX_VALUE};
|
||||
|
||||
for (short number : numbers) {
|
||||
Number deserialized = serializer.deserialize(number);
|
||||
assertThat(deserialized, instanceOf(cls));
|
||||
assertThat(deserialized.longValue(), is((long) number));
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(classes = {int.class, Integer.class, long.class, Long.class})
|
||||
void numberSerializerDeserializeIntegerToIntegerType(Class<? extends Number> cls) {
|
||||
NumberSerializer serializer = new NumberSerializer(cls);
|
||||
|
||||
int[] numbers = {Integer.MIN_VALUE, 0, Integer.MAX_VALUE};
|
||||
|
||||
for (int number : numbers) {
|
||||
Number deserialized = serializer.deserialize(number);
|
||||
assertThat(deserialized, instanceOf(cls));
|
||||
assertThat(deserialized.longValue(), is((long) number));
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(classes = {long.class, Long.class})
|
||||
void numberSerializerDeserializeLongToIntegerType(Class<? extends Number> cls) {
|
||||
NumberSerializer serializer = new NumberSerializer(cls);
|
||||
|
||||
long[] numbers = {Long.MIN_VALUE, 0, Long.MAX_VALUE};
|
||||
|
||||
for (long number : numbers) {
|
||||
Number deserialized = serializer.deserialize(number);
|
||||
assertThat(deserialized, instanceOf(cls));
|
||||
assertThat(deserialized, is(number));
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(classes = {byte.class, Byte.class})
|
||||
void numberSerializerDeserializeTooLargeIntegerTypeToByte(Class<? extends Number> cls) {
|
||||
NumberSerializer serializer = new NumberSerializer(cls);
|
||||
long[] values = {
|
||||
Long.MIN_VALUE, Integer.MIN_VALUE, Short.MIN_VALUE, Byte.MIN_VALUE - 1,
|
||||
Long.MAX_VALUE, Integer.MAX_VALUE, Short.MAX_VALUE, Byte.MAX_VALUE + 1
|
||||
};
|
||||
for (long value : values) {
|
||||
String msg = "Number " + value + " cannot be converted to type " + cls.getSimpleName() +
|
||||
". It does not fit into the range of valid values [-128, 127].";
|
||||
assertThrowsConfigurationException(() -> serializer.deserialize(value), msg);
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(classes = {short.class, Short.class})
|
||||
void numberSerializerDeserializeTooLargeIntegerTypeToShort(Class<? extends Number> cls) {
|
||||
NumberSerializer serializer = new NumberSerializer(cls);
|
||||
long[] values = {
|
||||
Long.MIN_VALUE, Integer.MIN_VALUE, Short.MIN_VALUE - 1,
|
||||
Long.MAX_VALUE, Integer.MAX_VALUE, Short.MAX_VALUE + 1,
|
||||
};
|
||||
for (long value : values) {
|
||||
String msg = "Number " + value + " cannot be converted to type " + cls.getSimpleName() +
|
||||
". It does not fit into the range of valid values [-32768, 32767].";
|
||||
assertThrowsConfigurationException(() -> serializer.deserialize(value), msg);
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(classes = {int.class, Integer.class})
|
||||
void numberSerializerDeserializeTooLargeIntegerTypeToInteger(Class<? extends Number> cls) {
|
||||
NumberSerializer serializer = new NumberSerializer(cls);
|
||||
long[] values = {
|
||||
Long.MIN_VALUE, Integer.MIN_VALUE - 1L,
|
||||
Long.MAX_VALUE, Integer.MAX_VALUE + 1L,
|
||||
};
|
||||
for (long value : values) {
|
||||
String msg = "Number " + value + " cannot be converted to type " + cls.getSimpleName() +
|
||||
". It does not fit into the range of valid values [-2147483648, 2147483647].";
|
||||
assertThrowsConfigurationException(() -> serializer.deserialize(value), msg);
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(classes = {float.class, Float.class, double.class, Double.class})
|
||||
void numberSerializerDeserializeFloatToFloatingPointType(Class<? extends Number> cls) {
|
||||
NumberSerializer serializer = new NumberSerializer(cls);
|
||||
|
||||
float[] numbers = {-Float.MAX_VALUE, -Float.MIN_VALUE, 0, Float.MIN_VALUE, Float.MAX_VALUE};
|
||||
|
||||
for (float number : numbers) {
|
||||
Number deserialized = serializer.deserialize(number);
|
||||
assertThat(deserialized, instanceOf(cls));
|
||||
assertThat(deserialized.doubleValue(), is((double) number));
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(classes = {double.class, Double.class})
|
||||
void numberSerializerDeserializeDoubleToFloatingPointType(Class<? extends Number> cls) {
|
||||
NumberSerializer serializer = new NumberSerializer(cls);
|
||||
|
||||
double[] numbers = {-Double.MAX_VALUE, -Double.MIN_VALUE, 0, Double.MIN_VALUE, Double.MAX_VALUE};
|
||||
|
||||
for (double number : numbers) {
|
||||
Number deserialized = serializer.deserialize(number);
|
||||
assertThat(deserialized, instanceOf(cls));
|
||||
assertThat(deserialized, is(number));
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(classes = {float.class, Float.class})
|
||||
void numberSerializerDeserializeTooLargeFloatingPointTypeToFloat(Class<? extends Number> cls) {
|
||||
NumberSerializer serializer = new NumberSerializer(cls);
|
||||
double ulpFloatMax = Math.ulp((double) Float.MAX_VALUE);
|
||||
double[] values = {
|
||||
+Double.MAX_VALUE,
|
||||
-Double.MAX_VALUE,
|
||||
(double) +Float.MAX_VALUE + ulpFloatMax,
|
||||
(double) -Float.MAX_VALUE - ulpFloatMax
|
||||
};
|
||||
for (double value : values) {
|
||||
String clsName = cls.getSimpleName();
|
||||
String msg = "Number " + value + " cannot be converted to type " + clsName + ". " +
|
||||
"It is larger than the largest possible " + clsName + " value.";
|
||||
assertThrowsConfigurationException(() -> serializer.deserialize(value), msg);
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(classes = {float.class, Float.class})
|
||||
void numberSerializerDeserializeTooSmallFloatingPointTypeToFloat(Class<? extends Number> cls) {
|
||||
NumberSerializer serializer = new NumberSerializer(cls);
|
||||
double ulpFloatMin = Math.ulp((double) Float.MIN_VALUE);
|
||||
double[] values = {
|
||||
+Double.MIN_VALUE,
|
||||
-Double.MIN_VALUE,
|
||||
(double) +Float.MIN_VALUE - ulpFloatMin,
|
||||
(double) -Float.MIN_VALUE + ulpFloatMin,
|
||||
};
|
||||
for (double value : values) {
|
||||
String clsName = cls.getSimpleName();
|
||||
String msg = "Number " + value + " cannot be converted to type " + clsName + ". " +
|
||||
"It is smaller than the smallest possible " + clsName + " value.";
|
||||
assertThrowsConfigurationException(() -> serializer.deserialize(value), msg);
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(classes = {float.class, Float.class})
|
||||
void numberSerializerDeserializeFloatSpecialValues(Class<? extends Number> cls) {
|
||||
NumberSerializer serializer = new NumberSerializer(cls);
|
||||
|
||||
numberSerializerDeserializeFloatingPointSpecialValues(
|
||||
serializer,
|
||||
Float.class,
|
||||
Float.POSITIVE_INFINITY,
|
||||
Float.NEGATIVE_INFINITY,
|
||||
Float.NaN
|
||||
);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@ValueSource(classes = {double.class, Double.class})
|
||||
void numberSerializerDeserializeDoubleSpecialValues(Class<? extends Number> cls) {
|
||||
NumberSerializer serializer = new NumberSerializer(cls);
|
||||
|
||||
numberSerializerDeserializeFloatingPointSpecialValues(
|
||||
serializer,
|
||||
Double.class,
|
||||
Double.POSITIVE_INFINITY,
|
||||
Double.NEGATIVE_INFINITY,
|
||||
Double.NaN
|
||||
);
|
||||
}
|
||||
|
||||
private <T> void numberSerializerDeserializeFloatingPointSpecialValues(
|
||||
NumberSerializer serializer,
|
||||
Class<T> type,
|
||||
T positiveInfinity,
|
||||
T negativeInfinity,
|
||||
T nan
|
||||
) {
|
||||
Number pinfDouble = serializer.deserialize(Double.POSITIVE_INFINITY);
|
||||
Number ninfDouble = serializer.deserialize(Double.NEGATIVE_INFINITY);
|
||||
Number nanDouble = serializer.deserialize(Double.NaN);
|
||||
|
||||
Number pinfFloat = serializer.deserialize(Float.POSITIVE_INFINITY);
|
||||
Number ninfFloat = serializer.deserialize(Float.NEGATIVE_INFINITY);
|
||||
Number nanFloat = serializer.deserialize(Float.NaN);
|
||||
|
||||
assertThat(pinfDouble, instanceOf(type));
|
||||
assertThat(ninfDouble, instanceOf(type));
|
||||
assertThat(nanDouble, instanceOf(type));
|
||||
|
||||
assertThat(pinfFloat, instanceOf(type));
|
||||
assertThat(ninfFloat, instanceOf(type));
|
||||
assertThat(nanFloat, instanceOf(type));
|
||||
|
||||
assertThat(pinfDouble, is(positiveInfinity));
|
||||
assertThat(ninfDouble, is(negativeInfinity));
|
||||
assertThat(nanDouble, is(nan));
|
||||
|
||||
assertThat(pinfFloat, is(positiveInfinity));
|
||||
assertThat(ninfFloat, is(negativeInfinity));
|
||||
assertThat(nanFloat, is(nan));
|
||||
}
|
||||
|
||||
@Test
|
||||
void stringSerializer() {
|
||||
Serializer<String, String> serializer = new Serializers.StringSerializer();
|
||||
|
||||
String random = "RANDOM";
|
||||
|
||||
assertThat(serializer.serialize(random), sameInstance(random));
|
||||
assertThat(serializer.deserialize(random), sameInstance(random));
|
||||
}
|
||||
|
||||
@Test
|
||||
void characterSerializer() {
|
||||
Serializer<Character, String> serializer = new Serializers.CharacterSerializer();
|
||||
|
||||
assertThat(serializer.serialize('c'), is("c"));
|
||||
assertThat(serializer.deserialize("c"), is('c'));
|
||||
}
|
||||
|
||||
@Test
|
||||
void characterSerializerDeserializeInvalidString() {
|
||||
Serializer<Character, String> serializer = new Serializers.CharacterSerializer();
|
||||
assertThrowsConfigurationException(
|
||||
() -> serializer.deserialize(""),
|
||||
"An empty string cannot be converted to a character."
|
||||
);
|
||||
assertThrowsConfigurationException(
|
||||
() -> serializer.deserialize("ab"),
|
||||
"String 'ab' is too long to be converted to a character."
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void bigIntegerSerializer() {
|
||||
Serializer<BigInteger, String> serializer = new Serializers.BigIntegerSerializer();
|
||||
|
||||
final String result = "18446744073709551614";
|
||||
|
||||
BigInteger integer = BigInteger.valueOf(Long.MAX_VALUE).multiply(BigInteger.TWO);
|
||||
assertThat(serializer.serialize(integer), is(result));
|
||||
|
||||
integer = new BigInteger(result);
|
||||
assertThat(serializer.deserialize(result), is(integer));
|
||||
}
|
||||
|
||||
@Test
|
||||
void bigDecimalSerializer() {
|
||||
Serializer<BigDecimal, String> serializer = new Serializers.BigDecimalSerializer();
|
||||
|
||||
final String result = "3.23170060713109998320439596646649E+616";
|
||||
final BigDecimal doubleMax = BigDecimal.valueOf(Double.MAX_VALUE);
|
||||
|
||||
BigDecimal decimal = doubleMax.multiply(doubleMax);
|
||||
|
||||
assertThat(serializer.serialize(decimal), is(result));
|
||||
decimal = new BigDecimal(result);
|
||||
assertThat(serializer.deserialize(result), is(decimal));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void localDateSerializer() {
|
||||
Serializer<LocalDate, String> serializer = new Serializers.LocalDateSerializer();
|
||||
|
||||
LocalDate date = LocalDate.of(2000, Month.FEBRUARY, 29);
|
||||
|
||||
assertThat(serializer.serialize(date), is("2000-02-29"));
|
||||
assertThat(serializer.deserialize("2000-02-29"), is(date));
|
||||
}
|
||||
|
||||
@Test
|
||||
void localTimeSerializer() {
|
||||
Serializer<LocalTime, String> serializer = new Serializers.LocalTimeSerializer();
|
||||
|
||||
LocalTime time = LocalTime.of(10, 11, 12, 13);
|
||||
|
||||
assertThat(serializer.serialize(time), is("10:11:12"));
|
||||
|
||||
time = time.truncatedTo(ChronoUnit.SECONDS);
|
||||
assertThat(serializer.deserialize("10:11:12"), is(time));
|
||||
}
|
||||
|
||||
@Test
|
||||
void localDateTimeSerializer() {
|
||||
Serializer<LocalDateTime, String> serializer = new Serializers.LocalDateTimeSerializer();
|
||||
|
||||
LocalDateTime dateTime = LocalDateTime.of(2000, Month.FEBRUARY, 29, 10, 11, 12, 13);
|
||||
|
||||
assertThat(serializer.serialize(dateTime), is("2000-02-29T10:11:12"));
|
||||
|
||||
dateTime = dateTime.truncatedTo(ChronoUnit.SECONDS);
|
||||
assertThat(serializer.deserialize("2000-02-29T10:11:12"), is(dateTime));
|
||||
}
|
||||
|
||||
enum E {X, Y, Z}
|
||||
|
||||
@ParameterizedTest
|
||||
@EnumSource(E.class)
|
||||
void enumSerializer(Enum<?> e) {
|
||||
Serializers.EnumSerializer serializer = new Serializers.EnumSerializer(E.class);
|
||||
|
||||
assertThat(serializer.serialize(e), is(e.name()));
|
||||
assertThat(serializer.deserialize(e.name()), is(e));
|
||||
}
|
||||
|
||||
@Test
|
||||
void enumSerializerMissingValue() {
|
||||
Serializers.EnumSerializer serializer = new Serializers.EnumSerializer(E.class);
|
||||
|
||||
assertThrowsConfigurationException(
|
||||
() -> serializer.deserialize("A"),
|
||||
"Enum class E does not contain enum 'A'. Valid values are: [X, Y, Z]"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void listSerializer() {
|
||||
var serializer = new Serializers.ListSerializer<>(StringToIntSerializer.INSTANCE, false, false);
|
||||
|
||||
List<Integer> integers = serializer.serialize(List.of("1", "2", "3"));
|
||||
assertThat(integers, is(List.of(1, 2, 3)));
|
||||
|
||||
List<String> strings = serializer.deserialize(List.of(4, 5, 6));
|
||||
assertThat(strings, is(List.of("4", "5", "6")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void listSerializerNoNulls() {
|
||||
var serializer = new Serializers.ListSerializer<>(StringToIntSerializer.INSTANCE, false, false);
|
||||
|
||||
List<Integer> integers = serializer.serialize(asList("1", null, "2", null, "3"));
|
||||
assertThat(integers, is(List.of(1, 2, 3)));
|
||||
|
||||
List<String> strings = serializer.deserialize(asList(4, null, 5, null, 6));
|
||||
assertThat(strings, is(List.of("4", "5", "6")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void listSerializerOutputNulls() {
|
||||
var serializer = new Serializers.ListSerializer<>(StringToIntSerializer.INSTANCE, true, false);
|
||||
|
||||
List<Integer> actual = serializer.serialize(asList("1", null, "3"));
|
||||
assertThat(actual, is(asList(1, null, 3)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void listSerializerInputNulls() {
|
||||
var serializer = new Serializers.ListSerializer<>(StringToIntSerializer.INSTANCE, false, true);
|
||||
|
||||
List<String> actual = serializer.deserialize(asList(4, null, 5));
|
||||
assertThat(actual, is(asList("4", null, "5")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setSerializer() {
|
||||
var serializer = new SetSerializer<>(StringToIntSerializer.INSTANCE, false, false);
|
||||
|
||||
Set<Integer> integers = serializer.serialize(Set.of("1", "2", "3"));
|
||||
assertThat(integers, is(Set.of(1, 2, 3)));
|
||||
|
||||
Set<String> strings = serializer.deserialize(Set.of(4, 5, 6));
|
||||
assertThat(strings, is(Set.of("4", "5", "6")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setSerializerNoNulls() {
|
||||
var serializer = new SetSerializer<>(StringToIntSerializer.INSTANCE, false, false);
|
||||
|
||||
Set<Integer> integers = serializer.serialize(asSet("1", null, "2", null, "3"));
|
||||
assertThat(integers, is(asSet(1, 2, 3)));
|
||||
|
||||
Set<String> strings = serializer.deserialize(asSet(4, null, 5, null, 6));
|
||||
assertThat(strings, is(asSet("4", "5", "6")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setSerializerOutputNulls() {
|
||||
var serializer = new SetSerializer<>(StringToIntSerializer.INSTANCE, true, false);
|
||||
|
||||
Set<Integer> actual = serializer.serialize(asSet("1", null, "3"));
|
||||
assertThat(actual, is(asSet(1, null, 3)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setSerializerInputNulls() {
|
||||
var serializer = new SetSerializer<>(StringToIntSerializer.INSTANCE, false, true);
|
||||
|
||||
Set<String> actual = serializer.deserialize(asSet(4, null, 5));
|
||||
assertThat(actual, is(asSet("4", null, "5")));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void setAsListSerializer() {
|
||||
var serializer = new SetAsListSerializer<>(StringToIntSerializer.INSTANCE, false, false);
|
||||
|
||||
List<Integer> integers = serializer.serialize(asSet("1", "2", "3"));
|
||||
assertThat(integers, is(asList(1, 2, 3)));
|
||||
|
||||
Set<String> strings = serializer.deserialize(List.of(4, 5, 6));
|
||||
assertThat(strings, is(Set.of("4", "5", "6")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setAsListSerializerNoNulls() {
|
||||
var serializer = new SetAsListSerializer<>(StringToIntSerializer.INSTANCE, false, false);
|
||||
|
||||
List<Integer> integers = serializer.serialize(asSet("1", null, "2", null, "3"));
|
||||
assertThat(integers, is(asList(1, 2, 3)));
|
||||
|
||||
Set<String> strings = serializer.deserialize(asList(4, null, 5, null, 6));
|
||||
assertThat(strings, is(asSet("4", "5", "6")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setAsListSerializerOutputNulls() {
|
||||
var serializer = new SetAsListSerializer<>(StringToIntSerializer.INSTANCE, true, false);
|
||||
|
||||
List<Integer> actual = serializer.serialize(asSet("1", null, "3"));
|
||||
assertThat(actual, is(asList(1, null, 3)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void setAsListSerializerInputNulls() {
|
||||
var serializer = new SetAsListSerializer<>(StringToIntSerializer.INSTANCE, false, true);
|
||||
|
||||
Set<String> actual = serializer.deserialize(asList(4, null, 5));
|
||||
assertThat(actual, is(asSet("4", null, "5")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapSerializer() {
|
||||
var serializer = new MapSerializer<>(
|
||||
StringToIntSerializer.INSTANCE,
|
||||
StringToIntSerializer.INSTANCE,
|
||||
false,
|
||||
false
|
||||
);
|
||||
|
||||
Map<Integer, Integer> integers = serializer.serialize(Map.of("1", "2", "3", "4"));
|
||||
assertThat(integers, is(Map.of(1, 2, 3, 4)));
|
||||
|
||||
Map<String, String> strings = serializer.deserialize(Map.of(5, 6, 7, 8));
|
||||
assertThat(strings, is(Map.of("5", "6", "7", "8")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapSerializerNoNulls() {
|
||||
var serializer = new MapSerializer<>(
|
||||
StringToIntSerializer.INSTANCE,
|
||||
StringToIntSerializer.INSTANCE,
|
||||
false,
|
||||
false
|
||||
);
|
||||
Map<Integer, Integer> integers = serializer.serialize(entriesAsMap(
|
||||
entry("1", "2"),
|
||||
entry(null, "4"),
|
||||
entry("5", null),
|
||||
entry(null, null),
|
||||
entry("9", "10")
|
||||
));
|
||||
assertThat(integers, is(Map.of(1, 2, 9, 10)));
|
||||
|
||||
Map<String, String> strings = serializer.deserialize(entriesAsMap(
|
||||
entry(11, 12),
|
||||
entry(null, 14),
|
||||
entry(15, null),
|
||||
entry(null, null),
|
||||
entry(19, 20)
|
||||
));
|
||||
assertThat(strings, is(Map.of("11", "12", "19", "20")));
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapSerializerOutputNulls() {
|
||||
var serializer = new MapSerializer<>(
|
||||
StringToIntSerializer.INSTANCE,
|
||||
StringToIntSerializer.INSTANCE,
|
||||
true,
|
||||
false
|
||||
);
|
||||
Map<Integer, Integer> actual = serializer.serialize(entriesAsMap(
|
||||
entry("1", "2"),
|
||||
entry(null, "4"),
|
||||
entry("5", null),
|
||||
entry(null, null),
|
||||
entry("9", "10")
|
||||
));
|
||||
Map<Integer, Integer> expected = entriesAsMap(
|
||||
entry(1, 2),
|
||||
entry(5, null),
|
||||
entry(null, null),
|
||||
entry(9, 10)
|
||||
);
|
||||
assertThat(actual, is(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapSerializerInputNulls() {
|
||||
var serializer = new MapSerializer<>(
|
||||
StringToIntSerializer.INSTANCE,
|
||||
StringToIntSerializer.INSTANCE,
|
||||
false,
|
||||
true
|
||||
);
|
||||
Map<String, String> actual = serializer.deserialize(entriesAsMap(
|
||||
entry(11, 12),
|
||||
entry(null, 14),
|
||||
entry(15, null),
|
||||
entry(null, null),
|
||||
entry(19, 20)
|
||||
));
|
||||
Map<String, String> expected = entriesAsMap(
|
||||
entry("11", "12"),
|
||||
entry("15", null),
|
||||
entry(null, null),
|
||||
entry("19", "20")
|
||||
);
|
||||
assertThat(actual, is(expected));
|
||||
}
|
||||
|
||||
@Test
|
||||
void arraySerializer() {
|
||||
var serializer = new Serializers.ArraySerializer<>(
|
||||
String.class,
|
||||
StringToIntSerializer.INSTANCE,
|
||||
false, false
|
||||
);
|
||||
|
||||
List<Integer> integers = serializer.serialize(new String[]{"1", "2", "3"});
|
||||
assertThat(integers, is(List.of(1, 2, 3)));
|
||||
|
||||
String[] strings = serializer.deserialize(List.of(4, 5, 6));
|
||||
assertThat(strings, is(new String[]{"4", "5", "6"}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void arraySerializerNested() {
|
||||
var serializer = new Serializers.ArraySerializer<>(
|
||||
String[].class,
|
||||
new Serializers.ArraySerializer<>(
|
||||
String.class,
|
||||
StringToIntSerializer.INSTANCE,
|
||||
false, false
|
||||
),
|
||||
false, false
|
||||
);
|
||||
|
||||
String[][] input = {{"1"}, {"2", "3"}};
|
||||
List<List<Integer>> integers = serializer.serialize(input);
|
||||
assertThat(integers, is(List.of(List.of(1), List.of(2, 3))));
|
||||
|
||||
String[][] output = serializer.deserialize(List.of(
|
||||
List.of(4),
|
||||
List.of(5, 6)
|
||||
));
|
||||
assertThat(output, is(new String[][]{{"4"}, {"5", "6"}}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void arraySerializerNoNulls() {
|
||||
var serializer = new Serializers.ArraySerializer<>(
|
||||
String.class,
|
||||
StringToIntSerializer.INSTANCE,
|
||||
false, false
|
||||
);
|
||||
|
||||
List<Integer> integers = serializer.serialize(new String[]{"1", null, "2", null, "3"});
|
||||
assertThat(integers, is(List.of(1, 2, 3)));
|
||||
|
||||
String[] strings = serializer.deserialize(asList(4, null, 5, null, 6));
|
||||
assertThat(strings, is(new String[]{"4", "5", "6"}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void arraySerializerOutputNulls() {
|
||||
var serializer = new Serializers.ArraySerializer<>(
|
||||
String.class,
|
||||
StringToIntSerializer.INSTANCE,
|
||||
true, false
|
||||
);
|
||||
|
||||
List<Integer> integers = serializer.serialize(new String[]{"1", null, "2", null, "3"});
|
||||
assertThat(integers, is(asList(1, null, 2, null, 3)));
|
||||
}
|
||||
|
||||
@Test
|
||||
void arraySerializerInputNulls() {
|
||||
var serializer = new Serializers.ArraySerializer<>(
|
||||
String.class,
|
||||
StringToIntSerializer.INSTANCE,
|
||||
false, true
|
||||
);
|
||||
|
||||
String[] strings = serializer.deserialize(asList(4, null, 5, null, 6));
|
||||
assertThat(strings, is(new String[]{"4", null, "5", null, "6"}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void primitiveBooleanArraySerializer() {
|
||||
var serializer = new Serializers.PrimitiveBooleanArraySerializer();
|
||||
|
||||
List<Boolean> list = serializer.serialize(new boolean[]{true, false, true});
|
||||
assertThat(list, is(List.of(true, false, true)));
|
||||
|
||||
boolean[] array = (boolean[]) serializer.deserialize(List.of(false, true, false));
|
||||
assertThat(array, is(new boolean[]{false, true, false}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void primitiveCharacterArraySerializer() {
|
||||
var serializer = new Serializers.PrimitiveCharacterArraySerializer();
|
||||
|
||||
List<String> list = serializer.serialize(new char[]{'a', 'b', 'c'});
|
||||
assertThat(list, is(List.of("a", "b", "c")));
|
||||
|
||||
char[] array = (char[]) serializer.deserialize(List.of("d", "e", "f"));
|
||||
assertThat(array, is(new char[]{'d', 'e', 'f'}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void primitiveByteArraySerializer() {
|
||||
var serializer = new Serializers.PrimitiveByteArraySerializer();
|
||||
|
||||
List<Number> list = serializer.serialize(new byte[]{(byte) 1, (byte) 2, (byte) 3});
|
||||
assertThat(list, is(List.of(1L, 2L, 3L)));
|
||||
|
||||
byte[] array = (byte[]) serializer.deserialize(List.of(3L, 4L, 5L));
|
||||
assertThat(array, is(new byte[]{(byte) 3, (byte) 4, (byte) 5}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void primitiveShortArraySerializer() {
|
||||
var serializer = new Serializers.PrimitiveShortArraySerializer();
|
||||
|
||||
List<Number> list = serializer.serialize(new short[]{(short) 1, (short) 2, (short) 3});
|
||||
assertThat(list, is(List.of(1L, 2L, 3L)));
|
||||
|
||||
short[] array = (short[]) serializer.deserialize(List.of(3L, 4L, 5L));
|
||||
assertThat(array, is(new short[]{(short) 3, (short) 4, (short) 5}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void primitiveIntegerArraySerializer() {
|
||||
var serializer = new Serializers.PrimitiveIntegerArraySerializer();
|
||||
|
||||
List<Number> list = serializer.serialize(new int[]{1, 2, 3});
|
||||
assertThat(list, is(List.of(1L, 2L, 3L)));
|
||||
|
||||
int[] array = (int[]) serializer.deserialize(List.of(3L, 4L, 5L));
|
||||
assertThat(array, is(new int[]{3, 4, 5}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void primitiveLongArraySerializer() {
|
||||
var serializer = new Serializers.PrimitiveLongArraySerializer();
|
||||
|
||||
List<Number> list = serializer.serialize(new long[]{1L, 2L, 3L});
|
||||
assertThat(list, is(List.of(1L, 2L, 3L)));
|
||||
|
||||
long[] array = (long[]) serializer.deserialize(List.of(3L, 4L, 5L));
|
||||
assertThat(array, is(new long[]{3L, 4L, 5L}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void primitiveFloatArraySerializer() {
|
||||
var serializer = new Serializers.PrimitiveFloatArraySerializer();
|
||||
|
||||
List<Number> list = serializer.serialize(new float[]{1f, 2f, 3f});
|
||||
assertThat(list, is(List.of(1d, 2d, 3d)));
|
||||
|
||||
float[] array = (float[]) serializer.deserialize(List.of(3d, 4d, 5d));
|
||||
assertThat(array, is(new float[]{3f, 4f, 5f}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void primitiveDoubleArraySerializer() {
|
||||
var serializer = new Serializers.PrimitiveDoubleArraySerializer();
|
||||
|
||||
List<Number> list = serializer.serialize(new double[]{1d, 2d, 3d});
|
||||
assertThat(list, is(List.of(1d, 2d, 3d)));
|
||||
|
||||
double[] array = (double[]) serializer.deserialize(List.of(3d, 4d, 5d));
|
||||
assertThat(array, is(new double[]{3d, 4d, 5d}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void primitiveBooleanArraySerializerDeserializeNullElement() {
|
||||
var serializer = new Serializers.PrimitiveBooleanArraySerializer();
|
||||
assertThrowsConfigurationException(
|
||||
() -> serializer.deserialize(asList(true, false, null)),
|
||||
"The boolean element at index 2 must not be null."
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void primitiveCharacterArraySerializerDeserializeNullElement() {
|
||||
var serializer = new Serializers.PrimitiveCharacterArraySerializer();
|
||||
assertThrowsConfigurationException(
|
||||
() -> serializer.deserialize(asList("a", null, "b")),
|
||||
"The char element at index 1 must not be null."
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void primitiveByteArraySerializerDeserializeNullElement() {
|
||||
var serializer = new Serializers.PrimitiveByteArraySerializer();
|
||||
assertThrowsConfigurationException(
|
||||
() -> serializer.deserialize(asList((byte) 1, (byte) 2, null)),
|
||||
"The byte element at index 2 must not be null."
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void primitiveShortArraySerializerDeserializeNullElement() {
|
||||
var serializer = new Serializers.PrimitiveShortArraySerializer();
|
||||
assertThrowsConfigurationException(
|
||||
() -> serializer.deserialize(asList((short) 1, null, (short) 2)),
|
||||
"The short element at index 1 must not be null."
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void primitiveIntegerArraySerializerDeserializeNullElement() {
|
||||
var serializer = new Serializers.PrimitiveIntegerArraySerializer();
|
||||
assertThrowsConfigurationException(
|
||||
() -> serializer.deserialize(asList(1, 2, null)),
|
||||
"The int element at index 2 must not be null."
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void primitiveLongArraySerializerDeserializeNullElement() {
|
||||
var serializer = new Serializers.PrimitiveLongArraySerializer();
|
||||
assertThrowsConfigurationException(
|
||||
() -> serializer.deserialize(asList(1L, null, 2L)),
|
||||
"The long element at index 1 must not be null."
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void primitiveFloatArraySerializerDeserializeNullElement() {
|
||||
var serializer = new Serializers.PrimitiveFloatArraySerializer();
|
||||
assertThrowsConfigurationException(
|
||||
() -> serializer.deserialize(asList(1f, 2f, null)),
|
||||
"The float element at index 2 must not be null."
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void primitiveDoubleArraySerializerDeserializeNullElement() {
|
||||
var serializer = new Serializers.PrimitiveDoubleArraySerializer();
|
||||
assertThrowsConfigurationException(
|
||||
() -> serializer.deserialize(asList(1d, null, 2d)),
|
||||
"The double element at index 1 must not be null."
|
||||
);
|
||||
}
|
||||
|
||||
private static final class StringToIntSerializer implements Serializer<String, Integer> {
|
||||
private static final StringToIntSerializer INSTANCE = new StringToIntSerializer();
|
||||
|
||||
@Override
|
||||
public Integer serialize(String element) {
|
||||
return Integer.valueOf(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String deserialize(Integer element) {
|
||||
return element.toString();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,229 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import org.junit.jupiter.api.function.Executable;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Field;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
public final class TestUtils {
|
||||
public static final PointSerializer POINT_SERIALIZER = new PointSerializer();
|
||||
public static final PointIdentitySerializer POINT_IDENTITY_SERIALIZER =
|
||||
new PointIdentitySerializer();
|
||||
public static final CustomBigIntegerSerializer CUSTOM_BIG_INTEGER_SERIALIZER
|
||||
= new CustomBigIntegerSerializer();
|
||||
|
||||
public static Field getField(Class<?> cls, String fieldName) {
|
||||
return Arrays.stream(cls.getDeclaredFields())
|
||||
.filter(field -> field.getName().equals(fieldName))
|
||||
.findAny()
|
||||
.orElseThrow();
|
||||
}
|
||||
|
||||
public static void assertThrowsNullPointerException(Executable executable, String argumentName) {
|
||||
String msg = "The " + argumentName + " must not be null.";
|
||||
assertThrowsException(NullPointerException.class, executable, msg);
|
||||
}
|
||||
|
||||
public static void assertThrowsIllegalArgumentException(
|
||||
Executable executable,
|
||||
String expectedExceptionMessage
|
||||
) {
|
||||
assertThrowsException(IllegalArgumentException.class, executable, expectedExceptionMessage);
|
||||
}
|
||||
|
||||
public static void assertThrowsConfigurationException(
|
||||
Executable executable,
|
||||
String expectedExceptionMessage
|
||||
) {
|
||||
assertThrowsException(ConfigurationException.class, executable, expectedExceptionMessage);
|
||||
}
|
||||
|
||||
public static void assertThrowsRuntimeException(
|
||||
Executable executable,
|
||||
String expectedExceptionMessage
|
||||
) {
|
||||
assertThrowsException(RuntimeException.class, executable, expectedExceptionMessage);
|
||||
}
|
||||
|
||||
public static <T extends Exception> void assertThrowsException(
|
||||
Class<T> exceptionType,
|
||||
Executable executable,
|
||||
String expectedExceptionMessage
|
||||
) {
|
||||
T exception = assertThrows(exceptionType, executable);
|
||||
assertEquals(expectedExceptionMessage, exception.getMessage());
|
||||
}
|
||||
|
||||
public static final class CustomBigIntegerSerializer implements Serializer<BigInteger, String> {
|
||||
|
||||
@Override
|
||||
public String serialize(BigInteger element) {
|
||||
return element.multiply(BigInteger.TWO).toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BigInteger deserialize(String element) {
|
||||
return new BigInteger(element).divide(BigInteger.TWO);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class PointSerializer implements Serializer<Point, String> {
|
||||
@Override
|
||||
public String serialize(Point element) {
|
||||
return element.x + ":" + element.y;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point deserialize(String element) {
|
||||
String[] parts = element.split(":");
|
||||
int x = Integer.parseInt(parts[0]);
|
||||
int y = Integer.parseInt(parts[1]);
|
||||
return new Point(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class PointIdentitySerializer implements Serializer<Point, Point> {
|
||||
@Override
|
||||
public Point serialize(Point element) {
|
||||
return element;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point deserialize(Point element) {
|
||||
return element;
|
||||
}
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
public static <E> Set<E> asSet(E... elements) {
|
||||
return new LinkedHashSet<>(Arrays.asList(elements));
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> asMap() {
|
||||
return new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> asMap(K k1, V v1) {
|
||||
final Map<K, V> result = new LinkedHashMap<>();
|
||||
result.put(k1, v1);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> asMap(K k1, V v1, K k2, V v2) {
|
||||
final Map<K, V> result = new LinkedHashMap<>();
|
||||
result.put(k1, v1);
|
||||
result.put(k2, v2);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> asMap(K k1, V v1, K k2, V v2, K k3, V v3) {
|
||||
final Map<K, V> result = new LinkedHashMap<>();
|
||||
result.put(k1, v1);
|
||||
result.put(k2, v2);
|
||||
result.put(k3, v3);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> asMap(K k1, V v1, K k2, V v2, K k3, V v3, K k4, V v4) {
|
||||
final Map<K, V> result = new LinkedHashMap<>();
|
||||
result.put(k1, v1);
|
||||
result.put(k2, v2);
|
||||
result.put(k3, v3);
|
||||
result.put(k4, v4);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> entriesAsMap(MEntry... entries) {
|
||||
final Map<Object, Object> result = new LinkedHashMap<>();
|
||||
Arrays.stream(entries).forEach(entry -> result.put(entry.getKey(), entry.getValue()));
|
||||
|
||||
// Suppressing this warning might lead to an exception.
|
||||
// Using proper generics for the MEntry class is possible.
|
||||
// However, doing so increases the compilation type by several seconds
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<K, V> returnResult = (Map<K, V>) result;
|
||||
return returnResult;
|
||||
}
|
||||
|
||||
public static <K, V> Map<K, V> extend(Map<K, V> base, MEntry... entries) {
|
||||
final Map<K, V> result = new LinkedHashMap<>(base);
|
||||
final Map<K, V> ext = entriesAsMap(entries);
|
||||
result.putAll(ext);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static MEntry entry(Object key, Object val) {
|
||||
return new MEntry(key, val);
|
||||
}
|
||||
|
||||
public static final class MEntry implements Map.Entry<Object, Object> {
|
||||
private final Object key;
|
||||
private final Object val;
|
||||
|
||||
public MEntry(Object key, Object val) {
|
||||
this.key = key;
|
||||
this.val = val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValue() {
|
||||
return val;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object setValue(Object value) {
|
||||
throw new UnsupportedOperationException("setValue not supported");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
MEntry mEntry = (MEntry) o;
|
||||
return Objects.equals(key, mEntry.key) && Objects.equals(val, mEntry.val);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static <T, C extends Collection<T[]>> boolean collectionOfArraysDeepEquals(
|
||||
C collection1,
|
||||
C collection2,
|
||||
Supplier<Collection<List<T>>> collectionFactory
|
||||
) {
|
||||
Collection<List<T>> c1 = collection1.stream().map(Arrays::asList)
|
||||
.collect(Collectors.toCollection(collectionFactory));
|
||||
Collection<List<T>> c2 = collection2.stream().map(Arrays::asList)
|
||||
.collect(Collectors.toCollection(collectionFactory));
|
||||
return c1.equals(c2);
|
||||
}
|
||||
|
||||
public static String readFile(Path file) {
|
||||
try (Stream<String> lines = Files.lines(file)) {
|
||||
return lines.collect(joining("\n"));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,235 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import com.google.common.jimfs.Jimfs;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static de.exlll.configlib.TestUtils.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class YamlConfigurationStoreTest {
|
||||
private final FileSystem fs = Jimfs.newFileSystem();
|
||||
private final Path yamlFile = fs.getPath("/tmp/config.yml");
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException {
|
||||
Files.createDirectories(yamlFile.getParent());
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws IOException {
|
||||
fs.close();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static final class A {
|
||||
String s = "S1";
|
||||
@Comment("A comment")
|
||||
Integer i = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
void save() {
|
||||
YamlConfigurationProperties properties = YamlConfigurationProperties.newBuilder()
|
||||
.header("The\nHeader")
|
||||
.footer("The\nFooter")
|
||||
.outputNulls(true)
|
||||
.setFieldFormatter(field -> field.getName().toUpperCase())
|
||||
.build();
|
||||
YamlConfigurationStore<A> store = new YamlConfigurationStore<>(A.class, properties);
|
||||
store.save(new A(), yamlFile);
|
||||
|
||||
String expected =
|
||||
"""
|
||||
# The
|
||||
# Header
|
||||
|
||||
S: S1
|
||||
# A comment
|
||||
I: null
|
||||
|
||||
# The
|
||||
# Footer\
|
||||
""";
|
||||
assertEquals(expected, TestUtils.readFile(yamlFile));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static final class B {
|
||||
String s = "S1";
|
||||
String t = "T1";
|
||||
Integer i = 1;
|
||||
}
|
||||
|
||||
@Test
|
||||
void load() throws IOException {
|
||||
YamlConfigurationProperties properties = YamlConfigurationProperties.newBuilder()
|
||||
.inputNulls(true)
|
||||
.setFieldFormatter(field -> field.getName().toUpperCase())
|
||||
.build();
|
||||
YamlConfigurationStore<B> store = new YamlConfigurationStore<>(B.class, properties);
|
||||
|
||||
Files.writeString(
|
||||
yamlFile,
|
||||
"""
|
||||
# The
|
||||
# Header
|
||||
|
||||
S: S2
|
||||
t: T2
|
||||
I: null
|
||||
|
||||
# The
|
||||
# Footer\
|
||||
"""
|
||||
);
|
||||
|
||||
B config = store.load(yamlFile);
|
||||
assertEquals("S2", config.s);
|
||||
assertEquals("T1", config.t);
|
||||
assertNull(config.i);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static final class C {
|
||||
int i;
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadInvalidYaml() throws IOException {
|
||||
YamlConfigurationStore<C> store = newDefaultStore(C.class);
|
||||
|
||||
Files.writeString(
|
||||
yamlFile,
|
||||
"""
|
||||
- - - - - a
|
||||
a
|
||||
"""
|
||||
);
|
||||
|
||||
assertThrowsConfigurationException(
|
||||
() -> store.load(yamlFile),
|
||||
"The configuration file at /tmp/config.yml does not contain valid YAML."
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadEmptyYaml() throws IOException {
|
||||
YamlConfigurationStore<C> store = newDefaultStore(C.class);
|
||||
|
||||
Files.writeString(yamlFile, "null");
|
||||
|
||||
assertThrowsConfigurationException(
|
||||
() -> store.load(yamlFile),
|
||||
"The configuration file at /tmp/config.yml is empty or only contains null."
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadNonMapYaml() throws IOException {
|
||||
YamlConfigurationStore<C> store = newDefaultStore(C.class);
|
||||
|
||||
Files.writeString(yamlFile, "a");
|
||||
|
||||
assertThrowsConfigurationException(
|
||||
() -> store.load(yamlFile),
|
||||
"The contents of the YAML file at /tmp/config.yml do not represent a " +
|
||||
"configuration. A valid configuration file contains a YAML map but instead a " +
|
||||
"'class java.lang.String' was found."
|
||||
);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static final class D {
|
||||
Point point = new Point(1, 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveConfigurationWithInvalidTargetType() {
|
||||
YamlConfigurationProperties properties = YamlConfigurationProperties.newBuilder()
|
||||
.addSerializer(Point.class, TestUtils.POINT_IDENTITY_SERIALIZER)
|
||||
.build();
|
||||
YamlConfigurationStore<D> store = new YamlConfigurationStore<>(D.class, properties);
|
||||
|
||||
assertThrowsConfigurationException(
|
||||
() -> store.save(new D(), yamlFile),
|
||||
"The given configuration could not be converted into YAML. \n" +
|
||||
"Do all custom serializers produce valid target types?"
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveCreatesParentDirectoriesIfPropertyTrue() {
|
||||
YamlConfigurationStore<A> store = newDefaultStore(A.class);
|
||||
|
||||
Path file = fs.getPath("/a/b/c.yml");
|
||||
store.save(new A(), file);
|
||||
|
||||
assertTrue(Files.exists(file.getParent()));
|
||||
assertTrue(Files.exists(file));
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveDoesNotCreateParentDirectoriesIfPropertyFalse() {
|
||||
YamlConfigurationProperties properties = YamlConfigurationProperties.newBuilder()
|
||||
.createParentDirectories(false)
|
||||
.build();
|
||||
YamlConfigurationStore<A> store = new YamlConfigurationStore<>(A.class, properties);
|
||||
|
||||
Path file = fs.getPath("/a/b/c.yml");
|
||||
assertThrowsRuntimeException(
|
||||
() -> store.save(new A(), file),
|
||||
"java.nio.file.NoSuchFileException: /a/b/c.yml"
|
||||
);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static final class E {
|
||||
int i = 10;
|
||||
int j = 11;
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateCreatesConfigurationFileIfItDoesNotExist() {
|
||||
YamlConfigurationStore<E> store = newDefaultStore(E.class);
|
||||
|
||||
assertFalse(Files.exists(yamlFile));
|
||||
E config = store.update(yamlFile);
|
||||
assertEquals("i: 10\nj: 11", readFile(yamlFile));
|
||||
assertEquals(10, config.i);
|
||||
assertEquals(11, config.j);
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateLoadsConfigurationFileIfItDoesExist() throws IOException {
|
||||
YamlConfigurationStore<E> store = newDefaultStore(E.class);
|
||||
|
||||
Files.writeString(yamlFile, "i: 20");
|
||||
E config = store.update(yamlFile);
|
||||
assertEquals(20, config.i);
|
||||
assertEquals(11, config.j);
|
||||
}
|
||||
|
||||
@Test
|
||||
void updateUpdatesFile() throws IOException {
|
||||
YamlConfigurationStore<E> store = newDefaultStore(E.class);
|
||||
|
||||
Files.writeString(yamlFile, "i: 20\nk: 30");
|
||||
E config = store.update(yamlFile);
|
||||
assertEquals(20, config.i);
|
||||
assertEquals(11, config.j);
|
||||
assertEquals("i: 20\nj: 11", readFile(yamlFile));
|
||||
}
|
||||
|
||||
private static <T> YamlConfigurationStore<T> newDefaultStore(Class<T> configType) {
|
||||
YamlConfigurationProperties properties = YamlConfigurationProperties.newBuilder().build();
|
||||
return new YamlConfigurationStore<>(configType, properties);
|
||||
}
|
||||
}
|
@ -0,0 +1,418 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import com.google.common.jimfs.Jimfs;
|
||||
import de.exlll.configlib.YamlConfigurationStore.YamlFileWriter;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.snakeyaml.engine.v2.api.Dump;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
class YamlFileWriterTest {
|
||||
private final FileSystem fs = Jimfs.newFileSystem();
|
||||
private final Path yamlFile = fs.getPath("/tmp/config.yml");
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException {
|
||||
Files.createDirectories(yamlFile.getParent());
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws IOException {
|
||||
fs.close();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static final class A {
|
||||
String s = "";
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeYamlWithNoComments() {
|
||||
writeConfig(A.class);
|
||||
assertFileContentEquals("s: ''");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeYamlWithHeaderAndFooter() {
|
||||
writeConfig(
|
||||
A.class,
|
||||
builder -> builder
|
||||
.header("This is a \n\n \nheader.")
|
||||
.footer("That is a\n\n \nfooter.")
|
||||
);
|
||||
assertFileContentEquals(
|
||||
"""
|
||||
# This is a\s
|
||||
|
||||
# \s
|
||||
# header.
|
||||
|
||||
s: ''
|
||||
|
||||
# That is a
|
||||
|
||||
# \s
|
||||
# footer.\
|
||||
"""
|
||||
);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static final class B {
|
||||
@Comment("Hello")
|
||||
String s = "s";
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeYamlSingleComment() {
|
||||
writeConfig(B.class);
|
||||
assertFileContentEquals(
|
||||
"""
|
||||
# Hello
|
||||
s: s\
|
||||
"""
|
||||
);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static final class C {
|
||||
@Comment({"Hello", "World"})
|
||||
Map<String, Integer> mapStringInteger = Map.of("1", 2);
|
||||
@Comment({"world", "hello"})
|
||||
Map<Integer, String> mapIntegerString = Map.of(2, "1");
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeYamlMultipleComments() {
|
||||
writeConfig(C.class);
|
||||
assertFileContentEquals(
|
||||
"""
|
||||
# Hello
|
||||
# World
|
||||
mapStringInteger:
|
||||
'1': 2
|
||||
# world
|
||||
# hello
|
||||
mapIntegerString:
|
||||
2: '1'\
|
||||
"""
|
||||
);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static final class D {
|
||||
@Comment({"Hello", "", " ", "World"})
|
||||
String s1 = "s1";
|
||||
@Comment({"", "", " ", "How are ", "you?", ""})
|
||||
String s2 = "s2";
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeYamlEmptyComments() {
|
||||
writeConfig(D.class);
|
||||
assertFileContentEquals(
|
||||
"""
|
||||
# Hello
|
||||
|
||||
# \s
|
||||
# World
|
||||
s1: s1
|
||||
|
||||
|
||||
# \s
|
||||
# How are\s
|
||||
# you?
|
||||
|
||||
s2: s2\
|
||||
"""
|
||||
);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static final class E1 {
|
||||
@Comment("m")
|
||||
Map<String, Map<String, Integer>> m = Map.of("c", Map.of("i", 1));
|
||||
@Comment("e2")
|
||||
E2 e2 = new E2();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static final class E2 {
|
||||
Map<String, Integer> m = Map.of("i", 1);
|
||||
@Comment("e3")
|
||||
E3 e3 = new E3();
|
||||
@Comment("j")
|
||||
int j = 10;
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static final class E3 {
|
||||
@Comment("i")
|
||||
int i = 1;
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeYamlNestedComments1() {
|
||||
writeConfig(E1.class);
|
||||
assertFileContentEquals(
|
||||
"""
|
||||
# m
|
||||
m:
|
||||
c:
|
||||
i: 1
|
||||
# e2
|
||||
e2:
|
||||
m:
|
||||
i: 1
|
||||
# e3
|
||||
e3:
|
||||
# i
|
||||
i: 1
|
||||
# j
|
||||
j: 10\
|
||||
"""
|
||||
);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static final class F1 {
|
||||
Map<String, Integer> m1 = Map.of("i", 1);
|
||||
F2 f2 = new F2();
|
||||
@Comment("f1.m2")
|
||||
Map<String, Integer> m2 = Map.of("i", 1);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static final class F2 {
|
||||
@Comment("f2.i")
|
||||
int i;
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeYamlNestedComments2() {
|
||||
writeConfig(F1.class);
|
||||
assertFileContentEquals(
|
||||
"""
|
||||
m1:
|
||||
i: 1
|
||||
f2:
|
||||
# f2.i
|
||||
i: 0
|
||||
# f1.m2
|
||||
m2:
|
||||
i: 1\
|
||||
"""
|
||||
);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static final class G1 {
|
||||
@Comment("g1.g2")
|
||||
G2 g2 = new G2();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static final class G2 {
|
||||
G3 g3 = new G3();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static final class G3 {
|
||||
G4 g4 = new G4();
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static final class G4 {
|
||||
@Comment({"g4.g3 1", "g4.g3 2"})
|
||||
int g3;
|
||||
@Comment("g4.g4")
|
||||
int g4;
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeYamlNestedComments3() {
|
||||
writeConfig(G1.class);
|
||||
assertFileContentEquals(
|
||||
"""
|
||||
# g1.g2
|
||||
g2:
|
||||
g3:
|
||||
g4:
|
||||
# g4.g3 1
|
||||
# g4.g3 2
|
||||
g3: 0
|
||||
# g4.g4
|
||||
g4: 0\
|
||||
"""
|
||||
);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static final class H1 {
|
||||
@Comment("h2.1")
|
||||
H2 h21 = new H2();
|
||||
@Comment("h2.2")
|
||||
H2 h22 = null;
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static final class H2 {
|
||||
@Comment("j")
|
||||
int j = 10;
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeYamlNullFields() {
|
||||
writeConfig(H1.class);
|
||||
assertFileContentEquals(
|
||||
"""
|
||||
# h2.1
|
||||
h21:
|
||||
# j
|
||||
j: 10\
|
||||
"""
|
||||
);
|
||||
writeConfig(H1.class, builder -> builder.outputNulls(true));
|
||||
assertFileContentEquals(
|
||||
"""
|
||||
# h2.1
|
||||
h21:
|
||||
# j
|
||||
j: 10
|
||||
# h2.2
|
||||
h22: null\
|
||||
"""
|
||||
);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class J1 {
|
||||
@Comment("sj1")
|
||||
String sJ1 = "sj1";
|
||||
}
|
||||
|
||||
static final class J2 extends J1 {
|
||||
@Comment("sj2")
|
||||
String sJ2 = "sj2";
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class K1 {
|
||||
@Comment("k1.j1")
|
||||
J1 k1J1 = new J1();
|
||||
@Comment("k1.j2")
|
||||
J2 k1J2 = new J2();
|
||||
}
|
||||
|
||||
static final class K2 extends K1 {
|
||||
@Comment("k2.j1")
|
||||
J1 k2J1 = new J1();
|
||||
@Comment("k2.j2")
|
||||
J2 k2J2 = new J2();
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeYamlInheritance() {
|
||||
writeConfig(K2.class);
|
||||
assertFileContentEquals(
|
||||
"""
|
||||
# k1.j1
|
||||
k1J1:
|
||||
# sj1
|
||||
sJ1: sj1
|
||||
# k1.j2
|
||||
k1J2:
|
||||
# sj1
|
||||
sJ1: sj1
|
||||
# sj2
|
||||
sJ2: sj2
|
||||
# k2.j1
|
||||
k2J1:
|
||||
# sj1
|
||||
sJ1: sj1
|
||||
# k2.j2
|
||||
k2J2:
|
||||
# sj1
|
||||
sJ1: sj1
|
||||
# sj2
|
||||
sJ2: sj2\
|
||||
"""
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
void lengthCommonPrefix() {
|
||||
List<String> ab = List.of("a", "b");
|
||||
List<String> abc = List.of("a", "b", "c");
|
||||
List<String> abcd = List.of("a", "b", "c", "d");
|
||||
List<String> aef = List.of("a", "e", "f");
|
||||
List<String> def = List.of("d", "e", "f");
|
||||
|
||||
assertEquals(2, YamlFileWriter.lengthCommonPrefix(ab, ab));
|
||||
assertEquals(2, YamlFileWriter.lengthCommonPrefix(abc, ab));
|
||||
assertEquals(2, YamlFileWriter.lengthCommonPrefix(ab, abc));
|
||||
assertEquals(2, YamlFileWriter.lengthCommonPrefix(ab, abcd));
|
||||
assertEquals(3, YamlFileWriter.lengthCommonPrefix(abc, abc));
|
||||
assertEquals(3, YamlFileWriter.lengthCommonPrefix(abc, abcd));
|
||||
|
||||
assertEquals(1, YamlFileWriter.lengthCommonPrefix(ab, aef));
|
||||
assertEquals(1, YamlFileWriter.lengthCommonPrefix(abcd, aef));
|
||||
|
||||
assertEquals(0, YamlFileWriter.lengthCommonPrefix(ab, def));
|
||||
assertEquals(0, YamlFileWriter.lengthCommonPrefix(abcd, def));
|
||||
}
|
||||
|
||||
String readFile() {
|
||||
return TestUtils.readFile(yamlFile);
|
||||
}
|
||||
|
||||
record YamlFileWriterArguments(
|
||||
String yaml,
|
||||
Queue<CommentNode> nodes,
|
||||
YamlConfigurationProperties properties
|
||||
) {}
|
||||
|
||||
void assertFileContentEquals(String expected) {
|
||||
assertEquals(expected, readFile());
|
||||
}
|
||||
|
||||
void writeConfig(Class<?> cls) {
|
||||
writeConfig(cls, builder -> {});
|
||||
}
|
||||
|
||||
<T> void writeConfig(Class<T> cls, Consumer<YamlConfigurationProperties.Builder<?>> configurer) {
|
||||
YamlFileWriterArguments args = argsFromConfig(cls, Reflect.newInstance(cls), configurer);
|
||||
YamlFileWriter writer = new YamlFileWriter(yamlFile, args.properties);
|
||||
writer.writeYaml(args.yaml, args.nodes);
|
||||
}
|
||||
|
||||
static <T> YamlFileWriterArguments argsFromConfig(
|
||||
Class<T> t,
|
||||
T c,
|
||||
Consumer<YamlConfigurationProperties.Builder<?>> configurer
|
||||
) {
|
||||
YamlConfigurationProperties.Builder<?> builder = YamlConfigurationProperties.newBuilder();
|
||||
configurer.accept(builder);
|
||||
YamlConfigurationProperties properties = builder.build();
|
||||
|
||||
ConfigurationSerializer<T> serializer = new ConfigurationSerializer<>(t, properties);
|
||||
Map<?, ?> serialize = serializer.serialize(c);
|
||||
Dump dump = YamlConfigurationStore.newYamlDumper();
|
||||
String yaml = dump.dumpToString(serialize);
|
||||
CommentNodeExtractor extractor = new CommentNodeExtractor(properties);
|
||||
Queue<CommentNode> nodes = extractor.extractCommentNodes(c);
|
||||
return new YamlFileWriterArguments(yaml, nodes, properties);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,188 @@
|
||||
package de.exlll.configlib.configurations;
|
||||
|
||||
import de.exlll.configlib.Configuration;
|
||||
import de.exlll.configlib.Ignore;
|
||||
|
||||
import java.awt.Point;
|
||||
import java.util.*;
|
||||
|
||||
@SuppressWarnings({"FieldCanBeLocal", "FieldMayBeFinal"})
|
||||
@Configuration
|
||||
public class ExampleConfigurationB1 {
|
||||
/* IGNORED FIELDS */
|
||||
private static final int b1_staticFinalInt = 1;
|
||||
private static int b1_staticInt = 2;
|
||||
private final int b1_finalInt = 3;
|
||||
private transient int b1_transientInt = 4;
|
||||
@Ignore
|
||||
private int b1_ignoredInt = 5;
|
||||
@Ignore
|
||||
private String b1_ignoredString = "ignoredString";
|
||||
@Ignore
|
||||
private List<String> b1_ignoredListString = List.of("ignored", "list", "string");
|
||||
|
||||
private boolean b1_primBool;
|
||||
private Character b1_refChar;
|
||||
private String b1_string;
|
||||
private List<Byte> b1_listByte;
|
||||
private Short[] b1_arrayShort;
|
||||
private Set<Integer> b1_setInteger;
|
||||
private List<Short> b1_listEmpty;
|
||||
private Map<Long, Long> b1_mapLongLong;
|
||||
private List<List<Byte>> b1_listListByte;
|
||||
private Point b1_point;
|
||||
|
||||
public static int getB1_staticFinalInt() {
|
||||
return b1_staticFinalInt;
|
||||
}
|
||||
|
||||
public static int getB1_staticInt() {
|
||||
return b1_staticInt;
|
||||
}
|
||||
|
||||
public static void setB1_staticInt(int b1_staticInt) {
|
||||
ExampleConfigurationB1.b1_staticInt = b1_staticInt;
|
||||
}
|
||||
|
||||
public int getB1_finalInt() {
|
||||
return b1_finalInt;
|
||||
}
|
||||
|
||||
public int getB1_transientInt() {
|
||||
return b1_transientInt;
|
||||
}
|
||||
|
||||
public void setB1_transientInt(int b1_transientInt) {
|
||||
this.b1_transientInt = b1_transientInt;
|
||||
}
|
||||
|
||||
public int getB1_ignoredInt() {
|
||||
return b1_ignoredInt;
|
||||
}
|
||||
|
||||
public void setB1_ignoredInt(int b1_ignoredInt) {
|
||||
this.b1_ignoredInt = b1_ignoredInt;
|
||||
}
|
||||
|
||||
public String getB1_ignoredString() {
|
||||
return b1_ignoredString;
|
||||
}
|
||||
|
||||
public void setB1_ignoredString(String b1_ignoredString) {
|
||||
this.b1_ignoredString = b1_ignoredString;
|
||||
}
|
||||
|
||||
public List<String> getB1_ignoredListString() {
|
||||
return b1_ignoredListString;
|
||||
}
|
||||
|
||||
public void setB1_ignoredListString(List<String> b1_ignoredListString) {
|
||||
this.b1_ignoredListString = b1_ignoredListString;
|
||||
}
|
||||
|
||||
public boolean isB1_primBool() {
|
||||
return b1_primBool;
|
||||
}
|
||||
|
||||
public void setB1_primBool(boolean b1_primBool) {
|
||||
this.b1_primBool = b1_primBool;
|
||||
}
|
||||
|
||||
public Character getB1_refChar() {
|
||||
return b1_refChar;
|
||||
}
|
||||
|
||||
public void setB1_refChar(Character b1_refChar) {
|
||||
this.b1_refChar = b1_refChar;
|
||||
}
|
||||
|
||||
public String getB1_string() {
|
||||
return b1_string;
|
||||
}
|
||||
|
||||
public void setB1_string(String b1_string) {
|
||||
this.b1_string = b1_string;
|
||||
}
|
||||
|
||||
public List<Byte> getB1_listByte() {
|
||||
return b1_listByte;
|
||||
}
|
||||
|
||||
public void setB1_listByte(List<Byte> b1_listByte) {
|
||||
this.b1_listByte = b1_listByte;
|
||||
}
|
||||
|
||||
public Short[] getB1_arrayShort() {
|
||||
return b1_arrayShort;
|
||||
}
|
||||
|
||||
public void setB1_arrayShort(Short[] b1_arrayShort) {
|
||||
this.b1_arrayShort = b1_arrayShort;
|
||||
}
|
||||
|
||||
public Set<Integer> getB1_setInteger() {
|
||||
return b1_setInteger;
|
||||
}
|
||||
|
||||
public void setB1_setInteger(Set<Integer> b1_setInteger) {
|
||||
this.b1_setInteger = b1_setInteger;
|
||||
}
|
||||
|
||||
public List<Short> getB1_listEmpty() {
|
||||
return b1_listEmpty;
|
||||
}
|
||||
|
||||
public void setB1_listEmpty(List<Short> b1_listEmpty) {
|
||||
this.b1_listEmpty = b1_listEmpty;
|
||||
}
|
||||
|
||||
public Map<Long, Long> getB1_mapLongLong() {
|
||||
return b1_mapLongLong;
|
||||
}
|
||||
|
||||
public void setB1_mapLongLong(Map<Long, Long> b1_mapLongLong) {
|
||||
this.b1_mapLongLong = b1_mapLongLong;
|
||||
}
|
||||
|
||||
public List<List<Byte>> getB1_listListByte() {
|
||||
return b1_listListByte;
|
||||
}
|
||||
|
||||
public void setB1_listListByte(List<List<Byte>> b1_listListByte) {
|
||||
this.b1_listListByte = b1_listListByte;
|
||||
}
|
||||
|
||||
public Point getB1_point() {
|
||||
return b1_point;
|
||||
}
|
||||
|
||||
public void setB1_point(Point b1_point) {
|
||||
this.b1_point = b1_point;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
ExampleConfigurationB1 that = (ExampleConfigurationB1) o;
|
||||
return b1_transientInt == that.b1_transientInt &&
|
||||
b1_ignoredInt == that.b1_ignoredInt &&
|
||||
b1_primBool == that.b1_primBool &&
|
||||
Objects.equals(b1_ignoredString, that.b1_ignoredString) &&
|
||||
Objects.equals(b1_ignoredListString, that.b1_ignoredListString) &&
|
||||
Objects.equals(b1_refChar, that.b1_refChar) &&
|
||||
Objects.equals(b1_string, that.b1_string) &&
|
||||
Objects.equals(b1_listByte, that.b1_listByte) &&
|
||||
Arrays.equals(b1_arrayShort, that.b1_arrayShort) &&
|
||||
Objects.equals(b1_setInteger, that.b1_setInteger) &&
|
||||
Objects.equals(b1_listEmpty, that.b1_listEmpty) &&
|
||||
Objects.equals(b1_mapLongLong, that.b1_mapLongLong) &&
|
||||
Objects.equals(b1_listListByte, that.b1_listListByte) &&
|
||||
Objects.equals(b1_point, that.b1_point);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return 0;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue