https://kotlinlang.org logo
#getting-started
Title
# getting-started
d

David Kubecka

11/21/2023, 1:23 PM
I have the following use case
Copy code
abstract class MessageProcessor(val supportedTypes: X, val validator: (X) -> Unit) {
  fun process(key: String, value: String) {
    val messageType = supportedValues.getIfSupported(value)?.let { ... }
    if (messageType != null) {
       validator(messageType, value)
    }
  }
}

fun interface X {
    fun getIfSupported(value: String): X?
}
Basically I want to init the abstract class with supported values which are then queried to get the value if it's supported or null. The problem is that the supportedTypes typically is an enum. So I thought that I create the
X
interface and implement that interface by each concrete enum class. This worked nicely on paper until I realized that I am unable to actually call MessageProcessor constructor because of course I can't instantiate the
X
if it's an enum. So it seems that my original design is flawed but hopefully, the general idea is not. My question is how to best model this in Kotlin.
k

Klitos Kyriacou

11/21/2023, 2:03 PM
Are you sure you want both the variable
supportedValues
and the value returned by
supportedValues.getIfSupported
to be the same type? The name
supportedValues
sounds like some sort of collection or container, so would be a different type than what
getIfSupported
returns. In the case of enums, consider, for example,
Colour.valueOf("RED")
. The
Colour
is a class, which can be considered a collection of its instances. If
Colour
implements
X
, you wouldn't pass
Colour
to
SomeClass
- you can only pass instances, but you don't want to pass a specific instance. You might want to pass an
EnumSet
- that would be a different type than
X
.
d

Daniel Pitts

11/21/2023, 3:27 PM
I don't quit follow what you're trying to do. Can you describe why you want to do this? What is your end-goal? Assuming this could be made to work, how would you want to use it?
r

Rodrigo Munera

11/21/2023, 4:10 PM
Is this kinda what you're trying to do?
Copy code
enum class EClass {
    CLASS1, CLASS2
}

abstract class AClass(open val someClass: EClass) {
    open fun process(value: String): EClass? {
        return null
    }
}

class ClassImpl(override val someClass: EClass = EClass.CLASS1): AClass(someClass) {
    override fun process(value: String): EClass {
        return try {
            EClass.valueOf(value)
        } catch (e: IllegalArgumentException) {
            EClass.CLASS2
        }
    }
}
I wrote this unit test and it passed based on the behavior ^
Copy code
import org.junit.jupiter.api.Test

import org.junit.jupiter.api.Assertions.*

class AClassTest {

    @Test
    fun process() {
        val classImpl = ClassImpl(EClass.CLASS1)
        val enum: EClass = classImpl.process("CLASS1")
        assert(enum == EClass.CLASS1)
        val enum2: EClass = classImpl.process("CLASS3")
        assert(enum2 == EClass.CLASS2)
    }

}
d

David Kubecka

11/21/2023, 4:10 PM
I've enhanced the example a little. Basically my class is an abstract message processor concrete instance of which are supposed to provide
supportedTypes
and some function, let's say a
validator
, which operates on the supported type. That's also the reason why
getIfSupported
returns
X
- this is a translation from a string to a concrete enum instance.
r

Rodrigo Munera

11/21/2023, 4:15 PM
Here's a simplified version where process is not overridable (e.g. not open)
Copy code
enum class EClass {
    CLASS1, CLASS2
}

abstract class AClass(open val someClass: EClass) {
    fun process(value: String): EClass? {
        return try {
            EClass.valueOf(value)
        } catch (e: IllegalArgumentException) {
            null
        }    
    }
}
I think I'm getting lost in your example where you mentioned an enum, probably an enum is not what you want to do, but instead have the messages implement your interface and use
typeof
checking to see if they're supported
Ok, after looking at your example in a playground locally, it seems ok. this is what I ended up with on my side of things.
Copy code
abstract class MessageProcessor(val supportedTypes: X, val validator: (X, String) -> Unit) {
    fun process(key: String, value: String) {
        val messageType: X? = supportedTypes.getIfSupported(value)?.let {
            // do stuff
            null
        }
        if (messageType != null) {
            validator(messageType, value)
        }
    }
}

interface X {
    fun getIfSupported(value: String): X?
}

enum class Message1: X {
    TYPE1, TYPE2;

    override fun getIfSupported(value: String): X? {
        return if (value == this.toString()) {
            this
        } else {
            null
        }
    }
}
d

David Kubecka

11/21/2023, 7:18 PM
@Rodrigo Munera As others correctly pointed out the
X
param represents a collection of enum values, not a single value. So unfortunately your solution doesn't work.
@Klitos Kyriacou Thanks for the
EnumSet
hint. That looks promising. But of course in my abstract class I run into the usual problems with type erasure. I.e. now the parameter becomes
Copy code
supportedTypes: EnumSet<T>
where
T : Enum<T>
and the whole
getIfSupported
ideally becomes just
Copy code
supportedTypes.contains(...)
I would have to somehow convert the input value string into the enum value which seems impossible with the generic enum type.
I could probably just convert the enum values to strings, though:
Copy code
supportedTypes.map { it.name }.contains(value)
Bad?
To be more precise and stick with the original author's intentions 🙂
Copy code
fun getIfSupported(value: String) =
  supportedTypes.singleOrNull { it.name == value }