Conversation
kforkartoshka
left a comment
There was a problem hiding this comment.
Hyperskill review
Good use of try-with-resources, but missed socket in Server.
Ok overall, the main issue is only with one-at-a-time connections
| try (ServerSocket server = new ServerSocket(PORT)) { | ||
| while (true) { | ||
| Session session = new Session(server.accept()); | ||
| session.run(); // it does not block this thread |
There was a problem hiding this comment.
Only it does block it. Well, not really blocks, cause execution still continues, but it only serves one client.
You have another infinite loop in Session::run. So by calling it inside the first infinite loop, its iteration won't end until a client disconnects.
You can fix it by making Session into Thread
| } | ||
|
|
||
| class Session { | ||
| private final Socket socket; |
|
|
||
| System.out.println("Recieved: " + msg); | ||
| output.writeUTF(msg); | ||
| System.out.println("Sent: " + msg); |
There was a problem hiding this comment.
Wrong input and output messages format.
You only send integers, but the task is to send "Give me a record # N" from Client and "A record # N was sent!" from Server
|
|
||
| output.writeUTF(msg); // sending message to the server | ||
| System.out.println("Sent: " + msg); | ||
| String receivedMsg = input.readUTF(); // response message |
There was a problem hiding this comment.
Redundant comments, it's clear that you're sending and receiving something from the method names
kforkartoshka
left a comment
There was a problem hiding this comment.
It's OK but could be simplified and decoupled more
Try to write methods with single-responsibility in mind – each method does one thing. Now your main method does everything - parameters validation, client connection, command handling, reading and writing to socket
It's way harder to read such code and so you start to rely on comments that explain obvious stuff. But if you decouple program behavior, you'll find it easier to reason with.
| DataInputStream input = new DataInputStream(socket.getInputStream()); | ||
| DataOutputStream output = new DataOutputStream(socket.getOutputStream()) | ||
| ) { | ||
| if (client.command.equals("set")) { |
There was a problem hiding this comment.
In Commands validator you use value.trim().toLowerCase(), but here you don't preprocess command, can lead to a potential bug
Also, when checking strings against hardcoded values, better to switch their places, e.g. "set".equals(client.command), or use Objects.equals(client.command, "set"), to avoid NullPointerException
Another approach would be to use switch, e.g. switch (client.command.trim().toLowerCase()) { case "get": ... }
| output.writeUTF(client.command + " " + client.index); | ||
| System.out.println("Sent: " + client.command + " " + client.index); | ||
| } | ||
| String receivedMsg = input.readUTF(); // response message |
There was a problem hiding this comment.
Redundant comment
You can just name the variable response
| System.out.println("Sent: " + msg); | ||
|
|
||
| int portNumber = Integer.parseInt(args[0]); | ||
| Protocol protocol = new Protocol(); |
There was a problem hiding this comment.
Protocol class is missing from the source code
| DataOutputStream output = new DataOutputStream(socket.getOutputStream()) | ||
| ) { | ||
| String inputLine, outputLine; | ||
| inputLine = input.readUTF(); // reading a message |
There was a problem hiding this comment.
Redundant comments duplicating method names
| public class Commands implements IParameterValidator { | ||
| public void validate(String name, String value) | ||
| throws ParameterException { | ||
| value = value.trim().toLowerCase(); |
There was a problem hiding this comment.
It's a bad practice to reassign input parameter
Better treat and mark them as final
More and more languages do that by default
| public void validate(String name, String value) | ||
| throws ParameterException { | ||
| value = value.trim().toLowerCase(); | ||
| if (!(value.equals("set") || value.equals("get") || value.equals("delete"))) { |
There was a problem hiding this comment.
That's fine, except for potential NPE, which I described in Client class
But could be simplified by creating a Set of allowed commands and checking if passed value is in this set
The set could also be used in exception message to create a string of allowed commands allowed.stream().collect(Collectors.joining("/"))
kforkartoshka
left a comment
There was a problem hiding this comment.
Ok overall, but try to separate logical parts more (with new lines or by extracting into separate methods)
Cause now your flow looks like this – createrequestparseittojsonsenditlogitreceiveresponselogit
It's just one indistinguishable blob of words
| import java.util.Objects; | ||
|
|
||
| public class Request { | ||
| public enum TYPE {SET, GET, DELETE} |
There was a problem hiding this comment.
For now it's okay, cause you use it only inside the Request class, but if you'd try to pass TYPE as a parameter somewhere else, you'll have to create an instance of 'Request' to access the enum. To avoid it make it static
Also, enum is just another class, and so follows the same naming convention (upper camel case, instead of all-caps). The values of enum are constants, and so they are written in caps, but not the enum itself
| package org.hyperskill.database.common; | ||
|
|
||
| public class Response { | ||
| public enum TYPE {OK, FAIL} |
| import java.util.Objects; | ||
|
|
||
| public class JsonProtocol { | ||
| JsonStorage storage = new JsonStorage(); |
There was a problem hiding this comment.
Would be nicer and cleaner to introduce inversion of control here by creating and using Storage interface instead of concrete implementation
| import com.beust.jcommander.IParameterValidator; | ||
| import com.beust.jcommander.ParameterException; | ||
|
|
||
| public class Commands implements IParameterValidator { |
There was a problem hiding this comment.
Better name it CommandValidator, cause just Commands is ambiguous
The name of a class should reflect on what it does
| import com.beust.jcommander.IParameterValidator; | ||
| import com.beust.jcommander.ParameterException; | ||
|
|
||
| public class PositiveInteger implements IParameterValidator { |
There was a problem hiding this comment.
Same with naming as with Commands
kforkartoshka
left a comment
There was a problem hiding this comment.
Mostly okay again, with the same problems about code readability and structure as in previous stages
Nice thought of input parameters validation in methods
| String command = null; | ||
| String key = null; | ||
| StringBuilder value = new StringBuilder(); | ||
| try (InputStream in = Files.newInputStream(file); |
There was a problem hiding this comment.
This whole thing could have been implemented in a couple of lines, using Files.lines(file)
But if you don't know about streams, it's ok
| import java.nio.file.Paths; | ||
| import java.util.Objects; | ||
|
|
||
| public class Util { |
There was a problem hiding this comment.
Creating an "utils" class is tempting but actually is a bad practice. It tempts you to put unrelated stuff together into one class
Here you do IO operations, so the name should convey this
| } | ||
| */ | ||
| } | ||
| /* |
There was a problem hiding this comment.
Don't commit commented-out code
It's just a noise
If you want to preserve it for potential future reference – git already did it for you, you can check file history for that
| return value; | ||
| } | ||
|
|
||
| public static TYPE validateCommand(String word) { |
There was a problem hiding this comment.
It's not validation but parsing
Also, it could be done with Enum::valueOf method, which will also throw IllegalArgumentException on unknown parameter
| public enum TYPE {OK, FAIL} | ||
|
|
||
| public boolean isOk() { | ||
| if (this.type == TYPE.OK) { |
There was a problem hiding this comment.
Again, when your method returns boolean, you can just return boolean expression used in if clause
| return false; | ||
| } | ||
|
|
||
| public TYPE getType() { |
There was a problem hiding this comment.
Better have class parameters at the top of its definition. This way you and everyone else can clearly see what this class is constructed of
Your whole structure here is upside-down
It usually goes as follows:
– parameters
– code blocks
– constructors
– getters/setters
– methods
– utility
| storage = JsonStorage.getINSTANCE(); | ||
| } | ||
|
|
||
| public void run() { // run the service |
There was a problem hiding this comment.
Again, what's the purpose of such comments?
It's like writing import java.net.Socket; //import Socket class from java.net package
| writeLock = lock.writeLock(); | ||
| } | ||
|
|
||
| public static JsonStorage getINSTANCE() { |
There was a problem hiding this comment.
No need to caps "instance" in getter name, it still follows camel-case in this situation
| private String value; | ||
|
|
||
| public Request(String type, String key, String value) { | ||
| public Request(String type, String key, String value) throws IllegalArgumentException { |
There was a problem hiding this comment.
Why not use TYPE enum as type parameter type instead of String
Usage of String here only introduces potential failure and complicates logic where you additionally need to parse it
| import java.util.concurrent.ExecutorService; | ||
| import java.util.concurrent.Executors; | ||
|
|
||
| class NetworkService implements Runnable { |
There was a problem hiding this comment.
Why making it Runnable if you're not using it as runnable
Calling nws.run(); in main doesn't execute it in a separate thread. You need to call start for that
No description provided.