[config] make configuration value parser available for export (#2703)
* Refactor and expose `ConfigMapper` to `ConfigParser` Signed-off-by: Jan N. Klug <github@klug.nrw>pull/2709/head
parent
59bc10a2af
commit
5a067ec55b
|
@ -0,0 +1,243 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.config.core;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Map an OSGi configuration map {@code Map<String, Object>} or type-less value to an individual configuration bean or
|
||||
* typed value.
|
||||
*
|
||||
* @author David Graeff - Initial contribution
|
||||
* @author Jan N. Klug - Extended and refactored to an exposed utility class
|
||||
*
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public final class ConfigParser {
|
||||
private static final transient Logger LOGGER = LoggerFactory.getLogger(ConfigParser.class);
|
||||
private static final Map<String, Class<?>> WRAPPER_CLASSES_MAP = Map.of(//
|
||||
"float", Float.class, //
|
||||
"double", Double.class, //
|
||||
"long", Long.class, //
|
||||
"int", Integer.class, //
|
||||
"short", Short.class, //
|
||||
"byte", Byte.class, //
|
||||
"boolean", Boolean.class);
|
||||
|
||||
private ConfigParser() {
|
||||
// prevent instantiation
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this method to automatically map a configuration collection to a Configuration holder object. A common
|
||||
* use-case would be within a service. Usage example:
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* public void modified(Map<String, Object> properties) {
|
||||
* YourConfig config = ConfigParser.configurationAs(properties, YourConfig.class);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* @param properties The configuration map.
|
||||
* @param configurationClass The configuration holder class. An instance of this will be created so make sure that
|
||||
* a default constructor is available.
|
||||
* @return The configuration holder object. All fields that matched a configuration option are set. If a required
|
||||
* field is not set, null is returned.
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public static <T> @Nullable T configurationAs(Map<String, @Nullable Object> properties,
|
||||
Class<T> configurationClass) {
|
||||
T configuration;
|
||||
try {
|
||||
configuration = configurationClass.getConstructor().newInstance();
|
||||
} catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException
|
||||
| IllegalArgumentException | InvocationTargetException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Field> fields = getAllFields(configurationClass);
|
||||
for (Field field : fields) {
|
||||
// Don't try to write to final fields and ignore transient fields
|
||||
if (Modifier.isFinal(field.getModifiers()) || Modifier.isTransient(field.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
String fieldName = field.getName();
|
||||
Class<?> type = field.getType();
|
||||
|
||||
Object value = properties.get(fieldName);
|
||||
// Consider RequiredField annotations
|
||||
if (value == null) {
|
||||
LOGGER.trace("Skipping field '{}', because config has no entry for {}", fieldName, fieldName);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Allows to have List<int>, List<Double>, List<String> etc (and the corresponding Set<?>)
|
||||
if (value instanceof Collection) {
|
||||
Class<?> innerClass = (Class<?>) ((ParameterizedType) field.getGenericType())
|
||||
.getActualTypeArguments()[0];
|
||||
Collection collection;
|
||||
if (List.class.isAssignableFrom(type)) {
|
||||
collection = new ArrayList<>();
|
||||
} else if (Set.class.isAssignableFrom(type)) {
|
||||
collection = new HashSet<>();
|
||||
} else {
|
||||
LOGGER.warn("Skipping field '{}', only List and Set is supported as target Collection", fieldName);
|
||||
continue;
|
||||
}
|
||||
for (final Object it : (Collection<?>) value) {
|
||||
final Object normalized = valueAs(it, innerClass);
|
||||
if (normalized == null) {
|
||||
continue;
|
||||
}
|
||||
collection.add(normalized);
|
||||
}
|
||||
value = collection;
|
||||
}
|
||||
|
||||
try {
|
||||
value = valueAs(value, type);
|
||||
if (value == null) {
|
||||
continue;
|
||||
}
|
||||
LOGGER.trace("Setting value ({}) {} to field '{}' in configuration class {}", type.getSimpleName(),
|
||||
value, fieldName, configurationClass.getName());
|
||||
field.setAccessible(true);
|
||||
field.set(configuration, value);
|
||||
} catch (SecurityException | IllegalArgumentException | IllegalAccessException ex) {
|
||||
LOGGER.warn("Could not set field value for field '{}': {}", fieldName, ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
return configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return fields of the given class as well as all super classes.
|
||||
*
|
||||
* @param clazz The class
|
||||
* @return A list of Field objects
|
||||
*/
|
||||
private static List<Field> getAllFields(Class<?> clazz) {
|
||||
List<Field> fields = new ArrayList<>();
|
||||
for (Class<?> superclazz = clazz; superclazz != null; superclazz = superclazz.getSuperclass()) {
|
||||
fields.addAll(Arrays.asList(superclazz.getDeclaredFields()));
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a value to a given type or return default value
|
||||
*
|
||||
* @param value input value or String representation of that value
|
||||
* @param type desired target class
|
||||
* @param defaultValue default value to be used if conversion fails or input value is null
|
||||
* @return the converted value or the default value if value is null or conversion fails
|
||||
*/
|
||||
public static <T> T valueAsOrElse(@Nullable Object value, Class<T> type, T defaultValue) {
|
||||
return Objects.requireNonNullElse(valueAs(value, type), defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a value to a given type
|
||||
*
|
||||
* @param value input value or String representation of that value
|
||||
* @param type desired target class
|
||||
* @return the converted value or null if conversion fails or input value is null
|
||||
*/
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public static <T> @Nullable T valueAs(@Nullable Object value, Class<T> type) {
|
||||
if (value == null || type.isAssignableFrom(value.getClass())) {
|
||||
// exit early if value is null or type is already compatible
|
||||
return (T) value;
|
||||
}
|
||||
|
||||
// make sure primitives are converted to their respective wrapper class
|
||||
Class<?> typeClass = WRAPPER_CLASSES_MAP.getOrDefault(type.getSimpleName(), type);
|
||||
|
||||
Object result = value;
|
||||
// Handle the conversion case of Number to Float,Double,Long,Integer,Short,Byte,BigDecimal
|
||||
if (value instanceof Number) {
|
||||
Number number = (Number) value;
|
||||
if (Float.class.equals(typeClass)) {
|
||||
result = number.floatValue();
|
||||
} else if (Double.class.equals(typeClass)) {
|
||||
result = number.doubleValue();
|
||||
} else if (Long.class.equals(typeClass)) {
|
||||
result = number.longValue();
|
||||
} else if (Integer.class.equals(typeClass)) {
|
||||
result = number.intValue();
|
||||
} else if (Short.class.equals(typeClass)) {
|
||||
result = number.shortValue();
|
||||
} else if (Byte.class.equals(typeClass)) {
|
||||
result = number.byteValue();
|
||||
} else if (BigDecimal.class.equals(typeClass)) {
|
||||
result = new BigDecimal(number.toString());
|
||||
}
|
||||
} else if (value instanceof String && !String.class.equals(typeClass)) {
|
||||
// Handle the conversion case of String to Float,Double,Long,Integer,BigDecimal,Boolean
|
||||
String strValue = (String) value;
|
||||
if (Float.class.equals(typeClass)) {
|
||||
result = Float.valueOf(strValue);
|
||||
} else if (Double.class.equals(typeClass)) {
|
||||
result = Double.valueOf(strValue);
|
||||
} else if (Long.class.equals(typeClass)) {
|
||||
result = Long.valueOf(strValue);
|
||||
} else if (Integer.class.equals(typeClass)) {
|
||||
result = Integer.valueOf(strValue);
|
||||
} else if (Short.class.equals(typeClass)) {
|
||||
result = Short.valueOf(strValue);
|
||||
} else if (Byte.class.equals(typeClass)) {
|
||||
result = Byte.valueOf(strValue);
|
||||
} else if (BigDecimal.class.equals(typeClass)) {
|
||||
result = new BigDecimal(strValue);
|
||||
} else if (Boolean.class.equals(typeClass)) {
|
||||
result = Boolean.valueOf(strValue);
|
||||
} else if (type.isEnum()) {
|
||||
final Class<? extends Enum> enumType = (Class<? extends Enum>) typeClass;
|
||||
result = Enum.valueOf(enumType, value.toString());
|
||||
} else if (Set.class.isAssignableFrom(typeClass)) {
|
||||
result = Set.of(value);
|
||||
} else if (Collection.class.isAssignableFrom(typeClass)) {
|
||||
result = List.of(value);
|
||||
}
|
||||
}
|
||||
|
||||
if (result != null && typeClass.isAssignableFrom(result.getClass())) {
|
||||
return (T) result;
|
||||
}
|
||||
|
||||
LOGGER.warn("Conversion of value '{}' with type '{}' to '{}' failed. Returning null", value, value.getClass(),
|
||||
type);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -24,7 +24,6 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.config.core.internal.ConfigMapper;
|
||||
|
||||
/**
|
||||
* This class is a wrapper for configuration settings of {@link Thing}s.
|
||||
|
@ -41,7 +40,7 @@ public class Configuration {
|
|||
private final Map<String, Object> properties;
|
||||
|
||||
public Configuration() {
|
||||
this(emptyMap(), true);
|
||||
this(Map.of(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -53,7 +52,7 @@ public class Configuration {
|
|||
* @param configuration the configuration that should be cloned (may be null)
|
||||
*/
|
||||
public Configuration(final @Nullable Configuration configuration) {
|
||||
this(configuration == null ? emptyMap() : configuration.properties, true);
|
||||
this(configuration == null ? Map.of() : configuration.properties, true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,7 +61,7 @@ public class Configuration {
|
|||
* @param properties the properties the configuration should be filled. If null, an empty configuration is created.
|
||||
*/
|
||||
public Configuration(@Nullable Map<String, Object> properties) {
|
||||
this(properties == null ? emptyMap() : properties, false);
|
||||
this(properties == null ? Map.of() : properties, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -77,7 +76,7 @@ public class Configuration {
|
|||
|
||||
public <T> T as(Class<T> configurationClass) {
|
||||
synchronized (properties) {
|
||||
return ConfigMapper.as(properties, configurationClass);
|
||||
return ConfigParser.configurationAs(properties, configurationClass);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,168 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.config.core.internal;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Map an OSGi configuration map {@code Map<String, Object>} to an individual configuration bean.
|
||||
*
|
||||
* @author David Graeff - Initial contribution
|
||||
*/
|
||||
public class ConfigMapper {
|
||||
private static final transient Logger LOGGER = LoggerFactory.getLogger(ConfigMapper.class);
|
||||
|
||||
/**
|
||||
* Use this method to automatically map a configuration collection to a Configuration holder object. A common
|
||||
* use-case would be within a service. Usage example:
|
||||
*
|
||||
* <pre>
|
||||
* {@code
|
||||
* public void modified(Map<String, Object> properties) {
|
||||
* YourConfig config = ConfigMapper.as(properties, YourConfig.class);
|
||||
* }
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
*
|
||||
* @param properties The configuration map.
|
||||
* @param configurationClass The configuration holder class. An instance of this will be created so make sure that
|
||||
* a default constructor is available.
|
||||
* @return The configuration holder object. All fields that matched a configuration option are set. If a required
|
||||
* field is not set, null is returned.
|
||||
*/
|
||||
public static <T> @Nullable T as(Map<String, Object> properties, Class<T> configurationClass) {
|
||||
T configuration = null;
|
||||
try {
|
||||
configuration = configurationClass.getConstructor().newInstance();
|
||||
} catch (NoSuchMethodException | SecurityException | InstantiationException | IllegalAccessException
|
||||
| IllegalArgumentException | InvocationTargetException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Field> fields = getAllFields(configurationClass);
|
||||
for (Field field : fields) {
|
||||
// Don't try to write to final fields and ignore transient fields
|
||||
if (Modifier.isFinal(field.getModifiers()) || Modifier.isTransient(field.getModifiers())) {
|
||||
continue;
|
||||
}
|
||||
String fieldName = field.getName();
|
||||
String configKey = fieldName;
|
||||
Class<?> type = field.getType();
|
||||
|
||||
Object value = properties.get(configKey);
|
||||
// Consider RequiredField annotations
|
||||
if (value == null) {
|
||||
LOGGER.trace("Skipping field '{}', because config has no entry for {}", fieldName, configKey);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Allows to have List<int>, List<Double>, List<String> etc
|
||||
if (value instanceof Collection) {
|
||||
Collection<?> c = (Collection<?>) value;
|
||||
Class<?> innerClass = (Class<?>) ((ParameterizedType) field.getGenericType())
|
||||
.getActualTypeArguments()[0];
|
||||
final List<Object> lst = new ArrayList<>(c.size());
|
||||
for (final Object it : c) {
|
||||
final Object normalized = objectConvert(it, innerClass);
|
||||
lst.add(normalized);
|
||||
}
|
||||
value = lst;
|
||||
}
|
||||
|
||||
try {
|
||||
value = objectConvert(value, type);
|
||||
LOGGER.trace("Setting value ({}) {} to field '{}' in configuration class {}", type.getSimpleName(),
|
||||
value, fieldName, configurationClass.getName());
|
||||
field.setAccessible(true);
|
||||
field.set(configuration, value);
|
||||
} catch (SecurityException | IllegalArgumentException | IllegalAccessException ex) {
|
||||
LOGGER.warn("Could not set field value for field '{}': {}", fieldName, ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
return configuration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return fields of the given class as well as all super classes.
|
||||
*
|
||||
* @param clazz The class
|
||||
* @return A list of Field objects
|
||||
*/
|
||||
private static List<Field> getAllFields(Class<?> clazz) {
|
||||
List<Field> fields = new ArrayList<>();
|
||||
for (Class<?> superclazz = clazz; superclazz != null; superclazz = superclazz.getSuperclass()) {
|
||||
fields.addAll(Arrays.asList(superclazz.getDeclaredFields()));
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
private static Object objectConvert(Object value, Class<?> type) {
|
||||
Object result = value;
|
||||
// Handle the conversion case of BigDecimal to Float,Double,Long,Integer and the respective
|
||||
// primitive types
|
||||
String typeName = type.getSimpleName();
|
||||
if (value instanceof BigDecimal && !BigDecimal.class.equals(type)) {
|
||||
BigDecimal bdValue = (BigDecimal) value;
|
||||
if (Float.class.equals(type) || "float".equals(typeName)) {
|
||||
result = bdValue.floatValue();
|
||||
} else if (Double.class.equals(type) || "double".equals(typeName)) {
|
||||
result = bdValue.doubleValue();
|
||||
} else if (Long.class.equals(type) || "long".equals(typeName)) {
|
||||
result = bdValue.longValue();
|
||||
} else if (Integer.class.equals(type) || "int".equals(typeName)) {
|
||||
result = bdValue.intValue();
|
||||
}
|
||||
} else
|
||||
// Handle the conversion case of String to Float,Double,Long,Integer,BigDecimal,Boolean and the respective
|
||||
// primitive types
|
||||
if (value instanceof String && !String.class.equals(type)) {
|
||||
String bdValue = (String) value;
|
||||
if (Float.class.equals(type) || "float".equals(typeName)) {
|
||||
result = Float.valueOf(bdValue);
|
||||
} else if (Double.class.equals(type) || "double".equals(typeName)) {
|
||||
result = Double.valueOf(bdValue);
|
||||
} else if (Long.class.equals(type) || "long".equals(typeName)) {
|
||||
result = Long.valueOf(bdValue);
|
||||
} else if (BigDecimal.class.equals(type)) {
|
||||
result = new BigDecimal(bdValue);
|
||||
} else if (Integer.class.equals(type) || "int".equals(typeName)) {
|
||||
result = Integer.valueOf(bdValue);
|
||||
} else if (Boolean.class.equals(type) || "boolean".equals(typeName)) {
|
||||
result = Boolean.valueOf(bdValue);
|
||||
} else if (type.isEnum()) {
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
final Class<? extends Enum> enumType = (Class<? extends Enum>) type;
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
final Enum<?> enumvalue = Enum.valueOf(enumType, value.toString());
|
||||
result = enumvalue;
|
||||
} else if (Collection.class.isAssignableFrom(type)) {
|
||||
result = List.of(value);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
/**
|
||||
* Copyright (c) 2010-2022 Contributors to the openHAB project
|
||||
*
|
||||
* See the NOTICE file(s) distributed with this work for additional
|
||||
* information.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.core.config.core;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
/**
|
||||
* The {@link ConfigParserTest} contains tests for the {@link ConfigParser}
|
||||
*
|
||||
* @author Jan N. Klug - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ConfigParserTest {
|
||||
|
||||
private static final List<TestParameter<?>> TEST_PARAMETERS = List.of( //
|
||||
// float/Float
|
||||
new TestParameter<>("7.5", float.class, 7.5f), //
|
||||
new TestParameter<>("-7.5", Float.class, -7.5f), //
|
||||
new TestParameter<>(-7.5, float.class, -7.5f), //
|
||||
new TestParameter<>(7.5, Float.class, 7.5f), //
|
||||
// double/Double
|
||||
new TestParameter<>("7.5", double.class, 7.5), //
|
||||
new TestParameter<>("-7.5", Double.class, -7.5), //
|
||||
new TestParameter<>(-7.5, double.class, -7.5), //
|
||||
new TestParameter<>(7.5, Double.class, 7.5), //
|
||||
// long/Long
|
||||
new TestParameter<>("1", long.class, 1L), //
|
||||
new TestParameter<>("-1", Long.class, -1L), //
|
||||
new TestParameter<>(-1, long.class, -1L), //
|
||||
new TestParameter<>(1, Long.class, 1L), //
|
||||
// int/Integer
|
||||
new TestParameter<>("1", int.class, 1), //
|
||||
new TestParameter<>("-1", Integer.class, -1), //
|
||||
new TestParameter<>(-1, int.class, -1), //
|
||||
new TestParameter<>(1, Integer.class, 1), //
|
||||
// short/Short
|
||||
new TestParameter<>("1", short.class, (short) 1), //
|
||||
new TestParameter<>("-1", Short.class, (short) -1), //
|
||||
new TestParameter<>(-1, short.class, (short) -1), //
|
||||
new TestParameter<>(1, Short.class, (short) 1), //
|
||||
// byte/Byte
|
||||
new TestParameter<>("1", byte.class, (byte) 1), //
|
||||
new TestParameter<>("-1", Byte.class, (byte) -1), //
|
||||
new TestParameter<>(-1, byte.class, (byte) -1), //
|
||||
new TestParameter<>(1, Byte.class, (byte) 1), //
|
||||
// boolean/Boolean
|
||||
new TestParameter<>("true", boolean.class, true), //
|
||||
new TestParameter<>("true", Boolean.class, true), //
|
||||
new TestParameter<>(false, boolean.class, false), //
|
||||
new TestParameter<>(false, Boolean.class, false), //
|
||||
// BigDecimal
|
||||
new TestParameter<>("7.5", BigDecimal.class, BigDecimal.valueOf(7.5)), //
|
||||
new TestParameter<>(BigDecimal.valueOf(-7.5), BigDecimal.class, BigDecimal.valueOf(-7.5)), //
|
||||
new TestParameter<>(1, BigDecimal.class, BigDecimal.ONE), //
|
||||
// String
|
||||
new TestParameter<>("foo", String.class, "foo"), //
|
||||
// Enum
|
||||
new TestParameter<>("ENUM1", TestEnum.class, TestEnum.ENUM1), //
|
||||
// List
|
||||
new TestParameter<>("1", List.class, List.of("1")), //
|
||||
new TestParameter<>(List.of(1, 2, 3), List.class, List.of(1, 2, 3)),
|
||||
// Set
|
||||
new TestParameter<>("1", Set.class, Set.of("1")), //
|
||||
new TestParameter<>(Set.of(1, 2, 3), Set.class, Set.of(1, 2, 3)), //
|
||||
// illegal conversion
|
||||
new TestParameter<>(1, Boolean.class, null), //
|
||||
// null input
|
||||
new TestParameter<>(null, Object.class, null) //
|
||||
);
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static Stream<TestParameter<?>> valueAsTest() {
|
||||
return TEST_PARAMETERS.stream();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource
|
||||
public void valueAsTest(TestParameter<?> parameter) {
|
||||
Object result = ConfigParser.valueAs(parameter.input, parameter.type);
|
||||
Assertions.assertEquals(parameter.result, result, "Failed equals: " + parameter);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void valueAsDefaultTest() {
|
||||
Object result = ConfigParser.valueAsOrElse(null, String.class, "foo");
|
||||
Assertions.assertEquals("foo", result);
|
||||
}
|
||||
|
||||
private enum TestEnum {
|
||||
ENUM1
|
||||
}
|
||||
|
||||
private static class TestParameter<T> {
|
||||
public final @Nullable Object input;
|
||||
public final Class<T> type;
|
||||
public final @Nullable T result;
|
||||
|
||||
public TestParameter(@Nullable Object input, Class<T> type, @Nullable T result) {
|
||||
this.input = input;
|
||||
this.type = type;
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TestParameter{input=" + input + ", type=" + type + ", result=" + result + "}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,6 +23,8 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
|
@ -31,6 +33,7 @@ import org.junit.jupiter.api.Test;
|
|||
* @author Dennis Nobel - Initial contribution
|
||||
* @author Wouter Born - Migrate tests from Groovy to Java
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ConfigurationTest {
|
||||
|
||||
public static class ConfigClass {
|
||||
|
@ -44,14 +47,15 @@ public class ConfigurationTest {
|
|||
public int intField;
|
||||
public boolean booleanField;
|
||||
public String stringField = "somedefault";
|
||||
public List<String> listField;
|
||||
public @NonNullByDefault({}) List<String> listField;
|
||||
public @NonNullByDefault({}) Set<String> setField;
|
||||
@SuppressWarnings("unused")
|
||||
private static final String CONSTANT = "SOME_CONSTANT";
|
||||
}
|
||||
|
||||
public static class ExtendedConfigClass extends ConfigClass {
|
||||
public int additionalIntField;
|
||||
public String listField;
|
||||
public @NonNullByDefault({}) String listField;
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -62,6 +66,7 @@ public class ConfigurationTest {
|
|||
configuration.put("stringField", "test");
|
||||
configuration.put("enumField", "ON");
|
||||
configuration.put("listField", List.of("one", "two", "three"));
|
||||
configuration.put("setField", List.of("one", "two", "three"));
|
||||
configuration.put("notExisitingProperty", true);
|
||||
|
||||
ConfigClass configClass = configuration.as(ConfigClass.class);
|
||||
|
@ -71,6 +76,7 @@ public class ConfigurationTest {
|
|||
assertThat(configClass.stringField, is("test"));
|
||||
assertThat(configClass.enumField, is(ConfigClass.MyEnum.ON));
|
||||
assertThat(configClass.listField, is(hasItems("one", "two", "three")));
|
||||
assertThat(configClass.setField, is(hasItems("one", "two", "three")));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -143,7 +149,7 @@ public class ConfigurationTest {
|
|||
|
||||
@Test
|
||||
public void assertToStringHandlesNullValuesGracefully() {
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
Map<String, @Nullable Object> properties = new HashMap<>();
|
||||
properties.put("stringField", null);
|
||||
|
||||
Configuration configuration = new Configuration(properties);
|
||||
|
|
Loading…
Reference in New Issue