forked from public-mirrors/ConfigLib
Update ConfigLib to version 2.0.0
parent
e1b629d5df
commit
49e5b56b5c
@ -0,0 +1,56 @@
|
||||
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 +1,5 @@
|
||||
name: ConfigLib
|
||||
author: Exlll
|
||||
|
||||
version: 1.4.1
|
||||
version: 2.0.0
|
||||
main: de.exlll.configlib.ConfigLib
|
@ -1,5 +1,5 @@
|
||||
name: ConfigLib
|
||||
author: Exlll
|
||||
|
||||
version: 1.4.1
|
||||
version: 2.0.0
|
||||
main: de.exlll.configlib.ConfigLib
|
@ -1,71 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
final class CommentAdder {
|
||||
private final Comments comments;
|
||||
private StringBuilder builder;
|
||||
|
||||
CommentAdder(Comments comments) {
|
||||
this.comments = comments;
|
||||
}
|
||||
|
||||
String addComments(String serializedMap) {
|
||||
builder = new StringBuilder();
|
||||
addClassComments();
|
||||
addMap(serializedMap);
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private void addClassComments() {
|
||||
List<String> clsComments = comments.getClassComments();
|
||||
if (!clsComments.isEmpty()) {
|
||||
addComments(clsComments);
|
||||
builder.append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
private void addComments(List<String> comments) {
|
||||
for (String comment : comments) {
|
||||
if (!comment.trim().isEmpty()) {
|
||||
builder.append("# ");
|
||||
builder.append(comment);
|
||||
}
|
||||
builder.append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
private void addMap(String map) {
|
||||
String[] lines = map.split("\n");
|
||||
for (String line : lines) {
|
||||
addLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
private void addLine(String line) {
|
||||
addLineComments(line);
|
||||
builder.append(line);
|
||||
builder.append('\n');
|
||||
}
|
||||
|
||||
private void addLineComments(String line) {
|
||||
if (!line.contains(":")) {
|
||||
return;
|
||||
}
|
||||
List<String> comments = getLineComments(line);
|
||||
addComments(comments);
|
||||
}
|
||||
|
||||
private List<String> getLineComments(String line) {
|
||||
Map<String, List<String>> entries = comments.getFieldComments();
|
||||
for (Map.Entry<String, List<String>> entry : entries.entrySet()) {
|
||||
String s = entry.getKey() + ":";
|
||||
if (line.startsWith(s)) {
|
||||
return entry.getValue();
|
||||
}
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
public final class ConfigException extends RuntimeException {
|
||||
public ConfigException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
@ -1,204 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.UnaryOperator;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class ConfigList<T> implements Defaultable<List<?>>, List<T>, RandomAccess {
|
||||
private final Class<T> cls;
|
||||
private final List<T> list;
|
||||
|
||||
public ConfigList(Class<T> cls) {
|
||||
Objects.requireNonNull(cls);
|
||||
if (!Reflect.isSimpleType(cls)) {
|
||||
Reflect.checkDefaultConstructor(cls);
|
||||
}
|
||||
this.cls = cls;
|
||||
this.list = Collections.checkedList(new ArrayList<>(), cls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return list.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return list.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return list.contains(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return list.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return list.toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T1> T1[] toArray(T1[] a) {
|
||||
return list.toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(T t) {
|
||||
return list.add(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
return list.remove(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
return list.containsAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends T> c) {
|
||||
return list.addAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(int index, Collection<? extends T> c) {
|
||||
return list.addAll(index, c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
return list.removeAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
return list.retainAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
list.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(int index) {
|
||||
return list.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T set(int index, T element) {
|
||||
return list.set(index, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void add(int index, T element) {
|
||||
list.add(index, element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public T remove(int index) {
|
||||
return list.remove(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int indexOf(Object o) {
|
||||
return list.indexOf(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int lastIndexOf(Object o) {
|
||||
return list.lastIndexOf(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator<T> listIterator() {
|
||||
return list.listIterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListIterator<T> listIterator(int index) {
|
||||
return list.listIterator(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<T> subList(int fromIndex, int toIndex) {
|
||||
return list.subList(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceAll(UnaryOperator<T> operator) {
|
||||
list.replaceAll(operator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sort(Comparator<? super T> c) {
|
||||
list.sort(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spliterator<T> spliterator() {
|
||||
return list.spliterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeIf(Predicate<? super T> filter) {
|
||||
return list.removeIf(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<T> stream() {
|
||||
return list.stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<T> parallelStream() {
|
||||
return list.parallelStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(Consumer<? super T> action) {
|
||||
list.forEach(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ConfigList{" +
|
||||
"cls=" + cls +
|
||||
", list=" + list +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<?> toDefault() {
|
||||
if (Reflect.isSimpleType(cls)) {
|
||||
return new ArrayList<>(list);
|
||||
}
|
||||
List<Object> l = new ArrayList<>();
|
||||
for (Object item : list) {
|
||||
l.add(FieldMapper.instanceToMap(item));
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fromDefault(Object value) {
|
||||
clear();
|
||||
for (Object item : (List<?>) value) {
|
||||
Object instance = fromDefault(item, cls);
|
||||
add(cls.cast(instance));
|
||||
}
|
||||
}
|
||||
|
||||
List<T> getList() {
|
||||
return list;
|
||||
}
|
||||
}
|
@ -1,184 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
public final class ConfigMap<K, V> implements Defaultable<Map<K, ?>>, Map<K, V> {
|
||||
private final Class<K> keyClass;
|
||||
private final Class<V> valueClass;
|
||||
private final Map<K, V> map;
|
||||
|
||||
public ConfigMap(Class<K> keyClass, Class<V> valueClass) {
|
||||
Objects.requireNonNull(keyClass);
|
||||
Objects.requireNonNull(valueClass);
|
||||
checkSimpleTypeKey(keyClass);
|
||||
if (!Reflect.isSimpleType(valueClass)) {
|
||||
Reflect.checkDefaultConstructor(valueClass);
|
||||
}
|
||||
this.keyClass = keyClass;
|
||||
this.valueClass = valueClass;
|
||||
this.map = Collections.checkedMap(
|
||||
new LinkedHashMap<>(), keyClass, valueClass
|
||||
);
|
||||
}
|
||||
|
||||
private void checkSimpleTypeKey(Class<?> keyClass) {
|
||||
if (!Reflect.isSimpleType(keyClass)) {
|
||||
String msg = "Class " + keyClass.getSimpleName() + " is not a simple type.\n" +
|
||||
"Only simple types can be used as keys in a map.";
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return map.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return map.containsKey(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return map.containsValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V get(Object key) {
|
||||
return map.get(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V put(K key, V value) {
|
||||
return map.put(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V remove(Object key) {
|
||||
return map.remove(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void putAll(Map<? extends K, ? extends V> m) {
|
||||
map.putAll(m);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
map.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<K> keySet() {
|
||||
return map.keySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<V> values() {
|
||||
return map.values();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Entry<K, V>> entrySet() {
|
||||
return map.entrySet();
|
||||
}
|
||||
|
||||
@Override
|
||||
public V getOrDefault(Object key, V defaultValue) {
|
||||
return map.getOrDefault(key, defaultValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(BiConsumer<? super K, ? super V> action) {
|
||||
map.forEach(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
|
||||
map.replaceAll(function);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V putIfAbsent(K key, V value) {
|
||||
return map.putIfAbsent(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object key, Object value) {
|
||||
return map.remove(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean replace(K key, V oldValue, V newValue) {
|
||||
return map.replace(key, oldValue, newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V replace(K key, V value) {
|
||||
return map.replace(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
|
||||
return map.computeIfAbsent(key, mappingFunction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V computeIfPresent(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
|
||||
return map.computeIfPresent(key, remappingFunction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V compute(K key, BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
|
||||
return map.compute(key, remappingFunction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
|
||||
return map.merge(key, value, remappingFunction);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ConfigMap{" +
|
||||
"valueClass=" + valueClass +
|
||||
", map=" + map +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<K, ?> toDefault() {
|
||||
if (Reflect.isSimpleType(valueClass)) {
|
||||
return new LinkedHashMap<>(map);
|
||||
}
|
||||
Map<K, Object> m = new LinkedHashMap<>();
|
||||
for (Entry<K, V> entry : entrySet()) {
|
||||
Object mapped = FieldMapper.instanceToMap(entry.getValue());
|
||||
m.put(entry.getKey(), mapped);
|
||||
}
|
||||
return m;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fromDefault(Object value) {
|
||||
clear();
|
||||
for (Map.Entry<?, ?> entry : ((Map<?, ?>) value).entrySet()) {
|
||||
Object instance = fromDefault(entry.getValue(), valueClass);
|
||||
Reflect.checkType(entry.getKey(), keyClass);
|
||||
put(keyClass.cast(entry.getKey()), valueClass.cast(instance));
|
||||
}
|
||||
}
|
||||
|
||||
Map<K, V> getMap() {
|
||||
return map;
|
||||
}
|
||||
}
|
@ -1,20 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Scanner;
|
||||
|
||||
enum ConfigReader {
|
||||
;
|
||||
|
||||
static String read(Path path) throws IOException {
|
||||
try (Scanner scanner = new Scanner(path)) {
|
||||
scanner.useDelimiter("\\z");
|
||||
StringBuilder builder = new StringBuilder();
|
||||
while (scanner.hasNext()) {
|
||||
builder.append(scanner.next());
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,143 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
public final class ConfigSet<T> implements Defaultable<Set<?>>, Set<T> {
|
||||
private final Class<T> cls;
|
||||
private final Set<T> set;
|
||||
|
||||
public ConfigSet(Class<T> cls) {
|
||||
Objects.requireNonNull(cls);
|
||||
if (!Reflect.isSimpleType(cls)) {
|
||||
Reflect.checkDefaultConstructor(cls);
|
||||
}
|
||||
this.cls = cls;
|
||||
this.set = Collections.checkedSet(new LinkedHashSet<>(), cls);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return set.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return set.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean contains(Object o) {
|
||||
return set.contains(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<T> iterator() {
|
||||
return set.iterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(Consumer<? super T> action) {
|
||||
set.forEach(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object[] toArray() {
|
||||
return set.toArray();
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T1> T1[] toArray(T1[] a) {
|
||||
return set.toArray(a);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean add(T t) {
|
||||
return set.add(t);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean remove(Object o) {
|
||||
return set.remove(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean containsAll(Collection<?> c) {
|
||||
return set.containsAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean addAll(Collection<? extends T> c) {
|
||||
return set.addAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean retainAll(Collection<?> c) {
|
||||
return set.retainAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeAll(Collection<?> c) {
|
||||
return set.removeAll(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeIf(Predicate<? super T> filter) {
|
||||
return set.removeIf(filter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
set.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Spliterator<T> spliterator() {
|
||||
return set.spliterator();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<T> stream() {
|
||||
return set.stream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Stream<T> parallelStream() {
|
||||
return set.parallelStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ConfigSet{" +
|
||||
"cls=" + cls +
|
||||
", set=" + set +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<?> toDefault() {
|
||||
if (Reflect.isSimpleType(cls)) {
|
||||
return new LinkedHashSet<>(set);
|
||||
}
|
||||
Set<Object> s = new LinkedHashSet<>();
|
||||
for (Object item : set) {
|
||||
s.add(FieldMapper.instanceToMap(item));
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fromDefault(Object value) {
|
||||
clear();
|
||||
for (Object item : (Set<?>) value) {
|
||||
Object instance = fromDefault(item, cls);
|
||||
add(cls.cast(instance));
|
||||
}
|
||||
}
|
||||
|
||||
Set<T> getSet() {
|
||||
return set;
|
||||
}
|
||||
}
|
@ -1,16 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
enum ConfigWriter {
|
||||
;
|
||||
|
||||
static void write(Path path, String text) throws IOException {
|
||||
try (Writer writer = Files.newBufferedWriter(path)) {
|
||||
writer.write(text);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,263 +1,174 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.constructor.BaseConstructor;
|
||||
import org.yaml.snakeyaml.constructor.Constructor;
|
||||
import org.yaml.snakeyaml.parser.ParserException;
|
||||
import org.yaml.snakeyaml.representer.Representer;
|
||||
import org.yaml.snakeyaml.resolver.Resolver;
|
||||
import de.exlll.configlib.format.FieldNameFormatter;
|
||||
import de.exlll.configlib.format.FieldNameFormatters;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class Configuration {
|
||||
private final Path configPath;
|
||||
private final CommentAdder adder;
|
||||
private YamlSerializer serializer;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 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>> {
|
||||
/**
|
||||
* Constructs a new {@code Configuration} instance.
|
||||
* <p>
|
||||
* You can use {@link java.io.File#toPath()} get a {@link Path} object
|
||||
* from a {@link java.io.File}.
|
||||
*
|
||||
* @param configPath location of the configuration file
|
||||
* @throws NullPointerException if {@code configPath} is null
|
||||
* {@code Comments} object containing all class and field comments
|
||||
* of this configuration
|
||||
*/
|
||||
protected Configuration(Path configPath) {
|
||||
this.configPath = configPath;
|
||||
this.adder = new CommentAdder(new Comments(getClass()));
|
||||
}
|
||||
|
||||
private void initSerializer() {
|
||||
if (serializer == null) {
|
||||
this.serializer = new YamlSerializer(
|
||||
createConstructor(), createRepresenter(),
|
||||
createDumperOptions(), createResolver()
|
||||
);
|
||||
}
|
||||
}
|
||||
protected final Comments comments;
|
||||
private final Properties props;
|
||||
|
||||
/**
|
||||
* Loads {@code this} configuration from a configuration file. The file is
|
||||
* located at the path pointed to by the {@code Path} object used to create
|
||||
* {@code this} instance.
|
||||
* <p>
|
||||
* The values of the fields of this instance are updated as follows:<br>
|
||||
* For each non-{@code final}, non-{@code static} and non-{@code transient}
|
||||
* field of {@code this} configuration instance: <br>
|
||||
* - If the field's value is null, throw a {@code NullPointerException} <br>
|
||||
* - If the file contains the field's name, update the field's value with
|
||||
* the value from the file. Otherwise, keep the default value. <br>
|
||||
* This algorithm is applied recursively for any non-default field.
|
||||
* Constructs a new {@code Configuration} object.
|
||||
*
|
||||
* @throws ClassCastException if parsed Object is not a {@code Map}
|
||||
* @throws IOException if an I/O error occurs when loading the configuration file
|
||||
* @throws NullPointerException if a value of a field of this instance is null
|
||||
* @throws ParserException if invalid YAML
|
||||
* @param properties {@code Properties} used to configure this configuration
|
||||
* @throws NullPointerException if {@code properties} is null
|
||||
*/
|
||||
public final void load() throws IOException {
|
||||
Map<String, Object> deserializedMap = readAndDeserialize();
|
||||
FieldMapper.instanceFromMap(this, deserializedMap);
|
||||
postLoadHook();
|
||||
}
|
||||
|
||||
private Map<String, Object> readAndDeserialize() throws IOException {
|
||||
initSerializer();
|
||||
String yaml = ConfigReader.read(configPath);
|
||||
return serializer.deserialize(yaml);
|
||||
protected Configuration(Properties properties) {
|
||||
this.props = Objects.requireNonNull(properties);
|
||||
this.comments = Comments.ofClass(getClass());
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the comments of {@code this} class as well as the names,
|
||||
* values and comments of its non-{@code final}, non-{@code static}
|
||||
* and non-{@code transient} fields to a configuration file. If this
|
||||
* class uses versioning, the current version is saved, too.
|
||||
* <p>
|
||||
* The file used to save this configuration is located at the path pointed
|
||||
* to by the {@code Path} object used to create {@code this} instance.
|
||||
* <p>
|
||||
* The default algorithm used to save {@code this} configuration to a file
|
||||
* is as follows:<br>
|
||||
* <ol>
|
||||
* <li>If the file doesn't exist, it is created.</li>
|
||||
* <li>For each non-{@code final}, non-{@code static} and non-{@code transient}
|
||||
* field of {@code this} configuration instance:
|
||||
* <ul>
|
||||
* <li>If the file doesn't contain the field's name, the field's name and
|
||||
* value are added. Otherwise, the value is simply updated.</li>
|
||||
* </ul>
|
||||
* </li>
|
||||
* <li>If the file contains field names that don't match any name of a field
|
||||
* of this class, the file's field names together with their values are
|
||||
* removed from the file.</li>
|
||||
* <li>(only with versioning) The current version is updated.</li>
|
||||
* </ol>
|
||||
* The default behavior can be overridden using <i>versioning</i>.
|
||||
* Saves this {@code Configuration}.
|
||||
*
|
||||
* @throws ConfigException if a name clash between a field name and the version
|
||||
* field name occurs (can only happen if versioning is used)
|
||||
* @throws NoSuchFileException if the old version contains illegal file path characters
|
||||
* @throws IOException if an I/O error occurs when saving the configuration file
|
||||
* @throws ParserException if invalid YAML
|
||||
* @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() throws IOException {
|
||||
initSerializer();
|
||||
createParentDirectories();
|
||||
Map<String, Object> map = FieldMapper.instanceToMap(this);
|
||||
version(map);
|
||||
String serializedMap = serializer.serialize(map);
|
||||
ConfigWriter.write(configPath, adder.addComments(serializedMap));
|
||||
}
|
||||
|
||||
private void version(Map<String, Object> map) throws IOException {
|
||||
final Version version = Reflect.getVersion(getClass());
|
||||
|
||||
if (version == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String vfn = version.fieldName();
|
||||
if (map.containsKey(vfn)) {
|
||||
String msg = "Problem: Configuration '" + this + "' cannot be " +
|
||||
"saved because one its fields has the same name as the " +
|
||||
"version field: '" + vfn + "'.\nSolution: Rename the " +
|
||||
"field or use a different version field name.";
|
||||
throw new ConfigException(msg);
|
||||
public final void save() {
|
||||
try {
|
||||
preSave();
|
||||
Map<String, Object> map = FieldMapper.instanceToMap(this, props);
|
||||
getSource().saveConfiguration(getThis(), map);
|
||||
} catch (IOException e) {
|
||||
throw new ConfigurationStoreException(e);
|
||||
}
|
||||
map.put(vfn, version.version());
|
||||
version.updateStrategy().update(this, version);
|
||||
}
|
||||
|
||||
private void createParentDirectories() throws IOException {
|
||||
Files.createDirectories(configPath.getParent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads and saves {@code this} configuration.
|
||||
* <p>
|
||||
* This method first calls {@link #load()} and then {@link #save()}.
|
||||
* Loads this {@code Configuration}.
|
||||
*
|
||||
* @throws ClassCastException if parsed Object is not a {@code Map}
|
||||
* @throws IOException if an I/O error occurs when loading or saving the configuration file
|
||||
* @throws NullPointerException if a value of a field of this instance is null
|
||||
* @throws ParserException if invalid YAML
|
||||
* @see #load()
|
||||
* @see #save()
|
||||
* @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 loadAndSave() throws IOException {
|
||||
public final void load() {
|
||||
try {
|
||||
load();
|
||||
save();
|
||||
} catch (NoSuchFileException e) {
|
||||
postLoadHook();
|
||||
save();
|
||||
Map<String, Object> map = getSource().loadConfiguration(getThis());
|
||||
FieldMapper.instanceFromMap(this, map, props);
|
||||
postLoad();
|
||||
} catch (IOException e) {
|
||||
throw new ConfigurationStoreException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Protected method invoked after all fields have successfully been loaded.
|
||||
* <p>
|
||||
* The default implementation of this method does nothing. Subclasses may
|
||||
* override this method in order to execute some action after all fields
|
||||
* have successfully been loaded.
|
||||
* Returns the {@link ConfigurationSource} used for saving and loading this
|
||||
* {@code Configuration}.
|
||||
*
|
||||
* @return {@code ConfigurationSource} used for saving and loading
|
||||
*/
|
||||
protected void postLoadHook() {}
|
||||
protected abstract ConfigurationSource<C> getSource();
|
||||
|
||||
/**
|
||||
* Returns a {@link BaseConstructor} which is used to configure a
|
||||
* {@link Yaml} object.
|
||||
* <p>
|
||||
* Override this method to change the way the {@code Yaml} object is created.
|
||||
* <p>
|
||||
* This method may not return null.
|
||||
* Returns this {@code Configuration}.
|
||||
*
|
||||
* @return a {@code BaseConstructor} object
|
||||
* @see org.yaml.snakeyaml.constructor.BaseConstructor
|
||||
* @see #createRepresenter()
|
||||
* @see #createDumperOptions()
|
||||
* @see #createResolver()
|
||||
* @return this {@code Configuration}
|
||||
*/
|
||||
protected BaseConstructor createConstructor() {
|
||||
return new Constructor();
|
||||
}
|
||||
protected abstract C getThis();
|
||||
|
||||
/**
|
||||
* Returns a {@link Representer} which is used to configure a {@link Yaml}
|
||||
* object.
|
||||
* <p>
|
||||
* Override this method to change the way the {@code Yaml} object is created.
|
||||
* Hook that is executed right before this {@code Configuration} is saved.
|
||||
* <p>
|
||||
* This method may not return null.
|
||||
*
|
||||
* @return a {@code Representer} object
|
||||
* @see org.yaml.snakeyaml.representer.Representer
|
||||
* @see #createConstructor()
|
||||
* @see #createDumperOptions()
|
||||
* @see #createResolver()
|
||||
* The default implementation of this method does nothing.
|
||||
*/
|
||||
protected Representer createRepresenter() {
|
||||
return new Representer();
|
||||
}
|
||||
protected void preSave() {}
|
||||
|
||||
/**
|
||||
* Returns a {@link DumperOptions} object which is used to configure a
|
||||
* {@link Yaml} object.
|
||||
* <p>
|
||||
* Override this method to change the way the {@code Yaml} object is created.
|
||||
* Hook that is executed right after this {@code Configuration} has
|
||||
* successfully been loaded.
|
||||
* <p>
|
||||
* This method may not return null.
|
||||
*
|
||||
* @return a {@code DumperOptions} object
|
||||
* @see org.yaml.snakeyaml.DumperOptions
|
||||
* @see #createConstructor()
|
||||
* @see #createRepresenter()
|
||||
* @see #createResolver()
|
||||
* The default implementation of this method does nothing.
|
||||
*/
|
||||
protected DumperOptions createDumperOptions() {
|
||||
DumperOptions options = new DumperOptions();
|
||||
options.setIndent(2);
|
||||
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
return options;
|
||||
}
|
||||
protected void postLoad() {}
|
||||
|
||||
/**
|
||||
* Returns a {@link Resolver} which is used to configure a {@link Yaml} object.
|
||||
* <p>
|
||||
* Override this method to change the way the {@code Yaml} object is created.
|
||||
* <p>
|
||||
* This method may not return null.
|
||||
*
|
||||
* @return a {@code Resolver} object
|
||||
* @see org.yaml.snakeyaml.resolver.Resolver
|
||||
* @see #createConstructor()
|
||||
* @see #createRepresenter()
|
||||
* @see #createDumperOptions()
|
||||
* Instances of a {@code Properties} class are used to configure different
|
||||
* aspects of a configuration.
|
||||
*/
|
||||
protected Resolver createResolver() {
|
||||
return new Resolver();
|
||||
}
|
||||
protected static class Properties {
|
||||
private final FieldNameFormatter formatter;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
final String currentFileVersion() throws IOException {
|
||||
final Version version = Reflect.getVersion(getClass());
|
||||
return (version == null) ? null : readCurrentFileVersion(version);
|
||||
}
|
||||
static Builder<?> builder() {
|
||||
return new Builder() {
|
||||
@Override
|
||||
protected Builder<?> getThis() {
|
||||
return this;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private String readCurrentFileVersion(Version version) throws IOException {
|
||||
try {
|
||||
final Map<String, Object> map = readAndDeserialize();
|
||||
return (String) map.get(version.fieldName());
|
||||
} catch (NoSuchFileException ignored) {
|
||||
/* there is no file version if the file doesn't exist */
|
||||
return null;
|
||||
/**
|
||||
* Returns the {@code FieldNameFormatter} of a configuration.
|
||||
*
|
||||
* @return {@code FieldNameFormatter} of a configuration
|
||||
*/
|
||||
public final FieldNameFormatter getFormatter() {
|
||||
return formatter;
|
||||
}
|
||||
}
|
||||
|
||||
final Path getPath() {
|
||||
return configPath;
|
||||
/**
|
||||
* 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;
|
||||
|
||||
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 ist null}
|
||||
*/
|
||||
public final B setFormatter(FieldNameFormatter formatter) {
|
||||
this.formatter = Objects.requireNonNull(formatter);
|
||||
return getThis();
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new {@code Properties} instance using the values set.
|
||||
*
|
||||
* @return new {@code Properties} instance
|
||||
*/
|
||||
public Properties build() {
|
||||
return new Properties(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,16 @@
|
||||
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.
|
||||
*/
|
||||
public final class ConfigurationException extends RuntimeException {
|
||||
ConfigurationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
ConfigurationException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
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;
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,196 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
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 <F> the type of the field value
|
||||
* @param <T> the type of the converted value
|
||||
*/
|
||||
public interface Converter<F, 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} will be thrown.
|
||||
*
|
||||
* @param element field value that is converted
|
||||
* @param info information about the current conversion step
|
||||
* @return converted field value
|
||||
*/
|
||||
T convertTo(F 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
|
||||
*/
|
||||
F 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 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 ConversionInfo(Field field, Object instance, Object mapValue,
|
||||
Configuration.Properties props) {
|
||||
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 = props;
|
||||
this.elementType = elementType(field);
|
||||
}
|
||||
|
||||
private static Class<?> elementType(Field field) {
|
||||
if (field.isAnnotationPresent(ElementType.class)) {
|
||||
ElementType et = field.getAnnotation(ElementType.class);
|
||||
return et.value();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static ConversionInfo of(Field field, Object instance,
|
||||
Configuration.Properties props) {
|
||||
return new ConversionInfo(field, instance, null, props);
|
||||
}
|
||||
|
||||
static ConversionInfo of(Field field, Object instance, Object mapValue,
|
||||
Configuration.Properties props) {
|
||||
return new ConversionInfo(field, instance, mapValue, props);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,529 @@
|
||||
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
|
||||
) {
|
||||
return o -> selectNonSimpleConverter(element.getClass(), info)
|
||||
.convertTo(o, info);
|
||||
}
|
||||
|
||||
private static Function<Object, ?> createFromConversionFunction(
|
||||
Object element, ConversionInfo info
|
||||
) {
|
||||
if ((element instanceof Map<?, ?>) && isTypeMap((Map<?, ?>) element)) {
|
||||
return o -> {
|
||||
Map<String, Object> map = toTypeMap(o, null);
|
||||
Object inst = Reflect.newInstance(info.getElementType());
|
||||
FieldMapper.instanceFromMap(inst, map, info.getProperties());
|
||||
return inst;
|
||||
};
|
||||
} else {
|
||||
return o -> selectNonSimpleConverter(element.getClass(), info)
|
||||
.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.getProperties());
|
||||
}
|
||||
|
||||
@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.getProperties());
|
||||
return newInstance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preConvertFrom(ConversionInfo info) {
|
||||
checkTypeHasNoArgsConstructor(info);
|
||||
checkTypeIsConfigurationElement(info.getValueType(), info.getFieldName());
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
interface Defaultable<T> {
|
||||
T toDefault();
|
||||
|
||||
void fromDefault(Object value);
|
||||
|
||||
default Object fromDefault(final Object instance, Class<?> cls) {
|
||||
Object newInstance = instance;
|
||||
if (!Reflect.isSimpleType(cls)) {
|
||||
Reflect.checkType(instance, Map.class);
|
||||
Reflect.checkMapEntries((Map<?, ?>) instance, String.class, Object.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, ?> map = (Map<String, ?>) instance;
|
||||
newInstance = Reflect.newInstance(cls);
|
||||
FieldMapper.instanceFromMap(newInstance, map);
|
||||
}
|
||||
Reflect.checkType(newInstance, cls);
|
||||
return newInstance;
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
enum FieldFilter implements Predicate<Field> {
|
||||
INSTANCE;
|
||||
|
||||
/**
|
||||
* Tests if a field is not final, static, synthetic and transient.
|
||||
*
|
||||
* @param field Field that is tested
|
||||
* @return true if {@code field} is not final, static, synthetic or transient
|
||||
*/
|
||||
@Override
|
||||
public boolean test(Field field) {
|
||||
int mods = field.getModifiers();
|
||||
boolean fst = Modifier.isFinal(mods) ||
|
||||
Modifier.isStatic(mods) ||
|
||||
Modifier.isTransient(mods);
|
||||
return !fst && !field.isSynthetic();
|
||||
}
|
||||
}
|
@ -1,75 +1,105 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import de.exlll.configlib.Converter.ConversionInfo;
|
||||
import de.exlll.configlib.format.FieldNameFormatter;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static de.exlll.configlib.Validator.*;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
enum FieldMapper {
|
||||
;
|
||||
|
||||
static Map<String, Object> instanceToMap(Object instance) {
|
||||
static Map<String, Object> instanceToMap(
|
||||
Object inst, Configuration.Properties props
|
||||
) {
|
||||
Map<String, Object> map = new LinkedHashMap<>();
|
||||
|
||||
FilteredFields ff = FilteredFields.of(instance.getClass());
|
||||
|
||||
for (Field field : ff) {
|
||||
Object value = Reflect.getValue(field, instance);
|
||||
checkNull(field, value);
|
||||
value = toSerializableObject(value);
|
||||
map.put(field.getName(), value);
|
||||
for (Field field : FieldFilter.filterFields(inst.getClass())) {
|
||||
Object val = toConvertibleObject(field, inst, props);
|
||||
FieldNameFormatter fnf = props.getFormatter();
|
||||
String fn = fnf.fromFieldName(field.getName());
|
||||
map.put(fn, val);
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
static Object toSerializableObject(Object input) {
|
||||
if (input instanceof Defaultable<?>) {
|
||||
return ((Defaultable<?>) input).toDefault();
|
||||
} else if (Reflect.isDefault(input.getClass())) {
|
||||
return input;
|
||||
} else {
|
||||
return instanceToMap(input);
|
||||
}
|
||||
private static Object toConvertibleObject(
|
||||
Field field, Object instance, Configuration.Properties props
|
||||
) {
|
||||
checkDefaultValueNull(field, instance);
|
||||
ConversionInfo info = ConversionInfo.of(field, instance, props);
|
||||
checkFieldWithElementTypeIsContainer(info);
|
||||
Object converted = Converters.convertTo(info);
|
||||
checkConverterNotReturnsNull(converted, info);
|
||||
return converted;
|
||||
}
|
||||
|
||||
static void instanceFromMap(Object instance, Map<String, ?> map) {
|
||||
FilteredFields ff = FilteredFields.of(instance.getClass());
|
||||
|
||||
for (Field field : ff) {
|
||||
Object value = map.get(field.getName());
|
||||
fromSerializedObject(field, instance, value);
|
||||
static void instanceFromMap(
|
||||
Object inst, Map<String, Object> instMap,
|
||||
Configuration.Properties props
|
||||
) {
|
||||
for (Field field : FieldFilter.filterFields(inst.getClass())) {
|
||||
FieldNameFormatter fnf = props.getFormatter();
|
||||
String fn = fnf.fromFieldName(field.getName());
|
||||
Object mapValue = instMap.get(fn);
|
||||
if (mapValue != null) {
|
||||
fromConvertedObject(field, inst, mapValue, props);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void fromSerializedObject(Field field, Object instance, Object serialized) {
|
||||
if (serialized == null) {
|
||||
return; // keep default value
|
||||
private static void fromConvertedObject(
|
||||
Field field, Object instance, Object mapValue,
|
||||
Configuration.Properties props
|
||||
) {
|
||||
checkDefaultValueNull(field, instance);
|
||||
ConversionInfo info = ConversionInfo.of(field, instance, mapValue, props);
|
||||
checkFieldWithElementTypeIsContainer(info);
|
||||
Object convert = Converters.convertFrom(info);
|
||||
|
||||
if (convert == null) {
|
||||
return;
|
||||
}
|
||||
Object fieldValue = Reflect.getValue(field, instance);
|
||||
checkNull(field, fieldValue);
|
||||
if (fieldValue instanceof Defaultable<?>) {
|
||||
((Defaultable<?>) fieldValue).fromDefault(serialized);
|
||||
} else if (Reflect.isDefault(field.getType())) {
|
||||
Reflect.setValue(field, instance, serialized);
|
||||
} else {
|
||||
instanceFromMap(fieldValue, castToMap(serialized));
|
||||
|
||||
if (Reflect.isContainerType(info.getFieldType())) {
|
||||
checkFieldTypeAssignableFrom(convert.getClass(), info);
|
||||
}
|
||||
|
||||
Reflect.setValue(field, instance, convert);
|
||||
}
|
||||
|
||||
private static void checkNull(Field field, Object o) {
|
||||
if (o == null) {
|
||||
String msg = "The value of field " + field.getName() + " is null.\n" +
|
||||
"Please assign a non-null default value or remove this field.";
|
||||
throw new NullPointerException(msg);
|
||||
}
|
||||
private static void checkDefaultValueNull(Field field, Object instance) {
|
||||
Object val = Reflect.getValue(field, instance);
|
||||
checkNotNull(val, field.getName());
|
||||
}
|
||||
|
||||
enum FieldFilter implements Predicate<Field> {
|
||||
DEFAULT;
|
||||
|
||||
private static Map<String, Object> castToMap(Object mapObject) {
|
||||
Reflect.checkType(mapObject, Map.class);
|
||||
Reflect.checkMapEntries((Map<?, ?>) mapObject, String.class, Object.class);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> m = (Map<String, Object>) mapObject;
|
||||
return m;
|
||||
static List<Field> filterFields(Class<?> cls) {
|
||||
Field[] fields = cls.getDeclaredFields();
|
||||
return Arrays.stream(fields)
|
||||
.filter(DEFAULT)
|
||||
.collect(toList());
|
||||
}
|
||||
|
||||
@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,32 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Arrays;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
final class FilteredFields implements Iterable<Field> {
|
||||
private final List<Field> filteredFields;
|
||||
|
||||
FilteredFields(Field[] fields, Predicate<Field> filter) {
|
||||
Objects.requireNonNull(fields);
|
||||
Objects.requireNonNull(filter);
|
||||
|
||||
this.filteredFields = Arrays.stream(fields)
|
||||
.filter(filter)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
static FilteredFields of(Class<?> cls) {
|
||||
Field[] fields = cls.getDeclaredFields();
|
||||
return new FilteredFields(fields, FieldFilter.INSTANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Field> iterator() {
|
||||
return filteredFields.iterator();
|
||||
}
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
enum TypeConverter {
|
||||
;
|
||||
|
||||
static Object convertValue(Class<?> target, Object value) {
|
||||
Objects.requireNonNull(target);
|
||||
Objects.requireNonNull(value);
|
||||
|
||||
if (target == value.getClass()) {
|
||||
return value;
|
||||
}
|
||||
if (value instanceof Number) {
|
||||
return convertNumber(target, (Number) value);
|
||||
}
|
||||
if (value instanceof String) {
|
||||
return convertString((String) value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static Character convertString(String s) {
|
||||
int len = s.length();
|
||||
if (len != 1) {
|
||||
String msg = "String '" + s + "' cannot be converted to a character." +
|
||||
" Length of s: " + len + " Required length: 1";
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
return s.charAt(0);
|
||||
}
|
||||
|
||||
static Number convertNumber(Class<?> targetType, Number number) {
|
||||
if (isByteClass(targetType)) {
|
||||
return number.byteValue();
|
||||
} else if (isShortClass(targetType)) {
|
||||
return number.shortValue();
|
||||
} else if (isIntegerClass(targetType)) {
|
||||
return number.intValue();
|
||||
} else if (isLongClass(targetType)) {
|
||||
return number.longValue();
|
||||
} else if (isFloatClass(targetType)) {
|
||||
return number.floatValue();
|
||||
} else if (isDoubleClass(targetType)) {
|
||||
return number.doubleValue();
|
||||
} else {
|
||||
String msg = "Number cannot be converted to target type " +
|
||||
"'" + targetType + "'";
|
||||
throw new IllegalArgumentException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
static boolean isBooleanClass(Class<?> cls) {
|
||||
return (cls == Boolean.class) || (cls == Boolean.TYPE);
|
||||
}
|
||||
|
||||
static boolean isByteClass(Class<?> cls) {
|
||||
return (cls == Byte.class) || (cls == Byte.TYPE);
|
||||
}
|
||||
|
||||
static boolean isShortClass(Class<?> cls) {
|
||||
return (cls == Short.class) || (cls == Short.TYPE);
|
||||
}
|
||||
|
||||
static boolean isIntegerClass(Class<?> cls) {
|
||||
return (cls == Integer.class) || (cls == Integer.TYPE);
|
||||
}
|
||||
|
||||
static boolean isLongClass(Class<?> cls) {
|
||||
return (cls == Long.class) || (cls == Long.TYPE);
|
||||
}
|
||||
|
||||
static boolean isFloatClass(Class<?> cls) {
|
||||
return (cls == Float.class) || (cls == Float.TYPE);
|
||||
}
|
||||
|
||||
static boolean isDoubleClass(Class<?> cls) {
|
||||
return (cls == Double.class) || (cls == Double.TYPE);
|
||||
}
|
||||
|
||||
static boolean isCharacterClass(Class<?> cls) {
|
||||
return (cls == Character.class) || (cls == Character.TYPE);
|
||||
}
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.*;
|
||||
|
||||
/**
|
||||
* The constants of this enumerated type describe strategies used to update
|
||||
* different versions of configuration files. The strategies are only applied
|
||||
* if a version change is detected.
|
||||
*/
|
||||
public enum UpdateStrategy {
|
||||
/**
|
||||
* Updates the configuration file using the default strategy described at
|
||||
* {@link Configuration#save()}.
|
||||
*/
|
||||
DEFAULT {
|
||||
@Override
|
||||
void update(Configuration config, Version version) {}
|
||||
},
|
||||
/**
|
||||
* Updates the configuration file using the {@link #DEFAULT} strategy.
|
||||
* Before the configuration is updated a copy of its current version is
|
||||
* saved. If the configuration uses versioning for the first time, the
|
||||
* copy is named "<filename>-old". Otherwise, the old version is
|
||||
* appended to the file name: "<filename>-v<old version>".
|
||||
*
|
||||
* @see UpdateStrategy#DEFAULT
|
||||
*/
|
||||
DEFAULT_RENAME {
|
||||
@Override
|
||||
void update(Configuration config, Version version) throws IOException {
|
||||
final Path path = config.getPath();
|
||||
|
||||
if (!Files.exists(path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final String fileVersion = config.currentFileVersion();
|
||||
if (!version.version().equals(fileVersion)) {
|
||||
final FileSystem fs = path.getFileSystem();
|
||||
final String v = (fileVersion == null) ? "-old" : "-v" + fileVersion;
|
||||
final String fn = path.toString() + v;
|
||||
Files.move(path, fs.getPath(fn), StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
abstract void update(Configuration config, Version version)
|
||||
throws IOException;
|
||||
}
|
@ -0,0 +1,334 @@
|
||||
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<?> convClass, String fn) {
|
||||
if (!Reflect.hasNoArgConstructor(convClass)) {
|
||||
String msg = "Converter '" + convClass.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 boolean isTypeMap(Map<?, ?> map) {
|
||||
return map.entrySet().stream().allMatch(entry -> {
|
||||
Class<?> keyCls = entry.getKey().getClass();
|
||||
Class<?> valCls = entry.getValue().getClass();
|
||||
return (String.class == keyCls) && Reflect.isSimpleType(valCls);
|
||||
});
|
||||
}
|
||||
|
||||
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 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,45 +0,0 @@
|
||||
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 {@link Configuration} has a version.
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Version {
|
||||
/**
|
||||
* Returns the current version of a configuration.
|
||||
*
|
||||
* @return current configuration version
|
||||
*/
|
||||
String version() default "1.0.0";
|
||||
|
||||
/**
|
||||
* Returns the name of the version field.
|
||||
*
|
||||
* @return name of the version field
|
||||
*/
|
||||
String fieldName() default "version";
|
||||
|
||||
/**
|
||||
* Returns the comments describing the version field.
|
||||
*
|
||||
* @return comments describing the version field
|
||||
*/
|
||||
String[] fieldComments() default {
|
||||
"", /* empty line */
|
||||
"The version of this configuration - DO NOT CHANGE!"
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns an {@link UpdateStrategy} describing the actions applied to different
|
||||
* versions of a configuration file when a version change is detected.
|
||||
*
|
||||
* @return {@code UpdateStrategy} applied to a configuration file
|
||||
*/
|
||||
UpdateStrategy updateStrategy() default UpdateStrategy.DEFAULT;
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.constructor.BaseConstructor;
|
||||
import org.yaml.snakeyaml.parser.ParserException;
|
||||
import org.yaml.snakeyaml.representer.Representer;
|
||||
import org.yaml.snakeyaml.resolver.Resolver;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
final class YamlSerializer {
|
||||
private final Yaml yaml;
|
||||
|
||||
/**
|
||||
* @param baseConstructor BaseConstructor which is used to configure the {@code Yaml} object.
|
||||
* @param representer Representer which is used to configure the {@code Yaml} object.
|
||||
* @param dumperOptions DumperOptions which is used to configure the {@code Yaml} object.
|
||||
* @param resolver Resolver which is used to configure the {@code Yaml} object.
|
||||
* @see org.yaml.snakeyaml.constructor.BaseConstructor
|
||||
* @see org.yaml.snakeyaml.representer.Representer
|
||||
* @see org.yaml.snakeyaml.DumperOptions
|
||||
* @see org.yaml.snakeyaml.resolver.Resolver
|
||||
*/
|
||||
YamlSerializer(BaseConstructor baseConstructor,
|
||||
Representer representer,
|
||||
DumperOptions dumperOptions,
|
||||
Resolver resolver) {
|
||||
Objects.requireNonNull(baseConstructor);
|
||||
Objects.requireNonNull(representer);
|
||||
Objects.requireNonNull(dumperOptions);
|
||||
Objects.requireNonNull(resolver);
|
||||
yaml = new Yaml(baseConstructor, representer, dumperOptions, resolver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes a Map.
|
||||
*
|
||||
* @param map Map to serialize
|
||||
* @return a serialized representation of the Map
|
||||
* @throws NullPointerException if {@code map} is null.
|
||||
*/
|
||||
String serialize(Map<String, ?> map) {
|
||||
return yaml.dump(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserializes a serialized Map.
|
||||
*
|
||||
* @param serializedMap a serialized YAML representation of a Map
|
||||
* @return deserialized Map
|
||||
* @throws ClassCastException if {@code serializedMap} doesn't represent a Map
|
||||
* @throws NullPointerException if {@code serializedMap} is null
|
||||
* @throws ParserException if {@code serializedMap} is invalid YAML
|
||||
*/
|
||||
Map<String, Object> deserialize(String serializedMap) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> map = (Map<String, Object>) yaml.load(serializedMap);
|
||||
return map;
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
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 {}
|
@ -0,0 +1,18 @@
|
||||
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();
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package de.exlll.configlib.annotation;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Indicates the of elements a {@code Collection} or {@code Map} contains.
|
||||
* <p>
|
||||
* This annotation must be used if element type is not simple.
|
||||
*/
|
||||
@Target(java.lang.annotation.ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ElementType {
|
||||
Class<?> value();
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
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 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 {}
|
@ -0,0 +1,46 @@
|
||||
package de.exlll.configlib.configs.yaml;
|
||||
|
||||
import de.exlll.configlib.Comments;
|
||||
|
||||
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() {
|
||||
Map<String, List<String>> fieldComments = comments.getFieldComments();
|
||||
return fieldComments.entrySet().stream()
|
||||
.map(this::toStringCommentEntry)
|
||||
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
}
|
||||
|
||||
private Map.Entry<String, String> toStringCommentEntry(
|
||||
Map.Entry<String, List<String>> entry
|
||||
) {
|
||||
String fieldComments = commentListToString(entry.getValue());
|
||||
return Map.entry(entry.getKey(), 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;
|
||||
}
|
||||
}
|
@ -0,0 +1,212 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,135 @@
|
||||
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.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 = List.of(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();
|
||||
for (Map.Entry<String, String> entry : map.entrySet()) {
|
||||
String prefix = entry.getKey() + ":";
|
||||
if (dumpLine.startsWith(prefix)) {
|
||||
builder.append(entry.getValue()).append('\n');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
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);
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
import de.exlll.configlib.*;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Suite;
|
||||
|
||||
@RunWith(Suite.class)
|
||||
@Suite.SuiteClasses({
|
||||
ConfigurationTest.class,
|
||||
ConfigReadWriteTest.class,
|
||||
CommentAdderTest.class,
|
||||
CommentsTest.class,
|
||||
ConfigListTest.class,
|
||||
ConfigSetTest.class,
|
||||
ConfigMapTest.class,
|
||||
FieldFilterTest.class,
|
||||
FieldMapperTest.class,
|
||||
FilteredFieldsTest.class,
|
||||
ReflectTest.class,
|
||||
TypeConverterTest.class,
|
||||
VersionTest.class,
|
||||
YamlSerializerTest.class
|
||||
})
|
||||
public class ConfigLibTestSuite {
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class CommentAdderTest {
|
||||
@Test
|
||||
public void adderAddsComments() throws Exception {
|
||||
String originalText = "i: 0\n" +
|
||||
"j: 0\n" +
|
||||
"k: 0\n";
|
||||
String commentedText = "# a\n" +
|
||||
"# b\n\n" +
|
||||
"# c\n" +
|
||||
"i: 0\n" +
|
||||
"# d\n" +
|
||||
"# e\n" +
|
||||
"j: 0\n" +
|
||||
"k: 0\n";
|
||||
Comments comments = new Comments(CommentsTest.TestClass.class);
|
||||
CommentAdder adder = new CommentAdder(comments);
|
||||
assertThat(adder.addComments(originalText), is(commentedText));
|
||||
}
|
||||
}
|
@ -1,31 +1,55 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import org.junit.Test;
|
||||
import de.exlll.configlib.annotation.Comment;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class CommentsTest {
|
||||
@Comment({"a", "b"})
|
||||
public static final class TestClass {
|
||||
@Comment("c")
|
||||
private int i;
|
||||
@Comment({"d", "e"})
|
||||
private int j;
|
||||
private int k;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getCommentsReturnsComments() throws Exception {
|
||||
Comments comments = new Comments(TestClass.class);
|
||||
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());
|
||||
|
||||
assertThat(comments.getClassComments(), is(Arrays.asList("a", "b")));
|
||||
comments = Comments.ofClass(B.class);
|
||||
assertThat(comments.getClassComments(), is(List.of("B")));
|
||||
assertThat(comments.getFieldComments().entrySet(), empty());
|
||||
|
||||
comments = Comments.ofClass(C.class);
|
||||
assertThat(comments.getClassComments(), is(List.of("C", "D")));
|
||||
assertThat(comments.getFieldComments().entrySet(), empty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void fieldCommentsAdded() {
|
||||
class A {
|
||||
int a;
|
||||
@Comment("b")
|
||||
int b;
|
||||
@Comment({"c", "d"})
|
||||
int c;
|
||||
}
|
||||
|
||||
assertThat(comments.getFieldComments().get("i"), is(Collections.singletonList("c")));
|
||||
assertThat(comments.getFieldComments().get("j"), is(Arrays.asList("d", "e")));
|
||||
assertThat(comments.getFieldComments().get("k"), nullValue());
|
||||
Comments comments = Comments.ofClass(A.class);
|
||||
assertThat(comments.getClassComments(), empty());
|
||||
assertThat(comments.getFieldComments(), is(Map.of(
|
||||
"b", List.of("b"),
|
||||
"c", List.of("c", "d")
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,82 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import de.exlll.configlib.classes.ConfigTypeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static de.exlll.configlib.classes.ConfigTypeClass.A;
|
||||
import static de.exlll.configlib.classes.ConfigTypeClass.from;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class ConfigListTest {
|
||||
@Rule
|
||||
public ExpectedException expectedException = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void constructorRequiresNonNullClass() throws Exception {
|
||||
expectedException.expect(NullPointerException.class);
|
||||
new ConfigList<>(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorRequiresClassWithDefaultConstructor() throws Exception {
|
||||
new ConfigList<>(String.class);
|
||||
|
||||
expectedException.expect(IllegalArgumentException.class);
|
||||
expectedException.expectMessage("Class ConfigList doesn't have a default constructor.");
|
||||
new ConfigList<>(ConfigList.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toDefaultReturnsArrayList() throws Exception {
|
||||
List<?> list = new ConfigList<>(String.class).toDefault();
|
||||
assertThat(list, instanceOf(ArrayList.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toDefaultReturnsSimpleTypes() throws Exception {
|
||||
ConfigTypeClass c = new ConfigTypeClass();
|
||||
List<?> l = c.configListSimple.toDefault();
|
||||
assertThat(l, is(c.configListSimple.getList()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toDefaultReturnsSerializedObjects() throws Exception {
|
||||
ConfigTypeClass c = new ConfigTypeClass();
|
||||
List<?> l = c.configList.toDefault();
|
||||
for (int i = 0; i < c.configList.size(); i++) {
|
||||
Object mapped = FieldMapper.instanceToMap(c.configList.get(i));
|
||||
assertThat(mapped, is(l.get(i)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromDefaultAddsSimpleTypes() throws Exception {
|
||||
List<String> list = Arrays.asList("a", "b");
|
||||
ConfigList<String> configList = new ConfigList<>(String.class);
|
||||
configList.fromDefault(list);
|
||||
assertThat(configList.getList(), is(list));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromDefaultAddsDeserializedObjects() throws Exception {
|
||||
A a = from("a");
|
||||
A b = from("b");
|
||||
Map<String, ?> map1 = FieldMapper.instanceToMap(a);
|
||||
Map<String, ?> map2 = FieldMapper.instanceToMap(b);
|
||||
|
||||
List<Map<String, ?>> list = Arrays.asList(map1, map2);
|
||||
ConfigList<A> configList = new ConfigList<>(A.class);
|
||||
configList.fromDefault(list);
|
||||
|
||||
assertThat(configList.getList(), is(Arrays.asList(a, b)));
|
||||
}
|
||||
}
|
@ -1,103 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import de.exlll.configlib.classes.ConfigTypeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static de.exlll.configlib.classes.ConfigTypeClass.*;
|
||||
import static de.exlll.configlib.classes.ConfigTypeClass.from;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class ConfigMapTest {
|
||||
@Rule
|
||||
public ExpectedException expectedException = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void constructorRequiresNonNullKeyClass() throws Exception {
|
||||
expectedException.expect(NullPointerException.class);
|
||||
new ConfigMap<>(null, Integer.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorRequiresNonNullValueClass() throws Exception {
|
||||
expectedException.expect(NullPointerException.class);
|
||||
new ConfigMap<>(String.class, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorRequiresValueClassWithDefaultConstructor() throws Exception {
|
||||
new ConfigMap<>(String.class, String.class);
|
||||
|
||||
expectedException.expect(IllegalArgumentException.class);
|
||||
expectedException.expectMessage("Class ConfigMap doesn't have a default constructor.");
|
||||
new ConfigMap<>(String.class, ConfigMap.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorRequiresKeyClassWithSimpleType() throws Exception {
|
||||
new ConfigMap<>(String.class, ConfigListTest.class);
|
||||
|
||||
String msg = "Class " + Map.class.getSimpleName() + " is not a simple type.\n" +
|
||||
"Only simple types can be used as keys in a map.";
|
||||
expectedException.expect(IllegalArgumentException.class);
|
||||
expectedException.expectMessage(msg);
|
||||
new ConfigMap<>(Map.class, ConfigListTest.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toDefaultReturnsLinkedHashMap() throws Exception {
|
||||
Map<String, ?> newMap = new ConfigMap<>(String.class, Integer.class).toDefault();
|
||||
assertThat(newMap, instanceOf(LinkedHashMap.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toDefaultReturnsSimpleTypes() throws Exception {
|
||||
ConfigTypeClass c = new ConfigTypeClass();
|
||||
Map<String, ?> m = c.configMapSimple.toDefault();
|
||||
assertThat(m, is(c.configMapSimple.getMap()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toDefaultReturnsSerializedObjects() throws Exception {
|
||||
ConfigTypeClass c = new ConfigTypeClass();
|
||||
Map<String, ?> s = c.configMap.toDefault();
|
||||
for (Map.Entry<String, ConfigTypeClass.A> entry : c.configMap.entrySet()) {
|
||||
Object mapped = FieldMapper.instanceToMap(entry.getValue());
|
||||
assertThat(mapped, is(s.get(entry.getKey())));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromDefaultAddsSimpleTypes() throws Exception {
|
||||
Map<String, Integer> map = new HashMap<>();
|
||||
map.put("a", 1);
|
||||
map.put("b", 2);
|
||||
ConfigMap<String, Integer> configMap = new ConfigMap<>(String.class, Integer.class);
|
||||
configMap.fromDefault(map);
|
||||
assertThat(configMap.getMap(), is(map));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromDefaultAddsDeserializedObjects() throws Exception {
|
||||
A a = from("a");
|
||||
A b = from("b");
|
||||
Map<String, ?> map1 = FieldMapper.instanceToMap(a);
|
||||
Map<String, ?> map2 = FieldMapper.instanceToMap(b);
|
||||
|
||||
Map<String, Map<String, ?>> map = new HashMap<>();
|
||||
map.put("a", map1);
|
||||
map.put("b", map2);
|
||||
ConfigMap<String, A> configMap = new ConfigMap<>(String.class, A.class);
|
||||
configMap.fromDefault(map);
|
||||
|
||||
Map<String, A> newMap = new HashMap<>();
|
||||
newMap.put("a", a);
|
||||
newMap.put("b", b);
|
||||
assertThat(configMap.getMap(), is(newMap));
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import com.google.common.jimfs.Jimfs;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class ConfigReadWriteTest {
|
||||
|
||||
@Test
|
||||
public void readWrite() throws Exception {
|
||||
FileSystem fs = Jimfs.newFileSystem();
|
||||
Path p = fs.getPath("/path/p");
|
||||
Files.createDirectories(p.getParent());
|
||||
|
||||
String text = "Hello\nWorld\n";
|
||||
ConfigWriter.write(p, text);
|
||||
|
||||
String read = ConfigReader.read(p);
|
||||
assertThat(read, is(text));
|
||||
|
||||
fs.close();
|
||||
}
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import de.exlll.configlib.classes.ConfigTypeClass;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import static de.exlll.configlib.classes.ConfigTypeClass.*;
|
||||
import static de.exlll.configlib.classes.ConfigTypeClass.from;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class ConfigSetTest {
|
||||
@Rule
|
||||
public ExpectedException expectedException = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void constructorRequiresNonNullClass() throws Exception {
|
||||
expectedException.expect(NullPointerException.class);
|
||||
new ConfigSet<>(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorRequiresClassWithDefaultConstructor() throws Exception {
|
||||
new ConfigSet<>(String.class);
|
||||
|
||||
expectedException.expect(IllegalArgumentException.class);
|
||||
expectedException.expectMessage("Class ConfigSet doesn't have a default constructor.");
|
||||
new ConfigSet<>(ConfigSet.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toDefaultReturnsLinkedHashSet() throws Exception {
|
||||
Set<?> set = new ConfigSet<>(String.class).toDefault();
|
||||
assertThat(set, instanceOf(LinkedHashSet.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toDefaultReturnsSimpleTypes() throws Exception {
|
||||
ConfigTypeClass c = new ConfigTypeClass();
|
||||
Set<?> s = c.configSetSimple.toDefault();
|
||||
assertThat(s, is(c.configSetSimple.getSet()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toDefaultReturnsSerializedObjects() throws Exception {
|
||||
ConfigTypeClass c = new ConfigTypeClass();
|
||||
Set<?> s = c.configSet.toDefault();
|
||||
for (A a : c.configSet) {
|
||||
Object mapped = FieldMapper.instanceToMap(a);
|
||||
assertThat(s, contains(mapped));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromDefaultAddsSimpleTypes() throws Exception {
|
||||
Set<String> set = new HashSet<>(Arrays.asList("a", "b"));
|
||||
ConfigSet<String> configSet = new ConfigSet<>(String.class);
|
||||
configSet.fromDefault(set);
|
||||
assertThat(configSet.getSet(), is(set));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromDefaultAddsDeserializedObjects() throws Exception {
|
||||
A a = from("a");
|
||||
A b = from("b");
|
||||
Map<String, ?> map1 = FieldMapper.instanceToMap(a);
|
||||
Map<String, ?> map2 = FieldMapper.instanceToMap(b);
|
||||
|
||||
Set<Map<String, ?>> set = new HashSet<>(Arrays.asList(map1, map2));
|
||||
ConfigSet<A> configSet = new ConfigSet<>(A.class);
|
||||
configSet.fromDefault(set);
|
||||
|
||||
assertThat(configSet.getSet(), is(new HashSet<>(Arrays.asList(a, b))));
|
||||
}
|
||||
}
|
@ -1,93 +1,49 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import com.google.common.jimfs.Jimfs;
|
||||
import de.exlll.configlib.classes.DefaultTypeClass;
|
||||
import de.exlll.configlib.classes.NonDefaultTypeClass;
|
||||
import de.exlll.configlib.classes.SimpleTypesClass;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import de.exlll.configlib.configs.mem.InSharedMemoryConfiguration;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class ConfigurationTest {
|
||||
private FileSystem fileSystem;
|
||||
private Path configPath;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
fileSystem = Jimfs.newFileSystem();
|
||||
configPath = fileSystem.getPath("/a/b/config.yml");
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
fileSystem.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveCreatesParentDirectories() throws Exception {
|
||||
TestConfiguration cfg = new TestConfiguration(configPath);
|
||||
assertThat(Files.exists(configPath.getParent()), is(false));
|
||||
|
||||
cfg.save();
|
||||
assertThat(Files.exists(configPath.getParent()), is(true));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveWritesConfig() throws Exception {
|
||||
TestConfiguration cfg = new TestConfiguration(configPath);
|
||||
assertThat(Files.exists(configPath), is(false));
|
||||
|
||||
cfg.save();
|
||||
assertThat(Files.exists(configPath), is(true));
|
||||
assertThat(ConfigReader.read(configPath), is(TestConfiguration.CONFIG_AS_TEXT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void simpleTypesConfigSavesAndLoads() throws Exception {
|
||||
Configuration cfg = new SimpleTypesClass(configPath);
|
||||
cfg.save();
|
||||
cfg.load();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultTypesConfigSavesAndLoads() throws Exception {
|
||||
Configuration cfg = new DefaultTypeClass(configPath);
|
||||
cfg.save();
|
||||
cfg.load();
|
||||
class ConfigurationTest {
|
||||
private static class TestHook extends InSharedMemoryConfiguration {
|
||||
protected TestHook() { super(Properties.builder().build()); }
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonDefaultConfigSavesAndLoads() throws Exception {
|
||||
Configuration cfg = new NonDefaultTypeClass(configPath);
|
||||
cfg.save();
|
||||
cfg.load();
|
||||
}
|
||||
void configExecutesPreSaveHook() throws IOException {
|
||||
class A extends TestHook {
|
||||
int i = 0;
|
||||
|
||||
@Test
|
||||
public void loadExecutesPostLoadHook() throws Exception {
|
||||
AtomicInteger integer = new AtomicInteger();
|
||||
Configuration cfg = new TestConfiguration(configPath, integer::incrementAndGet);
|
||||
@Override
|
||||
protected void preSave() { i++; }
|
||||
}
|
||||
A save = new A();
|
||||
save.save();
|
||||
assertThat(save.i, is(1));
|
||||
|
||||
cfg.save();
|
||||
assertThat(integer.get(), is(0));
|
||||
cfg.load();
|
||||
assertThat(integer.get(), is(1));
|
||||
A load = new A();
|
||||
load.load();
|
||||
assertThat(load.i, is(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadAndSaveExecutesPostLoadHook1() throws Exception {
|
||||
AtomicInteger integer = new AtomicInteger();
|
||||
Configuration cfg = new TestConfiguration(configPath, integer::incrementAndGet);
|
||||
|
||||
cfg.loadAndSave();
|
||||
assertThat(integer.get(), is(1));
|
||||
void configExecutesPostLoadHook() throws IOException {
|
||||
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,53 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class FieldFilterTest {
|
||||
|
||||
@Test
|
||||
public void filterTestsFields() throws Exception {
|
||||
Predicate<Field> test = FieldFilter.INSTANCE;
|
||||
Class<?> cls = TestClass.class;
|
||||
|
||||
assertThat(cls.getDeclaredFields().length, is(9));
|
||||
|
||||
assertThat(test.test(cls.getDeclaredField("a")), is(false));
|
||||
assertThat(test.test(cls.getDeclaredField("b")), is(false));
|
||||
assertThat(test.test(cls.getDeclaredField("c")), is(false));
|
||||
assertThat(test.test(cls.getDeclaredField("d")), is(true));
|
||||
assertThat(test.test(cls.getDeclaredField("e")), is(true));
|
||||
assertThat(test.test(cls.getDeclaredField("f")), is(true));
|
||||
assertThat(test.test(cls.getDeclaredField("g")), is(true));
|
||||
assertThat(test.test(cls.getDeclaredField("h")), is(true));
|
||||
assertThat(test.test(cls.getDeclaredField("i")), is(true));
|
||||
|
||||
cls = TestClassSynthetic.class;
|
||||
assertThat(test.test(cls.getDeclaredField("a")), is(true));
|
||||
assertThat(test.test(cls.getDeclaredField("this$0")), is(false));
|
||||
}
|
||||
|
||||
private static final class TestClass {
|
||||
/* filtered fields */
|
||||
private static int a;
|
||||
private final int b = 1;
|
||||
private transient int c;
|
||||
|
||||
/* not filtered fields */
|
||||
private int d;
|
||||
protected int e;
|
||||
int f;
|
||||
public int g;
|
||||
double h;
|
||||
volatile int i;
|
||||
}
|
||||
|
||||
private final class TestClassSynthetic {
|
||||
int a;
|
||||
}
|
||||
}
|
@ -0,0 +1,389 @@
|
||||
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 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 List.of(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 Map.of("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 = Map.of("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(), Map.of("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(List.of(1, 2)));
|
||||
assertThat(map.get("p2"), is(Map.of("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 = Map.of(
|
||||
"p1", List.of(10, 11),
|
||||
"p2", Map.of("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 = Map.of(
|
||||
"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 = Map.of(
|
||||
"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 = Map.of(
|
||||
"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 = Map.of(
|
||||
"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(LocalTestEnum.class)
|
||||
List<List<LocalTestEnum>> l = List.of();
|
||||
}
|
||||
Map<String, Object> map = Map.of(
|
||||
"l", List.of(List.of("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(LocalTestEnum.class)
|
||||
Set<List<LocalTestEnum>> s = Set.of();
|
||||
}
|
||||
Map<String, Object> map = Map.of(
|
||||
"s", Set.of(List.of("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(LocalTestEnum.class)
|
||||
Map<Integer, List<LocalTestEnum>> m = Map.of();
|
||||
}
|
||||
Map<String, Object> map = Map.of(
|
||||
"m", Map.of(1, List.of("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));
|
||||
}
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import de.exlll.configlib.annotation.ConfigurationElement;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static de.exlll.configlib.Configuration.Properties;
|
||||
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 assertItmCfgExceptionMessage(
|
||||
Object o, Properties props, String msg
|
||||
) {
|
||||
ConfigurationException ex = assertItmThrowsCfgException(o, props);
|
||||
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 void assertIfmCfgExceptionMessage(
|
||||
Object o, Map<String, Object> map, Properties props, String msg
|
||||
) {
|
||||
ConfigurationException ex = assertIfmThrowsCfgException(o, map, props);
|
||||
assertThat(ex.getMessage(), is(msg));
|
||||
}
|
||||
|
||||
public static ConfigurationException assertItmThrowsCfgException(Object o) {
|
||||
return assertThrows(
|
||||
ConfigurationException.class,
|
||||
() -> instanceToMap(o)
|
||||
);
|
||||
}
|
||||
|
||||
public static ConfigurationException assertItmThrowsCfgException(
|
||||
Object o, Configuration.Properties props
|
||||
) {
|
||||
return assertThrows(
|
||||
ConfigurationException.class,
|
||||
() -> instanceToMap(o, props)
|
||||
);
|
||||
}
|
||||
|
||||
public static ConfigurationException assertIfmThrowsCfgException(
|
||||
Object o, Map<String, Object> map
|
||||
) {
|
||||
return assertThrows(
|
||||
ConfigurationException.class,
|
||||
() -> instanceFromMap(o, map)
|
||||
);
|
||||
}
|
||||
|
||||
public static ConfigurationException assertIfmThrowsCfgException(
|
||||
Object o, Map<String, Object> map, Configuration.Properties props
|
||||
) {
|
||||
return assertThrows(
|
||||
ConfigurationException.class,
|
||||
() -> instanceFromMap(o, map, props)
|
||||
);
|
||||
}
|
||||
|
||||
public static Map<String, Object> instanceToMap(Object o) {
|
||||
return instanceToMap(o, DEFAULT);
|
||||
}
|
||||
|
||||
public static Map<String, Object> instanceToMap(
|
||||
Object o, Configuration.Properties props) {
|
||||
return FieldMapper.instanceToMap(o, props);
|
||||
}
|
||||
|
||||
public static <T> T instanceFromMap(T o, Map<String, Object> map) {
|
||||
FieldMapper.instanceFromMap(o, map, DEFAULT);
|
||||
return o;
|
||||
}
|
||||
|
||||
public static <T> T instanceFromMap(
|
||||
T o, Map<String, Object> map, Configuration.Properties props) {
|
||||
FieldMapper.instanceFromMap(o, map, props);
|
||||
return o;
|
||||
}
|
||||
}
|
@ -1,197 +1,530 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import de.exlll.configlib.classes.DefaultTypeClass;
|
||||
import de.exlll.configlib.classes.NonDefaultTypeClass;
|
||||
import org.junit.Test;
|
||||
import de.exlll.configlib.Converter.ConversionInfo;
|
||||
import de.exlll.configlib.FieldMapper.FieldFilter;
|
||||
import de.exlll.configlib.annotation.ElementType;
|
||||
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.format.FieldNameFormatters;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
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 java.util.stream.Collectors.*;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertAll;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
@SuppressWarnings({"unused", "ThrowableNotThrown"})
|
||||
class FieldMapperTest {
|
||||
private static final Class<ClassWithFinalStaticTransientField> CWFSTF =
|
||||
ClassWithFinalStaticTransientField.class;
|
||||
private static final Predicate<Field> filter = FieldFilter.DEFAULT;
|
||||
private static final TestClass t = TestClass.TEST_VALUES;
|
||||
private static final Configuration.Properties DEFAULT =
|
||||
Configuration.Properties.builder().build();
|
||||
private static final Map<String, Object> map = instanceToMap(
|
||||
TestClass.TEST_VALUES, DEFAULT
|
||||
);
|
||||
|
||||
private TestClass tmp;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
tmp = new TestClass();
|
||||
FieldMapper.instanceFromMap(tmp, map, DEFAULT);
|
||||
}
|
||||
|
||||
public class FieldMapperTest {
|
||||
private final Path path = Paths.get("a");
|
||||
@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
|
||||
public void toSerializableObjectReturnsObjectForDefaultTypes() throws Exception {
|
||||
DefaultTypeClass instance = new DefaultTypeClass(path);
|
||||
for (Field f : DefaultTypeClass.class.getDeclaredFields()) {
|
||||
Object value = Reflect.getValue(f, instance);
|
||||
assertThat(FieldMapper.toSerializableObject(value), sameInstance(value));
|
||||
}
|
||||
void instanceFromMapSetsEnums() {
|
||||
assertThat(tmp.getE1(), is(t.getE1()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void toSerializableObjectReturnsMapForNonDefaultTypes() throws Exception {
|
||||
DefaultTypeClass instance = new DefaultTypeClass(path);
|
||||
void instanceFromMapSetsContainersOfSimpleTypes() {
|
||||
assertAll(
|
||||
() -> assertThat(tmp.getInts(), is(t.getInts())),
|
||||
() -> assertThat(tmp.getStrings(), is(t.getStrings())),
|
||||
() -> assertThat(tmp.getDoubleByBool(), is(t.getDoubleByBool()))
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> map = (Map<String, Object>) FieldMapper.toSerializableObject(instance);
|
||||
@Test
|
||||
void instanceFromMapsConvertsMapsToTypes() {
|
||||
assertThat(tmp.getSubClass(), is(t.getSubClass()));
|
||||
}
|
||||
|
||||
int counter = 0;
|
||||
for (Field field : DefaultTypeClass.class.getDeclaredFields()) {
|
||||
Object fieldValue = Reflect.getValue(field, instance);
|
||||
assertThat(map.get(field.getName()), is(fieldValue));
|
||||
counter++;
|
||||
}
|
||||
assertThat(map.size(), is(counter));
|
||||
@Test
|
||||
void instanceFromMapsConvertsExcludedClasses() {
|
||||
assertThat(tmp.getExcludedClass(), is(t.getExcludedClass()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void fromSerializedObjectIgnoresNullValues() throws Exception {
|
||||
DefaultTypeClass instance = new DefaultTypeClass(path);
|
||||
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()));
|
||||
}
|
||||
|
||||
for (Field field : DefaultTypeClass.class.getDeclaredFields()) {
|
||||
Object currentValue = Reflect.getValue(field, instance);
|
||||
FieldMapper.fromSerializedObject(field, instance, null);
|
||||
Object newValue = Reflect.getValue(field, instance);
|
||||
@Test
|
||||
void instanceFromMapDoesNotSetFinalStaticOrTransientFields() {
|
||||
Map<String, Object> map = Map.of(
|
||||
"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));
|
||||
}
|
||||
|
||||
if (field.getType().isPrimitive()) {
|
||||
assertThat(currentValue, is(newValue));
|
||||
} else {
|
||||
assertThat(currentValue, sameInstance(newValue));
|
||||
}
|
||||
@Test
|
||||
void instanceFromMapConvertsAllFields() {
|
||||
assertThat(tmp, is(t));
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapThrowsExceptionIfEnumConstantDoesNotExist() {
|
||||
class A {
|
||||
LocalTestEnum t = LocalTestEnum.T;
|
||||
}
|
||||
Map<String, Object> map = Map.of(
|
||||
"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
|
||||
public void fromSerializedObjectSetsValueIfDefaultType() throws Exception {
|
||||
DefaultTypeClass instance = new DefaultTypeClass(path);
|
||||
void instanceFromMapIgnoresNullValues() {
|
||||
class A {
|
||||
TestSubClass c = new TestSubClass();
|
||||
}
|
||||
Map<String, Object> map = Map.of("c", Map.of("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(""));
|
||||
}
|
||||
|
||||
Map<String, Object> map = DefaultTypeClass.newValues();
|
||||
for (Field field : DefaultTypeClass.class.getDeclaredFields()) {
|
||||
String fieldName = field.getName();
|
||||
Object mapValue = map.get(fieldName);
|
||||
FieldMapper.fromSerializedObject(field, instance, mapValue);
|
||||
Object value = Reflect.getValue(field, instance);
|
||||
@Test
|
||||
void instanceFromMapSetsField() {
|
||||
TestClass ins = TestClass.TEST_VALUES;
|
||||
TestClass def = new TestClass();
|
||||
assertThat(ins, is(not(def)));
|
||||
instanceFromMap(def, map);
|
||||
assertThat(ins, is(def));
|
||||
}
|
||||
|
||||
if (field.getType().isPrimitive()) {
|
||||
assertThat(mapValue, is(value));
|
||||
} else {
|
||||
assertThat(mapValue, sameInstance(value));
|
||||
}
|
||||
@Test
|
||||
void instanceFromMapCreatesConcreteInstances() {
|
||||
class A {
|
||||
LocalTestInterface l = new LocalTestInterfaceImpl();
|
||||
}
|
||||
class B {
|
||||
LocalTestAbstractClass l = new LocalTestAbstractClassImpl();
|
||||
}
|
||||
instanceFromMap(new A(), Map.of("l", Map.of()));
|
||||
instanceFromMap(new B(), Map.of("l", Map.of()));
|
||||
}
|
||||
|
||||
|
||||
@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
|
||||
public void fromSerializedObjectUpdatesValueIfNotDefaultType() throws Exception {
|
||||
NonDefaultTypeClass instance = new NonDefaultTypeClass(path);
|
||||
Field field = NonDefaultTypeClass.class.getDeclaredField("defaultTypeClass");
|
||||
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()))
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, Object> map = DefaultTypeClass.newValues();
|
||||
FieldMapper.fromSerializedObject(field, instance, map);
|
||||
@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 = List.of(LocalTestEnum.S, LocalTestEnum.T);
|
||||
@ElementType(LocalTestEnum.class)
|
||||
Set<LocalTestEnum> es = Set.of(LocalTestEnum.S, LocalTestEnum.T);
|
||||
@ElementType(LocalTestEnum.class)
|
||||
Map<String, LocalTestEnum> em = Map.of(
|
||||
"1", LocalTestEnum.S, "2", LocalTestEnum.T
|
||||
);
|
||||
}
|
||||
Map<String, Object> map = instanceToMap(new A());
|
||||
assertThat(map.get("el"), is(List.of("S", "T")));
|
||||
assertThat(map.get("es"), is(Set.of("S", "T")));
|
||||
assertThat(map.get("em"), is(Map.of("1", "S", "2", "T")));
|
||||
}
|
||||
|
||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||
Field f = DefaultTypeClass.class.getDeclaredField(entry.getKey());
|
||||
Object value = Reflect.getValue(f, instance.defaultTypeClass);
|
||||
assertThat(value, is(entry.getValue()));
|
||||
@Test
|
||||
void instanceFromMapConvertsStringContainersToEnumContainers() {
|
||||
class A {
|
||||
@ElementType(LocalTestEnum.class)
|
||||
List<LocalTestEnum> el = List.of();
|
||||
@ElementType(LocalTestEnum.class)
|
||||
Set<LocalTestEnum> es = Set.of();
|
||||
@ElementType(LocalTestEnum.class)
|
||||
Map<String, LocalTestEnum> em = Map.of();
|
||||
}
|
||||
Map<String, Object> map = Map.of(
|
||||
"el", List.of("S", "T"),
|
||||
"es", Set.of("S", "T"),
|
||||
"em", Map.of("1", "S", "2", "T")
|
||||
);
|
||||
A a = instanceFromMap(new A(), map);
|
||||
assertThat(a.el, is(List.of(LocalTestEnum.S, LocalTestEnum.T)));
|
||||
assertThat(a.es, is(Set.of(LocalTestEnum.S, LocalTestEnum.T)));
|
||||
assertThat(a.em, is(Map.of(
|
||||
"1", LocalTestEnum.S, "2", LocalTestEnum.T
|
||||
)));
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void instanceTopMapCreatesMap() throws Exception {
|
||||
TestClass t = new TestClass();
|
||||
Map<String, Object> map = FieldMapper.instanceToMap(t);
|
||||
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 -> Map.entry(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))
|
||||
);
|
||||
}
|
||||
|
||||
assertThat(map.get("i"), is(1));
|
||||
assertThat(map.get("i"), instanceOf(Integer.class));
|
||||
@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()))
|
||||
);
|
||||
}
|
||||
|
||||
assertThat(map.get("z"), is(0));
|
||||
assertThat(map.get("z"), instanceOf(Integer.class));
|
||||
@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 -> Map.entry(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));
|
||||
}
|
||||
|
||||
assertThat(map.get("d"), is(2.0));
|
||||
assertThat(map.get("d"), instanceOf(Double.class));
|
||||
@Test
|
||||
void instanceToMapContainsEnums() {
|
||||
assertThat(map.get("e1"), is(t.getE1().toString()));
|
||||
}
|
||||
|
||||
assertThat(map.get("s"), is("s"));
|
||||
assertThat(map.get("s"), instanceOf(String.class));
|
||||
@Test
|
||||
void instanceToMapContainsEnumLists() {
|
||||
List<String> list = t.getEnums().stream()
|
||||
.map(Enum::toString)
|
||||
.collect(toList());
|
||||
assertThat(map.get("enums"), is(list));
|
||||
}
|
||||
|
||||
assertThat(map.get("c"), is('c'));
|
||||
assertThat(map.get("c"), instanceOf(Character.class));
|
||||
|
||||
assertThat(map.get("strings"), is(Arrays.asList("1", "2")));
|
||||
assertThat(map.get("strings"), instanceOf(List.class));
|
||||
@Test
|
||||
void instanceToMapConvertsConvertTypes() {
|
||||
String s = new TestSubClassConverter()
|
||||
.convertTo(t.getConverterSubClass(), null);
|
||||
assertThat(map.get("converterSubClass"), is(s));
|
||||
}
|
||||
|
||||
Map<String, Integer> intMap = new HashMap<>();
|
||||
intMap.put("a", 1);
|
||||
intMap.put("b", 2);
|
||||
assertThat(map.get("objects"), is(intMap));
|
||||
assertThat(map.get("objects"), instanceOf(Map.class));
|
||||
@Test
|
||||
void instanceToMapCreatesLinkedHashMap() {
|
||||
assertThat(instanceToMap(new Object()), instanceOf(LinkedHashMap.class));
|
||||
}
|
||||
|
||||
Map<String, Object> bMap = new HashMap<>();
|
||||
bMap.put("j", -1);
|
||||
bMap.put("t", "t");
|
||||
assertThat(map.get("b"), is(bMap));
|
||||
assertThat(map.get("b"), instanceOf(Map.class));
|
||||
@Test
|
||||
void filteredFieldsFiltersFields() throws NoSuchFieldException {
|
||||
List<Field> fields = FieldFilter.filterFields(CWFSTF);
|
||||
assertThat(fields.size(), is(0));
|
||||
|
||||
class A {
|
||||
private int i;
|
||||
private final int j = 0;
|
||||
private transient int k;
|
||||
}
|
||||
fields = FieldFilter.filterFields(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
|
||||
public void instanceFromMapKeepsDefaultValues() throws Exception {
|
||||
TestClass t = new TestClass();
|
||||
FieldMapper.instanceFromMap(t, new HashMap<>());
|
||||
assertThat(t.z, is(0));
|
||||
assertThat(t.i, is(1));
|
||||
assertThat(t.s, is("s"));
|
||||
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 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();
|
||||
}
|
||||
return ConversionInfo.of(field, o, null, null);
|
||||
}
|
||||
|
||||
private static ConversionInfo newInfo(String fieldName) {
|
||||
return newInfo(fieldName, new TestClass());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void instanceFromMapSetsValues() throws Exception {
|
||||
TestClass t = new TestClass();
|
||||
void typeConverterReturnsInstanceIfClassesMatch() {
|
||||
//noinspection RedundantStringConstructorCall
|
||||
String s = new String("123");
|
||||
Object val = converter.convertFrom(s, newInfo("string"));
|
||||
assertThat(val, sameInstance(s));
|
||||
}
|
||||
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("z", 2);
|
||||
map.put("i", 10);
|
||||
map.put("c", 'q');
|
||||
map.put("s", "t");
|
||||
map.put("strings", Arrays.asList("99", "100", "101"));
|
||||
@Test
|
||||
void typeConverterConvertsStringToCharacter() {
|
||||
String s = "1";
|
||||
Object vc = converter.convertFrom(s, newInfo("primChar"));
|
||||
Object vd = converter.convertFrom(s, newInfo("refChar"));
|
||||
|
||||
Map<String, Object> objects = new HashMap<>();
|
||||
objects.put("a", 100);
|
||||
objects.put("b", 200);
|
||||
objects.put("c", 300);
|
||||
objects.put("d", 400);
|
||||
map.put("objects", objects);
|
||||
assertThat(vc, instanceOf(Character.class));
|
||||
assertThat(vd, instanceOf(Character.class));
|
||||
}
|
||||
|
||||
Map<String, Object> bMap = new HashMap<>();
|
||||
bMap.put("j", 20);
|
||||
bMap.put("t", "v");
|
||||
map.put("b", bMap);
|
||||
@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 conv = converter.convertFrom(number, info);
|
||||
assertThat(conv, instanceOf(info.getFieldType()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FieldMapper.instanceFromMap(t, map);
|
||||
assertThat(t.z, is(2));
|
||||
assertThat(t.i, is(10));
|
||||
assertThat(t.c, is('q'));
|
||||
assertThat(t.s, is("t"));
|
||||
assertThat(t.strings, is(Arrays.asList("99", "100", "101")));
|
||||
assertThat(t.objects, is(objects));
|
||||
assertThat(t.b.j, is(20));
|
||||
assertThat(t.b.t, is("v"));
|
||||
@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 TestClass {
|
||||
private int z;
|
||||
private int i = 1;
|
||||
private double d = 2.0;
|
||||
private String s = "s";
|
||||
private List<String> strings = Arrays.asList("1", "2");
|
||||
private Map<String, Object> objects = new HashMap<>();
|
||||
private char c = 'c';
|
||||
private TestClassB b = new TestClassB();
|
||||
private static final class ExcludedClass {}
|
||||
|
||||
public TestClass() {
|
||||
objects.put("a", 1);
|
||||
objects.put("b", 2);
|
||||
@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));
|
||||
}
|
||||
|
||||
private static final class TestClassB {
|
||||
private int j = -1;
|
||||
private String t = "t";
|
||||
@Test
|
||||
void instanceFromMapSetsAutoConvertedInstancesAsIs() {
|
||||
class A {
|
||||
@NoConvert
|
||||
ExcludedClass ex = new ExcludedClass();
|
||||
}
|
||||
|
||||
ExcludedClass cls = new ExcludedClass();
|
||||
Map<String, Object> map = Map.of("ex", cls);
|
||||
A a = instanceFromMap(new A(), map);
|
||||
assertThat(a.ex, sameInstance(cls));
|
||||
}
|
||||
|
||||
private static final class ClassWithFinalStaticTransientField {
|
||||
private final int i = 0;
|
||||
private static int j;
|
||||
private transient int k;
|
||||
}
|
||||
|
||||
private final class ClassWithSyntheticField {}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
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"));
|
||||
}
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class FilteredFieldsTest {
|
||||
@Rule
|
||||
public ExpectedException expectedException = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void constructorRequiresNonNullArray() throws Exception {
|
||||
expectedException.expect(NullPointerException.class);
|
||||
new FilteredFields(null, field -> true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorRequiresNonNullPredicate() throws Exception {
|
||||
expectedException.expect(NullPointerException.class);
|
||||
new FilteredFields(FilteredFields.class.getDeclaredFields(), null);
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorAppliesFilter() throws Exception {
|
||||
FilteredFields ff = new FilteredFields(
|
||||
TestClass.class.getDeclaredFields(), field -> {
|
||||
int mods = field.getModifiers();
|
||||
return Modifier.isPublic(mods);
|
||||
});
|
||||
|
||||
for (Field f : ff) {
|
||||
int mods = f.getModifiers();
|
||||
assertThat(Modifier.isPublic(mods), is(true));
|
||||
}
|
||||
}
|
||||
|
||||
private static final class TestClass {
|
||||
private int a;
|
||||
protected int b;
|
||||
int c;
|
||||
public int d;
|
||||
}
|
||||
|
||||
}
|
@ -1,160 +1,60 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
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.List;
|
||||
import java.util.Map;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class ReflectTest {
|
||||
private static final Class<TestClass> cls1 = TestClass.class;
|
||||
private static final Class<NotDefaultConstructor> cls2 = NotDefaultConstructor.class;
|
||||
private final List<String> list = new ConfigList<>(String.class);
|
||||
private final Set<String> set = new ConfigSet<>(String.class);
|
||||
private final Map<?, String> map = new ConfigMap<>(String.class, String.class);
|
||||
private final Class<?>[] containerClasses = {List.class, Set.class, Map.class};
|
||||
private final Class<?>[] simpleClasses = {
|
||||
boolean.class, char.class, byte.class, short.class,
|
||||
int.class, long.class, float.class, double.class,
|
||||
Boolean.class, String.class, Character.class,
|
||||
Byte.class, Short.class, Integer.class, Long.class,
|
||||
Float.class, Double.class,
|
||||
};
|
||||
private final String errorMessage = "Class NotDefaultConstructor doesn't have a default constructor.";
|
||||
|
||||
@Rule
|
||||
public ExpectedException expectedException = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void checkType() throws Exception {
|
||||
Reflect.checkType(new HashMap<>(), Map.class);
|
||||
expectedException.expect(IllegalArgumentException.class);
|
||||
expectedException.expectMessage("Invalid type!\n" +
|
||||
"Object 'a' is of type String. Expected type: Map");
|
||||
Reflect.checkType("a", Map.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkMapEntriesChecksKeys() throws Exception {
|
||||
Map<String, Integer> map = new HashMap<>();
|
||||
map.put("a", 1);
|
||||
Reflect.checkMapEntries(map, String.class, Integer.class);
|
||||
expectedException.expect(IllegalArgumentException.class);
|
||||
expectedException.expectMessage("Invalid type!\n" +
|
||||
"Object 'a' is of type String. Expected type: Integer");
|
||||
Reflect.checkMapEntries(map, Integer.class, Integer.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkMapEntriesChecksValues() throws Exception {
|
||||
Map<String, Integer> map = new HashMap<>();
|
||||
map.put("a", 1);
|
||||
Reflect.checkMapEntries(map, String.class, Integer.class);
|
||||
expectedException.expect(IllegalArgumentException.class);
|
||||
expectedException.expectMessage("Invalid type!\n" +
|
||||
"Object '1' is of type Integer. Expected type: String");
|
||||
Reflect.checkMapEntries(map, String.class, String.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isDefault() throws Exception {
|
||||
for (Class<?> cls : containerClasses) {
|
||||
assertThat(Reflect.isDefault(cls), is(true));
|
||||
}
|
||||
for (Class<?> cls : simpleClasses) {
|
||||
assertThat(Reflect.isDefault(cls), is(true));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isSimpleType() throws Exception {
|
||||
for (Class<?> cls : simpleClasses) {
|
||||
class ReflectTest {
|
||||
private static final Set<Class<?>> ALL_SIMPLE_TYPES = Set.of(
|
||||
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
|
||||
public void isContainerType() throws Exception {
|
||||
assertThat(Reflect.isContainerType(list.getClass()), is(true));
|
||||
assertThat(Reflect.isContainerType(set.getClass()), is(true));
|
||||
assertThat(Reflect.isContainerType(map.getClass()), is(true));
|
||||
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
|
||||
public void getValueGetsValue() throws Exception {
|
||||
TestClass testClass = new TestClass();
|
||||
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");
|
||||
|
||||
Field s = TestClass.class.getDeclaredField("s");
|
||||
assertThat(Reflect.getValue(s, testClass), is("s"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setValueSetsValue() throws Exception {
|
||||
TestClass testClass = new TestClass();
|
||||
|
||||
Field s = TestClass.class.getDeclaredField("s");
|
||||
Reflect.setValue(s, testClass, "t");
|
||||
assertThat(testClass.s, is("t"));
|
||||
}
|
||||
Object value = Reflect.getValue(f1, testClass);
|
||||
assertThat(value, is(testClass.getString()));
|
||||
|
||||
@Test
|
||||
public void checkForDefaultConstructorsThrowsExceptionIfNoDefault() throws Exception {
|
||||
Reflect.checkDefaultConstructor(cls1);
|
||||
|
||||
expectedException.expect(IllegalArgumentException.class);
|
||||
expectedException.expectMessage(errorMessage);
|
||||
Reflect.checkDefaultConstructor(cls2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hasDefaultConstructor() throws Exception {
|
||||
assertThat(Reflect.hasDefaultConstructor(cls1), is(true));
|
||||
assertThat(Reflect.hasDefaultConstructor(cls2), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDefaultConstructor() throws Exception {
|
||||
assertThat(Reflect.getDefaultConstructor(cls1), is(cls1.getDeclaredConstructor()));
|
||||
expectedException.expect(RuntimeException.class);
|
||||
Reflect.getDefaultConstructor(cls2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void newInstanceChecksForDefaultConstructor() throws Exception {
|
||||
Reflect.newInstance(TestClass.class);
|
||||
expectedException.expect(IllegalArgumentException.class);
|
||||
expectedException.expectMessage(errorMessage);
|
||||
Reflect.newInstance(NotDefaultConstructor.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void newInstanceCreatesNewInstance() throws Exception {
|
||||
TestClass t = (TestClass) Reflect.newInstance(TestClass.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getVersion() throws Exception {
|
||||
assertThat(Reflect.getVersion(cls1), notNullValue());
|
||||
assertThat(Reflect.getVersion(cls2), nullValue());
|
||||
}
|
||||
|
||||
private static final class NotDefaultConstructor {
|
||||
public NotDefaultConstructor(String a) {
|
||||
}
|
||||
}
|
||||
value = Reflect.getValue(f2, testClass);
|
||||
assertThat(value, is(testClass.getPrimLong()));
|
||||
|
||||
@Version(version = "1.2.3")
|
||||
private static final class TestClass {
|
||||
private String s = "s";
|
||||
value = Reflect.getValue(f3, testClass);
|
||||
assertThat(value, is(TestClass.getStaticFinalInt()));
|
||||
}
|
||||
}
|
@ -1,145 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
@Comment({
|
||||
"This is a test configuration.",
|
||||
"This comment is applied to a class."
|
||||
})
|
||||
final class TestConfiguration extends Configuration {
|
||||
private transient Runnable postLoadAction;
|
||||
@Comment({
|
||||
"This comment is applied to a field.",
|
||||
"It has more than 1 line."
|
||||
})
|
||||
private int port = -1;
|
||||
private String localhost = "localhost";
|
||||
private double modifier = 3.14;
|
||||
@Comment("This comment is applied to a field.")
|
||||
private List<String> allowedIps = new ArrayList<>();
|
||||
private Map<String, Integer> intsByStrings = new HashMap<>();
|
||||
private Map<String, List<String>> stringListsByString = new HashMap<>();
|
||||
|
||||
private Credentials credentials = new Credentials();
|
||||
|
||||
public TestConfiguration(Path path) {
|
||||
super(path);
|
||||
allowedIps.add("127.0.0.1");
|
||||
allowedIps.add("127.0.0.2");
|
||||
allowedIps.add("127.0.0.3");
|
||||
|
||||
intsByStrings.put("first", 1);
|
||||
intsByStrings.put("second", 2);
|
||||
intsByStrings.put("third", 3);
|
||||
|
||||
stringListsByString.put("xa", Arrays.asList("x1", "x2"));
|
||||
stringListsByString.put("ya", Arrays.asList("y1", "y2"));
|
||||
stringListsByString.put("za", Arrays.asList("z1", "z2"));
|
||||
}
|
||||
|
||||
public TestConfiguration(Path path, Runnable postLoadAction) {
|
||||
this(path);
|
||||
this.postLoadAction = postLoadAction;
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
public void setPort(int port) {
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
public String getLocalhost() {
|
||||
return localhost;
|
||||
}
|
||||
|
||||
public void setLocalhost(String localhost) {
|
||||
this.localhost = localhost;
|
||||
}
|
||||
|
||||
public double getModifier() {
|
||||
return modifier;
|
||||
}
|
||||
|
||||
public void setModifier(double modifier) {
|
||||
this.modifier = modifier;
|
||||
}
|
||||
|
||||
public List<String> getAllowedIps() {
|
||||
return allowedIps;
|
||||
}
|
||||
|
||||
public void setAllowedIps(List<String> allowedIps) {
|
||||
this.allowedIps = allowedIps;
|
||||
}
|
||||
|
||||
public Map<String, Integer> getIntsByStrings() {
|
||||
return intsByStrings;
|
||||
}
|
||||
|
||||
public void setIntsByStrings(Map<String, Integer> intsByStrings) {
|
||||
this.intsByStrings = intsByStrings;
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getStringListsByString() {
|
||||
return stringListsByString;
|
||||
}
|
||||
|
||||
public void setStringListsByString(
|
||||
Map<String, List<String>> stringListsByString) {
|
||||
this.stringListsByString = stringListsByString;
|
||||
}
|
||||
|
||||
public Credentials getCredentials() {
|
||||
return credentials;
|
||||
}
|
||||
|
||||
public void setCredentials(Credentials credentials) {
|
||||
this.credentials = credentials;
|
||||
}
|
||||
|
||||
public static final class Credentials {
|
||||
private String username = "root";
|
||||
private String password = "1234";
|
||||
}
|
||||
|
||||
public static final String CONFIG_AS_TEXT = "# This is a test configuration.\n" +
|
||||
"# This comment is applied to a class.\n" +
|
||||
"\n" +
|
||||
"# This comment is applied to a field.\n" +
|
||||
"# It has more than 1 line.\n" +
|
||||
"port: -1\n" +
|
||||
"localhost: localhost\n" +
|
||||
"modifier: 3.14\n" +
|
||||
"# This comment is applied to a field.\n" +
|
||||
"allowedIps:\n" +
|
||||
"- 127.0.0.1\n" +
|
||||
"- 127.0.0.2\n" +
|
||||
"- 127.0.0.3\n" +
|
||||
"intsByStrings:\n" +
|
||||
" third: 3\n" +
|
||||
" first: 1\n" +
|
||||
" second: 2\n" +
|
||||
"stringListsByString:\n" +
|
||||
" za:\n" +
|
||||
" - z1\n" +
|
||||
" - z2\n" +
|
||||
" ya:\n" +
|
||||
" - y1\n" +
|
||||
" - y2\n" +
|
||||
" xa:\n" +
|
||||
" - x1\n" +
|
||||
" - x2\n" +
|
||||
"credentials:\n" +
|
||||
" username: root\n" +
|
||||
" password: '1234'\n";
|
||||
|
||||
@Override
|
||||
protected void postLoadHook() {
|
||||
if (postLoadAction != null) {
|
||||
postLoadAction.run();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,167 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.sameInstance;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class TypeConverterTest {
|
||||
@Rule
|
||||
public ExpectedException exception = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void convertValueReturnsSameInstanceIfNoConversion() throws Exception {
|
||||
Map<?, ?> map = new HashMap<>();
|
||||
assertThat(TypeConverter.convertValue(Map.class, map), sameInstance(map));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertValueConvertsString() throws Exception {
|
||||
assertThat(TypeConverter.convertString("z"), is('z'));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertValueConvertsNumber() throws Exception {
|
||||
Integer i = 10;
|
||||
Object o = TypeConverter.convertValue(Short.class, i);
|
||||
assertThat(o, instanceOf(Short.class));
|
||||
assertThat(o, is((short) 10));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertValueReturnsInstanceIfTypesMatch() throws Exception {
|
||||
Object o = new Object();
|
||||
Object converted = TypeConverter.convertValue(Object.class, o);
|
||||
assertThat(o, sameInstance(converted));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertValueRequiresNonNullClass() throws Exception {
|
||||
exception.expect(NullPointerException.class);
|
||||
TypeConverter.convertValue(Object.class, null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertValueRequiresNonNullValue() throws Exception {
|
||||
exception.expect(NullPointerException.class);
|
||||
TypeConverter.convertValue(null, new Object());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertStringThrowsExceptionIfStringLength0() throws Exception {
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
TypeConverter.convertString("");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertStringThrowsExceptionIfStringLengthBiggerThan1() throws Exception {
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
TypeConverter.convertString("ab");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertStringReturnsCharacter() throws Exception {
|
||||
String s = "z";
|
||||
char c = 'z';
|
||||
|
||||
Character character = TypeConverter.convertString(s);
|
||||
assertThat(character, is(c));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertNumberThrowsExceptionIfUnknownType() throws Exception {
|
||||
exception.expect(IllegalArgumentException.class);
|
||||
exception.expectMessage("Number cannot be converted to target type " +
|
||||
"'class java.lang.Object'");
|
||||
TypeConverter.convertNumber(Object.class, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void convertNumberReturnsConvertedNumber() throws Exception {
|
||||
Number[] numbers = {(byte) 1, (short) 2, 3, 4L, 5.0F, 6.0};
|
||||
Class<?>[] numClasses = {
|
||||
Byte.class, Short.class, Integer.class, Long.class,
|
||||
Float.class, Double.class
|
||||
};
|
||||
|
||||
for (Class<?> numClass : numClasses) {
|
||||
for (Number number : numbers) {
|
||||
Number n = TypeConverter.convertNumber(numClass, number);
|
||||
assertThat(n, instanceOf(numClass));
|
||||
|
||||
// this is only true because we use values that
|
||||
// don't cause an overflow
|
||||
assertThat(n.doubleValue(), is(number.doubleValue()));
|
||||
}
|
||||
}
|
||||
|
||||
int i = Short.MAX_VALUE + 1;
|
||||
Number n = TypeConverter.convertNumber(Short.class, i);
|
||||
assertThat(n, is(Short.MIN_VALUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isBooleanClass() throws Exception {
|
||||
assertThat(TypeConverter.isBooleanClass(Boolean.class), is(true));
|
||||
assertThat(TypeConverter.isBooleanClass(boolean.class), is(true));
|
||||
assertThat(TypeConverter.isBooleanClass(Object.class), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isByteClass() throws Exception {
|
||||
assertThat(TypeConverter.isByteClass(Byte.class), is(true));
|
||||
assertThat(TypeConverter.isByteClass(byte.class), is(true));
|
||||
assertThat(TypeConverter.isByteClass(Object.class), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isShortClass() throws Exception {
|
||||
assertThat(TypeConverter.isShortClass(Short.class), is(true));
|
||||
assertThat(TypeConverter.isShortClass(short.class), is(true));
|
||||
assertThat(TypeConverter.isShortClass(Object.class), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isIntegerClass() throws Exception {
|
||||
assertThat(TypeConverter.isIntegerClass(Integer.class), is(true));
|
||||
assertThat(TypeConverter.isIntegerClass(int.class), is(true));
|
||||
assertThat(TypeConverter.isIntegerClass(Object.class), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isLongClass() throws Exception {
|
||||
assertThat(TypeConverter.isLongClass(Long.class), is(true));
|
||||
assertThat(TypeConverter.isLongClass(long.class), is(true));
|
||||
assertThat(TypeConverter.isLongClass(Object.class), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isFloatClass() throws Exception {
|
||||
assertThat(TypeConverter.isFloatClass(Float.class), is(true));
|
||||
assertThat(TypeConverter.isFloatClass(float.class), is(true));
|
||||
assertThat(TypeConverter.isFloatClass(Object.class), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isDoubleClass() throws Exception {
|
||||
assertThat(TypeConverter.isDoubleClass(Double.class), is(true));
|
||||
assertThat(TypeConverter.isDoubleClass(double.class), is(true));
|
||||
assertThat(TypeConverter.isDoubleClass(Object.class), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isCharacterClass() throws Exception {
|
||||
assertThat(TypeConverter.isCharacterClass(Character.class), is(true));
|
||||
assertThat(TypeConverter.isCharacterClass(char.class), is(true));
|
||||
assertThat(TypeConverter.isCharacterClass(Object.class), is(false));
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,532 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import de.exlll.configlib.FieldMapperHelpers.*;
|
||||
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 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 = Map.of(
|
||||
"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 = Map.of(
|
||||
"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 = Map.of(
|
||||
"c", Map.of(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 = Map.of("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 = Map.of("s", Map.of());
|
||||
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 = Map.of("s", Map.of());
|
||||
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 = Map.of("l", List.of("s"));
|
||||
String msg = "Can not set field 'l' with type 'CopyOnWriteArrayList' " +
|
||||
"to 'List1'.";
|
||||
assertIfmCfgExceptionMessage(new A(), m, msg);
|
||||
|
||||
m = Map.of("s", Set.of("s"));
|
||||
msg = "Can not set field 's' with type 'ConcurrentSkipListSet' " +
|
||||
"to 'Set1'.";
|
||||
assertIfmCfgExceptionMessage(new B(), m, msg);
|
||||
|
||||
m = Map.of("m", Map.of(1, "s"));
|
||||
msg = "Can not set field 'm' with type 'ConcurrentHashMap' " +
|
||||
"to 'Map1'.";
|
||||
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 = Map.of("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<>(List.of(
|
||||
TestSubClass.of(1, "1")
|
||||
));
|
||||
}
|
||||
class B {
|
||||
List<Set<Map<Integer, TestSubClass>>> l = new ArrayList<>(List.of(
|
||||
Set.of(Map.of(1, TestSubClass.of(1, "1")))
|
||||
));
|
||||
}
|
||||
|
||||
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: [TestSubClass{\nprimInt=1,\nstring='1'}]";
|
||||
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=TestSubClass{\nprimInt=1,\nstring='1'}}]]";
|
||||
assertItmCfgExceptionMessage(b, msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapRequiresSetsWithoutElementTypeToContainSimpleTypes() {
|
||||
class A {
|
||||
Set<TestSubClass> s = new HashSet<>(Set.of(
|
||||
TestSubClass.of(1, "1")
|
||||
));
|
||||
}
|
||||
class B {
|
||||
Set<List<Map<Integer, TestSubClass>>> s = new HashSet<>(Set.of(
|
||||
List.of(Map.of(1, TestSubClass.of(1, "1")))
|
||||
));
|
||||
}
|
||||
|
||||
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: [TestSubClass{\nprimInt=1,\nstring='1'}]";
|
||||
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=TestSubClass{\nprimInt=1,\nstring='1'}}]]";
|
||||
assertItmCfgExceptionMessage(b, msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceToMapRequiresMapsWithoutElementTypeToContainSimpleTypes() {
|
||||
class A {
|
||||
Map<Integer, TestSubClass> m = new HashMap<>(Map.of(
|
||||
1, TestSubClass.of(1, "1")
|
||||
));
|
||||
}
|
||||
class B {
|
||||
Map<Integer, Set<List<TestSubClass>>> m = new HashMap<>(Map.of(
|
||||
1, Set.of(List.of(TestSubClass.of(1, "1")))
|
||||
));
|
||||
}
|
||||
|
||||
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=TestSubClass{\nprimInt=1,\nstring='1'}}";
|
||||
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=[[TestSubClass{\nprimInt=1,\nstring='1'}]]}";
|
||||
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 = Map.of("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 = Map.of("l", List.of());
|
||||
|
||||
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(), Map.of("s", ""), msg);
|
||||
}
|
||||
|
||||
@Test
|
||||
void instanceFromMapsRequiresElementTypeToBeEnumType() {
|
||||
class A {
|
||||
@ElementType(TestSubClass.class)
|
||||
List<List<TestSubClass>> l = List.of();
|
||||
}
|
||||
Map<String, Object> map = Map.of(
|
||||
"l", List.of(List.of("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(TestSubClass.class)
|
||||
List<List<TestSubClass>> l = List.of();
|
||||
}
|
||||
Map<String, Object> map = Map.of(
|
||||
"l", List.of(List.of(1, 2))
|
||||
);
|
||||
ConfigurationException ex = assertIfmThrowsCfgException(new A(), map);
|
||||
Throwable cause = ex.getCause();
|
||||
|
||||
String msg = "Initializing field 'l' requires objects of type " +
|
||||
"Map<String, Object> but element '1' is of type 'Integer'.";
|
||||
assertThat(cause.getMessage(), is(msg));
|
||||
}
|
||||
}
|
@ -1,118 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import com.google.common.jimfs.Jimfs;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class VersionTest {
|
||||
private static final String CONFIG_AS_STRING =
|
||||
"# class\n\n# field\nk: 0\n\n# c1\n";
|
||||
private static final String VERSION_CONFIG_AS_STRING =
|
||||
"# class\n\n# field\nk: 0\n\n# c1\nv: 1.2.3-alpha\n";
|
||||
private static final String OLD_VERSION_CONFIG_AS_STRING =
|
||||
"# class\n\n# field\nk: 0\n\n# c1\nv: 1.2.2-alpha\n";
|
||||
@Rule
|
||||
public ExpectedException exception = ExpectedException.none();
|
||||
private FileSystem fileSystem;
|
||||
private Path configPath;
|
||||
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
fileSystem = Jimfs.newFileSystem();
|
||||
configPath = fileSystem.getPath("/a/b/config.yml");
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
fileSystem.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void versionSaved() throws Exception {
|
||||
new VersionConfiguration(configPath).save();
|
||||
assertThat(ConfigReader.read(configPath), is(VERSION_CONFIG_AS_STRING));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void saveThrowsExceptionIfVersionNameClash() throws Exception {
|
||||
exception.expect(ConfigException.class);
|
||||
exception.expectMessage("Solution: Rename the field or use a " +
|
||||
"different version field name.");
|
||||
new VersionConfigurationWithVersionField(configPath).save();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void currentFileVersionReturnsEmptyOptionalIfFileDoesntExist() throws Exception {
|
||||
Configuration cfg = new VersionConfiguration(configPath);
|
||||
assertThat(cfg.currentFileVersion(), nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void currentFileVersionReturnsOptionalWithVersion() throws Exception {
|
||||
Configuration cfg = new VersionConfiguration(configPath);
|
||||
cfg.save();
|
||||
assertThat(cfg.currentFileVersion(), is("1.2.3-alpha"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateRenameApplied() throws Exception {
|
||||
Files.createDirectories(configPath.getParent());
|
||||
ConfigWriter.write(configPath, CONFIG_AS_STRING);
|
||||
|
||||
final Path oldPath = fileSystem.getPath(configPath.toString() + "-old");
|
||||
|
||||
new VersionConfiguration(configPath).save();
|
||||
assertThat(ConfigReader.read(configPath), is(VERSION_CONFIG_AS_STRING));
|
||||
assertThat(ConfigReader.read(oldPath), is(CONFIG_AS_STRING));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateRenameAppendsOldVersion() throws Exception {
|
||||
final Path oldPath = fileSystem.getPath(configPath.toString() + "-v1.2.2-alpha");
|
||||
|
||||
Files.createDirectories(configPath.getParent());
|
||||
ConfigWriter.write(configPath, OLD_VERSION_CONFIG_AS_STRING);
|
||||
ConfigWriter.write(oldPath, "123");
|
||||
|
||||
new VersionConfiguration(configPath).save();
|
||||
|
||||
assertThat(ConfigReader.read(configPath), is(VERSION_CONFIG_AS_STRING));
|
||||
assertThat(ConfigReader.read(oldPath), is(OLD_VERSION_CONFIG_AS_STRING));
|
||||
}
|
||||
|
||||
@Comment("class")
|
||||
@Version(version = "1.2.3-alpha",
|
||||
fieldName = "v",
|
||||
fieldComments = {"", "c1"},
|
||||
updateStrategy = UpdateStrategy.DEFAULT_RENAME)
|
||||
private static final class VersionConfiguration extends Configuration {
|
||||
@Comment("field")
|
||||
private int k;
|
||||
|
||||
protected VersionConfiguration(Path configPath) {
|
||||
super(configPath);
|
||||
}
|
||||
}
|
||||
|
||||
@Version
|
||||
private static final class VersionConfigurationWithVersionField
|
||||
extends Configuration {
|
||||
private int version;
|
||||
|
||||
protected VersionConfigurationWithVersionField(Path configPath) {
|
||||
super(configPath);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,110 +0,0 @@
|
||||
package de.exlll.configlib;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.constructor.Constructor;
|
||||
import org.yaml.snakeyaml.representer.Representer;
|
||||
import org.yaml.snakeyaml.resolver.Resolver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import static org.hamcrest.core.Is.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
public class YamlSerializerTest {
|
||||
@Rule
|
||||
public ExpectedException expectedException = ExpectedException.none();
|
||||
private DumperOptions dumperOptions;
|
||||
private Map<String, Object> map;
|
||||
private String serializedMap;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
dumperOptions = new DumperOptions();
|
||||
dumperOptions.setIndent(2);
|
||||
dumperOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
|
||||
map = new HashMap<>();
|
||||
map.put("1", 1);
|
||||
map.put("2", 2.2);
|
||||
map.put("3", "3");
|
||||
map.put("4", new ArrayList<>());
|
||||
map.put("5", Arrays.asList("5", "5", "5"));
|
||||
map.put("6", new HashMap<>());
|
||||
|
||||
Map<String, Object> subMap = new HashMap<>();
|
||||
subMap.put("1", 1);
|
||||
subMap.put("2", 2.2);
|
||||
subMap.put("3", "3");
|
||||
subMap.put("4", new ArrayList<>());
|
||||
subMap.put("5", Arrays.asList("5", "5", "5"));
|
||||
subMap.put("6", new HashMap<>());
|
||||
|
||||
map.put("7", subMap);
|
||||
|
||||
serializedMap = "'1': 1\n" +
|
||||
"'2': 2.2\n" +
|
||||
"'3': '3'\n" +
|
||||
"'4': []\n" +
|
||||
"'5':\n" +
|
||||
"- '5'\n" +
|
||||
"- '5'\n" +
|
||||
"- '5'\n" +
|
||||
"'6': {}\n" +
|
||||
"'7':\n" +
|
||||
" '1': 1\n" +
|
||||
" '2': 2.2\n" +
|
||||
" '3': '3'\n" +
|
||||
" '4': []\n" +
|
||||
" '5':\n" +
|
||||
" - '5'\n" +
|
||||
" - '5'\n" +
|
||||
" - '5'\n" +
|
||||
" '6': {}\n";
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorRequiresNonNullBaseConstructor() throws Exception {
|
||||
expectedException.expect(NullPointerException.class);
|
||||
new YamlSerializer(null, new Representer(), new DumperOptions(), new Resolver());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorRequiresNonNullRepresenter() throws Exception {
|
||||
expectedException.expect(NullPointerException.class);
|
||||
new YamlSerializer(new Constructor(), new Representer(), null, new Resolver());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorRequiresNonNullDumperOptions() throws Exception {
|
||||
expectedException.expect(NullPointerException.class);
|
||||
new YamlSerializer(new Constructor(), null, new DumperOptions(), new Resolver());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorRequiresNonNullResolver() throws Exception {
|
||||
expectedException.expect(NullPointerException.class);
|
||||
new YamlSerializer(new Constructor(), new Representer(), new DumperOptions(), null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serialize() throws Exception {
|
||||
String s = new YamlSerializer(
|
||||
new Constructor(), new Representer(), dumperOptions, new Resolver()
|
||||
).serialize(map);
|
||||
assertThat(s, is(serializedMap));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deserialize() throws Exception {
|
||||
assertThat(new YamlSerializer(
|
||||
new Constructor(), new Representer(), dumperOptions, new Resolver()
|
||||
).deserialize(serializedMap), is(map));
|
||||
}
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
package de.exlll.configlib.classes;
|
||||
|
||||
import de.exlll.configlib.ConfigList;
|
||||
import de.exlll.configlib.ConfigMap;
|
||||
import de.exlll.configlib.ConfigSet;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class ConfigTypeClass {
|
||||
public ConfigList<String> configListSimple = new ConfigList<>(String.class);
|
||||
public ConfigSet<String> configSetSimple = new ConfigSet<>(String.class);
|
||||
public ConfigMap<String, String> configMapSimple = new ConfigMap<>(String.class, String.class);
|
||||
public ConfigList<A> configList = new ConfigList<>(A.class);
|
||||
public ConfigSet<A> configSet = new ConfigSet<>(A.class);
|
||||
public ConfigMap<String, A> configMap = new ConfigMap<>(String.class, A.class);
|
||||
|
||||
public ConfigTypeClass() {
|
||||
configListSimple.add("a");
|
||||
configSetSimple.add("b");
|
||||
configMapSimple.put("c", "d");
|
||||
|
||||
configList.add(from("e"));
|
||||
configSet.add(from("f"));
|
||||
configMap.put("g", from("h"));
|
||||
}
|
||||
|
||||
public static Map<String, Object> newValues() {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
|
||||
List<String> configListSimple = new ConfigList<>(String.class);
|
||||
configListSimple.add("b");
|
||||
Set<String> configSetSimple = new ConfigSet<>(String.class);
|
||||
configSetSimple.add("c");
|
||||
Map<String, String> configMapSimple = new ConfigMap<>(String.class, String.class);
|
||||
configMapSimple.put("d", "e");
|
||||
List<A> configList = new ConfigList<>(A.class);
|
||||
configList.add(from("f"));
|
||||
Set<A> configSet = new ConfigSet<>(A.class);
|
||||
configSet.add(from("g"));
|
||||
Map<String, A> configMap = new ConfigMap<>(String.class, A.class);
|
||||
configMap.put("h", from("i"));
|
||||
|
||||
map.put("configListSimple", configListSimple);
|
||||
map.put("configSetSimple", configSetSimple);
|
||||
map.put("configMapSimple", configMapSimple);
|
||||
map.put("configList", configList);
|
||||
map.put("configSet", configSet);
|
||||
map.put("configMap", configMap);
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
public static A from(String string) {
|
||||
A a = new A();
|
||||
a.string = string;
|
||||
return a;
|
||||
}
|
||||
|
||||
public static final class A {
|
||||
private String string = "string";
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "A{" +
|
||||
"string='" + string + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
A a = (A) o;
|
||||
|
||||
return string.equals(a.string);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return string.hashCode();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
package de.exlll.configlib.classes;
|
||||
|
||||
import de.exlll.configlib.Configuration;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.*;
|
||||
|
||||
public class DefaultTypeClass extends Configuration {
|
||||
private boolean bool = true;
|
||||
private char c = 'c';
|
||||
private byte b = 1;
|
||||
private short s = 2;
|
||||
private int i = 3;
|
||||
private long l = 4;
|
||||
private float f = 5.0f;
|
||||
private double d = 6.0;
|
||||
private Boolean boolObject = true;
|
||||
private Character cObject = 'c';
|
||||
private Byte bObject = 1;
|
||||
private Short sObject = 2;
|
||||
private Integer iObject = 3;
|
||||
private Long lObject = 4L;
|
||||
private Float fObject = 5.0f;
|
||||
private Double dObject = 6.0;
|
||||
private String string = "string";
|
||||
private List<String> list = new ArrayList<>();
|
||||
private Set<String> set = new HashSet<>();
|
||||
private Map<String, String> map = new HashMap<>();
|
||||
|
||||
public DefaultTypeClass(Path path) {
|
||||
super(path);
|
||||
list.add("a");
|
||||
set.add("b");
|
||||
map.put("c", "d");
|
||||
}
|
||||
|
||||
public static Map<String, Object> newValues() {
|
||||
Map<String, Object> map = new HashMap<>();
|
||||
map.put("bool", false);
|
||||
map.put("c", 'd');
|
||||
map.put("b", (byte) 2);
|
||||
map.put("s", (short) 3);
|
||||
map.put("i", 4);
|
||||
map.put("l", 5L);
|
||||
map.put("f", 6.0f);
|
||||
map.put("d", 7.0);
|
||||
map.put("boolObject", false);
|
||||
map.put("cObject", 'd');
|
||||
map.put("bObject", (byte) 2);
|
||||
map.put("sObject", (short) 3);
|
||||
map.put("iObject", 4);
|
||||
map.put("lObject", 5L);
|
||||
map.put("fObject", 6.0f);
|
||||
map.put("dObject", 7.0);
|
||||
map.put("string", "string2");
|
||||
List<String> list = new ArrayList<>();
|
||||
list.add("b");
|
||||
map.put("list", list);
|
||||
Set<String> set = new HashSet<>();
|
||||
set.add("c");
|
||||
map.put("set", set);
|
||||
Map<String, String> map2 = new HashMap<>();
|
||||
map2.put("d", "e");
|
||||
map.put("map", map2);
|
||||
return map;
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package de.exlll.configlib.classes;
|
||||
|
||||
import de.exlll.configlib.Configuration;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class NonDefaultTypeClass extends Configuration {
|
||||
public DefaultTypeClass defaultTypeClass;
|
||||
|
||||
public NonDefaultTypeClass(Path configPath) {
|
||||
super(configPath);
|
||||
this.defaultTypeClass = new DefaultTypeClass(configPath);
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
package de.exlll.configlib.classes;
|
||||
|
||||
import de.exlll.configlib.Configuration;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public class SimpleTypesClass extends Configuration {
|
||||
private boolean bool = true;
|
||||
private char c = 'c';
|
||||
private byte b = 1;
|
||||
private short s = 2;
|
||||
private int i = 3;
|
||||
private long l = 4;
|
||||
private float f = 5.0f;
|
||||
private double d = 6.0;
|
||||
private Boolean boolObject = true;
|
||||
private Character cObject = 'c';
|
||||
private Byte bObject = 1;
|
||||
private Short sObject = 2;
|
||||
private Integer iObject = 3;
|
||||
private Long lObject = 4L;
|
||||
private Float fObject = 5.0f;
|
||||
private Double dObject = 6.0;
|
||||
private String string = "string";
|
||||
|
||||
public SimpleTypesClass(Path configPath) {
|
||||
super(configPath);
|
||||
}
|
||||
}
|
@ -0,0 +1,492 @@
|
||||
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 java.util.stream.Collectors.toCollection;
|
||||
|
||||
@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 = linkedHashSetOf(1, 2, 3);
|
||||
TEST_VALUES.strings = List.of("a", "b", "c");
|
||||
TEST_VALUES.doubleByBool = linkedHashMap(true, 1.0, false, 2.0);
|
||||
/* containers of other types */
|
||||
TEST_VALUES.subClassSet = linkedHashSetOf(
|
||||
TestSubClass.of(1, "1"), TestSubClass.of(2, "2")
|
||||
);
|
||||
TEST_VALUES.subClassList = List.of(
|
||||
TestSubClass.of(1, "1"), TestSubClass.of(2, "2")
|
||||
);
|
||||
TEST_VALUES.subClassMap = linkedHashMap(
|
||||
"1", TestSubClass.of(1, "1"),
|
||||
"2", TestSubClass.of(2, "2")
|
||||
);
|
||||
/* nested containers of simple types */
|
||||
TEST_VALUES.listsList = List.of(
|
||||
List.of(1, 2), List.of(3, 4)
|
||||
);
|
||||
TEST_VALUES.setsSet = linkedHashSetOf(
|
||||
linkedHashSetOf("a", "b"), linkedHashSetOf("c", "d")
|
||||
);
|
||||
TEST_VALUES.mapsMap = linkedHashMap(
|
||||
1, Map.of("1", 1), 2, Map.of("2", 2)
|
||||
);
|
||||
/* nested containers of custom types */
|
||||
TEST_VALUES.subClassListsList = List.of(
|
||||
List.of(TestSubClass.of(1, "1"), TestSubClass.of(2, "2"))
|
||||
);
|
||||
TEST_VALUES.subClassSetsSet = linkedHashSetOf(linkedHashSetOf(
|
||||
TestSubClass.of(1, "1"), TestSubClass.of(2, "2")
|
||||
));
|
||||
TEST_VALUES.subClassMapsMap = linkedHashMap(
|
||||
1, Map.of("1", TestSubClass.of(1, "2")),
|
||||
2, Map.of("2", TestSubClass.of(2, "2"))
|
||||
);
|
||||
TEST_VALUES.e1 = TestEnum.NON_DEFAULT;
|
||||
TEST_VALUES.enums = List.of(TestEnum.DEFAULT, TestEnum.NON_DEFAULT);
|
||||
TEST_VALUES.converterSubClass = TestSubClass.of(2, "2");
|
||||
TEST_VALUES.excludedClass = TestExcludedClass.TEST_VALUES;
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private static <T> Set<T> linkedHashSetOf(T... values) {
|
||||
return Arrays.stream(values).collect(toCollection(LinkedHashSet::new));
|
||||
}
|
||||
|
||||
private static <K, V> Map<K, V> linkedHashMap(K k1, V v1, K k2, V v2) {
|
||||
Map<K, V> map = new LinkedHashMap<>();
|
||||
map.put(k1, v1);
|
||||
map.put(k2, v2);
|
||||
return map;
|
||||
}
|
||||
|
||||
/* 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(TestSubClass.class)
|
||||
private List<List<TestSubClass>> subClassListsList = new ArrayList<>();
|
||||
@ElementType(TestSubClass.class)
|
||||
private Set<Set<TestSubClass>> subClassSetsSet = new HashSet<>();
|
||||
@ElementType(TestSubClass.class)
|
||||
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 +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
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 + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
package de.exlll.configlib.classes;
|
||||
|
||||
import de.exlll.configlib.annotation.ConfigurationElement;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@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";
|
||||
}
|
||||
|
||||
private final int finalInt = 1;
|
||||
private int primInt;
|
||||
private String string = "";
|
||||
|
||||
public static TestSubClass of(int primInt, String string) {
|
||||
TestSubClass subClass = new TestSubClass();
|
||||
subClass.primInt = primInt;
|
||||
subClass.string = string;
|
||||
return subClass;
|
||||
}
|
||||
|
||||
public Map<String, Object> asMap() {
|
||||
return Map.of("primInt", primInt, "string", string);
|
||||
}
|
||||
|
||||
public int getFinalInt() {
|
||||
return finalInt;
|
||||
}
|
||||
|
||||
public int getPrimInt() {
|
||||
return primInt;
|
||||
}
|
||||
|
||||
public String getString() {
|
||||
return string;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TestSubClass{" +
|
||||
"\nprimInt=" + primInt +
|
||||
",\nstring='" + string + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof TestSubClass)) return false;
|
||||
|
||||
TestSubClass subClass = (TestSubClass) o;
|
||||
|
||||
if (primInt != subClass.primInt) return false;
|
||||
return string.equals(subClass.string);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = primInt;
|
||||
result = 31 * result + string.hashCode();
|
||||
return result;
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
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";
|
||||
}
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,324 @@
|
||||
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 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 java.util.List;
|
||||
|
||||
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() throws IOException {
|
||||
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() throws IOException {
|
||||
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() throws IOException {
|
||||
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() throws IOException {
|
||||
setupConfigPath();
|
||||
Configuration configuration = new TestClass(configPath);
|
||||
assertThat(configuration, is(not(TestClass.TEST_VALUES)));
|
||||
configuration.load();
|
||||
assertThat(configuration, is((TestClass.TEST_VALUES)));
|
||||
}
|
||||
|
||||
private void setupConfigPath() throws IOException {
|
||||
Configuration configuration = new TestClass(
|
||||
configPath, TestClass.TEST_VALUES
|
||||
);
|
||||
configuration.save();
|
||||
}
|
||||
|
||||
@Test
|
||||
void loadThrowsExceptionIfTypesDontMatch() throws IOException {
|
||||
Configuration configuration = new TestClass(configPath);
|
||||
configuration.save();
|
||||
assertThrows(IllegalArgumentException.class, configuration::load);
|
||||
}
|
||||
|
||||
@Test
|
||||
void saveCreatesConfig() throws IOException {
|
||||
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(List.of("AB", "", "CD"))
|
||||
.setAppendedComments(List.of("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));
|
||||
}
|
||||
|
||||
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 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" +
|
||||
"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" +
|
||||
" : null\n" +
|
||||
" ? primInt: 2\n" +
|
||||
" string: '2'\n" +
|
||||
" : null\n" +
|
||||
"subClassList:\n" +
|
||||
"- primInt: 1\n" +
|
||||
" string: '1'\n" +
|
||||
"- primInt: 2\n" +
|
||||
" string: '2'\n" +
|
||||
"subClassMap:\n" +
|
||||
" '1':\n" +
|
||||
" primInt: 1\n" +
|
||||
" string: '1'\n" +
|
||||
" '2':\n" +
|
||||
" primInt: 2\n" +
|
||||
" string: '2'\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: 1\n" +
|
||||
" string: '1'\n" +
|
||||
" - primInt: 2\n" +
|
||||
" string: '2'\n" +
|
||||
"subClassSetsSet: !!set\n" +
|
||||
" ? !!set\n" +
|
||||
" ? primInt: 1\n" +
|
||||
" string: '1'\n" +
|
||||
" : null\n" +
|
||||
" ? primInt: 2\n" +
|
||||
" string: '2'\n" +
|
||||
" : null\n" +
|
||||
" : null\n" +
|
||||
"subClassMapsMap:\n" +
|
||||
" 1:\n" +
|
||||
" '1':\n" +
|
||||
" primInt: 1\n" +
|
||||
" string: '2'\n" +
|
||||
" 2:\n" +
|
||||
" '2':\n" +
|
||||
" primInt: 2\n" +
|
||||
" string: '2'\n" +
|
||||
"e1: NON_DEFAULT\n" +
|
||||
"enums:\n" +
|
||||
"- DEFAULT\n" +
|
||||
"- NON_DEFAULT\n" +
|
||||
"converterSubClass: '2:2'\n" +
|
||||
"excludedClass: !!de.exlll.configlib.classes.TestExcludedClass\n" +
|
||||
" primInt: 1\n" +
|
||||
" string: string";
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
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 java.util.Map;
|
||||
|
||||
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), Map.of());
|
||||
assertThat(Files.exists(parentDir), is(true));
|
||||
}
|
||||
}
|
@ -1,173 +1,457 @@
|
||||
# ConfigLib
|
||||
This library facilitates creating, saving and loading YAML configuration files. It does so
|
||||
by using Reflection on configuration classes and automatically saving and loading their field
|
||||
names and values, creating the configuration file and its parent directories if necessary.
|
||||
# ConfigLib v2
|
||||
|
||||
**A Bukkit and BungeeCord library for storing and loading configurations**
|
||||
|
||||
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.
|
||||
|
||||
Currently this library only supports storing configurations as YAML. However, users may provide their own
|
||||
storage systems.
|
||||
|
||||
## Features
|
||||
- automatic creation, saving and loading of YAML configurations
|
||||
- automatic creation of parent directories
|
||||
- option to add explanatory comments by adding annotations to the class or its fields
|
||||
- option to exclude fields by making them final, static or transient
|
||||
- option to change the style of the configuration file
|
||||
- option to version configuration files and change the way updates are applied
|
||||
* 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 `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)
|
||||
|
||||
## General information
|
||||
#### What can be serialized?
|
||||
You can add fields to your configuration class whose type is one of the following:
|
||||
- a simple type, which are all primitive types (e.g. `boolean`, `int`), their wrapper types (e.g.
|
||||
`Boolean`, `Integer`) and strings
|
||||
- `List`s, `Set`s and `Map`s of simple types (e.g `List<Double>`) or other lists, sets and maps
|
||||
(e.g. `List<List<Map<String, Integer>>>`)
|
||||
- custom types which have a no-argument constructor
|
||||
- `ConfigList`s, `ConfigSet`s and `ConfigMap`s of custom types
|
||||
|
||||
If you want to use lists, sets or maps containing objects of custom types,
|
||||
you have to use `ConfigList`, `ConfigSet` or `ConfigMap`, respectively. If you don't use these
|
||||
special classes for storing custom objects, the stored objects won't be properly (de-)serialized.
|
||||
#### Default and null values
|
||||
All reference type fields of a configuration class must be assigned non-null default values.
|
||||
If any value is `null`, (de-)serialization will fail with a `NullPointerException`.
|
||||
#### Serialization of custom classes
|
||||
You can add fields to your configuration class whose type is some custom class.
|
||||
`@Comment`s added to custom classes or their fields are ignored and won't be
|
||||
displayed in the configuration file.
|
||||
## How-to
|
||||
You can find a step-by-step tutorial here:
|
||||
[Tutorial](https://github.com/Exlll/ConfigLib/wiki/Tutorial)
|
||||
#### Creating a configuration
|
||||
To create a new configuration, create a class which extends `Configuration`. Fields which are
|
||||
not `final`, `static` or `transient` and whose type is one of the above can automatically be saved
|
||||
to the corresponding configuration file.
|
||||
#### Saving and loading a configuration
|
||||
#### 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_)
|
||||
#### 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` updates all fields of an instance with the values read from the configuration file.
|
||||
- `save` dumps all field names and values to a configuration file. If the file exists, it is
|
||||
overridden; otherwise, it is created.
|
||||
- `loadAndSave` first calls `load` and then `save`, which is useful when you have added or
|
||||
removed fields from the class or you simply don't know if the configuration file exists.
|
||||
- `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
|
||||
In order to add or to remove fields, you just need to add them to or remove them from your
|
||||
configuration class. The changes are saved to the configuration file the next time `save` or
|
||||
`loadAndSave` is called.
|
||||
#### Post load action
|
||||
You can override `postLoadHook` to execute some action after the configuration has successfully
|
||||
been loaded.
|
||||
#### Comments
|
||||
By using the `@Comment` annotation, you can add comments to your configuration file. The
|
||||
annotation can be applied to classes or fields. Each `String` of the passed array is
|
||||
written into a new line.
|
||||
#### Versioning
|
||||
Use the `@Version` annotation to enable versioning. Versioning lets you change the way
|
||||
how configuration files are updated when a version change is detected.
|
||||
#### Custom configuration style
|
||||
You can change the style of the configuration file by overriding the protected `create...` methods
|
||||
of your configuration class. Overriding these methods effectively changes the behavior of the
|
||||
underlying `Yaml` parser. Note that if one these methods returns `null`, a `NullPointerException`
|
||||
will be thrown.
|
||||
|
||||
For more information, consult the official
|
||||
[documentation](https://bitbucket.org/asomov/snakeyaml/wiki/Documentation).
|
||||
## Examples
|
||||
Step-by-step tutorial: [Tutorial](https://github.com/Exlll/ConfigLib/wiki/Tutorial)
|
||||
#### Example of a custom class
|
||||
```java
|
||||
public class Credentials {
|
||||
private String username = "minecraft";
|
||||
private String password = "secret";
|
||||
}
|
||||
```
|
||||
#### Example database configuration
|
||||
```java
|
||||
import de.exlll.configlib.Comment;
|
||||
import de.exlll.configlib.Configuration;
|
||||
/* other imports */
|
||||
|
||||
@Comment({
|
||||
"This is a multiline comment.",
|
||||
"It describes what the configuration is about."
|
||||
})
|
||||
@Version(version = "1.2.3")
|
||||
public final class DatabaseConfig extends Configuration {
|
||||
/* ignored fields */
|
||||
private final String ignored1 = ""; // ignored because final
|
||||
private static String ignored2 = ""; // ignored because static
|
||||
private transient String ignored3 = ""; // ignored because transient
|
||||
/* included fields */
|
||||
private String host = "localhost";
|
||||
private int port = 3306;
|
||||
@Comment("This is a single-line comment.")
|
||||
private List<String> strings = Arrays.asList("root", "local");
|
||||
@Comment({
|
||||
"This is a multiline comment.",
|
||||
"It describes what this field does."
|
||||
})
|
||||
private Map<String, List<String>> listByStrings = new HashMap<>();
|
||||
private Credentials credentials = new Credentials();
|
||||
|
||||
public DatabaseConfig(Path configPath) {
|
||||
super(configPath);
|
||||
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).
|
||||
|
||||
#### 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 = "";
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Enums are supported:
|
||||
|
||||
```java
|
||||
class MyConfiguration extends YamlConfiguration {
|
||||
private Material material = Material.AIR;
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
```java
|
||||
@ConfigurationElement
|
||||
class MyCustomClass1 {/* fields etc.*/}
|
||||
|
||||
@ConfigurationElement
|
||||
class MyCustomClass2 {
|
||||
private MyCustomClass1 cls1 = new MyCustomClass1();
|
||||
// ...
|
||||
}
|
||||
|
||||
class MyConfiguration extends YamlConfiguration {
|
||||
private MyCustomClass2 cls2 = new MyCustomClass2();
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### `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<>();
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Note: Even though sets are supported, their YAML-representation is 'pretty ugly', so it's better to use lists instead.
|
||||
If you need the set behavior, you can internally use lists and convert them to sets using the `preSave/postLoad`-hooks.
|
||||
|
||||
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.
|
||||
|
||||
```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<>();
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
Lists, sets and maps can be nested.
|
||||
```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(MyCustomClass.class)
|
||||
private List<List<MyCustomClass>> customClassListsList = new ArrayList<>();
|
||||
|
||||
@ElementType(MyCustomClass.class)
|
||||
private Set<Set<MyCustomClass>> customClassSetsSet = new HashSet<>();
|
||||
|
||||
@ElementType(MyCustomClass.class)
|
||||
private Map<Integer, Map<String, MyCustomClass>> customClassMapsMap = new HashMap<>();
|
||||
// ...
|
||||
}
|
||||
```
|
||||
#### 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;
|
||||
// ...
|
||||
}
|
||||
```
|
||||
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 ... */}
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### 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.
|
||||
|
||||
```java
|
||||
class MyConfiguration extends BukkitYamlConfiguration {
|
||||
@NoConvert
|
||||
private ItemStack itemStack = new ItemStack(Material.STONE, 1);
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Changing configuration properties
|
||||
To change the properties of a configuration, use the properties builder object.
|
||||
|
||||
##### 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.
|
||||
|
||||
```java
|
||||
YamlProperties properties = YamlProperties.builder()
|
||||
.setFormatter(FieldNameFormatters.LOWER_UNDERSCORE)
|
||||
// ...
|
||||
.build();
|
||||
```
|
||||
|
||||
Note: You should neither remove nor replace a formatter with one that has a different formatting style because this
|
||||
could break existing configurations.
|
||||
|
||||
##### (_YAML_) Prepending/appending text
|
||||
To prepend or append comments to a configuration file, use the `setPrependedComments` and `setAppendedComments` methods,
|
||||
respectively.
|
||||
|
||||
```java
|
||||
YamlProperties properties = YamlProperties.builder()
|
||||
.setPrependedComments(Arrays.asList("A", "B"))
|
||||
.setAppendedComments(Arrays.asList("C", "D"))
|
||||
// ...
|
||||
.build();
|
||||
```
|
||||
|
||||
##### (_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).
|
||||
|
||||
```java
|
||||
YamlProperties properties = YamlProperties.builder()
|
||||
.setConstructor(...)
|
||||
.setRepresenter(/* */)
|
||||
.setOptions(/* */)
|
||||
.setResolver(/* */)
|
||||
// ...
|
||||
.build();
|
||||
```
|
||||
|
||||
Note: Changing the configuration style may break adding comments using the `@Comment` annotation.
|
||||
|
||||
#### 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).
|
||||
|
||||
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.
|
||||
|
||||
```java
|
||||
import java.awt.Point;
|
||||
|
||||
class PointStringConverter implements Converter<Point, String> {
|
||||
@Override
|
||||
public String convertTo(Point element, ConversionInfo info) {
|
||||
return element.x + ":" + element.y;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
/* other methods */
|
||||
}
|
||||
```
|
||||
#### Example Bukkit plugin
|
||||
|
||||
To use your custom converter, pass its class to the `@Convert` annotation.
|
||||
```java
|
||||
public class ExamplePlugin extends JavaPlugin {
|
||||
class MyConfiguration extends YamlConfiguration {
|
||||
@Convert(PointStringConverter.class)
|
||||
private Point point = new Point(2, 3);
|
||||
//...
|
||||
}
|
||||
```
|
||||
|
||||
Note: Only a single converter instance is created which is cached.
|
||||
|
||||
## Example
|
||||
```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
|
||||
}
|
||||
|
||||
Credentials(String username, String password) {
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
String getUsername() { return username; }
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
Credentials getAdminAccount() { return adminAccount; }
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
Path configPath = new File(getDataFolder(), "config.yml").toPath();
|
||||
|
||||
DatabaseConfig config = new DatabaseConfig(configPath);
|
||||
try {
|
||||
config.loadAndSave();
|
||||
System.out.println(config.getPort());
|
||||
} catch (IOException e) {
|
||||
/* do something with exception */
|
||||
}
|
||||
DatabaseConfig config = new DatabaseConfig(configPath, props);
|
||||
config.loadAndSave();
|
||||
|
||||
System.out.println(config.getAdminAccount().getUsername());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Import
|
||||
#### Maven
|
||||
```xml
|
||||
<repository>
|
||||
<id>de.exlll</id>
|
||||
<url>https://repo.exlll.de/artifactory/releases/</url>
|
||||
<url>http://exlll.de:8081/artifactory/releases/</url>
|
||||
</repository>
|
||||
|
||||
<!-- for Bukkit plugins -->
|
||||
<dependency>
|
||||
<groupId>de.exlll</groupId>
|
||||
<artifactId>configlib-bukkit</artifactId>
|
||||
<version>1.4.1</version>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- for Bungee plugins -->
|
||||
<dependency>
|
||||
<groupId>de.exlll</groupId>
|
||||
<artifactId>configlib-bungee</artifactId>
|
||||
<version>1.4.1</version>
|
||||
<version>2.0.0</version>
|
||||
</dependency>
|
||||
```
|
||||
#### Gradle
|
||||
```groovy
|
||||
repositories {
|
||||
maven {
|
||||
url 'https://repo.exlll.de/artifactory/releases/'
|
||||
url 'http://exlll.de:8081/artifactory/releases/'
|
||||
}
|
||||
}
|
||||
dependencies {
|
||||
// for Bukkit plugins
|
||||
compile group: 'de.exlll', name: 'configlib-bukkit', version: '1.4.1'
|
||||
compile group: 'de.exlll', name: 'configlib-bukkit', version: '2.0.0'
|
||||
|
||||
// for Bungee plugins
|
||||
compile group: 'de.exlll', name: 'configlib-bungee', version: '1.4.1'
|
||||
compile group: 'de.exlll', name: 'configlib-bungee', version: '2.0.0'
|
||||
}
|
||||
```
|
||||
Additionally, you either have to import the Bukkit or BungeeCord API
|
||||
or disable transitive lookups. This project uses both of these APIs, so if you
|
||||
need an example of how to import them with Gradle, take a look at the `build.gradle`.
|
||||
|
||||
If, for some reason, you have SSL errors that you're unable to resolve, you can
|
||||
use `http://exlll.de:8081/artifactory/releases/` as the repository instead.
|
||||
```
|
@ -1,248 +0,0 @@
|
||||
## Creating a configuration
|
||||
|
||||
Let's say you want to create the following web chat configuration:
|
||||
```yaml
|
||||
# This is the default WebChat configuration
|
||||
# Author: John Doe
|
||||
|
||||
ipAddress: 127.0.0.1
|
||||
port: 12345
|
||||
# Usernames mapped to users
|
||||
users:
|
||||
User1:
|
||||
email: user1@example.com
|
||||
credentials:
|
||||
username: User1
|
||||
password: '12345'
|
||||
User2:
|
||||
email: user2@example.com
|
||||
credentials:
|
||||
username: User2
|
||||
password: '54321'
|
||||
User3:
|
||||
email: user3@example.com
|
||||
credentials:
|
||||
username: User3
|
||||
password: '51423'
|
||||
channels:
|
||||
- id: 1
|
||||
name: Channel1
|
||||
owner: User1
|
||||
members:
|
||||
- User2
|
||||
- User3
|
||||
- id: 2
|
||||
name: Channel2
|
||||
owner: User2
|
||||
members:
|
||||
- User1
|
||||
|
||||
# Current version - DON'T TOUCH!
|
||||
my_version: '1.2.3-alpha'
|
||||
```
|
||||
### 1. Create a configuration
|
||||
Create a class which extends `de.exlll.configlib.Configuration`.
|
||||
```java
|
||||
import de.exlll.configlib.Configuration;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public final class WebchatConfig extends Configuration {
|
||||
public WebchatConfig(Path configPath) {
|
||||
super(configPath);
|
||||
}
|
||||
}
|
||||
```
|
||||
### 2. Create custom classes
|
||||
Create some classes to hold the necessary information and assign default values to their
|
||||
fields. Be aware that custom classes must have a no-arguments constructor.
|
||||
```java
|
||||
import de.exlll.configlib.Configuration;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
|
||||
public final class WebchatConfig extends Configuration {
|
||||
public WebchatConfig(Path configPath) {
|
||||
super(configPath);
|
||||
}
|
||||
|
||||
public static final class User {
|
||||
private String email = "";
|
||||
private Credentials credentials = new Credentials();
|
||||
}
|
||||
|
||||
public static final class Credentials {
|
||||
private String username = "";
|
||||
private String password = "";
|
||||
}
|
||||
|
||||
public static final class Channel {
|
||||
private int id; // channel id
|
||||
private String name = ""; // channel name
|
||||
private String owner = ""; // username of the owner
|
||||
private List<String> members = new ArrayList<>(); // other usernames
|
||||
}
|
||||
}
|
||||
```
|
||||
### 3. Add fields
|
||||
You can add fields to a configuration class whose type is one of the following:
|
||||
- a simple type, which are all primitive types (e.g. `boolean`, `int`), their wrapper types (e.g.
|
||||
`Boolean`, `Integer`) and strings
|
||||
- `List`s, `Set`s and `Map`s of simple types (e.g `List<Double>`) or other lists, sets and maps
|
||||
(e.g. `List<List<Map<String, Integer>>>`)
|
||||
- custom types which have a no-argument constructor,
|
||||
- `ConfigList`s, `ConfigSet`s and `ConfigMap`s of custom types
|
||||
|
||||
If you want to use lists, sets or maps containing objects of custom types,
|
||||
you have to use `ConfigList`, `ConfigSet` or `ConfigMap`, respectively. If you don't use these
|
||||
special classes for storing custom objects, the stored objects won't be properly (de-)serialized.
|
||||
|
||||
If you don't want a field to be serialized, make it `final`, `static` or `transient`.
|
||||
|
||||
**NOTE:** all field values _must_ be non-null. If any value is `null`, serialization
|
||||
will fail with a `NullPointerException`.
|
||||
```java
|
||||
import de.exlll.configlib.ConfigList;
|
||||
import de.exlll.configlib.ConfigMap;
|
||||
import de.exlll.configlib.Configuration;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public final class WebchatConfig extends Configuration {
|
||||
// private String s; // fails with a NullPointerException if not assigned a value
|
||||
private String ipAddress = "127.0.0.1";
|
||||
private int port = 12345;
|
||||
private Map<String, User> users = new ConfigMap<>(String.class, User.class);
|
||||
private List<Channel> channels = new ConfigList<>(Channel.class);
|
||||
|
||||
public WebchatConfig(Path configPath) {
|
||||
super(configPath);
|
||||
}
|
||||
// ... remainder unchanged
|
||||
}
|
||||
```
|
||||
### 4. Add comments
|
||||
Comments can only be added to the configuration class or its fields.
|
||||
Comments you add to other custom classes or their fields will be ignored.
|
||||
```java
|
||||
import de.exlll.configlib.Comment;
|
||||
import de.exlll.configlib.ConfigList;
|
||||
import de.exlll.configlib.ConfigMap;
|
||||
import de.exlll.configlib.Configuration;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Comment({
|
||||
"This is the default WebChat configuration",
|
||||
"Author: John Doe"
|
||||
})
|
||||
public final class WebchatConfig extends Configuration {
|
||||
private String ipAddress = "127.0.0.1";
|
||||
private int port = 12345;
|
||||
@Comment("Usernames mapped to users")
|
||||
private Map<String, User> users = new ConfigMap<>(String.class, User.class);
|
||||
private List<Channel> channels = new ConfigList<>(Channel.class);
|
||||
|
||||
public WebchatConfig(Path configPath) {
|
||||
super(configPath);
|
||||
}
|
||||
|
||||
/* other classes and methods */
|
||||
}
|
||||
```
|
||||
### 5. Add default values
|
||||
Add some default values for lists, sets and maps.
|
||||
```java
|
||||
/* imports */
|
||||
public final class WebchatConfig extends Configuration {
|
||||
/* fields */
|
||||
|
||||
public WebchatConfig(Path configPath) {
|
||||
super(configPath);
|
||||
|
||||
Channel channel1 = createNewChannel(1, "Channel1", "User1",
|
||||
Arrays.asList("User2", "User3"));
|
||||
Channel channel2 = createNewChannel(2, "Channel2", "User2",
|
||||
Arrays.asList("User1"));
|
||||
channels.add(channel1);
|
||||
channels.add(channel2);
|
||||
|
||||
User user1 = createNewUser("user1@example.com", "User1", "12345");
|
||||
User user2 = createNewUser("user2@example.com", "User2", "54321");
|
||||
User user3 = createNewUser("user3@example.com", "User3", "51423");
|
||||
|
||||
users.put(user1.credentials.username, user1);
|
||||
users.put(user2.credentials.username, user2);
|
||||
users.put(user3.credentials.username, user3);
|
||||
}
|
||||
|
||||
private Channel createNewChannel(int id, String name, String owner,
|
||||
List<String> members) {
|
||||
Channel channel = new Channel();
|
||||
channel.id = id;
|
||||
channel.name = name;
|
||||
channel.owner = owner;
|
||||
channel.members = members;
|
||||
return channel;
|
||||
}
|
||||
|
||||
private User createNewUser(String email, String username, String password) {
|
||||
User user = new User();
|
||||
user.email = email;
|
||||
user.credentials.username = username;
|
||||
user.credentials.password = password;
|
||||
return user;
|
||||
}
|
||||
|
||||
/* other classes and methods */
|
||||
}
|
||||
```
|
||||
|
||||
### 6. Add the version
|
||||
```java
|
||||
import de.exlll.configlib.Configuration;
|
||||
import de.exlll.configlib.Version;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
@Version(
|
||||
version = "1.2.3-alpha",
|
||||
fieldName = "my_version",
|
||||
fieldComments = {
|
||||
"" /* empty line */,
|
||||
"Current version - DON'T TOUCH!"
|
||||
}
|
||||
)
|
||||
public final class WebchatConfig extends Configuration {
|
||||
// ... remainder unchanged
|
||||
}
|
||||
```
|
||||
|
||||
### 7. Create an instance of your configuration
|
||||
Create a `java.nio.file.Path` object and pass it to the configuration constructor.
|
||||
```java
|
||||
/* imports */
|
||||
public final class WebchatConfig extends Configuration {
|
||||
/*...*/
|
||||
public static void main(String[] args) {
|
||||
File configFolder = new File("folder");
|
||||
File configFile = new File(configFolder, "config.yml");
|
||||
Path path = configFile.toPath();
|
||||
/* of course, you can skip the above steps and directly
|
||||
* create a Path object using Paths.get(...) */
|
||||
WebchatConfig config = new WebchatConfig(path);
|
||||
try {
|
||||
config.loadAndSave();
|
||||
System.out.println(config.getIpAddress());
|
||||
System.out.println(config.getPort());
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
@ -0,0 +1,7 @@
|
||||
This section covers some the advanced features of this library.
|
||||
|
||||
## Converters
|
||||
|
||||
|
||||
## Configuration sources
|
||||
|
Loading…
Reference in New Issue