From 4d3f432dd2b90a252c45270dd089754c4946637c Mon Sep 17 00:00:00 2001 From: Sean Gilligan Date: Mon, 12 Sep 2022 13:27:24 -0700 Subject: [PATCH] Add TransactionInfo.transactionType(), JavaDoc comments and tests TransactionInfo.transactionType() returns Optional Optional is the recommended type for handling a TransactionType enum. With the forthcoming "Pattern Matching for Switch" it will be possible to use it in switch statements very elegantly. Tests have been added as well as extensive JavaDoc with code examples for how to use this variable type in earlier (and admittedly more verbose) versions of Java. --- .../java/foundation/omni/tx/Transactions.java | 44 ++++++++++++++++++- .../omni/tx/TransactionTypeSpec.groovy | 40 +++++++++++++++++ .../omni/tx/TransactionTypeTest.java | 38 ++++++++++++++++ .../omni/json/pojo/OmniTransactionInfo.java | 15 +++++++ 4 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 omnij-core/src/test/groovy/foundation/omni/tx/TransactionTypeSpec.groovy create mode 100644 omnij-core/src/test/java/foundation/omni/tx/TransactionTypeTest.java diff --git a/omnij-core/src/main/java/foundation/omni/tx/Transactions.java b/omnij-core/src/main/java/foundation/omni/tx/Transactions.java index ccb9e2c1..bfbb2071 100644 --- a/omnij-core/src/main/java/foundation/omni/tx/Transactions.java +++ b/omnij-core/src/main/java/foundation/omni/tx/Transactions.java @@ -30,7 +30,49 @@ public interface OmniRefTx extends OmniTx { *

* Transaction type is an unsigned 16-bit value, and stored as a Java {@code short} that is treated as unsigned. * The {@link #value()} accessor performs the proper conversion and returns an unsigned {@code int}. - * This is a partial list, see {@code omnicore.h} for the complete list. + *

+ * With JEP 427: Pattern Matching for Switch it is possible + * to handle {@code null} as a {@code case}. So if you're using a recent version of Java (with preview enabled) you + * can handle undefined transaction types in a single {@code switch} statement/expression. If you have an integer with a transaction type code named {@code typeInt}, you can do something like: + *

 {@code
+     * Optional optionalType = TransactionType.find(typeInt);
+     * boolean isSend = switch(optionalType.orElse(null)) {
+     *     case SIMPLE_SEND, SEND_TO_OWNERS, SEND_ALL -> true;
+     *     default -> false;
+     *     case null -> false;
+     * }
+     * }
+ * The {@code default} case represents defined enum constants not handled with explicit cases and the {@code null} case + * provides a way to handle numeric codes not (yet) defined in the enum. + *

+ * For versions of Java with switch expressions but no pattern matching, this above code can be written as: + *

 {@code
+     * Optional optionalType = TransactionType.find(typeInt);
+     * boolean isSend = optionalType.map(t -> switch(t) {
+     *     case SIMPLE_SEND, SEND_TO_OWNERS, SEND_ALL -> true;
+     *     default -> false;
+     * }).orElse(false);
+     * }
+ *

+ * For even earlier versions of Java (back to Java 9), it can be written as: + *

 {@code
+     * Optional optionalType = TransactionType.find(typeInt);
+     * boolean isSend;
+     * optionalType.ifPresentOrElse(t -> switch(t) {
+     *     case SIMPLE_SEND:
+     *     case SEND_TO_OWNERS:
+     *     case SEND_ALL:
+     *       isSend = true;
+     *       break;
+     *     default:
+     *       isSend = false;
+     * }, {
+     *  isSend = false;
+     * }
+     * }
+ * For a Java 8 example see the Java unit test {@code TransactionTypeTest.java}. + *

+ * This is a partial list of transaction types, see {@code omnicore.h} for the complete list. * @see enum TransactionType in omnicore.h */ public enum TransactionType { diff --git a/omnij-core/src/test/groovy/foundation/omni/tx/TransactionTypeSpec.groovy b/omnij-core/src/test/groovy/foundation/omni/tx/TransactionTypeSpec.groovy new file mode 100644 index 00000000..d264ef53 --- /dev/null +++ b/omnij-core/src/test/groovy/foundation/omni/tx/TransactionTypeSpec.groovy @@ -0,0 +1,40 @@ +package foundation.omni.tx + +import spock.lang.Specification + +import foundation.omni.tx.Transactions.TransactionType + +/** + * Tests for {@link TransactionType} + */ +class TransactionTypeSpec extends Specification { + + void "switch test"(int code, boolean expectedResult) { + given: "An Optional-wrapped Transaction Type enum" + Optional optionalType = TransactionType.find(code) + + when: "We use a switch expression to calculate a boolean value " + boolean isSend = switch(optionalType.orElse(null)) { + case TransactionType.SIMPLE_SEND, + TransactionType.SEND_TO_OWNERS, + TransactionType.SEND_ALL -> true + case null -> false + default -> false + } + + then: "We get the correct result" + isSend == expectedResult + + where: + code | expectedResult + // Transaction codes that are "sends" + 0 | true + 3 | true + 4 | true + // Defined Transactions that are not "sends" + 20 | false + 25 | false + // Undefined transaction codes + 300 | false + } +} diff --git a/omnij-core/src/test/java/foundation/omni/tx/TransactionTypeTest.java b/omnij-core/src/test/java/foundation/omni/tx/TransactionTypeTest.java new file mode 100644 index 00000000..21a56d61 --- /dev/null +++ b/omnij-core/src/test/java/foundation/omni/tx/TransactionTypeTest.java @@ -0,0 +1,38 @@ +package foundation.omni.tx; + +import org.junit.Test; + +import java.util.Optional; + +import foundation.omni.tx.Transactions.TransactionType; +import static org.junit.Assert.assertTrue; + +/** + * Tests for {@link Transactions.TransactionType} + */ +public class TransactionTypeTest { + + // TODO: Upgrade to JUnit 5 and make this a parameterized test, like its Spock equivalent + @Test + public void switchTest() { + Optional optionalType = TransactionType.find(0); + + boolean isSend; + if (optionalType.isPresent()) { + switch (optionalType.get()) { + case SIMPLE_SEND: + case SEND_TO_OWNERS: + case SEND_ALL: + isSend = true; + break; + default: + isSend = false; + break; + } + } else { + isSend = false; + } + + assertTrue(isSend); + } +} diff --git a/omnij-jsonrpc/src/main/java/foundation/omni/json/pojo/OmniTransactionInfo.java b/omnij-jsonrpc/src/main/java/foundation/omni/json/pojo/OmniTransactionInfo.java index a3c6dccc..2dffd1c3 100644 --- a/omnij-jsonrpc/src/main/java/foundation/omni/json/pojo/OmniTransactionInfo.java +++ b/omnij-jsonrpc/src/main/java/foundation/omni/json/pojo/OmniTransactionInfo.java @@ -2,9 +2,11 @@ import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import foundation.omni.CurrencyID; import foundation.omni.OmniValue; +import foundation.omni.tx.Transactions; import org.bitcoinj.core.Address; import org.bitcoinj.core.Coin; import org.bitcoinj.core.Sha256Hash; @@ -12,6 +14,7 @@ import java.time.Instant; import java.util.HashMap; import java.util.Map; +import java.util.Optional; /** * (Mostly) Immutable representation of OmniTransaction info JSON @@ -147,6 +150,18 @@ public String getType() { return type; } + /** + * Get the transaction type as an {@code Optional} {@link Transactions.TransactionType} or + * as an {@link Optional#empty()} if it's a transaction type not (yet) included in + * {@link Transactions.TransactionType}. + * + * @return The type or {@link Optional#empty()} if it's an unknown type + */ + @JsonIgnore + public Optional transactionType() { + return Transactions.TransactionType.find(typeInt); + } + public OmniValue getAmount() { return amount; }