From 63df33457f18c6f3631d576187e0981625ecb54b Mon Sep 17 00:00:00 2001 From: bvn13 Date: Sat, 19 Feb 2022 14:02:27 +0300 Subject: [PATCH] added possibility to use multiple bytes as separator --- build.gradle | 2 +- .../me/bvn13/sewy/AbstractClientListener.java | 32 +++++++++++--- src/main/java/me/bvn13/sewy/Client.java | 10 +++-- .../me/bvn13/sewy/CommandClientListener.java | 2 +- src/main/java/me/bvn13/sewy/Server.java | 4 +- src/main/java/me/bvn13/sewy/Sewy.java | 6 +-- src/test/java/me/bvn13/sewy/ServerTest.java | 41 +++++++++++++++++ .../me/bvn13/sewy/command/ComplexCommand.java | 44 +++++++++++++++++++ 8 files changed, 125 insertions(+), 16 deletions(-) create mode 100644 src/test/java/me/bvn13/sewy/command/ComplexCommand.java diff --git a/build.gradle b/build.gradle index aa2cc0e..620c137 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { } group 'me.bvn13' -version '1.2.2' +version '1.2.4' repositories { mavenCentral() diff --git a/src/main/java/me/bvn13/sewy/AbstractClientListener.java b/src/main/java/me/bvn13/sewy/AbstractClientListener.java index 3768f49..4ef729e 100644 --- a/src/main/java/me/bvn13/sewy/AbstractClientListener.java +++ b/src/main/java/me/bvn13/sewy/AbstractClientListener.java @@ -63,6 +63,7 @@ public abstract class AbstractClientListener implements Runnable { /** * Reads line (separated with '\n') from socket + * * @return the line read from socket */ public String readLine() { @@ -78,17 +79,32 @@ public abstract class AbstractClientListener implements Runnable { /** * Reads data from socket until {@code separator} is encountered + * * @param separator byte to separate data portions * @return array of bytes read from socket */ - public byte[] readBytes(byte separator) { - final List data = new ArrayList<>(256); + public byte[] readBytes(byte[] separator) { + final List data = new ArrayList<>(2048 * 2048); + List buffer = new ArrayList<>(separator.length); + int separatorPosition = 0; try { while (socket.isConnected()) { byte[] portion = in.readNBytes(1); - if (portion == null || portion.length == 0 || portion[0] == separator) { + if (portion == null || portion.length == 0) { break; } + if (portion[0] == separator[separatorPosition]) { + if (separatorPosition == separator.length - 1) { + break; + } + separatorPosition++; + buffer.add(portion[0]); + continue; + } else { + separatorPosition = 0; + data.addAll(buffer); + buffer.clear(); + } data.add(portion[0]); } final byte[] bytes = new byte[data.size()]; @@ -96,7 +112,7 @@ public abstract class AbstractClientListener implements Runnable { for (Byte aByte : data) { bytes[i++] = aByte; } - if (log.isTraceEnabled()) log.trace("Received: " + new String(bytes)); + if (log.isTraceEnabled()) log.trace("Received {} bytes: {}", bytes.length, bytes); return bytes; } catch (IOException e) { throw new RuntimeException(e); @@ -106,11 +122,12 @@ public abstract class AbstractClientListener implements Runnable { /** * Writes line into socket ending with default separator '\n'. * Flushes after writing. - * @param bytes bytes to be sent into socket + * + * @param bytes bytes to be sent into socket * @param separator byte to separate data portions */ - public void writeBytes(byte[] bytes, byte separator) { - if (log.isTraceEnabled()) log.trace("Sending: " + new String(bytes)); + public void writeBytes(byte[] bytes, byte[] separator) { + if (log.isTraceEnabled()) log.trace("Sending {} bytes: {}", bytes.length, bytes); try { out.write(bytes); out.write(separator); @@ -123,6 +140,7 @@ public abstract class AbstractClientListener implements Runnable { /** * Writes line into socket ending with default separator '\n'. * Flushes after writing. + * * @param data data to be sent into socket */ public void writeLine(String data) { diff --git a/src/main/java/me/bvn13/sewy/Client.java b/src/main/java/me/bvn13/sewy/Client.java index 965754f..be36bfe 100644 --- a/src/main/java/me/bvn13/sewy/Client.java +++ b/src/main/java/me/bvn13/sewy/Client.java @@ -89,9 +89,13 @@ public class Client { */ public void stop() { log.debug("Stopping client"); - client.stop(); + if (client != null) { + client.stop(); + } try { - socket.close(); + if (socket != null) { + socket.close(); + } } catch (IOException e) { log.error("Failed to close socket"); } @@ -119,7 +123,7 @@ public class Client { * @param separator * @return */ - public byte[] readBytes(byte separator) { + public byte[] readBytes(byte[] separator) { return client.readBytes(separator); } diff --git a/src/main/java/me/bvn13/sewy/CommandClientListener.java b/src/main/java/me/bvn13/sewy/CommandClientListener.java index 419d025..9445094 100644 --- a/src/main/java/me/bvn13/sewy/CommandClientListener.java +++ b/src/main/java/me/bvn13/sewy/CommandClientListener.java @@ -59,7 +59,7 @@ public class CommandClientListener extends AbstractClientListener implements Abs log.error("Unexpected command received"); continue; } - log.debug("Command received: " + command); + log.debug("Command received: " + command.getClass()); if (!(command instanceof AbstractCommand)) { log.warn("Incorrect command received: " + command); continue; diff --git a/src/main/java/me/bvn13/sewy/Server.java b/src/main/java/me/bvn13/sewy/Server.java index aad977e..c60c1fb 100644 --- a/src/main/java/me/bvn13/sewy/Server.java +++ b/src/main/java/me/bvn13/sewy/Server.java @@ -99,7 +99,9 @@ public class Server { iterator.remove(); } try { - socket.close(); + if (socket != null) { + socket.close(); + } } catch (IOException e) { log.error("Failed to close socket"); } diff --git a/src/main/java/me/bvn13/sewy/Sewy.java b/src/main/java/me/bvn13/sewy/Sewy.java index 0092e34..3e9c55e 100644 --- a/src/main/java/me/bvn13/sewy/Sewy.java +++ b/src/main/java/me/bvn13/sewy/Sewy.java @@ -33,7 +33,7 @@ public final class Sewy { private static final ReentrantLock LOCK = new ReentrantLock(); private final List> registeredDataTypes = new CopyOnWriteArrayList<>(); - private byte separator = SEPARATOR; + private byte[] separator = new byte[] { SEPARATOR }; /** * Registers command in white list for further communications @@ -55,11 +55,11 @@ public final class Sewy { } } - public static byte getSeparator() { + public static byte[] getSeparator() { return getInstance().separator; } - public static void setSeparator(byte separator) { + public static void setSeparator(byte[] separator) { try { LOCK.lock(); getInstance().separator = separator; diff --git a/src/test/java/me/bvn13/sewy/ServerTest.java b/src/test/java/me/bvn13/sewy/ServerTest.java index 6443532..3ebbcd1 100644 --- a/src/test/java/me/bvn13/sewy/ServerTest.java +++ b/src/test/java/me/bvn13/sewy/ServerTest.java @@ -16,6 +16,7 @@ package me.bvn13.sewy; import me.bvn13.sewy.command.AbstractCommand; +import me.bvn13.sewy.command.ComplexCommand; import me.bvn13.sewy.command.PingCommand; import me.bvn13.sewy.command.PongCommand; import org.junit.jupiter.api.Assertions; @@ -23,6 +24,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; public class ServerTest { @@ -112,4 +114,43 @@ public class ServerTest { Assertions.assertTrue(latency.get() > 0); } + @ParameterizedTest + @ValueSource(ints = START_PORT + 7) + void wideSeparatorTest(int port) throws InterruptedException { + Sewy.register(ComplexCommand.class); + Sewy.setSeparator(new byte[] { '\n', 'e', 'n', 'd', '\n' }); + + AtomicReference check = new AtomicReference<>(); + + CommandServer server = new CommandServer("localhost", port, (socket) -> new CommandClientListener(socket) { + @Override + public AbstractCommand onCommand(AbstractCommand command) { + if (command instanceof ComplexCommand) { + check.set((ComplexCommand) command); + return null; + } + throw new IllegalArgumentException(command.toString()); + } + }); + + CommandClient client = new CommandClient("localhost", port, (socket) -> new CommandClientListener(socket) { + @Override + public AbstractCommand onCommand(AbstractCommand command) { + throw new IllegalArgumentException(command.toString()); + } + }); + + ComplexCommand command = new ComplexCommand(); + command.add(new ComplexCommand.SimpleData("a1")); + command.add(new ComplexCommand.SimpleData("b2")); + command.add(new ComplexCommand.SimpleData("finish")); + + client.send(command); + Thread.sleep(1000); + Assertions.assertNotNull(check.get()); + Assertions.assertEquals(3, check.get().getDatum().size()); + Assertions.assertEquals("a1", check.get().getDatum().get(0).getString()); + Assertions.assertEquals("b2", check.get().getDatum().get(1).getString()); + Assertions.assertEquals("finish", check.get().getDatum().get(2).getString()); + } } diff --git a/src/test/java/me/bvn13/sewy/command/ComplexCommand.java b/src/test/java/me/bvn13/sewy/command/ComplexCommand.java new file mode 100644 index 0000000..a748244 --- /dev/null +++ b/src/test/java/me/bvn13/sewy/command/ComplexCommand.java @@ -0,0 +1,44 @@ +package me.bvn13.sewy.command; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +public class ComplexCommand extends AbstractCommand { + + private List datum; + + public ComplexCommand() { + datum = new ArrayList<>(); + } + + public ComplexCommand(List datum) { + this.datum = datum; + } + + public void add(SimpleData data) { + datum.add(data); + } + + public ComplexCommand setDatum(List datum) { + this.datum = datum; + return this; + } + + public List getDatum() { + return datum; + } + + public static class SimpleData implements Serializable { + private final String string; + + public SimpleData(String string) { + this.string = string; + } + + public String getString() { + return string; + } + } + +}