diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs
index cb635b1..262bd4f 100644
--- a/.settings/org.eclipse.jdt.core.prefs
+++ b/.settings/org.eclipse.jdt.core.prefs
@@ -1,8 +1,107 @@
eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
+org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
+org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
+org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
+org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
+org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
+org.eclipse.jdt.core.compiler.problem.APILeak=warning
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
+org.eclipse.jdt.core.compiler.problem.deadCode=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore
org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
+org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning
+org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
+org.eclipse.jdt.core.compiler.problem.nullReference=warning
+org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
+org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
+org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
+org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedImport=warning
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=warning
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning
+org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
org.eclipse.jdt.core.compiler.release=disabled
org.eclipse.jdt.core.compiler.source=1.8
diff --git a/src/main/java/envoy/client/LocalDB.java b/src/main/java/envoy/client/LocalDB.java
index b1668ad..3929315 100644
--- a/src/main/java/envoy/client/LocalDB.java
+++ b/src/main/java/envoy/client/LocalDB.java
@@ -1,305 +1,300 @@
-package envoy.client;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
-import java.time.Instant;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.logging.Logger;
-
-import javax.xml.datatype.DatatypeConfigurationException;
-import javax.xml.datatype.DatatypeFactory;
-
-import envoy.exception.EnvoyException;
-import envoy.schema.Message;
-import envoy.schema.Message.Metadata.MessageState;
-import envoy.schema.ObjectFactory;
-import envoy.schema.Sync;
-import envoy.schema.User;
-
-/**
- * Project: envoy-client
- * File: LocalDB.java
- * Created: 27.10.2019
- *
- * @author Kai S. K. Engelbart
- * @author Maximilian Käfer
- * @since Envoy v0.1-alpha
- */
-public class LocalDB {
-
- private File localDB;
- private User sender;
- private List chats = new ArrayList<>();
- private ObjectFactory objectFactory = new ObjectFactory();
- private DatatypeFactory datatypeFactory;
-
- private static final Logger logger = Logger.getLogger(LocalDB.class.getSimpleName());
-
- private Sync unreadMessagesSync = objectFactory.createSync();
- private Sync sync = objectFactory.createSync();
- private Sync readMessages = objectFactory.createSync();
-
- /**
- * Constructs an empty local database.
- *
- * @param sender the user that is logged in with this client
- * @since Envoy v0.1-alpha
- */
- public LocalDB(User sender) {
- this.sender = sender;
- try {
- datatypeFactory = DatatypeFactory.newInstance();
- } catch (DatatypeConfigurationException e) {
- e.printStackTrace();
- }
- }
-
- /**
- * Initializes the local database and fills it with values
- * if the user has already sent or received messages.
- *
- * @param localDBDir the directory where we wish to save/load the database from.
- * @throws EnvoyException if the directory selected is not an actual directory.
- * @since Envoy v0.1-alpha
- */
- public void initializeDBFile(File localDBDir) throws EnvoyException {
- if (localDBDir.exists() && !localDBDir.isDirectory())
- throw new EnvoyException(String.format("LocalDBDir '%s' is not a directory!", localDBDir.getAbsolutePath()));
- localDB = new File(localDBDir, sender.getID() + ".db");
- if (localDB.exists()) loadFromLocalDB();
- }
-
- /**
- * Saves the database into a file for future use.
- *
- * @throws IOException if something went wrong during saving
- * @since Envoy v0.1-alpha
- */
- public void saveToLocalDB() {
- try {
- localDB.getParentFile().mkdirs();
- localDB.createNewFile();
- } catch (IOException e) {
- e.printStackTrace();
- logger.warning("unable to save the messages");
- }
- try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(localDB))) {
- out.writeObject(chats);
- } catch (IOException ex) {
- ex.printStackTrace();
- logger.warning("unable to save the messages");
- }
- }
-
- /**
- * Loads all chats saved by Envoy for the client user.
- *
- * @throws EnvoyException if something fails while loading.
- * @since Envoy v0.1-alpha
- */
- @SuppressWarnings("unchecked")
- private void loadFromLocalDB() throws EnvoyException {
- try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(localDB))) {
- Object obj = in.readObject();
- if (obj instanceof ArrayList>) chats = (ArrayList) obj;
- } catch (ClassNotFoundException | IOException e) {
- throw new EnvoyException(e);
- }
- }
-
- /**
- * Creates a {@link Message} object serializable to XML.
- *
- * @param textContent The content (text) of the message
- * @param recipient The recipient of the message
- * @return prepared {@link Message} object
- * @since Envoy v0.1-alpha
- */
- public Message createMessage(String textContent, User recipient) {
- Message.Metadata metaData = objectFactory.createMessageMetadata();
- metaData.setSender(sender.getID());
- metaData.setRecipient(recipient.getID());
- metaData.setState(MessageState.WAITING);
- metaData.setDate(datatypeFactory.newXMLGregorianCalendar(Instant.now().toString()));
-
- Message.Content content = objectFactory.createMessageContent();
- content.setType("text");
- content.setText(textContent);
-
- Message message = objectFactory.createMessage();
- message.setMetadata(metaData);
- message.getContent().add(content);
-
- return message;
- }
-
- public Sync fillSync(long userId) {
- addWaitingMessagesToSync();
-
- sync.getMessages().addAll(readMessages.getMessages());
- readMessages.getMessages().clear();
-
- logger.info(String.format("Filled sync with %d messages.", sync.getMessages().size()));
- return sync;
- }
-
- public void applySync(Sync returnSync) {
- for (int i = 0; i < returnSync.getMessages().size(); i++) {
- if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0
- && returnSync.getMessages().get(i).getMetadata().getState() == MessageState.SENT) {
- // Update Local Messages with State WAITING (add Message ID and change State to
- // SENT)
- for (int j = 0; j < sync.getMessages().size(); j++) {
- if (j == i) {
- sync.getMessages().get(j).getMetadata().setMessageId(returnSync.getMessages().get(j).getMetadata().getMessageId());
- sync.getMessages().get(j).getMetadata().setState(returnSync.getMessages().get(j).getMetadata().getState());
- }
- }
- }
- if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0 && returnSync.getMessages().get(i).getMetadata().getSender() != 0
- && returnSync.getMessages().get(i).getMetadata().getState() == MessageState.RECEIVED) {
- // these are the unread Messages from the server
- unreadMessagesSync.getMessages().add(returnSync.getMessages().get(i));
- }
-
- if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0 && returnSync.getMessages().get(i).getMetadata().getSender() == 0
- && returnSync.getMessages().get(i).getMetadata().getState() == MessageState.RECEIVED) {
- // Update Messages in localDB to state RECEIVED
- for (int j = 0; j < getChats().size(); j++) {
- if (getChats().get(j).getRecipient().getID() == returnSync.getMessages().get(i).getMetadata().getRecipient()) {
- for (int k = 0; k < getChats().get(j).getModel().getSize(); k++) {
- if (getChats().get(j).getModel().get(k).getMetadata().getMessageId() == returnSync.getMessages()
- .get(i)
- .getMetadata()
- .getMessageId()) {
- // Update Message in LocalDB
- getChats().get(j).getModel().get(k).getMetadata().setState(returnSync.getMessages().get(j).getMetadata().getState());
- }
- }
- }
- }
-
- }
-
- if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0
- && returnSync.getMessages().get(i).getMetadata().getState() == MessageState.READ) {
- // Update local Messages to state READ
- logger.info("Message with ID: " + returnSync.getMessages().get(i).getMetadata().getMessageId()
- + "was initialized to be set to READ in localDB.");
- for (int j = 0; j < getChats().size(); j++) {
- if (getChats().get(j)
- .getRecipient()
- .getID() == returnSync.getMessages().get(i).getMetadata().getRecipient()) {
- logger.info("Chat with: " + getChats().get(j).getRecipient().getID() + "was selected.");
- for (int k = 0; k < getChats().get(j).getModel().getSize(); k++) {
- if (getChats().get(j).getModel().get(k).getMetadata().getMessageId() == returnSync.getMessages()
- .get(i)
- .getMetadata()
- .getMessageId()) {
- logger.info("Message with ID: "
- + getChats().get(j).getModel().get(k).getMetadata().getMessageId()
- + "was selected.");
- getChats().get(j)
- .getModel()
- .get(k)
- .getMetadata()
- .setState(returnSync.getMessages().get(i).getMetadata().getState());
- logger.info("Message State is now: "
- + getChats().get(j).getModel().get(k).getMetadata().getState().toString());
- }
- }
- }
- }
- }
- }
-
- // Updating UserStatus of all users in LocalDB
- for (int j = 0; j < returnSync.getUsers().size(); j++) {
- for (int k = 0; k < getChats().size(); k++) {
- if (getChats().get(k).getRecipient().getID() == returnSync.getUsers().get(j).getID()) {
-
- getChats().get(k).getRecipient().setStatus(returnSync.getUsers().get(j).getStatus());
- logger.info(getChats().get(k).getRecipient().getStatus().toString());
- }
- }
- }
-
- sync.getMessages().clear();
- sync.getUsers().clear();
- }
-
- /**
- * Adds the unread messages returned from the server in the latest sync to the
- * right chats in the LocalDB.
- *
- * @param localDB
- * @since Envoy v0.1-alpha
- */
- public void addUnreadMessagesToLocalDB() {
- Sync unreadMessages = unreadMessagesSync;
- for (int i = 0; i < unreadMessages.getMessages().size(); i++)
- for (int j = 0; j < getChats().size(); j++)
- if (getChats().get(j).getRecipient().getID() == unreadMessages.getMessages().get(i).getMetadata().getSender()) {
- getChats().get(j).appendMessage(unreadMessages.getMessages().get(i));
- }
- }
-
- /**
- * Changes all messages with state {@code RECEIVED} of a specific chat to state
- * {@code READ}.
- *
- * Adds these messages to the {@code readMessages} {@link Sync} object.
- *
- * @param currentChat
- * @since Envoy v0.1-alpha
- */
- public void setMessagesToRead(Chat currentChat) {
- for (int i = currentChat.getModel().size() - 1; i >= 0; --i)
- if (currentChat.getModel().get(i).getMetadata().getRecipient() != currentChat.getRecipient().getID())
- if (currentChat.getModel().get(i).getMetadata().getState() == MessageState.RECEIVED) {
- currentChat.getModel().get(i).getMetadata().setState(MessageState.READ);
- readMessages.getMessages().add(currentChat.getModel().get(i));
- } else break;
- }
-
- /**
- * Adds all messages with state {@code WAITING} from the {@link LocalDB} to the
- * {@link Sync} object.
- *
- * @since Envoy v0.1-alpha
- */
- private void addWaitingMessagesToSync() {
- for (Chat chat : getChats())
- for (int i = 0; i < chat.getModel().size(); i++)
- if (chat.getModel().get(i).getMetadata().getState() == MessageState.WAITING) {
- // addMessageToSync(localDB.getChats().get(i).getModel().get(j));
- logger.info("Got Waiting Message");
- sync.getMessages().add(chat.getModel().get(i));
- }
- }
-
- /**
- * Clears the {@code unreadMessagesSync} {@link Sync} object.
- *
- * @since Envoy v0.1-alpha
- */
- public void clearUnreadMessagesSync() { unreadMessagesSync.getMessages().clear(); }
-
- /**
- * @return all saves {@link Chat} objects that list the client user as the
- * client
- * @since Envoy v0.1-alpha
- **/
- public List getChats() { return chats; }
-
- /**
- * @return the {@link User} who initialized the local database
- * @since Envoy v0.1-alpha
- */
- public User getUser() { return sender; }
-}
+package envoy.client;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Logger;
+
+import javax.xml.datatype.DatatypeConfigurationException;
+import javax.xml.datatype.DatatypeFactory;
+
+import envoy.client.event.EventBus;
+import envoy.client.event.MessageCreationEvent;
+import envoy.exception.EnvoyException;
+import envoy.schema.Message;
+import envoy.schema.Message.Metadata.MessageState;
+import envoy.schema.ObjectFactory;
+import envoy.schema.Sync;
+import envoy.schema.User;
+
+/**
+ * Project: envoy-client
+ * File: LocalDB.java
+ * Created: 27.10.2019
+ *
+ * @author Kai S. K. Engelbart
+ * @author Maximilian Käfer
+ * @since Envoy v0.1-alpha
+ */
+public class LocalDB {
+
+ private File localDB;
+ private User sender;
+ private List chats = new ArrayList<>();
+ private ObjectFactory objectFactory = new ObjectFactory();
+ private DatatypeFactory datatypeFactory;
+
+ private Sync unreadMessagesSync = objectFactory.createSync();
+ private Sync sync = objectFactory.createSync();
+ private Sync readMessages = objectFactory.createSync();
+
+ private static final Logger logger = Logger.getLogger(LocalDB.class.getSimpleName());
+
+ /**
+ * Constructs an empty local database.
+ *
+ * @param sender the user that is logged in with this client
+ * @since Envoy v0.1-alpha
+ */
+ public LocalDB(User sender) {
+ this.sender = sender;
+ try {
+ datatypeFactory = DatatypeFactory.newInstance();
+ } catch (DatatypeConfigurationException e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Initializes the local database and fills it with values
+ * if the user has already sent or received messages.
+ *
+ * @param localDBDir the directory where we wish to save/load the database from.
+ * @throws EnvoyException if the directory selected is not an actual directory.
+ * @since Envoy v0.1-alpha
+ */
+ public void initializeDBFile(File localDBDir) throws EnvoyException {
+ if (localDBDir.exists() && !localDBDir.isDirectory())
+ throw new EnvoyException(String.format("LocalDBDir '%s' is not a directory!", localDBDir.getAbsolutePath()));
+ localDB = new File(localDBDir, sender.getID() + ".db");
+ if (localDB.exists()) loadFromLocalDB();
+ }
+
+ /**
+ * Saves the database into a file for future use.
+ *
+ * @throws IOException if something went wrong during saving
+ * @since Envoy v0.1-alpha
+ */
+ public void saveToLocalDB() {
+ try {
+ localDB.getParentFile().mkdirs();
+ localDB.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ logger.warning("unable to save the messages");
+ }
+ try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(localDB))) {
+ out.writeObject(chats);
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ logger.warning("unable to save the messages");
+ }
+ }
+
+ /**
+ * Loads all chats saved by Envoy for the client user.
+ *
+ * @throws EnvoyException if something fails while loading.
+ * @since Envoy v0.1-alpha
+ */
+ @SuppressWarnings("unchecked")
+ private void loadFromLocalDB() throws EnvoyException {
+ try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(localDB))) {
+ Object obj = in.readObject();
+ if (obj instanceof ArrayList>) chats = (ArrayList) obj;
+ } catch (ClassNotFoundException | IOException e) {
+ throw new EnvoyException(e);
+ }
+ }
+
+ /**
+ * Creates a {@link Message} object serializable to XML.
+ *
+ * @param textContent The content (text) of the message
+ * @param recipient The recipient of the message
+ * @return prepared {@link Message} object
+ * @since Envoy v0.1-alpha
+ */
+ public Message createMessage(String textContent, User recipient) {
+ Message.Metadata metaData = objectFactory.createMessageMetadata();
+ metaData.setSender(sender.getID());
+ metaData.setRecipient(recipient.getID());
+ metaData.setState(MessageState.WAITING);
+ metaData.setDate(datatypeFactory.newXMLGregorianCalendar(Instant.now().toString()));
+
+ Message.Content content = objectFactory.createMessageContent();
+ content.setType("text");
+ content.setText(textContent);
+
+ Message message = objectFactory.createMessage();
+ message.setMetadata(metaData);
+ message.getContent().add(content);
+
+ return message;
+ }
+
+ /**
+ * Creates a {@link Sync} object filled with the changes that occurred to the
+ * local database since the last synchronization.
+ *
+ * @param userId the ID of the user that is synchronized by this client
+ * @return {@link Sync} object filled with the current changes
+ */
+ public Sync fillSync(long userId) {
+ addWaitingMessagesToSync();
+
+ sync.getMessages().addAll(readMessages.getMessages());
+ readMessages.getMessages().clear();
+
+ logger.info(String.format("Filled sync with %d messages.", sync.getMessages().size()));
+ return sync;
+ }
+
+ /**
+ * Applies the changes carried by a {@link Sync} object to the local database
+ *
+ * @param returnSync the {@link Sync} object to apply
+ */
+ public void applySync(Sync returnSync) {
+ for (int i = 0; i < returnSync.getMessages().size(); i++) {
+
+ // The message has an ID
+ if (returnSync.getMessages().get(i).getMetadata().getMessageId() != 0) {
+
+ // Messages are processes differently corresponding to their state
+ switch (returnSync.getMessages().get(i).getMetadata().getState()) {
+ case SENT:
+ // Update previously waiting and now sent messages that were assigned an ID by
+ // the server
+ sync.getMessages().get(i).getMetadata().setMessageId(returnSync.getMessages().get(i).getMetadata().getMessageId());
+ sync.getMessages().get(i).getMetadata().setState(returnSync.getMessages().get(i).getMetadata().getState());
+ break;
+ case RECEIVED:
+ if (returnSync.getMessages().get(i).getMetadata().getSender() != 0) {
+ // these are the unread Messages from the server
+ unreadMessagesSync.getMessages().add(returnSync.getMessages().get(i));
+
+ // Create and dispatch message creation event
+ EventBus.getInstance().dispatch(new MessageCreationEvent(returnSync.getMessages().get(i)));
+ } else {
+ // Update Messages in localDB to state RECEIVED
+ for (Chat chat : getChats())
+ if (chat.getRecipient().getID() == returnSync.getMessages().get(i).getMetadata().getRecipient())
+ for (int j = 0; j < chat.getModel().getSize(); j++)
+ if (chat.getModel().get(j).getMetadata().getMessageId() == returnSync.getMessages()
+ .get(i)
+ .getMetadata()
+ .getMessageId())
+ chat.getModel().get(j).getMetadata().setState(returnSync.getMessages().get(i).getMetadata().getState());
+ }
+ break;
+ case READ:
+ // Update local Messages to state READ
+ logger.info("Message with ID: " + returnSync.getMessages().get(i).getMetadata().getMessageId()
+ + "was initialized to be set to READ in localDB.");
+ for (Chat chat : getChats())
+ if (chat.getRecipient().getID() == returnSync.getMessages().get(i).getMetadata().getRecipient()) {
+ logger.info("Chat with: " + chat.getRecipient().getID() + "was selected.");
+ for (int k = 0; k < chat.getModel().getSize(); k++)
+ if (chat.getModel().get(k).getMetadata().getMessageId() == returnSync.getMessages()
+ .get(i)
+ .getMetadata()
+ .getMessageId()) {
+ logger.info("Message with ID: " + chat.getModel().get(k).getMetadata().getMessageId() + "was selected.");
+ chat.getModel().get(k).getMetadata().setState(returnSync.getMessages().get(i).getMetadata().getState());
+ logger.info("Message State is now: " + chat.getModel().get(k).getMetadata().getState());
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ // Updating UserStatus of all users in LocalDB
+ for (User user : returnSync.getUsers())
+ for (Chat chat : getChats())
+ if (user.getID() == chat.getRecipient().getID()) {
+ chat.getRecipient().setStatus(user.getStatus());
+ logger.info(chat.getRecipient().getStatus().toString());
+ }
+
+ sync.getMessages().clear();
+ sync.getUsers().clear();
+ }
+
+ /**
+ * Adds the unread messages returned from the server in the latest sync to the
+ * right chats in the LocalDB.
+ *
+ * @param localDB
+ * @since Envoy v0.1-alpha
+ */
+ public void addUnreadMessagesToLocalDB() {
+ for (Message message : unreadMessagesSync.getMessages())
+ for (Chat chat : getChats())
+ if (message.getMetadata().getSender() == chat.getRecipient().getID()) {
+ chat.appendMessage(message);
+ break;
+ }
+ }
+
+ /**
+ * Changes all messages with state {@code RECEIVED} of a specific chat to state
+ * {@code READ}.
+ *
+ * Adds these messages to the {@code readMessages} {@link Sync} object.
+ *
+ * @param currentChat
+ * @since Envoy v0.1-alpha
+ */
+ public void setMessagesToRead(Chat currentChat) {
+ for (int i = currentChat.getModel().size() - 1; i >= 0; --i)
+ if (currentChat.getModel().get(i).getMetadata().getRecipient() != currentChat.getRecipient().getID())
+ if (currentChat.getModel().get(i).getMetadata().getState() == MessageState.RECEIVED) {
+ currentChat.getModel().get(i).getMetadata().setState(MessageState.READ);
+ readMessages.getMessages().add(currentChat.getModel().get(i));
+ } else break;
+ }
+
+ /**
+ * Adds all messages with state {@code WAITING} from the {@link LocalDB} to the
+ * {@link Sync} object.
+ *
+ * @since Envoy v0.1-alpha
+ */
+ private void addWaitingMessagesToSync() {
+ for (Chat chat : getChats())
+ for (int i = 0; i < chat.getModel().size(); i++)
+ if (chat.getModel().get(i).getMetadata().getState() == MessageState.WAITING) {
+ logger.info("Got Waiting Message");
+ sync.getMessages().add(chat.getModel().get(i));
+ }
+ }
+
+ /**
+ * Clears the {@code unreadMessagesSync} {@link Sync} object.
+ *
+ * @since Envoy v0.1-alpha
+ */
+ public void clearUnreadMessagesSync() { unreadMessagesSync.getMessages().clear(); }
+
+ /**
+ * @return all saved {@link Chat} objects that list the client user as the
+ * sender
+ * @since Envoy v0.1-alpha
+ **/
+ public List getChats() { return chats; }
+
+ /**
+ * @return the {@link User} who initialized the local database
+ * @since Envoy v0.1-alpha
+ */
+ public User getUser() { return sender; }
+}
\ No newline at end of file
diff --git a/src/main/java/envoy/client/event/Event.java b/src/main/java/envoy/client/event/Event.java
new file mode 100644
index 0000000..9db2477
--- /dev/null
+++ b/src/main/java/envoy/client/event/Event.java
@@ -0,0 +1,17 @@
+package envoy.client.event;
+
+/**
+ * Project: envoy-clientChess
+ * File: Event.javaEvent.java
+ * Created: 04.12.2019
+ *
+ * @author Kai S. K. Engelbart
+ * @since Envoy v0.2-alpha
+ */
+public interface Event {
+
+ /**
+ * @return the data associated with this event
+ */
+ T get();
+}
diff --git a/src/main/java/envoy/client/event/EventBus.java b/src/main/java/envoy/client/event/EventBus.java
new file mode 100644
index 0000000..f6da3f5
--- /dev/null
+++ b/src/main/java/envoy/client/event/EventBus.java
@@ -0,0 +1,71 @@
+package envoy.client.event;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * This class handles events by allowing {@link EventHandler} object to register
+ * themselves and then be notified about certain events dispatched by the event
+ * bus.
+ *
+ * The event bus is a singleton and can be used across the entire application to
+ * guarantee the propagation of events.
+ * Project: envoy-client
+ * File: EventBus.java
+ * Created: 04.12.2019
+ *
+ * @author Kai S. K. Engelbart
+ * @since Envoy v0.2-alpha
+ */
+public class EventBus {
+
+ /**
+ * Contains all {@link EventHandler} instances registered at this
+ * {@link EventBus}.
+ */
+ private List handlers = new ArrayList<>();
+
+ /**
+ * The singleton instance of this {@link EventBus} that is used across the
+ * entire application.
+ */
+ private static EventBus eventBus = new EventBus();
+
+ /**
+ * This constructor is not accessible from outside this class because a
+ * singleton instance of it is provided by the {@link EventBus#getInstance()}
+ * method.
+ */
+ private EventBus() {}
+
+ /**
+ * @return the singleton instance of the {@link EventBus}
+ * @since Envoy v0.2-alpha
+ */
+ public static EventBus getInstance() { return eventBus; }
+
+ /**
+ * Registers a list of {@link EventHandler} objects to be notified when a
+ * {@link Event} is dispatched that they are subscribed to.
+ *
+ * @param handlers the {@link EventHandler} objects to register
+ * @since Envoy v0.2-alpha
+ */
+ public void register(EventHandler... handlers) { this.handlers.addAll(Arrays.asList(handlers)); }
+
+ /**
+ * Dispatches a {@link Event} to every {@link EventHandler} subscribed to it.
+ *
+ * @param event the {@link Event} to dispatch
+ * @since Envoy v0.2-alpha
+ */
+ public void dispatch(Event> event) { handlers.stream().filter(h -> h.supports().contains(event.getClass())).forEach(h -> h.handle(event)); }
+
+ /**
+ * @return a list of all {@link EventHandler} instances currently registered at
+ * this {@link EventBus}
+ * @since Envoy v0.2-alpha
+ */
+ public List getHandlers() { return handlers; }
+}
diff --git a/src/main/java/envoy/client/event/EventHandler.java b/src/main/java/envoy/client/event/EventHandler.java
new file mode 100644
index 0000000..a6e5b81
--- /dev/null
+++ b/src/main/java/envoy/client/event/EventHandler.java
@@ -0,0 +1,25 @@
+package envoy.client.event;
+
+import java.util.Set;
+
+/**
+ * Project: envoy-clientChess
+ * File: EventHandler.javaEvent.java
+ * Created: 04.12.2019
+ *
+ * @author Kai S. K. Engelbart
+ */
+public interface EventHandler {
+
+ /**
+ * Consumes an event dispatched by the event bus.
+ *
+ * @param event The event dispatched by the event bus, only of supported type
+ */
+ void handle(Event> event);
+
+ /**
+ * @return A set of classes this class is supposed to handle in events
+ */
+ Set>> supports();
+}
diff --git a/src/main/java/envoy/client/event/MessageCreationEvent.java b/src/main/java/envoy/client/event/MessageCreationEvent.java
new file mode 100644
index 0000000..28f1b58
--- /dev/null
+++ b/src/main/java/envoy/client/event/MessageCreationEvent.java
@@ -0,0 +1,18 @@
+package envoy.client.event;
+
+import envoy.schema.Message;
+
+/**
+ * Project: envoy-client
+ * File: MessageCreationEvent.java
+ * Created: 4 Dec 2019
+ *
+ * @author Kai S. K. Engelbart
+ */
+public class MessageCreationEvent extends MessageEvent {
+
+ /**
+ * @param message the {@link Message} that has been created
+ */
+ public MessageCreationEvent(Message message) { super(message); }
+}
diff --git a/src/main/java/envoy/client/event/MessageEvent.java b/src/main/java/envoy/client/event/MessageEvent.java
new file mode 100644
index 0000000..014a7bb
--- /dev/null
+++ b/src/main/java/envoy/client/event/MessageEvent.java
@@ -0,0 +1,20 @@
+package envoy.client.event;
+
+import envoy.schema.Message;
+
+/**
+ * Project: envoy-client
+ * File: MessageCreationEvent.java
+ * Created: 4 Dec 2019
+ *
+ * @author Kai S. K. Engelbart
+ */
+public class MessageEvent implements Event {
+
+ protected final Message message;
+
+ public MessageEvent(Message message) { this.message = message; }
+
+ @Override
+ public Message get() { return message; }
+}
diff --git a/src/main/java/envoy/client/event/MessageModificationEvent.java b/src/main/java/envoy/client/event/MessageModificationEvent.java
new file mode 100644
index 0000000..0b83ef0
--- /dev/null
+++ b/src/main/java/envoy/client/event/MessageModificationEvent.java
@@ -0,0 +1,18 @@
+package envoy.client.event;
+
+import envoy.schema.Message;
+
+/**
+ * Project: envoy-client
+ * File: MessageModificationEvent.java
+ * Created: 4 Dec 2019
+ *
+ * @author Kai S. K. Engelbart
+ */
+public class MessageModificationEvent extends MessageEvent {
+
+ /**
+ * @param message the {@link Message} that has been modified
+ */
+ public MessageModificationEvent(Message message) { super(message); }
+}
diff --git a/src/main/java/envoy/client/ui/ChatWindow.java b/src/main/java/envoy/client/ui/ChatWindow.java
index 39a907f..e6e6872 100644
--- a/src/main/java/envoy/client/ui/ChatWindow.java
+++ b/src/main/java/envoy/client/ui/ChatWindow.java
@@ -38,7 +38,7 @@ import envoy.schema.User;
* Project: envoy-client
* File: ChatWindow.java
* Created: 28 Sep 2019
- *
+ *
* @author Kai S. K. Engelbart
* @author Maximilian Käfer
* @author Leon Hofmeister
@@ -125,13 +125,11 @@ public class ChatWindow extends JFrame {
@Override
public void keyReleased(KeyEvent e) {
-
- if (e.getKeyCode() == KeyEvent.VK_ENTER && ((SettingsScreen.enterToSend && e.getModifiersEx() == 0)
- || (e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK))) {
+ if (e.getKeyCode() == KeyEvent.VK_ENTER
+ && ((SettingsScreen.enterToSend && e.getModifiersEx() == 0) || (e.getModifiersEx() == KeyEvent.CTRL_DOWN_MASK))) {
postMessage(messageList);
}
-
}
});
// Checks for changed Message
@@ -219,11 +217,7 @@ public class ChatWindow extends JFrame {
final User user = selectedUserList.getSelectedValue();
client.setRecipient(user);
- currentChat = localDB.getChats()
- .stream()
- .filter(chat -> chat.getRecipient().getID() == user.getID())
- .findFirst()
- .get();
+ currentChat = localDB.getChats().stream().filter(chat -> chat.getRecipient().getID() == user.getID()).findFirst().get();
// Set all unread messages in the chat to read
readCurrentChat();
@@ -262,10 +256,8 @@ public class ChatWindow extends JFrame {
private void postMessage(JList messageList) {
if (!client.hasRecipient()) {
- JOptionPane.showMessageDialog(this,
- "Please select a recipient!",
- "Cannot send message",
- JOptionPane.INFORMATION_MESSAGE);
+ JOptionPane.showMessageDialog(this, "Please select a recipient!", "Cannot send message", JOptionPane.INFORMATION_MESSAGE);
+ return;
}
if (!messageEnterTextArea.getText().isEmpty()) try {
@@ -290,7 +282,7 @@ public class ChatWindow extends JFrame {
/**
* Initializes the elements of the user list by downloading them from the
* server.
- *
+ *
* @since Envoy v0.1-alpha
*/
private void loadUsersAndChats() {
@@ -311,7 +303,7 @@ public class ChatWindow extends JFrame {
/**
* Updates the data model and the UI repeatedly after a certain amount of
* time.
- *
+ *
* @param timeout the amount of time that passes between two requests sent to
* the server
* @since Envoy v0.1-alpha
@@ -321,8 +313,7 @@ public class ChatWindow extends JFrame {
new Thread(() -> {
// Synchronize
- localDB.applySync(
- client.sendSync(client.getSender().getID(), localDB.fillSync(client.getSender().getID())));
+ localDB.applySync(client.sendSync(client.getSender().getID(), localDB.fillSync(client.getSender().getID())));
// Process unread messages
localDB.addUnreadMessagesToLocalDB();
@@ -332,8 +323,7 @@ public class ChatWindow extends JFrame {
readCurrentChat();
// Update UI
- SwingUtilities
- .invokeLater(() -> { updateUserStates(); contentPane.revalidate(); contentPane.repaint(); });
+ SwingUtilities.invokeLater(() -> { updateUserStates(); contentPane.revalidate(); contentPane.repaint(); });
}).start();
}).start();
}
diff --git a/src/main/java/envoy/client/ui/Startup.java b/src/main/java/envoy/client/ui/Startup.java
index 4d6a198..473e912 100644
--- a/src/main/java/envoy/client/ui/Startup.java
+++ b/src/main/java/envoy/client/ui/Startup.java
@@ -72,8 +72,9 @@ public class Startup {
EventQueue.invokeLater(() -> {
try {
- ChatWindow frame = new ChatWindow(client, localDB);
- frame.setVisible(true);
+ ChatWindow chatWindow = new ChatWindow(client, localDB);
+ new StatusTrayIcon(chatWindow).show();
+ chatWindow.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
diff --git a/src/main/java/envoy/client/ui/StatusTrayIcon.java b/src/main/java/envoy/client/ui/StatusTrayIcon.java
new file mode 100644
index 0000000..01cc8d7
--- /dev/null
+++ b/src/main/java/envoy/client/ui/StatusTrayIcon.java
@@ -0,0 +1,133 @@
+package envoy.client.ui;
+
+import java.awt.AWTException;
+import java.awt.Image;
+import java.awt.MenuItem;
+import java.awt.PopupMenu;
+import java.awt.SystemTray;
+import java.awt.Toolkit;
+import java.awt.TrayIcon;
+import java.awt.TrayIcon.MessageType;
+import java.awt.Window;
+import java.awt.event.WindowAdapter;
+import java.awt.event.WindowEvent;
+import java.util.HashSet;
+import java.util.Set;
+
+import envoy.client.event.Event;
+import envoy.client.event.EventBus;
+import envoy.client.event.EventHandler;
+import envoy.client.event.MessageCreationEvent;
+import envoy.exception.EnvoyException;
+import envoy.schema.Message;
+
+/**
+ * Project: envoy-client
+ * File: StatusTrayIcon.java
+ * Created: 3 Dec 2019
+ *
+ * @author Kai S. K. Engelbart
+ * @since Envoy v0.2-alpha
+ */
+public class StatusTrayIcon implements EventHandler {
+
+ /**
+ * The {@link TrayIcon} provided by the System Tray API for controlling the
+ * system tray. This includes displaying the icon, but also creating
+ * notifications when new messages are received.
+ */
+ private TrayIcon trayIcon;
+
+ /**
+ * A received {@link Message} is only displayed as a system tray notification if
+ * this variable is set to {@code true}.
+ */
+ private boolean displayMessages = false;
+
+ /**
+ * Creates a {@link StatusTrayIcon} with the Envoy logo, a tool tip and a pop-up
+ * menu.
+ *
+ * @param focusTarget the {@link Window} which focus determines if message
+ * notifications are displayed
+ * @throws EnvoyException if the currently used OS does not support the System
+ * Tray API
+ * @since Envoy v0.2-alpha
+ */
+ public StatusTrayIcon(Window focusTarget) throws EnvoyException {
+ if (!SystemTray.isSupported()) throw new EnvoyException("The Envoy tray icon is not supported.");
+
+ ClassLoader loader = Thread.currentThread().getContextClassLoader();
+ Image img = Toolkit.getDefaultToolkit().createImage(loader.getResource("envoy_logo.png"));
+ trayIcon = new TrayIcon(img, "Envoy Client");
+ trayIcon.setImageAutoSize(true);
+ trayIcon.setToolTip("You are notified if you have unread messages.");
+
+ PopupMenu popup = new PopupMenu();
+
+ MenuItem exitMenuItem = new MenuItem("Exit");
+ exitMenuItem.addActionListener((evt) -> System.exit(0));
+ popup.add(exitMenuItem);
+
+ trayIcon.setPopupMenu(popup);
+
+ // Only display messages if the chat window is not focused
+ focusTarget.addWindowFocusListener(new WindowAdapter() {
+
+ @Override
+ public void windowGainedFocus(WindowEvent e) {
+ displayMessages = false;
+ }
+
+ @Override
+ public void windowLostFocus(WindowEvent e) {
+ displayMessages = true;
+ }
+ });
+
+ // Start processing message events
+ EventBus.getInstance().register(this);
+ }
+
+ /**
+ * Makes this {@link StatusTrayIcon} appear in the system tray.
+ *
+ * @throws EnvoyException if the status icon could not be attaches to the system
+ * tray for system-internal reasons
+ * @since Envoy v0.2-alpha
+ */
+ public void show() throws EnvoyException {
+ try {
+ SystemTray.getSystemTray().add(trayIcon);
+ } catch (AWTException e) {
+ throw new EnvoyException("Could not attach Envoy tray icon to system tray.", e);
+ }
+ }
+
+ /**
+ * Notifies the user of a message by displaying a pop-up every time a new
+ * message is received.
+ *
+ * @since Envoy v0.2-alpha
+ */
+ @Override
+ public void handle(Event> event) {
+ System.out.println("Message received. Displaying message: " + displayMessages);
+ if (displayMessages)
+ trayIcon.displayMessage("New message received", ((MessageCreationEvent) event).get().getContent().get(0).getText(), MessageType.INFO);
+ }
+
+ /**
+ * The {@link StatusTrayIcon} only reacts to {@link MessageCreationEvent}
+ * instances which signify newly received messages.
+ *
+ * @return A set with the single element {@code MessageCreationEvent.class}
+ * @since Envoy v0.2-alpha
+ */
+ @Override
+ public Set>> supports() {
+ Set>> supportedEvents = new HashSet<>();
+ supportedEvents.add(MessageCreationEvent.class);
+ return supportedEvents;
+ }
+}
diff --git a/src/main/resources/envoy_logo.png b/src/main/resources/envoy_logo.png
new file mode 100644
index 0000000..35ef7d9
Binary files /dev/null and b/src/main/resources/envoy_logo.png differ