Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
bc46045
Bump version -> `2.0.0-SNAPSHOT.265`
alexander-yevsyukov Feb 24, 2025
f9dded9
Bump local dependencies
alexander-yevsyukov Feb 24, 2025
98d0658
Update dependency reports
alexander-yevsyukov Feb 24, 2025
0b5f903
Add documentation
alexander-yevsyukov Feb 24, 2025
0be93a3
Detect duplicated route functions
alexander-yevsyukov Feb 24, 2025
3c23de0
Improve code layout
alexander-yevsyukov Feb 24, 2025
d490600
Extract `lineNumber` property
alexander-yevsyukov Feb 24, 2025
4592990
Simplify code flow
alexander-yevsyukov Feb 24, 2025
9cbbf02
Turn the function into extension
alexander-yevsyukov Feb 25, 2025
6cec723
Improve documentation
alexander-yevsyukov Feb 25, 2025
0110691
Test generated command routing
alexander-yevsyukov Feb 25, 2025
a3204dc
Add codegen for state update routing setup
alexander-yevsyukov Feb 25, 2025
0246160
Introduce an abstract base for multicast setup generators
alexander-yevsyukov Feb 25, 2025
6e314ad
Fix the dependency on `AutoService.annotations`
alexander-yevsyukov Feb 25, 2025
cf7ee03
Update build time
alexander-yevsyukov Feb 25, 2025
bac82ed
Improve documentation
alexander-yevsyukov Feb 25, 2025
b0a2dab
Bump local dependencies
alexander-yevsyukov Feb 26, 2025
0052047
Update dependency report
alexander-yevsyukov Feb 26, 2025
adeeaf6
Bump Base -> `2.0.0-SNAPSHOT.244`
alexander-yevsyukov Feb 26, 2025
cbd1cc1
Improve code layout
alexander-yevsyukov Feb 26, 2025
cec3e81
Improve doc language
alexander-yevsyukov Feb 26, 2025
8d04894
Improve function name
alexander-yevsyukov Feb 26, 2025
5035eda
Improve doc language
alexander-yevsyukov Feb 26, 2025
89d0d11
Fix package name
alexander-yevsyukov Feb 26, 2025
f6bb9a6
Update config
alexander-yevsyukov Feb 26, 2025
c0c03e8
Suppress warnings
alexander-yevsyukov Feb 26, 2025
84455e5
Improve doc wording
alexander-yevsyukov Feb 26, 2025
a04627a
Update dependency reports
alexander-yevsyukov Feb 26, 2025
336f351
Improve comment language
alexander-yevsyukov Feb 26, 2025
1b57492
Remove redundant (c) statement
alexander-yevsyukov Feb 26, 2025
3e73626
Remove redundant word in docs
alexander-yevsyukov Feb 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion buildSrc/src/main/kotlin/io/spine/dependency/lib/Kotlin.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024, TeamDev. All rights reserved.
* Copyright 2025, TeamDev. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down
4 changes: 2 additions & 2 deletions buildSrc/src/main/kotlin/io/spine/dependency/local/Base.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ package io.spine.dependency.local
*/
@Suppress("ConstPropertyName")
object Base {
const val version = "2.0.0-SNAPSHOT.242"
const val versionForBuildScript = "2.0.0-SNAPSHOT.242"
const val version = "2.0.0-SNAPSHOT.244"
const val versionForBuildScript = "2.0.0-SNAPSHOT.244"
const val group = Spine.group
const val artifact = "spine-base"
const val lib = "$group:$artifact:$version"
Expand Down
4 changes: 2 additions & 2 deletions buildSrc/src/main/kotlin/io/spine/dependency/local/McJava.kt
Original file line number Diff line number Diff line change
Expand Up @@ -42,12 +42,12 @@ object McJava {
/**
* The version used to in the build classpath.
*/
const val dogfoodingVersion = "2.0.0-SNAPSHOT.263"
const val dogfoodingVersion = "2.0.0-SNAPSHOT.264"

/**
* The version to be used for integration tests.
*/
const val version = "2.0.0-SNAPSHOT.264"
const val version = "2.0.0-SNAPSHOT.265"

/**
* The ID of the Gradle plugin.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ object ProtoData {
* The version of ProtoData dependencies.
*/
val version: String
private const val fallbackVersion = "0.92.7"
private const val fallbackVersion = "0.92.9"

/**
* The distinct version of ProtoData used by other build tools.
Expand All @@ -82,7 +82,7 @@ object ProtoData {
* transitional dependencies, this is the version used to build the project itself.
*/
val dogfoodingVersion: String
private const val fallbackDfVersion = "0.92.6"
private const val fallbackDfVersion = "0.92.9"

/**
* The artifact for the ProtoData Gradle plugin.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ object Validation {
/**
* The version of the Validation library artifacts.
*/
const val version = "2.0.0-SNAPSHOT.193"
const val version = "2.0.0-SNAPSHOT.195"

const val group = "io.spine.validation"
private const val prefix = "spine-validation"
Expand Down
23 changes: 12 additions & 11 deletions buildSrc/src/main/kotlin/io/spine/gradle/report/pom/PomGenerator.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2024, TeamDev. All rights reserved.
* Copyright 2025, TeamDev. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -75,20 +75,21 @@ object PomGenerator {
plugin(BasePlugin::class.java)
}

val task = project.tasks.create("generatePom")
task.doLast {
val pomFile = project.projectDir.resolve("pom.xml")
project.delete(pomFile)
val task = project.tasks.register("generatePom") {
doLast {
val pomFile = project.projectDir.resolve("pom.xml")
project.delete(pomFile)

val projectData = project.metadata()
val writer = PomXmlWriter(projectData)
writer.writeTo(pomFile)
val projectData = project.metadata()
val writer = PomXmlWriter(projectData)
writer.writeTo(pomFile)
}

val assembleTask = project.tasks.findByName("assemble")!!
dependsOn(assembleTask)
}

val buildTask = project.tasks.findByName("build")!!
buildTask.finalizedBy(task)

val assembleTask = project.tasks.findByName("assemble")!!
task.dependsOn(assembleTask)
}
}
2 changes: 1 addition & 1 deletion config
168 changes: 130 additions & 38 deletions dependencies.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright 2025, TeamDev. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Redistribution and use in source and/or binary forms, with or without
* modification, must retain the above copyright notice and the following
* disclaimer.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package io.spine.tools.mc.java.routing.tests

import io.spine.given.home.Device
import io.spine.given.home.DeviceAggregate
import io.spine.given.home.DeviceId
import io.kotest.matchers.shouldBe
import io.spine.given.home.State
import io.spine.given.home.commands.addDevice
import io.spine.given.home.commands.setState
import io.spine.given.home.homeAutomation
import io.spine.testing.server.blackbox.BlackBox
import io.spine.testing.server.blackbox.assertEntity
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test

@DisplayName("Generated `CommandRoutingSetup` should")
internal class CommandRoutingSetupITest {

@Test
fun `apply generated routes`() {
BlackBox.from(homeAutomation()).use { context ->
val l1 = DeviceId.generate()

context.receivesCommand(
addDevice {
device = l1
name = "First Lamp"
}
)

// The first command is handled using the standard routing
// via the first command field.
var lamp = context.readState(l1)
lamp.state shouldBe State.OFF

context.receivesCommand(
setState {
device = l1
state = State.ON
}
)

// The command was handled via custom routing declared in the class.
lamp = context.readState(l1)
lamp.state shouldBe State.ON
}
}
}

/**
* Reads the state of the [DeviceAggregate] with the given ID.
*/
private fun BlackBox.readState(id: DeviceId) =
assertEntity<DeviceAggregate, DeviceId>(id).actual()?.state() as Device
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,18 @@ import io.spine.given.home.homeAutomation
import io.spine.testing.server.blackbox.BlackBox
import org.junit.jupiter.api.Test
import io.spine.testing.server.blackbox.assertEntity
import org.junit.jupiter.api.DisplayName

@DisplayName("Generated `EventRoutingSetup` should")
internal class EventRoutingSetupITest {

/**
* This test verifies that routing functions declared in the [RoomProjection] class
* effectively work because the state of the projection is expected after
* the dispatched events.
*/
@Test
fun `loads event routing setup as a service`() {
fun `apply generated routes`() {
BlackBox.from(homeAutomation()).use { context ->
val r1 = RoomId.generate()
val lamp = DeviceId.generate()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright 2025, TeamDev. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Redistribution and use in source and/or binary forms, with or without
* modification, must retain the above copyright notice and the following
* disclaimer.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package io.spine.tools.mc.java.routing.tests

import io.kotest.matchers.shouldBe
import io.spine.given.home.Home
import io.spine.given.home.HomeProjection
import io.spine.given.home.HomeProjection.Companion.SINGLETON_ID
import io.spine.given.home.RoomId
import io.spine.given.home.events.roomAdded
import io.spine.given.home.homeAutomation
import io.spine.testing.server.blackbox.BlackBox
import io.spine.testing.server.blackbox.assertEntity
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test

@DisplayName("Generated `StateRoutingSetup` should")
internal class StateRoutingSetupITest {

@Test
fun `apply generated routes`() {
BlackBox.from(homeAutomation()).use { context ->
val r1 = RoomId.generate()
val r2 = RoomId.generate()

context.receivesEvent(
roomAdded {
room = r1
name = "Living Room"
}
)
context.receivesEvent(
roomAdded {
room = r2
name = "Bedroom"
}
)

val home = context.assertEntity<HomeProjection, String>(SINGLETON_ID)
.actual()?.state() as Home

// This means that two `Room` instances were routed by the routing
// function directing all the updates to the singleton.
home.roomList.map { it.name } shouldBe listOf("Living Room", "Bedroom")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,27 +26,35 @@

package io.spine.given.home

import com.google.common.annotations.VisibleForTesting
import io.spine.core.Subscribe
import io.spine.given.home.commands.AddDevice
import io.spine.given.home.commands.SetState
import io.spine.given.home.events.DeviceAdded
import io.spine.given.home.events.DeviceMoved
import io.spine.given.home.events.RoomAdded
import io.spine.given.home.events.RoomEvent
import io.spine.given.home.events.RoomRenamed
import io.spine.given.home.events.StateChanged
import io.spine.given.home.events.deviceAdded
import io.spine.given.home.events.stateChanged
import io.spine.protobuf.isDefault
import io.spine.server.BoundedContext
import io.spine.server.aggregate.Aggregate
import io.spine.server.aggregate.Apply
import io.spine.server.command.Assign
import io.spine.server.entity.alter
import io.spine.server.projection.Projection
import io.spine.server.projection.ProjectionRepository
import io.spine.server.route.EventRouting
import io.spine.server.route.setup.EventRoutingSetup
import io.spine.server.route.Route
import io.spine.server.route.setup.StateRoutingSetup
import io.spine.server.route.StateUpdateRouting

fun homeAutomation(): BoundedContext = BoundedContext.singleTenant("HomeAutomation")
.add(RoomProjectionRepository())
.add(RoomProjection::class.java)
.add(DeviceAggregate::class.java)
.add(HomeProjection::class.java)
.build()

public class RoomProjection : Projection<RoomId, Room, Room.Builder>() {
@VisibleForTesting
class RoomProjection : Projection<RoomId, Room, Room.Builder>() {

@Subscribe
internal fun on(e: RoomAdded) = alter {
Expand All @@ -73,33 +81,67 @@ public class RoomProjection : Projection<RoomId, Room, Room.Builder>() {

companion object {

/**
* The routing function accepting the interface.
*/
@Route
@JvmStatic
fun route(e: RoomEvent): RoomId = e.room

/**
* The routing function accepting the event class.
*/
@Route
@JvmStatic
fun routeMoved(e: DeviceMoved): Set<RoomId> =
if (e.prevRoom.isDefault()) setOf(e.room) else setOf(e.prevRoom, e.room)
}
}

internal class RoomProjectionRepository : ProjectionRepository<RoomId, RoomProjection, Room>() {
@VisibleForTesting
class DeviceAggregate : Aggregate<DeviceId, Device, Device.Builder>() {

override fun setupEventRouting(routing: EventRouting<RoomId>) {
super.setupEventRouting(routing)
@Assign
internal fun handle(c: AddDevice): DeviceAdded =
deviceAdded { device = c.device; name = c.name }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why to have them on the same line? The methods below don't do that.

Copy link
Contributor Author

@alexander-yevsyukov alexander-yevsyukov Feb 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because these functions generate events, while appliers update the state.

Also, I'm experimenting with the compact style for code which merely transforms a command into an event without doing much work.


// Remove routs added via reflective class analysis.
routing.run {
remove<RoomEvent>()
remove<DeviceMoved>()
}
@Assign
internal fun handle(c: SetState): StateChanged =
stateChanged { device = id(); current = c.state }

@Apply
private fun event(e: DeviceAdded) = alter {
name = e.name
state = State.OFF
}

@Apply
private fun event(e: StateChanged) = alter {
state = e.current
}

companion object {

@Route
fun command(c: SetState): DeviceId = c.device
}
}

EventRoutingSetup.apply(entityClass(), routing)
@VisibleForTesting
class HomeProjection : Projection<String, Home, Home.Builder>() {

@Subscribe
internal fun on(updated: Room) = alter {
val builder = roomBuilderList.find { b -> b.id == updated.id }
if (builder != null) {
builder.clear().mergeFrom(updated)
} else {
addRoom(updated)
}
}

override fun setupStateRouting(routing: StateUpdateRouting<RoomId>) {
super.setupStateRouting(routing)
StateRoutingSetup.apply(entityClass(), routing)
companion object {
const val SINGLETON_ID = "OurHome"

@Route
internal fun room(@Suppress("UNUSED_PARAMETER") r: Room): String = SINGLETON_ID
}
}
Loading
Loading