Skip to content

Commit

Permalink
add handle and map methods to AffectedNode
Browse files Browse the repository at this point in the history
  • Loading branch information
nkramer44 committed Jun 27, 2023
1 parent 79e179e commit 05eee14
Show file tree
Hide file tree
Showing 2 changed files with 278 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -1,14 +1,77 @@
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
* <a href="https://xrpl.org/transaction-metadata.html#affectednodes">AffectedNodes</a>.
*/
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 <T extends MetaLedgerObject> void handle(
final Consumer<CreatedNode<T>> createdNodeConsumer,
final Consumer<ModifiedNode<T>> modifiedNodeConsumer,
final Consumer<DeletedNode<T>> deletedNodeConsumer
) {
Objects.requireNonNull(createdNodeConsumer);
Objects.requireNonNull(modifiedNodeConsumer);
Objects.requireNonNull(deletedNodeConsumer);

if (CreatedNode.class.isAssignableFrom(this.getClass())) {
createdNodeConsumer.accept((CreatedNode<T>) this);
} else if (ModifiedNode.class.isAssignableFrom(this.getClass())) {
modifiedNodeConsumer.accept((ModifiedNode<T>) this);
} else if (DeletedNode.class.isAssignableFrom(this.getClass())) {
deletedNodeConsumer.accept((DeletedNode<T>) 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 <R> The type of object to return after mapping.
* @return A {@link R} that is constructed by the appropriate mapper function.
*/
default <T extends MetaLedgerObject, R> R map(
final Function<CreatedNode<T>, R> createdNodeMapper,
final Function<ModifiedNode<T>, R> modifiedNodeMapper,
final Function<DeletedNode<T>, R> deletedNodeMapper
) {
Objects.requireNonNull(createdNodeMapper);
Objects.requireNonNull(modifiedNodeMapper);
Objects.requireNonNull(deletedNodeMapper);

if (CreatedNode.class.isAssignableFrom(this.getClass())) {
return createdNodeMapper.apply((CreatedNode<T>) this);
} else if (ModifiedNode.class.isAssignableFrom(this.getClass())) {
return modifiedNodeMapper.apply((ModifiedNode<T>) this);
} else if (DeletedNode.class.isAssignableFrom(this.getClass())) {
return deletedNodeMapper.apply((DeletedNode<T>) this);
} else {
throw new IllegalStateException(String.format("Unsupported AffectedNode type: %s", this.getClass()));
}
}

/**
* The type of ledger entry this {@link AffectedNode} affected.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -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");
}
}

0 comments on commit 05eee14

Please sign in to comment.