I'm trying to build a string with properties that are initialized in a subclass.
I read about lazy initialization but somehow this doesn't work as I expected.
abstract class SubProcessFullNameBuilder(technicalDomain: TechnicalDomainEnumeration) {
protected val moduleName = "td.${technicalDomain.value().toLowerCase()}.shared"
private val packageName by lazy { packageName() }
private val processName by lazy { processName() }
val processFullName: String = "$moduleName/$packageName.$processName"
protected abstract fun packageName(): String
protected abstract fun processName(): String
}
class WorkerFullNameBuilder(
private val jmsDirection: JmsDirectionEnumeration,
technicalDomain: TechnicalDomainEnumeration,
private val cdmCode: String) : SubProcessFullNameBuilder(technicalDomain) {
override fun packageName() = "$moduleName.workers.${jmsDirection.value().toLowerCase()}.${cdmCode.toLowerCase()}"
override fun processName() = "Worker"
}
Since I have overridden the packageName() and processName() properties, I would expect that on calling the packageName property it would use the implementation from the subclass.
But when I call the processFullName property, it throws a java.lang.NullPointerException.
val builder = WorkerFullNameBuilder(JmsDirectionEnumeration.ESB_IN, TechnicalDomainEnumeration.INFOR, "ccmd")
val name = builder.processFullName
How can I initialize the packageName and processName properties in a proper way?
This is a case of calling a non-final method in a constructor and thus accessing uninitialized variables.
This line is still evaluated eagerly, at the time when the base class is constructed:
val processFullName: String = "$moduleName/$packageName.$processName"
To get the values of the two lazy properties, this will make calls to the abstract methods, of which packageName() refers to jmsDirection and cdmCode to return its value - these properties are not initialized yet, because their values are set after the superclass constructor runs. Here's a simplified version of the subclass' constructor, decompiled back to Java:
public WorkerFullNameBuilder(#NotNull JmsDirectionEnumeration jmsDirection, #NotNull TechnicalDomainEnumeration technicalDomain, #NotNull String cdmCode) {
super(technicalDomain);
this.jmsDirection = jmsDirection;
this.cdmCode = cdmCode;
}
As a demonstration, if you don't refer to these, for example, if you return constants in both of the subclass methods, your code will actually run fine:
override fun packageName() = "foo"
override fun processName() = "Worker"
However, the solution you need here is most likely to make the processFullName property itself lazy instead of the two values it uses (which you're evaluating at constructor time right now anyway, so you're not making use of them being lazy). This means you don't even need those two as separate properties:
abstract class SubProcessFullNameBuilder(technicalDomain: TechnicalDomainEnumeration) {
protected val moduleName = "td.${technicalDomain.value().toLowerCase()}.shared"
val processFullName by lazy { "$moduleName/${packageName()}.${processName()}" }
protected abstract fun packageName(): String
protected abstract fun processName(): String
}
Related
I want to dynamically implement an interface by extending an existing class as an anonymous object.
This anonymous object captures a method parameter to implement the interface method:
fun myFunc(someObj: SomeObj, update: Boolean) = object : SomeObj(/*copy some values from someObj*/), SomeInterface {
override fun doUpdate() = update
}
This implementation captures the update method parameter and adds it as a synthetic $update field into the anonymous object. I need to annotate this field as my serialization framework includes the $update field when its not marked as #Transient.
Another approach by delegation suffers from the same issue:
fun myFunc(someObj: SomeObj, update: Boolean) {
val someInterfaceImpl = object : SomeInterface {
override fun doUpdate() = update
}
return object : SomeObj(/*copy some values from someObj*/), SomeInterface by someInterfaceImpl
}
I cannnot annotate someInterfaceImpl in any place with #delegate:Transient or #Transient.
In essence:
Is there a way to annotate captured variables in Kotlin?
Is there a way to annotate the field when delegating to an object?
I am required to do this by annotations as the framework does not offer any other way to exclude fields, not even by names.
Furthermore I am not talking about delegated properties but delegated interfaces.
Create a named class:
fun myFunc(someObj: SomeObj, update: Boolean): SomeObj {
class SomeObjSubclass(someObj: SomeObj, #Transient val update: Boolean):
SomeObj(someObj.prop1, someObj.prop2, /* and so on*/), SomeInterface {
override fun doUpdate() = update
}
return SomeObjSubclass(someObj, update)
}
Notice that myFunc is now merely a wrapper for SomeObj. Depending on your design, you could just make myFunc the SomeObj subclass instead:
class MyFunc(someObj: SomeObj, #Transient val update: Boolean):
SomeObj(someObj.prop1, someObj.prop2, /* and so on*/), SomeInterface {
override fun doUpdate() = update
}
Callers would call MyFunc(...) as if it were a function, and they would receive something assignable to SomeObj, just like before.
You can also add a secondary constructor to SomeObj that takes a SomeObj, and copy the properties there
constructor(someObj: SomeObj): this(
someObj.prop1, someObj.prop2, /* and so on */
)
Then the declaration of MyFunc can just be:
class MyFunc(someObj: SomeObj, #Transient val update: Boolean):
SomeObj(someObj), SomeInterface {
override fun doUpdate() = update
}
I want to make Jackson work with enums not by name and not by ordinal, but with a custom property I added called "stringId".
I wanted to support this with all Enums in the system so I made an interface called StringIdEnum which the FooEnum will implement.
I'm using Kotlin so I created a property in the interface called stringId which I override in each enum value.
Now I want to make Jackson serialize and deserialize using this stringId field, from what I seen I have several options:
Use #JsonProperty annotation on each enum value and make sure it is aligned with the stringId property.
I see two issues with this approach. one it's a lot of annotation to add (we have many enum classes across the system). two I need to make sure the annotation value and the property value should be always the same which can cause issues in the future.
I tried to use the READ_ENUMS_USING_TO_STRING feature, but because I'm using an interface I can't override the toString in the interface class (I can override it in every enum class but that again seems like a lot of redundant code)
Implement a custom serializer/deserializer.
The serializer is pretty straightforward, however, I had trouble with the deserializer.
I wanted to register the deserializer on the StringIdEnum interface, but I had an issue getting all the runtime enum values for the actual FooType enum.
StringIdEnum:
interface StringIdEnum {
val stringId: String
}
enum class FooType(override val stringId: String) : StringIdEnum {
FOO("FOO"),
GOO("GOO");
}
Managed to get it working:
#JsonSerialize(using = StringIdEnumSerializer::class)
#JsonDeserialize(using = StringIdEnumDeserializer::class)
interface StringIdEnum: DbEnum {
val stringId: String
}
class StringIdEnumSerializer: StdSerializer<StringIdEnum>(StringIdEnum::class.java) {
override fun serialize(value: StringIdEnum, gen: JsonGenerator, provider: SerializerProvider) {
gen.writeString(value.stringId)
}
}
class StringIdEnumDeserializer : JsonDeserializer<Enum<*>>(), ContextualDeserializer {
private lateinit var type: JavaType
override fun deserialize(p: JsonParser, ctxt: DeserializationContext): Enum<*> {
val t = p.text
val enumConstants = (type.rawClass as Class<Enum<*>>).enumConstants
return enumConstants.single { (it as StringIdEnum).stringId == t }
}
override fun createContextual(ctxt: DeserializationContext?, property: BeanProperty?): JsonDeserializer<*> {
val wrapperType: JavaType = property!!.type
val stringIdEnumDeserializer = StringIdEnumDeserializer()
stringIdEnumDeserializer.type = wrapperType
return stringIdEnumDeserializer
}
}
I have a base class:
abstract class JSONDeserializationStrategy<T : Any>: DeserializationStrategy<T> {
protected abstract fun parse(json: JsonObject): T
protected abstract fun getSerializationException(): SerializationException
}
and then a derived class
class MyClassParserDeserializationStrategy : JSONDeserializationStrategy<MyClass>() {
override val descriptor: SerialDescriptor
= buildClassSerialDescriptor("MyClass")
override fun getSerializationException(): SerializationException
= throw SerializationException("Invalid JSON received for MyClass.")
How could I move the property descriptor and the method getSerializationException from the derived class into the base class, since they only "adapt" by providing their name as String? I was trying to do something in the direction of T::class.java.simpleName as String but it didnt work. What is the best way to do this?
As #Tenfour04 explained, T is erased, so it is not directly accessible. However, as long as the subclass of JSONDeserializationStrategy provides the T as a specific class/type, it can be acquired with a bit of reflection voodoo:
fun main() {
val strategy = MyClassParserDeserializationStrategy()
println(strategy.descriptor.serialName) // MyClass
}
abstract class JSONDeserializationStrategy<T : Any>: DeserializationStrategy<T> {
protected val type: KType = this::class.supertypes
.first { it.classifier == JSONDeserializationStrategy::class }
.arguments[0].type!!
#Suppress("UNCHECKED_CAST")
protected val typeClass = requireNotNull(type.classifier as? KClass<T>) {
"T is unknown"
}
override val descriptor: SerialDescriptor = buildClassSerialDescriptor(typeClass.simpleName!!)
fun getSerializationException(): SerializationException =
throw SerializationException("Invalid JSON received for ${typeClass.simpleName!!}.")
...
}
I'm not 100% sure, but I believe both type!! and simpleName!! are safe, they can't be null.
Also, it won't work if T is really fully erased and unknown, e.g.:
val strategy = GenericParserDeserializationStrategy<MyClass>() // exception
For this reason it makes sense to open type/typeClass properties for overriding, so generic non-abstract subclasses could provide their own means to acquire T. However, then we would probably need to move the initialization of most of properties outside of the constructor.
Because of type erasure, T's class is not accessible. Work-around could be to add it as a constructor property that returns the type, and then the subclasses must pass the type. The property needs to be in the constructor, rather than provided as an abstract property for subclasses to override because you need it to initialize descriptor at instantiation time. (It's highly discouraged to call an open property at class initialization time.)
abstract class JSONDeserializationStrategy<T : Any>(protected val typeClass: KClass<out T>): DeserializationStrategy<T> {
protected abstract fun parse(json: JsonObject): T
override val descriptor: SerialDescriptor = buildClassSerialDescriptor(typeClass.simpleName!!)
fun getSerializationException(): SerializationException =
throw SerializationException("Invalid JSON received for ${typeClass.simpleName}.")
}
class TodaySaleParserDeserializationStrategy : JSONDeserializationStrategy<TodaySale>(TodaySale::class) {
}
Consider the following code:
sealed class DataType<T : Any> {
abstract fun inputToType(input: String): T
abstract fun typeToSql(value: T): String
companion object {
val all = listOf(StringDt, LongDt)
}
}
object StringDt : DataType<String>() {
override fun inputToType(input: String) = input
override fun typeToSql(value: String) = "\"${value}\""
}
object LongDt : DataType<Long>() {
override fun inputToType(input: String) = input.toLong()
override fun typeToSql(value: Long) = value.toString()
}
val dataTypeList = listOfNotNull(StringDt, LongDt)
println(dataTypeList)
println(DataType.all)
Things to consider:
object as per documentation (and my understanding as well) is singleton and always instantiated
the two objects (StringDt and LongDt) are quite similar
The result of println(DataType.all) shows that one of the objects are not initialized. How is that possible? I would expect all the list elements to be initialized.
IntelliJ version: CE 2020.2
Kotlin plugin version: 1.4.0-release-IJ2020.2-1
Here's a running example which shows that the static list has a null element, while the non-static one contains both objects initialized.
It happens due to cyclical static initializations. It's pretty hard to explain this problem in two words but you can read about it here.
To fix this behavior you can change all initialization like this:
val all by lazy { listOf(StringDt, LongDt) }
With given kotlin code :
sealed class Event(val id:String= UUID.randomUUID().toString(), val timestamp:Instant = Instant.now())
data class BarEvent(val additionalInfo:String):Event()
object FooEvent:Event()
// data class CorrectFooEvent():Event() // invalid kotlin
fun main(args: Array<String>) {
val b1 = BarEvent("b1")
val f1 = FooEvent
Thread.sleep(1000)
val b2 = BarEvent("b2")
val f2 = FooEvent
println("${b1.id} ${b1.timestamp} $b1")
println("${f1.id} ${f1.timestamp} $f1")
println("${b2.id} ${b2.timestamp} $b2")
println("${f2.id} ${f2.timestamp} $f2")
}
There is no issue with BarEvent.
But as FooEvent has no more parameter than the ones in Event, I would like it to have empty constructor. It's not authorized for data class, so I made it an object. But object is singleton, so it doesn't behave as an instanciated event.
The only workaround that I see (keeping the class as a data class) is something like :
sealed class Event(open val id:String= UUID.randomUUID().toString(), open val timestamp:Instant = Instant.now())
data class FooEvent(override val id:String= UUID.randomUUID().toString(), override val timestamp:Instant = Instant.now()):Event()
But it's not very elegant.
Just change FooEvent to a normal class, and add (or generate them using your IDE) toString(), hashCode() and equals(Object) if needed:
class FooEvent: Event() {
override hashCode() = ...
override equals(other: Object) {
...
}
override toString() = ...
}
To make the event a data class, simply add an unused property to it. Not pretty, but as short as it can be in Kotlin at the moment:
data class FooEvent(val dummy: Unit = Unit) : Event()
There seems to be no intention to remove this limitation soon:
Data class without arguments deprecated in 1.0. Why?
Suggestion for parameterless data class