contracts, core: Expose top-level DSL values/functions to Java by wrapping them in an object

core: Add overloads for convenient Java interop

contracts, core: Uniform Java interop for tests, use camelCase
This commit is contained in:
Andras Slemmer 2016-06-21 18:15:41 +01:00
parent 28e85923a3
commit 040e51ec12
7 changed files with 220 additions and 138 deletions

View File

@ -48,25 +48,42 @@ fun generateState() = DummyContract.State(Random().nextInt())
// contract `fails requirement` "some substring of the error message"
// }
infix fun Cash.State.`owned by`(owner: PublicKey) = copy(owner = owner)
infix fun Cash.State.`issued by`(party: Party) = copy(amount = Amount<Issued<Currency>>(amount.quantity, issuanceDef.copy(issuer = deposit.copy(party = party))))
infix fun Cash.State.`issued by`(deposit: PartyAndReference) = copy(amount = Amount<Issued<Currency>>(amount.quantity, issuanceDef.copy(issuer = deposit)))
infix fun Cash.State.`with notary`(notary: Party) = TransactionState(this, notary)
// For Java compatibility please define helper methods here and then define the infix notation
object Java {
@JvmStatic fun ownedBy(state: Cash.State, owner: PublicKey) = state.copy(owner = owner)
@JvmStatic fun issuedBy(state: Cash.State, party: Party) = state.copy(amount = Amount<Issued<Currency>>(state.amount.quantity, state.issuanceDef.copy(issuer = state.deposit.copy(party = party))))
@JvmStatic fun issuedBy(state: Cash.State, deposit: PartyAndReference) = state.copy(amount = Amount<Issued<Currency>>(state.amount.quantity, state.issuanceDef.copy(issuer = deposit)))
@JvmStatic fun withNotary(state: Cash.State, notary: Party) = TransactionState(state, notary)
@JvmStatic fun withDeposit(state: Cash.State, deposit: PartyAndReference) = state.copy(amount = state.amount.copy(token = state.amount.token.copy(issuer = deposit)))
infix fun CommercialPaper.State.`owned by`(owner: PublicKey) = this.copy(owner = owner)
infix fun CommercialPaper.State.`with notary`(notary: Party) = TransactionState(this, notary)
infix fun ICommercialPaperState.`owned by`(new_owner: PublicKey) = this.withOwner(new_owner)
@JvmStatic fun ownedBy(state: CommercialPaper.State, owner: PublicKey) = state.copy(owner = owner)
@JvmStatic fun withNotary(state: CommercialPaper.State, notary: Party) = TransactionState(state, notary)
@JvmStatic fun ownedBy(state: ICommercialPaperState, new_owner: PublicKey) = state.withOwner(new_owner)
infix fun Cash.State.`with deposit`(deposit: PartyAndReference): Cash.State =
copy(amount = amount.copy(token = amount.token.copy(issuer = deposit)))
@JvmStatic fun withNotary(state: ContractState, notary: Party) = TransactionState(state, notary)
@JvmStatic fun CASH(amount: Amount<Currency>) = Cash.State(
Amount<Issued<Currency>>(amount.quantity, Issued<Currency>(DUMMY_CASH_ISSUER, amount.token)),
NullPublicKey)
@JvmStatic fun STATE(amount: Amount<Issued<Currency>>) = Cash.State(amount, NullPublicKey)
}
infix fun Cash.State.`owned by`(owner: PublicKey) = Java.ownedBy(this, owner)
infix fun Cash.State.`issued by`(party: Party) = Java.issuedBy(this, party)
infix fun Cash.State.`issued by`(deposit: PartyAndReference) = Java.issuedBy(this, deposit)
infix fun Cash.State.`with notary`(notary: Party) = Java.withNotary(this, notary)
infix fun Cash.State.`with deposit`(deposit: PartyAndReference): Cash.State = Java.withDeposit(this, deposit)
infix fun CommercialPaper.State.`owned by`(owner: PublicKey) = Java.ownedBy(this, owner)
infix fun CommercialPaper.State.`with notary`(notary: Party) = Java.withNotary(this, notary)
infix fun ICommercialPaperState.`owned by`(new_owner: PublicKey) = Java.ownedBy(this, new_owner)
infix fun ContractState.`with notary`(notary: Party) = Java.withNotary(this, notary)
val DUMMY_CASH_ISSUER_KEY = generateKeyPair()
val DUMMY_CASH_ISSUER = Party("Snake Oil Issuer", DUMMY_CASH_ISSUER_KEY.public).ref(1)
/** Allows you to write 100.DOLLARS.CASH */
val Amount<Currency>.CASH: Cash.State get() = Cash.State(
Amount<Issued<Currency>>(this.quantity, Issued<Currency>(DUMMY_CASH_ISSUER, this.token)),
NullPublicKey)
val Amount<Currency>.CASH: Cash.State get() = Java.CASH(this)
val Amount<Issued<Currency>>.STATE: Cash.State get() = Java.STATE(this)
val Amount<Issued<Currency>>.STATE: Cash.State get() = Cash.State(this, NullPublicKey)
infix fun ContractState.`with notary`(notary: Party) = TransactionState(this, notary)

View File

@ -0,0 +1,58 @@
package com.r3corda.contracts.cash;
import com.r3corda.core.contracts.PartyAndReference;
import com.r3corda.core.serialization.OpaqueBytes;
import org.junit.Test;
import static com.r3corda.core.testing.Java.*;
import static com.r3corda.core.contracts.Java.*;
import static com.r3corda.contracts.testing.Java.*;
/**
* This is an incomplete Java replica of CashTests.kt to show how to use the Java test DSL
*/
public class CashTestsJava {
private OpaqueBytes defaultRef = new OpaqueBytes(new byte[]{1});;
private PartyAndReference defaultIssuer = MEGA_CORP.ref(defaultRef);
private Cash.State inState = new Cash.State(issuedBy(DOLLARS(1000), defaultIssuer), DUMMY_PUBKEY_1);
private Cash.State outState = new Cash.State(inState.getAmount(), DUMMY_PUBKEY_2);;
@Test
public void trivial() {
transaction(tx -> {
tx.input(inState);
tx.failsRequirement("the amounts balance");
tx.tweak(tw -> {
tw.output(new Cash.State(issuedBy(DOLLARS(2000), defaultIssuer), DUMMY_PUBKEY_2));
return tw.failsRequirement("the amounts balance");
});
tx.tweak(tw -> {
tw.output(outState);
// No command arguments
return tw.failsRequirement("required com.r3corda.contracts.cash.FungibleAsset.Commands.Move command");
});
tx.tweak(tw -> {
tw.output(outState);
tw.arg(DUMMY_PUBKEY_2, new Cash.Commands.Move());
return tw.failsRequirement("the owning keys are the same as the signing keys");
});
tx.tweak(tw -> {
tw.output(outState);
tw.output(issuedBy(outState, MINI_CORP));
tw.arg(DUMMY_PUBKEY_1, new Cash.Commands.Move());
return tw.failsRequirement("at least one asset input");
});
// Simple reallocation works.
return tx.tweak(tw -> {
tw.output(outState);
tw.arg(DUMMY_PUBKEY_1, new Cash.Commands.Move());
return tw.accepts();
});
});
}
}

View File

@ -1,56 +0,0 @@
package com.r3corda.contracts.cash;
import com.r3corda.core.contracts.PartyAndReference;
import com.r3corda.core.serialization.OpaqueBytes;
import com.r3corda.core.testing.TransactionTestBase;
import org.junit.Test;
import static com.r3corda.core.testing.Dummies.*;
import static com.r3corda.contracts.testing.Methods.*;
import static com.r3corda.core.contracts.Currencies.*;
import static com.r3corda.core.contracts.Methods.*;
public class CashTestsJava extends TransactionTestBase {
private OpaqueBytes defaultRef = new OpaqueBytes(new byte[]{1});;
private PartyAndReference defaultIssuer = MEGA_CORP.ref(defaultRef);
private Cash.State inState = new Cash.State(issued_by(DOLLARS(2000), defaultIssuer), DUMMY_PUBKEY_1);
private Cash.State outState = inState.copy(inState.getAmount(), DUMMY_PUBKEY_2);;
@Test
public void trivial() {
transaction(begin
.input(inState)
.fails_requirement("the amounts balance")
.tweak(begin
.output(outState.copy(issued_by(DOLLARS(2000), defaultIssuer), DUMMY_PUBKEY_2))
.fails_requirement("the amounts balance")
)
.tweak(begin
.output(outState)
.fails_requirement("required com.r3corda.contracts.cash.FungibleAsset.Commands.Move command")
)
.tweak(begin
.output(outState)
.arg(DUMMY_PUBKEY_2, new Cash.Commands.Move())
.fails_requirement("the owning keys are the same as the signing keys")
)
.tweak(begin
.output(outState)
.output(issued_by(outState, MINI_CORP))
.arg(DUMMY_PUBKEY_1, new Cash.Commands.Move())
.fails_requirement("at least one asset input")
)
.tweak(begin
.output(outState)
.arg(DUMMY_PUBKEY_1, new Cash.Commands.Move())
.accepts()
)
);
}
}

View File

@ -19,18 +19,32 @@ import java.util.*
fun currency(code: String) = Currency.getInstance(code)
val USD = currency("USD")
val GBP = currency("GBP")
val CHF = currency("CHF")
// Java interop
object Java {
@JvmField val USD = currency("USD")
@JvmField val GBP = currency("GBP")
@JvmField val CHF = currency("CHF")
val Int.DOLLARS: Amount<Currency> get() = Amount(this.toLong() * 100, USD)
val Int.POUNDS: Amount<Currency> get() = Amount(this.toLong() * 100, GBP)
val Int.SWISS_FRANCS: Amount<Currency> get() = Amount(this.toLong() * 100, CHF)
@JvmStatic fun DOLLARS(amount: Int) = Amount(amount.toLong() * 100, USD)
@JvmStatic fun DOLLARS(amount: Double) = Amount((amount * 100).toLong(), USD)
@JvmStatic fun POUNDS(amount: Int) = Amount(amount.toLong() * 100, GBP)
@JvmStatic fun SWISS_FRANCS(amount: Int) = Amount(amount.toLong() * 100, CHF)
val Double.DOLLARS: Amount<Currency> get() = Amount((this * 100).toLong(), USD)
@JvmStatic fun issuedBy(currency: Currency, deposit: PartyAndReference) = Issued<Currency>(deposit, currency)
@JvmStatic fun issuedBy(amount: Amount<Currency>, deposit: PartyAndReference) = Amount(amount.quantity, issuedBy(amount.token, deposit))
}
infix fun Currency.`issued by`(deposit: PartyAndReference) : Issued<Currency> = Issued<Currency>(deposit, this)
infix fun <T> Amount<T>.`issued by`(deposit: PartyAndReference) : Amount<Issued<T>> = Amount(quantity, Issued<T>(deposit, this.token))
val USD = Java.USD
val GBP = Java.GBP
val CHF = Java.CHF
val Int.DOLLARS: Amount<Currency> get() = Java.DOLLARS(this)
val Double.DOLLARS: Amount<Currency> get() = Java.DOLLARS(this)
val Int.POUNDS: Amount<Currency> get() = Java.POUNDS(this)
val Int.SWISS_FRANCS: Amount<Currency> get() = Java.SWISS_FRANCS(this)
infix fun Currency.`issued by`(deposit: PartyAndReference) = Java.issuedBy(this, deposit)
infix fun Amount<Currency>.`issued by`(deposit: PartyAndReference) = Java.issuedBy(this, deposit)
//// Requirements /////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@ -43,8 +43,8 @@ object TestUtils {
* JAVA INTEROP. Please keep the following points in mind when extending the Kotlin DSL
*
* - Annotate functions with Kotlin defaults with @JvmOverloads. This produces the relevant overloads for Java.
* - Void closures in arguments are inconvenient in Java, use overloading to define non-closure variants as well
* - Top-level vals should be defined in the [Java] object and annotated with @JvmField first and should be referred
* - Void closures in arguments are inconvenient in Java, use overloading to define non-closure variants as well.
* - Top-level vals should be defined in a [Java] object and annotated with @JvmField first and should be referred
* to from the global val. This allows static importing of [Java] in Java tests, which mimicks top-level vals.
* - Same goes for top-level funs. Define them in [Java] with annotation @JvmStatic and define a global alias later.
* - Infix functions work as regular ones from Java, but symbols with spaces in them don't! Define a camelCase variant
@ -52,39 +52,73 @@ object TestUtils {
* - varargs are exposed as array types in Java. Define overloads for common cases.
* - The Int.DOLLARS syntax doesn't work from Java. To remedy add a @JvmStatic DOLLARS(Int) function to [Java]
*/
object Java {
// A dummy time at which we will be pretending test transactions are created.
@JvmField val TEST_TX_TIME = Instant.parse("2015-04-17T12:00:00.00Z")
// A few dummy values for testing.
val MEGA_CORP_KEY = TestUtils.keypair
val MEGA_CORP_PUBKEY = MEGA_CORP_KEY.public
@JvmField val MEGA_CORP_KEY = TestUtils.keypair
@JvmField val MEGA_CORP_PUBKEY = MEGA_CORP_KEY.public
val MINI_CORP_KEY = TestUtils.keypair2
val MINI_CORP_PUBKEY = MINI_CORP_KEY.public
@JvmField val MINI_CORP_KEY = TestUtils.keypair2
@JvmField val MINI_CORP_PUBKEY = MINI_CORP_KEY.public
val ORACLE_KEY = TestUtils.keypair3
val ORACLE_PUBKEY = ORACLE_KEY.public
@JvmField val ORACLE_KEY = TestUtils.keypair3
@JvmField val ORACLE_PUBKEY = ORACLE_KEY.public
val DUMMY_PUBKEY_1 = DummyPublicKey("x1")
val DUMMY_PUBKEY_2 = DummyPublicKey("x2")
@JvmField val DUMMY_PUBKEY_1 = DummyPublicKey("x1")
@JvmField val DUMMY_PUBKEY_2 = DummyPublicKey("x2")
val ALICE_KEY = generateKeyPair()
val ALICE_PUBKEY = ALICE_KEY.public
val ALICE = Party("Alice", ALICE_PUBKEY)
@JvmField val ALICE_KEY = generateKeyPair()
@JvmField val ALICE_PUBKEY = ALICE_KEY.public
@JvmField val ALICE = Party("Alice", ALICE_PUBKEY)
val BOB_KEY = generateKeyPair()
val BOB_PUBKEY = BOB_KEY.public
val BOB = Party("Bob", BOB_PUBKEY)
@JvmField val BOB_KEY = generateKeyPair()
@JvmField val BOB_PUBKEY = BOB_KEY.public
@JvmField val BOB = Party("Bob", BOB_PUBKEY)
val MEGA_CORP = Party("MegaCorp", MEGA_CORP_PUBKEY)
val MINI_CORP = Party("MiniCorp", MINI_CORP_PUBKEY)
@JvmField val MEGA_CORP = Party("MegaCorp", MEGA_CORP_PUBKEY)
@JvmField val MINI_CORP = Party("MiniCorp", MINI_CORP_PUBKEY)
val DUMMY_NOTARY_KEY = generateKeyPair()
val DUMMY_NOTARY = Party("Notary Service", DUMMY_NOTARY_KEY.public)
@JvmField val DUMMY_NOTARY_KEY = generateKeyPair()
@JvmField val DUMMY_NOTARY = Party("Notary Service", DUMMY_NOTARY_KEY.public)
val ALL_TEST_KEYS = listOf(MEGA_CORP_KEY, MINI_CORP_KEY, ALICE_KEY, BOB_KEY, DUMMY_NOTARY_KEY)
@JvmField val ALL_TEST_KEYS = listOf(MEGA_CORP_KEY, MINI_CORP_KEY, ALICE_KEY, BOB_KEY, DUMMY_NOTARY_KEY)
val MOCK_IDENTITY_SERVICE = MockIdentityService(listOf(MEGA_CORP, MINI_CORP, DUMMY_NOTARY))
@JvmField val MOCK_IDENTITY_SERVICE = MockIdentityService(listOf(MEGA_CORP, MINI_CORP, DUMMY_NOTARY))
fun generateStateRef() = StateRef(SecureHash.randomSHA256(), 0)
@JvmStatic fun generateStateRef() = StateRef(SecureHash.randomSHA256(), 0)
@JvmStatic fun transaction(body: TransactionForTest.() -> LastLineShouldTestForAcceptOrFailure): LastLineShouldTestForAcceptOrFailure {
return body(TransactionForTest())
}
}
val TEST_TX_TIME = Java.TEST_TX_TIME
val MEGA_CORP_KEY = Java.MEGA_CORP_KEY
val MEGA_CORP_PUBKEY = Java.MEGA_CORP_PUBKEY
val MINI_CORP_KEY = Java.MINI_CORP_KEY
val MINI_CORP_PUBKEY = Java.MINI_CORP_PUBKEY
val ORACLE_KEY = Java.ORACLE_KEY
val ORACLE_PUBKEY = Java.ORACLE_PUBKEY
val DUMMY_PUBKEY_1 = Java.DUMMY_PUBKEY_1
val DUMMY_PUBKEY_2 = Java.DUMMY_PUBKEY_2
val ALICE_KEY = Java.ALICE_KEY
val ALICE_PUBKEY = Java.ALICE_PUBKEY
val ALICE = Java.ALICE
val BOB_KEY = Java.BOB_KEY
val BOB_PUBKEY = Java.BOB_PUBKEY
val BOB = Java.BOB
val MEGA_CORP = Java.MEGA_CORP
val MINI_CORP = Java.MINI_CORP
val DUMMY_NOTARY_KEY = Java.DUMMY_NOTARY_KEY
val DUMMY_NOTARY = Java.DUMMY_NOTARY
val ALL_TEST_KEYS = Java.ALL_TEST_KEYS
val MOCK_IDENTITY_SERVICE = Java.MOCK_IDENTITY_SERVICE
fun generateStateRef() = Java.generateStateRef()
fun transaction(body: TransactionForTest.() -> LastLineShouldTestForAcceptOrFailure) = Java.transaction(body)
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//
@ -120,7 +154,10 @@ abstract class AbstractTransactionForTest {
protected val signers = LinkedHashSet<PublicKey>()
protected val type = TransactionType.General()
@JvmOverloads
open fun output(label: String? = null, s: () -> ContractState) = LabeledOutput(label, TransactionState(s(), DUMMY_NOTARY)).apply { outStates.add(this) }
@JvmOverloads
open fun output(label: String? = null, s: ContractState) = output(label) { s }
protected fun commandsToAuthenticatedObjects(): List<AuthenticatedObject<CommandData>> {
return commands.map { AuthenticatedObject(it.signers, it.signers.mapNotNull { MOCK_IDENTITY_SERVICE.partyFromKey(it) }, it.value) }
@ -130,10 +167,11 @@ abstract class AbstractTransactionForTest {
attachments.add(attachmentID)
}
fun arg(vararg key: PublicKey, c: () -> CommandData) {
val keys = listOf(*key)
addCommand(Command(c(), keys))
fun arg(vararg keys: PublicKey, c: () -> CommandData) {
val keysList = listOf(*keys)
addCommand(Command(c(), keysList))
}
fun arg(key: PublicKey, c: CommandData) = arg(key) { c }
fun timestamp(time: Instant) {
val data = TimestampCommand(time, 30.seconds)
@ -165,12 +203,15 @@ sealed class LastLineShouldTestForAcceptOrFailure {
}
// Corresponds to the args to Contract.verify
// Note on defaults: try to avoid Kotlin defaults as they don't work from Java. Instead define overloads
open class TransactionForTest : AbstractTransactionForTest() {
private val inStates = arrayListOf<TransactionState<ContractState>>()
fun input(s: () -> ContractState) {
signers.add(DUMMY_NOTARY.owningKey)
inStates.add(TransactionState(s(), DUMMY_NOTARY))
}
fun input(s: ContractState) = input { s }
protected fun runCommandsAndVerify(time: Instant) {
val cmds = commandsToAuthenticatedObjects()
@ -178,10 +219,13 @@ open class TransactionForTest : AbstractTransactionForTest() {
tx.verify()
}
@JvmOverloads
fun accepts(time: Instant = TEST_TX_TIME): LastLineShouldTestForAcceptOrFailure {
runCommandsAndVerify(time)
return LastLineShouldTestForAcceptOrFailure.Token
}
@JvmOverloads
fun rejects(withMessage: String? = null, time: Instant = TEST_TX_TIME): LastLineShouldTestForAcceptOrFailure {
val r = try {
runCommandsAndVerify(time)
@ -202,8 +246,7 @@ open class TransactionForTest : AbstractTransactionForTest() {
* Used to confirm that the test, when (implicitly) run against the .verify() method, fails with the text of the message
*/
infix fun `fails requirement`(msg: String): LastLineShouldTestForAcceptOrFailure = rejects(msg)
fun fails_requirement(msg: String) = this.`fails requirement`(msg)
fun failsRequirement(msg: String) = this.`fails requirement`(msg)
// Use this to create transactions where the output of this transaction is automatically used as an input of
// the next.
@ -251,10 +294,6 @@ open class TransactionForTest : AbstractTransactionForTest() {
}
}
fun transaction(body: TransactionForTest.() -> LastLineShouldTestForAcceptOrFailure): LastLineShouldTestForAcceptOrFailure {
return body(TransactionForTest())
}
class TransactionGroupDSL<T : ContractState>(private val stateType: Class<T>) {
open inner class WireTransactionDSL : AbstractTransactionForTest() {
private val inStates = ArrayList<StateRef>()
@ -335,6 +374,7 @@ class TransactionGroupDSL<T : ContractState>(private val stateType: Class<T>) {
val txns = ArrayList<WireTransaction>()
private val txnToLabelMap = HashMap<SecureHash, String>()
@JvmOverloads
fun transaction(label: String? = null, body: WireTransactionDSL.() -> Unit): WireTransaction {
val forTest = InternalWireTransactionDSL()
forTest.body()

View File

@ -1,24 +0,0 @@
package com.r3corda.contracts.testing
import com.r3corda.contracts.Obligation
import com.r3corda.contracts.cash.Cash
import com.r3corda.core.contracts.Amount
import com.r3corda.core.contracts.Issued
import com.r3corda.core.crypto.NullPublicKey
import com.r3corda.core.crypto.Party
import com.r3corda.core.testing.MINI_CORP
import com.r3corda.core.utilities.nonEmptySetOf
import java.security.PublicKey
import java.time.Instant
import java.util.*
infix fun <T> Obligation.State<T>.`at`(dueBefore: Instant) = copy(template = template.copy(dueBefore = dueBefore))
infix fun <T> Obligation.State<T>.`between`(parties: Pair<Party, PublicKey>) = copy(issuer = parties.first, owner = parties.second)
infix fun <T> Obligation.State<T>.`owned by`(owner: PublicKey) = copy(owner = owner)
infix fun <T> Obligation.State<T>.`issued by`(party: Party) = copy(issuer = party)
// Allows you to write 100.DOLLARS.OBLIGATION
val Issued<Currency>.OBLIGATION_DEF: Obligation.StateTemplate<Currency> get() = Obligation.StateTemplate(nonEmptySetOf(Cash().legalContractReference),
nonEmptySetOf(this), Instant.parse("2020-01-01T17:00:00Z"))
val Amount<Issued<Currency>>.OBLIGATION: Obligation.State<Currency> get() = Obligation.State(Obligation.Lifecycle.NORMAL, MINI_CORP,
this.token.OBLIGATION_DEF, this.quantity, NullPublicKey)

View File

@ -0,0 +1,33 @@
package com.r3corda.contracts.testing
import com.r3corda.contracts.Obligation
import com.r3corda.contracts.cash.Cash
import com.r3corda.core.contracts.Amount
import com.r3corda.core.contracts.Issued
import com.r3corda.core.crypto.NullPublicKey
import com.r3corda.core.crypto.Party
import com.r3corda.core.testing.MINI_CORP
import com.r3corda.core.utilities.nonEmptySetOf
import java.security.PublicKey
import java.time.Instant
import java.util.*
object JavaExperimental {
@JvmStatic fun <T> at(state: Obligation.State<T>, dueBefore: Instant) = state.copy(template = state.template.copy(dueBefore = dueBefore))
@JvmStatic fun <T> between(state: Obligation.State<T>, parties: Pair<Party, PublicKey>) = state.copy(issuer = parties.first, owner = parties.second)
@JvmStatic fun <T> ownedBy(state: Obligation.State<T>, owner: PublicKey) = state.copy(owner = owner)
@JvmStatic fun <T> issuedBy(state: Obligation.State<T>, party: Party) = state.copy(issuer = party)
@JvmStatic fun OBLIGATION_DEF(issued: Issued<Currency>)
= Obligation.StateTemplate(nonEmptySetOf(Cash().legalContractReference), nonEmptySetOf(issued), Instant.parse("2020-01-01T17:00:00Z"))
@JvmStatic fun OBLIGATION(amount: Amount<Issued<Currency>>) = Obligation.State(Obligation.Lifecycle.NORMAL, MINI_CORP,
OBLIGATION_DEF(amount.token), amount.quantity, NullPublicKey)
}
infix fun <T> Obligation.State<T>.`at`(dueBefore: Instant) = JavaExperimental.at(this, dueBefore)
infix fun <T> Obligation.State<T>.`between`(parties: Pair<Party, PublicKey>) = JavaExperimental.between(this, parties)
infix fun <T> Obligation.State<T>.`owned by`(owner: PublicKey) = JavaExperimental.ownedBy(this, owner)
infix fun <T> Obligation.State<T>.`issued by`(party: Party) = JavaExperimental.issuedBy(this, party)
// Allows you to write 100.DOLLARS.OBLIGATION
val Issued<Currency>.OBLIGATION_DEF: Obligation.StateTemplate<Currency> get() = JavaExperimental.OBLIGATION_DEF(this)
val Amount<Issued<Currency>>.OBLIGATION: Obligation.State<Currency> get() = JavaExperimental.OBLIGATION(this)