From 03e120d04b8016b8a2623407c68a6467586e22a4 Mon Sep 17 00:00:00 2001 From: Ross Nicoll Date: Fri, 19 Aug 2016 16:26:28 +0100 Subject: [PATCH] Add default values for ifMatched/ifNotMatched/requiredCommands --- .../main/kotlin/com/r3corda/contracts/IRS.kt | 6 +---- .../com/r3corda/contracts/asset/Obligation.kt | 6 +---- .../com/r3corda/contracts/clause/Net.kt | 11 +++----- .../com/r3corda/core/contracts/Structures.kt | 6 +---- .../r3corda/core/contracts/clauses/Clause.kt | 10 ++++++- .../core/contracts/clauses/ClauseVerifier.kt | 2 +- .../contracts/clauses/GroupClauseVerifier.kt | 2 +- .../contracts/clauses/InterceptorClause.kt | 6 +++-- .../contracts/clauses/VerifyClausesTests.kt | 27 ++++--------------- docs/source/release-notes.rst | 1 + docs/source/tutorial-contract-clauses.rst | 20 +++++++------- 11 files changed, 39 insertions(+), 58 deletions(-) diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/IRS.kt b/contracts/src/main/kotlin/com/r3corda/contracts/IRS.kt index 9e01cc916f..11ec5119b5 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/IRS.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/IRS.kt @@ -517,11 +517,7 @@ class InterestRateSwap() : Contract { override val clauses = listOf(Agree(), Fix(), Pay(), Mature()) } - class Timestamped : SingleClause { - override val ifMatched = MatchBehaviour.CONTINUE - override val ifNotMatched = MatchBehaviour.ERROR - override val requiredCommands = emptySet>() - + class Timestamped : SingleClause() { override fun verify(tx: TransactionForContract, commands: Collection>): Set { require(tx.timestamp?.midpoint != null) { "must be timestamped" } // We return an empty set because we don't process any commands diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/asset/Obligation.kt b/contracts/src/main/kotlin/com/r3corda/contracts/asset/Obligation.kt index 53ab2e2cb5..c0e4fad216 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/asset/Obligation.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/asset/Obligation.kt @@ -197,11 +197,7 @@ class Obligation

: Contract { * any lifecycle change clause, which is the only clause that involve * non-standard lifecycle states on input/output. */ - class VerifyLifecycle

: SingleClause, GroupClause, Issued>> { - override val requiredCommands: Set> = emptySet() - override val ifMatched: MatchBehaviour = MatchBehaviour.CONTINUE - override val ifNotMatched: MatchBehaviour = MatchBehaviour.ERROR - + class VerifyLifecycle

: SingleClause(), GroupClause, Issued>> { override fun verify(tx: TransactionForContract, commands: Collection>): Set = verify( tx.inputs.filterIsInstance>(), diff --git a/contracts/src/main/kotlin/com/r3corda/contracts/clause/Net.kt b/contracts/src/main/kotlin/com/r3corda/contracts/clause/Net.kt index cca285f51c..5566aa38a9 100644 --- a/contracts/src/main/kotlin/com/r3corda/contracts/clause/Net.kt +++ b/contracts/src/main/kotlin/com/r3corda/contracts/clause/Net.kt @@ -43,13 +43,10 @@ data class MultilateralNetState

( * Clause for netting contract states. Currently only supports obligation contract. */ // TODO: Make this usable for any nettable contract states -open class NetClause

: SingleClause { - override val ifNotMatched: MatchBehaviour - get() = MatchBehaviour.CONTINUE - override val ifMatched: MatchBehaviour - get() = MatchBehaviour.END - override val requiredCommands: Set> - get() = setOf(Obligation.Commands.Net::class.java) +open class NetClause

: SingleClause() { + override val ifMatched: MatchBehaviour = MatchBehaviour.END + override val ifNotMatched: MatchBehaviour = MatchBehaviour.CONTINUE + override val requiredCommands: Set> = setOf(Obligation.Commands.Net::class.java) @Suppress("ConvertLambdaToReference") override fun verify(tx: TransactionForContract, commands: Collection>): Set { diff --git a/core/src/main/kotlin/com/r3corda/core/contracts/Structures.kt b/core/src/main/kotlin/com/r3corda/core/contracts/Structures.kt index 69e0195de2..8305ffcda7 100644 --- a/core/src/main/kotlin/com/r3corda/core/contracts/Structures.kt +++ b/core/src/main/kotlin/com/r3corda/core/contracts/Structures.kt @@ -218,11 +218,7 @@ interface LinearState: ContractState { /** * Standard clause to verify the LinearState safety properties. */ - class ClauseVerifier(val stateClass: Class) : SingleClause { - override val ifMatched = MatchBehaviour.CONTINUE - override val ifNotMatched = MatchBehaviour.ERROR - override val requiredCommands = emptySet>() - + class ClauseVerifier(val stateClass: Class) : SingleClause() { override fun verify(tx: TransactionForContract, commands: Collection>): Set { val inputs = tx.inputs.filterIsInstance(stateClass) val inputIds = inputs.map { it.linearId }.distinct() diff --git a/core/src/main/kotlin/com/r3corda/core/contracts/clauses/Clause.kt b/core/src/main/kotlin/com/r3corda/core/contracts/clauses/Clause.kt index 1c61161efa..b7b542525a 100644 --- a/core/src/main/kotlin/com/r3corda/core/contracts/clauses/Clause.kt +++ b/core/src/main/kotlin/com/r3corda/core/contracts/clauses/Clause.kt @@ -41,4 +41,12 @@ interface SingleVerify { } -interface SingleClause : Clause, SingleVerify \ No newline at end of file +/** + * A single verifiable clause. By default always matches, continues to the next clause when matched and errors + * if not matched. + */ +abstract class SingleClause : Clause, SingleVerify { + override val ifMatched: MatchBehaviour = MatchBehaviour.CONTINUE + override val ifNotMatched: MatchBehaviour = MatchBehaviour.ERROR + override val requiredCommands: Set> = emptySet() +} \ No newline at end of file diff --git a/core/src/main/kotlin/com/r3corda/core/contracts/clauses/ClauseVerifier.kt b/core/src/main/kotlin/com/r3corda/core/contracts/clauses/ClauseVerifier.kt index d734fcd43f..eaa4331c3d 100644 --- a/core/src/main/kotlin/com/r3corda/core/contracts/clauses/ClauseVerifier.kt +++ b/core/src/main/kotlin/com/r3corda/core/contracts/clauses/ClauseVerifier.kt @@ -27,7 +27,7 @@ fun verifyClauses(tx: TransactionForContract, } when (matchBehaviour) { - MatchBehaviour.ERROR -> throw IllegalStateException() + MatchBehaviour.ERROR -> throw IllegalStateException("Error due to matching/not matching ${clause}") MatchBehaviour.CONTINUE -> { } MatchBehaviour.END -> break@verify diff --git a/core/src/main/kotlin/com/r3corda/core/contracts/clauses/GroupClauseVerifier.kt b/core/src/main/kotlin/com/r3corda/core/contracts/clauses/GroupClauseVerifier.kt index 3478fe8027..cf1b4a2007 100644 --- a/core/src/main/kotlin/com/r3corda/core/contracts/clauses/GroupClauseVerifier.kt +++ b/core/src/main/kotlin/com/r3corda/core/contracts/clauses/GroupClauseVerifier.kt @@ -21,7 +21,7 @@ interface GroupVerify { interface GroupClause : Clause, GroupVerify -abstract class GroupClauseVerifier : SingleClause { +abstract class GroupClauseVerifier : SingleClause() { abstract val clauses: List> override val requiredCommands: Set> get() = emptySet() diff --git a/core/src/main/kotlin/com/r3corda/core/contracts/clauses/InterceptorClause.kt b/core/src/main/kotlin/com/r3corda/core/contracts/clauses/InterceptorClause.kt index 9f9f4d3625..e1a64d58d6 100644 --- a/core/src/main/kotlin/com/r3corda/core/contracts/clauses/InterceptorClause.kt +++ b/core/src/main/kotlin/com/r3corda/core/contracts/clauses/InterceptorClause.kt @@ -9,10 +9,10 @@ import java.util.* * A clause which intercepts calls to a wrapped clause, and passes them through verification * only from a pre-clause. This is similar to an inceptor in aspect orientated programming. */ -data class InterceptorClause( +class InterceptorClause( val preclause: SingleVerify, val clause: SingleClause -) : SingleClause { +) : SingleClause() { override val ifNotMatched: MatchBehaviour get() = clause.ifNotMatched override val ifMatched: MatchBehaviour @@ -25,4 +25,6 @@ data class InterceptorClause( consumed.addAll(clause.verify(tx, commands)) return consumed } + + override fun toString(): String = "Interceptor clause [${clause}]" } \ No newline at end of file diff --git a/core/src/test/kotlin/com/r3corda/core/contracts/clauses/VerifyClausesTests.kt b/core/src/test/kotlin/com/r3corda/core/contracts/clauses/VerifyClausesTests.kt index b97d943745..271a1588f2 100644 --- a/core/src/test/kotlin/com/r3corda/core/contracts/clauses/VerifyClausesTests.kt +++ b/core/src/test/kotlin/com/r3corda/core/contracts/clauses/VerifyClausesTests.kt @@ -19,11 +19,7 @@ class VerifyClausesTests { /** Very simple check that the function doesn't error when given any clause */ @Test fun minimal() { - val clause = object : SingleClause { - override val requiredCommands: Set> - get() = emptySet() - override val ifMatched: MatchBehaviour - get() = MatchBehaviour.CONTINUE + val clause = object : SingleClause() { override val ifNotMatched: MatchBehaviour get() = MatchBehaviour.CONTINUE @@ -36,14 +32,7 @@ class VerifyClausesTests { /** Check that when there are no required commands, a clause always matches */ @Test fun emptyAlwaysMatches() { - val clause = object : SingleClause { - override val requiredCommands: Set> - get() = emptySet() - override val ifMatched: MatchBehaviour - get() = MatchBehaviour.CONTINUE - override val ifNotMatched: MatchBehaviour - get() = MatchBehaviour.ERROR - + val clause = object : SingleClause() { override fun verify(tx: TransactionForContract, commands: Collection>): Set = emptySet() } val tx = TransactionForContract(emptyList(), emptyList(), emptyList(), emptyList(), SecureHash.randomSHA256()) @@ -53,9 +42,7 @@ class VerifyClausesTests { @Test fun errorSuperfluousCommands() { - val clause = object : SingleClause { - override val requiredCommands: Set> - get() = emptySet() + val clause = object : SingleClause() { override val ifMatched: MatchBehaviour get() = MatchBehaviour.ERROR override val ifNotMatched: MatchBehaviour @@ -73,7 +60,7 @@ class VerifyClausesTests { /** Check triggering of error if matched */ @Test fun errorMatched() { - val clause = object : SingleClause { + val clause = object : SingleClause() { override val requiredCommands: Set> get() = setOf(DummyContract.Commands.Create::class.java) override val ifMatched: MatchBehaviour @@ -98,13 +85,9 @@ class VerifyClausesTests { /** Check triggering of error if unmatched */ @Test fun errorUnmatched() { - val clause = object : SingleClause { + val clause = object : SingleClause() { override val requiredCommands: Set> get() = setOf(DummyContract.Commands.Create::class.java) - override val ifMatched: MatchBehaviour - get() = MatchBehaviour.CONTINUE - override val ifNotMatched: MatchBehaviour - get() = MatchBehaviour.ERROR override fun verify(tx: TransactionForContract, commands: Collection>): Set = emptySet() } diff --git a/docs/source/release-notes.rst b/docs/source/release-notes.rst index 778691f8bc..04ea2ef75e 100644 --- a/docs/source/release-notes.rst +++ b/docs/source/release-notes.rst @@ -54,6 +54,7 @@ API changes: * The ``arg`` method in the test DSL is now called ``command`` to be consistent with the rest of the data model. * The messaging APIs have changed somewhat to now use a new ``TopicSession`` object. These APIs will continue to change in the upcoming releases. +* Clauses now have default values provided for ``ifMatched``, ``ifNotMatched`` and ``requiredCommands``. New documentation: diff --git a/docs/source/tutorial-contract-clauses.rst b/docs/source/tutorial-contract-clauses.rst index 47948b0aef..eb1ee343c0 100644 --- a/docs/source/tutorial-contract-clauses.rst +++ b/docs/source/tutorial-contract-clauses.rst @@ -16,13 +16,13 @@ generally the same for all fungible contracts, so a single issuance clause can b error, and improves consistency of behaviour. Clauses can be composed of subclauses, either to combine clauses in different ways, or to apply specialised clauses. -In the case of commercial paper, we have a "Grouping" outermost clause, which will contain the "Issue", "Move" and -"Redeem" clauses. The result is a contract that looks something like this: +In the case of commercial paper, we have a ``Group`` outermost clause, which will contain the ``Issue``, ``Move`` and +``Redeem`` clauses. The result is a contract that looks something like this: 1. Group input and output states together, and then apply the following clauses on each group: - a. If an Issue command is present, run appropriate tests and end processing this group. - b. If a Move command is present, run appropriate tests and end processing this group. - c. If a Redeem command is present, run appropriate tests and end processing this group. + a. If an ``Issue`` command is present, run appropriate tests and end processing this group. + b. If a ``Move`` command is present, run appropriate tests and end processing this group. + c. If a ``Redeem`` command is present, run appropriate tests and end processing this group. Commercial paper class ---------------------- @@ -71,9 +71,9 @@ Clauses We'll tackle the inner clauses that contain the bulk of the verification logic, first, and the clause which handles grouping of input/output states later. The inner clauses need to implement the ``GroupClause`` interface, which defines -the verify() function, and properties for key information on how the clause is processed. These properties specify the -command(s) which must be present in order for the clause to be matched, and what to do after processing the clause -depending on whether it was matched or not. +the verify() function, and properties (``ifMatched``, ``ifNotMatched`` and ``requiredCommands``) defining how the clause +is processed. These properties specify the command(s) which must be present in order for the clause to be matched, +and what to do after processing the clause depending on whether it was matched or not. The ``verify()`` functions defined in the ``SingleClause`` and ``GroupClause`` interfaces is similar to the conventional ``Contract`` verification function, although it adds new parameters and returns the set of commands which it has processed. @@ -157,7 +157,9 @@ The post-processing ``MatchBehaviour`` options are: * ERROR In this case we process commands against each group, until the first matching clause is found, so we ``END`` on a match -and ``CONTINUE`` otherwise. ``ERROR`` can be used as a part of a clause which must always/never be matched. +and ``CONTINUE`` otherwise. ``ERROR`` can be used as a part of a clause which must always/never be matched. By default +clauses are always matched (``requiredCommands`` is an empty set), execution continues after a clause is matched, and an +error is raised if a clause is not matched. Group Clause ------------