Lately I was trying to read a config file with Kotlin. The config file is written in YAML and I’m using SnakeYAML for reading the file.
Consider the following file:
# Size of the universe
universeSize:
  maxGalaxies: 1
  maxSystems: 3
  maxPlanets: 3
# Starter planet settings
starterPlanet:
  # Starting resources
  resources:
    crystal: 200
    gas: 100
    energy: 800
# Round time in seconds
roundTime: 5
How to model the class hierarchy so that my Kotlin program can read the files? First I tried Kotlin data classes:
data class UniverseSizeDto(var maxGalaxies: Int, var maxSystems: Int, var maxPlanets: Int)
data class ResourcesDto(var crystal: Int, var gas: Int, var energy: Int)
data class StarterPlanetDto(var resources: ResourcesDto)
data class ConfigDto(var universeSize: UniverseSizeDto, var starterPlanet: StarterPlanetDto, var roundTime: Int)
Reading the config file is relatively easy:
fun loadFromFile(path: Path): ConfigDto {
    val yaml = Yaml(Constructor(ConfigDto::class.java))
    return Files.newBufferedReader(path).use {
        yaml.load(it) as ConfigDto
    }
}
Unfortunately this results in the following exception:
Exception in thread "main" Can't construct a java object for tag:yaml.org,2002:restwars.business.config.Config$ConfigDto; exception=java.lang.NoSuchMethodException:     restwars.business.config.Config$ConfigDto.<init>()
in 'reader', line 2, column 1:
universeSize:
^
at org.yaml.snakeyaml.constructor.Constructor$ConstructYamlObject.construct(Constructor.java:349)
at org.yaml.snakeyaml.constructor.BaseConstructor.constructObject(BaseConstructor.java:182)
at org.yaml.snakeyaml.constructor.BaseConstructor.constructDocument(BaseConstructor.java:141)
at org.yaml.snakeyaml.constructor.BaseConstructor.getSingleData(BaseConstructor.java:127)
at org.yaml.snakeyaml.Yaml.loadFromReader(Yaml.java:450)
at org.yaml.snakeyaml.Yaml.load(Yaml.java:393)
Damn. Kotlin data classes have no parameterless constructor by default. They get a parameterless constructor if all parameters have a default value. Naah, I don’t want this. Okay, so no data classes then. Next try:
class UniverseSizeDto {
    var maxGalaxies: Int
    var maxSystems: Int
    var maxPlanets: Int
}
class ResourcesDto {
    var crystal: Int
    var gas: Int
    var energy: Int
}
class StarterPlanetDto {
    var resources: ResourcesDto
}
class ConfigDto {
    var universeSize: UniverseSizeDto
    var starterPlanet: StarterPlanetDto
    var roundTime: Int
}
Argh, doesn’t compile:
Error:(15, 9) Kotlin: Property must be initialized or be abstract
Error:(16, 9) Kotlin: Property must be initialized or be abstract
Error:(17, 9) Kotlin: Property must be initialized or be abstract
Error:(25, 9) Kotlin: Property must be initialized or be abstract
Error:(26, 9) Kotlin: Property must be initialized or be abstract
Error:(27, 9) Kotlin: Property must be initialized or be abstract
Error:(35, 9) Kotlin: Property must be initialized or be abstract
Error:(43, 9) Kotlin: Property must be initialized or be abstract
Error:(44, 9) Kotlin: Property must be initialized or be abstract
Error:(45, 9) Kotlin: Property must be initialized or be abstract
Okay, Kotlin forces us to initialize the variable either in the initializer or in the constructor. After snooping around in the documentation, i found late-initialized properties:
Normally, properties declared as having a non-null type must be initialized in the constructor. However, fairly often this is not convenient. For example, properties can be initialized through dependency injection, or in the setup method of a unit test. In this case, you cannot supply a non-null initializer in the constructor, but you still want to avoid null checks when referencing the property inside the body of a class.
Okay, so some other guy at JetBrains had a similar problem. Let’s give it a try:
class UniverseSizeDto {
    lateinit var maxGalaxies: Int
    lateinit var maxSystems: Int
    lateinit var maxPlanets: Int
}
class ResourcesDto {
    lateinit var crystal: Int
    lateinit var gas: Int
    lateinit var energy: Int
}
class StarterPlanetDto {
    lateinit var resources: ResourcesDto
}
class ConfigDto {
    lateinit var universeSize: UniverseSizeDto
    lateinit var starterPlanet: StarterPlanetDto
    lateinit var roundTime: Int
}
Argh again, doesn’t compile:
Error:(15, 9) Kotlin: 'lateinit' modifier is not allowed on primitive type properties
Error:(16, 9) Kotlin: 'lateinit' modifier is not allowed on primitive type properties
Error:(17, 9) Kotlin: 'lateinit' modifier is not allowed on primitive type properties
Error:(25, 9) Kotlin: 'lateinit' modifier is not allowed on primitive type properties
Error:(26, 9) Kotlin: 'lateinit' modifier is not allowed on primitive type properties
Error:(27, 9) Kotlin: 'lateinit' modifier is not allowed on primitive type properties
Error:(45, 9) Kotlin: 'lateinit' modifier is not allowed on primitive type properties
Now the compiler is whining about the lateinit bevor the Int fields. Dammit. You can write it this way:
class UniverseSizeDto {
    lateinit var maxGalaxies: Integer
    lateinit var maxSystems: Integer
    lateinit var maxPlanets: Integer
}
class ResourcesDto {
    lateinit var crystal: Integer
    lateinit var gas: Integer
    lateinit var energy: Integer
}
class StarterPlanetDto {
    lateinit var resources: ResourcesDto
}
class ConfigDto {
    lateinit var universeSize: UniverseSizeDto
    lateinit var starterPlanet: StarterPlanetDto
    lateinit var roundTime: Integer
}
Now IntelliJ is shouting at me because I should use Int instead of Integer. At least it compiles and throws no exceptions. But i don’t like warnings either.
Looks like I’m stuck with default values, or I could write my own SnakeYAML constructor. Sigh, default values then.
Now the code looks like this:
data class UniverseSizeDto(var maxGalaxies: Int = 0, var maxSystems: Int = 0, var maxPlanets: Int = 0)
data class ResourcesDto(var crystal: Int = 0, var gas: Int = 0, var energy: Int = 0)
data class StarterPlanetDto(var resources: ResourcesDto = ResourcesDto())
data class ConfigDto(var universeSize: UniverseSizeDto = UniverseSizeDto(), var starterPlanet: StarterPlanetDto = StarterPlanetDto(), var roundTime: Int = 0)
If someone knows a solution to my dilemma, please feel free to contact me.
Update 2016-02-27
I found a pretty solution using Jackson.