diff --git a/ConfigLib-Bukkit/src/main/resources/plugin.yml b/ConfigLib-Bukkit/src/main/resources/plugin.yml index 01445b4..dc456a3 100644 --- a/ConfigLib-Bukkit/src/main/resources/plugin.yml +++ b/ConfigLib-Bukkit/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: ConfigLib author: Exlll -version: 2.0.2 +version: 2.0.3 main: de.exlll.configlib.ConfigLib \ No newline at end of file diff --git a/ConfigLib-Bungee/src/main/resources/plugin.yml b/ConfigLib-Bungee/src/main/resources/plugin.yml index 01445b4..dc456a3 100644 --- a/ConfigLib-Bungee/src/main/resources/plugin.yml +++ b/ConfigLib-Bungee/src/main/resources/plugin.yml @@ -1,5 +1,5 @@ name: ConfigLib author: Exlll -version: 2.0.2 +version: 2.0.3 main: de.exlll.configlib.ConfigLib \ No newline at end of file diff --git a/ConfigLib-Core/src/main/java/de/exlll/configlib/Converter.java b/ConfigLib-Core/src/main/java/de/exlll/configlib/Converter.java index ef03868..d7ad86b 100644 --- a/ConfigLib-Core/src/main/java/de/exlll/configlib/Converter.java +++ b/ConfigLib-Core/src/main/java/de/exlll/configlib/Converter.java @@ -67,6 +67,8 @@ public interface Converter { private final Class elementType; private final String fieldName; private final Configuration.Properties props; + private final int nestingLevel; + private int currentNestingLevel; private ConversionInfo(Field field, Object instance, Object mapValue, Configuration.Properties props) { @@ -79,6 +81,7 @@ public interface Converter { this.fieldName = field.getName(); this.props = props; this.elementType = elementType(field); + this.nestingLevel = nestingLevel(field); } private static Class elementType(Field field) { @@ -89,6 +92,14 @@ public interface Converter { return null; } + private static int nestingLevel(Field field) { + if (field.isAnnotationPresent(ElementType.class)) { + ElementType et = field.getAnnotation(ElementType.class); + return et.nestingLevel(); + } + return -1; + } + static ConversionInfo of(Field field, Object instance, Configuration.Properties props) { return new ConversionInfo(field, instance, null, props); @@ -192,5 +203,17 @@ public interface Converter { public boolean hasElementType() { return elementType != null; } + + int getNestingLevel() { + return nestingLevel; + } + + int getCurrentNestingLevel() { + return currentNestingLevel; + } + + void incCurrentNestingLevel() { + currentNestingLevel++; + } } } diff --git a/ConfigLib-Core/src/main/java/de/exlll/configlib/Converters.java b/ConfigLib-Core/src/main/java/de/exlll/configlib/Converters.java index 3ab8587..95404f6 100644 --- a/ConfigLib-Core/src/main/java/de/exlll/configlib/Converters.java +++ b/ConfigLib-Core/src/main/java/de/exlll/configlib/Converters.java @@ -344,26 +344,48 @@ final class Converters { private static Function createToConversionFunction( Object element, ConversionInfo info ) { - return o -> selectNonSimpleConverter(element.getClass(), info) - .convertTo(o, info); + checkNestingLevel(element, info); + if (Reflect.isContainerType(element.getClass())) { + info.incCurrentNestingLevel(); + } + Converter converter = selectNonSimpleConverter( + element.getClass(), info + ); + return o -> converter.convertTo(o, info); } private static Function createFromConversionFunction( Object element, ConversionInfo info ) { - if ((element instanceof Map) && isTypeMap((Map) element)) { + boolean currentLevelSameAsExpected = + info.getNestingLevel() == info.getCurrentNestingLevel(); + checkCurrentLevelSameAsExpectedRequiresMapOrString( + currentLevelSameAsExpected, element, info + ); + if ((element instanceof Map) && currentLevelSameAsExpected) { return o -> { Map map = toTypeMap(o, null); Object inst = Reflect.newInstance(info.getElementType()); FieldMapper.instanceFromMap(inst, map, info.getProperties()); return inst; }; + } else if ((element instanceof String) && currentLevelSameAsExpected) { + return createNonSimpleConverter(element, info); } else { - return o -> selectNonSimpleConverter(element.getClass(), info) - .convertFrom(o, info); + info.incCurrentNestingLevel(); + return createNonSimpleConverter(element, info); } } + private static Function createNonSimpleConverter( + Object element, ConversionInfo info + ) { + Converter converter = selectNonSimpleConverter( + element.getClass(), info + ); + return o -> converter.convertFrom(o, info); + } + private static Map toTypeMap(Object value, String fn) { checkIsMap(value, fn); checkMapKeysAreStrings((Map) value, fn); diff --git a/ConfigLib-Core/src/main/java/de/exlll/configlib/Validator.java b/ConfigLib-Core/src/main/java/de/exlll/configlib/Validator.java index 8b30470..f6db312 100644 --- a/ConfigLib-Core/src/main/java/de/exlll/configlib/Validator.java +++ b/ConfigLib-Core/src/main/java/de/exlll/configlib/Validator.java @@ -315,6 +315,37 @@ final class Validator { } } + static void checkNestingLevel(Object element, ConversionInfo info) { + if (!Reflect.isContainerType(element.getClass())) { + if (info.getNestingLevel() != info.getCurrentNestingLevel()) { + String msg = "Field '" + info.getFieldName() + "' of class " + + "'" + getClsName(info.getInstance().getClass()) + "' " + + "has a nesting level of " + info.getNestingLevel() + + " but the first object of type '" + + getClsName(info.getElementType()) + "' was found on " + + "level " + info.getCurrentNestingLevel() + "."; + throw new ConfigurationException(msg); + } + } + } + + static void checkCurrentLevelSameAsExpectedRequiresMapOrString( + boolean currentLevelSameAsExpected, + Object element, ConversionInfo info + ) { + boolean isMapOrString = (element instanceof Map) || + (element instanceof String); + if (currentLevelSameAsExpected && !isMapOrString) { + Class cls = info.getInstance().getClass(); + String msg = "Field '" + info.getFieldName() + "' of class '" + + getClsName(cls) + "' has a nesting level" + + " of " + info.getNestingLevel() + " but element '" + element + + "' of type '" + getClsName(element.getClass()) + "' cannot be " + + "converted to '" + getClsName(info.getElementType()) + "'."; + throw new ConfigurationException(msg); + } + } + static void checkElementTypeIsEnumType(Class type, ConversionInfo info) { if (!Reflect.isEnumType(type)) { String msg = "Element type '" + getClsName(type) + "' of field " + diff --git a/ConfigLib-Core/src/main/java/de/exlll/configlib/annotation/ElementType.java b/ConfigLib-Core/src/main/java/de/exlll/configlib/annotation/ElementType.java index 9267b20..c528c88 100644 --- a/ConfigLib-Core/src/main/java/de/exlll/configlib/annotation/ElementType.java +++ b/ConfigLib-Core/src/main/java/de/exlll/configlib/annotation/ElementType.java @@ -10,9 +10,21 @@ import java.lang.annotation.Target; * This annotation must only be used if a {@code Collection} or {@code Map} contains * elements whose type is not simple. Note that {@code Map} keys can only be of some * simple type. + *

+ * If collections are nested, the {@code nestingLevel} must be set. Examples: + *

    + *
  • nestingLevel 1: {@code List>}
  • + *
  • nestingLevel 1: {@code List>}
  • + *
  • nestingLevel 1: {@code List>}
  • + *
  • nestingLevel 2: {@code List>>}
  • + *
  • nestingLevel 2: {@code List>>}
  • + *
  • nestingLevel 2: {@code List>>}
  • + *
*/ @Target(java.lang.annotation.ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface ElementType { Class value(); + + int nestingLevel() default 0; } diff --git a/ConfigLib-Core/src/test/java/de/exlll/configlib/FieldMapperConverterTest.java b/ConfigLib-Core/src/test/java/de/exlll/configlib/FieldMapperConverterTest.java index c53ef20..580cae5 100644 --- a/ConfigLib-Core/src/test/java/de/exlll/configlib/FieldMapperConverterTest.java +++ b/ConfigLib-Core/src/test/java/de/exlll/configlib/FieldMapperConverterTest.java @@ -342,7 +342,7 @@ public class FieldMapperConverterTest { @Test void instanceFromMapCatchesClassCastExceptionOfUnknownEnumConstantsInLists() { class A { - @ElementType(LocalTestEnum.class) + @ElementType(value = LocalTestEnum.class, nestingLevel = 1) List> l = listOf(); } Map map = mapOf( @@ -359,7 +359,7 @@ public class FieldMapperConverterTest { @Test void instanceFromMapCatchesClassCastExceptionOfUnknownEnumConstantsInSets() { class A { - @ElementType(LocalTestEnum.class) + @ElementType(value = LocalTestEnum.class, nestingLevel = 1) Set> s = setOf(); } Map map = mapOf( @@ -376,7 +376,7 @@ public class FieldMapperConverterTest { @Test void instanceFromMapCatchesClassCastExceptionOfUnknownEnumConstantsInMaps() { class A { - @ElementType(LocalTestEnum.class) + @ElementType(value = LocalTestEnum.class, nestingLevel = 1) Map> m = mapOf(); } Map map = mapOf( diff --git a/ConfigLib-Core/src/test/java/de/exlll/configlib/ValidatorTest.java b/ConfigLib-Core/src/test/java/de/exlll/configlib/ValidatorTest.java index b77390a..67d492d 100644 --- a/ConfigLib-Core/src/test/java/de/exlll/configlib/ValidatorTest.java +++ b/ConfigLib-Core/src/test/java/de/exlll/configlib/ValidatorTest.java @@ -12,9 +12,7 @@ import java.util.concurrent.ConcurrentSkipListSet; import java.util.concurrent.CopyOnWriteArrayList; import static de.exlll.configlib.FieldMapperHelpers.*; -import static de.exlll.configlib.util.CollectionFactory.listOf; -import static de.exlll.configlib.util.CollectionFactory.mapOf; -import static de.exlll.configlib.util.CollectionFactory.setOf; +import static de.exlll.configlib.util.CollectionFactory.*; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -117,12 +115,12 @@ public class ValidatorTest { m = mapOf("s", setOf("s")); msg = "Can not set field 's' with type 'ConcurrentSkipListSet' " + - "to 'HashSet'."; + "to 'LinkedHashSet'."; assertIfmCfgExceptionMessage(new B(), m, msg); m = mapOf("m", mapOf(1, "s")); msg = "Can not set field 'm' with type 'ConcurrentHashMap' " + - "to 'HashMap'."; + "to 'LinkedHashMap'."; assertIfmCfgExceptionMessage(new C(), m, msg); } @@ -152,25 +150,27 @@ public class ValidatorTest { void instanceToMapRequiresListsWithoutElementTypeToContainSimpleTypes() { class A { List l = new ArrayList<>(listOf( - TestSubClass.of(1, "1") + TestSubClass.TEST_VALUES )); } class B { List>> l = new ArrayList<>(listOf( - setOf(mapOf(1, TestSubClass.of(1, "1"))) + setOf(mapOf(1, TestSubClass.TEST_VALUES)) )); } + String asString = TestSubClass.TEST_VALUES.toString(); + A a = new A(); String msg = "The type of an element of list 'l' is not a simple type " + "but list 'l' is missing the ElementType annotation.\n" + - "All elements: [TestSubClass{\nprimInt=1,\nstring='1'}]"; + "All elements: [" + asString + "]"; assertItmCfgExceptionMessage(a, msg); B b = new B(); msg = "The type of an element of list 'l' is not a simple type " + "but list 'l' is missing the ElementType annotation.\n" + - "All elements: [[{1=TestSubClass{\nprimInt=1,\nstring='1'}}]]"; + "All elements: [[{1=" + asString + "}]]"; assertItmCfgExceptionMessage(b, msg); } @@ -178,25 +178,27 @@ public class ValidatorTest { void instanceToMapRequiresSetsWithoutElementTypeToContainSimpleTypes() { class A { Set s = new HashSet<>(setOf( - TestSubClass.of(1, "1") + TestSubClass.TEST_VALUES )); } class B { Set>> s = new HashSet<>(setOf( - listOf(mapOf(1, TestSubClass.of(1, "1"))) + listOf(mapOf(1, TestSubClass.TEST_VALUES)) )); } + String asString = TestSubClass.TEST_VALUES.toString(); + A a = new A(); String msg = "The type of an element of set 's' is not a simple type " + "but set 's' is missing the ElementType annotation.\n" + - "All elements: [TestSubClass{\nprimInt=1,\nstring='1'}]"; + "All elements: [" + asString + "]"; assertItmCfgExceptionMessage(a, msg); B b = new B(); msg = "The type of an element of set 's' is not a simple type " + "but set 's' is missing the ElementType annotation.\n" + - "All elements: [[{1=TestSubClass{\nprimInt=1,\nstring='1'}}]]"; + "All elements: [[{1=" + asString + "}]]"; assertItmCfgExceptionMessage(b, msg); } @@ -204,25 +206,27 @@ public class ValidatorTest { void instanceToMapRequiresMapsWithoutElementTypeToContainSimpleTypes() { class A { Map m = new HashMap<>(mapOf( - 1, TestSubClass.of(1, "1") + 1, TestSubClass.TEST_VALUES )); } class B { Map>> m = new HashMap<>(mapOf( - 1, setOf(listOf(TestSubClass.of(1, "1"))) + 1, setOf(listOf(TestSubClass.TEST_VALUES)) )); } + String asString = TestSubClass.TEST_VALUES.toString(); + A a = new A(); String msg = "The type of a value of map 'm' is not a simple type " + "but map 'm' is missing the ElementType annotation.\n" + - "All entries: {1=TestSubClass{\nprimInt=1,\nstring='1'}}"; + "All entries: {1=" + asString + "}"; assertItmCfgExceptionMessage(a, msg); B b = new B(); msg = "The type of a value of map 'm' is not a simple type " + "but map 'm' is missing the ElementType annotation.\n" + - "All entries: {1=[[TestSubClass{\nprimInt=1,\nstring='1'}]]}"; + "All entries: {1=[[" + asString + "]]}"; assertItmCfgExceptionMessage(b, msg); } @@ -503,7 +507,7 @@ public class ValidatorTest { @Test void instanceFromMapsRequiresElementTypeToBeEnumType() { class A { - @ElementType(TestSubClass.class) + @ElementType(value = TestSubClass.class, nestingLevel = 1) List> l = listOf(); } Map map = mapOf( @@ -519,17 +523,146 @@ public class ValidatorTest { @Test void instanceFromMapElementConverterRequiresObjectsOfTypeMapStringObject() { class A { - @ElementType(TestSubClass.class) + @ElementType(value = TestSubClass.class, nestingLevel = 1) List> l = listOf(); } Map map = mapOf( "l", listOf(listOf(1, 2)) ); - ConfigurationException ex = assertIfmThrowsCfgException(new A(), map); - Throwable cause = ex.getCause(); + String msg = "Field 'l' of class 'A' has a nesting level of 1 but element " + + "'1' of type 'Integer' cannot be converted to 'TestSubClass'."; + assertIfmCfgExceptionMessage(new A(), map, msg); + } - String msg = "Initializing field 'l' requires objects of type " + - "Map but element '1' is of type 'Integer'."; - assertThat(cause.getMessage(), is(msg)); + @Test + void instanceToMapRequiresCorrectNestingLevelForLists() { + TestSubClass testValues = TestSubClass.TEST_VALUES; + class A { + @ElementType(TestSubClass.class) + List> l1 = listOf(); + + @ElementType(TestSubClass.class) + List> l2 = listOf(listOf(testValues)); + } + class B { + @ElementType(value = TestSubClass.class, nestingLevel = 1) + List>> l = listOf(listOf(listOf(testValues))); + } + class C { + @ElementType(value = TestSubClass.class, nestingLevel = 3) + List>> l = listOf(listOf(listOf(testValues))); + } + class D { + @ElementType(value = TestSubClass.class, nestingLevel = 1) + List> l = listOf(listOf( + TestSubClass.of(11, "11"), TestSubClass.of(12, "12"), + TestSubClass.of(13, "13"), TestSubClass.of(14, "14") + )); + } + + String msg = "Field 'l2' of class 'A' has a nesting level of 0 but the " + + "first object of type 'TestSubClass' was found on level 1."; + assertItmCfgExceptionMessage(new A(), msg); + + msg = "Field 'l' of class 'B' has a nesting level of 1 but the " + + "first object of type 'TestSubClass' was found on level 2."; + assertItmCfgExceptionMessage(new B(), msg); + + msg = "Field 'l' of class 'C' has a nesting level of 3 but the " + + "first object of type 'TestSubClass' was found on level 2."; + assertItmCfgExceptionMessage(new C(), msg); + + Map map = instanceToMap(new D()); + D d = instanceFromMap(new D(), map); + assertThat(d.l, is(new D().l)); + } + + + @Test + void instanceToMapRequiresCorrectNestingLevelForMaps() { + TestSubClass testValues = TestSubClass.TEST_VALUES; + class A { + @ElementType(TestSubClass.class) + Map> m1 = mapOf(); + + @ElementType(TestSubClass.class) + Map> m2 = mapOf( + 1, mapOf("1", TestSubClass.of(11, "11")), + 2, mapOf("2", TestSubClass.of(12, "12")) + ); + } + class B { + @ElementType(value = TestSubClass.class, nestingLevel = 1) + Map>> m = mapOf( + "1", mapOf("2", mapOf("3", testValues)), + "1", mapOf("2", mapOf("3", testValues)) + ); + } + class C { + @ElementType(value = TestSubClass.class, nestingLevel = 3) + Map>> m = mapOf( + "1", mapOf("2", mapOf("3", testValues)), + "1", mapOf("2", mapOf("3", testValues)) + ); + } + class D { + @ElementType(value = TestSubClass.class, nestingLevel = 1) + Map> m = mapOf( + 1, mapOf("1", TestSubClass.of(11, "11")), + 2, mapOf("2", TestSubClass.of(12, "12")), + 3, mapOf("3", TestSubClass.of(13, "13")), + 4, mapOf("4", TestSubClass.of(14, "14")) + ); + } + + String msg = "Field 'm2' of class 'A' has a nesting level of 0 but the " + + "first object of type 'TestSubClass' was found on level 1."; + assertItmCfgExceptionMessage(new A(), msg); + + msg = "Field 'm' of class 'B' has a nesting level of 1 but the " + + "first object of type 'TestSubClass' was found on level 2."; + assertItmCfgExceptionMessage(new B(), msg); + + msg = "Field 'm' of class 'C' has a nesting level of 3 but the " + + "first object of type 'TestSubClass' was found on level 2."; + assertItmCfgExceptionMessage(new C(), msg); + + Map map = instanceToMap(new D()); + D d = instanceFromMap(new D(), map); + assertThat(d.m, is(new D().m)); + } + + /* The case that the nestingLevel is set to high cannot properly be detected. */ + @Test + void instanceFromMapRequiresCorrectNestingLevelForLists() { + class A { + @ElementType(TestSubClass.class) + List l = listOf(); + } + class B { + @ElementType(LocalTestEnum.class) + List l = listOf(); + } + class C { + @ElementType(TestSubClass.class) + List> l = listOf(); + } + class D { + @ElementType(LocalTestEnum.class) + List> l = listOf(); + } + Map m = TestSubClass.TEST_VALUES.asMap(); + instanceFromMap(new A(), mapOf("l", listOf(m))); + instanceFromMap(new B(), mapOf("l", listOf("S", "T"))); + + String elementAsString = m.toString(); + String msg = "Field 'l' of class 'C' has a nesting level of 0 but element '[" + + elementAsString + "]' of type 'ArrayList' cannot be converted " + + "to 'TestSubClass'."; + assertIfmCfgExceptionMessage(new C(), mapOf("l", listOf(listOf(m))), msg); + + msg = "Field 'l' of class 'D' has a nesting level of 0 but element '[S, T]' of type " + + "'ArrayList' cannot be converted to 'LocalTestEnum'."; + assertIfmCfgExceptionMessage(new D(), mapOf("l", listOf(listOf("S", "T"))), msg); } } diff --git a/ConfigLib-Core/src/test/java/de/exlll/configlib/classes/TestClass.java b/ConfigLib-Core/src/test/java/de/exlll/configlib/classes/TestClass.java index a4fe155..a215829 100644 --- a/ConfigLib-Core/src/test/java/de/exlll/configlib/classes/TestClass.java +++ b/ConfigLib-Core/src/test/java/de/exlll/configlib/classes/TestClass.java @@ -10,9 +10,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; -import static de.exlll.configlib.util.CollectionFactory.listOf; -import static de.exlll.configlib.util.CollectionFactory.mapOf; -import static java.util.stream.Collectors.toCollection; +import static de.exlll.configlib.util.CollectionFactory.*; @SuppressWarnings("FieldCanBeLocal") @Comment({"A", "", "B", "C"}) @@ -45,59 +43,49 @@ public final class TestClass extends YamlConfiguration { /* other types */ TEST_VALUES.subClass = TestSubClass.TEST_VALUES; /* containers of simple types */ - TEST_VALUES.ints = linkedHashSetOf(1, 2, 3); + TEST_VALUES.ints = setOf(1, 2, 3); TEST_VALUES.strings = listOf("a", "b", "c"); - TEST_VALUES.doubleByBool = linkedHashMap(true, 1.0, false, 2.0); + TEST_VALUES.doubleByBool = mapOf(true, 1.0, false, 2.0); /* containers of other types */ - TEST_VALUES.subClassSet = linkedHashSetOf( - TestSubClass.of(1, "1"), TestSubClass.of(2, "2") + TEST_VALUES.subClassSet = setOf( + TestSubClass.of(1, "1"), + TestSubClass.of(2, "2") ); TEST_VALUES.subClassList = listOf( - TestSubClass.of(1, "1"), TestSubClass.of(2, "2") + TestSubClass.of(3, "3"), + TestSubClass.of(4, "4") ); - TEST_VALUES.subClassMap = linkedHashMap( - "1", TestSubClass.of(1, "1"), - "2", TestSubClass.of(2, "2") + TEST_VALUES.subClassMap = mapOf( + "5", TestSubClass.of(5, "5"), + "6", TestSubClass.of(6, "6") ); /* nested containers of simple types */ TEST_VALUES.listsList = listOf( listOf(1, 2), listOf(3, 4) ); - TEST_VALUES.setsSet = linkedHashSetOf( - linkedHashSetOf("a", "b"), linkedHashSetOf("c", "d") + TEST_VALUES.setsSet = setOf( + setOf("a", "b"), setOf("c", "d") ); - TEST_VALUES.mapsMap = linkedHashMap( + TEST_VALUES.mapsMap = mapOf( 1, mapOf("1", 1), 2, mapOf("2", 2) ); /* nested containers of custom types */ TEST_VALUES.subClassListsList = listOf( - listOf(TestSubClass.of(1, "1"), TestSubClass.of(2, "2")) + listOf(TestSubClass.of(7, "7"), TestSubClass.of(8, "8")) ); - TEST_VALUES.subClassSetsSet = linkedHashSetOf(linkedHashSetOf( - TestSubClass.of(1, "1"), TestSubClass.of(2, "2") + TEST_VALUES.subClassSetsSet = setOf(setOf( + TestSubClass.of(9, "9"), TestSubClass.of(10, "10") )); - TEST_VALUES.subClassMapsMap = linkedHashMap( - 1, mapOf("1", TestSubClass.of(1, "2")), - 2, mapOf("2", TestSubClass.of(2, "2")) + TEST_VALUES.subClassMapsMap = mapOf( + 1, mapOf("1", TestSubClass.of(11, "11")), + 2, mapOf("2", TestSubClass.of(12, "12")) ); TEST_VALUES.e1 = TestEnum.NON_DEFAULT; TEST_VALUES.enums = listOf(TestEnum.DEFAULT, TestEnum.NON_DEFAULT); - TEST_VALUES.converterSubClass = TestSubClass.of(2, "2"); + TEST_VALUES.converterSubClass = TestSubClass.of(13, "13"); TEST_VALUES.excludedClass = TestExcludedClass.TEST_VALUES; } - @SafeVarargs - private static Set linkedHashSetOf(T... values) { - return Arrays.stream(values).collect(toCollection(LinkedHashSet::new)); - } - - private static Map linkedHashMap(K k1, V v1, K k2, V v2) { - Map 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; @@ -144,11 +132,11 @@ public final class TestClass extends YamlConfiguration { private Set> setsSet = new HashSet<>(); private Map> mapsMap = new HashMap<>(); /* nested containers of custom types */ - @ElementType(TestSubClass.class) + @ElementType(value = TestSubClass.class, nestingLevel = 1) private List> subClassListsList = new ArrayList<>(); - @ElementType(TestSubClass.class) + @ElementType(value = TestSubClass.class, nestingLevel = 1) private Set> subClassSetsSet = new HashSet<>(); - @ElementType(TestSubClass.class) + @ElementType(value = TestSubClass.class, nestingLevel = 1) private Map> subClassMapsMap = new HashMap<>(); private TestEnum e1 = TestEnum.DEFAULT; diff --git a/ConfigLib-Core/src/test/java/de/exlll/configlib/classes/TestSubClass.java b/ConfigLib-Core/src/test/java/de/exlll/configlib/classes/TestSubClass.java index 6bacaf4..0ffcc98 100644 --- a/ConfigLib-Core/src/test/java/de/exlll/configlib/classes/TestSubClass.java +++ b/ConfigLib-Core/src/test/java/de/exlll/configlib/classes/TestSubClass.java @@ -1,10 +1,17 @@ package de.exlll.configlib.classes; import de.exlll.configlib.annotation.ConfigurationElement; +import de.exlll.configlib.annotation.ElementType; +import java.util.Collections; +import java.util.List; import java.util.Map; +import java.util.Set; -import static de.exlll.configlib.util.CollectionFactory.mapOf; +import static de.exlll.configlib.util.CollectionFactory.*; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; +import static java.util.stream.Collectors.toSet; @SuppressWarnings("FieldCanBeLocal") @ConfigurationElement @@ -15,25 +22,73 @@ public final class TestSubClass { TEST_VALUES = new TestSubClass(); TEST_VALUES.primInt = 1; TEST_VALUES.string = "string"; + TEST_VALUES.list = listOf("list"); + TEST_VALUES.set = setOf("set"); + TEST_VALUES.map = mapOf("map", 1); + TEST_VALUES.testSubSubClass = TestSubSubClass.of(14, "14"); + TEST_VALUES.subClassList = listOf( + TestSubSubClass.of(15, "15"), TestSubSubClass.of(16, "16") + ); + TEST_VALUES.subClassSet = setOf( + TestSubSubClass.of(17, "17"), TestSubSubClass.of(18, "18") + ); + TEST_VALUES.subClassMap = mapOf("map", TestSubSubClass.of(19, "19")); } private final int finalInt = 1; private int primInt; private String string = ""; + private List list = Collections.emptyList(); + private Set set = Collections.emptySet(); + private Map map = Collections.emptyMap(); + private TestSubSubClass testSubSubClass = new TestSubSubClass(); + @ElementType(TestSubSubClass.class) + private List subClassList = Collections.emptyList(); + @ElementType(TestSubSubClass.class) + private Set subClassSet = Collections.emptySet(); + @ElementType(TestSubSubClass.class) + private Map subClassMap = Collections.emptyMap(); public static TestSubClass of(int primInt, String string) { - TestSubClass subClass = new TestSubClass(); - subClass.primInt = primInt; - subClass.string = string; - return subClass; + TestSubClass cls = new TestSubClass(); + cls.primInt = primInt; + cls.string = string; + cls.list = listOf(string); + cls.set = setOf(string); + cls.map = mapOf(string, primInt); + cls.testSubSubClass = TestSubSubClass.of(primInt, string); + cls.subClassList = listOf( + TestSubSubClass.of(primInt * 100, string) + ); + cls.subClassSet = setOf( + TestSubSubClass.of(primInt * 101, string) + ); + cls.subClassMap = mapOf(string, TestSubSubClass.of(primInt * 102, string)); + return cls; } public Map asMap() { - return mapOf("primInt", primInt, "string", string); - } - - public int getFinalInt() { - return finalInt; + Map asMap = mapOf("primInt", primInt, "string", string); + asMap.put("list", list); + asMap.put("set", set); + asMap.put("map", map); + asMap.put("testSubSubClass", testSubSubClass.asMap()); + asMap.put( + "subClassList", subClassList.stream() + .map(TestSubSubClass::asMap) + .collect(toList()) + ); + asMap.put( + "subClassSet", subClassSet.stream() + .map(TestSubSubClass::asMap) + .collect(toSet()) + ); + asMap.put( + "subClassMap", subClassMap.entrySet().stream() + .map(e -> mapEntry(e.getKey(), e.getValue().asMap())) + .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)) + ); + return asMap; } public int getPrimInt() { @@ -44,29 +99,79 @@ public final class TestSubClass { return string; } + public List getList() { + return list; + } + + public Set getSet() { + return set; + } + + public Map getMap() { + return map; + } + + public TestSubSubClass getTestSubSubClass() { + return testSubSubClass; + } + + public List getSubClassList() { + return subClassList; + } + + public Set getSubClassSet() { + return subClassSet; + } + + public Map getSubClassMap() { + return subClassMap; + } + @Override public String toString() { return "TestSubClass{" + - "\nprimInt=" + primInt + - ",\nstring='" + string + '\'' + + "primInt=" + primInt + + ", string='" + string + '\'' + + ", list=" + list + + ", set=" + set + + ", map=" + map + + ", testSubSubClass=" + testSubSubClass + + ", subClassList=" + subClassList + + ", subClassSet=" + subClassSet + + ", subClassMap=" + subClassMap + '}'; } @Override public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof TestSubClass)) return false; - - TestSubClass subClass = (TestSubClass) o; - - if (primInt != subClass.primInt) return false; - return string.equals(subClass.string); + if (o == null || getClass() != o.getClass()) return false; + + TestSubClass that = (TestSubClass) o; + + if (primInt != that.primInt) return false; + if (!string.equals(that.string)) return false; + if (!list.equals(that.list)) return false; + if (!set.equals(that.set)) return false; + if (!map.equals(that.map)) return false; + if (!testSubSubClass.equals(that.testSubSubClass)) return false; + if (!subClassList.equals(that.subClassList)) return false; + if (!subClassSet.equals(that.subClassSet)) return false; + return subClassMap.equals(that.subClassMap); } @Override public int hashCode() { - int result = primInt; + int result = finalInt; + result = 31 * result + primInt; result = 31 * result + string.hashCode(); + result = 31 * result + list.hashCode(); + result = 31 * result + set.hashCode(); + result = 31 * result + map.hashCode(); + result = 31 * result + testSubSubClass.hashCode(); + result = 31 * result + subClassList.hashCode(); + result = 31 * result + subClassSet.hashCode(); + result = 31 * result + subClassMap.hashCode(); return result; } } diff --git a/ConfigLib-Core/src/test/java/de/exlll/configlib/classes/TestSubSubClass.java b/ConfigLib-Core/src/test/java/de/exlll/configlib/classes/TestSubSubClass.java new file mode 100644 index 0000000..6fddb20 --- /dev/null +++ b/ConfigLib-Core/src/test/java/de/exlll/configlib/classes/TestSubSubClass.java @@ -0,0 +1,103 @@ +package de.exlll.configlib.classes; + +import de.exlll.configlib.annotation.ConfigurationElement; + +import java.util.*; + +import static de.exlll.configlib.util.CollectionFactory.listOf; +import static de.exlll.configlib.util.CollectionFactory.mapOf; +import static de.exlll.configlib.util.CollectionFactory.setOf; + +@ConfigurationElement +public final class TestSubSubClass { + public static final TestSubSubClass TEST_VALUES; + + static { + TEST_VALUES = new TestSubSubClass(); + TEST_VALUES.primInt = 1; + TEST_VALUES.string = "string"; + TEST_VALUES.list = listOf("list"); + TEST_VALUES.set = setOf("set"); + TEST_VALUES.map = mapOf("map", 1); + } + + private int primInt; + private String string = ""; + private List list = listOf(); + private Set set = setOf(); + private Map map = mapOf(); + + public static TestSubSubClass of(int primInt, String string) { + TestSubSubClass cls = new TestSubSubClass(); + cls.primInt = primInt; + cls.string = string; + String concat = string + string; + cls.list = listOf(concat); + cls.set = setOf(concat); + cls.map = mapOf(concat, primInt); + return cls; + } + + public Map asMap() { + Map asMap = mapOf("primInt", primInt, "string", string); + asMap.put("list", list); + asMap.put("set", set); + asMap.put("map", map); + return asMap; + } + + public int getPrimInt() { + return primInt; + } + + public String getString() { + return string; + } + + public List getList() { + return list; + } + + public Set getSet() { + return set; + } + + public Map getMap() { + return map; + } + + @Override + public String toString() { + return "TestSubSubClass{" + + "primInt=" + primInt + + ", string='" + string + '\'' + + ", list=" + list + + ", set=" + set + + ", map=" + map + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + TestSubSubClass that = (TestSubSubClass) o; + + if (primInt != that.primInt) return false; + if (!string.equals(that.string)) return false; + if (!list.equals(that.list)) return false; + if (!set.equals(that.set)) return false; + return map.equals(that.map); + } + + @Override + public int hashCode() { + int result = primInt; + result = 31 * result + string.hashCode(); + result = 31 * result + list.hashCode(); + result = 31 * result + set.hashCode(); + result = 31 * result + map.hashCode(); + return result; + } +} diff --git a/ConfigLib-Core/src/test/java/de/exlll/configlib/configs/yaml/YamlConfigurationTest.java b/ConfigLib-Core/src/test/java/de/exlll/configlib/configs/yaml/YamlConfigurationTest.java index 6bcbb37..ad677a7 100644 --- a/ConfigLib-Core/src/test/java/de/exlll/configlib/configs/yaml/YamlConfigurationTest.java +++ b/ConfigLib-Core/src/test/java/de/exlll/configlib/configs/yaml/YamlConfigurationTest.java @@ -269,6 +269,67 @@ class YamlConfigurationTest { "subClass:\n" + " primInt: 1\n" + " string: string\n" + + " list:\n" + + " - list\n" + + " set: !!set\n" + + " set: null\n" + + " map:\n" + + " map: 1\n" + + " testSubSubClass:\n" + + " primInt: 14\n" + + " string: '14'\n" + + " list:\n" + + " - '1414'\n" + + " set: !!set\n" + + " '1414': null\n" + + " map:\n" + + " '1414': 14\n" + + " subClassList:\n" + + " - primInt: 15\n" + + " string: '15'\n" + + " list:\n" + + " - '1515'\n" + + " set: !!set\n" + + " '1515': null\n" + + " map:\n" + + " '1515': 15\n" + + " - primInt: 16\n" + + " string: '16'\n" + + " list:\n" + + " - '1616'\n" + + " set: !!set\n" + + " '1616': null\n" + + " map:\n" + + " '1616': 16\n" + + " subClassSet: !!set\n" + + " ? primInt: 18\n" + + " string: '18'\n" + + " list:\n" + + " - '1818'\n" + + " set: !!set\n" + + " '1818': null\n" + + " map:\n" + + " '1818': 18\n" + + " : null\n" + + " ? primInt: 17\n" + + " string: '17'\n" + + " list:\n" + + " - '1717'\n" + + " set: !!set\n" + + " '1717': null\n" + + " map:\n" + + " '1717': 17\n" + + " : null\n" + + " subClassMap:\n" + + " map:\n" + + " primInt: 19\n" + + " string: '19'\n" + + " list:\n" + + " - '1919'\n" + + " set: !!set\n" + + " '1919': null\n" + + " map:\n" + + " '1919': 19\n" + "ints: !!set\n" + " 1: null\n" + " 2: null\n" + @@ -283,22 +344,286 @@ class YamlConfigurationTest { "subClassSet: !!set\n" + " ? primInt: 1\n" + " string: '1'\n" + + " list:\n" + + " - '1'\n" + + " set: !!set\n" + + " '1': null\n" + + " map:\n" + + " '1': 1\n" + + " testSubSubClass:\n" + + " primInt: 1\n" + + " string: '1'\n" + + " list:\n" + + " - '11'\n" + + " set: !!set\n" + + " '11': null\n" + + " map:\n" + + " '11': 1\n" + + " subClassList:\n" + + " - primInt: 100\n" + + " string: '1'\n" + + " list:\n" + + " - '11'\n" + + " set: !!set\n" + + " '11': null\n" + + " map:\n" + + " '11': 100\n" + + " subClassSet: !!set\n" + + " ? primInt: 101\n" + + " string: '1'\n" + + " list:\n" + + " - '11'\n" + + " set: !!set\n" + + " '11': null\n" + + " map:\n" + + " '11': 101\n" + + " : null\n" + + " subClassMap:\n" + + " '1':\n" + + " primInt: 102\n" + + " string: '1'\n" + + " list:\n" + + " - '11'\n" + + " set: !!set\n" + + " '11': null\n" + + " map:\n" + + " '11': 102\n" + " : null\n" + " ? primInt: 2\n" + " string: '2'\n" + + " list:\n" + + " - '2'\n" + + " set: !!set\n" + + " '2': null\n" + + " map:\n" + + " '2': 2\n" + + " testSubSubClass:\n" + + " primInt: 2\n" + + " string: '2'\n" + + " list:\n" + + " - '22'\n" + + " set: !!set\n" + + " '22': null\n" + + " map:\n" + + " '22': 2\n" + + " subClassList:\n" + + " - primInt: 200\n" + + " string: '2'\n" + + " list:\n" + + " - '22'\n" + + " set: !!set\n" + + " '22': null\n" + + " map:\n" + + " '22': 200\n" + + " subClassSet: !!set\n" + + " ? primInt: 202\n" + + " string: '2'\n" + + " list:\n" + + " - '22'\n" + + " set: !!set\n" + + " '22': null\n" + + " map:\n" + + " '22': 202\n" + + " : null\n" + + " subClassMap:\n" + + " '2':\n" + + " primInt: 204\n" + + " string: '2'\n" + + " list:\n" + + " - '22'\n" + + " set: !!set\n" + + " '22': null\n" + + " map:\n" + + " '22': 204\n" + " : null\n" + "subClassList:\n" + - "- primInt: 1\n" + - " string: '1'\n" + - "- primInt: 2\n" + - " string: '2'\n" + + "- primInt: 3\n" + + " string: '3'\n" + + " list:\n" + + " - '3'\n" + + " set: !!set\n" + + " '3': null\n" + + " map:\n" + + " '3': 3\n" + + " testSubSubClass:\n" + + " primInt: 3\n" + + " string: '3'\n" + + " list:\n" + + " - '33'\n" + + " set: !!set\n" + + " '33': null\n" + + " map:\n" + + " '33': 3\n" + + " subClassList:\n" + + " - primInt: 300\n" + + " string: '3'\n" + + " list:\n" + + " - '33'\n" + + " set: !!set\n" + + " '33': null\n" + + " map:\n" + + " '33': 300\n" + + " subClassSet: !!set\n" + + " ? primInt: 303\n" + + " string: '3'\n" + + " list:\n" + + " - '33'\n" + + " set: !!set\n" + + " '33': null\n" + + " map:\n" + + " '33': 303\n" + + " : null\n" + + " subClassMap:\n" + + " '3':\n" + + " primInt: 306\n" + + " string: '3'\n" + + " list:\n" + + " - '33'\n" + + " set: !!set\n" + + " '33': null\n" + + " map:\n" + + " '33': 306\n" + + "- primInt: 4\n" + + " string: '4'\n" + + " list:\n" + + " - '4'\n" + + " set: !!set\n" + + " '4': null\n" + + " map:\n" + + " '4': 4\n" + + " testSubSubClass:\n" + + " primInt: 4\n" + + " string: '4'\n" + + " list:\n" + + " - '44'\n" + + " set: !!set\n" + + " '44': null\n" + + " map:\n" + + " '44': 4\n" + + " subClassList:\n" + + " - primInt: 400\n" + + " string: '4'\n" + + " list:\n" + + " - '44'\n" + + " set: !!set\n" + + " '44': null\n" + + " map:\n" + + " '44': 400\n" + + " subClassSet: !!set\n" + + " ? primInt: 404\n" + + " string: '4'\n" + + " list:\n" + + " - '44'\n" + + " set: !!set\n" + + " '44': null\n" + + " map:\n" + + " '44': 404\n" + + " : null\n" + + " subClassMap:\n" + + " '4':\n" + + " primInt: 408\n" + + " string: '4'\n" + + " list:\n" + + " - '44'\n" + + " set: !!set\n" + + " '44': null\n" + + " map:\n" + + " '44': 408\n" + "subClassMap:\n" + - " '1':\n" + - " primInt: 1\n" + - " string: '1'\n" + - " '2':\n" + - " primInt: 2\n" + - " string: '2'\n" + + " '5':\n" + + " primInt: 5\n" + + " string: '5'\n" + + " list:\n" + + " - '5'\n" + + " set: !!set\n" + + " '5': null\n" + + " map:\n" + + " '5': 5\n" + + " testSubSubClass:\n" + + " primInt: 5\n" + + " string: '5'\n" + + " list:\n" + + " - '55'\n" + + " set: !!set\n" + + " '55': null\n" + + " map:\n" + + " '55': 5\n" + + " subClassList:\n" + + " - primInt: 500\n" + + " string: '5'\n" + + " list:\n" + + " - '55'\n" + + " set: !!set\n" + + " '55': null\n" + + " map:\n" + + " '55': 500\n" + + " subClassSet: !!set\n" + + " ? primInt: 505\n" + + " string: '5'\n" + + " list:\n" + + " - '55'\n" + + " set: !!set\n" + + " '55': null\n" + + " map:\n" + + " '55': 505\n" + + " : null\n" + + " subClassMap:\n" + + " '5':\n" + + " primInt: 510\n" + + " string: '5'\n" + + " list:\n" + + " - '55'\n" + + " set: !!set\n" + + " '55': null\n" + + " map:\n" + + " '55': 510\n" + + " '6':\n" + + " primInt: 6\n" + + " string: '6'\n" + + " list:\n" + + " - '6'\n" + + " set: !!set\n" + + " '6': null\n" + + " map:\n" + + " '6': 6\n" + + " testSubSubClass:\n" + + " primInt: 6\n" + + " string: '6'\n" + + " list:\n" + + " - '66'\n" + + " set: !!set\n" + + " '66': null\n" + + " map:\n" + + " '66': 6\n" + + " subClassList:\n" + + " - primInt: 600\n" + + " string: '6'\n" + + " list:\n" + + " - '66'\n" + + " set: !!set\n" + + " '66': null\n" + + " map:\n" + + " '66': 600\n" + + " subClassSet: !!set\n" + + " ? primInt: 606\n" + + " string: '6'\n" + + " list:\n" + + " - '66'\n" + + " set: !!set\n" + + " '66': null\n" + + " map:\n" + + " '66': 606\n" + + " : null\n" + + " subClassMap:\n" + + " '6':\n" + + " primInt: 612\n" + + " string: '6'\n" + + " list:\n" + + " - '66'\n" + + " set: !!set\n" + + " '66': null\n" + + " map:\n" + + " '66': 612\n" + "listsList:\n" + "- - 1\n" + " - 2\n" + @@ -319,33 +644,297 @@ class YamlConfigurationTest { " 2:\n" + " '2': 2\n" + "subClassListsList:\n" + - "- - primInt: 1\n" + - " string: '1'\n" + - " - primInt: 2\n" + - " string: '2'\n" + + "- - primInt: 7\n" + + " string: '7'\n" + + " list:\n" + + " - '7'\n" + + " set: !!set\n" + + " '7': null\n" + + " map:\n" + + " '7': 7\n" + + " testSubSubClass:\n" + + " primInt: 7\n" + + " string: '7'\n" + + " list:\n" + + " - '77'\n" + + " set: !!set\n" + + " '77': null\n" + + " map:\n" + + " '77': 7\n" + + " subClassList:\n" + + " - primInt: 700\n" + + " string: '7'\n" + + " list:\n" + + " - '77'\n" + + " set: !!set\n" + + " '77': null\n" + + " map:\n" + + " '77': 700\n" + + " subClassSet: !!set\n" + + " ? primInt: 707\n" + + " string: '7'\n" + + " list:\n" + + " - '77'\n" + + " set: !!set\n" + + " '77': null\n" + + " map:\n" + + " '77': 707\n" + + " : null\n" + + " subClassMap:\n" + + " '7':\n" + + " primInt: 714\n" + + " string: '7'\n" + + " list:\n" + + " - '77'\n" + + " set: !!set\n" + + " '77': null\n" + + " map:\n" + + " '77': 714\n" + + " - primInt: 8\n" + + " string: '8'\n" + + " list:\n" + + " - '8'\n" + + " set: !!set\n" + + " '8': null\n" + + " map:\n" + + " '8': 8\n" + + " testSubSubClass:\n" + + " primInt: 8\n" + + " string: '8'\n" + + " list:\n" + + " - '88'\n" + + " set: !!set\n" + + " '88': null\n" + + " map:\n" + + " '88': 8\n" + + " subClassList:\n" + + " - primInt: 800\n" + + " string: '8'\n" + + " list:\n" + + " - '88'\n" + + " set: !!set\n" + + " '88': null\n" + + " map:\n" + + " '88': 800\n" + + " subClassSet: !!set\n" + + " ? primInt: 808\n" + + " string: '8'\n" + + " list:\n" + + " - '88'\n" + + " set: !!set\n" + + " '88': null\n" + + " map:\n" + + " '88': 808\n" + + " : null\n" + + " subClassMap:\n" + + " '8':\n" + + " primInt: 816\n" + + " string: '8'\n" + + " list:\n" + + " - '88'\n" + + " set: !!set\n" + + " '88': null\n" + + " map:\n" + + " '88': 816\n" + "subClassSetsSet: !!set\n" + " ? !!set\n" + - " ? primInt: 1\n" + - " string: '1'\n" + + " ? primInt: 10\n" + + " string: '10'\n" + + " list:\n" + + " - '10'\n" + + " set: !!set\n" + + " '10': null\n" + + " map:\n" + + " '10': 10\n" + + " testSubSubClass:\n" + + " primInt: 10\n" + + " string: '10'\n" + + " list:\n" + + " - '1010'\n" + + " set: !!set\n" + + " '1010': null\n" + + " map:\n" + + " '1010': 10\n" + + " subClassList:\n" + + " - primInt: 1000\n" + + " string: '10'\n" + + " list:\n" + + " - '1010'\n" + + " set: !!set\n" + + " '1010': null\n" + + " map:\n" + + " '1010': 1000\n" + + " subClassSet: !!set\n" + + " ? primInt: 1010\n" + + " string: '10'\n" + + " list:\n" + + " - '1010'\n" + + " set: !!set\n" + + " '1010': null\n" + + " map:\n" + + " '1010': 1010\n" + + " : null\n" + + " subClassMap:\n" + + " '10':\n" + + " primInt: 1020\n" + + " string: '10'\n" + + " list:\n" + + " - '1010'\n" + + " set: !!set\n" + + " '1010': null\n" + + " map:\n" + + " '1010': 1020\n" + " : null\n" + - " ? primInt: 2\n" + - " string: '2'\n" + + " ? primInt: 9\n" + + " string: '9'\n" + + " list:\n" + + " - '9'\n" + + " set: !!set\n" + + " '9': null\n" + + " map:\n" + + " '9': 9\n" + + " testSubSubClass:\n" + + " primInt: 9\n" + + " string: '9'\n" + + " list:\n" + + " - '99'\n" + + " set: !!set\n" + + " '99': null\n" + + " map:\n" + + " '99': 9\n" + + " subClassList:\n" + + " - primInt: 900\n" + + " string: '9'\n" + + " list:\n" + + " - '99'\n" + + " set: !!set\n" + + " '99': null\n" + + " map:\n" + + " '99': 900\n" + + " subClassSet: !!set\n" + + " ? primInt: 909\n" + + " string: '9'\n" + + " list:\n" + + " - '99'\n" + + " set: !!set\n" + + " '99': null\n" + + " map:\n" + + " '99': 909\n" + + " : null\n" + + " subClassMap:\n" + + " '9':\n" + + " primInt: 918\n" + + " string: '9'\n" + + " list:\n" + + " - '99'\n" + + " set: !!set\n" + + " '99': null\n" + + " map:\n" + + " '99': 918\n" + " : null\n" + " : null\n" + "subClassMapsMap:\n" + " 1:\n" + " '1':\n" + - " primInt: 1\n" + - " string: '2'\n" + + " primInt: 11\n" + + " string: '11'\n" + + " list:\n" + + " - '11'\n" + + " set: !!set\n" + + " '11': null\n" + + " map:\n" + + " '11': 11\n" + + " testSubSubClass:\n" + + " primInt: 11\n" + + " string: '11'\n" + + " list:\n" + + " - '1111'\n" + + " set: !!set\n" + + " '1111': null\n" + + " map:\n" + + " '1111': 11\n" + + " subClassList:\n" + + " - primInt: 1100\n" + + " string: '11'\n" + + " list:\n" + + " - '1111'\n" + + " set: !!set\n" + + " '1111': null\n" + + " map:\n" + + " '1111': 1100\n" + + " subClassSet: !!set\n" + + " ? primInt: 1111\n" + + " string: '11'\n" + + " list:\n" + + " - '1111'\n" + + " set: !!set\n" + + " '1111': null\n" + + " map:\n" + + " '1111': 1111\n" + + " : null\n" + + " subClassMap:\n" + + " '11':\n" + + " primInt: 1122\n" + + " string: '11'\n" + + " list:\n" + + " - '1111'\n" + + " set: !!set\n" + + " '1111': null\n" + + " map:\n" + + " '1111': 1122\n" + " 2:\n" + " '2':\n" + - " primInt: 2\n" + - " string: '2'\n" + + " primInt: 12\n" + + " string: '12'\n" + + " list:\n" + + " - '12'\n" + + " set: !!set\n" + + " '12': null\n" + + " map:\n" + + " '12': 12\n" + + " testSubSubClass:\n" + + " primInt: 12\n" + + " string: '12'\n" + + " list:\n" + + " - '1212'\n" + + " set: !!set\n" + + " '1212': null\n" + + " map:\n" + + " '1212': 12\n" + + " subClassList:\n" + + " - primInt: 1200\n" + + " string: '12'\n" + + " list:\n" + + " - '1212'\n" + + " set: !!set\n" + + " '1212': null\n" + + " map:\n" + + " '1212': 1200\n" + + " subClassSet: !!set\n" + + " ? primInt: 1212\n" + + " string: '12'\n" + + " list:\n" + + " - '1212'\n" + + " set: !!set\n" + + " '1212': null\n" + + " map:\n" + + " '1212': 1212\n" + + " : null\n" + + " subClassMap:\n" + + " '12':\n" + + " primInt: 1224\n" + + " string: '12'\n" + + " list:\n" + + " - '1212'\n" + + " set: !!set\n" + + " '1212': null\n" + + " map:\n" + + " '1212': 1224\n" + "e1: NON_DEFAULT\n" + "enums:\n" + "- DEFAULT\n" + "- NON_DEFAULT\n" + - "converterSubClass: '2:2'\n" + + "converterSubClass: '13:13'\n" + "excludedClass: !!de.exlll.configlib.classes.TestExcludedClass\n" + " primInt: 1\n" + " string: string"; diff --git a/ConfigLib-Core/src/test/java/de/exlll/configlib/util/CollectionFactory.java b/ConfigLib-Core/src/test/java/de/exlll/configlib/util/CollectionFactory.java index 2633f91..d6b87a5 100644 --- a/ConfigLib-Core/src/test/java/de/exlll/configlib/util/CollectionFactory.java +++ b/ConfigLib-Core/src/test/java/de/exlll/configlib/util/CollectionFactory.java @@ -13,15 +13,15 @@ public final class CollectionFactory { @SafeVarargs public static Set setOf(T... values) { - return new HashSet<>(Arrays.asList(values)); + return new LinkedHashSet<>(Arrays.asList(values)); } public static Map mapOf() { - return new HashMap<>(); + return new LinkedHashMap<>(); } public static Map mapOf(K k, V v) { - HashMap map = new HashMap<>(); + HashMap map = new LinkedHashMap<>(); map.put(k, v); return map; } diff --git a/README.md b/README.md index 5ec6c82..1a04510 100644 --- a/README.md +++ b/README.md @@ -188,7 +188,7 @@ class MyConfiguration extends YamlConfiguration { Note: Even though sets are supported, their YAML-representation is 'pretty ugly', so it's better to use lists instead. If you need set behavior, you can internally use lists and convert them to sets using the `preSave/postLoad`-hooks. -Lists, sets and maps that contain other types (e.g. custom types or enums) must use the `ElementType` annotation. +Lists, sets and maps that contain other types (e.g. custom types or enums) must use the `@ElementType` annotation. Only simple types can be used as map keys. ```java @@ -208,7 +208,15 @@ class MyConfiguration extends YamlConfiguration { } ``` -Lists, sets and maps can be nested. +Lists, sets and maps can be nested. If nested collections contain custom types, you must specify the +nesting level using the `@ElementType` annotation. Examples: + +* `List` requires a nesting level of 0, which is the default value, so you don't have to set it +* `List>` requires a nesting level of 1 +* `List>>` requires a nesting level of 2 +* `List>` requires a nesting level of 1 +* `List>>` requires a nesting level of 2 + ```java @ConfigurationElement class MyCustomClass {/* fields etc.*/} @@ -218,19 +226,19 @@ class MyConfiguration extends YamlConfiguration { private Set> setsSet = new HashSet<>(); private Map> mapsMap = new HashMap<>(); - @ElementType(MyCustomClass.class) + @ElementType(value = MyCustomClass.class, nestingLevel = 1) private List> customClassListsList = new ArrayList<>(); - @ElementType(MyCustomClass.class) + @ElementType(value = MyCustomClass.class, nestingLevel = 1) private Set> customClassSetsSet = new HashSet<>(); - @ElementType(MyCustomClass.class) + @ElementType(value = MyCustomClass.class, nestingLevel = 1) private Map> customClassMapsMap = new HashMap<>(); // ... } ``` #### Adding comments -You can add comments to a configuration class or a its field by using the `Comment` annotation. +You can add comments to a configuration class or a its field by using the `@Comment` annotation. Class comments are saved at the beginning of a configuration file. ```java @@ -259,7 +267,7 @@ class MyConfiguration extends YamlConfiguration { ``` #### Excluding fields from being converted -To exclude fields from being converted, annotate them with the `NoConvert` annotation. This may be useful if the +To exclude fields from being converted, annotate them with the `@NoConvert` annotation. This may be useful if the configuration knows how to (de-)serialize instances of that type. For example, a `BukkitYamlConfiguration` knows how to serialize `ItemStack` instances. @@ -439,14 +447,14 @@ public final class DatabasePlugin extends JavaPlugin { de.exlll configlib-bukkit - 2.0.2 + 2.0.3 de.exlll configlib-bungee - 2.0.2 + 2.0.3 ``` #### Gradle @@ -458,9 +466,9 @@ repositories { } dependencies { // for Bukkit plugins - compile group: 'de.exlll', name: 'configlib-bukkit', version: '2.0.2' + compile group: 'de.exlll', name: 'configlib-bukkit', version: '2.0.3' // for Bungee plugins - compile group: 'de.exlll', name: 'configlib-bungee', version: '2.0.2' + compile group: 'de.exlll', name: 'configlib-bungee', version: '2.0.3' } ``` \ No newline at end of file diff --git a/build.gradle b/build.gradle index 25144f9..25500f0 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ allprojects { group 'de.exlll' - version '2.0.2' + version '2.0.3' } subprojects { apply plugin: 'java' diff --git a/docs/tutorial.md b/docs/tutorial.md index e532c83..0176fcd 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -186,7 +186,7 @@ final class Credentials { private String password; // ConfigurationElements must have a no-args constructor (can be private) - private Credentials() {} + private Credentials() { this("", ""); } public Credentials(String username, String password) { this.username = username; @@ -200,7 +200,7 @@ final class User { private Credentials credentials; private String email; - private User() {} + private User() { this("", "", ""); } public User(String username, String password, String email) { this.credentials = new Credentials(username, password); @@ -215,7 +215,7 @@ Now we can use the `User` class for our `moderator` field: ```java public final class GameConfig extends BukkitYamlConfiguration { // ... - private User moderator = new User("alex", "a@b.c", "123"); + private User moderator = new User("alex", "123", "a@b.c"); // ... } ``` @@ -238,8 +238,8 @@ public final class GameConfig extends BukkitYamlConfiguration { private Map initUsersByName() { Map usersByName = new HashMap<>(); - usersByName.put("Pete", new User("Pete", "pete@example.com", "123")); - usersByName.put("Mary", new User("Mary", "mary@example.com", "456")); + usersByName.put("Pete", new User("Pete", "123", "pete@example.com")); + usersByName.put("Mary", new User("Mary", "456", "mary@example.com")); // ... return usersByName; } @@ -416,7 +416,7 @@ public final class GamePlugin extends JavaPlugin { final class GameConfig extends BukkitYamlConfiguration { @Comment("Valid color codes: &4, &c, &e") private String winMessage = "&4YOU WON"; - private User moderator = new User("alex", "a@b.c", "123"); + private User moderator = new User("alex", "123", "a@b.c"); private List blockedUsers = Arrays.asList("root", "john"); private List> teamMembers = Arrays.asList( Arrays.asList("Pete", "Mary", "Alice", "Leo"), @@ -447,16 +447,16 @@ final class GameConfig extends BukkitYamlConfiguration { super(path, properties); } - private HashMap initUsersByName() { - HashMap usersByName = new HashMap<>(); - usersByName.put("Pete", new User("Pete", "pete@example.com", "123")); - usersByName.put("Mary", new User("Mary", "mary@example.com", "456")); - usersByName.put("Alice", new User("Alice", "alice@example.com", "789")); - usersByName.put("Leo", new User("Leo", "leo@example.com", "135")); - usersByName.put("Eli", new User("Eli", "eli@example.com", "246")); - usersByName.put("Eve", new User("Eve", "eve@example.com", "357")); - usersByName.put("Paul", new User("Paul", "paul@example.com", "468")); - usersByName.put("Patrick", new User("Patrick", "patrick@example.com", "579")); + private Map initUsersByName() { + Map usersByName = new HashMap<>(); + usersByName.put("Pete", new User("Pete", "123", "pete@example.com")); + usersByName.put("Mary", new User("Mary", "456", "mary@example.com")); + usersByName.put("Alice", new User("Alice", "789", "alice@example.com")); + usersByName.put("Leo", new User("Leo", "135", "leo@example.com")); + usersByName.put("Eli", new User("Eli", "246", "eli@example.com")); + usersByName.put("Eve", new User("Eve", "357", "eve@example.com")); + usersByName.put("Paul", new User("Paul", "468", "paul@example.com")); + usersByName.put("Patrick", new User("Patrick", "579", "patrick@example.com")); return usersByName; } @@ -497,7 +497,7 @@ final class User { private Credentials credentials; private String email; - private User() {} + private User() { this("", "", ""); } public User(String username, String password, String email) { this.credentials = new Credentials(username, password); @@ -511,7 +511,7 @@ final class Credentials { private String password; // ConfigurationElements must have a no-args constructor (can be private) - private Credentials() {} + private Credentials() { this("", ""); } public Credentials(String username, String password) { this.username = username;