Config value is not set in nested bean value, if bean doesn't have default value
If I create a custom bean property, and pass default bean with default value, config is created with that instance.
If one of the fields is then removed, ConfigMe throws ConfigMeMapperException instead of adding the missing field from default instance.
According to https://github.com/AuthMe/ConfigMe/wiki/Bean-properties#error-handling , it should use the default object instead:
If it does not succeed to set a field's value, the mapping is considered to have failed and the default object is used. It is therefore important to initialize fields as shown with the country name above if you don't want the absence of one field to make ConfigMe ignore all fields and use the default bean object provided on initialization.
I've written example project, based on https://github.com/AuthMe/ConfigMe/wiki/Bean-properties#using-beans-within-beans.
Example stacktrace:
Exception in thread "main" ch.jalu.configme.beanmapper.ConfigMeMapperException: Could not create object of type 'dev.bloodstone.tests.configmetest.Country'. It is required to have a default constructor.
at ch.jalu.configme.beanmapper.MapperImpl.createBeanMatchingType(MapperImpl.java:359)
at ch.jalu.configme.beanmapper.MapperImpl.createBean(MapperImpl.java:333)
at ch.jalu.configme.beanmapper.MapperImpl.convertValueForType(MapperImpl.java:189)
at ch.jalu.configme.beanmapper.MapperImpl.convertToBean(MapperImpl.java:159)
at ch.jalu.configme.properties.types.BeanPropertyType.convert(BeanPropertyType.java:28)
at ch.jalu.configme.properties.TypeBasedProperty.getFromReader(TypeBasedProperty.java:34)
at ch.jalu.configme.properties.BaseProperty.determineValue(BaseProperty.java:46)
at ch.jalu.configme.configurationdata.ConfigurationDataImpl.lambda$initializeValues$0(ConfigurationDataImpl.java:67)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1540)
at java.base/java.util.Collections$UnmodifiableCollection.forEach(Collections.java:1085)
at ch.jalu.configme.configurationdata.ConfigurationDataImpl.initializeValues(ConfigurationDataImpl.java:67)
at ch.jalu.configme.SettingsManagerImpl.loadFromResourceAndValidate(SettingsManagerImpl.java:88)
at ch.jalu.configme.SettingsManagerImpl.<init>(SettingsManagerImpl.java:45)
at ch.jalu.configme.SettingsManagerBuilder.create(SettingsManagerBuilder.java:115)
at dev.bloodstone.tests.configmetest.ConfigManager.<init>(ConfigManager.kt:11)
at dev.bloodstone.tests.configmetest.MainKt.main(Main.kt:5)
Caused by: java.lang.NoSuchMethodException: dev.bloodstone.tests.configmetest.Country.<init>()
at java.base/java.lang.Class.getConstructor0(Class.java:3349)
at java.base/java.lang.Class.getDeclaredConstructor(Class.java:2553)
at ch.jalu.configme.beanmapper.MapperImpl.createBeanMatchingType(MapperImpl.java:357)
... 15 more
Reproduction
Download: https://github.com/Prof-Bloodstone/ConfigMeTest
Build the project and run it once - it should create /tmp/config.yml
Remove leader name from config, and run it again - it'll throw above error.
git clone https://github.com/Prof-Bloodstone/ConfigMeTest.git /tmp/cmt
cd /tmp/cmt
./gradlew build
java -jar build/libs/configmetest-1.0-SNAPSHOT.jar
sed -i '5 d' /tmp/config.yml
java -jar build/libs/configmetest-1.0-SNAPSHOT.jar
Use case
I have multiple fields in a config - each of those is the same bean type, but each of those have different default values. This makes it harder to do that.
Thanks for the detailed error description! Being able to run the project was really insightful. I get the same behavior even if I leave in the leader name—the problem is the bean mapper is implemented to use a no-args constructor since it works with JavaBeans:
ConfigMeMapperException: Could not create object of type 'dev.bloodstone.tests.configmetest.Country'. It is required to have a default constructor
I'm afraid it looks like using Kotlin data classes with the bean mapper is not possible right now. You and someone else have reported a few Kotlin-related bugs or improvements which I think would make sense to tackle in a common release. I'll put this on the roadmap for 1.3.
For the record: usually I don't reply so late and I'm really thankful for all bug reports, improvement ideas etc.
Sorry, I too got busy :grin:
The problem is the bean mapper is implemented to use a no-args constructor since it works with JavaBeans.
Yeah - if I provide default values to all fields, or make them nullable, then it generates a default constructor. But doing so isn't ideal and would require a proxy as a hack to make all fields not null, so that I don't have to check nullability each time etc.
I'm afraid it looks like using Kotlin data classes with the bean mapper is not possible right now.
I think the only required change might be to read the bean values and pass them to the constructor. I don't know how it's written. Interested in accepting a PR if I can figure it out? Or other Kotlin-related improvements?
The truth is that despite ConfigMe being far from perfect for me, especially in Kotlin, I tried so many different config libs that did worse that I think contributing here makes most sense to me. I'm still on the lookout though.
I wish releases were done more frequent - there's some features from months ago that weren't released yet.
So, I've thought about it a little bit, but this is not possible to fix in java.
Imagine following scenario:
class MyBean {
private String x;
private int y;
private String z;
public MyBean(String x, int y);
public MyBean(int y, String z);
public MyBean(String x, int y, String z);
// Assume above constructors and setters etc are implemented
}
If I have a map {"x": "value", "y": 20}, then I can't really map it with java, since I can only use the count and order of argument types, can't access names etc. There is also a huge ambiguity to what should be prefered etc.
My only idea RN is to extend MapperImpl in Kotlin and override it's createBean method: https://github.com/AuthMe/ConfigMe/blob/master/src/main/java/ch/jalu/configme/beanmapper/MapperImpl.java#L329
This would mean that there's a Kotlin-specific API for some things too, but I think it's for the better - ability to define config by delegation instead of annotations would be sick!
The truth is that despite ConfigMe being far from perfect for me, especially in Kotlin, I tried so many different config libs that did worse that I think contributing here makes most sense to me. I'm still on the lookout though.
That means a lot. I'm happy to hear more about things you're unhappy about (or what you appreciate, of course). In general, I know ConfigMe is used in a few projects right now and I find it very interesting to hear how they use it, as it also helps me keep use cases in mind I wouldn't necessarily have personally.
I wish releases were done more frequent - there's some features from months ago that weren't released yet.
Sorry, I promised to release a new version ~2 weeks ago and then forgot. This is back on my radar and bumped to the top of my todo list.
The PR is much appreciated. I'll have a look at it and comment there. I want to properly understand your comments, your PR and some Kotlin details to provide it the attention it deserves.
@Prof-Bloodstone Just to mention that I've now released 1.2.0. I was hoping to do it earlier in the week but now is better than never I guess. :)