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