From 272d3f08ef895274c246727fdf704a991e66948c Mon Sep 17 00:00:00 2001 From: delvh Date: Wed, 8 Jul 2020 21:31:02 +0200 Subject: [PATCH 1/7] Added context menu in MessageControl --- .../envoy/client/ui/controller/ChatScene.java | 23 +----- .../client/ui/listcell/MessageControl.java | 82 +++++++++++++++---- src/main/resources/fxml/ChatScene.fxml | 16 ---- 3 files changed, 67 insertions(+), 54 deletions(-) diff --git a/src/main/java/envoy/client/ui/controller/ChatScene.java b/src/main/java/envoy/client/ui/controller/ChatScene.java index 956ce21..4d585e4 100644 --- a/src/main/java/envoy/client/ui/controller/ChatScene.java +++ b/src/main/java/envoy/client/ui/controller/ChatScene.java @@ -425,25 +425,7 @@ public final class ChatScene implements Restorable { } // Context menu actions - - @FXML - private void copyMessage() { - try { - Toolkit.getDefaultToolkit() - .getSystemClipboard() - .setContents(new StringSelection(messageList.getSelectionModel().getSelectedItem().getText()), null); - } catch (final NullPointerException e) {} - } - - @FXML - private void deleteMessage() { try {} catch (final NullPointerException e) {} } - - @FXML - private void forwardMessage() { try {} catch (final NullPointerException e) {} } - - @FXML - private void quoteMessage() { try {} catch (final NullPointerException e) {} } - + @FXML private void deleteContact() { try {} catch (final NullPointerException e) {} } @@ -456,7 +438,4 @@ public final class ChatScene implements Restorable { updateRemainingCharsLabel(); postButton.setDisable(messageText.isBlank()); } - - @FXML - private void loadMessageInfoScene() { try {} catch (final NullPointerException e) {} } } diff --git a/src/main/java/envoy/client/ui/listcell/MessageControl.java b/src/main/java/envoy/client/ui/listcell/MessageControl.java index a5a9404..0628c7a 100644 --- a/src/main/java/envoy/client/ui/listcell/MessageControl.java +++ b/src/main/java/envoy/client/ui/listcell/MessageControl.java @@ -1,10 +1,16 @@ package envoy.client.ui.listcell; +import java.awt.Toolkit; +import java.awt.datatransfer.StringSelection; import java.time.format.DateTimeFormatter; import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; import javafx.geometry.Insets; +import javafx.scene.control.ContextMenu; import javafx.scene.control.Label; +import javafx.scene.control.MenuItem; import javafx.scene.image.Image; import javafx.scene.image.ImageView; import javafx.scene.layout.VBox; @@ -14,6 +20,7 @@ import envoy.client.ui.IconUtil; import envoy.data.Message; import envoy.data.Message.MessageStatus; import envoy.data.User; +import envoy.util.EnvoyLog; /** * This class formats a single {@link Message} into a UI component. @@ -25,48 +32,91 @@ import envoy.data.User; * @author Leon Hofmeister * @since Envoy Client v0.1-beta */ -public class MessageControl extends VBox { +public class MessageControl extends Label { private static User client; private static final DateTimeFormatter dateFormat = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"); private static final Map statusImages = IconUtil.loadByEnum(MessageStatus.class, 16); + private static final Logger logger = EnvoyLog.getLogger(MessageControl.class); + /** * * @param message the message that should be formatted * @since Envoy Client v0.1-beta */ public MessageControl(Message message) { - // Creating the underlying VBox, the dateLabel and the textLabel - super(new Label(dateFormat.format(message.getCreationDate()))); + // Creating the underlying VBox and the dateLabel + final var vbox = new VBox(new Label(dateFormat.format(message.getCreationDate()))); + + // Creating the actions for the MenuItems + final ContextMenu contextMenu = new ContextMenu(); + final MenuItem copyMenuItem = new MenuItem("Copy"); + final MenuItem deleteMenuItem = new MenuItem("Delete"); + final MenuItem forwardMenuItem = new MenuItem("Forward"); + final MenuItem quoteMenuItem = new MenuItem("Quote"); + final MenuItem infoMenuItem = new MenuItem("Info"); + copyMenuItem.setOnAction(e -> copyMessage(message)); + deleteMenuItem.setOnAction(e -> deleteMessage(message)); + forwardMenuItem.setOnAction(e -> forwardMessage(message)); + quoteMenuItem.setOnAction(e -> quoteMessage(message)); + infoMenuItem.setOnAction(e -> loadMessageInfoScene(message)); + contextMenu.getItems().addAll(copyMenuItem, deleteMenuItem, forwardMenuItem, quoteMenuItem, infoMenuItem); // Handling message attachment display - if (message.hasAttachment()) switch (message.getAttachment().getType()) { - case PICTURE: - break; - case VIDEO: - break; - case VOICE: - getChildren().add(new AudioControl(message.getAttachment().getData())); - break; - case DOCUMENT: - break; + if (message.hasAttachment()) { + switch (message.getAttachment().getType()) { + case PICTURE: + break; + case VIDEO: + break; + case VOICE: + vbox.getChildren().add(new AudioControl(message.getAttachment().getData())); + break; + case DOCUMENT: + break; + } + final var saveAttachment = new MenuItem("Save attachment"); + saveAttachment.setOnAction(e -> saveAttachment(message)); + contextMenu.getItems().add(saveAttachment); } - + // Creating the textLabel final var textLabel = new Label(message.getText()); textLabel.setWrapText(true); - getChildren().add(textLabel); + vbox.getChildren().add(textLabel); // Setting the message status icon and background color if (message.getRecipientID() != client.getID()) { final var statusIcon = new ImageView(statusImages.get(message.getStatus())); statusIcon.setPreserveRatio(true); - getChildren().add(statusIcon); + vbox.getChildren().add(statusIcon); getStyleClass().add("own-message"); } else getStyleClass().add("received-message"); // Adjusting height and weight of the cell to the corresponding ListView paddingProperty().setValue(new Insets(5, 20, 5, 20)); + setContextMenu(contextMenu); + setGraphic(vbox); } + // Context Menu actions + + private void copyMessage(Message message) { + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(message.getText()), null); + } + + private void deleteMessage(Message message) { logger.log(Level.FINEST, "message deletion was requested for " + message); } + + private void forwardMessage(Message message) { logger.log(Level.FINEST, "message forwarding was requested for " + message); } + + private void quoteMessage(Message message) { logger.log(Level.FINEST, "message quotation was requested for " + message); } + + private void loadMessageInfoScene(Message message) { logger.log(Level.FINEST, "message info scene was requested for " + message); } + + /** + * @param message + * @since Envoy Client v0.1-beta + */ + private void saveAttachment(Message message) { logger.log(Level.FINEST, "attachment saving was requested for " + message); } + /** * @param client the user who has logged in * @since Envoy Client v0.1-beta diff --git a/src/main/resources/fxml/ChatScene.fxml b/src/main/resources/fxml/ChatScene.fxml index 0ecdeb2..4a6ac3a 100644 --- a/src/main/resources/fxml/ChatScene.fxml +++ b/src/main/resources/fxml/ChatScene.fxml @@ -92,22 +92,6 @@ - - - - - - - - - - - Date: Wed, 8 Jul 2020 21:46:44 +0200 Subject: [PATCH 2/7] For the first time DELETED Javadoc Co-authored-by: CyB3RC0nN0R --- src/main/java/envoy/client/ui/listcell/MessageControl.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/envoy/client/ui/listcell/MessageControl.java b/src/main/java/envoy/client/ui/listcell/MessageControl.java index 0628c7a..54b1ad5 100644 --- a/src/main/java/envoy/client/ui/listcell/MessageControl.java +++ b/src/main/java/envoy/client/ui/listcell/MessageControl.java @@ -111,10 +111,6 @@ public class MessageControl extends Label { private void loadMessageInfoScene(Message message) { logger.log(Level.FINEST, "message info scene was requested for " + message); } - /** - * @param message - * @since Envoy Client v0.1-beta - */ private void saveAttachment(Message message) { logger.log(Level.FINEST, "attachment saving was requested for " + message); } /** From 31da9c4af6a102a8b904e50f9c88d4579a2802e7 Mon Sep 17 00:00:00 2001 From: delvh Date: Thu, 9 Jul 2020 09:12:41 +0200 Subject: [PATCH 3/7] Current working status --- src/main/java/envoy/client/net/Client.java | 80 ++++--------------- src/main/java/envoy/client/ui/Startup.java | 24 +++--- .../client/ui/controller/LoginScene.java | 77 +++++------------- 3 files changed, 48 insertions(+), 133 deletions(-) diff --git a/src/main/java/envoy/client/net/Client.java b/src/main/java/envoy/client/net/Client.java index 7348abe..50cf9dc 100644 --- a/src/main/java/envoy/client/net/Client.java +++ b/src/main/java/envoy/client/net/Client.java @@ -3,7 +3,9 @@ package envoy.client.net; import java.io.Closeable; import java.io.IOException; import java.net.Socket; +import java.util.Map; import java.util.concurrent.TimeoutException; +import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; @@ -38,6 +40,8 @@ public class Client implements Closeable { private Receiver receiver; private boolean online; + private Map, Cache> cacheMap; + // Asynchronously initialized during handshake private volatile User sender; private volatile boolean rejected; @@ -53,33 +57,14 @@ public class Client implements Closeable { * will block for up to 5 seconds. If the handshake does exceed this time limit, * an exception is thrown. * - * @param credentials the login credentials of the - * user - * @param receivedMessageCache a message cache containing all - * unread messages from the server - * that can be relayed after - * initialization - * @param receivedGroupMessageCache a groupMessage cache containing - * all unread groupMessages from - * the server that can be relayed - * after initialization - * @param receivedMessageStatusChangeCache an event cache containing all - * received - * messageStatusChangeEvents from - * the server that can be relayed - * after initialization - * @param receivedGroupMessageStatusChangeCache an event cache containing all - * received - * groupMessageStatusChangeEvents - * from the server that can be - * relayed after initialization + * @param credentials the login credentials of the user + * @param cacheMap the map of all caches needed * @throws TimeoutException if the server could not be reached * @throws IOException if the login credentials could not be written * @throws InterruptedException if the current thread is interrupted while * waiting for the handshake response */ - public void performHandshake(LoginCredentials credentials, Cache receivedMessageCache, Cache receivedGroupMessageCache, - Cache receivedMessageStatusChangeCache, Cache receivedGroupMessageStatusChangeCache) + public void performHandshake(LoginCredentials credentials, Map, Cache> cacheMap) throws TimeoutException, IOException, InterruptedException { if (online) throw new IllegalStateException("Handshake has already been performed successfully"); @@ -93,10 +78,7 @@ public class Client implements Closeable { // Register user creation processor, contact list processor and message cache receiver.registerProcessor(User.class, sender -> this.sender = sender); - receiver.registerProcessor(Message.class, receivedMessageCache); - receiver.registerProcessor(GroupMessage.class, receivedGroupMessageCache); - receiver.registerProcessor(MessageStatusChange.class, receivedMessageStatusChangeCache); - receiver.registerProcessor(GroupMessageStatusChange.class, receivedGroupMessageStatusChangeCache); + cacheMap.forEach((inputclass, cache) -> receiver.registerProcessor(inputclass, cache)); receiver.registerProcessor(HandshakeRejection.class, evt -> { rejected = true; eventBus.dispatch(evt); }); rejected = false; @@ -135,43 +117,14 @@ public class Client implements Closeable { * Initializes the {@link Receiver} used to process data sent from the server to * this client. * - * @param localDB the local database used to - * persist - * the current - * {@link IDGenerator} - * @param receivedMessageCache a message cache containing all - * unread - * messages - * from the server that can be - * relayed - * after - * initialization - * @param receivedGroupMessageCache a groupMessage cache containing - * all - * unread - * groupMessages - * from the server that can be - * relayed - * after - * initialization - * @param receivedMessageStatusChangeCache an event cache containing all - * received - * messageStatusChangeEvents - * from the server that can be - * relayed - * after initialization - * @param receivedGroupMessageStatusChangeCache an event cache containing all - * received - * groupMessageStatusChangeEvents - * from the server that can be - * relayed after initialization + * @param localDB the local database used to persist the current + * {@link IDGenerator} + * @param cacheMap the map of all caches needed * @throws IOException if no {@link IDGenerator} is present and none could be * requested from the server * @since Envoy Client v0.2-alpha */ - public void initReceiver(LocalDB localDB, Cache receivedMessageCache, Cache receivedGroupMessageCache, - Cache receivedMessageStatusChangeCache, Cache receivedGroupMessageStatusChangeCache) - throws IOException { + public void initReceiver(LocalDB localDB, Map, Cache> cacheMap) throws IOException { checkOnline(); // Process incoming messages @@ -188,12 +141,11 @@ public class Client implements Closeable { receiver.registerProcessor(GroupMessageStatusChange.class, groupMessageStatusChangeProcessor); // Relay cached unread messages and unread groupMessages - receivedMessageCache.setProcessor(receivedMessageProcessor); - receivedGroupMessageCache.setProcessor(receivedGroupMessageProcessor); - + cacheMap.get(Message.class).setProcessor((Consumer) receivedMessageProcessor); + cacheMap.get(GroupMessage.class).setProcessor((Consumer) receivedGroupMessageProcessor); // Process message status changes - receivedMessageStatusChangeCache.setProcessor(messageStatusChangeProcessor); - receivedGroupMessageStatusChangeCache.setProcessor(groupMessageStatusChangeProcessor); + cacheMap.get(MessageStatusChange.class).setProcessor((Consumer) messageStatusChangeProcessor); + cacheMap.get(GroupMessageStatusChange.class).setProcessor((Consumer) groupMessageStatusChangeProcessor); // Process user status changes receiver.registerProcessor(UserStatusChange.class, eventBus::dispatch); diff --git a/src/main/java/envoy/client/ui/Startup.java b/src/main/java/envoy/client/ui/Startup.java index 54a72d8..605501c 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.io.File; import java.io.IOException; +import java.util.HashMap; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; @@ -42,12 +43,8 @@ public final class Startup extends Application { */ public static final String VERSION = "0.1-beta"; - private LocalDB localDB; - private Client client; - private Cache messageCache; - private Cache groupMessageCache; - private Cache messageStatusCache; - private Cache groupMessageStatusCache; + private LocalDB localDB; + private Client client; private static final ClientConfig config = ClientConfig.getInstance(); private static final Logger logger = EnvoyLog.getLogger(Startup.class); @@ -101,19 +98,20 @@ public final class Startup extends Application { } // Initialize client and unread message cache - client = new Client(); - messageCache = new Cache<>(); - groupMessageCache = new Cache<>(); - messageStatusCache = new Cache<>(); - groupMessageStatusCache = new Cache<>(); + client = new Client(); + + final var cacheMap = new HashMap, Cache>(); + cacheMap.put(Message.class, new Cache()); + cacheMap.put(GroupMessage.class, new Cache()); + cacheMap.put(MessageStatusChange.class, new Cache()); + cacheMap.put(GroupMessageStatusChange.class, new Cache()); stage.setTitle("Envoy"); stage.getIcons().add(IconUtil.loadIcon("envoy_logo")); final var sceneContext = new SceneContext(stage); sceneContext.load(SceneInfo.LOGIN_SCENE); - sceneContext.getController() - .initializeData(client, localDB, messageCache, groupMessageCache, messageStatusCache, groupMessageStatusCache, sceneContext); + sceneContext.getController().initializeData(client, localDB, cacheMap, sceneContext); } /** diff --git a/src/main/java/envoy/client/ui/controller/LoginScene.java b/src/main/java/envoy/client/ui/controller/LoginScene.java index 897cd08..98f9940 100644 --- a/src/main/java/envoy/client/ui/controller/LoginScene.java +++ b/src/main/java/envoy/client/ui/controller/LoginScene.java @@ -2,6 +2,7 @@ package envoy.client.ui.controller; import java.io.FileNotFoundException; import java.io.IOException; +import java.util.Map; import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; @@ -16,9 +17,11 @@ import envoy.client.net.Client; import envoy.client.ui.ClearableTextField; import envoy.client.ui.SceneContext; import envoy.client.ui.Startup; -import envoy.data.*; +import envoy.data.LoginCredentials; +import envoy.data.User; import envoy.data.User.UserStatus; -import envoy.event.*; +import envoy.event.EventBus; +import envoy.event.HandshakeRejection; import envoy.exception.EnvoyException; import envoy.util.Bounds; import envoy.util.EnvoyLog; @@ -52,13 +55,10 @@ public final class LoginScene { @FXML private Label connectionLabel; - private Client client; - private LocalDB localDB; - private Cache receivedMessageCache; - private Cache receivedGroupMessageCache; - private Cache receivedMessageStatusChangeCache; - private Cache receivedGroupMessageStatusChangeCache; - private SceneContext sceneContext; + private Client client; + private LocalDB localDB; + private Map, Cache> cacheMap; + private SceneContext sceneContext; private static final Logger logger = EnvoyLog.getLogger(LoginScene.class); private static final EventBus eventBus = EventBus.getInstance(); @@ -75,41 +75,17 @@ public final class LoginScene { /** * Loads the login dialog using the FXML file {@code LoginDialog.fxml}. * - * @param client the client used to perform the - * handshake - * @param localDB the local database used for - * offline - * login - * @param receivedMessageCache the cache storing messages - * received - * during - * the handshake - * @param receivedGroupMessageCache the cache storing groupMessages - * received during the handshake - * @param receivedMessageStatusChangeCache the cache storing - * messageStatusChangeEvents - * received - * during handshake - * @param receivedGroupMessageStatusChangeCache the cache storing - * groupMessageStatusChangeEvents - * received - * during handshake - * @param sceneContext the scene context used to - * initialize - * the chat - * scene + * @param client the client used to perform the handshake + * @param localDB the local database used for offline login + * @param cacheMap the map of all caches needed + * @param sceneContext the scene context used to initialize the chat scene * @since Envoy Client v0.1-beta */ - public void initializeData(Client client, LocalDB localDB, Cache receivedMessageCache, Cache receivedGroupMessageCache, - Cache receivedMessageStatusChangeCache, Cache receivedGroupMessageStatusChangeCache, - SceneContext sceneContext) { - this.client = client; - this.localDB = localDB; - this.receivedMessageCache = receivedMessageCache; - this.receivedGroupMessageCache = receivedGroupMessageCache; - this.receivedMessageStatusChangeCache = receivedMessageStatusChangeCache; - this.receivedGroupMessageStatusChangeCache = receivedGroupMessageStatusChangeCache; - this.sceneContext = sceneContext; + public void initializeData(Client client, LocalDB localDB, Map, Cache> cacheMap, SceneContext sceneContext) { + this.client = client; + this.localDB = localDB; + this.cacheMap = cacheMap; + this.sceneContext = sceneContext; // Prepare handshake localDB.loadIDGenerator(); @@ -157,17 +133,9 @@ public final class LoginScene { private void performHandshake(LoginCredentials credentials) { try { - client.performHandshake(credentials, - receivedMessageCache, - receivedGroupMessageCache, - receivedMessageStatusChangeCache, - receivedGroupMessageStatusChangeCache); + client.performHandshake(credentials, cacheMap); if (client.isOnline()) { - client.initReceiver(localDB, - receivedMessageCache, - receivedGroupMessageCache, - receivedMessageStatusChangeCache, - receivedGroupMessageStatusChangeCache); + client.initReceiver(localDB, cacheMap); loadChatScene(); } } catch (IOException | InterruptedException | TimeoutException e) { @@ -230,9 +198,6 @@ public final class LoginScene { sceneContext.getController().initializeData(sceneContext, localDB, client, writeProxy); // Relay unread messages from cache - if (receivedMessageCache != null && client.isOnline()) receivedMessageCache.relay(); - if (receivedGroupMessageCache != null && client.isOnline()) receivedGroupMessageCache.relay(); - if (receivedMessageStatusChangeCache != null && client.isOnline()) receivedMessageStatusChangeCache.relay(); - if (receivedGroupMessageStatusChangeCache != null && client.isOnline()) receivedGroupMessageStatusChangeCache.relay(); + if (client.isOnline()) cacheMap.values().forEach(cache -> { if (cache != null) cache.relay(); }); } } From 9257f3fad957fe1990cfd8b68ed442d09a4be690 Mon Sep 17 00:00:00 2001 From: CyB3RC0nN0R Date: Thu, 9 Jul 2020 09:37:31 +0200 Subject: [PATCH 4/7] Simplify cache storage with a CacheMap class --- src/main/java/envoy/client/data/CacheMap.java | 46 +++++++++++++++++++ src/main/java/envoy/client/net/Client.java | 28 +++++------ src/main/java/envoy/client/net/Receiver.java | 12 +++-- src/main/java/envoy/client/ui/Startup.java | 3 +- .../client/ui/controller/LoginScene.java | 13 +++--- 5 files changed, 74 insertions(+), 28 deletions(-) create mode 100644 src/main/java/envoy/client/data/CacheMap.java diff --git a/src/main/java/envoy/client/data/CacheMap.java b/src/main/java/envoy/client/data/CacheMap.java new file mode 100644 index 0000000..462b966 --- /dev/null +++ b/src/main/java/envoy/client/data/CacheMap.java @@ -0,0 +1,46 @@ +package envoy.client.data; + +import java.util.HashMap; +import java.util.Map; + +/** + * Stores a heterogeneous map of {@link Cache} objects with different type + * parameters. + *

+ * Project: envoy-client
+ * File: CacheMap.java
+ * Created: 09.07.2020
+ * + * @author Kai S. K. Engelbart + * @since Envoy Client v0.1-beta + */ +public final class CacheMap { + + private final Map, Cache> map = new HashMap<>(); + + /** + * Adds a cache to the map. + * + * @param the type accepted by the cache + * @param key the class that maps to the cache + * @param cache the cache to store + * @since Envoy Client v0.1-beta + */ + public void put(Class key, Cache cache) { map.put(key, cache); } + + /** + * Returns a cache mapped by a class. + * + * @param the type accepted by the cache + * @param key the class that maps to the cache + * @return the cache + * @since Envoy Client v0.1-beta + */ + public Cache get(Class key) { return (Cache) map.get(key); } + + /** + * @return the map in which the caches are stored + * @since Envoy Client v0.1-beta + */ + public Map, Cache> getMap() { return map; } +} diff --git a/src/main/java/envoy/client/net/Client.java b/src/main/java/envoy/client/net/Client.java index 50cf9dc..f9ca937 100644 --- a/src/main/java/envoy/client/net/Client.java +++ b/src/main/java/envoy/client/net/Client.java @@ -3,13 +3,11 @@ package envoy.client.net; import java.io.Closeable; import java.io.IOException; import java.net.Socket; -import java.util.Map; import java.util.concurrent.TimeoutException; -import java.util.function.Consumer; import java.util.logging.Level; import java.util.logging.Logger; -import envoy.client.data.Cache; +import envoy.client.data.CacheMap; import envoy.client.data.ClientConfig; import envoy.client.data.LocalDB; import envoy.client.event.SendEvent; @@ -40,8 +38,6 @@ public class Client implements Closeable { private Receiver receiver; private boolean online; - private Map, Cache> cacheMap; - // Asynchronously initialized during handshake private volatile User sender; private volatile boolean rejected; @@ -64,7 +60,8 @@ public class Client implements Closeable { * @throws InterruptedException if the current thread is interrupted while * waiting for the handshake response */ - public void performHandshake(LoginCredentials credentials, Map, Cache> cacheMap) + public void performHandshake(LoginCredentials credentials, + CacheMap cacheMap) throws TimeoutException, IOException, InterruptedException { if (online) throw new IllegalStateException("Handshake has already been performed successfully"); @@ -78,7 +75,7 @@ public class Client implements Closeable { // Register user creation processor, contact list processor and message cache receiver.registerProcessor(User.class, sender -> this.sender = sender); - cacheMap.forEach((inputclass, cache) -> receiver.registerProcessor(inputclass, cache)); + receiver.registerProcessors(cacheMap.getMap()); receiver.registerProcessor(HandshakeRejection.class, evt -> { rejected = true; eventBus.dispatch(evt); }); rejected = false; @@ -124,7 +121,7 @@ public class Client implements Closeable { * requested from the server * @since Envoy Client v0.2-alpha */ - public void initReceiver(LocalDB localDB, Map, Cache> cacheMap) throws IOException { + public void initReceiver(LocalDB localDB, CacheMap cacheMap) throws IOException { checkOnline(); // Process incoming messages @@ -134,18 +131,17 @@ public class Client implements Closeable { final GroupMessageStatusChangeProcessor groupMessageStatusChangeProcessor = new GroupMessageStatusChangeProcessor(); receiver.registerProcessor(GroupMessage.class, receivedGroupMessageProcessor); - receiver.registerProcessor(Message.class, receivedMessageProcessor); - receiver.registerProcessor(MessageStatusChange.class, messageStatusChangeProcessor); - receiver.registerProcessor(GroupMessageStatusChange.class, groupMessageStatusChangeProcessor); + // Relay cached unread messages and unread groupMessages - cacheMap.get(Message.class).setProcessor((Consumer) receivedMessageProcessor); - cacheMap.get(GroupMessage.class).setProcessor((Consumer) receivedGroupMessageProcessor); - // Process message status changes - cacheMap.get(MessageStatusChange.class).setProcessor((Consumer) messageStatusChangeProcessor); - cacheMap.get(GroupMessageStatusChange.class).setProcessor((Consumer) groupMessageStatusChangeProcessor); + cacheMap.get(Message.class).setProcessor(receivedMessageProcessor); + cacheMap.get(GroupMessage.class).setProcessor(receivedGroupMessageProcessor); + + // Relay cached status changes + cacheMap.get(MessageStatusChange.class).setProcessor(messageStatusChangeProcessor); + cacheMap.get(GroupMessageStatusChange.class).setProcessor(groupMessageStatusChangeProcessor); // Process user status changes receiver.registerProcessor(UserStatusChange.class, eventBus::dispatch); diff --git a/src/main/java/envoy/client/net/Receiver.java b/src/main/java/envoy/client/net/Receiver.java index f90d10b..e325c4f 100644 --- a/src/main/java/envoy/client/net/Receiver.java +++ b/src/main/java/envoy/client/net/Receiver.java @@ -79,9 +79,7 @@ public class Receiver extends Thread { @SuppressWarnings("rawtypes") final Consumer processor = processors.get(obj.getClass()); if (processor == null) - logger.log(Level.WARNING, String.format( - "The received object has the %s for which no processor is defined.", - obj.getClass())); + logger.log(Level.WARNING, String.format("The received object has the %s for which no processor is defined.", obj.getClass())); else processor.accept(obj); } } catch (final SocketException e) { @@ -103,6 +101,14 @@ public class Receiver extends Thread { */ public void registerProcessor(Class processorClass, Consumer processor) { processors.put(processorClass, processor); } + /** + * Adds a map of object processors to this {@link Receiver}. + * + * @param processors the processors to add the processors to add + * @since Envoy Client v0.1-beta + */ + public void registerProcessors(Map, ? extends Consumer> processors) { this.processors.putAll(processors); } + /** * Removes all object processors registered at this {@link Receiver}. * diff --git a/src/main/java/envoy/client/ui/Startup.java b/src/main/java/envoy/client/ui/Startup.java index 605501c..99f163e 100644 --- a/src/main/java/envoy/client/ui/Startup.java +++ b/src/main/java/envoy/client/ui/Startup.java @@ -2,7 +2,6 @@ package envoy.client.ui; import java.io.File; import java.io.IOException; -import java.util.HashMap; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; @@ -100,7 +99,7 @@ public final class Startup extends Application { // Initialize client and unread message cache client = new Client(); - final var cacheMap = new HashMap, Cache>(); + final var cacheMap = new CacheMap(); cacheMap.put(Message.class, new Cache()); cacheMap.put(GroupMessage.class, new Cache()); cacheMap.put(MessageStatusChange.class, new Cache()); diff --git a/src/main/java/envoy/client/ui/controller/LoginScene.java b/src/main/java/envoy/client/ui/controller/LoginScene.java index 98f9940..7d7ba99 100644 --- a/src/main/java/envoy/client/ui/controller/LoginScene.java +++ b/src/main/java/envoy/client/ui/controller/LoginScene.java @@ -2,7 +2,6 @@ package envoy.client.ui.controller; import java.io.FileNotFoundException; import java.io.IOException; -import java.util.Map; import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; @@ -55,10 +54,10 @@ public final class LoginScene { @FXML private Label connectionLabel; - private Client client; - private LocalDB localDB; - private Map, Cache> cacheMap; - private SceneContext sceneContext; + private Client client; + private LocalDB localDB; + private CacheMap cacheMap; + private SceneContext sceneContext; private static final Logger logger = EnvoyLog.getLogger(LoginScene.class); private static final EventBus eventBus = EventBus.getInstance(); @@ -81,7 +80,7 @@ public final class LoginScene { * @param sceneContext the scene context used to initialize the chat scene * @since Envoy Client v0.1-beta */ - public void initializeData(Client client, LocalDB localDB, Map, Cache> cacheMap, SceneContext sceneContext) { + public void initializeData(Client client, LocalDB localDB, CacheMap cacheMap, SceneContext sceneContext) { this.client = client; this.localDB = localDB; this.cacheMap = cacheMap; @@ -198,6 +197,6 @@ public final class LoginScene { sceneContext.getController().initializeData(sceneContext, localDB, client, writeProxy); // Relay unread messages from cache - if (client.isOnline()) cacheMap.values().forEach(cache -> { if (cache != null) cache.relay(); }); + if (client.isOnline()) cacheMap.getMap().values().forEach(cache -> { if (cache != null) cache.relay(); }); } } From ab61d2dbfd00f8b713f99677993e5621d1ba62ce Mon Sep 17 00:00:00 2001 From: CyB3RC0nN0R Date: Thu, 9 Jul 2020 10:53:27 +0200 Subject: [PATCH 5/7] Add subclass consumption for Cache with CacheMap#getApplicable --- src/main/java/envoy/client/data/Cache.java | 2 +- src/main/java/envoy/client/data/CacheMap.java | 22 +++++++++- src/main/java/envoy/client/data/LocalDB.java | 40 ++++++------------- .../envoy/client/data/PersistentLocalDB.java | 9 ++--- .../java/envoy/client/net/WriteProxy.java | 15 +++---- .../client/ui/controller/LoginScene.java | 4 +- 6 files changed, 46 insertions(+), 46 deletions(-) diff --git a/src/main/java/envoy/client/data/Cache.java b/src/main/java/envoy/client/data/Cache.java index 786e96b..25b985e 100644 --- a/src/main/java/envoy/client/data/Cache.java +++ b/src/main/java/envoy/client/data/Cache.java @@ -20,7 +20,7 @@ import envoy.util.EnvoyLog; * @author Kai S. K. Engelbart * @since Envoy Client v0.3-alpha */ -public class Cache implements Consumer, Serializable { +public final class Cache implements Consumer, Serializable { private final Queue elements = new LinkedList<>(); private transient Consumer processor; diff --git a/src/main/java/envoy/client/data/CacheMap.java b/src/main/java/envoy/client/data/CacheMap.java index 462b966..8c1fcb2 100644 --- a/src/main/java/envoy/client/data/CacheMap.java +++ b/src/main/java/envoy/client/data/CacheMap.java @@ -1,5 +1,6 @@ package envoy.client.data; +import java.io.Serializable; import java.util.HashMap; import java.util.Map; @@ -14,10 +15,12 @@ import java.util.Map; * @author Kai S. K. Engelbart * @since Envoy Client v0.1-beta */ -public final class CacheMap { +public final class CacheMap implements Serializable { private final Map, Cache> map = new HashMap<>(); + private static final long serialVersionUID = 1L; + /** * Adds a cache to the map. * @@ -38,6 +41,23 @@ public final class CacheMap { */ public Cache get(Class key) { return (Cache) map.get(key); } + /** + * Returns a cache mapped by a class or any of its subclasses. + * + * @param the type accepted by the cache + * @param key the class that maps to the cache + * @return the cache + * @since Envoy Client v0.1-beta + */ + public Cache getApplicable(Class key) { + Cache cache = get(key); + if (cache == null) + for (var e : map.entrySet()) + if (e.getKey().isAssignableFrom(key)) + cache = (Cache) e.getValue(); + return cache; + } + /** * @return the map in which the caches are stored * @since Envoy Client v0.1-beta diff --git a/src/main/java/envoy/client/data/LocalDB.java b/src/main/java/envoy/client/data/LocalDB.java index 7899510..421cb38 100644 --- a/src/main/java/envoy/client/data/LocalDB.java +++ b/src/main/java/envoy/client/data/LocalDB.java @@ -20,12 +20,16 @@ import envoy.event.NameChange; */ public abstract class LocalDB { - protected User user; - protected Map users = new HashMap<>(); - protected List chats = new ArrayList<>(); - protected IDGenerator idGenerator; - protected Cache messageCache = new Cache<>(); - protected Cache statusCache = new Cache<>(); + protected User user; + protected Map users = new HashMap<>(); + protected List chats = new ArrayList<>(); + protected IDGenerator idGenerator; + protected CacheMap cacheMap = new CacheMap(); + + { + cacheMap.put(Message.class, new Cache<>()); + cacheMap.put(MessageStatusChange.class, new Cache<>()); + } /** * Initializes a storage space for a user-specific list of chats. @@ -139,28 +143,10 @@ public abstract class LocalDB { public boolean hasIDGenerator() { return idGenerator != null; } /** - * @return the offline message cache - * @since Envoy Client v0.3-alpha + * @return the cache map for messages and message status changes + * @since Envoy Client v0.1-beta */ - public Cache getMessageCache() { return messageCache; } - - /** - * @param messageCache the offline message cache to set - * @since Envoy Client v0.3-alpha - */ - public void setMessageCache(Cache messageCache) { this.messageCache = messageCache; } - - /** - * @return the offline status cache - * @since Envoy Client v0.3-alpha - */ - public Cache getStatusCache() { return statusCache; } - - /** - * @param statusCache the offline status cache to set - * @since Envoy Client v0.3-alpha - */ - public void setStatusCache(Cache statusCache) { this.statusCache = statusCache; } + public CacheMap getCacheMap() { return cacheMap; } /** * Searches for a message by ID. diff --git a/src/main/java/envoy/client/data/PersistentLocalDB.java b/src/main/java/envoy/client/data/PersistentLocalDB.java index cc6cad9..182a4c8 100644 --- a/src/main/java/envoy/client/data/PersistentLocalDB.java +++ b/src/main/java/envoy/client/data/PersistentLocalDB.java @@ -5,8 +5,6 @@ import java.util.ArrayList; import java.util.HashMap; import envoy.data.IDGenerator; -import envoy.data.Message; -import envoy.event.MessageStatusChange; import envoy.util.SerializationUtils; /** @@ -64,7 +62,7 @@ public final class PersistentLocalDB extends LocalDB { SerializationUtils.write(usersFile, users); // Save user data - if (user != null) SerializationUtils.write(userFile, chats, messageCache, statusCache); + if (user != null) SerializationUtils.write(userFile, chats, cacheMap); // Save id generator if (hasIDGenerator()) SerializationUtils.write(idGeneratorFile, idGenerator); @@ -76,9 +74,8 @@ public final class PersistentLocalDB extends LocalDB { @Override public void loadUserData() throws ClassNotFoundException, IOException { try (var in = new ObjectInputStream(new FileInputStream(userFile))) { - chats = (ArrayList) in.readObject(); - messageCache = (Cache) in.readObject(); - statusCache = (Cache) in.readObject(); + chats = (ArrayList) in.readObject(); + cacheMap = (CacheMap) in.readObject(); } } diff --git a/src/main/java/envoy/client/net/WriteProxy.java b/src/main/java/envoy/client/net/WriteProxy.java index 339f1f2..0eccf6a 100644 --- a/src/main/java/envoy/client/net/WriteProxy.java +++ b/src/main/java/envoy/client/net/WriteProxy.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; +import envoy.client.data.Cache; import envoy.client.data.LocalDB; import envoy.data.Message; import envoy.event.MessageStatusChange; @@ -43,7 +44,7 @@ public class WriteProxy { this.localDB = localDB; // Initialize cache processors for messages and message status change events - localDB.getMessageCache().setProcessor(msg -> { + localDB.getCacheMap().get(Message.class).setProcessor(msg -> { try { logger.log(Level.FINER, "Sending cached " + msg); client.sendMessage(msg); @@ -51,7 +52,7 @@ public class WriteProxy { logger.log(Level.SEVERE, "Could not send cached message: ", e); } }); - localDB.getStatusCache().setProcessor(evt -> { + localDB.getCacheMap().get(MessageStatusChange.class).setProcessor(evt -> { logger.log(Level.FINER, "Sending cached " + evt); try { client.sendEvent(evt); @@ -68,11 +69,7 @@ public class WriteProxy { * @since Envoy Client v0.3-alpha */ public void flushCache() { - // Send messages - localDB.getMessageCache().relay(); - - // Send message status change events - localDB.getStatusCache().relay(); + localDB.getCacheMap().getMap().values().forEach(Cache::relay); } /** @@ -85,7 +82,7 @@ public class WriteProxy { */ public void writeMessage(Message message) throws IOException { if (client.isOnline()) client.sendMessage(message); - else localDB.getMessageCache().accept(message); + else localDB.getCacheMap().getApplicable(Message.class).accept(message); } /** @@ -98,6 +95,6 @@ public class WriteProxy { */ public void writeMessageStatusChange(MessageStatusChange evt) throws IOException { if (client.isOnline()) client.sendEvent(evt); - else localDB.getStatusCache().accept(evt); + else localDB.getCacheMap().getApplicable(MessageStatusChange.class).accept(evt); } } diff --git a/src/main/java/envoy/client/ui/controller/LoginScene.java b/src/main/java/envoy/client/ui/controller/LoginScene.java index 7d7ba99..7dfb467 100644 --- a/src/main/java/envoy/client/ui/controller/LoginScene.java +++ b/src/main/java/envoy/client/ui/controller/LoginScene.java @@ -196,7 +196,7 @@ public final class LoginScene { sceneContext.load(SceneContext.SceneInfo.CHAT_SCENE); sceneContext.getController().initializeData(sceneContext, localDB, client, writeProxy); - // Relay unread messages from cache - if (client.isOnline()) cacheMap.getMap().values().forEach(cache -> { if (cache != null) cache.relay(); }); + // Relay the caches if online + if (client.isOnline()) cacheMap.getMap().values().forEach(Cache::relay); } } From f7ac79d872365851a4e75ee2977a4365e74c82e3 Mon Sep 17 00:00:00 2001 From: CyB3RC0nN0R Date: Thu, 9 Jul 2020 15:18:06 +0200 Subject: [PATCH 6/7] Fix hideous timing bug --- src/main/java/envoy/client/net/Client.java | 40 ++++++------------- .../client/ui/controller/LoginScene.java | 8 ++-- 2 files changed, 16 insertions(+), 32 deletions(-) diff --git a/src/main/java/envoy/client/net/Client.java b/src/main/java/envoy/client/net/Client.java index f9ca937..b63910d 100644 --- a/src/main/java/envoy/client/net/Client.java +++ b/src/main/java/envoy/client/net/Client.java @@ -7,9 +7,7 @@ import java.util.concurrent.TimeoutException; import java.util.logging.Level; import java.util.logging.Logger; -import envoy.client.data.CacheMap; -import envoy.client.data.ClientConfig; -import envoy.client.data.LocalDB; +import envoy.client.data.*; import envoy.client.event.SendEvent; import envoy.data.*; import envoy.event.*; @@ -60,9 +58,7 @@ public class Client implements Closeable { * @throws InterruptedException if the current thread is interrupted while * waiting for the handshake response */ - public void performHandshake(LoginCredentials credentials, - CacheMap cacheMap) - throws TimeoutException, IOException, InterruptedException { + public void performHandshake(LoginCredentials credentials, CacheMap cacheMap) throws TimeoutException, IOException, InterruptedException { if (online) throw new IllegalStateException("Handshake has already been performed successfully"); // Establish TCP connection @@ -104,9 +100,6 @@ public class Client implements Closeable { online = true; - // Remove all processors as they are only used during the handshake - receiver.removeAllProcessors(); - logger.log(Level.INFO, "Handshake completed."); } @@ -124,22 +117,23 @@ public class Client implements Closeable { public void initReceiver(LocalDB localDB, CacheMap cacheMap) throws IOException { checkOnline(); + // Remove all processors as they are only used during the handshake + receiver.removeAllProcessors(); + // Process incoming messages - final ReceivedMessageProcessor receivedMessageProcessor = new ReceivedMessageProcessor(); - final ReceivedGroupMessageProcessor receivedGroupMessageProcessor = new ReceivedGroupMessageProcessor(); - final MessageStatusChangeProcessor messageStatusChangeProcessor = new MessageStatusChangeProcessor(); - final GroupMessageStatusChangeProcessor groupMessageStatusChangeProcessor = new GroupMessageStatusChangeProcessor(); + final var receivedMessageProcessor = new ReceivedMessageProcessor(); + final var receivedGroupMessageProcessor = new ReceivedGroupMessageProcessor(); + final var messageStatusChangeProcessor = new MessageStatusChangeProcessor(); + final var groupMessageStatusChangeProcessor = new GroupMessageStatusChangeProcessor(); receiver.registerProcessor(GroupMessage.class, receivedGroupMessageProcessor); receiver.registerProcessor(Message.class, receivedMessageProcessor); receiver.registerProcessor(MessageStatusChange.class, messageStatusChangeProcessor); receiver.registerProcessor(GroupMessageStatusChange.class, groupMessageStatusChangeProcessor); - // Relay cached unread messages and unread groupMessages + // Relay caches cacheMap.get(Message.class).setProcessor(receivedMessageProcessor); cacheMap.get(GroupMessage.class).setProcessor(receivedGroupMessageProcessor); - - // Relay cached status changes cacheMap.get(MessageStatusChange.class).setProcessor(messageStatusChangeProcessor); cacheMap.get(GroupMessageStatusChange.class).setProcessor(groupMessageStatusChangeProcessor); @@ -172,18 +166,10 @@ public class Client implements Closeable { // Request a generator if none is present or the existing one is consumed if (!localDB.hasIDGenerator() || !localDB.getIDGenerator().hasNext()) requestIdGenerator(); - } - /** - * Creates a new write proxy that uses this client to communicate with the - * server. - * - * @param localDB the local database that the write proxy will use to access - * caches - * @return a new write proxy - * @since Envoy Client v0.3-alpha - */ - public WriteProxy createWriteProxy(LocalDB localDB) { return new WriteProxy(this, localDB); } + // Relay caches + cacheMap.getMap().values().forEach(Cache::relay); + } /** * Sends a message to the server. The message's status will be incremented once diff --git a/src/main/java/envoy/client/ui/controller/LoginScene.java b/src/main/java/envoy/client/ui/controller/LoginScene.java index 7dfb467..b29613a 100644 --- a/src/main/java/envoy/client/ui/controller/LoginScene.java +++ b/src/main/java/envoy/client/ui/controller/LoginScene.java @@ -13,6 +13,7 @@ import javafx.scene.control.Alert.AlertType; import envoy.client.data.*; import envoy.client.net.Client; +import envoy.client.net.WriteProxy; import envoy.client.ui.ClearableTextField; import envoy.client.ui.SceneContext; import envoy.client.ui.Startup; @@ -134,8 +135,8 @@ public final class LoginScene { try { client.performHandshake(credentials, cacheMap); if (client.isOnline()) { - client.initReceiver(localDB, cacheMap); loadChatScene(); + client.initReceiver(localDB, cacheMap); } } catch (IOException | InterruptedException | TimeoutException e) { logger.log(Level.INFO, "Could not connect to server. Entering offline mode..."); @@ -175,7 +176,7 @@ public final class LoginScene { } // Initialize write proxy - final var writeProxy = client.createWriteProxy(localDB); + final var writeProxy = new WriteProxy(client, localDB); localDB.synchronize(); @@ -195,8 +196,5 @@ public final class LoginScene { sceneContext.getStage().setMinWidth(350); sceneContext.load(SceneContext.SceneInfo.CHAT_SCENE); sceneContext.getController().initializeData(sceneContext, localDB, client, writeProxy); - - // Relay the caches if online - if (client.isOnline()) cacheMap.getMap().values().forEach(Cache::relay); } } From cc860d19c1050405c1033bb628abeedbca98ff21 Mon Sep 17 00:00:00 2001 From: CyB3RC0nN0R Date: Thu, 9 Jul 2020 14:07:34 +0000 Subject: [PATCH 7/7] Improve a comment Co-authored-by: delvh --- src/main/java/envoy/client/net/Client.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/envoy/client/net/Client.java b/src/main/java/envoy/client/net/Client.java index b63910d..afa423a 100644 --- a/src/main/java/envoy/client/net/Client.java +++ b/src/main/java/envoy/client/net/Client.java @@ -131,7 +131,7 @@ public class Client implements Closeable { receiver.registerProcessor(MessageStatusChange.class, messageStatusChangeProcessor); receiver.registerProcessor(GroupMessageStatusChange.class, groupMessageStatusChangeProcessor); - // Relay caches + // Relay cached messages and message status changes cacheMap.get(Message.class).setProcessor(receivedMessageProcessor); cacheMap.get(GroupMessage.class).setProcessor(receivedGroupMessageProcessor); cacheMap.get(MessageStatusChange.class).setProcessor(messageStatusChangeProcessor);