Skip to content

Commit b8caedc

Browse files
committed
Fix: Converting back fails for nested collections of complex ConfigurationElements
Note that you now have to set a nesting level when using nested collections that require the ElementType annotation.
1 parent ee53d0c commit b8caedc

File tree

16 files changed

+1159
-145
lines changed

16 files changed

+1159
-145
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: ConfigLib
22
author: Exlll
33

4-
version: 2.0.2
4+
version: 2.0.3
55
main: de.exlll.configlib.ConfigLib
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: ConfigLib
22
author: Exlll
33

4-
version: 2.0.2
4+
version: 2.0.3
55
main: de.exlll.configlib.ConfigLib

ConfigLib-Core/src/main/java/de/exlll/configlib/Converter.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ final class ConversionInfo {
6767
private final Class<?> elementType;
6868
private final String fieldName;
6969
private final Configuration.Properties props;
70+
private final int nestingLevel;
71+
private int currentNestingLevel;
7072

7173
private ConversionInfo(Field field, Object instance, Object mapValue,
7274
Configuration.Properties props) {
@@ -79,6 +81,7 @@ private ConversionInfo(Field field, Object instance, Object mapValue,
7981
this.fieldName = field.getName();
8082
this.props = props;
8183
this.elementType = elementType(field);
84+
this.nestingLevel = nestingLevel(field);
8285
}
8386

8487
private static Class<?> elementType(Field field) {
@@ -89,6 +92,14 @@ private static Class<?> elementType(Field field) {
8992
return null;
9093
}
9194

95+
private static int nestingLevel(Field field) {
96+
if (field.isAnnotationPresent(ElementType.class)) {
97+
ElementType et = field.getAnnotation(ElementType.class);
98+
return et.nestingLevel();
99+
}
100+
return -1;
101+
}
102+
92103
static ConversionInfo of(Field field, Object instance,
93104
Configuration.Properties props) {
94105
return new ConversionInfo(field, instance, null, props);
@@ -192,5 +203,17 @@ public Class<?> getElementType() {
192203
public boolean hasElementType() {
193204
return elementType != null;
194205
}
206+
207+
int getNestingLevel() {
208+
return nestingLevel;
209+
}
210+
211+
int getCurrentNestingLevel() {
212+
return currentNestingLevel;
213+
}
214+
215+
void incCurrentNestingLevel() {
216+
currentNestingLevel++;
217+
}
195218
}
196219
}

ConfigLib-Core/src/main/java/de/exlll/configlib/Converters.java

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -344,26 +344,48 @@ public void preConvertFrom(ConversionInfo info) {
344344
private static Function<Object, ?> createToConversionFunction(
345345
Object element, ConversionInfo info
346346
) {
347-
return o -> selectNonSimpleConverter(element.getClass(), info)
348-
.convertTo(o, info);
347+
checkNestingLevel(element, info);
348+
if (Reflect.isContainerType(element.getClass())) {
349+
info.incCurrentNestingLevel();
350+
}
351+
Converter<Object, ?> converter = selectNonSimpleConverter(
352+
element.getClass(), info
353+
);
354+
return o -> converter.convertTo(o, info);
349355
}
350356

351357
private static Function<Object, ?> createFromConversionFunction(
352358
Object element, ConversionInfo info
353359
) {
354-
if ((element instanceof Map<?, ?>) && isTypeMap((Map<?, ?>) element)) {
360+
boolean currentLevelSameAsExpected =
361+
info.getNestingLevel() == info.getCurrentNestingLevel();
362+
checkCurrentLevelSameAsExpectedRequiresMapOrString(
363+
currentLevelSameAsExpected, element, info
364+
);
365+
if ((element instanceof Map<?, ?>) && currentLevelSameAsExpected) {
355366
return o -> {
356367
Map<String, Object> map = toTypeMap(o, null);
357368
Object inst = Reflect.newInstance(info.getElementType());
358369
FieldMapper.instanceFromMap(inst, map, info.getProperties());
359370
return inst;
360371
};
372+
} else if ((element instanceof String) && currentLevelSameAsExpected) {
373+
return createNonSimpleConverter(element, info);
361374
} else {
362-
return o -> selectNonSimpleConverter(element.getClass(), info)
363-
.convertFrom(o, info);
375+
info.incCurrentNestingLevel();
376+
return createNonSimpleConverter(element, info);
364377
}
365378
}
366379

380+
private static Function<Object, ?> createNonSimpleConverter(
381+
Object element, ConversionInfo info
382+
) {
383+
Converter<?, Object> converter = selectNonSimpleConverter(
384+
element.getClass(), info
385+
);
386+
return o -> converter.convertFrom(o, info);
387+
}
388+
367389
private static Map<String, Object> toTypeMap(Object value, String fn) {
368390
checkIsMap(value, fn);
369391
checkMapKeysAreStrings((Map<?, ?>) value, fn);

ConfigLib-Core/src/main/java/de/exlll/configlib/Validator.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,37 @@ static void checkElementIsConvertibleToConfigurationElement(
315315
}
316316
}
317317

318+
static void checkNestingLevel(Object element, ConversionInfo info) {
319+
if (!Reflect.isContainerType(element.getClass())) {
320+
if (info.getNestingLevel() != info.getCurrentNestingLevel()) {
321+
String msg = "Field '" + info.getFieldName() + "' of class " +
322+
"'" + getClsName(info.getInstance().getClass()) + "' " +
323+
"has a nesting level of " + info.getNestingLevel() +
324+
" but the first object of type '" +
325+
getClsName(info.getElementType()) + "' was found on " +
326+
"level " + info.getCurrentNestingLevel() + ".";
327+
throw new ConfigurationException(msg);
328+
}
329+
}
330+
}
331+
332+
static void checkCurrentLevelSameAsExpectedRequiresMapOrString(
333+
boolean currentLevelSameAsExpected,
334+
Object element, ConversionInfo info
335+
) {
336+
boolean isMapOrString = (element instanceof Map<?, ?>) ||
337+
(element instanceof String);
338+
if (currentLevelSameAsExpected && !isMapOrString) {
339+
Class<?> cls = info.getInstance().getClass();
340+
String msg = "Field '" + info.getFieldName() + "' of class '" +
341+
getClsName(cls) + "' has a nesting level" +
342+
" of " + info.getNestingLevel() + " but element '" + element +
343+
"' of type '" + getClsName(element.getClass()) + "' cannot be " +
344+
"converted to '" + getClsName(info.getElementType()) + "'.";
345+
throw new ConfigurationException(msg);
346+
}
347+
}
348+
318349
static void checkElementTypeIsEnumType(Class<?> type, ConversionInfo info) {
319350
if (!Reflect.isEnumType(type)) {
320351
String msg = "Element type '" + getClsName(type) + "' of field " +

ConfigLib-Core/src/main/java/de/exlll/configlib/annotation/ElementType.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,21 @@
1010
* This annotation must only be used if a {@code Collection} or {@code Map} contains
1111
* elements whose type is not simple. Note that {@code Map} keys can only be of some
1212
* simple type.
13+
* <p>
14+
* If collections are nested, the {@code nestingLevel} must be set. Examples:
15+
* <ul>
16+
* <li>nestingLevel 1: {@code List<List<T>>}</li>
17+
* <li>nestingLevel 1: {@code List<Set<T>>}</li>
18+
* <li>nestingLevel 1: {@code List<Map<String, T>>}</li>
19+
* <li>nestingLevel 2: {@code List<List<List<T>>>}</li>
20+
* <li>nestingLevel 2: {@code List<Set<List<T>>>}</li>
21+
* <li>nestingLevel 2: {@code List<List<Map<String, T>>>}</li>
22+
* </ul>
1323
*/
1424
@Target(java.lang.annotation.ElementType.FIELD)
1525
@Retention(RetentionPolicy.RUNTIME)
1626
public @interface ElementType {
1727
Class<?> value();
28+
29+
int nestingLevel() default 0;
1830
}

ConfigLib-Core/src/test/java/de/exlll/configlib/FieldMapperConverterTest.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ class A {
342342
@Test
343343
void instanceFromMapCatchesClassCastExceptionOfUnknownEnumConstantsInLists() {
344344
class A {
345-
@ElementType(LocalTestEnum.class)
345+
@ElementType(value = LocalTestEnum.class, nestingLevel = 1)
346346
List<List<LocalTestEnum>> l = listOf();
347347
}
348348
Map<String, Object> map = mapOf(
@@ -359,7 +359,7 @@ class A {
359359
@Test
360360
void instanceFromMapCatchesClassCastExceptionOfUnknownEnumConstantsInSets() {
361361
class A {
362-
@ElementType(LocalTestEnum.class)
362+
@ElementType(value = LocalTestEnum.class, nestingLevel = 1)
363363
Set<List<LocalTestEnum>> s = setOf();
364364
}
365365
Map<String, Object> map = mapOf(
@@ -376,7 +376,7 @@ class A {
376376
@Test
377377
void instanceFromMapCatchesClassCastExceptionOfUnknownEnumConstantsInMaps() {
378378
class A {
379-
@ElementType(LocalTestEnum.class)
379+
@ElementType(value = LocalTestEnum.class, nestingLevel = 1)
380380
Map<Integer, List<LocalTestEnum>> m = mapOf();
381381
}
382382
Map<String, Object> map = mapOf(

0 commit comments

Comments
 (0)