added possibility to use multiple bytes as separator

master
bvn13 2022-02-19 14:02:27 +03:00
parent 1dcf9dec7a
commit 63df33457f
8 changed files with 125 additions and 16 deletions

View File

@ -5,7 +5,7 @@ plugins {
}
group 'me.bvn13'
version '1.2.2'
version '1.2.4'
repositories {
mavenCentral()

View File

@ -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<Byte> data = new ArrayList<>(256);
public byte[] readBytes(byte[] separator) {
final List<Byte> data = new ArrayList<>(2048 * 2048);
List<Byte> 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) {

View File

@ -89,9 +89,13 @@ public class Client<T extends AbstractClientListener> {
*/
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<T extends AbstractClientListener> {
* @param separator
* @return
*/
public byte[] readBytes(byte separator) {
public byte[] readBytes(byte[] separator) {
return client.readBytes(separator);
}

View File

@ -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;

View File

@ -99,7 +99,9 @@ public class Server<T extends AbstractClientListener> {
iterator.remove();
}
try {
socket.close();
if (socket != null) {
socket.close();
}
} catch (IOException e) {
log.error("Failed to close socket");
}

View File

@ -33,7 +33,7 @@ public final class Sewy {
private static final ReentrantLock LOCK = new ReentrantLock();
private final List<Class<?>> 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;

View File

@ -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<ComplexCommand> 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());
}
}

View File

@ -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<SimpleData> datum;
public ComplexCommand() {
datum = new ArrayList<>();
}
public ComplexCommand(List<SimpleData> datum) {
this.datum = datum;
}
public void add(SimpleData data) {
datum.add(data);
}
public ComplexCommand setDatum(List<SimpleData> datum) {
this.datum = datum;
return this;
}
public List<SimpleData> 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;
}
}
}