diff --git a/src/main/java/ru/hh/school/stdlib/Launcher.java b/src/main/java/ru/hh/school/stdlib/Launcher.java index 1bee1a8..ab11d5d 100644 --- a/src/main/java/ru/hh/school/stdlib/Launcher.java +++ b/src/main/java/ru/hh/school/stdlib/Launcher.java @@ -2,28 +2,31 @@ import java.io.IOException; import java.net.InetSocketAddress; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; public class Launcher { - public static void main(String[] args) throws IOException { - String host; - int port; - try { - if (args.length == 0) { - host = "127.0.0.1"; - port = 9129; - } else if (args.length == 3) { - host = args[1]; - port = Integer.parseInt(args[2]); - } else { - throw new IllegalArgumentException(); - } - } catch (Exception e) { - System.err.printf("Usage: %s [host port]%n", args[0]); - System.exit(1); - return; // попробуйте закомментировать этот return - } - InetSocketAddress addr = InetSocketAddress.createUnresolved(host, port); + public static void main(String[] args) throws IOException { + String host; + int port; + try { + if (args.length == 0) { + host = "127.0.0.1"; + port = 9129; + } else if (args.length == 3) { + host = args[1]; + port = Integer.parseInt(args[2]); + } else { + throw new IllegalArgumentException(); + } + } catch (Exception e) { + System.err.printf("Usage: %s [host port]%n", args[0]); + System.exit(1); + return; + } - new Server(addr).run(); - } + InetSocketAddress addr = InetSocketAddress.createUnresolved(host, port); + ExecutorService exec = Executors.newSingleThreadExecutor(); + exec.execute(new Server(addr)); + } } diff --git a/src/main/java/ru/hh/school/stdlib/RecursiveException.java b/src/main/java/ru/hh/school/stdlib/RecursiveException.java new file mode 100644 index 0000000..9467771 --- /dev/null +++ b/src/main/java/ru/hh/school/stdlib/RecursiveException.java @@ -0,0 +1,4 @@ +package ru.hh.school.stdlib; + +public class RecursiveException extends Exception { +} diff --git a/src/main/java/ru/hh/school/stdlib/Server.java b/src/main/java/ru/hh/school/stdlib/Server.java index 8284c32..2508d80 100644 --- a/src/main/java/ru/hh/school/stdlib/Server.java +++ b/src/main/java/ru/hh/school/stdlib/Server.java @@ -2,17 +2,53 @@ import java.io.IOException; import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; -public class Server { - public Server(InetSocketAddress addr) { - throw new UnsupportedOperationException(); - } +public class Server implements Runnable { + private int serverPort = 4444; + private ServerSocket serverSocket = null; + private Substitutor3000 substitutor; + private int sleepTime = 0; - public void run() throws IOException { - throw new UnsupportedOperationException(); - } + public Server(InetSocketAddress addr) { + this.serverPort = addr.getPort(); + substitutor = new Substitutor3000(); + } - public int getPort() { - throw new UnsupportedOperationException(); - } -} + public void run() { + openServerSocket(); + ExecutorService exec = Executors.newCachedThreadPool(); + while(true){ + Socket clientSocket; + try { + clientSocket = this.serverSocket.accept(); + } catch (IOException e) { + throw new RuntimeException("Error accepting client connection", e); + } + exec.execute(new ServerThread(clientSocket, substitutor, this)); + } + } + + public int getPort() { + return serverPort; + } + + private void openServerSocket() { + try { + this.serverSocket = new ServerSocket(this.serverPort); + } catch (IOException e) { + throw new RuntimeException("Cannot open port 8080", e); + } + } + + public synchronized int getSleepTime() { + return sleepTime; + } + + public synchronized void setSleepTime(int sleepTime) { + this.sleepTime = sleepTime; + } +} \ No newline at end of file diff --git a/src/main/java/ru/hh/school/stdlib/ServerThread.java b/src/main/java/ru/hh/school/stdlib/ServerThread.java new file mode 100644 index 0000000..0ee2965 --- /dev/null +++ b/src/main/java/ru/hh/school/stdlib/ServerThread.java @@ -0,0 +1,94 @@ +package ru.hh.school.stdlib; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.Socket; +import java.util.concurrent.TimeUnit; + +public class ServerThread implements Runnable{ + private final static String SUCCESS_REQUEST = "OK\nconnection closed"; + private final static String WRONG_REQUEST = "ERROR\nWrong request. Please try again."; + private final static String RECURSIVE_ERROR = "ERROR\nRecursive link detected"; + + private Socket clientSocket; + private Substitutor3000 substitutor; + private Server server; + + public ServerThread(Socket clientSocket, Substitutor3000 substitutor, Server server) { + this.clientSocket = clientSocket; + this.substitutor = substitutor; + this.server = server; + } + + public void run() { + try { + PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true); + BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream())); + String inputLine, outputLine; + + while ((inputLine = in.readLine()) != null) { + outputLine = processInput(inputLine); + out.println(outputLine); + if (outputLine.equals("EXIT")) + break; + } + + out.close(); + in.close(); + clientSocket.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + private String processInput(String inputLine) { + String[] input = inputLine.split("\\s"); + + if (input[0].equals("GET")) { + sleep(); + String key = input[1]; + String value; + try { + value = substitutor.get(key); + } catch (RecursiveException e) { + return RECURSIVE_ERROR; + } + return "VALUE\n" + value; + } + else if (input[0].equals("PUT")) { + sleep(); + String key = input[1]; + String value = ""; + for (int i = 2; i < input.length; i++) + value += input[i]; + substitutor.put(key, value); + return SUCCESS_REQUEST; + } + else if (input[0].equals("SET")) { + if (input[1].equals("SLEEP")) { + server.setSleepTime(Integer.parseInt(input[2])); + return SUCCESS_REQUEST; + } + else + return WRONG_REQUEST; + } + else if (input[0].equals("EXIT")) { + return "EXIT"; + } + else { + return WRONG_REQUEST; + } + } + + private void sleep() { + if (server.getSleepTime() != 0) { + try { + TimeUnit.MILLISECONDS.sleep(server.getSleepTime()); + } catch (InterruptedException e) { + System.out.println(e); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/ru/hh/school/stdlib/Substitutor3000.java b/src/main/java/ru/hh/school/stdlib/Substitutor3000.java index 728598c..a414494 100644 --- a/src/main/java/ru/hh/school/stdlib/Substitutor3000.java +++ b/src/main/java/ru/hh/school/stdlib/Substitutor3000.java @@ -1,11 +1,39 @@ package ru.hh.school.stdlib; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + public class Substitutor3000 { - public void put(String key, String value) { - throw new UnsupportedOperationException(); - } + private Map map = Collections.synchronizedMap(new HashMap()); + private ArrayList keys = new ArrayList(); + + public synchronized void put(String key, String value) { + map.put(key, value); + } + + public synchronized String get(String key) throws RecursiveException { + String regex = "\\$\\{(\\w+)\\}"; + String value = map.get(key); + if (value == null) + return ""; + Pattern p = Pattern.compile(regex); + Matcher m = p.matcher(value); + + if (keys.contains(value)) + throw new RecursiveException(); + keys.add(value); + while (m.find()) { + value = value.replaceFirst(regex, get(m.group(1))); + m = p.matcher(value); + } + keys.remove(value); + if (!value.contains(" ")) + value += " "; - public String get(String key) { - throw new UnsupportedOperationException(); - } + return value; + } } diff --git a/src/test/java/ru/hh/school/stdlib/BaseFunctionalTest.java b/src/test/java/ru/hh/school/stdlib/BaseFunctionalTest.java index 5fb134c..70a262c 100644 --- a/src/test/java/ru/hh/school/stdlib/BaseFunctionalTest.java +++ b/src/test/java/ru/hh/school/stdlib/BaseFunctionalTest.java @@ -1,40 +1,40 @@ -package ru.hh.school.stdlib; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.Socket; - -public class BaseFunctionalTest { - private static Server server; - - protected BaseFunctionalTest() { - synchronized (BaseFunctionalTest.class) { - try { - if (server == null) { - server = new Server(new InetSocketAddress("127.0.0.1", 0)); - new Thread(new Runnable() { - public void run() { - try { - server.run(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - }).start(); - Thread.sleep(100); - System.out.printf("Server started on port %d%n", server.getPort()); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } - - protected Socket connect() { - try { - return new Socket("127.0.0.1", server.getPort()); - } catch (IOException e) { - throw new RuntimeException(e); - } - } -} +package ru.hh.school.stdlib; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; + +public class BaseFunctionalTest { + private static Server server; + + protected BaseFunctionalTest() { + synchronized (BaseFunctionalTest.class) { + try { + if (server == null) { + server = new Server(new InetSocketAddress("127.0.0.1", 4444)); + new Thread(new Runnable() { + public void run() { + //try { + server.run(); + //} catch (IOException e) { + //throw new RuntimeException(e); + // } + } + }).start(); + Thread.sleep(100); + System.out.printf("Server started on port %d%n", server.getPort()); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + + protected Socket connect() { + try { + return new Socket("127.0.0.1", server.getPort()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +} diff --git a/src/test/java/ru/hh/school/stdlib/MultiConnection.java b/src/test/java/ru/hh/school/stdlib/MultiConnection.java new file mode 100644 index 0000000..628ee07 --- /dev/null +++ b/src/test/java/ru/hh/school/stdlib/MultiConnection.java @@ -0,0 +1,38 @@ +package ru.hh.school.stdlib; + +import org.junit.Assert; +import org.junit.Test; + +import java.io.*; +import java.net.Socket; + +public class MultiConnection extends BaseFunctionalTest { + + @Test + public void testMultiConnection() throws IOException { + long timePeriod = 2000; + + Socket s1 = connect(); + Socket s2 = connect(); + + Writer out1 = new PrintWriter(s1.getOutputStream()); + BufferedReader in1 = new BufferedReader(new InputStreamReader(s1.getInputStream())); + Writer out2 = new PrintWriter(s2.getOutputStream()); + BufferedReader in2 = new BufferedReader(new InputStreamReader(s2.getInputStream())); + + out1.append("SET SLEEP " + timePeriod + "\n").flush(); + Assert.assertEquals("OK", in1.readLine()); + Assert.assertEquals("connection closed", in1.readLine()); + long start = System.currentTimeMillis(); + out1.append("GET k1\n").flush(); + out2.append("GET k1\n").flush(); + Assert.assertEquals("VALUE", in1.readLine()); + Assert.assertEquals("", in1.readLine()); + Assert.assertEquals("VALUE", in2.readLine()); + Assert.assertEquals("", in2.readLine()); + long end = System.currentTimeMillis(); + + Assert.assertTrue((end - start) < (timePeriod * 2)); + System.out.println(end - start); + } +} diff --git a/src/test/java/ru/hh/school/stdlib/SimpleGetTest.java b/src/test/java/ru/hh/school/stdlib/SimpleGetTest.java index 0fe1b49..e1b666c 100644 --- a/src/test/java/ru/hh/school/stdlib/SimpleGetTest.java +++ b/src/test/java/ru/hh/school/stdlib/SimpleGetTest.java @@ -1,26 +1,84 @@ package ru.hh.school.stdlib; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.PrintWriter; -import java.io.Writer; -import java.net.Socket; import org.junit.Assert; import org.junit.Test; +import java.io.*; +import java.net.Socket; + public class SimpleGetTest extends BaseFunctionalTest { - @Test - public void simpleGet() throws IOException { - Socket s = connect(); - - Writer out = new PrintWriter(s.getOutputStream()); - out.append("GET k1\n").flush(); - BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream())); - - Assert.assertEquals("VALUE", in.readLine()); - Assert.assertEquals("", in.readLine()); - - s.close(); - } + @Test + public void simpleGet() throws IOException { + Socket s = connect(); + + Writer out = new PrintWriter(s.getOutputStream()); + out.append("GET k1\n").flush(); + BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream())); + + Assert.assertEquals("VALUE", in.readLine()); + Assert.assertEquals("", in.readLine()); + + out.append("PUT mo monday\n").flush(); + + Assert.assertEquals("OK", in.readLine()); + Assert.assertEquals("connection closed", in.readLine()); + + out.append("GET mo\n").flush(); + + Assert.assertEquals("VALUE", in.readLine()); + Assert.assertEquals("monday ", in.readLine()); + + out.append("PUT tu tuesday\n").flush(); + Assert.assertEquals("OK", in.readLine()); + Assert.assertEquals("connection closed", in.readLine()); + + out.append("PUT wed wednesday\n").flush(); + Assert.assertEquals("OK", in.readLine()); + Assert.assertEquals("connection closed", in.readLine()); + + out.append("PUT days1 ${mo} ${tu} ${wed}\n").flush(); + Assert.assertEquals("OK", in.readLine()); + Assert.assertEquals("connection closed", in.readLine()); + + out.append("PUT th thursday\n").flush(); + Assert.assertEquals("OK", in.readLine()); + Assert.assertEquals("connection closed", in.readLine()); + + out.append("PUT fr friday\n").flush(); + Assert.assertEquals("OK", in.readLine()); + Assert.assertEquals("connection closed", in.readLine()); + + out.append("PUT days2 ${th} ${fr}\n").flush(); + Assert.assertEquals("OK", in.readLine()); + Assert.assertEquals("connection closed", in.readLine()); + + out.append("PUT workdays ${days1} ${days2}\n").flush(); + Assert.assertEquals("OK", in.readLine()); + Assert.assertEquals("connection closed", in.readLine()); + + out.append("GET workdays\n").flush(); + Assert.assertEquals("VALUE", in.readLine()); + Assert.assertEquals("monday tuesday wednesday thursday friday ", in.readLine()); + + s.close(); + } + + @Test + public void testSleep() throws IOException { + long timePeriod = 1000; + Socket t = connect(); + Writer out = new PrintWriter(t.getOutputStream()); + BufferedReader in = new BufferedReader(new InputStreamReader(t.getInputStream())); + + out.append("SET SLEEP " + timePeriod + "\n").flush(); + Assert.assertEquals("OK", in.readLine()); + Assert.assertEquals("connection closed", in.readLine()); + long start = System.currentTimeMillis(); + out.append("GET k1\n").flush(); + Assert.assertEquals("VALUE", in.readLine()); + Assert.assertEquals("", in.readLine()); + long end = System.currentTimeMillis(); + Assert.assertTrue(timePeriod - 100 < (end - start)); + Assert.assertTrue((end - start) < (timePeriod + 100)); + } } diff --git a/src/test/java/ru/hh/school/stdlib/Substitutor3000Test.java b/src/test/java/ru/hh/school/stdlib/Substitutor3000Test.java index 3d63300..109f8f0 100644 --- a/src/test/java/ru/hh/school/stdlib/Substitutor3000Test.java +++ b/src/test/java/ru/hh/school/stdlib/Substitutor3000Test.java @@ -4,21 +4,44 @@ import org.junit.Test; public class Substitutor3000Test { - @Test - public void replacement() { - Substitutor3000 sbst = new Substitutor3000(); - sbst.put("k1", "one"); - sbst.put("k2", "two"); - sbst.put("keys", "1: ${k1}, 2: ${k2}"); - - Assert.assertEquals("1: one, 2: two", sbst.get("keys")); - } + @Test + public void replacement() { + RecursiveException thrown = null; + try { + Substitutor3000 sbst = new Substitutor3000(); + sbst.put("k1", "one"); + sbst.put("k2", "two"); + sbst.put("keys", "1: ${k1}, 2: ${k2}"); + Assert.assertEquals("1: one , 2: two ", sbst.get("keys")); - @Test - public void emptyReplacement() { - Substitutor3000 sbst = new Substitutor3000(); - sbst.put("k", "bla-${inexistent}-bla"); - - Assert.assertEquals("bla--bla", sbst.get("k")); - } + sbst.put("a", "avril"); + sbst.put("keys2", "1: ${a}, 2: ${b}"); + Assert.assertEquals("1: avril , 2: ", sbst.get("keys2")); + + sbst.put("c3po", "human robot"); + sbst.put("r2d2", "wheel robot"); + sbst.put("keys3", "1: ${c3po}, 2: ${r2d2}"); + Assert.assertEquals("1: human robot, 2: wheel robot", sbst.get("keys3")); + + + //Checking how cycle references are handled + sbst.put("r1","${r2}"); + sbst.put("r2","${r1}"); + sbst.get("r1"); + } catch (RecursiveException e) { + thrown = e; + } + Assert.assertNotNull(thrown); + } + + @Test + public void emptyReplacement() { + Substitutor3000 sbst = new Substitutor3000(); + sbst.put("k", "bla-${inexistent}-bla"); + try { + Assert.assertEquals("bla--bla ", sbst.get("k")); + } catch (RecursiveException e) { + System.out.println(e); + } + } }