bhylu.com

Advertisements

Kotlin Bicycle Hire Project

Combine validation and state changes in a small hire desk with an explicit refusal policy.

How it works

The desk stores a bounded capacity and one current hired count. A positive request succeeds only if it fits, and a rejected request must preserve the earlier state. This project is in-memory and single-threaded; the Mutex lesson handles a separate concurrent boundary. Returning Boolean keeps this small acceptance result simple.

Run this Kotlin example

These are local Kotlin/JVM programs. Install a JDK and the compiler using the official Kotlin compiler guide, or use an IDE with Kotlin support. Check java -version and kotlinc -version in a new terminal. The reviewed compiler is Kotlin 2.4.21 with a JVM 21 target, executed on Temurin JDK 25.0.5. Use JDK 21 or newer for this target. Android, Kotlin/JS and Kotlin/Native require separate platform projects and are not the tested environment here.

Save the complete source as hire-project.kt in its own learning folder. The package line gives this example a namespace, not a network address. Bhylu has no Kotlin browser runner; do not use the JavaScript or Python editor. Apache, PHP and MySQL are not required.

Compile with kotlinc hire-project.kt -jvm-target 21 -include-runtime -d lesson.jar, then run java -jar lesson.jar. Use a learning folder you own and keep generated JARs outside the website. These sources require no third-party application libraries.

Download the complete hire-project.kt · Download all source files with hash checks

For the optional collection, save download-examples.mjs in a new learning folder and run node download-examples.mjs. A local preview uses node download-examples.mjs http://127.0.0.1:8000/examples/kotlin/. It refuses an existing bhylu-kotlin-examples folder and installs or executes no Kotlin code. Compile one ordinary lesson at a time by filename.

Example

hire-project.kt

package bhylu.lessons.k_hire_project

class HireCounter(val capacity: Int) {
    init { require(capacity >= 0) }
    var hired = 0
        private set
    val free: Int get() = capacity - hired
    fun hire(amount: Int): Boolean {
        if (amount <= 0 || amount > free) return false
        hired += amount
        return true
    }
}

fun main() {
    val desk = HireCounter(5)
    for (amount in listOf(2, 0, 4, 3)) {
        println("${desk.hire(amount)} ${desk.hired}")
    }
    println("free=${desk.free}")
}

Expected result

true 2
false 2
false 2
true 5
free=0

The sequence contains both an invalid zero and a too-large request between valid operations. Neither refusal changes hired. The separate multi-file lab compiles this domain rule with tests, including a maximum-Int fixture.

A real multi-file lab

The source collection includes a checked hire counter, station report and independent tests. Inside the downloaded bhylu-kotlin-examples folder, run node setup-lab.mjs. It verifies the included source hashes and creates a new bike-lab folder; existing output is refused. Change to that folder. Compile the app with kotlinc src/Domain.kt src/App.kt -jvm-target 21 -include-runtime -d app.jar, then java -jar app.jar. Compile tests separately with kotlinc src/Domain.kt src/Tests.kt -jvm-target 21 -include-runtime -d tests.jar, then java -jar tests.jar. This lab uses only Kotlin/JDK libraries and does not need coroutines or a test-framework download.

lab-domain.kt

Download lab-domain.kt

lab-domain.kt

package bhylu.bikelab

class HireCounter(val capacity: Int) {
    init { require(capacity >= 0) }
    var hired = 0
        private set
    val free: Int get() = capacity - hired
    fun hire(amount: Int): Boolean {
        if (amount <= 0 || amount > free) return false
        hired += amount
        return true
    }
}

data class HireRow(val id: String, val station: String, val minutes: Int)

fun stationTotals(rows: List<HireRow>): Map<String, Long> {
    require(rows.size <= 1000)
    val seen = mutableSetOf<String>()
    val totals = mutableMapOf<String, Long>()
    for (row in rows) {
        require(row.id.isNotBlank() && row.station.isNotBlank() && row.minutes in 1..240)
        require(seen.add(row.id)) { "Duplicate hire id" }
        totals[row.station] = (totals[row.station] ?: 0L) + row.minutes.toLong()
    }
    return totals.toSortedMap()
}

lab-app.kt

Download lab-app.kt

lab-app.kt

package bhylu.bikelab

fun main() {
    val desk = HireCounter(5)
    for (amount in listOf(2, 0, 4, 3)) println("${desk.hire(amount)} ${desk.hired}")
    println("free=${desk.free}")
    println(stationTotals(listOf(HireRow("H-21", "Canal", 20), HireRow("H-22", "Hill", 35), HireRow("H-23", "Canal", 25))))
}

lab-tests.kt

Download lab-tests.kt

lab-tests.kt

package bhylu.bikelab

private fun mustReject(block: () -> Unit) {
    try { block() } catch (expected: IllegalArgumentException) { return }
    error("Invalid input accepted")
}

fun main() {
    mustReject { HireCounter(-1) }
    for (capacity in 0..12) {
        val desk = HireCounter(capacity)
        var expected = 0
        for (amount in listOf(1, 2, 0, -1, 4, 1, 9, 1)) {
            val accepted = amount > 0 && amount.toLong() + expected.toLong() <= capacity.toLong()
            if (accepted) expected += amount
            check(desk.hire(amount) == accepted)
            check(desk.hired == expected && desk.free == capacity - expected)
        }
    }
    val large = HireCounter(Int.MAX_VALUE)
    check(large.hire(Int.MAX_VALUE - 1))
    check(!large.hire(2) && large.hired == Int.MAX_VALUE - 1)
    check(large.hire(1) && large.free == 0)
    for (size in 0..20) {
        val rows = List(size) { i -> HireRow("H-$i", if (i % 2 == 0) "Canal" else "Hill", i + 1) }
        val expected = mutableMapOf<String, Long>()
        for (row in rows) expected[row.station] = (expected[row.station] ?: 0L) + row.minutes
        check(stationTotals(rows) == expected)
    }
    for (rows in listOf(
        listOf(HireRow("", "Canal", 1)),
        listOf(HireRow("H", " ", 1)),
        listOf(HireRow("H", "Canal", 0)),
        listOf(HireRow("H", "Canal", 241)),
        listOf(HireRow("H", "Canal", 2), HireRow("H", "Hill", 3)),
        List(1001) { HireRow("H-$it", "Canal", 1) }
    )) mustReject { stationTotals(rows) }
    println("Hire boundaries, independent sequence model and report fixtures passed")
}

For exact language and library contracts, consult the official Kotlin documentation. These fictional fixtures are learning programs, not a deployed hire service.

Advertisements