Why is Kotlin's generateSequence returning one too many items in the example below? - kotlin

I'm calculating the projection of instants in time based on a cron expression and returning them as a Sequence. Here's the class:
// (package omitted)
import org.springframework.scheduling.support.CronExpression
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.ZonedDateTime
import java.time.temporal.ChronoUnit
class Recurrence(val cronExpression: String) {
private val cron = CronExpression.parse(cronExpression)
fun instants(
fromInclusive: LocalDate = LocalDate.now(),
toExclusive: LocalDate = fromInclusive.plusMonths(1)
): Sequence<LocalDateTime> = instants(fromInclusive.atStartOfDay(), toExclusive.atStartOfDay())
fun instants(
fromInclusive: LocalDateTime = LocalDateTime.now(),
toExclusive: LocalDateTime = fromInclusive.plusMonths(1)
): Sequence<LocalDateTime> {
return generateSequence(cron.next(fromInclusive.minusNanos(1))) {
if (it.isBefore(toExclusive)) {
cron.next(it)
} else {
null
}
}
}
}
The following test fails because the first assertion is false: the returned list has one extra, unexpected element at the end.
// (package omitted)
import java.time.LocalDate
import java.time.Month
import kotlin.test.Test
import kotlin.test.assertEquals
class RecurrenceTest {
#Test
fun testInstants() {
val r = Recurrence("#daily")
val from = LocalDate.of(2021, Month.JANUARY, 1)
val forDays = 31
val instants = r.instants(from, from.plusDays(forDays.toLong())).toList()
assertEquals(forDays, instants.size)
(1..forDays).forEach {
assertEquals(from.plusDays(it.toLong() - 1).atStartOfDay(), instants[it - 1])
}
}
}
If I reimplement by building an ArrayList instead, it works as expected:
// new collection-based methods in Recurrence
fun instantsList(
fromInclusive: LocalDate = LocalDate.now(),
toExclusive: LocalDate = fromInclusive.plusMonths(1)
): List<LocalDateTime> = instantsList(fromInclusive.atStartOfDay(), toExclusive.atStartOfDay())
fun instantsList(
fromInclusive: LocalDateTime = LocalDateTime.now(),
toExclusive: LocalDateTime = fromInclusive.plusMonths(1)
): List<LocalDateTime> {
val list = arrayListOf<LocalDateTime>()
var it = cron.next(fromInclusive.minusNanos(1))
while (it !== null) {
if (it.isBefore(toExclusive)) {
list.add(it)
it = cron.next(it)
} else {
break
}
}
return list
}
The one line to change in the test is to use the new method:
val instants = r.instantsList(from, from.plusDays(forDays.toLong()))
Why is the sequence-based implementation returning me one more element than the list-based one?

If I read your code correctly, in list implementation you check if it.isBefore(toExclusive) and only then you add it to the list. In sequence implementation you do the same check it.isBefore(toExclusive) and then you add next item to the sequence.
Similar with the first item. In list implementation you check if cron.next(fromInclusive.minusNanos(1)) meets the requirement. In sequence implementation you always add it.

Thanks, #broot -- you spotted the issue. Just took another set of eyeballs. Correct sequence implementation is
fun instants(
fromInclusive: LocalDateTime = LocalDateTime.now(),
toExclusive: LocalDateTime = fromInclusive.plusMonths(1)
): Sequence<LocalDateTime> {
val seed = cron.next(fromInclusive.minusNanos(1))
return generateSequence(seed) {
val next = cron.next(it)
if (next.isBefore(toExclusive)) {
next
} else {
null
}
}
}

Related

Add property path to custom validation in Micronaut

Given the below, working validator factory, is it possible somehow to manipulate propertyPath? When debugging, I can see propertyPath inside io.micronaut.validation.validator.DefaultValidator$DefaultConstraintValidatorContext.
Motivation for doing so, is that some fields are conditionally required, and I need to put validation annotation on class-level, but then propertyPath is set to class, not to the actual fields with validation errors. When returning validation errors to API-clients, it would be nice to have correct property name for validation errors.
import io.micronaut.context.annotation.Factory
import io.micronaut.validation.validator.constraints.ConstraintValidator
import jakarta.inject.Singleton
import no.mycompany.myapp.web.config.UiConfig
import no.mycompany.myapp.web.viewmodel.MyExampleFormVm
#Factory
class ExampleValidatorFactory(private val uiConfig: UiConfig) {
#Singleton
fun validForm(): ConstraintValidator<ValidForm, MyExampleFormVm> = ConstraintValidator { value, _, context ->
val formTypeFromConfig = uiConfig.formTypes.first { it.id == value.formType }
if (formTypeFromConfig.labelSubCompanyId != null && value.subCompanyId == null) {
context.messageTemplate("Form requires one or more sub-company-ids")
// TODO: set propertyPath to "subCompanyId"
return#ConstraintValidator false
}
if (formTypeFromConfig.labelCompanyId != null && value.companyId == null) {
context.messageTemplate("Form requires company-id")
// TODO: set propertyPath to "companyId"
return#ConstraintValidator false
}
true
}
}
Annotation
import javax.validation.Constraint
#Retention(AnnotationRetention.RUNTIME)
#Constraint(validatedBy = [])
annotation class ValidForm(
val message: String = "Invalid form ({validatedValue})"
)
Example bean to validate:
import io.micronaut.core.annotation.Introspected
import javax.validation.constraints.NotBlank
import javax.validation.constraints.Pattern
#Introspected
#ValidForm
data class MyExampleFormVm(
#field:NotBlank
val formType: String = "",
#field:Pattern(regexp = "[8|9]\\d{8}")
val companyId: String? = null,
#field:Pattern(regexp = "[8|9]\\d{8}")
val subCompanyId: String? = null
)
This is my custom ConstraintExceptionHandler. I want it.propertyPath.lastOrNull()?.name to return e.g. "subCompanyId".
import io.micronaut.context.annotation.Replaces
import io.micronaut.context.annotation.Requires
import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
import io.micronaut.http.annotation.Produces
import io.micronaut.http.server.exceptions.response.ErrorResponseProcessor
import io.micronaut.validation.exceptions.ConstraintExceptionHandler
import jakarta.inject.Singleton
import java.time.Instant
import java.util.*
import javax.validation.ConstraintViolationException
#Produces
#Singleton
#Replaces(ConstraintExceptionHandler::class)
#Requires(classes = [ConstraintViolationException::class, ConstraintExceptionHandler::class])
class CustomConstraintExceptionHandler(responseProcessor: ErrorResponseProcessor<*>) :
ConstraintExceptionHandler(responseProcessor) {
override fun handle(request: HttpRequest<*>, exception: ConstraintViolationException): HttpResponse<*> =
HttpResponse.badRequest(
ApiError(
timestamp = Instant.now().toEpochMilli(),
httpStatusCode = HttpStatus.BAD_REQUEST.code,
errorType = ApiErrorType.VALIDATION_ERROR,
url = request.path,
validationErrors = exception.constraintViolations
.associate { (it.propertyPath.lastOrNull()?.name ?: FALLBACK_PROPERTY_PATH) to it.message }
))
companion object {
const val FALLBACK_PROPERTY_PATH = "ROOT"
}
}
---
import com.fasterxml.jackson.annotation.JsonInclude
import io.micronaut.core.annotation.Introspected
#Introspected
#JsonInclude(value = JsonInclude.Include.NON_NULL)
data class ApiError(
val timestamp: Long,
val httpStatusCode: Int,
val errorType: ApiErrorType,
val url: String,
val validationErrors: Map<String, String>? = null
)
enum class ApiErrorType {
VALIDATION_ERROR,
SYSTEM_ERROR;
}
Question from #Bonatti
If it.propertyPath.lastOrNull() is null, on the Handler, then that is
weird.
If you do like this, resultMap will contain an entry with key="ROOT".
val exampleForm = MyExampleFormVm(
formType = "SomeValue",
companyId = "912345678",
subCompanyId = null
)
val constraintViolations: Set<ConstraintViolation<MyExampleFormVm>> = validator.validate(exampleForm)
val resultMap = constraintViolations.associate { (it.propertyPath.lastOrNull()?.name ?: "ROOT") to it.message }
Environment: Micronaut 3.7.4

Kotlin validator for List<Pair<A, B>> doesn't work

I have a data class which I need to validate:
import javax.validation.Valid
import whatever.pckg.validation.PkiSignWithBusinessCode
import whatever.pckg.validation.NullOrNotBlank
data class UploadFileReq(
val id: String? = null,
...(other fields)...
#get:Valid
val signaturesInfo: MutableList<Pair<SignatureInfo, Object>> = mutableListOf() # Object here is for simplicity
) {
#PkiSignWithBusinessCode
data class SignatureInfo(
val typeSign: String = "",
#get:NullOrNotBlank
val businessCode: String? = null,
)
}
#NullOrNotBlank annotation is just a simple merge of standard #NotBlank and #Null annotations.
I also have another custom validation annotation #PkiSignWithBusinessCode, its definition is below:
import whatever.pckg.UploadFileReq
import javax.validation.*
import kotlin.annotation.AnnotationRetention.RUNTIME
import kotlin.reflect.KClass
#Constraint(validatedBy = [PkiSignWithBusinessCodeValidator::class])
#Target(AnnotationTarget.CLASS)
#Retention(RUNTIME)
annotation class PkiSignWithBusinessCode(
val message: String = "PKI signature requires filled businessCode",
val groups: Array<KClass<*>> = [],
val payload: Array<KClass<out Payload>> = []
)
class PkiSignWithBusinessCodeValidator: ConstraintValidator<PkiSignWithBusinessCode, UploadFileReq.SignatureInfo>> {
override fun isValid(obj: UploadFileReq.SignatureInfo?, context: ConstraintValidatorContext): Boolean {
if (obj != null) {
if ((obj.typeSign == "PKI") && (obj.businessCode == null)) {
return false
}
}
return true
}
Logic of above annotation is quite simple - when typeSign equals PKI and businessCode is null, then validator should treat that as invalid object.
For your reference here's a simple unit-test that tries to check the work of #PkiSignWithBusinessCode:
import org.junit.jupiter.api.Test
import whatever.pckg.UploadFileReq
import javax.validation.Validation
import kotlin.test.assertEquals
class PkiSignWithBusinessCodeTest {
#Test
fun `validate PkiSignWithBusinessCodeTest`() {
val validator = Validation.buildDefaultValidatorFactory().validator
val signatureInfo = UploadFileReq.SignatureInfo(
typeSign = "PKI",
businessCode = null
)
val uploadFileReq = UploadFileReq(
null,
signaturesInfo = mutableListOf(signatureInfo to Object)
)
val result = validator.validate(uploadFileReq)
assertEquals(1, result.size)
assertEquals("PKI signature requires filled businessCode", result.first().messageTemplate)
}
}
But this test obviously fails on first assertion state: java.lang.AssertionError: Expected <1>, actual <0>. So no constraint violations found by validator.
The problem is that Spring ignores validation rule of above annotation. As an assumption I suppose that somehow Pair class wrap prevents Spring from using my validation annotation. Maybe it's a bug?
Or maybe I overlooked something in my code?
Found a workaround on this - need to make own ValidatingPair with #Valid annotations on first and second members of this new Pair:
import javax.validation.Valid
data class ValidatingPair<out A, out B>(
#get:Valid
public val first: A,
#get:Valid
public val second: B
) : java.io.Serializable {
override fun toString(): String = "($first, $second)"
}
And make:
val signaturesInfo: MutableList<Pair<SignatureInfo, Object>>
to become
val signaturesInfo: MutableList<ValidatingPair<SignatureInfo, Object>>
Then validation starts working for list members.

How can I check the constructur arguments of a mockk Mock?

I have the following code (in Kotlin):
class X {
fun foo() {
val A(1, true, "three")
val b = B()
b.bar(A)
}
}
What I want to to is find out what A has been instantiated with.
My test code looks like so:
// Needed for something else
every { anyConstructed<A>().go() } returns "testString"
// What I'm using to extract A
val barSlot = slot<A>()
verify { anyConstructed<B>().bar(capture(barSlot)) }
val a = barSlot.captured
How can I check what values A has been instantiated with now I've managed to capture the mock that was created when it was constructed (thanks to the every statement)?
Thanks!
You can do it in two ways:
Using slot to capture the parameter:
#Test
fun shouldCheckValuesAtConstruct() {
val a = A(1, true, "s")
val b = mockk<B>()
val aSlot = slot<A>()
every { b.bar(a = capture(aSlot)) } returns Unit
b.bar(a)
val captured = aSlot.captured
assertEquals(1, captured.a)
assertEquals(true, captured.b)
assertEquals("s", captured.s)
}
Or using withArg function and inline assertions
#Test
fun shouldCheckValuesAtConstructInlineAssertion() {
val a = A(1, true, "s")
val b = mockk<B>()
every { b.bar(a) } returns Unit
b.bar(a)
verify {
b.bar(withArg {
assertEquals(1, it.a)
assertEquals(true, it.b)
assertEquals("s", it.s)
})
}
}

Writing an addition visitor function in Kotlin

I'm trying to write a visitor function in Kotlin that adds two integers together. I've been working off of some sample code and I can't figure out what these .value or .visit functions are. It doesn't seem to be declared in the sample code, so I'm unsure how to declare it in my code. Whenever I compile the code, I get an error saying that value is an unresolved reference.
Relevant Kotlin code:
package backend
import org.antlr.v4.runtime.*
import grammar.*
abstract class Data
class IntData(val value: Int): Data() {
override fun toString(): String
= "Int($value)"
}
class Context(): HashMap<String, Data>() {
constructor(parent: Context): this() {
this.putAll(parent)
}
}
abstract class Expr {
abstract fun eval(scope: Context): Data
fun run(program: Expr) {
try {
val data = program.eval(Context())
println("=> ${data}")
} catch(e: Exception) {
println("[err] ${e}")
}
}
}
class IntLiteral(val value: Int): Expr() {
override fun eval(scope:Context): Data
= IntData(value)
}
enum class Op {
Add,
Sub,
Mul,
Div
}
class Arithmetic(
val op: Op,
val left: Expr,
val right: Expr): Expr() {
override fun eval(scope: Context): Data {
val x = (left.eval(scope) as IntData).value
val y = (right.eval(scope) as IntData).value
return IntData(
when(op) {
Op.Add -> x + y
Op.Mul -> x * y
Op.Sub -> x - y
Op.Div -> x / y
}
)
}
}
}
class Compiler: PLBaseVisitor<Expr>() {
val scope = mutableMapOf<String, Expr>()
override fun visitAddExpr(ctx: PLParser.AddExprContext): Expr {
val xValue = this.visit(ctx.x)
val yValue = this.visit(ctx.y)
val result = xValue.value + yValue.value
return IntLiteral(result)
}
}
Relevant Antlr Grammar:
expr : x=expr '+' y=expr # addExpr
| x=expr '-' y=expr # subExpr
| x=expr '*' y=expr # mulExpr
| x=expr '/' y=expr # divExpr
;
Code I'm trying to execute:
val test = """
x=1+2
print(x)
"""
fun parse(source: String): PLParser.ProgramContext {
val input = CharStreams.fromString(source)
val lexer = PLLexer(input)
val tokens = CommonTokenStream(lexer)
val parser = PLParser(tokens)
}
val testTree = parse(source1)
val testTree = parse(source1)
fun execute(program: Expr?) {
if(program == null) {
println("Program is null.")
return
}
try {
val data = program.eval(Context())
println("> ${data}")
} catch(e: Exception) {
println("[err] ${e}")
}
}
execute(testProgram)
Code from sample:
data class NodeValue(val value: Int)
val visitor = object: CalcBaseVisitor<NodeValue>() {
override fun visitAddition(ctx: CalcParser.AdditionContext): NodeValue {
val xValue = this.visit(ctx.x)
val yValue = this.visit(ctx.y)
return NodeValue(xValue.value + yValue.value)
}
override fun visitValue(ctx: CalcParser.ValueContext): NodeValue {
val lexeme = ctx.Number().getText()
return NodeValue(lexeme.toInt())
}
}
You don’t show the code for your program.eval() method.
The eval function would need to create an instance of your Visitor. (You’ve done that and called it visitor).
You also have the root expr node in you program variable.
Now you would have your visitor “visit” that node and save the return value:
val nodeVal = visitor.visit(program)
At that point nodeVal.value will have the result of visiting that expression.
note: since you’re doing the evaluation in your visitor, there’s not really any use for your Arithmetic class (unless you refactor your visitor to use it instead of just doing the math, but I don’t see much value in that as the visitor is already pretty easy to read).

Setters don't modify two-dimensional array

Kotlin. I want a button to display values from a two-dimensional ArrayList, and a second button to modify one of them. But the setters don't modify the two-dimensional ArrayList. We can see the values with the first button, and after modifying the values at index 2 (third) with the second button, the values don't change:
model.get(2).setDateStrs("03/03/20")
model.get(2).setHourStrs("10:27")
What's wrong?
ReModel.kt file:
package com.example.updatearraylist
class ReModel {
var dateStr:String = "12/31/2029"
var hourStr: String = "00:00"
fun getDateStrs(): String {
return dateStr
}
fun setDateStrs(dateStr: String) {
this.dateStr = dateStr
}
fun getHourStrs(): String {
return hourStr
}
fun setHourStrs(hourStr: String) {
this.hourStr = hourStr
}
}
MainActivity.kt file:
package com.example.updatearraylist
import android.R.attr
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.widget.Button
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import java.lang.reflect.Array.get
import java.util.*
import kotlin.collections.ArrayList
class MainActivity : AppCompatActivity() {
private var displayValueBtn: Button? = null
private var changeValueBtn: Button? = null
val model: ArrayList<ReModel>
get() {
val list = ArrayList<ReModel>()
for (i in 0..7) {
val model = ReModel()
model.setDateStrs("01/16/2020")
model.setHourStrs("01:08")
list.add(model)
}
return list
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
displayValueBtn =findViewById<Button>(R.id.displayValueBtn)
changeValueBtn=findViewById<Button>(R.id.changeValueBtn)
displayValueBtn!!.setOnClickListener {
for(i in 0..7){
Toast.makeText(this, "Value position "+i+" "+model.get(i).getDateStrs()+" "+
model.get(i).getHourStrs()
,Toast.LENGTH_SHORT).show()
}
}
changeValueBtn!!.setOnClickListener {
model.get(2).setDateStrs("03/03/20")
model.get(2).setHourStrs("10:27")
Toast.makeText(this,"List Modified",Toast.LENGTH_LONG).show()
}
}
}
The custom getter on model will be executed each time model is accessed so any changes to the array are overwritten. If you want to verify that use a single println in the custom getter and whatever you print will display multiple times.
By "custom getter" I mean the get() on model and the associated block of code.
One solution is to use lazy initialization instead of a custom getter so that model is initialized only once. Here's how that would look:
val model: ArrayList<ReModel> by lazy {
val list = ArrayList<ReModel>()
for (i in 0..7) {
val model = ReModel()
model.setDateStrs("01/16/2020")
model.setHourStrs("01:08")
list.add(model)
}
list
}
Note that the last line with just list on it returns the value of list. return is not allowed there.