From 46d9cd49f437edb25e9d73dd049f70c2e4cb36e5 Mon Sep 17 00:00:00 2001 From: kske Date: Tue, 31 Dec 2019 11:57:11 +0200 Subject: [PATCH] Added Client#sendMessage(Message), closing socket on exit --- src/main/java/envoy/client/Client.java | 45 +++++++++++++++---- .../java/envoy/client/ObjectProcessor.java | 15 ------- src/main/java/envoy/client/Receiver.java | 27 ++++++++--- src/main/java/envoy/client/ui/ChatWindow.java | 7 ++- src/main/java/envoy/client/ui/Startup.java | 31 ++++++++----- 5 files changed, 81 insertions(+), 44 deletions(-) delete mode 100644 src/main/java/envoy/client/ObjectProcessor.java diff --git a/src/main/java/envoy/client/Client.java b/src/main/java/envoy/client/Client.java index a037e8c..b87f31d 100644 --- a/src/main/java/envoy/client/Client.java +++ b/src/main/java/envoy/client/Client.java @@ -1,11 +1,14 @@ package envoy.client; +import java.io.Closeable; +import java.io.IOException; import java.net.Socket; import java.util.Map; import java.util.logging.Logger; import envoy.client.util.EnvoyLog; import envoy.data.LoginCredentials; +import envoy.data.Message; import envoy.data.User; import envoy.exception.EnvoyException; import envoy.util.SerializationUtils; @@ -20,13 +23,16 @@ import envoy.util.SerializationUtils; * @author Leon Hofmeister * @since Envoy v0.1-alpha */ -public class Client { +public class Client implements Closeable { + + private Socket socket; + private Receiver receiver; + private boolean online; - private Socket socket; - private Config config = Config.getInstance(); private volatile User sender; private User recipient; - private boolean online; + + private Config config = Config.getInstance(); private static final Logger logger = EnvoyLog.getLogger(Client.class.getSimpleName()); @@ -39,20 +45,21 @@ public class Client { * @since Envoy v0.2-alpha */ public void onlineInit(LoginCredentials credentials) throws Exception { + // Establish TCP connection logger.info(String.format("Attempting connection to server %s:%d...", config.getServer(), config.getPort())); socket = new Socket(config.getServer(), config.getPort()); logger.info("Successfully connected to server."); - // Write login credentials - logger.finest("Sending login credentials..."); - SerializationUtils.writeBytesWithLength(credentials, socket.getOutputStream()); - // Create message receiver - Receiver receiver = new Receiver(socket.getInputStream()); + receiver = new Receiver(socket.getInputStream()); // Register user creation processor receiver.registerProcessor(User.class, sender -> { logger.info("Acquired user object " + sender); this.sender = sender; }); + // Write login credentials + logger.finest("Sending login credentials..."); + SerializationUtils.writeBytesWithLength(credentials, socket.getOutputStream()); + // Start receiver new Thread(receiver).start(); @@ -66,6 +73,18 @@ public class Client { online = true; } + /** + * Sends a message to the server. + * + * @param message the message to send + * @throws IOException if the message does not reach the server + * @since Envoy v0.3-alpha + */ + public void sendMessage(Message message) throws IOException { + if (!online) throw new IllegalStateException("Client is not online"); + SerializationUtils.writeBytesWithLength(message, socket.getOutputStream()); + } + /** * @return a {@code Map} of all users on the server with their * user names as keys @@ -76,6 +95,9 @@ public class Client { return null; } + @Override + public void close() throws IOException { if (online) socket.close(); } + /** * @return the sender object that represents this client. * @since Envoy v0.1-alpha @@ -110,6 +132,11 @@ public class Client { */ public boolean hasRecipient() { return recipient != null; } + /** + * @return the {@link Receiver} used by this {@link Client} + */ + public Receiver getReceiver() { return receiver; } + /** * @return {@code true} if a connection to the server could be established * @since Envoy v0.2-alpha diff --git a/src/main/java/envoy/client/ObjectProcessor.java b/src/main/java/envoy/client/ObjectProcessor.java deleted file mode 100644 index b0aed2d..0000000 --- a/src/main/java/envoy/client/ObjectProcessor.java +++ /dev/null @@ -1,15 +0,0 @@ -package envoy.client; - - -/** - * Project: envoy-client
- * File: ObjectProcessor.java
- * Created: 30.12.2019
- * - * @author Kai S. K. Engelbart - * @since Envoy v0.3-alpha - */ -public interface ObjectProcessor { - - void process(T input); -} \ No newline at end of file diff --git a/src/main/java/envoy/client/Receiver.java b/src/main/java/envoy/client/Receiver.java index 64a4655..f70d6f9 100644 --- a/src/main/java/envoy/client/Receiver.java +++ b/src/main/java/envoy/client/Receiver.java @@ -4,6 +4,7 @@ import java.io.InputStream; import java.io.ObjectInputStream; import java.util.HashMap; import java.util.Map; +import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; @@ -19,11 +20,16 @@ import envoy.client.util.EnvoyLog; */ public class Receiver implements Runnable { - private InputStream in; - private Map, ObjectProcessor> processors = new HashMap<>(); + private InputStream in; + private Map, Consumer> processors = new HashMap<>(); private static final Logger logger = EnvoyLog.getLogger(Receiver.class.getSimpleName()); + /** + * Creates an instance of {@link Receiver}. + * + * @param in the {@link InputStream} to parse objects from + */ public Receiver(InputStream in) { this.in = in; } @SuppressWarnings("unchecked") @@ -35,16 +41,23 @@ public class Receiver implements Runnable { logger.finest("Received object " + obj); // Get appropriate processor - ObjectProcessor processor = processors.get(obj.getClass()); + @SuppressWarnings("rawtypes") + Consumer processor = processors.get(obj.getClass()); if (processor == null) logger.severe(String.format("The received object has the class %s for which no processor is defined.", obj.getClass())); - else - processor.process(obj); + else processor.accept(obj); } - } catch(Exception e) { + } catch (Exception e) { logger.log(Level.SEVERE, "Error on receiver thread", e); } } - public void registerProcessor(Class processorClass, ObjectProcessor processor) { processors.put(processorClass, processor); } + /** + * Adds an object processor to this {@link Receiver}. It will be called once an + * object of the accepted class has been received. + * + * @param processorClass the object class accepted by the processor + * @param processor the object processor + */ + public void registerProcessor(Class processorClass, Consumer processor) { processors.put(processorClass, processor); } } \ No newline at end of file diff --git a/src/main/java/envoy/client/ui/ChatWindow.java b/src/main/java/envoy/client/ui/ChatWindow.java index 591be9f..b839c4c 100644 --- a/src/main/java/envoy/client/ui/ChatWindow.java +++ b/src/main/java/envoy/client/ui/ChatWindow.java @@ -246,8 +246,13 @@ public class ChatWindow extends JFrame { if (!messageEnterTextArea.getText().isEmpty()) try { - // Create and send message object + // Create message final Message message = new MessageBuilder(localDB.getUser(), currentChat.getRecipient()).setText(messageEnterTextArea.getText()).build(); + + // Send message + client.sendMessage(message); + + // Add message to LocalDB and update UI currentChat.appendMessage(message); messageList.setModel(currentChat.getModel()); diff --git a/src/main/java/envoy/client/ui/Startup.java b/src/main/java/envoy/client/ui/Startup.java index 1a2c96e..ae01e82 100644 --- a/src/main/java/envoy/client/ui/Startup.java +++ b/src/main/java/envoy/client/ui/Startup.java @@ -2,6 +2,7 @@ package envoy.client.ui; import java.awt.EventQueue; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; @@ -76,7 +77,7 @@ public class Startup { logger.severe("User name is not set or empty. Exiting..."); System.exit(1); } - + // TODO: create dialog String pass = JOptionPane.showInputDialog("Enter password"); @@ -125,17 +126,17 @@ public class Startup { // Initialize chats in local database try { localDB.initializeDBFile(); - // TODO: localDB.loadChats(); + localDB.loadChats(); + } catch (FileNotFoundException e) { + // The local database file has not yet been created, probably first login } catch (Exception e) { e.printStackTrace(); JOptionPane.showMessageDialog(null, - "Error while loading local database: " + e.toString() + "\nChats will not be stored locally.", + "Error while loading local database: " + e.toString() + "\nChats might not be stored locally.", "Local DB error", JOptionPane.WARNING_MESSAGE); } - logger.info("Client user ID: " + client.getSender().getId()); - // Save all users to the local database if (client.isOnline()) localDB.setUsers(client.getUsers()); @@ -147,8 +148,13 @@ public class Startup { try { new StatusTrayIcon(chatWindow).show(); - // If the tray icon is supported and corresponding settings is set, hide the chat window on close - Settings.getInstance().getItems().get("onCloseMode").setChangeHandler((onCloseMode) -> chatWindow.setDefaultCloseOperation((boolean) onCloseMode ? JFrame.HIDE_ON_CLOSE : JFrame.EXIT_ON_CLOSE)); + // If the tray icon is supported and corresponding settings is set, hide the + // chat window on close + Settings.getInstance() + .getItems() + .get("onCloseMode") + .setChangeHandler((onCloseMode) -> chatWindow + .setDefaultCloseOperation((boolean) onCloseMode ? JFrame.HIDE_ON_CLOSE : JFrame.EXIT_ON_CLOSE)); } catch (EnvoyException e) { logger.warning("The StatusTrayIcon is not supported on this platform!"); } @@ -160,13 +166,14 @@ public class Startup { // Save Settings and LocalDB on shutdown Runtime.getRuntime().addShutdownHook(new Thread(() -> { try { - logger.info("Saving local database..."); + logger.info("Closing connection..."); + client.close(); + + logger.info("Saving local database and settings..."); localDB.save(); - logger.info("Saving settings..."); Settings.getInstance().save(); - } catch (IOException e1) { - e1.printStackTrace(); - logger.log(Level.WARNING, "Unable to save the messages", e1); + } catch (IOException e) { + logger.log(Level.SEVERE, "Unable to save local files", e); } })); }