Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 6 additions & 6 deletions api/src/main/kotlin/io/spine/tools/compiler/plugin/Plugin.kt
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ import kotlin.reflect.KClass
*
* The Compiler uses the reactive approach to handling Protobuf source info.
* We handle events which describe a Protobuf source
* set via a set of [views][View] and [policies][Policy].
* set via a set of [views][View] and [policies][Reaction].
*
* Users may want to define bespoke [views][View] and [policies][Policy] based
* Users may want to define bespoke [views][View] and [policies][Reaction] based
* on the Protobuf compiler events.
* To do so, define your handlers and events and expose the components via
* [Plugin.viewRepositories], [Plugin.views], and [Plugin.policies] properties.
* [Plugin.viewRepositories], [Plugin.views], and [Plugin.reactions] properties.
*
* Implementing classes must provide a parameterless constructor so that
* the Compiler can instantiate a plugin via its fully qualified class name.
Expand All @@ -62,13 +62,13 @@ import kotlin.reflect.KClass
* the view may not have a need for repository.
* In such a case, please use [Plugin.views] instead.
*
* @property policies The [policies][Policy] added by this plugin.
* @property reactions The [reactions][Reaction] added by this plugin.
*/
public abstract class Plugin(
public val renderers: List<Renderer<*>> = listOf(),
public val views: Set<Class<out View<*, *, *>>> = setOf(),
public val viewRepositories: Set<ViewRepository<*, *, *>> = setOf(),
public val policies: Set<Policy<*>> = setOf(),
public val reactions: Set<Reaction<*>> = setOf(),
) {
/**
* Extends the given bounded context being built with additional functionality.
Expand Down Expand Up @@ -110,7 +110,7 @@ public fun Plugin.applyTo(context: BoundedContextBuilder, typeSystem: TypeSystem
repos.addAll(defaultRepos)
checkNoViewRepoDuplication(repos)
repos.forEach(context::add)
policies.forEach {
reactions.forEach {
context.addEventDispatcher(it)
it.use(typeSystem)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,28 +28,28 @@ package io.spine.tools.compiler.plugin

import io.spine.base.EntityState
import io.spine.base.EventMessage
import io.spine.tools.compiler.settings.LoadsSettings
import io.spine.tools.compiler.type.TypeSystem
import io.spine.server.event.NoReaction
import io.spine.server.event.Policy
import io.spine.server.event.Reaction
import io.spine.server.event.asB
import io.spine.server.query.QueryingClient
import io.spine.server.tuple.EitherOf2
import io.spine.tools.compiler.settings.LoadsSettings
import io.spine.tools.compiler.type.TypeSystem

/**
* A policy converts one event into zero to many other events.
* A reaction converts one event into zero to many other events.
*
* As a rule of thumb, a policy should read:
* As a rule of thumb, a reaction should read:
* ```markdown
* Whenever <something happens>, then <something else must happen>.
* ```
* For example:
* ```markdown
* Whenever a field option is discovered, a validation rule must be added.
* ```
* To implement the policy, declare a method which reacts to an event with an event:
* To implement the reaction, declare a method which reacts to an event with an event:
* ```kotlin
* class MyPolicy : Policy<FieldOptionDiscovered>() {
* class MyReaction : Reaction<FieldOptionDiscovered>() {
*
* @React
* override fun whenever(@External event: FieldOptionDiscovered): Just<ValidationRuleAdded> {
Expand All @@ -62,7 +62,7 @@ import io.spine.server.tuple.EitherOf2
* [@External][io.spine.core.External]. See the whole list of Protobuf compiler events
* in `spine/compiler/events.proto`.
*
* One policy only accepts one kind of events. Declaring multiple methods with
* One reaction only accepts one kind of events. Declaring multiple methods with
* the [@React][io.spine.server.event.React] annotation causes a runtime error.
*
* The `whenever` method accepts a single event and produces an `Iterable` of events. In case if
Expand All @@ -78,14 +78,8 @@ import io.spine.server.tuple.EitherOf2
*
* Finally, if there are multiple events of the same type, use a typed list,
* e.g. `List<SomethingHappened>`.
*
* ### The note on avoiding intermediate types
* Often when talking about policies, people imply converting an event into a command, not
* an event. We believe such an approach would introduce additional complexity without adding
* any value. Not so many commands will do anything but produce events with the same
* information in the code generation domain. Thus, we directly convert between events.
*/
public abstract class Policy<E : EventMessage> : Policy<E>(), LoadsSettings {
public abstract class Reaction<E : EventMessage> : Reaction<E>(), LoadsSettings {

/**
* The backing field for the [typeSystem] property.
Expand All @@ -95,20 +89,19 @@ public abstract class Policy<E : EventMessage> : Policy<E>(), LoadsSettings {
/**
* The type system for resolving type information for generating events.
*
* A non-null value is available in
* a [rendering pipeline][io.spine.tools.compiler.backend.Pipeline.invoke].
* A non-null value is available in a rendering pipeline.
*/
protected open val typeSystem: TypeSystem by lazy {
check(::_typeSystem.isInitialized) {
"Access to `Policy.typeSystem` property is not allowed until" +
"Access to `Reaction.typeSystem` property is not allowed until" +
" the `Code Generation` context has been injected and" +
" `Policy.use(typeSystem: TypeSystem)` invoked."
" `Reaction.use(typeSystem: TypeSystem)` invoked."
}
_typeSystem
}

/**
* Assigns the type system to the policy.
* Assigns the type system to this reaction.
*/
internal fun use(typeSystem: TypeSystem) {
_typeSystem = typeSystem
Expand Down
16 changes: 9 additions & 7 deletions api/src/main/kotlin/io/spine/tools/compiler/plugin/View.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
package io.spine.tools.compiler.plugin

import io.spine.base.EntityState
import io.spine.base.ProjectionState
import io.spine.server.BoundedContextBuilder
import io.spine.server.DefaultRepository
import io.spine.server.projection.Projection
Expand Down Expand Up @@ -63,7 +64,7 @@ import kotlin.reflect.KClass
* The `(entity)` option ensures that is the case.
*
* The state of a view is constructed based on events, which are produced either by
* the Protobuf Compiler or by a [Policy].
* the Protobuf Compiler or by a [Reaction].
*
* To listen to the events, define single parameter methods annotated with
* [@Subscribe][io.spine.core.Subscribe]. In these methods, change the state of the view via
Expand Down Expand Up @@ -100,7 +101,7 @@ import kotlin.reflect.KClass
* @param M The type of the view's state; must be a Protobuf message implementing [EntityState].
* @param B The type of the view's state builder; must match `<M>`.
*/
public open class View<I : Any, M : EntityState<I>, B : ValidatingBuilder<M>> :
public open class View<I : Any, M : ProjectionState<I>, B : ValidatingBuilder<M>> :
Projection<I, M, B>()

/**
Expand All @@ -117,14 +118,14 @@ public open class View<I : Any, M : EntityState<I>, B : ValidatingBuilder<M>> :
* If no customization is required from a `ViewRepository`, users should prefer
* [ViewRepository.default] to creating custom repository types.
*/
public open class ViewRepository<I : Any, V : View<I, S, *>, S : EntityState<I>>
public open class ViewRepository<I : Any, V : View<I, S, *>, S : ProjectionState<I>>
: ProjectionRepository<I, V, S>() {

public companion object {

@Suppress("UNCHECKED_CAST")
public fun default(cls: Class<out View<*, *, *>>): ViewRepository<*, *, *> {
val cast = cls as Class<View<Any, EntityState<Any>, *>>
val cast = cls as Class<View<Any, ProjectionState<Any>, *>>
return DefaultViewRepository(cast)
}
}
Expand Down Expand Up @@ -166,10 +167,11 @@ public fun MutableSet<ViewRepository<*, *, *>>.addDefault(view: KClass<out View<
* Otherwise, users should use `DefaultViewRepository` by calling [ViewRepository.default].
*/
internal class DefaultViewRepository(
private val cls: Class<View<Any, EntityState<Any>, *>>
) : ViewRepository<Any, View<Any, EntityState<Any>, *>, EntityState<Any>>(), DefaultRepository {
private val cls: Class<View<Any, ProjectionState<Any>, *>>
) : ViewRepository<Any, View<Any, ProjectionState<Any>, *>, ProjectionState<Any>>(),
DefaultRepository {

override fun entityModelClass(): ProjectionClass<View<Any, EntityState<Any>, *>> =
override fun entityModelClass(): ProjectionClass<View<Any, ProjectionState<Any>, *>> =
ProjectionClass.asProjectionClass(cls)

override fun logName(): String =
Expand Down
2 changes: 1 addition & 1 deletion api/src/main/proto/spine/compiler/source.proto
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ message ProtoFileHeader {
// Rather, it serves for informative purposes.
//
message ProtobufDependency {
option (entity).kind = VIEW;
option (entity).kind = PROJECTION;

// The relative path to the dependency.
//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,29 +36,29 @@

import static com.google.common.truth.Truth.assertThat;

@DisplayName("`Policy` Java API should")
class PolicyJavaApiSpec {
@DisplayName("`Reaction` Java API should")
class ReactionJavaApiSpec {

/**
* This test merely makes the {@link Policy#ignore} method used without making any
* This test merely makes the {@link Reaction#ignore} method used without making any
* meaningful assertions.
*
* <p>It creates a {@link Policy} which calls the `protected` method of the companion object
* <p>It creates a {@link Reaction} which calls the `protected` method of the companion object
* showing the usage scenario.
*
* @see PolicySpec#allowIgnoring() the test for Kotlin API
* @see ReactionSpec#allowIgnoring() the test for Kotlin API
*/
@Test
@DisplayName("have static factory method for ignoring incoming events")
void allowIgnoring() {
var policy = new Policy<TypeEntered>() {
var reaction = new Reaction<TypeEntered>() {
@React
@Override
protected EitherOf2<TypeEntered, NoReaction> whenever(
@External TypeEntered entered) {
return ignore();
}
};
assertThat(policy).isNotNull();
assertThat(reaction).isNotNull();
}
}
28 changes: 14 additions & 14 deletions api/src/test/kotlin/io/spine/tools/compiler/plugin/PluginSpec.kt
Original file line number Diff line number Diff line change
Expand Up @@ -51,24 +51,24 @@ import org.junit.jupiter.api.io.TempDir
@DisplayName("`Plugin` should")
internal class PluginSpec {

private lateinit var policy1: StubPolicy1
private lateinit var policy2: StubPolicy2
private lateinit var re1: StubReaction1
private lateinit var re2: StubReaction2
private lateinit var plugin: Plugin

@BeforeEach
fun createStubs() {
policy1 = StubPolicy1()
policy2 = StubPolicy2()
plugin = StubPlugin(policy1, policy2)
re1 = StubReaction1()
re2 = StubReaction2()
plugin = StubPlugin(re1, re2)
}

@Test
fun `propagate 'TypeSystem' into its policies`() {
fun `propagate 'TypeSystem' into its reactions`() {
val ctx = BoundedContext.singleTenant("Stubs")
val typeSystem = TypeSystem(ProtoFileList(emptyList()), emptySet())
plugin.applyTo(ctx, typeSystem)
policy1.typeSystem() shouldBe typeSystem
policy2.typeSystem() shouldBe typeSystem
re1.typeSystem() shouldBe typeSystem
re2.typeSystem() shouldBe typeSystem
}

@Test
Expand All @@ -78,9 +78,9 @@ internal class PluginSpec {
) {
runPipeline(src, target)

policy1.context() shouldNotBe null
policy1.context().name().value shouldStartWith CodeGenerationContext.NAME_PREFIX
policy2.context() shouldBe policy1.context()
re1.context() shouldNotBe null
re1.context().name().value shouldStartWith CodeGenerationContext.NAME_PREFIX
re2.context() shouldBe re1.context()
}

@Test
Expand All @@ -104,7 +104,7 @@ internal class PluginSpec {
}
}

private class StubPlugin(vararg policies: Policy<*>) : Plugin(policies = policies.toSet()) {
private class StubPlugin(vararg policies: Reaction<*>) : Plugin(reactions = policies.toSet()) {

lateinit var contextBuilder: BoundedContextBuilder

Expand All @@ -114,12 +114,12 @@ private class StubPlugin(vararg policies: Policy<*>) : Plugin(policies = policie
}
}

private class StubPolicy1 : TsStubPolicy<FieldEntered>() {
private class StubReaction1 : TsStubReaction<FieldEntered>() {
@React
override fun whenever(event: FieldEntered): Just<NoReaction> = Just.noReaction
}

private class StubPolicy2 : TsStubPolicy<FieldExited>() {
private class StubReaction2 : TsStubReaction<FieldExited>() {
@React
override fun whenever(event: FieldExited): Just<NoReaction> = Just.noReaction
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,48 +41,48 @@ import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows

@DisplayName("`Policy` should")
internal class PolicySpec {
@DisplayName("`Reaction` should")
internal class ReactionSpec {

@Test
fun `obtain 'TypeSystem' after injected`() {
val policy = StubPolicy()
val reaction = StubReaction()
assertThrows<IllegalStateException> {
policy.typeSystem()
reaction.typeSystem()
}

val typeSystem = TypeSystem(
ProtoFileList(emptyList()),
emptySet()
)

policy.use(typeSystem)
policy.typeSystem() shouldBe typeSystem
reaction.use(typeSystem)
reaction.typeSystem() shouldBe typeSystem
}

/**
* This test merely makes the [Policy.ignore] method used without making any
* This test merely makes the [Reaction.ignore] method used without making any
* meaningful assertions.
*
* It creates a [Policy] which calls the `protected` method of the companion object
* It creates a [Reaction] which calls the `protected` method of the companion object
* showing the usage scenario.
*
* @see PolicyJavaApiSpec.allowIgnoring the test for Java API.
* @see ReactionJavaApiSpec.allowIgnoring the test for Java API.
*/
@Test
@JvmName("allowIgnoring")
fun `have a shortcut for ignoring incoming events`() {
val policy = object : Policy<TypeEntered>() {
val reaction = object : Reaction<TypeEntered>() {
@React
override fun whenever(
@External event: TypeEntered
): EitherOf2<TypeEntered, NoReaction> = ignore()
}
policy shouldNotBe null
reaction shouldNotBe null
}
}

private class StubPolicy : TsStubPolicy<TypeDiscovered>() {
private class StubReaction : TsStubReaction<TypeDiscovered>() {
@React
override fun whenever(@External event: TypeDiscovered): Just<NoReaction> = Just.noReaction
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ import io.spine.server.BoundedContext
import io.spine.tools.compiler.type.TypeSystem

/**
* The abstract base for stub policy classes used in tests related to injecting [TypeSystem].
* The abstract base for stub reaction classes used in tests related to injecting [TypeSystem].
*/
internal abstract class TsStubPolicy<E : EventMessage> : Policy<E>() {
internal abstract class TsStubReaction<E : EventMessage> : Reaction<E>() {

/**
* Opens access to the protected [typeSystem] property.
Expand Down
3 changes: 2 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ buildscript {
classpath(io.spine.dependency.lib.Protobuf.GradlePlugin.lib)
classpath(io.spine.dependency.build.Ksp.run { artifact(gradlePlugin) })
classpath(io.spine.dependency.local.ToolBase.jvmToolPluginDogfooding)
classpath(spineCompiler.pluginLib)
classpath(coreJvmCompiler.pluginLib)
}
configurations.all {
Expand All @@ -67,7 +68,7 @@ plugins {
jacoco
`gradle-doctor`
`project-report`
`dokka-for-kotlin`
`dokka-setup`
}

/**
Expand Down
Loading
Loading