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
|
name: ConfigLib
|
||||||
author: Exlll
|
author: Exlll
|
||||||
|
|
||||||
version: 1.4.1
|
version: 2.0.0
|
||||||
main: de.exlll.configlib.ConfigLib
|
main: de.exlll.configlib.ConfigLib
|
@ -1,5 +1,5 @@
|
|||||||
name: ConfigLib
|
name: ConfigLib
|
||||||
author: Exlll
|
author: Exlll
|
||||||
|
|
||||||
version: 1.4.1
|
version: 2.0.0
|
||||||
main: de.exlll.configlib.ConfigLib
|
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;
|
package de.exlll.configlib;
|
||||||
|
|
||||||
import org.yaml.snakeyaml.DumperOptions;
|
import de.exlll.configlib.format.FieldNameFormatter;
|
||||||
import org.yaml.snakeyaml.Yaml;
|
import de.exlll.configlib.format.FieldNameFormatters;
|
||||||
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 java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.util.*;
|
||||||
import java.nio.file.NoSuchFileException;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public abstract class Configuration {
|
/**
|
||||||
private final Path configPath;
|
* Parent class of all configurations.
|
||||||
private final CommentAdder adder;
|
|
||||||
private YamlSerializer serializer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs a new {@code Configuration} instance.
|
|
||||||
* <p>
|
* <p>
|
||||||
* You can use {@link java.io.File#toPath()} get a {@link Path} object
|
* This class contains the most basic methods that every configuration needs.
|
||||||
* from a {@link java.io.File}.
|
|
||||||
*
|
*
|
||||||
* @param configPath location of the configuration file
|
* @param <C> type of the configuration
|
||||||
* @throws NullPointerException if {@code configPath} is null
|
|
||||||
*/
|
*/
|
||||||
protected Configuration(Path configPath) {
|
public abstract class Configuration<C extends Configuration<C>> {
|
||||||
this.configPath = configPath;
|
/**
|
||||||
this.adder = new CommentAdder(new Comments(getClass()));
|
* {@code Comments} object containing all class and field comments
|
||||||
}
|
* of this configuration
|
||||||
|
*/
|
||||||
private void initSerializer() {
|
protected final Comments comments;
|
||||||
if (serializer == null) {
|
private final Properties props;
|
||||||
this.serializer = new YamlSerializer(
|
|
||||||
createConstructor(), createRepresenter(),
|
|
||||||
createDumperOptions(), createResolver()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads {@code this} configuration from a configuration file. The file is
|
* Constructs a new {@code Configuration} object.
|
||||||
* 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.
|
|
||||||
*
|
*
|
||||||
* @throws ClassCastException if parsed Object is not a {@code Map}
|
* @param properties {@code Properties} used to configure this configuration
|
||||||
* @throws IOException if an I/O error occurs when loading the configuration file
|
* @throws NullPointerException if {@code properties} is null
|
||||||
* @throws NullPointerException if a value of a field of this instance is null
|
|
||||||
* @throws ParserException if invalid YAML
|
|
||||||
*/
|
*/
|
||||||
public final void load() throws IOException {
|
protected Configuration(Properties properties) {
|
||||||
Map<String, Object> deserializedMap = readAndDeserialize();
|
this.props = Objects.requireNonNull(properties);
|
||||||
FieldMapper.instanceFromMap(this, deserializedMap);
|
this.comments = Comments.ofClass(getClass());
|
||||||
postLoadHook();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<String, Object> readAndDeserialize() throws IOException {
|
|
||||||
initSerializer();
|
|
||||||
String yaml = ConfigReader.read(configPath);
|
|
||||||
return serializer.deserialize(yaml);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves the comments of {@code this} class as well as the names,
|
* Saves this {@code Configuration}.
|
||||||
* 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>.
|
|
||||||
*
|
*
|
||||||
* @throws ConfigException if a name clash between a field name and the version
|
* @throws ConfigurationException if any field is not properly configured
|
||||||
* field name occurs (can only happen if versioning is used)
|
* @throws ConfigurationStoreException if an I/O error occurred while loading
|
||||||
* @throws NoSuchFileException if the old version contains illegal file path characters
|
* this configuration
|
||||||
* @throws IOException if an I/O error occurs when saving the configuration file
|
|
||||||
* @throws ParserException if invalid YAML
|
|
||||||
*/
|
*/
|
||||||
public final void save() throws IOException {
|
public final void save() {
|
||||||
initSerializer();
|
try {
|
||||||
createParentDirectories();
|
preSave();
|
||||||
Map<String, Object> map = FieldMapper.instanceToMap(this);
|
Map<String, Object> map = FieldMapper.instanceToMap(this, props);
|
||||||
version(map);
|
getSource().saveConfiguration(getThis(), map);
|
||||||
String serializedMap = serializer.serialize(map);
|
} catch (IOException e) {
|
||||||
ConfigWriter.write(configPath, adder.addComments(serializedMap));
|
throw new ConfigurationStoreException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
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)) {
|
* Loads this {@code Configuration}.
|
||||||
String msg = "Problem: Configuration '" + this + "' cannot be " +
|
*
|
||||||
"saved because one its fields has the same name as the " +
|
* @throws ConfigurationException if values cannot be converted back to their
|
||||||
"version field: '" + vfn + "'.\nSolution: Rename the " +
|
* original representation
|
||||||
"field or use a different version field name.";
|
* @throws ConfigurationStoreException if an I/O error occurred while loading
|
||||||
throw new ConfigException(msg);
|
* this configuration
|
||||||
|
*/
|
||||||
|
public final void load() {
|
||||||
|
try {
|
||||||
|
Map<String, Object> map = getSource().loadConfiguration(getThis());
|
||||||
|
FieldMapper.instanceFromMap(this, map, props);
|
||||||
|
postLoad();
|
||||||
|
} 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());
|
* Returns the {@link ConfigurationSource} used for saving and loading this
|
||||||
}
|
* {@code Configuration}.
|
||||||
|
*
|
||||||
|
* @return {@code ConfigurationSource} used for saving and loading
|
||||||
|
*/
|
||||||
|
protected abstract ConfigurationSource<C> getSource();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads and saves {@code this} configuration.
|
* Returns this {@code Configuration}.
|
||||||
* <p>
|
|
||||||
* This method first calls {@link #load()} and then {@link #save()}.
|
|
||||||
*
|
*
|
||||||
* @throws ClassCastException if parsed Object is not a {@code Map}
|
* @return this {@code Configuration}
|
||||||
* @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()
|
|
||||||
*/
|
*/
|
||||||
public final void loadAndSave() throws IOException {
|
protected abstract C getThis();
|
||||||
try {
|
|
||||||
load();
|
|
||||||
save();
|
|
||||||
} catch (NoSuchFileException e) {
|
|
||||||
postLoadHook();
|
|
||||||
save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Protected method invoked after all fields have successfully been loaded.
|
* Hook that is executed right before this {@code Configuration} is saved.
|
||||||
* <p>
|
* <p>
|
||||||
* The default implementation of this method does nothing. Subclasses may
|
* The default implementation of this method does nothing.
|
||||||
* override this method in order to execute some action after all fields
|
|
||||||
* have successfully been loaded.
|
|
||||||
*/
|
*/
|
||||||
protected void postLoadHook() {}
|
protected void preSave() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a {@link BaseConstructor} which is used to configure a
|
* Hook that is executed right after this {@code Configuration} has
|
||||||
* {@link Yaml} object.
|
* successfully been loaded.
|
||||||
* <p>
|
* <p>
|
||||||
* Override this method to change the way the {@code Yaml} object is created.
|
* The default implementation of this method does nothing.
|
||||||
* <p>
|
*/
|
||||||
* This method may not return null.
|
protected void postLoad() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instances of a {@code Properties} class are used to configure different
|
||||||
|
* aspects of a configuration.
|
||||||
|
*/
|
||||||
|
protected static class Properties {
|
||||||
|
private final FieldNameFormatter formatter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a new {@code Properties} object.
|
||||||
*
|
*
|
||||||
* @return a {@code BaseConstructor} object
|
* @param builder {@code Builder} used for construction
|
||||||
* @see org.yaml.snakeyaml.constructor.BaseConstructor
|
* @throws NullPointerException if {@code builder} is null
|
||||||
* @see #createRepresenter()
|
|
||||||
* @see #createDumperOptions()
|
|
||||||
* @see #createResolver()
|
|
||||||
*/
|
*/
|
||||||
protected BaseConstructor createConstructor() {
|
protected Properties(Builder<?> builder) {
|
||||||
return new Constructor();
|
this.formatter = builder.formatter;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Builder<?> builder() {
|
||||||
|
return new Builder() {
|
||||||
|
@Override
|
||||||
|
protected Builder<?> getThis() {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a {@link Representer} which is used to configure a {@link Yaml}
|
* Returns the {@code FieldNameFormatter} of a configuration.
|
||||||
* 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 Representer} object
|
* @return {@code FieldNameFormatter} of a configuration
|
||||||
* @see org.yaml.snakeyaml.representer.Representer
|
|
||||||
* @see #createConstructor()
|
|
||||||
* @see #createDumperOptions()
|
|
||||||
* @see #createResolver()
|
|
||||||
*/
|
*/
|
||||||
protected Representer createRepresenter() {
|
public final FieldNameFormatter getFormatter() {
|
||||||
return new Representer();
|
return formatter;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a {@link DumperOptions} object which is used to configure a
|
* Builder classes are used for constructing {@code Properties}.
|
||||||
* {@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 DumperOptions} object
|
* @param <B> type of the builder
|
||||||
* @see org.yaml.snakeyaml.DumperOptions
|
|
||||||
* @see #createConstructor()
|
|
||||||
* @see #createRepresenter()
|
|
||||||
* @see #createResolver()
|
|
||||||
*/
|
*/
|
||||||
protected DumperOptions createDumperOptions() {
|
protected static abstract class Builder<B extends Builder<B>> {
|
||||||
DumperOptions options = new DumperOptions();
|
private FieldNameFormatter formatter = FieldNameFormatters.IDENTITY;
|
||||||
options.setIndent(2);
|
|
||||||
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
protected Builder() {}
|
||||||
return options;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a {@link Resolver} which is used to configure a {@link Yaml} object.
|
* Returns this {@code Builder}.
|
||||||
* <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
|
* @return this {@code Builder}
|
||||||
* @see org.yaml.snakeyaml.resolver.Resolver
|
|
||||||
* @see #createConstructor()
|
|
||||||
* @see #createRepresenter()
|
|
||||||
* @see #createDumperOptions()
|
|
||||||
*/
|
*/
|
||||||
protected Resolver createResolver() {
|
protected abstract B getThis();
|
||||||
return new Resolver();
|
|
||||||
}
|
|
||||||
|
|
||||||
final String currentFileVersion() throws IOException {
|
/**
|
||||||
final Version version = Reflect.getVersion(getClass());
|
* Sets the {@link FieldNameFormatter} for a configuration.
|
||||||
return (version == null) ? null : readCurrentFileVersion(version);
|
*
|
||||||
|
* @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();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String readCurrentFileVersion(Version version) throws IOException {
|
/**
|
||||||
try {
|
* Builds a new {@code Properties} instance using the values set.
|
||||||
final Map<String, Object> map = readAndDeserialize();
|
*
|
||||||
return (String) map.get(version.fieldName());
|
* @return new {@code Properties} instance
|
||||||
} catch (NoSuchFileException ignored) {
|
*/
|
||||||
/* there is no file version if the file doesn't exist */
|
public Properties build() {
|
||||||
return null;
|
return new Properties(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final Path getPath() {
|
|
||||||
return configPath;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
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.Field;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
import static de.exlll.configlib.Validator.*;
|
||||||
|
import static java.util.stream.Collectors.toList;
|
||||||
|
|
||||||
enum FieldMapper {
|
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<>();
|
Map<String, Object> map = new LinkedHashMap<>();
|
||||||
|
for (Field field : FieldFilter.filterFields(inst.getClass())) {
|
||||||
FilteredFields ff = FilteredFields.of(instance.getClass());
|
Object val = toConvertibleObject(field, inst, props);
|
||||||
|
FieldNameFormatter fnf = props.getFormatter();
|
||||||
for (Field field : ff) {
|
String fn = fnf.fromFieldName(field.getName());
|
||||||
Object value = Reflect.getValue(field, instance);
|
map.put(fn, val);
|
||||||
checkNull(field, value);
|
|
||||||
value = toSerializableObject(value);
|
|
||||||
map.put(field.getName(), value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Object toSerializableObject(Object input) {
|
private static Object toConvertibleObject(
|
||||||
if (input instanceof Defaultable<?>) {
|
Field field, Object instance, Configuration.Properties props
|
||||||
return ((Defaultable<?>) input).toDefault();
|
) {
|
||||||
} else if (Reflect.isDefault(input.getClass())) {
|
checkDefaultValueNull(field, instance);
|
||||||
return input;
|
ConversionInfo info = ConversionInfo.of(field, instance, props);
|
||||||
} else {
|
checkFieldWithElementTypeIsContainer(info);
|
||||||
return instanceToMap(input);
|
Object converted = Converters.convertTo(info);
|
||||||
}
|
checkConverterNotReturnsNull(converted, info);
|
||||||
|
return converted;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void instanceFromMap(Object instance, Map<String, ?> map) {
|
static void instanceFromMap(
|
||||||
FilteredFields ff = FilteredFields.of(instance.getClass());
|
Object inst, Map<String, Object> instMap,
|
||||||
|
Configuration.Properties props
|
||||||
for (Field field : ff) {
|
) {
|
||||||
Object value = map.get(field.getName());
|
for (Field field : FieldFilter.filterFields(inst.getClass())) {
|
||||||
fromSerializedObject(field, instance, value);
|
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) {
|
private static void fromConvertedObject(
|
||||||
if (serialized == null) {
|
Field field, Object instance, Object mapValue,
|
||||||
return; // keep default value
|
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 (Reflect.isContainerType(info.getFieldType())) {
|
||||||
if (fieldValue instanceof Defaultable<?>) {
|
checkFieldTypeAssignableFrom(convert.getClass(), info);
|
||||||
((Defaultable<?>) fieldValue).fromDefault(serialized);
|
|
||||||
} else if (Reflect.isDefault(field.getType())) {
|
|
||||||
Reflect.setValue(field, instance, serialized);
|
|
||||||
} else {
|
|
||||||
instanceFromMap(fieldValue, castToMap(serialized));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reflect.setValue(field, instance, convert);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void checkNull(Field field, Object o) {
|
private static void checkDefaultValueNull(Field field, Object instance) {
|
||||||
if (o == null) {
|
Object val = Reflect.getValue(field, instance);
|
||||||
String msg = "The value of field " + field.getName() + " is null.\n" +
|
checkNotNull(val, field.getName());
|
||||||
"Please assign a non-null default value or remove this field.";
|
|
||||||
throw new NullPointerException(msg);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum FieldFilter implements Predicate<Field> {
|
||||||
|
DEFAULT;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
private static Map<String, Object> castToMap(Object mapObject) {
|
int mods = field.getModifiers();
|
||||||
Reflect.checkType(mapObject, Map.class);
|
return !(Modifier.isFinal(mods) ||
|
||||||
Reflect.checkMapEntries((Map<?, ?>) mapObject, String.class, Object.class);
|
Modifier.isStatic(mods) ||
|
||||||
@SuppressWarnings("unchecked")
|
Modifier.isTransient(mods));
|
||||||
Map<String, Object> m = (Map<String, Object>) mapObject;
|
}
|
||||||
return m;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
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.is;
|
||||||
import static org.hamcrest.Matchers.nullValue;
|
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
public class CommentsTest {
|
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
|
@Test
|
||||||
public void getCommentsReturnsComments() throws Exception {
|
void classCommentsAdded() {
|
||||||
Comments comments = new Comments(TestClass.class);
|
class A {}
|
||||||
|
|
||||||
|
@Comment("B")
|
||||||
|
class B {}
|
||||||
|
|
||||||
|
@Comment({"C", "D"})
|
||||||
|
class C {}
|
||||||
|
|
||||||
|
Comments comments = Comments.ofClass(A.class);
|
||||||
|
assertThat(comments.getClassComments(), empty());
|
||||||
|
assertThat(comments.getFieldComments().entrySet(), empty());
|
||||||
|
|
||||||
|
comments = Comments.ofClass(B.class);
|
||||||
|
assertThat(comments.getClassComments(), is(List.of("B")));
|
||||||
|
assertThat(comments.getFieldComments().entrySet(), empty());
|
||||||
|
|
||||||
assertThat(comments.getClassComments(), is(Arrays.asList("a", "b")));
|
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")));
|
Comments comments = Comments.ofClass(A.class);
|
||||||
assertThat(comments.getFieldComments().get("j"), is(Arrays.asList("d", "e")));
|
assertThat(comments.getClassComments(), empty());
|
||||||
assertThat(comments.getFieldComments().get("k"), nullValue());
|
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;
|
package de.exlll.configlib;
|
||||||
|
|
||||||
import com.google.common.jimfs.Jimfs;
|
import de.exlll.configlib.configs.mem.InSharedMemoryConfiguration;
|
||||||
import de.exlll.configlib.classes.DefaultTypeClass;
|
import org.junit.jupiter.api.Test;
|
||||||
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 java.nio.file.FileSystem;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
public class ConfigurationTest {
|
class ConfigurationTest {
|
||||||
private FileSystem fileSystem;
|
private static class TestHook extends InSharedMemoryConfiguration {
|
||||||
private Path configPath;
|
protected TestHook() { super(Properties.builder().build()); }
|
||||||
|
|
||||||
@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
|
@Test
|
||||||
public void simpleTypesConfigSavesAndLoads() throws Exception {
|
void configExecutesPreSaveHook() throws IOException {
|
||||||
Configuration cfg = new SimpleTypesClass(configPath);
|
class A extends TestHook {
|
||||||
cfg.save();
|
int i = 0;
|
||||||
cfg.load();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Override
|
||||||
public void defaultTypesConfigSavesAndLoads() throws Exception {
|
protected void preSave() { i++; }
|
||||||
Configuration cfg = new DefaultTypeClass(configPath);
|
|
||||||
cfg.save();
|
|
||||||
cfg.load();
|
|
||||||
}
|
}
|
||||||
|
A save = new A();
|
||||||
|
save.save();
|
||||||
|
assertThat(save.i, is(1));
|
||||||
|
|
||||||
@Test
|
A load = new A();
|
||||||
public void nonDefaultConfigSavesAndLoads() throws Exception {
|
load.load();
|
||||||
Configuration cfg = new NonDefaultTypeClass(configPath);
|
assertThat(load.i, is(1));
|
||||||
cfg.save();
|
|
||||||
cfg.load();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void loadExecutesPostLoadHook() throws Exception {
|
void configExecutesPostLoadHook() throws IOException {
|
||||||
AtomicInteger integer = new AtomicInteger();
|
class A extends TestHook {
|
||||||
Configuration cfg = new TestConfiguration(configPath, integer::incrementAndGet);
|
int i = 0;
|
||||||
|
|
||||||
cfg.save();
|
@Override
|
||||||
assertThat(integer.get(), is(0));
|
protected void postLoad() { i++; }
|
||||||
cfg.load();
|
|
||||||
assertThat(integer.get(), is(1));
|
|
||||||
}
|
}
|
||||||
|
A save = new A();
|
||||||
|
save.save();
|
||||||
|
assertThat(save.i, is(0));
|
||||||
|
|
||||||
@Test
|
A load = new A();
|
||||||
public void loadAndSaveExecutesPostLoadHook1() throws Exception {
|
load.load();
|
||||||
AtomicInteger integer = new AtomicInteger();
|
assertThat(load.i, is(1));
|
||||||
Configuration cfg = new TestConfiguration(configPath, integer::incrementAndGet);
|
|
||||||
|
|
||||||
cfg.loadAndSave();
|
|
||||||
assertThat(integer.get(), 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;
|
package de.exlll.configlib;
|
||||||
|
|
||||||
import de.exlll.configlib.classes.DefaultTypeClass;
|
import de.exlll.configlib.Converter.ConversionInfo;
|
||||||
import de.exlll.configlib.classes.NonDefaultTypeClass;
|
import de.exlll.configlib.FieldMapper.FieldFilter;
|
||||||
import org.junit.Test;
|
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.lang.reflect.Field;
|
||||||
import java.nio.file.Path;
|
import java.lang.reflect.Modifier;
|
||||||
import java.nio.file.Paths;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
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.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 {
|
@Test
|
||||||
private final Path path = Paths.get("a");
|
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
|
@Test
|
||||||
public void toSerializableObjectReturnsObjectForDefaultTypes() throws Exception {
|
void instanceFromMapSetsEnums() {
|
||||||
DefaultTypeClass instance = new DefaultTypeClass(path);
|
assertThat(tmp.getE1(), is(t.getE1()));
|
||||||
for (Field f : DefaultTypeClass.class.getDeclaredFields()) {
|
|
||||||
Object value = Reflect.getValue(f, instance);
|
|
||||||
assertThat(FieldMapper.toSerializableObject(value), sameInstance(value));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void instanceFromMapSetsContainersOfSimpleTypes() {
|
||||||
|
assertAll(
|
||||||
|
() -> assertThat(tmp.getInts(), is(t.getInts())),
|
||||||
|
() -> assertThat(tmp.getStrings(), is(t.getStrings())),
|
||||||
|
() -> assertThat(tmp.getDoubleByBool(), is(t.getDoubleByBool()))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void toSerializableObjectReturnsMapForNonDefaultTypes() throws Exception {
|
void instanceFromMapsConvertsMapsToTypes() {
|
||||||
DefaultTypeClass instance = new DefaultTypeClass(path);
|
assertThat(tmp.getSubClass(), is(t.getSubClass()));
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@Test
|
||||||
Map<String, Object> map = (Map<String, Object>) FieldMapper.toSerializableObject(instance);
|
void instanceFromMapsConvertsExcludedClasses() {
|
||||||
|
assertThat(tmp.getExcludedClass(), is(t.getExcludedClass()));
|
||||||
|
}
|
||||||
|
|
||||||
int counter = 0;
|
@Test
|
||||||
for (Field field : DefaultTypeClass.class.getDeclaredFields()) {
|
void instanceFromMapsConvertsContainersOfMaps() {
|
||||||
Object fieldValue = Reflect.getValue(field, instance);
|
assertThat(tmp.getSubClassList(), is(t.getSubClassList()));
|
||||||
assertThat(map.get(field.getName()), is(fieldValue));
|
assertThat(tmp.getSubClassSet(), is(t.getSubClassSet()));
|
||||||
counter++;
|
assertThat(tmp.getSubClassMap(), is(t.getSubClassMap()));
|
||||||
|
assertThat(tmp.getSubClassListsList(), is(t.getSubClassListsList()));
|
||||||
|
assertThat(tmp.getSubClassSetsSet(), is(t.getSubClassSetsSet()));
|
||||||
|
assertThat(tmp.getSubClassMapsMap(), is(t.getSubClassMapsMap()));
|
||||||
}
|
}
|
||||||
assertThat(map.size(), is(counter));
|
|
||||||
|
@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));
|
||||||
|
}
|
||||||
|
|
||||||
|
@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
|
@Test
|
||||||
public void fromSerializedObjectIgnoresNullValues() throws Exception {
|
void instanceFromMapIgnoresNullValues() {
|
||||||
DefaultTypeClass instance = new DefaultTypeClass(path);
|
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(""));
|
||||||
|
}
|
||||||
|
|
||||||
for (Field field : DefaultTypeClass.class.getDeclaredFields()) {
|
@Test
|
||||||
Object currentValue = Reflect.getValue(field, instance);
|
void instanceFromMapSetsField() {
|
||||||
FieldMapper.fromSerializedObject(field, instance, null);
|
TestClass ins = TestClass.TEST_VALUES;
|
||||||
Object newValue = Reflect.getValue(field, instance);
|
TestClass def = new TestClass();
|
||||||
|
assertThat(ins, is(not(def)));
|
||||||
|
instanceFromMap(def, map);
|
||||||
|
assertThat(ins, is(def));
|
||||||
|
}
|
||||||
|
|
||||||
if (field.getType().isPrimitive()) {
|
@Test
|
||||||
assertThat(currentValue, is(newValue));
|
void instanceFromMapCreatesConcreteInstances() {
|
||||||
} else {
|
class A {
|
||||||
assertThat(currentValue, sameInstance(newValue));
|
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
|
@Test
|
||||||
public void fromSerializedObjectSetsValueIfDefaultType() throws Exception {
|
void instanceToMapUsesFieldNameFormatter() {
|
||||||
DefaultTypeClass instance = new DefaultTypeClass(path);
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, Object> map = DefaultTypeClass.newValues();
|
@Test
|
||||||
for (Field field : DefaultTypeClass.class.getDeclaredFields()) {
|
void instanceToMapContainsAllFields() {
|
||||||
String fieldName = field.getName();
|
assertThat(map.size(), is(34));
|
||||||
Object mapValue = map.get(fieldName);
|
}
|
||||||
FieldMapper.fromSerializedObject(field, instance, mapValue);
|
|
||||||
Object value = Reflect.getValue(field, instance);
|
|
||||||
|
|
||||||
if (field.getType().isPrimitive()) {
|
@Test
|
||||||
assertThat(mapValue, is(value));
|
void instanceToMapDoesNotContainFinalStaticOrTransientFields() {
|
||||||
} else {
|
assertAll(
|
||||||
assertThat(mapValue, sameInstance(value));
|
() -> assertThat(map.get("staticFinalInt"), is(nullValue())),
|
||||||
|
() -> assertThat(map.get("staticInt"), is(nullValue())),
|
||||||
|
() -> assertThat(map.get("finalInt"), is(nullValue())),
|
||||||
|
() -> assertThat(map.get("transientInt"), is(nullValue()))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void instanceToMapContainsAllSimpleFields() {
|
||||||
|
assertAll(
|
||||||
|
() -> assertThat(map.get("primBool"), is(t.getPrimBool())),
|
||||||
|
() -> assertThat(map.get("refBool"), is(t.getRefBool())),
|
||||||
|
() -> assertThat(map.get("primByte"), is(t.getPrimByte())),
|
||||||
|
() -> assertThat(map.get("refByte"), is(t.getRefByte())),
|
||||||
|
() -> assertThat(map.get("primChar"), is(t.getPrimChar())),
|
||||||
|
() -> assertThat(map.get("refChar"), is(t.getRefChar())),
|
||||||
|
() -> assertThat(map.get("primShort"), is(t.getPrimShort())),
|
||||||
|
() -> assertThat(map.get("refShort"), is(t.getRefShort())),
|
||||||
|
() -> assertThat(map.get("primInt"), is(t.getPrimInt())),
|
||||||
|
() -> assertThat(map.get("refInt"), is(t.getRefInt())),
|
||||||
|
() -> assertThat(map.get("primLong"), is(t.getPrimLong())),
|
||||||
|
() -> assertThat(map.get("refLong"), is(t.getRefLong())),
|
||||||
|
() -> assertThat(map.get("primFloat"), is(t.getPrimFloat())),
|
||||||
|
() -> assertThat(map.get("refFloat"), is(t.getRefFloat())),
|
||||||
|
() -> assertThat(map.get("primDouble"), is(t.getPrimDouble())),
|
||||||
|
() -> assertThat(map.get("refDouble"), is(t.getRefDouble())),
|
||||||
|
() -> assertThat(map.get("string"), is(t.getString()))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void instanceToMapContainsAllContainersOfSimpleTypes() {
|
||||||
|
assertAll(
|
||||||
|
() -> assertThat(map.get("ints"), is(t.getInts())),
|
||||||
|
() -> assertThat(map.get("strings"), is(t.getStrings())),
|
||||||
|
() -> assertThat(map.get("doubleByBool"), is(t.getDoubleByBool()))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void fromSerializedObjectUpdatesValueIfNotDefaultType() throws Exception {
|
void instanceToMapConvertsTypesToMaps() {
|
||||||
NonDefaultTypeClass instance = new NonDefaultTypeClass(path);
|
assertThat(map.get("subClass"), is(t.getSubClass().asMap()));
|
||||||
Field field = NonDefaultTypeClass.class.getDeclaredField("defaultTypeClass");
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void instanceToMapConvertsExcludedClasses() {
|
||||||
|
assertThat(map.get("excludedClass"), is(t.getExcludedClass()));
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, Object> map = DefaultTypeClass.newValues();
|
@Test
|
||||||
FieldMapper.fromSerializedObject(field, instance, map);
|
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()) {
|
@Test
|
||||||
Field f = DefaultTypeClass.class.getDeclaredField(entry.getKey());
|
void instanceFromMapConvertsStringContainersToEnumContainers() {
|
||||||
Object value = Reflect.getValue(f, instance.defaultTypeClass);
|
class A {
|
||||||
assertThat(value, is(entry.getValue()));
|
@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
|
||||||
|
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))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void instanceTopMapCreatesMap() throws Exception {
|
void instanceToMapContainsNestedContainersOfSimpleTypes() {
|
||||||
TestClass t = new TestClass();
|
assertAll(
|
||||||
Map<String, Object> map = FieldMapper.instanceToMap(t);
|
() -> assertThat(map.get("listsList"), is(t.getListsList())),
|
||||||
|
() -> assertThat(map.get("setsSet"), is(t.getSetsSet())),
|
||||||
|
() -> assertThat(map.get("mapsMap"), is(t.getMapsMap()))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
assertThat(map.get("i"), is(1));
|
@Test
|
||||||
assertThat(map.get("i"), instanceOf(Integer.class));
|
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("z"), is(0));
|
@Test
|
||||||
assertThat(map.get("z"), instanceOf(Integer.class));
|
void instanceToMapContainsEnums() {
|
||||||
|
assertThat(map.get("e1"), is(t.getE1().toString()));
|
||||||
|
}
|
||||||
|
|
||||||
assertThat(map.get("d"), is(2.0));
|
@Test
|
||||||
assertThat(map.get("d"), instanceOf(Double.class));
|
void instanceToMapContainsEnumLists() {
|
||||||
|
List<String> list = t.getEnums().stream()
|
||||||
|
.map(Enum::toString)
|
||||||
|
.collect(toList());
|
||||||
|
assertThat(map.get("enums"), is(list));
|
||||||
|
}
|
||||||
|
|
||||||
assertThat(map.get("s"), is("s"));
|
|
||||||
assertThat(map.get("s"), instanceOf(String.class));
|
|
||||||
|
|
||||||
assertThat(map.get("c"), is('c'));
|
@Test
|
||||||
assertThat(map.get("c"), instanceOf(Character.class));
|
void instanceToMapConvertsConvertTypes() {
|
||||||
|
String s = new TestSubClassConverter()
|
||||||
|
.convertTo(t.getConverterSubClass(), null);
|
||||||
|
assertThat(map.get("converterSubClass"), is(s));
|
||||||
|
}
|
||||||
|
|
||||||
assertThat(map.get("strings"), is(Arrays.asList("1", "2")));
|
@Test
|
||||||
assertThat(map.get("strings"), instanceOf(List.class));
|
void instanceToMapCreatesLinkedHashMap() {
|
||||||
|
assertThat(instanceToMap(new Object()), instanceOf(LinkedHashMap.class));
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, Integer> intMap = new HashMap<>();
|
@Test
|
||||||
intMap.put("a", 1);
|
void filteredFieldsFiltersFields() throws NoSuchFieldException {
|
||||||
intMap.put("b", 2);
|
List<Field> fields = FieldFilter.filterFields(CWFSTF);
|
||||||
assertThat(map.get("objects"), is(intMap));
|
assertThat(fields.size(), is(0));
|
||||||
assertThat(map.get("objects"), instanceOf(Map.class));
|
|
||||||
|
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")));
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, Object> bMap = new HashMap<>();
|
@Test
|
||||||
bMap.put("j", -1);
|
void defaultFilterFiltersSyntheticFields() {
|
||||||
bMap.put("t", "t");
|
for (Field field : ClassWithSyntheticField.class.getDeclaredFields()) {
|
||||||
assertThat(map.get("b"), is(bMap));
|
assertThat(field.isSynthetic(), is(true));
|
||||||
assertThat(map.get("b"), instanceOf(Map.class));
|
assertThat(filter.test(field), is(false));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void instanceFromMapKeepsDefaultValues() throws Exception {
|
void defaultFilterFiltersFinalStaticTransientFields()
|
||||||
TestClass t = new TestClass();
|
throws NoSuchFieldException {
|
||||||
FieldMapper.instanceFromMap(t, new HashMap<>());
|
Field field = CWFSTF.getDeclaredField("i");
|
||||||
assertThat(t.z, is(0));
|
assertThat(Modifier.isFinal(field.getModifiers()), is(true));
|
||||||
assertThat(t.i, is(1));
|
assertThat(filter.test(field), is(false));
|
||||||
assertThat(t.s, is("s"));
|
|
||||||
|
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
|
@Test
|
||||||
public void instanceFromMapSetsValues() throws Exception {
|
void typeConverterReturnsInstanceIfClassesMatch() {
|
||||||
TestClass t = new TestClass();
|
//noinspection RedundantStringConstructorCall
|
||||||
|
String s = new String("123");
|
||||||
|
Object val = converter.convertFrom(s, newInfo("string"));
|
||||||
|
assertThat(val, sameInstance(s));
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, Object> map = new HashMap<>();
|
@Test
|
||||||
map.put("z", 2);
|
void typeConverterConvertsStringToCharacter() {
|
||||||
map.put("i", 10);
|
String s = "1";
|
||||||
map.put("c", 'q');
|
Object vc = converter.convertFrom(s, newInfo("primChar"));
|
||||||
map.put("s", "t");
|
Object vd = converter.convertFrom(s, newInfo("refChar"));
|
||||||
map.put("strings", Arrays.asList("99", "100", "101"));
|
|
||||||
|
|
||||||
Map<String, Object> objects = new HashMap<>();
|
assertThat(vc, instanceOf(Character.class));
|
||||||
objects.put("a", 100);
|
assertThat(vd, instanceOf(Character.class));
|
||||||
objects.put("b", 200);
|
}
|
||||||
objects.put("c", 300);
|
|
||||||
objects.put("d", 400);
|
|
||||||
map.put("objects", objects);
|
|
||||||
|
|
||||||
Map<String, Object> bMap = new HashMap<>();
|
@Test
|
||||||
bMap.put("j", 20);
|
void typeConverterChecksInvalidStrings() {
|
||||||
bMap.put("t", "v");
|
String msg = "An empty string cannot be converted to a character.";
|
||||||
map.put("b", bMap);
|
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);
|
@Test
|
||||||
assertThat(t.z, is(2));
|
void typeConverterChecksInvalidNumbers() {
|
||||||
assertThat(t.i, is(10));
|
String msg = "Number '1' cannot be converted to type 'String'";
|
||||||
assertThat(t.c, is('q'));
|
assertThrows(
|
||||||
assertThat(t.s, is("t"));
|
IllegalArgumentException.class,
|
||||||
assertThat(t.strings, is(Arrays.asList("99", "100", "101")));
|
() -> converter.convertFrom(1, newInfo("string")),
|
||||||
assertThat(t.objects, is(objects));
|
msg
|
||||||
assertThat(t.b.j, is(20));
|
);
|
||||||
assertThat(t.b.t, is("v"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class TestClass {
|
private static final class ExcludedClass {}
|
||||||
private int z;
|
|
||||||
private int i = 1;
|
@Test
|
||||||
private double d = 2.0;
|
void instanceToMapSetsAutoConvertedInstancesAsIs() {
|
||||||
private String s = "s";
|
class A {
|
||||||
private List<String> strings = Arrays.asList("1", "2");
|
@NoConvert
|
||||||
private Map<String, Object> objects = new HashMap<>();
|
ExcludedClass ex = new ExcludedClass();
|
||||||
private char c = 'c';
|
}
|
||||||
private TestClassB b = new TestClassB();
|
A a = new A();
|
||||||
|
Map<String, Object> map = instanceToMap(a);
|
||||||
|
assertThat(map.get("ex"), sameInstance(a.ex));
|
||||||
|
}
|
||||||
|
|
||||||
public TestClass() {
|
@Test
|
||||||
objects.put("a", 1);
|
void instanceFromMapSetsAutoConvertedInstancesAsIs() {
|
||||||
objects.put("b", 2);
|
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 TestClassB {
|
private static final class ClassWithFinalStaticTransientField {
|
||||||
private int j = -1;
|
private final int i = 0;
|
||||||
private String t = "t";
|
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;
|
package de.exlll.configlib;
|
||||||
|
|
||||||
import org.junit.Rule;
|
import de.exlll.configlib.classes.TestClass;
|
||||||
import org.junit.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.rules.ExpectedException;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.is;
|
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 {
|
class ReflectTest {
|
||||||
private static final Class<TestClass> cls1 = TestClass.class;
|
private static final Set<Class<?>> ALL_SIMPLE_TYPES = Set.of(
|
||||||
private static final Class<NotDefaultConstructor> cls2 = NotDefaultConstructor.class;
|
boolean.class, Boolean.class,
|
||||||
private final List<String> list = new ConfigList<>(String.class);
|
byte.class, Byte.class,
|
||||||
private final Set<String> set = new ConfigSet<>(String.class);
|
char.class, Character.class,
|
||||||
private final Map<?, String> map = new ConfigMap<>(String.class, String.class);
|
short.class, Short.class,
|
||||||
private final Class<?>[] containerClasses = {List.class, Set.class, Map.class};
|
int.class, Integer.class,
|
||||||
private final Class<?>[] simpleClasses = {
|
long.class, Long.class,
|
||||||
boolean.class, char.class, byte.class, short.class,
|
float.class, Float.class,
|
||||||
int.class, long.class, float.class, double.class,
|
double.class, Double.class,
|
||||||
Boolean.class, String.class, Character.class,
|
String.class
|
||||||
Byte.class, Short.class, Integer.class, Long.class,
|
);
|
||||||
Float.class, Double.class,
|
|
||||||
};
|
@Test
|
||||||
private final String errorMessage = "Class NotDefaultConstructor doesn't have a default constructor.";
|
void isSimpleType() {
|
||||||
|
for (Class<?> cls : ALL_SIMPLE_TYPES) {
|
||||||
@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) {
|
|
||||||
assertThat(Reflect.isSimpleType(cls), is(true));
|
assertThat(Reflect.isSimpleType(cls), is(true));
|
||||||
}
|
}
|
||||||
|
assertThat(Reflect.isSimpleType(Object.class), is(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void isContainerType() throws Exception {
|
void isContainerType() {
|
||||||
assertThat(Reflect.isContainerType(list.getClass()), is(true));
|
|
||||||
assertThat(Reflect.isContainerType(set.getClass()), is(true));
|
|
||||||
assertThat(Reflect.isContainerType(map.getClass()), is(true));
|
|
||||||
assertThat(Reflect.isContainerType(Object.class), is(false));
|
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
|
@Test
|
||||||
public void getValueGetsValue() throws Exception {
|
void getValue() throws NoSuchFieldException {
|
||||||
TestClass testClass = new TestClass();
|
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");
|
Object value = Reflect.getValue(f1, testClass);
|
||||||
assertThat(Reflect.getValue(s, testClass), is("s"));
|
assertThat(value, is(testClass.getString()));
|
||||||
}
|
|
||||||
|
|
||||||
@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"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
value = Reflect.getValue(f2, testClass);
|
||||||
public void checkForDefaultConstructorsThrowsExceptionIfNoDefault() throws Exception {
|
assertThat(value, is(testClass.getPrimLong()));
|
||||||
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) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Version(version = "1.2.3")
|
value = Reflect.getValue(f3, testClass);
|
||||||
private static final class TestClass {
|
assertThat(value, is(TestClass.getStaticFinalInt()));
|
||||||
private String s = "s";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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,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