refactoring, describing with JavaDocs

master
bvn13 2022-01-28 23:45:02 +03:00
parent ac945faef8
commit 380dd5f8b0
17 changed files with 712 additions and 124 deletions

View File

@ -15,6 +15,12 @@ dependencies {
// https://mvnrepository.com/artifact/org.slf4j/slf4j-api
implementation group: 'org.slf4j', name: 'slf4j-api', version: '1.7.35'
// https://mvnrepository.com/artifact/commons-io/commons-io
implementation group: 'commons-io', name: 'commons-io', version: '2.11.0'
// https://mvnrepository.com/artifact/org.apache.commons/commons-lang3
implementation group: 'org.apache.commons', name: 'commons-lang3', version: '3.12.0'
testImplementation 'ch.qos.logback:logback-classic:1.2.10'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 Vyacheslav Boyko
Copyright 2022 Vyacheslav Boyko
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -18,49 +18,114 @@ package me.bvn13.sewy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import static me.bvn13.sewy.Sewy.SEPARATOR;
/**
* TCP Client listener.
* This class provides methods to manage communications between {@link Server} and {@link Client}.
* Inherit this class to implement your own communication type.
*/
public abstract class AbstractClientListener implements Runnable {
protected static final Logger log = LoggerFactory.getLogger(AbstractClientListener.class);
protected final Logger log = LoggerFactory.getLogger(this.getClass());
protected final Socket socket;
protected PrintWriter out;
protected BufferedReader in;
protected OutputStream out;
protected InputStream in;
protected AbstractClientListener(Socket socket) {
log.debug("Initializing client listener");
this.socket = socket;
try {
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
out = new PrintWriter(socket.getOutputStream(), true);
this.in = socket.getInputStream();
log.debug("BufferedReader successfully created");
log.debug("PrintWriter successfully created");
out = socket.getOutputStream();
log.debug("OutputStream successfully created");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Thread runner.
* Override it according the needs
*/
@Override
public abstract void run();
/**
* Reads line (separated with '\n') from socket
* @return the line read from socket
*/
public String readLine() {
final byte[] bytes = readBytes(SEPARATOR);
final StringBuilder sb = new StringBuilder();
for (byte aByte : bytes) {
sb.append((char) aByte);
}
final String string = sb.toString();
if (log.isTraceEnabled()) log.trace("Received: " + string);
return string;
}
/**
* 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);
try {
return in.readLine();
while (socket.isConnected()) {
byte[] portion = in.readNBytes(1);
if (portion == null || portion.length == 0 || portion[0] == separator) {
break;
}
data.add(portion[0]);
}
final byte[] bytes = new byte[data.size()];
int i = 0;
for (Byte aByte : data) {
bytes[i++] = aByte;
}
if (log.isTraceEnabled()) log.trace("Received: " + new String(bytes));
return bytes;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 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) {
out.println(data);
if (log.isTraceEnabled()) log.trace("Sending: " + data);
try {
out.write(data.getBytes());
out.write(SEPARATOR);
out.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
void stop() {
out.close();
/**
* Stops client listener gracefully
*/
public void stop() {
log.debug("Stopping");
try {
out.close();
in.close();
} catch (IOException e) {
log.warn("Unable to close IN client buffer");

View File

@ -0,0 +1,22 @@
package me.bvn13.sewy;
import me.bvn13.sewy.command.AbstractCommand;
/**
* Interface to describe command executor.
* Every command encountered by {@link CommandClientListener}
* will be sent into such executor provided
* in {@link Client#Client(java.lang.String, int, java.lang.Class)}
* or {@link Server#Server(java.lang.String, int, java.lang.Class)}
* while instantiating Client and Server
* @param <T>
*/
@FunctionalInterface
public interface AbstractCommandExecutor<T extends AbstractCommand> {
/**
* Command handler
* @param command incoming command
* @return response on incoming command is another command for corresponding side
*/
AbstractCommand onCommand(T command);
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 Vyacheslav Boyko
Copyright 2022 Vyacheslav Boyko
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -18,81 +18,111 @@ package me.bvn13.sewy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.Function;
import static java.lang.String.format;
import static me.bvn13.sewy.ClientListenerFactory.createClientListenerConstructor;
public class Client {
private static final Logger log = LoggerFactory.getLogger(Client.class);
/**
* TCP Client.
* Create the instance of this class to connect to {@link Server}
*/
public class Client<T extends AbstractClientListener> {
protected final Logger log = LoggerFactory.getLogger(this.getClass());
private final ExecutorService executor = Executors.newCachedThreadPool();
protected T client;
private Socket socket;
private PrintWriter out;
private BufferedReader in;
protected Socket socket;
/**
* Default constructor is to delay connecting to server
*/
public Client() {
}
public Client(String host, int port) {
connect(host, port);
/**
* Connects to server immediately
* @param host host to connect to
* @param port port to be used while connecting
* @param clientListenerClass client listener class describing protocol of communications
*/
public Client(String host, int port, Class clientListenerClass) {
this(host, port, createClientListenerConstructor(clientListenerClass));
}
public void stop() {
out.close();
try {
in.close();
} catch (IOException e) {
log.warn("Unable to close IN client buffer");
}
try {
socket.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
/**
* Connects to server immediately
* @param host host to connect to
* @param port port to be used while connecting
* @param clientListenerConstructor to provide constructor for client listener (see {@link me.bvn13.sewy.Client#Client(java.lang.String, int, java.lang.Class)})
*/
public Client(String host, int port, Function<Socket, T> clientListenerConstructor) {
log.debug("Creating client");
connect(host, port, clientListenerConstructor);
}
public void connect(String host, int port) {
/**
* Connects to {@link Server}
* @param host host to connect to
* @param port port to be used while connecting
* @param clientListenerConstructor to provide constructor for client listener (see {@link me.bvn13.sewy.Client#Client(java.lang.String, int, java.lang.Class)})
*/
public void connect(String host, int port, Function<Socket, T> clientListenerConstructor) {
try {
log.debug(format("Connecting to %s:%d", host, port));
socket = new Socket(host, port);
client = clientListenerConstructor.apply(socket);
executor.execute(client);
} catch (IOException e) {
log.error(format("Error while conversation with %s:%d", host, port), e);
stop();
return;
}
try {
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
} catch (IOException e) {
throw new RuntimeException(e);
}
try {
out = new PrintWriter(socket.getOutputStream(), true);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Stops client gracefully
*/
public void stop() {
log.debug("Stopping client");
client.stop();
executor.shutdown();
}
/**
* To check whether socket is online
* @return
*/
public boolean isConnected() {
return socket != null && socket.isConnected();
}
/**
* Reads one line from socket
* @return the line read from socket
*/
public String readLine() {
try {
return in.readLine();
} catch (IOException e) {
throw new RuntimeException(e);
}
return client.readLine();
}
/**
* Reads data from socket until {@code separator} is encountered
* @param separator
* @return
*/
public byte[] readBytes(byte separator) {
return client.readBytes(separator);
}
/**
* Writes line into socket ending with default separator '\n'.
* @param data data to be sent into socket
*/
public void writeLine(String data) {
out.println(data);
client.writeLine(data);
}
boolean isConnected() {
return socket.isConnected();
}
}

View File

@ -0,0 +1,52 @@
/*
Copyright 2022 Vyacheslav Boyko
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package me.bvn13.sewy;
import java.lang.reflect.Constructor;
import java.net.Socket;
import java.util.function.Function;
/**
* Factory constructing client listeners
*/
class ClientListenerFactory {
/**
* Creates client listener constructor
* @param clientListenerClass class to be used as client listener
* @param <T> generic type
* @return lambda method to create client listener
*/
@SuppressWarnings("unchecked")
static <T extends AbstractClientListener> Function<Socket, T> createClientListenerConstructor(Class clientListenerClass) {
if (clientListenerClass.getGenericSuperclass() == null
/*|| !clientListenerClass.getGenericSuperclass().equals(T.class)*/) {
throw new IllegalArgumentException("Wrong client listener of type: "+clientListenerClass.getName());
}
return (client) -> {
try {
final Constructor<CommandClientListener> constructor = clientListenerClass.getDeclaredConstructor(Socket.class);
constructor.setAccessible(true);
return (T) constructor.newInstance(client);
} catch (Exception e) {
throw new RuntimeException(e);
}
};
}
}

View File

@ -0,0 +1,81 @@
/*
Copyright 2022 Vyacheslav Boyko
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package me.bvn13.sewy;
import me.bvn13.sewy.command.AbstractCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.Socket;
import java.util.function.Function;
import static java.lang.String.format;
import static me.bvn13.sewy.ClientListenerFactory.createClientListenerConstructor;
/**
* TCP Client.
* Works with command protocol.
* Create the instance of this class to connect to {@link Server}
*/
public class CommandClient extends Client<CommandClientListener> {
private final Logger log = LoggerFactory.getLogger(this.getClass());
/**
* Default constructor is to delay connecting to Server
*/
public CommandClient() {
}
/**
* Starts to connect to server immediately
* @param host host to connect to
* @param port port to be used while connecting
*/
public CommandClient(String host, int port) {
this(host, port, CommandClientListener.class);
}
/**
* Starts to connect to server immediately
* @param host host to connect to
* @param port port to be used while connecting
* @param clientListenerClass client listener class describing protocol of communications
*/
public CommandClient(String host, int port, Class clientListenerClass) {
this(host, port, createClientListenerConstructor(clientListenerClass));
}
/**
* Connects to server immediately
* @param host host to connect to
* @param port port to be used while connecting
* @param clientListenerConstructor to provide constructor for client listener (see {@link me.bvn13.sewy.Client#Client(java.lang.String, int, java.lang.Class)})
*/
public CommandClient(String host, int port, Function<Socket, CommandClientListener> clientListenerConstructor) {
log.debug("Creating client");
connect(host, port, clientListenerConstructor);
}
/**
* Sends command to server
* @param command command to be sent
* @param <T> generic type
*/
public <T extends AbstractCommand> void send(T command) {
log.debug("Start to send command: " + command);
client.send(command);
}
}

View File

@ -0,0 +1,102 @@
/*
Copyright 2022 Vyacheslav Boyko
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package me.bvn13.sewy;
import me.bvn13.sewy.command.AbstractCommand;
import org.apache.commons.lang3.SerializationUtils;
import java.io.IOException;
import java.io.Serializable;
import java.net.Socket;
import static java.lang.String.format;
import static me.bvn13.sewy.Sewy.SEPARATOR;
/**
* Client listener describing protocol-oriented communication
*/
public class CommandClientListener extends AbstractClientListener implements AbstractCommandExecutor {
public CommandClientListener(Socket socket) {
super(socket);
}
/**
* Thread runner
*/
@Override
public void run() {
for (Thread.yield(); !socket.isConnected() && !socket.isClosed(); Thread.yield()) {
}
while (socket.isConnected()) {
Thread.yield();
byte[] line = readBytes(SEPARATOR);
if (line == null || line.length == 0) {
continue;
}
final Object command = SerializationUtils.deserialize(line);
if (command == null) {
continue;
}
if (!Sewy.getRegisteredDataTypes().contains(command.getClass())) {
log.error("Unexpected command received");
continue;
}
log.debug("Command received: " + command);
if (!(command instanceof AbstractCommand)) {
log.warn("Incorrect command received: " + command);
continue;
}
final Serializable response = onCommand((AbstractCommand) command);
log.debug(format("Response for %s is: %s", command, response));
try {
out.write(SerializationUtils.serialize(response));
out.write(SEPARATOR);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/**
* Method to receive the data command-by-command incoming from clients
* You need to override it
*
* @param command serialized command to be checked by using
* <p>{@code instanceof ConcreteCommandClass}</p>
* @return server answer on client command
*/
public AbstractCommand onCommand(AbstractCommand command) {
return null;
}
/**
* Sends command to opposite side
* @param command command to be sent
* @param <T> generic type
*/
public <T extends AbstractCommand> void send(T command) {
log.debug("Start to send command: " + command);
try {
out.write(SerializationUtils.serialize(command));
out.write(SEPARATOR);
out.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -1,14 +0,0 @@
package me.bvn13.sewy;
import java.net.Socket;
public class DefaultClientListener extends AbstractClientListener {
public DefaultClientListener(Socket socket) {
super(socket);
}
@Override
public void run() {
}
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 Vyacheslav Boyko
Copyright 2022 Vyacheslav Boyko
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -19,7 +19,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
@ -32,28 +31,47 @@ import java.util.concurrent.Executors;
import java.util.function.Function;
import static java.lang.String.format;
import static me.bvn13.sewy.ClientListenerFactory.createClientListenerConstructor;
/**
* TCP Server.
* Create the instance of this class to connect to {@link Client}
*/
public class Server {
private static final Logger log = LoggerFactory.getLogger(Server.class);
private final Logger log = LoggerFactory.getLogger(this.getClass());
private final ExecutorService executor = Executors.newCachedThreadPool();
final List<AbstractClientListener> clients = Collections.synchronizedList(new ArrayList<>());
private final List<AbstractClientListener> clients = Collections.synchronizedList(new ArrayList<>());
private ServerSocket socket;
/**
* @param host host to bind in order to start listen to clients
* @param port port to start listen to
*/
public Server(String host, int port) {
this(host, port, DefaultClientListener.class);
this(host, port, CommandClientListener.class);
}
@SuppressWarnings("unchecked")
/**
* @param host host to bind in order to start listen to clients
* @param port port to start listen to
* @param clientListenerClass client listen class to be used for communication
*/
public Server(String host, int port, Class clientListenerClass) {
this(host, port, defaultClientListenerConstructor(clientListenerClass));
this(host, port, createClientListenerConstructor(clientListenerClass));
}
/**
*
* @param host host to bind in order to start listen to clients
* @param port port to start listen to
* @param clientListenerConstructor to provide constructor for client listener (see {@link me.bvn13.sewy.Server#Server(java.lang.String, int, java.lang.Class)})
*/
@SuppressWarnings("unchecked")
public Server(String host, int port, Function<Socket, AbstractClientListener> clientListenerConstructor) {
public Server(String host, int port, Function<Socket, CommandClientListener> clientListenerConstructor) {
log.debug("Starting server");
executor.execute(() -> {
try (final ServerSocket server = new ServerSocket(port, 0, InetAddress.getByName(host))) {
@ -73,7 +91,12 @@ public class Server {
}
/**
* Stops server gracefully
* Disconnects from every client
*/
public void stop() {
log.debug("Stopping server");
final Iterator<AbstractClientListener> iterator = clients.iterator();
while (iterator.hasNext()) {
final AbstractClientListener client = iterator.next();
@ -83,28 +106,12 @@ public class Server {
executor.shutdown();
}
boolean isListening() {
/**
* To check whether the server is ready for new connections
* @return
*/
public boolean isListening() {
return socket != null && socket.isBound();
}
@SuppressWarnings("unchecked")
private static Function<Socket, AbstractClientListener> defaultClientListenerConstructor(Class clientListenerClass) {
if (clientListenerClass.getGenericSuperclass() == null
|| !clientListenerClass.getGenericSuperclass().equals(AbstractClientListener.class)) {
throw new IllegalArgumentException("Wrong client listener of type: "+clientListenerClass.getName());
}
return (client) -> {
try {
final Constructor<AbstractClientListener> constructor = clientListenerClass.getDeclaredConstructor(Socket.class);
constructor.setAccessible(true);
return constructor.newInstance(client);
} catch (Exception e) {
throw new RuntimeException(e);
}
};
}
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2020 Vyacheslav Boyko
Copyright 2022 Vyacheslav Boyko
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@ -15,23 +15,56 @@
*/
package me.bvn13.sewy;
import java.io.Serializable;
import me.bvn13.sewy.command.AbstractCommand;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantLock;
/**
* Supporting class providing protocol restrictions
*/
public final class Sewy {
static final byte SEPARATOR = '\n';
private static Sewy INSTANCE;
private static final ReentrantLock LOCK = new ReentrantLock();
private final List<Serializable> registeredDataTypes = new CopyOnWriteArrayList<>();
private final List<Class<?>> registeredDataTypes = new CopyOnWriteArrayList<>();
public static void register(Serializable clazz) {
/**
* Registers command in white list for further communications
* @param clazz command class
* @param <T> generic type
*/
public static <T extends AbstractCommand> void register(Class<T> clazz) {
getInstance().registeredDataTypes.add(clazz);
}
private Sewy() {}
/**
* Registers commands in white list for further communications
* @param classes array of command classes
* @param <T> generic type
*/
public static <T extends AbstractCommand> void register(Class<T>[] classes) {
for (Class<T> clazz: classes) {
register(clazz);
}
}
private Sewy() {
}
@SuppressWarnings("unchecked")
static List<Class<AbstractCommand>> getRegisteredDataTypes() {
final List<Class<AbstractCommand>> dataTypes = new ArrayList<>();
for (Class<?> registeredDataType : INSTANCE.registeredDataTypes) {
dataTypes.add((Class<AbstractCommand>) registeredDataType);
}
return dataTypes;
}
private static Sewy getInstance() {
try {

View File

@ -0,0 +1,36 @@
/*
Copyright 2022 Vyacheslav Boyko
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package me.bvn13.sewy;
import java.net.Socket;
/**
* Simple client listener is to disable automatic receiving and sending data.
* Override {@link SimpleClientListener#run()} to implement business logic of communicating
*/
public class SimpleClientListener extends AbstractClientListener {
protected SimpleClientListener(Socket socket) {
super(socket);
}
/**
* Thread runner
*/
@Override
public void run() {
}
}

View File

@ -0,0 +1,25 @@
/*
Copyright 2022 Vyacheslav Boyko
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package me.bvn13.sewy.command;
import java.io.Serializable;
/**
* The very parent class every command should be inherited from
*/
public class AbstractCommand implements Serializable {
}

View File

@ -0,0 +1,22 @@
package me.bvn13.sewy.command;
import java.time.Instant;
public class PingCommand extends AbstractCommand {
private final long time;
public PingCommand() {
this.time = Instant.now().toEpochMilli();
}
public long getTime() {
return time;
}
@Override
public String toString() {
return "PingCommand{" +
"time=" + time +
'}';
}
}

View File

@ -0,0 +1,38 @@
package me.bvn13.sewy.command;
import java.time.Instant;
public class PongCommand extends AbstractCommand {
private final long time;
private long pingTime;
public PongCommand(PingCommand ping) {
this.pingTime = ping.getTime();
this.time = Instant.now().toEpochMilli();
}
public long getTime() {
return time;
}
public long getPingTime() {
return pingTime;
}
public void setPingTime(long pingTime) {
this.pingTime = pingTime;
}
public long getLatency() {
return time - pingTime;
}
@Override
public String toString() {
return "PongCommand{" +
"time=" + time +
", pingTime=" + pingTime +
", latency=" + getLatency() +
'}';
}
}

View File

@ -1,16 +1,40 @@
/*
Copyright 2022 Vyacheslav Boyko
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package me.bvn13.sewy;
import java.net.Socket;
/**
* Simple ECHOed client listener
* Writes into socket all the data received before
*/
public class EchoClientListener extends AbstractClientListener {
public EchoClientListener(Socket socket) {
super(socket);
}
/**
* Thread runner
*/
@Override
public void run() {
while (socket.isConnected()) {
writeLine(readLine());
Thread.yield();
final String data = readLine();
writeLine(data);
}
}
}

View File

@ -1,9 +1,29 @@
/*
Copyright 2022 Vyacheslav Boyko
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package me.bvn13.sewy;
import me.bvn13.sewy.command.AbstractCommand;
import me.bvn13.sewy.command.PingCommand;
import me.bvn13.sewy.command.PongCommand;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import java.util.concurrent.atomic.AtomicLong;
public class ServerTest {
private static final int START_PORT = 12345;
@ -21,7 +41,7 @@ public class ServerTest {
@ValueSource(ints = START_PORT + 2)
void givenServerRunning_whenClientConnects_thenServerCanStopClientListener(int port) throws InterruptedException {
Server server = new Server("localhost", port);
Client client = new Client("localhost", port);
Client<SimpleClientListener> client = new Client<>("localhost", port, SimpleClientListener.class);
Thread.sleep(1000);
Assertions.assertTrue(server.isListening());
Assertions.assertTrue(client.isConnected());
@ -39,13 +59,8 @@ public class ServerTest {
@ParameterizedTest
@ValueSource(ints = START_PORT + 4)
void serverStartedWithLambdaProvidedClientListener(int port) throws InterruptedException {
Server server = new Server("localhost", port, (socket) -> new AbstractClientListener(socket) {
@Override
public void run() {
}
});
void serverIsAbleToStartWithLambdaProvidedClientListener(int port) throws InterruptedException {
Server server = new Server("localhost", port, SimpleClientListener.class);
Thread.sleep(1000);
Assertions.assertTrue(server.isListening());
server.stop();
@ -54,8 +69,8 @@ public class ServerTest {
@ParameterizedTest
@ValueSource(ints = START_PORT + 5)
void simpleEchoClientServer(int port) {
new Server("localhost", port, EchoClientListener.class);
Client client = new Client("localhost", port);
new Server("192.168.0.153", port, EchoClientListener.class);
Client<SimpleClientListener> client = new Client<>("192.168.0.153", port, SimpleClientListener.class);
client.writeLine("hello");
String response1 = client.readLine();
Assertions.assertEquals("hello", response1);
@ -64,4 +79,37 @@ public class ServerTest {
Assertions.assertEquals("olleh", response2);
}
@ParameterizedTest
@ValueSource(ints = START_PORT + 6)
void serverIsAbleToPingPong(int port) throws InterruptedException {
Sewy.register(PingCommand.class);
Sewy.register(PongCommand.class);
Server server = new Server("localhost", port, (socket) -> new CommandClientListener(socket) {
@Override
public AbstractCommand onCommand(AbstractCommand command) {
if (command instanceof PingCommand) {
return new PongCommand((PingCommand) command);
}
throw new IllegalArgumentException(command.toString());
}
});
AtomicLong latency = new AtomicLong(0);
CommandClient client = new CommandClient("localhost", port, (socket) -> new CommandClientListener(socket) {
@Override
public AbstractCommand onCommand(AbstractCommand command) {
if (command instanceof PongCommand) {
latency.set(((PongCommand)command).getLatency());
return null;
} else {
throw new IllegalArgumentException(command.toString());
}
}
});
client.send(new PingCommand());
Thread.sleep(1000);
Assertions.assertTrue(latency.get() > 0);
}
}

View File

@ -0,0 +1,11 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} \(%class{0}.java:%line\) - %msg%n</pattern>
</encoder>
</appender>
<root level="trace">
<appender-ref ref="STDOUT" />
</root>
</configuration>