diff --git a/src/main/java/dev.toad/ClientObserveStream.java b/src/main/java/dev.toad/ClientObserveStream.java index 7eee837..d816baf 100644 --- a/src/main/java/dev.toad/ClientObserveStream.java +++ b/src/main/java/dev.toad/ClientObserveStream.java @@ -15,13 +15,13 @@ public class ClientObserveStream { public ClientObserveStream(Client client, Message message) { this.state = State.OPEN; this.client = client; - this.message = message.modify().option(Observe.REGISTER).build(); + this.message = message.buildCopy().option(Observe.REGISTER).build(); this.buffered = Optional.of(client.send(this.message)); } public CompletableFuture close() { return this.client.send( - this.message.modify().option(Observe.DEREGISTER).unsetId().build() + this.message.buildCopy().option(Observe.DEREGISTER).unsetId().build() ) .thenAccept(m -> { this.state = State.CLOSED; @@ -29,7 +29,7 @@ public class ClientObserveStream { } public CompletableFuture next() { - if (this.state == State.CLOSED) { + if (State.eq.test(State.CLOSED, this.state)) { throw new RuntimeException( "ClientObserveStream.next() invoked after .close()" ); @@ -47,6 +47,8 @@ public class ClientObserveStream { public static final class State { + public static final Eq eq = Eq.int_.contramap((State s) -> s.state); + public static final State OPEN = new State(0); public static final State CLOSED = new State(1); @@ -56,14 +58,10 @@ public class ClientObserveStream { this.state = state; } - public boolean equals(State other) { - return this.state == other.state; - } - @Override public boolean equals(Object other) { return switch (other) { - case State s -> this.equals(s); + case State s -> State.eq.test(this, s); default -> false; }; } diff --git a/src/main/java/dev.toad/Eq.java b/src/main/java/dev.toad/Eq.java new file mode 100644 index 0000000..ebb26c9 --- /dev/null +++ b/src/main/java/dev.toad/Eq.java @@ -0,0 +1,87 @@ +package dev.toad; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.BiFunction; +import java.util.function.Function; + +public final class Eq { + + public static final Eq short_ = new Eq<>((a, b) -> a == b); + public static final Eq int_ = new Eq<>((a, b) -> a == b); + public static final Eq long_ = new Eq<>((a, b) -> a == b); + public static final Eq string = new Eq<>((a, b) -> + (a != null && b != null && a.equals(b)) || (a == null && b == null) + ); + public static final Eq byteArray = new Eq<>((a, b) -> + Arrays.equals(a, b) + ); + + public static final Eq socketAddress = new Eq<>( + (a, b) -> + a.equals(b) + ); + + public static Eq all(List> eqs) { + return new Eq<>((a, b) -> eqs.stream().allMatch(eq -> eq.test(a, b))); + } + + public static Eq> optional(Eq eq) { + return new Eq<>((a, b) -> { + if (!a.isEmpty() && !b.isEmpty()) { + return eq.test(a.get(), b.get()); + } else { + return a.isEmpty() && b.isEmpty(); + } + }); + } + + public static Eq> map(Eq eq) { + return new Eq<>((a, b) -> { + if (a.entrySet().size() != b.entrySet().size()) { + return false; + } + + for (var ent : a.entrySet()) { + var val = b.get(ent.getKey()); + if (val == null || !eq.test(val, ent.getValue())) { + return false; + } + } + + return true; + }); + } + + public static Eq> list(Eq eq) { + return new Eq<>((a, b) -> { + if (a.size() != b.size()) { + return false; + } + + for (int i = 0; i < a.size(); i++) { + if (!eq.test(a.get(i), b.get(i))) { + return false; + } + } + + return true; + }); + } + + final BiFunction eq; + + public Eq(BiFunction eq) { + this.eq = eq; + } + + public boolean test(T a, T b) { + return this.eq.apply(a, b); + } + + public Eq contramap(Function from) { + return new Eq<>((a, b) -> this.test(from.apply(a), from.apply(b))); + } +} diff --git a/src/main/java/dev.toad/Server.java b/src/main/java/dev.toad/Server.java index 3916281..13a8389 100644 --- a/src/main/java/dev.toad/Server.java +++ b/src/main/java/dev.toad/Server.java @@ -110,9 +110,7 @@ public final class Server { CompletableFuture.completedFuture(Optional.empty()); public static final Function notFound = m -> { - return Middleware.respond( - m.modify().unsetId().code(Code.NOT_FOUND).build() - ); + return Middleware.respond(m.buildResponse().code(Code.NOT_FOUND).build()); }; public static final BiFunction debugExceptionHandler = @@ -126,8 +124,7 @@ public final class Server { ); var rep = m - .modify() - .unsetId() + .buildResponse() .code(Code.INTERNAL_SERVER_ERROR) .payload(Payload.text(e.toString())) .build(); @@ -144,7 +141,7 @@ public final class Server { String.format("while handling %s", m.toDebugString()), e ); - var rep = m.modify().unsetId().code(Code.INTERNAL_SERVER_ERROR).build(); + var rep = m.buildResponse().code(Code.INTERNAL_SERVER_ERROR).build(); return Middleware.respond(rep); }; @@ -358,8 +355,11 @@ public final class Server { public Builder put(String path, Function f) { return this.when( m -> - m.code().equals(Code.PUT) && - m.getPath().map(p -> p.matches(path)).orElse(path == ""), + Code.eq.test(m.code(), Code.PUT) && + m + .getPath() + .map(p -> p.matches(path)) + .orElse(path == null || path.isEmpty()), f ); } @@ -367,8 +367,11 @@ public final class Server { public Builder post(String path, Function f) { return this.when( m -> - m.code().equals(Code.POST) && - m.getPath().map(p -> p.matches(path)).orElse(path == ""), + Code.eq.test(m.code(), Code.POST) && + m + .getPath() + .map(p -> p.matches(path)) + .orElse(path == null || path.isEmpty()), f ); } @@ -376,8 +379,11 @@ public final class Server { public Builder delete(String path, Function f) { return this.when( m -> - m.code().equals(Code.DELETE) && - m.getPath().map(p -> p.matches(path)).orElse(path == ""), + Code.eq.test(m.code(), Code.DELETE) && + m + .getPath() + .map(p -> p.matches(path)) + .orElse(path == null || path.isEmpty()), f ); } @@ -385,7 +391,7 @@ public final class Server { public Builder get(String path, Function f) { return this.when( m -> - m.code().equals(Code.GET) && + Code.eq.test(m.code(), Code.GET) && m .getPath() .map(p -> p.matches(path)) diff --git a/src/main/java/dev.toad/Toad.java b/src/main/java/dev.toad/Toad.java index ee31d56..ea58e8b 100644 --- a/src/main/java/dev.toad/Toad.java +++ b/src/main/java/dev.toad/Toad.java @@ -208,7 +208,8 @@ public final class Toad implements AutoCloseable { @Override public boolean equals(Object other) { return switch (other) { - case Config o -> o.concurrency == this.concurrency && o.msg == this.msg; + case Config o -> o.concurrency.equals(this.concurrency) && + o.msg.equals(this.msg); default -> false; }; } @@ -258,11 +259,11 @@ public final class Toad implements AutoCloseable { @Override public boolean equals(Object other) { return switch (other) { - case Msg o -> o.tokenSeed == this.tokenSeed && - o.probingRateBytesPerSecond == this.probingRateBytesPerSecond && - o.multicastResponseLeisure == this.multicastResponseLeisure && - o.con == this.con && - o.non == this.non; + case Msg o -> o.tokenSeed.equals(this.tokenSeed) && + o.probingRateBytesPerSecond.equals(this.probingRateBytesPerSecond) && + o.multicastResponseLeisure.equals(this.multicastResponseLeisure) && + o.con.equals(this.con) && + o.non.equals(this.non); default -> false; }; } @@ -362,9 +363,11 @@ public final class Toad implements AutoCloseable { @Override public boolean equals(Object other) { return switch (other) { - case Con o -> this.ackedRetryStrategy == o.ackedRetryStrategy && - this.unackedRetryStrategy == o.unackedRetryStrategy && - this.maxAttempts == o.maxAttempts; + case Con o -> this.ackedRetryStrategy.equals( + o.ackedRetryStrategy + ) && + this.unackedRetryStrategy.equals(o.unackedRetryStrategy) && + this.maxAttempts.equals(o.maxAttempts); default -> false; }; } @@ -435,8 +438,8 @@ public final class Toad implements AutoCloseable { @Override public boolean equals(Object other) { return switch (other) { - case Non o -> this.retryStrategy == o.retryStrategy && - this.maxAttempts == o.maxAttempts; + case Non o -> this.retryStrategy.equals(o.retryStrategy) && + this.maxAttempts.equals(o.maxAttempts); default -> false; }; } diff --git a/src/main/java/dev.toad/msg/Code.java b/src/main/java/dev.toad/msg/Code.java index 4ff4c8a..49c0898 100644 --- a/src/main/java/dev.toad/msg/Code.java +++ b/src/main/java/dev.toad/msg/Code.java @@ -1,13 +1,22 @@ package dev.toad.msg; import dev.toad.Debug; +import dev.toad.Eq; import dev.toad.ffi.u8; +import java.util.List; public final class Code implements Debug { final u8 clazz; final u8 detail; + public static Eq eq = Eq.all( + List.of( + Eq.short_.contramap(Code::codeClass), + Eq.short_.contramap(Code::codeDetail) + ) + ); + public static final Code EMPTY = new Code(0, 0); public static final Code GET = new Code(0, 1); @@ -112,17 +121,10 @@ public final class Code implements Debug { return this.toString(); } - public boolean equals(Code other) { - return ( - this.codeClass() == other.codeClass() && - this.codeDetail() == other.codeDetail() - ); - } - @Override public boolean equals(Object other) { return switch (other) { - case Code c -> c.equals(this); + case Code c -> Code.eq.test(c, this); default -> false; }; } diff --git a/src/main/java/dev.toad/msg/Id.java b/src/main/java/dev.toad/msg/Id.java index c8c6d9d..130480c 100644 --- a/src/main/java/dev.toad/msg/Id.java +++ b/src/main/java/dev.toad/msg/Id.java @@ -1,12 +1,15 @@ package dev.toad.msg; import dev.toad.Debug; +import dev.toad.Eq; import dev.toad.ffi.u16; public final class Id implements Debug { public static native Id defaultId(); + public static final Eq eq = Eq.int_.contramap(Id::toInt); + final u16 id; public Id(int id) { @@ -17,6 +20,14 @@ public final class Id implements Debug { return this.id.intValue(); } + @Override + public boolean equals(Object other) { + return switch (other) { + case Id i -> Id.eq.test(this, i); + default -> false; + }; + } + @Override public String toDebugString() { return String.format("Id(%d)", this.toInt()); diff --git a/src/main/java/dev.toad/msg/Message.java b/src/main/java/dev.toad/msg/Message.java index 0ae8737..2ed0d0e 100644 --- a/src/main/java/dev.toad/msg/Message.java +++ b/src/main/java/dev.toad/msg/Message.java @@ -1,6 +1,7 @@ package dev.toad.msg; import dev.toad.Debug; +import dev.toad.Eq; import dev.toad.msg.option.Accept; import dev.toad.msg.option.ContentFormat; import dev.toad.msg.option.Host; @@ -35,8 +36,12 @@ public interface Message extends Debug { public byte[] toBytes(); - public default dev.toad.msg.build.Message modify() { - return dev.toad.msg.build.Message.from(this); + public default dev.toad.msg.build.Message buildCopy() { + return dev.toad.msg.build.Message.copyOf(this); + } + + public default dev.toad.msg.build.MessageNeeds.Code buildResponse() { + return dev.toad.msg.build.Message.respondTo(this); } public default Optional