Access build.gradle.kt values - kotlin

The question was: how to access from your Kotlin code, a arbitrary value that exists in build.gradle.kt?
If you have the following entry in my build.gradle.kt file (trying to add a feature flag):
android {
...
buildTypes {
getByName("debug") {
buildConfigField("Boolean", "FOO", "true")
}
getByName("release") {
buildConfigField("Boolean", "FOO", "false")
}
}
...
}
A way to access the value of FOO within your Kotlin code is:
class FeatureFlag {
fun isFeatureEnabled(): Boolean = BuildConfig.FOO
}

Use buildConfigField instead of resValue. Same arguments. Then in Kotlin code:
class FeatureFlag {
fun isFeatureEnabled(): Boolean =
BuildConfig.FOO.toBoolean()
}
Although I'm not sure why you don't just use a "boolean" type to begin with instead of a String that you have to convert to Boolean at runtime.

Related

Jackson ContextualDeserializer cannot get the contextualType when decoding a generic Kotlin class

I have a custom implementation of a JsonDeserializer which implements a ContextualDeserializer in order to deserialize a generic class. Everything works fine until the generic class itself has a property which is also a generic class.
For demo purposes, I simplified the code to the minimum
My class is
data class MyObject<out Content>(
val content: Content
)
Deserializing MyObject<String> works:
{
"content": "Hello"
}
but deserializing MyObject<MyObject<String>>> does not:
{
"content": {
"content": "Hello"
}
}
The custom deserializer is the following. The issue seems to be that contextualType.containedType is returning null.
I believe this issue is in the line
val content = ctxt.readTreeAsValue(contentNode, contentType!!.rawClass)
because the .rawClass does not have any additional information about the generic type. So the content is deserialized as just MyObject instead of MyObject<String>.
class MyObjectDeserializer : JsonDeserializer<MyObject<*>?>(), ContextualDeserializer {
private var contentType: JavaType? = null
override fun createContextual(ctxt: DeserializationContext?, property: BeanProperty?): JsonDeserializer<*> {
contentType = if (property == null)
ctxt!!.contextualType.containedType(0)
else
property.type.containedType(0)
if (contentType != null) {
println("${ctxt!!.contextualType} has contained type $contentType")
} else {
// Here is where the issue occurs
println("${ctxt!!.contextualType} does not have any contained types")
}
return this
}
override fun deserialize(p: JsonParser?, ctxt: DeserializationContext): MyObject<*> {
val codec = p?.codec ?: throw NullPointerException()
val node = codec.readTree<JsonNode>(p)
val contentNode = node.get("content")
val content = ctxt.readTreeAsValue(contentNode, contentType!!.rawClass)
return MyObject(content)
}
}
and I register the deserialzers like so
fun ObjectMapper.registerMyDeserializers(): ObjectMapper {
val module = SimpleModule().also {
it.addDeserializer(MyObject::class.java, MyObjectDeserializer())
}
return this.registerModule(module)
}
The unit test the demonstrates the issue is the follow. The first test works fine, the second fails.
class ResponseParsingTests {
private val objectMapper = jacksonObjectMapper().registerMyDeserializers()
#Test
fun `parses String as content`() {
val json = """
{
"content": "Hello"
}
""".trimIndent()
val result = objectMapper.readValue<MyObject<String>>(json)
assertEquals("Hello", result.content)
}
#Test
fun `parses MyObject as content`() {
val json = """
{
"content": {
"content": "Hello"
}
}
""".trimIndent()
val result = objectMapper.readValue<MyObject<MyObject<String>>>(json)
assertEquals("Hello", result.content.content)
}
}
Please note that I'm aware the this example class does not require any custom deserialzer at all. However my real use case is a bit more complex and I need to use a custom deserializer because I'm publishing my code as part of a library which suppoorts multiple serialization frameworks (gson, jackson, kotlinx). So the serialization cannot be part of my actual class but rather in a separate one.

Should I get rid of big switch case?

I have a factory which includes many HTML attribute generators which returns one of them based on the type of attribute, so I wanted to see if there is a better way of doing this.
class AttributeHtmlGeneratorFactory {
fun create(property: String): AttributeHtmlGenerator {
when (property) {
"animation" -> {
return AnimationHtmlGenerator()
}
...
"left", "top" -> {
return PositionHtmlGenerator()
}
...
"scaleX" , "scaleY", ... , "direction" -> {
return UnusedAttributesHtmlGenerator()
}
this when switch has like 20 switch cases in it.
this is the interface which all these classes are using
interface AttributeHtmlGenerator {
fun generateHtml(member: KProperty1<HtmlComponentDataModel, *>, component: HtmlComponentDataModel ): String
}
and this is where and how I'm using all of these:
var result = ""
HtmlComponentDataModel::class.memberProperties.forEach { member ->
val generator = AttributeHtmlGeneratorFactory().create(member.name)
result = result.plus(generator.generateHtml(member, component))
}
return result
also, this is a simple implementation of the interface:
class ButtonFillHtmlGenerator : AttributeHtmlGenerator {
override fun generateHtml(member: KProperty1<HtmlComponentDataModel, *>, component: HtmlComponentDataModel): String {
var result = ""
member.get(component)?.let {
result = result.plus("background-color:${it};")
}
return result
}
}
is there anyway to make this better?
If you just want to reformat the when statement, I suggest you you do like this:
fun create(property: String): AttributeHtmlGenerator = when (property)
{
"animation" -> AnimationHtmlGenerator()
"left", "top" -> PositionHtmlGenerator()
"scaleX", "scaleY", "direction" -> UnusedAttributesHtmlGenerator()
else -> error("No generator found for property $property")
}
If you want to split this logic across modules, you would use a Map.
class AttributeHtmlGeneratorFactory {
private val generatorMap = mutableMapOf<String, () -> AttributeHtmlGenerator>()
init {
assignGeneratorToProperties("animation") { AnimationHtmlGenerator() }
assignGeneratorToProperties("left", "top") { PositionHtmlGenerator() }
}
fun create(property: String): AttributeHtmlGenerator {
return generatorMap[property]?.invoke() ?: error("No generator found for property $property")
}
fun assignGeneratorToProperties(vararg properties: String, provider: () -> AttributeHtmlGenerator) {
properties.forEach {
generatorMap[it] = provider
}
}
}
This way you can call assignGeneratorToProperties in parts of the code and thus split the initialization logic.
Performance-wise, when/if-else statements are really performant when you have a few cases but a HashMap outperforms them for a lot of elements. You decide what to use depending on your case.

How to validate json in kotlin

There is a kotlin class with the following structure.
data class Person(
#field:Length(max = 5)
val name: String,
val phones: List<Phone>
)
data class Phone(
#field:Length(max = 10)
val number: String
)
When converting the json string through objectMapper, I want to receive all the violation values.
ex) JSON object is not valid. Reasons (3) name length must be 5, number length must be 10, ...
#Test
fun test() {
val json = """
{
"name": "name",
"phones": [
{ "number": "1234567890123456" },
{ "number": "1234567890123456" }
]
}
""".trimIndent()
try {
objectMapper.readValue(json, Person::class.java)
} catch (ex: ConstraintViolationException) {
val violations = ex.constraintViolations
println(violations.size) // expected size = 3
}
}
However, the above code fails to catch the exception and causes the exception below.
com.fasterxml.jackson.databind.JsonMappingException: JSON object is not valid. Reasons (1): {"bean":"Phone","property":"number","value":"1234567890123456","message": "..."}, (through reference chain: Person["phones"]->java.util.ArrayList[0])
Looking at the reason, those wrapped in a list do not throw ConstructionViolationException, but throw JsonMappingException.
below dependencies
plugins {
id("org.springframework.boot") version "2.4.3"
id("io.spring.dependency-management") version "1.0.11.RELEASE"
kotlin("jvm") version "1.4.30"
kotlin("plugin.spring") version "1.4.30"
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.springframework.boot:spring-boot-starter-validation")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
class BeanValidationDeserializer(base: BeanDeserializerBase?) : BeanDeserializer(base) {
private val logger = LoggerFactory.getLogger(this::class.java)
private val validator = Validation.buildDefaultValidatorFactory().validator
override fun deserialize(parser: JsonParser?, ctxt: DeserializationContext?): Any {
val instance = super.deserialize(parser, ctxt)
validate(instance)
return instance
}
private fun validate(instance: Any) {
val violations = validator.validate(instance)
if (violations.isNotEmpty()) {
val message = StringBuilder()
message.append("JSON object is not valid. Reasons (").append(violations.size).append("): ")
for (violation in violations) {
message.append("{\"bean\":\"${violation.rootBeanClass.name}\",")
.append("\"property\":\"${violation.propertyPath}\",")
.append("\"value\":\"${violation.invalidValue}\",")
.append("\"message\": \"${violation.message}\"}")
.append(", ")
}
logger.warn(message.toString())
throw ConstraintViolationException(message.toString(), violations)
}
}
}
#Bean
fun objectMapper(): ObjectMapper = Jackson2ObjectMapperBuilder.json()
.featuresToDisable(MapperFeature.DEFAULT_VIEW_INCLUSION)
.featuresToDisable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES)
.featuresToDisable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)
.modules(ParameterNamesModule(), JavaTimeModule(), Jdk8Module(), KotlinModule(), customValidationModule())
.build()
#Bean
fun customValidationModule(): SimpleModule {
val validationModule = SimpleModule()
validationModule.setDeserializerModifier(object : BeanDeserializerModifier() {
override fun modifyDeserializer(
config: DeserializationConfig?,
beanDesc: BeanDescription?,
deserializer: JsonDeserializer<*>?
): JsonDeserializer<*>? {
return if (deserializer is BeanDeserializer) {
BeanValidationDeserializer(deserializer as BeanDeserializer?)
} else deserializer
}
})
return validationModule
}
I'm not sure how to do it. I ask for your help.
I would say an easier and more maintainable way would be to define a JSON Schema.
After that is in place, you can use one of the two json validation libraries mentioned here (https://json-schema.org/implementations.html#validator-kotlin) to validate your json.
The answer by #rbs is good but requires the overhead of creating json schema per json you want to validate.
Seems like object mapper can be configured not to wrap the exceptions it throws with JsonMappingException. - https://github.com/FasterXML/jackson-databind/issues/2033
All you need to do is to disable WRAP_EXCEPTIONS feature for the objectmapper
Note you cant choose a specific exception type, it will not wrap ALL the exceptions.

How to use Internationalization in tornadofx

I am trying to use internationalization in a Kotlin application using the tornadofx framework.
I have created a properties file and depending on the selected language the correct file is loaded. But when I want to change the language in the running application the UI does not update accordingly.
For internationalization you should use a companion object to get the related translation anywhere in your application.
First of all your translation class should know which is the actual selected language/locale. For this I use an enum with the possible locales for the application:
fun setLocale(locale: SupportedLocale) {
if (SupportedLocale.supportedLocals.contains(locale)) {
Locale.setDefault(locale.local)
actualLocal = locale.local
//Good practice would be to store it in a properties file to have the information after restart
} else {
//Throw a warning or sth with your preferred logger
}
}
Then we need a method which gets the particular string value from your resource bundle like:
operator fun get(#PropertyKey(resourceBundle = BUNDLE_NAME) key: String, vararg args: Any): String {
val bundle = ResourceBundle.getBundle(BUNDLE_NAME, actualLocal)
return MessageFormat.format(bundle.getString(key), *args)
}
In JavaFx applications (also TornadoFX) you should use StringBindings (https://docs.oracle.com/javase/8/javafx/api/javafx/beans/binding/StringBinding.html) for example to bind a label text property to your translated string. For that we will implement a special method:
fun createStringBinding(#PropertyKey(resourceBundle = BUNDLE_NAME) key: String, vararg args: Any): StringBinding {
return Bindings.createStringBinding(Callable { get(key, *args) }, Settings.languageProperty())
}
Now you can use your object like this:
textProperty().bind(MyLang.createStringBinding("MyApp.MyTranslation"))
Here an runnable example:
MyLang.kt
enum class SupportedLocale(val local:Locale) {
ENGLISH(Locale.ENGLISH),
GERMAN(Locale.GERMAN);
companion object {
val supportedLocals: List<SupportedLocale>
get() = SupportedLocale.values().toList()
}
}
class MyLang {
companion object {
private const val BUNDLE_NAME = "Language" //prefix of your resource bundle
private var actualLocal = Locale.getDefault()
fun setLocale(locale: SupportedLocale) {
if (SupportedLocale.supportedLocals.contains(locale)) {
Locale.setDefault(locale.local)
actualLocal = locale.local
//Good practice would be to store it in a properties file to have the information after restart
} else {
//Throw a warning or sth with your preferred logger
}
}
operator fun get(#PropertyKey(resourceBundle = BUNDLE_NAME) key: String, vararg args: Any): String {
val bundle = ResourceBundle.getBundle(BUNDLE_NAME, actualLocal)
return MessageFormat.format(bundle.getString(key), *args)
}
fun createStringBinding(#PropertyKey(resourceBundle = BUNDLE_NAME) key: String, vararg args: Any): StringBinding {
return Bindings.createStringBinding(Callable { get(key, *args) }, Settings.languageProperty())
}
}
}
fun main() {
println("My translation: " + MyLang.createStringBinding("MyApp.MyTranslation").get())
//The get() here is only to get the string for assign a property its not needed like in the example
}
If you need any explanations or its unclear. Just ask! Its just written down maybe I forgot something to explain.

RxJava Filter on Error

This question is loosely related to this question, but there were no answers. The answer from Bob Dalgleish is close, but doesn't support the potential error coming from a Single (which I think that OP actually wanted as well).
I'm basically looking for a way to "filter on error" - but don't think this exists when the lookup is RX based. I am trying to take a list of values, run them through a lookup, and skip any result that returns a lookup failure (throwable). I'm having trouble figuring out how to accomplish this in a reactive fashion.
I've tried various forms of error handling operators combined with mapping. Filter only works for raw values - or at least I couldn't figure out how to use it to support what I'd like to do.
In my use case, I iterate a list of IDs, requesting data for each from a remote service. If the service returns 404, then the item doesn't exist anymore. I should remove non-existing items from the local database and continue processing IDs. The stream should return the list of looked up values.
Here is a loose example. How do I write getStream() so that canFilterOnError passes?
import io.reactivex.Single
import io.reactivex.schedulers.Schedulers
import org.junit.Test
class SkipExceptionTest {
private val data: Map<Int, String> = mapOf(
Pair(1, "one"),
Pair(2, "two"),
Pair(4, "four"),
Pair(5, "five")
)
#Test
fun canFilterOnError() {
getStream(listOf(1, 2, 3, 4, 5))
.subscribeOn(Schedulers.trampoline())
.observeOn(Schedulers.trampoline())
.test()
.assertComplete()
.assertNoErrors()
.assertValueCount(1)
.assertValue {
it == listOf(
"one", "two", "four", "five"
)
}
}
fun getStream(list: List<Int>): Single<List<String>> {
// for each item in the list
// get it's value via getValue()
// if a call to getValue() results in a NotFoundException, skip that value and continue
// mutate the results using mutate()
TODO("not implemented")
}
fun getValue(id: Int): Single<String> {
return Single.fromCallable {
val value: String? = data[id]
if (value != null) {
data[id]
} else {
throw NotFoundException("dat with id $id does not exist")
}
}
}
class NotFoundException(message: String) : Exception(message)
}
First .materialize(), then .filter() on non-error events, then .dematerialize():
getStream(/* ... */)
.materialize()
.filter(notification -> { return !notification.isOnError(); })
.dematerialize()
I ended up mapping getValue() to Optional<String>, then calling onErrorResumeNext() on that and either returning Single.error() or Single.just(Optional.empty()). From there, the main stream could filter out the empty Optional.
private fun getStream(list: List<Int>): Single<List<String>> {
return Observable.fromIterable(list)
.flatMapSingle {
getValue(it)
.map {
Optional.of(it)
}
.onErrorResumeNext {
when (it) {
is NotFoundException -> Single.just(Optional.empty())
else -> Single.error(it)
}
}
}
.filter { it.isPresent }
.map { it.get() }
.toList()
}