Make LocalDB Thread Safe and Simplify its API #38
@@ -1,4 +1,5 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
 | 
					<!DOCTYPE xml>
 | 
				
			||||||
<projectDescription>
 | 
					<projectDescription>
 | 
				
			||||||
	<name>client</name>
 | 
						<name>client</name>
 | 
				
			||||||
	<comment></comment>
 | 
						<comment></comment>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,8 @@
 | 
				
			|||||||
package envoy.client.data.commands;
 | 
					package envoy.client.data.commands;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.util.*;
 | 
					import java.util.*;
 | 
				
			||||||
import java.util.function.Function;
 | 
					import java.util.function.Consumer;
 | 
				
			||||||
import java.util.logging.Level;
 | 
					import java.util.logging.*;
 | 
				
			||||||
import java.util.logging.Logger;
 | 
					 | 
				
			||||||
import java.util.regex.Pattern;
 | 
					import java.util.regex.Pattern;
 | 
				
			||||||
import java.util.stream.Collectors;
 | 
					import java.util.stream.Collectors;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -198,13 +197,13 @@ public final class SystemCommandsMap {
 | 
				
			|||||||
	 *               are present
 | 
						 *               are present
 | 
				
			||||||
	 * @since Envoy Client v0.2-beta
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
	 */
 | 
						 */
 | 
				
			||||||
	public void requestRecommendations(String input, Function<Set<String>, Void> action) {
 | 
						public void requestRecommendations(String input, Consumer<Set<String>> action) {
 | 
				
			||||||
		final var partialCommand = getCommand(input);
 | 
							final var partialCommand = getCommand(input);
 | 
				
			||||||
		// Get the expected commands
 | 
							// Get the expected commands
 | 
				
			||||||
		final var recommendations = recommendCommands(partialCommand);
 | 
							final var recommendations = recommendCommands(partialCommand);
 | 
				
			||||||
		if (recommendations.isEmpty()) return;
 | 
							if (recommendations.isEmpty()) return;
 | 
				
			||||||
		// Execute the given action
 | 
							// Execute the given action
 | 
				
			||||||
		else action.apply(recommendations);
 | 
							else action.accept(recommendations);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ import java.util.logging.Level;
 | 
				
			|||||||
import javafx.application.Platform;
 | 
					import javafx.application.Platform;
 | 
				
			||||||
import javafx.fxml.FXMLLoader;
 | 
					import javafx.fxml.FXMLLoader;
 | 
				
			||||||
import javafx.scene.*;
 | 
					import javafx.scene.*;
 | 
				
			||||||
 | 
					import javafx.scene.input.*;
 | 
				
			||||||
import javafx.stage.Stage;
 | 
					import javafx.stage.Stage;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.client.data.Settings;
 | 
					import envoy.client.data.Settings;
 | 
				
			||||||
@@ -105,6 +106,17 @@ public final class SceneContext implements EventListener {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
			sceneStack.push(scene);
 | 
								sceneStack.push(scene);
 | 
				
			||||||
			stage.setScene(scene);
 | 
								stage.setScene(scene);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Adding the option to exit Linux-like with "Control" + "Q"
 | 
				
			||||||
 | 
								scene.getAccelerators()
 | 
				
			||||||
 | 
									.put(new KeyCodeCombination(KeyCode.Q, KeyCombination.CONTROL_DOWN),
 | 
				
			||||||
 | 
											() -> {
 | 
				
			||||||
 | 
												// Presumably no Settings are loaded in the login scene, hence Envoy is closed
 | 
				
			||||||
 | 
												// directly
 | 
				
			||||||
 | 
												if (sceneInfo != SceneInfo.LOGIN_SCENE && settings.isHideOnClose()) stage.setIconified(true);
 | 
				
			||||||
 | 
												else System.exit(0);
 | 
				
			||||||
 | 
											});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			// The LoginScene is the only scene not intended to be resized
 | 
								// The LoginScene is the only scene not intended to be resized
 | 
				
			||||||
			// As strange as it seems, this is needed as otherwise the LoginScene won't be
 | 
								// As strange as it seems, this is needed as otherwise the LoginScene won't be
 | 
				
			||||||
			// displayed on some OS (...Debian...)
 | 
								// displayed on some OS (...Debian...)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -67,7 +67,7 @@ public final class Startup extends Application {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// Initialize the local database
 | 
							// Initialize the local database
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			File localDBDir = new File(config.getHomeDirectory(), config.getLocalDB().getPath());
 | 
								final var localDBDir = new File(config.getHomeDirectory(), config.getLocalDB().getPath());
 | 
				
			||||||
			logger.info("Initializing LocalDB at " + localDBDir);
 | 
								logger.info("Initializing LocalDB at " + localDBDir);
 | 
				
			||||||
			localDB = new LocalDB(localDBDir);
 | 
								localDB = new LocalDB(localDBDir);
 | 
				
			||||||
		} catch (IOException | EnvoyException e) {
 | 
							} catch (IOException | EnvoyException e) {
 | 
				
			||||||
@@ -95,12 +95,10 @@ public final class Startup extends Application {
 | 
				
			|||||||
			if (!performHandshake(
 | 
								if (!performHandshake(
 | 
				
			||||||
					LoginCredentials.loginWithToken(localDB.getUser().getName(), localDB.getAuthToken(), VERSION, localDB.getLastSync())))
 | 
										LoginCredentials.loginWithToken(localDB.getUser().getName(), localDB.getAuthToken(), VERSION, localDB.getLastSync())))
 | 
				
			||||||
				sceneContext.load(SceneInfo.LOGIN_SCENE);
 | 
									sceneContext.load(SceneInfo.LOGIN_SCENE);
 | 
				
			||||||
		} else {
 | 
							} else 
 | 
				
			||||||
 | 
					 | 
				
			||||||
			// Load login scene
 | 
								// Load login scene
 | 
				
			||||||
			sceneContext.load(SceneInfo.LOGIN_SCENE);
 | 
								sceneContext.load(SceneInfo.LOGIN_SCENE);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
	 * Tries to perform a Handshake with the server.
 | 
						 * Tries to perform a Handshake with the server.
 | 
				
			||||||
@@ -122,9 +120,7 @@ public final class Startup extends Application {
 | 
				
			|||||||
				loadChatScene();
 | 
									loadChatScene();
 | 
				
			||||||
				client.initReceiver(localDB, cacheMap);
 | 
									client.initReceiver(localDB, cacheMap);
 | 
				
			||||||
				return true;
 | 
									return true;
 | 
				
			||||||
			} else {
 | 
								} else return false;
 | 
				
			||||||
				return false;
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		} catch (IOException | InterruptedException | TimeoutException e) {
 | 
							} catch (IOException | InterruptedException | TimeoutException e) {
 | 
				
			||||||
			logger.log(Level.INFO, "Could not connect to server. Entering offline mode...");
 | 
								logger.log(Level.INFO, "Could not connect to server. Entering offline mode...");
 | 
				
			||||||
			return attemptOfflineMode(credentials.getIdentifier());
 | 
								return attemptOfflineMode(credentials.getIdentifier());
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,6 +30,7 @@ import envoy.client.data.commands.*;
 | 
				
			|||||||
import envoy.client.event.*;
 | 
					import envoy.client.event.*;
 | 
				
			||||||
import envoy.client.net.*;
 | 
					import envoy.client.net.*;
 | 
				
			||||||
import envoy.client.ui.*;
 | 
					import envoy.client.ui.*;
 | 
				
			||||||
 | 
					import envoy.client.ui.custom.TextInputContextMenu;
 | 
				
			||||||
import envoy.client.ui.listcell.*;
 | 
					import envoy.client.ui.listcell.*;
 | 
				
			||||||
import envoy.client.util.ReflectionUtil;
 | 
					import envoy.client.util.ReflectionUtil;
 | 
				
			||||||
import envoy.data.*;
 | 
					import envoy.data.*;
 | 
				
			||||||
@@ -168,6 +169,11 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
		messageList.setCellFactory(MessageListCell::new);
 | 
							messageList.setCellFactory(MessageListCell::new);
 | 
				
			||||||
		chatList.setCellFactory(new ListCellFactory<>(ChatControl::new));
 | 
							chatList.setCellFactory(new ListCellFactory<>(ChatControl::new));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// JavaFX provides an internal way of populating the context menu of a textarea.
 | 
				
			||||||
 | 
							// We, however, need additional functionality.
 | 
				
			||||||
 | 
							messageTextArea.setContextMenu(new TextInputContextMenu(messageTextArea, e -> checkKeyCombination(null)));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Set the icons of buttons and image views
 | 
				
			||||||
		settingsButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("settings", DEFAULT_ICON_SIZE)));
 | 
							settingsButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("settings", DEFAULT_ICON_SIZE)));
 | 
				
			||||||
		voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
 | 
							voiceButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("microphone", DEFAULT_ICON_SIZE)));
 | 
				
			||||||
		attachmentButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("attachment", DEFAULT_ICON_SIZE)));
 | 
							attachmentButton.setGraphic(new ImageView(IconUtil.loadIconThemeSensitive("attachment", DEFAULT_ICON_SIZE)));
 | 
				
			||||||
@@ -210,7 +216,7 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
	@Event(eventType = BackEvent.class)
 | 
						@Event(eventType = BackEvent.class)
 | 
				
			||||||
	private void onBackEvent() { tabPane.getSelectionModel().select(Tabs.CONTACT_LIST.ordinal()); }
 | 
						private void onBackEvent() { tabPane.getSelectionModel().select(Tabs.CONTACT_LIST.ordinal()); }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Event
 | 
						@Event(includeSubtypes = true)
 | 
				
			||||||
	private void onMessage(Message message) {
 | 
						private void onMessage(Message message) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// The sender of the message is the recipient of the chat
 | 
							// The sender of the message is the recipient of the chat
 | 
				
			||||||
@@ -542,16 +548,23 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
	 */
 | 
						 */
 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private void checkKeyCombination(KeyEvent e) {
 | 
						private void checkKeyCombination(KeyEvent e) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Checks whether the text is too long
 | 
							// Checks whether the text is too long
 | 
				
			||||||
		messageTextUpdated();
 | 
							messageTextUpdated();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Sending an IsTyping event if none has been sent for
 | 
							// Sending an IsTyping event if none has been sent for
 | 
				
			||||||
		// IsTyping#millisecondsActive
 | 
							// IsTyping#millisecondsActive
 | 
				
			||||||
		if (currentChat.getLastWritingEvent() + IsTyping.millisecondsActive <= System.currentTimeMillis()) {
 | 
							if (currentChat.getLastWritingEvent() + IsTyping.millisecondsActive <= System.currentTimeMillis()) {
 | 
				
			||||||
			eventBus.dispatch(new SendEvent(new IsTyping(getChatID(), currentChat.getRecipient().getID())));
 | 
								eventBus.dispatch(new SendEvent(new IsTyping(getChatID(), currentChat.getRecipient().getID())));
 | 
				
			||||||
			currentChat.lastWritingEventWasNow();
 | 
								currentChat.lastWritingEventWasNow();
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		// Automatic sending of messages via (ctrl +) enter
 | 
					
 | 
				
			||||||
		checkPostConditions(e);
 | 
							// KeyPressed will be called before the char has been added to the text, hence
 | 
				
			||||||
 | 
							// this is needed for the first char
 | 
				
			||||||
 | 
							if (messageTextArea.getText().length() == 1 && e != null) checkPostConditions(e);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// This is needed for the messageTA context menu
 | 
				
			||||||
 | 
							else if (e == null) checkPostConditions(false);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	/**
 | 
						/**
 | 
				
			||||||
@@ -572,8 +585,25 @@ public final class ChatScene implements EventListener, Restorable {
 | 
				
			|||||||
	 */
 | 
						 */
 | 
				
			||||||
	@FXML
 | 
						@FXML
 | 
				
			||||||
	private void checkPostConditions(KeyEvent e) {
 | 
						private void checkPostConditions(KeyEvent e) {
 | 
				
			||||||
		checkPostConditions(settings.isEnterToSend() && e.getCode() == KeyCode.ENTER
 | 
							final var	enterPressed	= e.getCode() == KeyCode.ENTER;
 | 
				
			||||||
				|| !settings.isEnterToSend() && e.getCode() == KeyCode.ENTER && e.isControlDown());
 | 
							final var	messagePosted	= enterPressed ? settings.isEnterToSend() ? !e.isControlDown() : e.isControlDown() : false;
 | 
				
			||||||
 | 
							if (messagePosted) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								// Removing an inserted line break if added by pressing enter
 | 
				
			||||||
 | 
								final var	text			= messageTextArea.getText();
 | 
				
			||||||
 | 
								final var	textPosition	= messageTextArea.getCaretPosition() - 1;
 | 
				
			||||||
 | 
								if (!e.isControlDown() && !text.isEmpty() && text.charAt(textPosition) == '\n')
 | 
				
			||||||
 | 
									messageTextArea.setText(new StringBuilder(text).deleteCharAt(textPosition).toString());
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// if control is pressed, the enter press is originally invalidated. Here it'll
 | 
				
			||||||
 | 
							// be inserted again
 | 
				
			||||||
 | 
							else if (enterPressed && e.isControlDown()) {
 | 
				
			||||||
 | 
								var caretPosition = messageTextArea.getCaretPosition();
 | 
				
			||||||
 | 
								messageTextArea.setText(new StringBuilder(messageTextArea.getText()).insert(caretPosition, '\n').toString());
 | 
				
			||||||
 | 
								messageTextArea.positionCaret(++caretPosition);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							checkPostConditions(messagePosted);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	private void checkPostConditions(boolean postMessage) {
 | 
						private void checkPostConditions(boolean postMessage) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,109 @@
 | 
				
			|||||||
 | 
					package envoy.client.ui.custom;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import java.util.function.Consumer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import javafx.event.*;
 | 
				
			||||||
 | 
					import javafx.scene.control.*;
 | 
				
			||||||
 | 
					import javafx.scene.input.Clipboard;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Displays a context menu that offers an additional option when one of
 | 
				
			||||||
 | 
					 * its menu items has been clicked.
 | 
				
			||||||
 | 
					 * <p>
 | 
				
			||||||
 | 
					 * Current options are:
 | 
				
			||||||
 | 
					 * <ul>
 | 
				
			||||||
 | 
					 * <li>undo</li>
 | 
				
			||||||
 | 
					 * <li>redo</li>
 | 
				
			||||||
 | 
					 * <li>cut</li>
 | 
				
			||||||
 | 
					 * <li>copy</li>
 | 
				
			||||||
 | 
					 * <li>paste</li>
 | 
				
			||||||
 | 
					 * <li>delete</li>
 | 
				
			||||||
 | 
					 * <li>clear</li>
 | 
				
			||||||
 | 
					 * <li>Select all</li>
 | 
				
			||||||
 | 
					 * </ul>
 | 
				
			||||||
 | 
					 * <p>
 | 
				
			||||||
 | 
					 * Project: <strong>client</strong><br>
 | 
				
			||||||
 | 
					 * File: <strong>TextInputContextMenu.java</strong><br>
 | 
				
			||||||
 | 
					 * Created: <strong>20.09.2020</strong><br>
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * @author Leon Hofmeister
 | 
				
			||||||
 | 
					 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
					 * @apiNote please refrain from using
 | 
				
			||||||
 | 
					 *          {@link ContextMenu#setOnShowing(EventHandler)} as this is already
 | 
				
			||||||
 | 
					 *          used by this component
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					public class TextInputContextMenu extends ContextMenu {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private final MenuItem	undoMI		= new MenuItem("Undo");
 | 
				
			||||||
 | 
						private final MenuItem	redoMI		= new MenuItem("Redo");
 | 
				
			||||||
 | 
						private final MenuItem	cutMI		= new MenuItem("Cut");
 | 
				
			||||||
 | 
						private final MenuItem	copyMI		= new MenuItem("Copy");
 | 
				
			||||||
 | 
						private final MenuItem	pasteMI		= new MenuItem("Paste");
 | 
				
			||||||
 | 
						private final MenuItem	deleteMI	= new MenuItem("Delete selection");
 | 
				
			||||||
 | 
						private final MenuItem	clearMI		= new MenuItem("Clear");
 | 
				
			||||||
 | 
						private final MenuItem	selectAllMI	= new MenuItem("Select all");
 | 
				
			||||||
 | 
						private final MenuItem	separatorMI	= new SeparatorMenuItem();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/**
 | 
				
			||||||
 | 
						 * Creates a new {@code TextInputContextMenu} with an optional action when
 | 
				
			||||||
 | 
						 * this menu was clicked. Currently shows:
 | 
				
			||||||
 | 
						 * <ul>
 | 
				
			||||||
 | 
						 * <li>undo</li>
 | 
				
			||||||
 | 
						 * <li>redo</li>
 | 
				
			||||||
 | 
						 * <li>cut</li>
 | 
				
			||||||
 | 
						 * <li>copy</li>
 | 
				
			||||||
 | 
						 * <li>paste</li>
 | 
				
			||||||
 | 
						 * <li>delete</li>
 | 
				
			||||||
 | 
						 * <li>clear</li>
 | 
				
			||||||
 | 
						 * <li>Select all</li>
 | 
				
			||||||
 | 
						 * </ul>
 | 
				
			||||||
 | 
						 *
 | 
				
			||||||
 | 
						 * @param control         the text input component to display this
 | 
				
			||||||
 | 
						 *                        {@code ContextMenu}
 | 
				
			||||||
 | 
						 * @param menuItemClicked the second action to perform when a menu item of this
 | 
				
			||||||
 | 
						 *                        context menu has been clicked
 | 
				
			||||||
 | 
						 * @since Envoy Client v0.2-beta
 | 
				
			||||||
 | 
						 * @apiNote please refrain from using
 | 
				
			||||||
 | 
						 *          {@link ContextMenu#setOnShowing(EventHandler)} as this is already
 | 
				
			||||||
 | 
						 *          used by this component
 | 
				
			||||||
 | 
						 */
 | 
				
			||||||
 | 
						public TextInputContextMenu(TextInputControl control, Consumer<ActionEvent> menuItemClicked) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Define the actions when clicked
 | 
				
			||||||
 | 
							undoMI.setOnAction(addAction(e -> control.undo(), menuItemClicked));
 | 
				
			||||||
 | 
							redoMI.setOnAction(addAction(e -> control.redo(), menuItemClicked));
 | 
				
			||||||
 | 
							cutMI.setOnAction(addAction(e -> control.cut(), menuItemClicked));
 | 
				
			||||||
 | 
							copyMI.setOnAction(addAction(e -> control.copy(), menuItemClicked));
 | 
				
			||||||
 | 
							pasteMI.setOnAction(addAction(e -> control.paste(), menuItemClicked));
 | 
				
			||||||
 | 
							deleteMI.setOnAction(addAction(e -> control.replaceSelection(""), menuItemClicked));
 | 
				
			||||||
 | 
							clearMI.setOnAction(addAction(e -> control.setText(""), menuItemClicked));
 | 
				
			||||||
 | 
							selectAllMI.setOnAction(addAction(e -> control.selectAll(), menuItemClicked));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Define the times it will be disabled
 | 
				
			||||||
 | 
							undoMI.disableProperty().bind(control.undoableProperty().not());
 | 
				
			||||||
 | 
							redoMI.disableProperty().bind(control.redoableProperty().not());
 | 
				
			||||||
 | 
							cutMI.disableProperty().bind(control.selectedTextProperty().isEmpty());
 | 
				
			||||||
 | 
							copyMI.disableProperty().bind(control.selectedTextProperty().isEmpty());
 | 
				
			||||||
 | 
							deleteMI.disableProperty().bind(control.selectedTextProperty().isEmpty());
 | 
				
			||||||
 | 
							clearMI.disableProperty().bind(control.textProperty().isEmpty());
 | 
				
			||||||
 | 
							setOnShowing(e -> pasteMI.setDisable(!Clipboard.getSystemClipboard().hasString()));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							selectAllMI.getProperties().put("refreshMenu", Boolean.TRUE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Add all items to the ContextMenu
 | 
				
			||||||
 | 
							getItems().add(undoMI);
 | 
				
			||||||
 | 
							getItems().add(redoMI);
 | 
				
			||||||
 | 
							getItems().add(cutMI);
 | 
				
			||||||
 | 
							getItems().add(copyMI);
 | 
				
			||||||
 | 
							getItems().add(pasteMI);
 | 
				
			||||||
 | 
							getItems().add(separatorMI);
 | 
				
			||||||
 | 
							getItems().add(deleteMI);
 | 
				
			||||||
 | 
							getItems().add(clearMI);
 | 
				
			||||||
 | 
							getItems().add(separatorMI);
 | 
				
			||||||
 | 
							getItems().add(selectAllMI);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private EventHandler<ActionEvent> addAction(Consumer<ActionEvent> originalAction, Consumer<ActionEvent> additionalAction) {
 | 
				
			||||||
 | 
							return e -> { originalAction.accept(e); additionalAction.accept(e); };
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -23,7 +23,7 @@
 | 
				
			|||||||
		<dependency>
 | 
							<dependency>
 | 
				
			||||||
			<groupId>dev.kske</groupId>
 | 
								<groupId>dev.kske</groupId>
 | 
				
			||||||
			<artifactId>event-bus</artifactId>
 | 
								<artifactId>event-bus</artifactId>
 | 
				
			||||||
			<version>0.0.3</version>
 | 
								<version>0.0.4</version>
 | 
				
			||||||
		</dependency>
 | 
							</dependency>
 | 
				
			||||||
		<dependency>
 | 
							<dependency>
 | 
				
			||||||
			<groupId>org.junit.jupiter</groupId>
 | 
								<groupId>org.junit.jupiter</groupId>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,7 @@
 | 
				
			|||||||
package envoy.data;
 | 
					package envoy.data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import java.time.Instant;
 | 
					import java.time.Instant;
 | 
				
			||||||
import java.util.HashMap;
 | 
					import java.util.*;
 | 
				
			||||||
import java.util.Map;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
import envoy.data.Message.MessageStatus;
 | 
					import envoy.data.Message.MessageStatus;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -80,21 +79,15 @@ public final class MessageBuilder {
 | 
				
			|||||||
	 * Creates an instance of {@link Message} with the previously supplied values.
 | 
						 * Creates an instance of {@link Message} with the previously supplied values.
 | 
				
			||||||
	 * If a mandatory value is not set, a default value will be used instead:<br>
 | 
						 * If a mandatory value is not set, a default value will be used instead:<br>
 | 
				
			||||||
	 * <br>
 | 
						 * <br>
 | 
				
			||||||
	 * <table border="1">
 | 
						 * {@code date}
 | 
				
			||||||
	 * <tr>
 | 
						 * {@code Instant.now()} and {@code null} for {@code receivedDate} and
 | 
				
			||||||
	 * <td>{@code date}</td>
 | 
						 * {@code readDate}
 | 
				
			||||||
	 * <td>{@code Instant.now()} and {@code null} for {@code receivedDate} and
 | 
						 * <br>
 | 
				
			||||||
	 * {@code readDate}</td>
 | 
						 * {@code text}
 | 
				
			||||||
	 * <tr>
 | 
						 * {@code ""}
 | 
				
			||||||
	 * <tr>
 | 
						 * <br>
 | 
				
			||||||
	 * <td>{@code text}</td>
 | 
						 * {@code status}
 | 
				
			||||||
	 * <td>{@code ""}</td>
 | 
						 * {@code MessageStatus.WAITING}
 | 
				
			||||||
	 * <tr>
 | 
					 | 
				
			||||||
	 * <tr>
 | 
					 | 
				
			||||||
	 * <td>{@code status}</td>
 | 
					 | 
				
			||||||
	 * <td>{@code MessageStatus.WAITING}</td>
 | 
					 | 
				
			||||||
	 * <tr>
 | 
					 | 
				
			||||||
	 * </table>
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @return a new instance of {@link Message}
 | 
						 * @return a new instance of {@link Message}
 | 
				
			||||||
	 * @since Envoy Common v0.2-alpha
 | 
						 * @since Envoy Common v0.2-alpha
 | 
				
			||||||
@@ -111,16 +104,12 @@ public final class MessageBuilder {
 | 
				
			|||||||
	 * If a mandatory value is not set, a default value will be used
 | 
						 * If a mandatory value is not set, a default value will be used
 | 
				
			||||||
	 * instead:<br>
 | 
						 * instead:<br>
 | 
				
			||||||
	 * <br>
 | 
						 * <br>
 | 
				
			||||||
	 * <table border="1">
 | 
						 * {@code time stamp}
 | 
				
			||||||
	 * <tr>
 | 
						 * {@code Instant.now()}
 | 
				
			||||||
	 * <td>{@code time stamp}</td>
 | 
						 * <br>
 | 
				
			||||||
	 * <td>{@code Instant.now()}</td>
 | 
						 * {@code text}
 | 
				
			||||||
	 * <tr>
 | 
						 * {@code ""}
 | 
				
			||||||
	 * <tr>
 | 
						 * <br>
 | 
				
			||||||
	 * <td>{@code text}</td>
 | 
					 | 
				
			||||||
	 * <td>{@code ""}</td>
 | 
					 | 
				
			||||||
	 * <tr>
 | 
					 | 
				
			||||||
	 * </table>
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param group the {@link Group} that is used to fill the map of member
 | 
						 * @param group the {@link Group} that is used to fill the map of member
 | 
				
			||||||
	 *              statuses
 | 
						 *              statuses
 | 
				
			||||||
@@ -138,16 +127,11 @@ public final class MessageBuilder {
 | 
				
			|||||||
	 * values. If a mandatory value is not set, a default value will be used
 | 
						 * values. If a mandatory value is not set, a default value will be used
 | 
				
			||||||
	 * instead:<br>
 | 
						 * instead:<br>
 | 
				
			||||||
	 * <br>
 | 
						 * <br>
 | 
				
			||||||
	 * <table border="1">
 | 
						 * {@code time stamp}
 | 
				
			||||||
	 * <tr>
 | 
						 * {@code Instant.now()}
 | 
				
			||||||
	 * <td>{@code time stamp}</td>
 | 
						 * <br>
 | 
				
			||||||
	 * <td>{@code Instant.now()}</td>
 | 
						 * {@code text}
 | 
				
			||||||
	 * <tr>
 | 
						 * {@code ""}
 | 
				
			||||||
	 * <tr>
 | 
					 | 
				
			||||||
	 * <td>{@code text}</td>
 | 
					 | 
				
			||||||
	 * <td>{@code ""}</td>
 | 
					 | 
				
			||||||
	 * <tr>
 | 
					 | 
				
			||||||
	 * </table>
 | 
					 | 
				
			||||||
	 *
 | 
						 *
 | 
				
			||||||
	 * @param group          the {@link Group} that is used to fill the map of
 | 
						 * @param group          the {@link Group} that is used to fill the map of
 | 
				
			||||||
	 *                       member statuses
 | 
						 *                       member statuses
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user