From 05eee14e6977cbfe0ce5248d7d8d4f6a9418b532 Mon Sep 17 00:00:00 2001 From: nkramer44 Date: Tue, 27 Jun 2023 15:10:22 -0400 Subject: [PATCH] add handle and map methods to AffectedNode --- .../transactions/metadata/AffectedNode.java | 63 +++++ .../metadata/AffectedNodeTest.java | 215 ++++++++++++++++++ 2 files changed, 278 insertions(+) create mode 100644 xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/metadata/AffectedNodeTest.java diff --git a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/AffectedNode.java b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/AffectedNode.java index a0896038d..16de8dc46 100644 --- a/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/AffectedNode.java +++ b/xrpl4j-core/src/main/java/org/xrpl/xrpl4j/model/transactions/metadata/AffectedNode.java @@ -1,7 +1,14 @@ package org.xrpl.xrpl4j.model.transactions.metadata; import com.fasterxml.jackson.annotation.JsonProperty; +import org.xrpl.xrpl4j.model.transactions.CurrencyAmount; import org.xrpl.xrpl4j.model.transactions.Hash256; +import org.xrpl.xrpl4j.model.transactions.IssuedCurrencyAmount; +import org.xrpl.xrpl4j.model.transactions.XrpCurrencyAmount; + +import java.util.Objects; +import java.util.function.Consumer; +import java.util.function.Function; /** * Top level interface for all types of transaction metadata @@ -9,6 +16,62 @@ */ public interface AffectedNode { + /** + * Handle this {@link AffectedNode} depending on its actual polymorphic subtype. + * + * @param createdNodeConsumer A {@link Consumer} that is called if this instance is of type {@link CreatedNode}. + * @param modifiedNodeConsumer A {@link Consumer} that is called if this instance is of type {@link ModifiedNode}. + * @param deletedNodeConsumer A {@link Consumer} that is called if this instance is of type {@link DeletedNode}. + */ + default void handle( + final Consumer> createdNodeConsumer, + final Consumer> modifiedNodeConsumer, + final Consumer> deletedNodeConsumer + ) { + Objects.requireNonNull(createdNodeConsumer); + Objects.requireNonNull(modifiedNodeConsumer); + Objects.requireNonNull(deletedNodeConsumer); + + if (CreatedNode.class.isAssignableFrom(this.getClass())) { + createdNodeConsumer.accept((CreatedNode) this); + } else if (ModifiedNode.class.isAssignableFrom(this.getClass())) { + modifiedNodeConsumer.accept((ModifiedNode) this); + } else if (DeletedNode.class.isAssignableFrom(this.getClass())) { + deletedNodeConsumer.accept((DeletedNode) this); + } else { + throw new IllegalStateException(String.format("Unsupported AffectedNode type: %s", this.getClass())); + } + } + + /** + * Map this {@link AffectedNode} to an instance of {@link R}, depending on its actual polymorphic subtype. + * + * @param createdNodeMapper A {@link Function} that is called if this instance is of type {@link CreatedNode}. + * @param modifiedNodeMapper A {@link Function} that is called if this instance is of type {@link ModifiedNode}. + * @param deletedNodeMapper A {@link Function} that is called if this instance is of type {@link DeletedNode}. + * @param The type of object to return after mapping. + * @return A {@link R} that is constructed by the appropriate mapper function. + */ + default R map( + final Function, R> createdNodeMapper, + final Function, R> modifiedNodeMapper, + final Function, R> deletedNodeMapper + ) { + Objects.requireNonNull(createdNodeMapper); + Objects.requireNonNull(modifiedNodeMapper); + Objects.requireNonNull(deletedNodeMapper); + + if (CreatedNode.class.isAssignableFrom(this.getClass())) { + return createdNodeMapper.apply((CreatedNode) this); + } else if (ModifiedNode.class.isAssignableFrom(this.getClass())) { + return modifiedNodeMapper.apply((ModifiedNode) this); + } else if (DeletedNode.class.isAssignableFrom(this.getClass())) { + return deletedNodeMapper.apply((DeletedNode) this); + } else { + throw new IllegalStateException(String.format("Unsupported AffectedNode type: %s", this.getClass())); + } + } + /** * The type of ledger entry this {@link AffectedNode} affected. * diff --git a/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/metadata/AffectedNodeTest.java b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/metadata/AffectedNodeTest.java new file mode 100644 index 000000000..0ca25a89f --- /dev/null +++ b/xrpl4j-core/src/test/java/org/xrpl/xrpl4j/model/transactions/metadata/AffectedNodeTest.java @@ -0,0 +1,215 @@ +package org.xrpl.xrpl4j.model.transactions.metadata; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.Mockito.mock; + +import com.google.common.base.Strings; +import org.junit.jupiter.api.Test; +import org.xrpl.xrpl4j.model.transactions.Hash256; + +class AffectedNodeTest { + + @Test + void handleWithNullHandlers() { + AffectedNode affectedNode = ImmutableCreatedNode.builder() + .ledgerEntryType(MetaLedgerEntryType.ACCOUNT_ROOT) + .newFields(mock(MetaAccountRootObject.class)) + .ledgerIndex(Hash256.of(Strings.repeat("0", 64))) + .build(); + + assertThatThrownBy( + () -> affectedNode.handle( + null, + $ -> new Object(), + $ -> new Object() + ) + ).isInstanceOf(NullPointerException.class); + + assertThatThrownBy( + () -> affectedNode.handle( + $ -> new Object(), + null, + $ -> new Object() + ) + ).isInstanceOf(NullPointerException.class); + + assertThatThrownBy( + () -> affectedNode.handle( + $ -> new Object(), + $ -> new Object(), + null + ) + ).isInstanceOf(NullPointerException.class); + } + + @Test + void handleUnsupportedType() { + assertThatThrownBy( + () -> new AffectedNode() { + @Override + public MetaLedgerEntryType ledgerEntryType() { + return null; + } + + @Override + public Hash256 ledgerIndex() { + return null; + } + }.handle( + $ -> new Object(), + $ -> new Object(), + $ -> new Object() + ) + ).isInstanceOf(IllegalStateException.class); + } + + @Test + void handleCreatedNode() { + AffectedNode affectedNode = ImmutableCreatedNode.builder() + .ledgerEntryType(MetaLedgerEntryType.ACCOUNT_ROOT) + .newFields(mock(MetaAccountRootObject.class)) + .ledgerIndex(Hash256.of(Strings.repeat("0", 64))) + .build(); + + affectedNode.handle( + $ -> assertThat($.ledgerEntryType()).isEqualTo(MetaLedgerEntryType.ACCOUNT_ROOT), + $ -> fail(), + $ -> fail() + ); + } + + @Test + void handleModifiedNode() { + AffectedNode affectedNode = ImmutableModifiedNode.builder() + .ledgerEntryType(MetaLedgerEntryType.ACCOUNT_ROOT) + .ledgerIndex(Hash256.of(Strings.repeat("0", 64))) + .build(); + + affectedNode.handle( + $ -> fail(), + $ -> assertThat($.ledgerEntryType()).isEqualTo(MetaLedgerEntryType.ACCOUNT_ROOT), + $ -> fail() + ); + } + + @Test + void handleDeletedNode() { + AffectedNode affectedNode = ImmutableDeletedNode.builder() + .ledgerEntryType(MetaLedgerEntryType.ACCOUNT_ROOT) + .finalFields(mock(MetaAccountRootObject.class)) + .ledgerIndex(Hash256.of(Strings.repeat("0", 64))) + .build(); + + affectedNode.handle( + $ -> fail(), + $ -> fail(), + $ -> assertThat($.ledgerEntryType()).isEqualTo(MetaLedgerEntryType.ACCOUNT_ROOT) + ); + } + + @Test + void mapWithNullHandlers() { + AffectedNode affectedNode = ImmutableCreatedNode.builder() + .ledgerEntryType(MetaLedgerEntryType.ACCOUNT_ROOT) + .newFields(mock(MetaAccountRootObject.class)) + .ledgerIndex(Hash256.of(Strings.repeat("0", 64))) + .build(); + + assertThatThrownBy( + () -> affectedNode.map( + null, + $ -> new Object(), + $ -> new Object() + ) + ).isInstanceOf(NullPointerException.class); + + assertThatThrownBy( + () -> affectedNode.map( + $ -> new Object(), + null, + $ -> new Object() + ) + ).isInstanceOf(NullPointerException.class); + + assertThatThrownBy( + () -> affectedNode.map( + $ -> new Object(), + $ -> new Object(), + null + ) + ).isInstanceOf(NullPointerException.class); + } + + @Test + void mapUnsupportedType() { + assertThatThrownBy( + () -> new AffectedNode() { + @Override + public MetaLedgerEntryType ledgerEntryType() { + return null; + } + + @Override + public Hash256 ledgerIndex() { + return null; + } + }.map( + $ -> new Object(), + $ -> new Object(), + $ -> new Object() + ) + ).isInstanceOf(IllegalStateException.class); + } + + @Test + void mapCreatedNode() { + AffectedNode affectedNode = ImmutableCreatedNode.builder() + .ledgerEntryType(MetaLedgerEntryType.ACCOUNT_ROOT) + .newFields(mock(MetaAccountRootObject.class)) + .ledgerIndex(Hash256.of(Strings.repeat("0", 64))) + .build(); + + String mapped = affectedNode.map( + $ -> "success", + $ -> "fail", + $ -> "fail" + ); + + assertThat(mapped).isEqualTo("success"); + } + + @Test + void mapModifiedNode() { + AffectedNode affectedNode = ImmutableModifiedNode.builder() + .ledgerEntryType(MetaLedgerEntryType.ACCOUNT_ROOT) + .ledgerIndex(Hash256.of(Strings.repeat("0", 64))) + .build(); + + String mapped = affectedNode.map( + $ -> "fail", + $ -> "success", + $ -> "fail" + ); + + assertThat(mapped).isEqualTo("success"); + } + + @Test + void mapDeletedNode() { + AffectedNode affectedNode = ImmutableDeletedNode.builder() + .ledgerEntryType(MetaLedgerEntryType.ACCOUNT_ROOT) + .finalFields(mock(MetaAccountRootObject.class)) + .ledgerIndex(Hash256.of(Strings.repeat("0", 64))) + .build(); + + String mapped = affectedNode.map( + $ -> "fail", + $ -> "fail", + $ -> "success" + ); + + assertThat(mapped).isEqualTo("success"); + } +} \ No newline at end of file