diff --git a/pom.xml b/pom.xml
index c8f2162..6d17e05 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
4.0.0
fr.univ-lyon1
- Java TP
+ java-tp
1.0-SNAPSHOT
@@ -65,7 +65,7 @@
default-cli
- fr.watteau.javafx_demo/fr.watteau.javafx_demo.HelloApplication
+ fr.univ.lyon1.gui.MainGui
diff --git a/src/fr/univ/lyon1/client/Client.java b/src/fr/univ/lyon1/client/Client.java
new file mode 100644
index 0000000..19718cc
--- /dev/null
+++ b/src/fr/univ/lyon1/client/Client.java
@@ -0,0 +1,69 @@
+package fr.univ.lyon1.client;
+
+import fr.univ.lyon1.common.Message;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.Socket;
+
+public class Client {
+ private final int port;
+ private final String address;
+ protected final Socket socket;
+ protected final ObjectOutputStream out;
+ private ObjectInputStream in;
+ protected boolean started = false;
+
+ public Client(String address, int port) throws IOException, InterruptedException {
+ this.address = address;
+ this.port = port;
+ socket = new Socket(address, port);
+ out = new ObjectOutputStream(socket.getOutputStream());
+ }
+
+ public void disconnectedServer() throws IOException {
+ socket.close();
+ out.close();
+ if (in != null)
+ in.close();
+
+ System.exit(0);
+ }
+
+ public String sendMessage(String content) {
+ Message msg = new Message(null, content);
+
+ try {
+ out.writeObject(msg);
+ out.flush();
+ } catch (IOException e) {
+ System.err.println("Fail to send message !");
+ e.printStackTrace();
+ }
+
+ return content;
+ }
+
+ public Message messageReceived(Message msg) {
+ System.out.println();
+ System.out.println(msg);
+ return msg;
+ }
+
+ public void run() throws InterruptedException, IOException {
+ if (started)
+ return;
+ Thread clientSendThread = new Thread(new ClientSend(this, out, socket));
+ clientSendThread.start();
+
+ Thread clientReceiveThread = new Thread(new ClientReceive(this, socket));
+ clientReceiveThread.start();
+
+ started = true;
+
+ clientSendThread.join();
+ socket.close();
+ clientReceiveThread.interrupt();
+ }
+}
diff --git a/src/fr/univ/lyon1/client/ClientReceive.java b/src/fr/univ/lyon1/client/ClientReceive.java
new file mode 100644
index 0000000..ddccb12
--- /dev/null
+++ b/src/fr/univ/lyon1/client/ClientReceive.java
@@ -0,0 +1,61 @@
+package fr.univ.lyon1.client;
+
+import fr.univ.lyon1.common.Message;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.net.Socket;
+import java.net.SocketException;
+
+public class ClientReceive implements Runnable {
+ private final Client client;
+ private ObjectInputStream in;
+ private final Socket socket;
+
+ public ClientReceive(Client client, Socket socket) {
+ this.client = client;
+ this.socket = socket;
+ }
+
+ @Override
+ public void run() {
+ try {
+ in = new ObjectInputStream(socket.getInputStream());
+ } catch (IOException e) {
+ e.printStackTrace();
+ return;
+ }
+
+ while(true) {
+ Message msg;
+ try {
+ msg = (Message) in.readObject();
+ } catch (ClassNotFoundException|IOException e) {
+ if (e instanceof SocketException) {
+ System.err.println("Connexion closed");
+ break;
+ }
+ System.err.println("Fail to read message object !");
+ e.printStackTrace();
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException ex) {
+ break;
+ }
+ continue;
+ }
+
+ if (msg == null)
+ break;
+
+ this.client.messageReceived(msg);
+ }
+
+ try {
+ client.disconnectedServer();
+ } catch (IOException e) {
+ System.err.println("Fail to disconnect !");
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/src/fr/univ/lyon1/client/ClientSend.java b/src/fr/univ/lyon1/client/ClientSend.java
new file mode 100644
index 0000000..d3d82fe
--- /dev/null
+++ b/src/fr/univ/lyon1/client/ClientSend.java
@@ -0,0 +1,31 @@
+package fr.univ.lyon1.client;
+
+import java.io.ObjectOutputStream;
+import java.net.Socket;
+import java.util.Scanner;
+
+public class ClientSend implements Runnable {
+ private final Client client;
+ private final ObjectOutputStream out;
+ private final Socket socket;
+
+ public ClientSend(Client client, ObjectOutputStream out, Socket socket) {
+ this.client = client;
+ this.out = out;
+ this.socket = socket;
+ }
+
+ public void run() {
+ Scanner sc = new Scanner(System.in);
+
+ while (true) {
+ System.out.print(">> ");
+ String m = sc.nextLine();
+
+ if (m.equals("exit"))
+ break;
+
+ client.sendMessage(m);
+ }
+ }
+}
diff --git a/src/fr/univ/lyon1/client/MainClient.java b/src/fr/univ/lyon1/client/MainClient.java
new file mode 100644
index 0000000..82c088b
--- /dev/null
+++ b/src/fr/univ/lyon1/client/MainClient.java
@@ -0,0 +1,26 @@
+package fr.univ.lyon1.client;
+
+import java.io.IOException;
+
+public class MainClient {
+ public static void main(String[] args) {
+ try {
+ if (args.length != 2) {
+ printUsage();
+ } else {
+ String address = args[0];
+ int port = Integer.parseInt(args[1]);
+ Client c = new Client(address, port);
+ c.run();
+ }
+ } catch (IOException|InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private static void printUsage() {
+ System.out.println("java client.Client ");
+ System.out.println("\t: server's ip address");
+ System.out.println("\t: server's port");
+ }
+}
diff --git a/src/fr/univ/lyon1/common/Message.java b/src/fr/univ/lyon1/common/Message.java
new file mode 100644
index 0000000..15a8638
--- /dev/null
+++ b/src/fr/univ/lyon1/common/Message.java
@@ -0,0 +1,31 @@
+package fr.univ.lyon1.common;
+
+import java.io.Serializable;
+
+public class Message implements Serializable {
+ private String sender;
+ private final String content;
+
+
+ public Message(String sender, String content) {
+ this.sender = sender;
+ this.content = content;
+ }
+
+ public void setSender(String sender) {
+ this.sender = sender;
+ }
+
+ public String getSender() {
+ return sender;
+ }
+
+ public String getContent() {
+ return content;
+ }
+
+ @Override
+ public String toString() {
+ return sender + ": " + content;
+ }
+}
diff --git a/src/fr/univ/lyon1/gui/ClientGUI.java b/src/fr/univ/lyon1/gui/ClientGUI.java
new file mode 100644
index 0000000..634aefc
--- /dev/null
+++ b/src/fr/univ/lyon1/gui/ClientGUI.java
@@ -0,0 +1,33 @@
+package fr.univ.lyon1.gui;
+
+import fr.univ.lyon1.client.Client;
+import fr.univ.lyon1.client.ClientReceive;
+import fr.univ.lyon1.common.Message;
+
+import java.io.IOException;
+
+public class ClientGUI extends Client {
+ private final MainGui gui;
+
+ public ClientGUI(MainGui gui, String address, int port) throws IOException, InterruptedException {
+ super(address, port);
+ this.gui = gui;
+ }
+
+ @Override
+ public Message messageReceived(Message msg) {
+ gui.receiveMessage(msg.toString());
+ return msg;
+ }
+
+ @Override
+ public void run() {
+ if (started)
+ return;
+
+ Thread clientReceiveThread = new Thread(new ClientReceive(this, super.socket));
+ clientReceiveThread.start();
+
+ started = true;
+ }
+}
diff --git a/src/fr/univ/lyon1/gui/ClientPanel.java b/src/fr/univ/lyon1/gui/ClientPanel.java
new file mode 100644
index 0000000..e47bd98
--- /dev/null
+++ b/src/fr/univ/lyon1/gui/ClientPanel.java
@@ -0,0 +1,85 @@
+package fr.univ.lyon1.gui;
+
+import javafx.application.Platform;
+import javafx.event.ActionEvent;
+import javafx.scene.Parent;
+import javafx.scene.control.Button;
+import javafx.scene.control.ScrollPane;
+import javafx.scene.control.TextArea;
+import javafx.scene.input.KeyCode;
+import javafx.scene.input.KeyEvent;
+import javafx.scene.text.Text;
+import javafx.scene.text.TextFlow;
+
+public class ClientPanel extends Parent {
+ private TextArea textToSend = new TextArea();
+ private ScrollPane scrollReceivedText = new ScrollPane();
+ private TextFlow receivedText = new TextFlow();
+ private Button sendBtn = new Button();
+ private Button clearBtn = new Button();
+ private final MainGui gui;
+
+ ClientPanel(MainGui gui) {
+ this.gui = gui;
+ scrollReceivedText.setLayoutX(20);
+ scrollReceivedText.setLayoutY(20);
+ scrollReceivedText.setPrefWidth(400);
+ scrollReceivedText.setPrefHeight(350);
+ scrollReceivedText.setContent(receivedText);
+ scrollReceivedText.vvalueProperty().bind(receivedText.heightProperty());
+ this.getChildren().add(scrollReceivedText);
+
+ sendBtn.setPrefWidth(80);
+
+ int btnMargin = 20;
+
+ textToSend.setLayoutX(scrollReceivedText.getLayoutX());
+ textToSend.setLayoutY(scrollReceivedText.getLayoutY() + scrollReceivedText.getPrefHeight() + 20);
+ textToSend.setPrefWidth(400-sendBtn.getPrefWidth()-btnMargin);
+ textToSend.setPrefHeight(100);
+ this.getChildren().add(textToSend);
+ textToSend.setOnKeyPressed(this::textToSendKeyPressed);
+
+ sendBtn.setText("Send");
+ sendBtn.setLayoutX(textToSend.getLayoutX() + textToSend.getPrefWidth() + btnMargin);
+ sendBtn.setLayoutY(textToSend.getLayoutY());
+ sendBtn.setPrefWidth(80);
+ sendBtn.setPrefHeight(40);
+ sendBtn.setVisible(true);
+ this.getChildren().add(sendBtn);
+ sendBtn.setOnAction(this::sendBtnAction);
+
+ clearBtn.setText("Clear");
+ clearBtn.setLayoutX(sendBtn.getLayoutX());
+ clearBtn.setPrefWidth(sendBtn.getPrefWidth());
+ clearBtn.setPrefHeight(sendBtn.getPrefHeight());
+ clearBtn.setLayoutY(textToSend.getLayoutY() + textToSend.getPrefHeight() - clearBtn.getPrefHeight());
+ clearBtn.setVisible(true);
+ this.getChildren().add(clearBtn);
+ clearBtn.setOnAction(this::clearBtnAction);
+ }
+
+ private void send() {
+ String msg = textToSend.getText();
+ gui.sendMessage(msg);
+ textToSend.clear();
+ addMessage(msg);
+ }
+
+ private void sendBtnAction(ActionEvent e) {
+ send();
+ }
+
+ private void textToSendKeyPressed(KeyEvent e) {
+ if (e.getCode() == KeyCode.ENTER)
+ send();
+ }
+
+ private void clearBtnAction(ActionEvent e) {
+ textToSend.clear();
+ }
+
+ public void addMessage(String message) {
+ Platform.runLater(() -> receivedText.getChildren().add(new Text(message+"\n")));
+ }
+}
diff --git a/src/fr/univ/lyon1/gui/MainGui.java b/src/fr/univ/lyon1/gui/MainGui.java
new file mode 100644
index 0000000..2b1c691
--- /dev/null
+++ b/src/fr/univ/lyon1/gui/MainGui.java
@@ -0,0 +1,45 @@
+package fr.univ.lyon1.gui;
+
+import javafx.application.Application;
+import javafx.scene.Group;
+import javafx.scene.Scene;
+import javafx.stage.Stage;
+
+import java.util.List;
+
+public class MainGui extends Application {
+ private ClientPanel clientPanel;
+ private ClientGUI client;
+
+ @Override
+ public void start(Stage stage) throws Exception {
+ List parameters = this.getParameters().getUnnamed();
+ client = new ClientGUI(this, parameters.get(0), Integer.parseInt(parameters.get(1)));
+ //ToDo: error management especially for bad server IP/port
+ //ToDo: Server IP/port enter by user on the GUI
+
+ stage.setTitle("Chat client");
+ stage.setWidth(440);
+
+ clientPanel = new ClientPanel(this);
+ Group root = new Group();
+ root.getChildren().add(clientPanel);
+ Scene scene = new Scene(root, 600, 500);
+
+ stage.setScene(scene);
+ stage.show();
+ client.run();
+ }
+
+ public static void main(String[] args) {
+ Application.launch(MainGui.class, args);
+ }
+
+ public void sendMessage(String msg) {
+ client.sendMessage(msg);
+ }
+
+ public void receiveMessage(String msg) {
+ clientPanel.addMessage(msg);
+ }
+}
diff --git a/src/fr/univ/lyon1/server/ConnectedClient.java b/src/fr/univ/lyon1/server/ConnectedClient.java
new file mode 100644
index 0000000..39d8d0b
--- /dev/null
+++ b/src/fr/univ/lyon1/server/ConnectedClient.java
@@ -0,0 +1,64 @@
+package fr.univ.lyon1.server;
+
+import fr.univ.lyon1.common.Message;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.net.Socket;
+
+public class ConnectedClient implements Runnable {
+ private static int idCounter = 0;
+ private final int id = idCounter++;
+ private final Server server;
+ private final Socket socket;
+ private final ObjectOutputStream out;
+ private ObjectInputStream in;
+
+ ConnectedClient(Server server, Socket socket) throws IOException {
+ this.server = server;
+ this.socket = socket;
+ this.out = new ObjectOutputStream(socket.getOutputStream());
+ }
+
+ public Message sendMessage(Message message) throws IOException {
+ out.writeObject(message);
+ out.flush();
+ return message;
+ }
+
+ public void run() {
+ try {
+ in = new ObjectInputStream(socket.getInputStream());
+
+ while (true) {
+ Message msg = (Message) in.readObject();
+
+ if (msg == null)
+ break;
+
+ msg.setSender(String.valueOf(id));
+ server.broadcastMessage(msg, id);
+ }
+ } catch (IOException | ClassNotFoundException e) {
+ if (!(e instanceof EOFException)) {
+ System.err.println("Client connection error");
+ e.printStackTrace();
+ }
+ } finally {
+ server.disconnectedClient(this);
+ }
+ }
+
+ public void closeClient() throws IOException {
+ if (in != null)
+ in.close();
+ out.close();
+ socket.close();
+ }
+
+ public int getId() {
+ return id;
+ }
+}
diff --git a/src/fr/univ/lyon1/server/Connection.java b/src/fr/univ/lyon1/server/Connection.java
new file mode 100644
index 0000000..7ab3b09
--- /dev/null
+++ b/src/fr/univ/lyon1/server/Connection.java
@@ -0,0 +1,31 @@
+package fr.univ.lyon1.server;
+
+import java.io.IOException;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+public class Connection implements Runnable {
+ private final Server server;
+ private final ServerSocket serverSocket;
+
+ Connection(Server server) throws IOException {
+ this.server = server;
+ this.serverSocket = new ServerSocket(server.getPort());
+ }
+
+ public void run() {
+ while (true) {
+ Socket clientSocket;
+ try {
+ clientSocket = serverSocket.accept();
+ ConnectedClient client = new ConnectedClient(server, clientSocket);
+ server.addClient(client);
+ Thread clientThread = new Thread(client);
+ clientThread.start();
+ } catch (IOException e) {
+ System.err.println("Fail to connect the new client !");
+ e.printStackTrace();
+ }
+ }
+ }
+}
diff --git a/src/fr/univ/lyon1/server/MainServer.java b/src/fr/univ/lyon1/server/MainServer.java
new file mode 100644
index 0000000..5809841
--- /dev/null
+++ b/src/fr/univ/lyon1/server/MainServer.java
@@ -0,0 +1,23 @@
+package fr.univ.lyon1.server;
+
+import java.io.IOException;
+
+public class MainServer {
+ public static void main(String[] args) {
+ try {
+ if (args.length != 1) {
+ printUsage();
+ } else {
+ int port = Integer.parseInt(args[0]);
+ new Server(port);
+ }
+ } catch (IOException e1) {
+ e1.printStackTrace();
+ }
+ }
+
+ private static void printUsage() {
+ System.out.println("java server.Server ");
+ System.out.println("\t: server's port");
+ }
+}
diff --git a/src/fr/univ/lyon1/server/Server.java b/src/fr/univ/lyon1/server/Server.java
new file mode 100644
index 0000000..ca711d6
--- /dev/null
+++ b/src/fr/univ/lyon1/server/Server.java
@@ -0,0 +1,65 @@
+package fr.univ.lyon1.server;
+
+import fr.univ.lyon1.common.Message;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class Server {
+ private final int port;
+ private List clients = new ArrayList<>();
+
+ Server(int port) throws IOException {
+ this.port = port;
+ Thread connection = new Thread(new Connection(this));
+ connection.start();
+ }
+
+ public ConnectedClient addClient(ConnectedClient newClient) {
+ Message msg = new Message( "Server", newClient.getId() + " is connected !");
+
+ clients.add(newClient);
+
+ broadcastMessage(msg, -1);
+
+ System.out.println("Client "+newClient.getId()+" is connected !");
+
+ return newClient;
+ }
+
+ public int broadcastMessage(Message message, int id) {
+ for (ConnectedClient client : clients) {
+ if (id == -1 || client.getId() != id)
+ try {
+ client.sendMessage(message);
+ } catch (IOException e) {
+ System.err.println("Fail to send message to " + client.getId());
+ e.printStackTrace();
+ }
+ }
+ return id;
+ }
+
+ public ConnectedClient disconnectedClient(ConnectedClient client) {
+ try {
+ client.closeClient();
+ } catch (IOException e) {
+ System.err.println("Fail to close client "+client.getId());
+ e.printStackTrace();
+ }
+
+ clients.remove(client);
+
+ Message msg = new Message("Server", "Client "+client.getId()+" is disconnected");
+
+ broadcastMessage(msg, -1);
+
+ System.out.println("Client "+client.getId()+" disconnected");
+ return client;
+ }
+
+ public int getPort() {
+ return port;
+ }
+}