From 847fcd2738ebfdbf595c95ad20baf49b9ba46089 Mon Sep 17 00:00:00 2001 From: Timothy Oluwole Date: Fri, 26 Feb 2021 01:43:43 +0000 Subject: [PATCH 1/2] Finished Coding Challenge --- chat.json | 1 + out.json | 1 + pom.xml | 10 +- src/main/java/META-INF/MANIFEST.MF | 3 + .../recruitment/mychat/Conversation.java | 82 +++- .../mychat/ConversationExporter.java | 179 +++++++-- .../ConversationExporterConfiguration.java | 25 +- .../mychat/ConversationFilterer.java | 66 ++++ .../mindlinksoft/recruitment/mychat/Main.java | 13 + .../recruitment/mychat/Message.java | 32 +- ...onversationExporterConfigurationTests.java | 57 +++ .../mychat/ConversationExporterTests.java | 351 ++++++++++++++++-- 12 files changed, 752 insertions(+), 68 deletions(-) create mode 100644 chat.json create mode 100644 out.json create mode 100644 src/main/java/META-INF/MANIFEST.MF create mode 100644 src/main/java/com/mindlinksoft/recruitment/mychat/ConversationFilterer.java create mode 100644 src/main/java/com/mindlinksoft/recruitment/mychat/Main.java create mode 100644 src/test/java/com/mindlinksoft/recruitment/mychat/ConversationExporterConfigurationTests.java diff --git a/chat.json b/chat.json new file mode 100644 index 00000000..cbd95a4f --- /dev/null +++ b/chat.json @@ -0,0 +1 @@ +{"name":"My Conversation","messages":[{"content":"Hello there!","timestamp":1448470901,"senderId":"bob"},{"content":"how are you?","timestamp":1448470905,"senderId":"mike"},{"content":"I\u0027m good thanks, do you like pie?","timestamp":1448470906,"senderId":"bob"},{"content":"no, let me ask Angus...","timestamp":1448470910,"senderId":"mike"},{"content":"Hell yes! Are we buying some pie?","timestamp":1448470912,"senderId":"angus"},{"content":"No, just want to know if there\u0027s anybody else in the pie society...","timestamp":1448470914,"senderId":"bob"},{"content":"YES! I\u0027m the head pie eater there...","timestamp":1448470915,"senderId":"angus"}]} \ No newline at end of file diff --git a/out.json b/out.json new file mode 100644 index 00000000..cbd95a4f --- /dev/null +++ b/out.json @@ -0,0 +1 @@ +{"name":"My Conversation","messages":[{"content":"Hello there!","timestamp":1448470901,"senderId":"bob"},{"content":"how are you?","timestamp":1448470905,"senderId":"mike"},{"content":"I\u0027m good thanks, do you like pie?","timestamp":1448470906,"senderId":"bob"},{"content":"no, let me ask Angus...","timestamp":1448470910,"senderId":"mike"},{"content":"Hell yes! Are we buying some pie?","timestamp":1448470912,"senderId":"angus"},{"content":"No, just want to know if there\u0027s anybody else in the pie society...","timestamp":1448470914,"senderId":"bob"},{"content":"YES! I\u0027m the head pie eater there...","timestamp":1448470915,"senderId":"angus"}]} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 577ea153..fc8f8659 100644 --- a/pom.xml +++ b/pom.xml @@ -7,6 +7,7 @@ com.mindlinksoft.recruitment.mychat my-chat 1.0-SNAPSHOT + my-chat 1.8 @@ -31,6 +32,11 @@ gson 2.5 + + commons-cli + commons-cli + 1.0 + @@ -57,9 +63,7 @@ - true - lib/ - com.mindlinksoft.recruitment.mychat.ConversationExporter + com.mindlinksoft.recruitment.mychat.Main diff --git a/src/main/java/META-INF/MANIFEST.MF b/src/main/java/META-INF/MANIFEST.MF new file mode 100644 index 00000000..8c702e52 --- /dev/null +++ b/src/main/java/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Main-Class: com.mindlinksoft.recruitment.mychat.Main + diff --git a/src/main/java/com/mindlinksoft/recruitment/mychat/Conversation.java b/src/main/java/com/mindlinksoft/recruitment/mychat/Conversation.java index d7809f00..a6a86ef4 100644 --- a/src/main/java/com/mindlinksoft/recruitment/mychat/Conversation.java +++ b/src/main/java/com/mindlinksoft/recruitment/mychat/Conversation.java @@ -1,6 +1,8 @@ package com.mindlinksoft.recruitment.mychat; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; /** * Represents the model of a conversation. @@ -9,12 +11,17 @@ public final class Conversation { /** * The name of the conversation. */ - public String name; + private String name; /** * The messages in the conversation. */ - public Collection messages; + private Collection messages; + + /** + * NEW FEATURE: The messaging activity of the users in the conversation. + */ + private List activity; /** * Initializes a new instance of the {@link Conversation} class. @@ -25,4 +32,75 @@ public Conversation(String name, Collection messages) { this.name = name; this.messages = messages; } + + public void trackActivity() { + activity = new ArrayList<>(); + + int i; + boolean senderActivityAlreadyTracked; + + for (Message message : messages) { + senderActivityAlreadyTracked = false; + for (i = 0; i < activity.size(); i++) { + // check if sender has already been tracked + if (message.getSenderId().equals(activity.get(i).getName())) { + senderActivityAlreadyTracked = true; + break; + } + } + // if the sender has not already been tracked, add to tracked activity + if (!senderActivityAlreadyTracked) { + activity.add(new SenderMessagePairing(message.getSenderId())); + } + // increment sender's message count + activity.get(i).incrementCount(); + } + + // sort senders in descending order of message count + activity.sort((a1, a2) -> (a2.count - a1.count)); + } + + // - Getters and Setters (changed the class variables to private) + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Collection getMessages() { + return messages; + } + + public void setMessages(Collection messages) { + this.messages = messages; + } + + public List getActivity() { + return activity; + } + + public void setActivity(List activity) { + this.activity = activity; + } + + class SenderMessagePairing { + private String name; + private int count; + + public SenderMessagePairing(String name) { + this.name = name; + this.count = 0; + } + + public void incrementCount() { + this.count++; + } + + public String getName() { + return this.name; + } + } } diff --git a/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationExporter.java b/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationExporter.java index bf5fa9bf..ec2b60d6 100644 --- a/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationExporter.java +++ b/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationExporter.java @@ -11,6 +11,7 @@ import java.lang.reflect.Type; import java.time.Instant; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -18,19 +19,22 @@ */ public class ConversationExporter { + private String user; // user whose messages are to be output + private String keyword; // keyword whose containing messages are to be output + private String[] blacklistedWords; // words to be redacted in the output + private boolean report; // flag determining whether or not to add the messaging activity to the output + /** - * The application entry point. - * @param args The command line arguments. - * @throws Exception Thrown when something bad happens. + * Run method from which the rest of the exporter starts */ - public static void main(String[] args) throws Exception { - // We use picocli to parse the command line - see https://picocli.info/ + public void run(String[] args) { ConversationExporterConfiguration configuration = new ConversationExporterConfiguration(); CommandLine cmd = new CommandLine(configuration); + try { ParseResult parseResult = cmd.parseArgs(args); - + if (parseResult.isUsageHelpRequested()) { cmd.usage(cmd.getOut()); System.exit(cmd.getCommandSpec().exitCodeOnUsageHelp()); @@ -43,39 +47,83 @@ public static void main(String[] args) throws Exception { return; } - ConversationExporter exporter = new ConversationExporter(); - - exporter.exportConversation(configuration.inputFilePath, configuration.outputFilePath); + // get parameters from command line + getCLIParameters(configuration); + // start exporting conversation + exportConversation(configuration.inputFilePath, configuration.outputFilePath); System.exit(cmd.getCommandSpec().exitCodeOnSuccess()); - } catch (ParameterException ex) { - cmd.getErr().println(ex.getMessage()); - if (!UnmatchedArgumentException.printSuggestions(ex, cmd.getErr())) { - ex.getCommandLine().usage(cmd.getErr()); + } catch (ParameterException e) { + cmd.getErr().println(e.getMessage()); + if (!UnmatchedArgumentException.printSuggestions(e, cmd.getErr())) { + e.getCommandLine().usage(cmd.getErr()); } System.exit(cmd.getCommandSpec().exitCodeOnInvalidInput()); - } catch (Exception ex) { - ex.printStackTrace(cmd.getErr()); + } catch (Exception e) { + e.printStackTrace(cmd.getErr()); System.exit(cmd.getCommandSpec().exitCodeOnExecutionException()); } } + /** * Exports the conversation at {@code inputFilePath} as JSON to {@code outputFilePath}. + * @param configuration The configuration which contains the paramters for exportation. + */ + public void getCLIParameters(ConversationExporterConfiguration configuration) { + // stores the arguments + this.user = configuration.user; + this.keyword = configuration.keyword; + this.blacklistedWords = configuration.blacklistedWords; + this.report = configuration.report; + } + + + /** + * Exports the conversation at {@code inputFilePath} as JSON to {@code outputFilePath}. Holds the sequence of stages of exportation. * @param inputFilePath The input file path. * @param outputFilePath The output file path. * @throws Exception Thrown when something bad happens. */ public void exportConversation(String inputFilePath, String outputFilePath) throws Exception { - Conversation conversation = this.readConversation(inputFilePath); + long startTime; + double exportTime; // time taken to export conversation + + // start timing exportation + startTime = System.nanoTime(); + + // read conversation from file + Conversation conversation = readConversation(inputFilePath); + // filter conversation based on specified parameters (user, keyword, blacklisted words) + filterConversation(conversation); + // track conversation activity if a report is specified + if (this.report) { + conversation.trackActivity(); + } + // write to JSon file + writeConversation(conversation, outputFilePath); - this.writeConversation(conversation, outputFilePath); + // finish timing exportation and convert from nanoseconds to milliseconds + exportTime = ((double) (System.nanoTime() - startTime)) / 1_000_000.0; - // TODO: Add more logging... - System.out.println("Conversation exported from '" + inputFilePath + "' to '" + outputFilePath); + // report the completion of the exportation + reportExportCompletion(inputFilePath, outputFilePath, exportTime); } + + /** + * Reports on the completion of the exportation + * @param inputFilePath The input file path. + * @param outputFilePath The output file path. + * @param exportTime The time (in milliseconds) over which the exportation took place. + */ + public void reportExportCompletion(String inputFilePath, String outputFilePath, double exportTime) { + System.out.println("\nConversation exported from '" + inputFilePath + "' to '" + outputFilePath + "'."); + System.out.println("Exportation took " + exportTime + " milliseconds to execute."); + } + + /** * Helper method to write the given {@code conversation} as JSON to the given {@code outputFilePath}. * @param conversation The conversation to write. @@ -83,26 +131,25 @@ public void exportConversation(String inputFilePath, String outputFilePath) thro * @throws Exception Thrown when something bad happens. */ public void writeConversation(Conversation conversation, String outputFilePath) throws Exception { - // TODO: Do we need both to be resources, or will buffered writer close the stream? - try (OutputStream os = new FileOutputStream(outputFilePath, true); - BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os))) { + try (OutputStream os = new FileOutputStream(outputFilePath, false); + BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os))) { - // TODO: Maybe reuse this? Make it more testable... GsonBuilder gsonBuilder = new GsonBuilder(); gsonBuilder.registerTypeAdapter(Instant.class, new InstantSerializer()); Gson g = gsonBuilder.create(); - bw.write(g.toJson(conversation)); + } catch (FileNotFoundException e) { - // TODO: Maybe include more information? - throw new IllegalArgumentException("The file was not found."); + throw new FileNotFoundException("Cannot find file named \'" + outputFilePath + "\'."); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Output buffer size is ef illegal value. Must be a non-zero value"); } catch (IOException e) { - // TODO: Should probably throw different exception to be more meaningful :/ - throw new Exception("Something went wrong"); + throw new IOException("An I/O error has occurred."); } } + /** * Represents a helper to read a conversation from the given {@code inputFilePath}. * @param inputFilePath The path to the input file. @@ -113,25 +160,93 @@ public Conversation readConversation(String inputFilePath) throws Exception { try(InputStream is = new FileInputStream(inputFilePath); BufferedReader r = new BufferedReader(new InputStreamReader(is))) { - List messages = new ArrayList(); + List messages = new ArrayList<>(); String conversationName = r.readLine(); String line; while ((line = r.readLine()) != null) { String[] split = line.split(" "); + StringBuilder content = new StringBuilder(); + + // adds the rest of the splits to the message content + for (int i = 2; i < split.length; i++) { + content.append(split[i]); + // adds spaces in between all the splits (makes sure an extra one isn't added to the end) + if (i < split.length - 1) { + content.append(" "); + } + } - messages.add(new Message(Instant.ofEpochSecond(Long.parseUnsignedLong(split[0])), split[1], split[2])); + messages.add(new Message(Instant.ofEpochSecond(Long.parseUnsignedLong(split[0])), split[1], content.toString())); } return new Conversation(conversationName, messages); } catch (FileNotFoundException e) { - throw new IllegalArgumentException("The file was not found."); + throw new FileNotFoundException("Cannot find file named \'" + inputFilePath + "\'."); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Input buffer size is ef illegal value. Must be a non-zero value"); } catch (IOException e) { - throw new Exception("Something went wrong"); + throw new IOException("An I/O error has occurred."); } } + + /** + * Filters conversation content based on the filter parameters (user, keyword, blacklist) + * @param conversation The conversation to be filtered. + */ + public void filterConversation(Conversation conversation) { + ConversationFilterer filterer = new ConversationFilterer(); + + // if a user is specified, filter conversation to only feature messages by the specified user + if (this.user != null) { + filterer.filterConversationByUser(conversation, this.user); + } + + // if a keyword is specified, filter conversation to only feature messages containing the specified keyword + if (this.keyword != null) { + filterer.filterConversationByKeyword(conversation, this.keyword); + } + + // if blacklisted words are specified, redact blacklisted words + if (this.blacklistedWords != null) { + filterer.redactBlacklistedWords(conversation, this.blacklistedWords); + } + } + + public String getUser() { + return user; + } + + public String getKeyword() { + return keyword; + } + + public String[] getBlacklistedWords() { + return blacklistedWords; + } + + public boolean isReport() { + return report; + } + + public void setUser(String user) { + this.user = user; + } + + public void setKeyword(String keyword) { + this.keyword = keyword; + } + + public void setBlacklistedWords(String[] blacklistedWords) { + this.blacklistedWords = blacklistedWords; + } + + public void setReport(boolean report) { + this.report = report; + } + class InstantSerializer implements JsonSerializer { @Override public JsonElement serialize(Instant instant, Type type, JsonSerializationContext jsonSerializationContext) { diff --git a/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationExporterConfiguration.java b/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationExporterConfiguration.java index 5d785d40..eae899b8 100644 --- a/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationExporterConfiguration.java +++ b/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationExporterConfiguration.java @@ -1,13 +1,13 @@ package com.mindlinksoft.recruitment.mychat; +import org.apache.commons.lang.ObjectUtils; import picocli.CommandLine.Option; import picocli.CommandLine.Command; /** * Represents the configuration for the exporter. */ -@Command(name = "export", mixinStandardHelpOptions = true, version = "exporter 1.0", - description = "Exports a plain text chat log into a JSON file.") +@Command(name = "export", mixinStandardHelpOptions = true, version = "exporter 1.0", description = "Exports a plain text chat log into a JSON file.") public final class ConversationExporterConfiguration { /** * Gets the input file path. @@ -20,4 +20,25 @@ public final class ConversationExporterConfiguration { */ @Option(names = { "-o", "--outputFilePath" }, description = "The path to the output JSON file.", required = true) public String outputFilePath; + + /** + * NEW FEATURE: Gets the specified user whose messages should be output. + */ + @Option(names = { "-u", "--filterByUser" }, description = "Filter Messages by User - only messages sent by the specified user are output") + public String user = null; + + /** + * NEW FEATURE: Gets the specified keyword whose containing messages should be output. + */ + @Option(names = { "-k", "--filterByKeyword" }, description = "Filter Messages by Keyword - only messages containing the specified keyword are output") + public String keyword = null; + + /** + * NEW FEATURE: Gets the list of blacklisted words who are redacted from the output. + */ + @Option(names = { "-b", "--blacklist" }, description = "Blacklist Word - redacts any occurrence of the blacklisted word in the output") + public String[] blacklistedWords = null; + + @Option(names = { "-r", "--report" }, description = "Include Report - adds a report detailing the number of messages sent by each user in the output") + public boolean report = false; } diff --git a/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationFilterer.java b/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationFilterer.java new file mode 100644 index 00000000..21048b30 --- /dev/null +++ b/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationFilterer.java @@ -0,0 +1,66 @@ +package com.mindlinksoft.recruitment.mychat; + +import java.util.ArrayList; +import java.util.List; + +public class ConversationFilterer { + + /** + * Filters a conversation to only contain messages sent by the specified user + * @param conversation The conversation to filter. + * @param user The user whose messages are being kept. + */ + public void filterConversationByUser(Conversation conversation, String user) { + List messagesToDelete = new ArrayList<>(); + + // set up message for removal if its sender is not the same as the user specified (ignores case if users have capitalised names, for example) + for (Message message : conversation.getMessages()) { + if (!message.getSenderId().equalsIgnoreCase(user)) { + messagesToDelete.add(message); + } + } + // remove those messages + conversation.getMessages().removeAll(messagesToDelete); + + } + + /** + * Filters a conversation to only contain messages that contain the specified keyword + * @param conversation The conversation to filter. + * @param keyword The keyword whose containing messages are being kept. + */ + public void filterConversationByKeyword(Conversation conversation, String keyword) { + List messagesToDelete = new ArrayList<>(); + + // set up message for removal if it does not contain the keyword specified + for (Message message : conversation.getMessages()) { + if (!message.getContent().contains(keyword)) { + messagesToDelete.add(message); + } + } + // remove those messages + conversation.getMessages().removeAll(messagesToDelete); + } + + /** + * Filters a conversation to only contain messages that contain the specified keyword + * @param conversation The conversation to filter. + * @param blacklistedWords The words to be redacted from all messages. + */ + public void redactBlacklistedWords(Conversation conversation, String[] blacklistedWords) { + for (String blacklistedWord : blacklistedWords) { + for (Message message : conversation.getMessages()) { + if (message.getContent().contains(blacklistedWord)) { + // indexes of the start and end of the blacklisted word in the message + int wordStartIndex = message.getContent().indexOf(blacklistedWord); + int wordEndIndex = wordStartIndex + blacklistedWord.length(); + // get the parts of the message that come before and after the blacklisted word + String messageBeforeWord = message.getContent().substring(0, wordStartIndex); + String messageAfterWord = message.getContent().substring(wordEndIndex); + // replace the message with post-redaction message + message.setContent(messageBeforeWord + "*redacted*" + messageAfterWord); + } + } + } + } +} diff --git a/src/main/java/com/mindlinksoft/recruitment/mychat/Main.java b/src/main/java/com/mindlinksoft/recruitment/mychat/Main.java new file mode 100644 index 00000000..80bb00cd --- /dev/null +++ b/src/main/java/com/mindlinksoft/recruitment/mychat/Main.java @@ -0,0 +1,13 @@ +package com.mindlinksoft.recruitment.mychat; + +public class Main { + + /** + * The application entry point. + * @param args The command line arguments. + */ + public static void main(String[] args) { + ConversationExporter exporter = new ConversationExporter(); + exporter.run(args); + } +} diff --git a/src/main/java/com/mindlinksoft/recruitment/mychat/Message.java b/src/main/java/com/mindlinksoft/recruitment/mychat/Message.java index 4135d497..614da7bb 100644 --- a/src/main/java/com/mindlinksoft/recruitment/mychat/Message.java +++ b/src/main/java/com/mindlinksoft/recruitment/mychat/Message.java @@ -9,17 +9,17 @@ public final class Message { /** * The message content. */ - public String content; + private String content; /** * The message timestamp. */ - public Instant timestamp; + private Instant timestamp; /** * The message sender. */ - public String senderId; + private String senderId; /** * Initializes a new instance of the {@link Message} class. @@ -32,4 +32,30 @@ public Message(Instant timestamp, String senderId, String content) { this.timestamp = timestamp; this.senderId = senderId; } + + // - Getters and Setters (changed the class variables to private) + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public Instant getTimestamp() { + return timestamp; + } + + public void setTimestamp(Instant timestamp) { + this.timestamp = timestamp; + } + + public String getSenderId() { + return senderId; + } + + public void setSenderId(String senderId) { + this.senderId = senderId; + } } diff --git a/src/test/java/com/mindlinksoft/recruitment/mychat/ConversationExporterConfigurationTests.java b/src/test/java/com/mindlinksoft/recruitment/mychat/ConversationExporterConfigurationTests.java new file mode 100644 index 00000000..bf5d32b3 --- /dev/null +++ b/src/test/java/com/mindlinksoft/recruitment/mychat/ConversationExporterConfigurationTests.java @@ -0,0 +1,57 @@ +package com.mindlinksoft.recruitment.mychat; + + +import org.junit.Test; +import picocli.CommandLine; + +import java.util.Arrays; + +import static org.junit.Assert.*; + +/** + * Tests for the {@link ConversationExporter}. + */ +public class ConversationExporterConfigurationTests { + /** + * Tests that parameters are correctly parsed into the configuration + */ + @Test + public void testParameterParsing() { + ConversationExporterConfiguration configuration; + CommandLine cmd; + String[] args; + + // - JUST INPUT AND OUTPUT FILES ----------------------------------------------------- + args = new String[]{"-i", "chat.txt", "-o", "out.json"}; + + configuration = new ConversationExporterConfiguration(); + cmd = new CommandLine(configuration); + cmd.parseArgs(args); + + // input and output file parameters + assertEquals("chat.txt", configuration.inputFilePath); + assertEquals("out.json", configuration.outputFilePath); + // extra feature parameters + assertNull(configuration.user); + assertNull(configuration.keyword); + assertNull(configuration.blacklistedWords); + assertFalse(configuration.report); + + + // - EXTRA PARAMETERS ---------------------------------------------------------------- + args = new String[]{"-i", "chat2.txt", "-o", "out2.json", "-b", "you", "-u", "mike", "-k", "pie", "-b", "there", "-r"}; + + configuration = new ConversationExporterConfiguration(); + cmd = new CommandLine(configuration); + cmd.parseArgs(args); + + // input and output file parameters + assertEquals("chat2.txt", configuration.inputFilePath); + assertEquals("out2.json", configuration.outputFilePath); + // extra feature parameters + assertEquals("mike", configuration.user); + assertEquals("pie", configuration.keyword); + assertEquals(Arrays.toString(new String[]{"you, there"}), Arrays.toString(configuration.blacklistedWords)); + assertTrue(configuration.report); + } +} diff --git a/src/test/java/com/mindlinksoft/recruitment/mychat/ConversationExporterTests.java b/src/test/java/com/mindlinksoft/recruitment/mychat/ConversationExporterTests.java index ebd59fe0..8addc859 100644 --- a/src/test/java/com/mindlinksoft/recruitment/mychat/ConversationExporterTests.java +++ b/src/test/java/com/mindlinksoft/recruitment/mychat/ConversationExporterTests.java @@ -2,13 +2,17 @@ import com.google.gson.*; import org.junit.Test; +import picocli.CommandLine; import java.io.FileInputStream; import java.io.InputStreamReader; import java.lang.reflect.Type; import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; /** * Tests for the {@link ConversationExporter}. @@ -31,42 +35,337 @@ public void testExportingConversationExportsConversation() throws Exception { Conversation c = g.fromJson(new InputStreamReader(new FileInputStream("chat.json")), Conversation.class); - assertEquals("My Conversation", c.name); + assertEquals("My Conversation", c.getName()); - assertEquals(7, c.messages.size()); + assertEquals(7, c.getMessages().size()); - Message[] ms = new Message[c.messages.size()]; - c.messages.toArray(ms); + Message[] ms = new Message[c.getMessages().size()]; + c.getMessages().toArray(ms); - assertEquals(Instant.ofEpochSecond(1448470901), ms[0].timestamp); - assertEquals("bob", ms[0].senderId); - assertEquals("Hello there!", ms[0].content); + assertEquals(Instant.ofEpochSecond(1448470901), ms[0].getTimestamp()); + assertEquals("bob", ms[0].getSenderId()); + assertEquals("Hello there!", ms[0].getContent()); - assertEquals(Instant.ofEpochSecond(1448470905), ms[1].timestamp); - assertEquals("mike", ms[1].senderId); - assertEquals("how are you?", ms[1].content); + assertEquals(Instant.ofEpochSecond(1448470905), ms[1].getTimestamp()); + assertEquals("mike", ms[1].getSenderId()); + assertEquals("how are you?", ms[1].getContent()); - assertEquals(Instant.ofEpochSecond(1448470906), ms[2].timestamp); - assertEquals("bob", ms[2].senderId); - assertEquals("I'm good thanks, do you like pie?", ms[2].content); + assertEquals(Instant.ofEpochSecond(1448470906), ms[2].getTimestamp()); + assertEquals("bob", ms[2].getSenderId()); + assertEquals("I'm good thanks, do you like pie?", ms[2].getContent()); - assertEquals(Instant.ofEpochSecond(1448470910), ms[3].timestamp); - assertEquals("mike", ms[3].senderId); - assertEquals("no, let me ask Angus...", ms[3].content); + assertEquals(Instant.ofEpochSecond(1448470910), ms[3].getTimestamp()); + assertEquals("mike", ms[3].getSenderId()); + assertEquals("no, let me ask Angus...", ms[3].getContent()); - assertEquals(Instant.ofEpochSecond(1448470912), ms[4].timestamp); - assertEquals("angus", ms[4].senderId); - assertEquals("Hell yes! Are we buying some pie?", ms[4].content); + assertEquals(Instant.ofEpochSecond(1448470912), ms[4].getTimestamp()); + assertEquals("angus", ms[4].getSenderId()); + assertEquals("Hell yes! Are we buying some pie?", ms[4].getContent()); - assertEquals(Instant.ofEpochSecond(1448470914), ms[5].timestamp); - assertEquals("bob", ms[5].senderId); - assertEquals("No, just want to know if there's anybody else in the pie society...", ms[5].content); + assertEquals(Instant.ofEpochSecond(1448470914), ms[5].getTimestamp()); + assertEquals("bob", ms[5].getSenderId()); + assertEquals("No, just want to know if there's anybody else in the pie society...", ms[5].getContent()); - assertEquals(Instant.ofEpochSecond(1448470915), ms[6].timestamp); - assertEquals("angus", ms[6].senderId); - assertEquals("YES! I'm the head pie eater there...", ms[6].content); + assertEquals(Instant.ofEpochSecond(1448470915), ms[6].getTimestamp()); + assertEquals("angus", ms[6].getSenderId()); + assertEquals("YES! I'm the head pie eater there...", ms[6].getContent()); } + /** + * Tests that filtering by username functions correctly + */ + @Test + public void testFilterConversationByUser() throws Exception { + ConversationExporter exporter = new ConversationExporter(); + Conversation c; + + // - BOB --------------------------------------------------------- + c = exporter.readConversation("chat.txt"); + + exporter.setUser("bob"); + exporter.filterConversation(c); + + for (Message m : c.getMessages()) { + assertEquals("bob", m.getSenderId()); + } + + assertEquals(3, c.getMessages().size()); + + // - BOB (CAPITALISED) ----------------------------------------- + c = exporter.readConversation("chat.txt"); + // should work the same way + exporter.setUser("BOB"); + exporter.filterConversation(c); + + for (Message m : c.getMessages()) { + assertEquals("bob", m.getSenderId()); + } + + assertEquals(3, c.getMessages().size()); + + // - MIKE -------------------------------------------------------- + c = exporter.readConversation("chat.txt"); + + exporter.setUser("mike"); + exporter.filterConversation(c); + + for (Message m : c.getMessages()) { + assertEquals("mike", m.getSenderId()); + } + + assertEquals(2, c.getMessages().size()); + + // - ALICE ------------------------------------------------------- + c = exporter.readConversation("chat.txt"); + + exporter.setUser("alice"); + exporter.filterConversation(c); + + assertEquals(0, c.getMessages().size()); + } + + + /** + * Tests that filtering for keywords functions correctly + */ + @Test + public void testFilterConversationByKeyword() throws Exception { + ConversationExporter exporter = new ConversationExporter(); + Conversation c; + List ms; + + // - PIE --------------------------------------------------------- + c = exporter.readConversation("chat.txt"); + + exporter.setKeyword("pie"); + exporter.filterConversation(c); + ms = (ArrayList) c.getMessages(); + + assertEquals("I'm good thanks, do you like pie?", ms.get(0).getContent()); + assertEquals("Hell yes! Are we buying some pie?", ms.get(1).getContent()); + assertEquals("No, just want to know if there's anybody else in the pie society...", ms.get(2).getContent()); + assertEquals("YES! I'm the head pie eater there...", ms.get(3).getContent()); + + // - HELL --------------------------------------------------------- + c = exporter.readConversation("chat.txt"); + + exporter.setKeyword("Hell"); + exporter.filterConversation(c); + ms = (ArrayList) c.getMessages(); + + assertEquals("Hello there!", ms.get(0).getContent()); + assertEquals("Hell yes! Are we buying some pie?", ms.get(1).getContent()); + + // - NOPE --------------------------------------------------------- + c = exporter.readConversation("chat.txt"); + + exporter.setKeyword("nope"); + exporter.filterConversation(c); + ms = (ArrayList) c.getMessages(); + + assertEquals(0, ms.size()); + } + + + /** + * Tests that redacting blacklisted words functions correctly + */ + @Test + public void testRedactBlacklistedWords() throws Exception { + ConversationExporter exporter = new ConversationExporter(); + Conversation originalConversation; + Conversation c; + List originalMessages; + List ms; + + // gets the original conversation (unfiltered) + originalConversation = exporter.readConversation("chat.txt"); + originalMessages = (ArrayList) originalConversation.getMessages(); + + // - [PIE] --------------------------------------------------------- + c = exporter.readConversation("chat.txt"); + + exporter.setBlacklistedWords(new String[]{"pie"}); + exporter.filterConversation(c); + ms = (ArrayList) c.getMessages(); + + assertEquals("Hello there!", ms.get(0).getContent()); + assertEquals("how are you?", ms.get(1).getContent()); + assertEquals("I'm good thanks, do you like *redacted*?", ms.get(2).getContent()); + assertEquals("no, let me ask Angus...", ms.get(3).getContent()); + assertEquals("Hell yes! Are we buying some *redacted*?", ms.get(4).getContent()); + assertEquals("No, just want to know if there's anybody else in the *redacted* society...", ms.get(5).getContent()); + assertEquals("YES! I'm the head *redacted* eater there...", ms.get(6).getContent()); + + // - [I'M, YOU] ----------------------------------------------------- + c = exporter.readConversation("chat.txt"); + + exporter.setBlacklistedWords(new String[]{"I'm", "you"}); + exporter.filterConversation(c); + ms = (ArrayList) c.getMessages(); + + assertEquals("Hello there!", ms.get(0).getContent()); + assertEquals("how are *redacted*?", ms.get(1).getContent()); + assertEquals("*redacted* good thanks, do *redacted* like pie?", ms.get(2).getContent()); + assertEquals("no, let me ask Angus...", ms.get(3).getContent()); + assertEquals("Hell yes! Are we buying some pie?", ms.get(4).getContent()); + assertEquals("No, just want to know if there's anybody else in the pie society...", ms.get(5).getContent()); + assertEquals("YES! *redacted* the head pie eater there...", ms.get(6).getContent()); + + // - [NON-EXISTENT] ----------------------------------------------------- + c = exporter.readConversation("chat.txt"); + + exporter.setBlacklistedWords(new String[]{"non-existent"}); + exporter.filterConversation(c); + ms = (ArrayList) c.getMessages(); + + // messages should be the same as the original as the "non-existent" string has no occurrences in the conversation + for (int i = 0; i < ms.size(); i++) { + assertEquals(originalMessages.get(i).getContent(), ms.get(i).getContent()); + } + } + + + /** + * Tests that multiple filters concurrently function correctly + */ + @Test + public void testMultipleFilters() throws Exception { + ConversationExporter exporter = new ConversationExporter(); + Conversation c; + List ms; + + // - USER : BOB, KEYWORD : THERE --------------------------------------- + c = exporter.readConversation("chat.txt"); + + exporter.setUser("bob"); + exporter.setKeyword("there"); + exporter.setBlacklistedWords(null); + + exporter.filterConversation(c); + ms = (ArrayList) c.getMessages(); + + assertEquals(Instant.ofEpochSecond(1448470901), ms.get(0).getTimestamp()); + assertEquals("bob", ms.get(0).getSenderId()); + assertEquals("Hello there!", ms.get(0).getContent()); + + assertEquals(Instant.ofEpochSecond(1448470914), ms.get(1).getTimestamp()); + assertEquals("bob", ms.get(1).getSenderId()); + assertEquals("No, just want to know if there's anybody else in the pie society...", ms.get(1).getContent()); + + assertEquals(2, ms.size()); + + + // - USER : MIKE, BLACKLIST : [YOU, ANGUS] ------------------------------ + c = exporter.readConversation("chat.txt"); + + exporter.setUser("mike"); + exporter.setKeyword(null); + exporter.setBlacklistedWords(new String[]{"you", "Angus"}); + + exporter.filterConversation(c); + ms = (ArrayList) c.getMessages(); + + assertEquals(Instant.ofEpochSecond(1448470905), ms.get(0).getTimestamp()); + assertEquals("mike", ms.get(0).getSenderId()); + assertEquals("how are *redacted*?", ms.get(0).getContent()); + + assertEquals(Instant.ofEpochSecond(1448470910), ms.get(1).getTimestamp()); + assertEquals("mike", ms.get(1).getSenderId()); + assertEquals("no, let me ask *redacted*...", ms.get(1).getContent()); + + assertEquals(2, ms.size()); + + + // - KEYWORD : PIE, BLACKLIST : [PIE] ----------------------------------- + c = exporter.readConversation("chat.txt"); + + exporter.setUser(null); + exporter.setKeyword("pie"); + exporter.setBlacklistedWords(new String[]{"pie"}); + + exporter.filterConversation(c); + ms = (ArrayList) c.getMessages(); + + assertEquals(Instant.ofEpochSecond(1448470906), ms.get(0).getTimestamp()); + assertEquals("bob", ms.get(0).getSenderId()); + assertEquals("I'm good thanks, do you like *redacted*?", ms.get(0).getContent()); + + assertEquals(Instant.ofEpochSecond(1448470912), ms.get(1).getTimestamp()); + assertEquals("angus", ms.get(1).getSenderId()); + assertEquals("Hell yes! Are we buying some *redacted*?", ms.get(1).getContent()); + + assertEquals(Instant.ofEpochSecond(1448470914), ms.get(2).getTimestamp()); + assertEquals("bob", ms.get(2).getSenderId()); + assertEquals("No, just want to know if there's anybody else in the *redacted* society...", ms.get(2).getContent()); + + assertEquals(Instant.ofEpochSecond(1448470915), ms.get(3).getTimestamp()); + assertEquals("angus", ms.get(3).getSenderId()); + assertEquals("YES! I'm the head *redacted* eater there...", ms.get(3).getContent()); + + assertEquals(4, ms.size()); + + + // - USER : ANGUS, KEYWORD : YES, BLACKLIST : [SOME] -------------------- + c = exporter.readConversation("chat.txt"); + + exporter.setUser("angus"); + exporter.setKeyword("yes"); + exporter.setBlacklistedWords(new String[]{"some"}); + + exporter.filterConversation(c); + ms = (ArrayList) c.getMessages(); + + assertEquals(Instant.ofEpochSecond(1448470912), ms.get(0).getTimestamp()); + assertEquals("angus", ms.get(0).getSenderId()); + assertEquals("Hell yes! Are we buying *redacted* pie?", ms.get(0).getContent()); + + assertEquals(1, ms.size()); + + } + + + /** + * Tests that parameters are correctly parsed into the exporter's parameter variables + */ + @Test + public void testParameterParsing() { + ConversationExporter exporter; + ConversationExporterConfiguration configuration; + CommandLine cmd; + String[] args; + + // - JUST INPUT AND OUTPUT FILES ----------------------------------------------------- + args = new String[]{"-i", "chat.txt", "-o", "out.json"}; + + exporter = new ConversationExporter(); + configuration = new ConversationExporterConfiguration(); + cmd = new CommandLine(configuration); + cmd.parseArgs(args); + exporter.getCLIParameters(configuration); + + assertNull(exporter.getUser()); + assertNull(exporter.getKeyword()); + assertNull(exporter.getBlacklistedWords()); + assertFalse(exporter.isReport()); + + + // - EXTRA PARAMETERS ---------------------------------------------------------------- + args = new String[]{"-i", "chat2.txt", "-o", "out2.json", "-b", "you", "-u", "mike", "-k", "pie", "-b", "there", "-r"}; + + exporter = new ConversationExporter(); + configuration = new ConversationExporterConfiguration(); + cmd = new CommandLine(configuration); + cmd.parseArgs(args); + exporter.getCLIParameters(configuration); + + assertEquals("mike", exporter.getUser()); + assertEquals("pie", exporter.getKeyword()); + assertEquals(Arrays.toString(new String[]{"you, there"}), Arrays.toString(exporter.getBlacklistedWords())); + assertTrue(exporter.isReport()); + } + + class InstantDeserializer implements JsonDeserializer { @Override From dacae58e701e6b815edee81104510aa843799b12 Mon Sep 17 00:00:00 2001 From: Timothy Oluwole Date: Thu, 4 Mar 2021 16:49:15 +0000 Subject: [PATCH 2/2] Second go at the test, have further separated business logic from I/O, included more tests, including unit tests, separated filters, made Message/Conversation class variables final --- out.json | 2 +- .../recruitment/mychat/Conversation.java | 31 +-- .../mychat/ConversationExporter.java | 130 +++--------- .../mychat/ConversationExporterIO.java | 88 ++++++++ .../mychat/ConversationFilterer.java | 66 ------ .../BlacklistedWordFilterer.java | 54 +++++ .../ConversationFilterers/Filterer.java | 7 + .../KeywordFilterer.java | 41 ++++ .../ConversationFilterers/UserFilterer.java | 41 ++++ .../mindlinksoft/recruitment/mychat/Main.java | 4 +- .../recruitment/mychat/Message.java | 18 +- .../mychat/BlacklistedWordFiltererTests.java | 94 +++++++++ .../mychat/ConversationExporterIOTests.java | 110 ++++++++++ .../mychat/ConversationExporterTests.java | 194 ++++-------------- .../recruitment/mychat/ConversationTests.java | 160 +++++++++++++++ .../mychat/KeywordFiltererTests.java | 84 ++++++++ .../recruitment/mychat/UserFiltererTests.java | 78 +++++++ 17 files changed, 848 insertions(+), 354 deletions(-) create mode 100644 src/main/java/com/mindlinksoft/recruitment/mychat/ConversationExporterIO.java delete mode 100644 src/main/java/com/mindlinksoft/recruitment/mychat/ConversationFilterer.java create mode 100644 src/main/java/com/mindlinksoft/recruitment/mychat/ConversationFilterers/BlacklistedWordFilterer.java create mode 100644 src/main/java/com/mindlinksoft/recruitment/mychat/ConversationFilterers/Filterer.java create mode 100644 src/main/java/com/mindlinksoft/recruitment/mychat/ConversationFilterers/KeywordFilterer.java create mode 100644 src/main/java/com/mindlinksoft/recruitment/mychat/ConversationFilterers/UserFilterer.java create mode 100644 src/test/java/com/mindlinksoft/recruitment/mychat/BlacklistedWordFiltererTests.java create mode 100644 src/test/java/com/mindlinksoft/recruitment/mychat/ConversationExporterIOTests.java create mode 100644 src/test/java/com/mindlinksoft/recruitment/mychat/ConversationTests.java create mode 100644 src/test/java/com/mindlinksoft/recruitment/mychat/KeywordFiltererTests.java create mode 100644 src/test/java/com/mindlinksoft/recruitment/mychat/UserFiltererTests.java diff --git a/out.json b/out.json index cbd95a4f..b7bf314e 100644 --- a/out.json +++ b/out.json @@ -1 +1 @@ -{"name":"My Conversation","messages":[{"content":"Hello there!","timestamp":1448470901,"senderId":"bob"},{"content":"how are you?","timestamp":1448470905,"senderId":"mike"},{"content":"I\u0027m good thanks, do you like pie?","timestamp":1448470906,"senderId":"bob"},{"content":"no, let me ask Angus...","timestamp":1448470910,"senderId":"mike"},{"content":"Hell yes! Are we buying some pie?","timestamp":1448470912,"senderId":"angus"},{"content":"No, just want to know if there\u0027s anybody else in the pie society...","timestamp":1448470914,"senderId":"bob"},{"content":"YES! I\u0027m the head pie eater there...","timestamp":1448470915,"senderId":"angus"}]} \ No newline at end of file +{"name":"My Conversation","messages":[{"content":"Hello there!","timestamp":1448470901,"senderId":"bob"},{"content":"how are you?","timestamp":1448470905,"senderId":"mike"},{"content":"I\u0027m good thanks, do you like pie?","timestamp":1448470906,"senderId":"bob"},{"content":"no, let me ask Angus...","timestamp":1448470910,"senderId":"mike"},{"content":"Hell yes! Are we buying some pie?","timestamp":1448470912,"senderId":"angus"},{"content":"No, just want to know if there\u0027s anybody else in the pie society...","timestamp":1448470914,"senderId":"bob"},{"content":"YES! I\u0027m the head pie eater there...","timestamp":1448470915,"senderId":"angus"}],"activity":[{"name":"bob","count":3},{"name":"mike","count":2},{"name":"angus","count":2}]} \ No newline at end of file diff --git a/src/main/java/com/mindlinksoft/recruitment/mychat/Conversation.java b/src/main/java/com/mindlinksoft/recruitment/mychat/Conversation.java index a6a86ef4..7aebc6bf 100644 --- a/src/main/java/com/mindlinksoft/recruitment/mychat/Conversation.java +++ b/src/main/java/com/mindlinksoft/recruitment/mychat/Conversation.java @@ -11,12 +11,12 @@ public final class Conversation { /** * The name of the conversation. */ - private String name; + private final String name; /** * The messages in the conversation. */ - private Collection messages; + private final Collection messages; /** * NEW FEATURE: The messaging activity of the users in the conversation. @@ -34,6 +34,11 @@ public Conversation(String name, Collection messages) { } public void trackActivity() { + // cannot track activity on null messages + if (messages == null) { + return; + } + activity = new ArrayList<>(); int i; @@ -55,7 +60,13 @@ public void trackActivity() { // increment sender's message count activity.get(i).incrementCount(); } + } + public void sortActivity() { + // cannot sort null activity + if (activity == null) { + return; + } // sort senders in descending order of message count activity.sort((a1, a2) -> (a2.count - a1.count)); } @@ -66,26 +77,14 @@ public String getName() { return name; } - public void setName(String name) { - this.name = name; - } - public Collection getMessages() { return messages; } - public void setMessages(Collection messages) { - this.messages = messages; - } - public List getActivity() { return activity; } - public void setActivity(List activity) { - this.activity = activity; - } - class SenderMessagePairing { private String name; private int count; @@ -102,5 +101,9 @@ public void incrementCount() { public String getName() { return this.name; } + + public int getCount() { + return count; + } } } diff --git a/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationExporter.java b/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationExporter.java index ec2b60d6..85172536 100644 --- a/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationExporter.java +++ b/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationExporter.java @@ -1,19 +1,13 @@ package com.mindlinksoft.recruitment.mychat; -import com.google.gson.*; - +import com.mindlinksoft.recruitment.mychat.ConversationFilterers.BlacklistedWordFilterer; +import com.mindlinksoft.recruitment.mychat.ConversationFilterers.KeywordFilterer; +import com.mindlinksoft.recruitment.mychat.ConversationFilterers.UserFilterer; import picocli.CommandLine; import picocli.CommandLine.ParameterException; import picocli.CommandLine.ParseResult; import picocli.CommandLine.UnmatchedArgumentException; -import java.io.*; -import java.lang.reflect.Type; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - /** * Represents a conversation exporter that can read a conversation and write it out in JSON. */ @@ -69,7 +63,7 @@ public void run(String[] args) { /** * Exports the conversation at {@code inputFilePath} as JSON to {@code outputFilePath}. - * @param configuration The configuration which contains the paramters for exportation. + * @param configuration The configuration which contains the parameters for exportation. */ public void getCLIParameters(ConversationExporterConfiguration configuration) { // stores the arguments @@ -87,6 +81,7 @@ public void getCLIParameters(ConversationExporterConfiguration configuration) { * @throws Exception Thrown when something bad happens. */ public void exportConversation(String inputFilePath, String outputFilePath) throws Exception { + ConversationExporterIO exporterIO = new ConversationExporterIO(); long startTime; double exportTime; // time taken to export conversation @@ -94,15 +89,16 @@ public void exportConversation(String inputFilePath, String outputFilePath) thro startTime = System.nanoTime(); // read conversation from file - Conversation conversation = readConversation(inputFilePath); + Conversation conversation = exporterIO.readConversation(inputFilePath); + // filter conversation based on specified parameters (user, keyword, blacklisted words) - filterConversation(conversation); + conversation = filterConversation(conversation); + // track conversation activity if a report is specified - if (this.report) { - conversation.trackActivity(); - } + checkReport(conversation); + // write to JSon file - writeConversation(conversation, outputFilePath); + exporterIO.writeConversation(conversation, outputFilePath); // finish timing exportation and convert from nanoseconds to milliseconds exportTime = ((double) (System.nanoTime() - startTime)) / 1_000_000.0; @@ -120,75 +116,7 @@ public void exportConversation(String inputFilePath, String outputFilePath) thro */ public void reportExportCompletion(String inputFilePath, String outputFilePath, double exportTime) { System.out.println("\nConversation exported from '" + inputFilePath + "' to '" + outputFilePath + "'."); - System.out.println("Exportation took " + exportTime + " milliseconds to execute."); - } - - - /** - * Helper method to write the given {@code conversation} as JSON to the given {@code outputFilePath}. - * @param conversation The conversation to write. - * @param outputFilePath The file path where the conversation should be written. - * @throws Exception Thrown when something bad happens. - */ - public void writeConversation(Conversation conversation, String outputFilePath) throws Exception { - try (OutputStream os = new FileOutputStream(outputFilePath, false); - BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os))) { - - GsonBuilder gsonBuilder = new GsonBuilder(); - gsonBuilder.registerTypeAdapter(Instant.class, new InstantSerializer()); - - Gson g = gsonBuilder.create(); - bw.write(g.toJson(conversation)); - - } catch (FileNotFoundException e) { - throw new FileNotFoundException("Cannot find file named \'" + outputFilePath + "\'."); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("Output buffer size is ef illegal value. Must be a non-zero value"); - } catch (IOException e) { - throw new IOException("An I/O error has occurred."); - } - } - - - /** - * Represents a helper to read a conversation from the given {@code inputFilePath}. - * @param inputFilePath The path to the input file. - * @return The {@link Conversation} representing by the input file. - * @throws Exception Thrown when something bad happens. - */ - public Conversation readConversation(String inputFilePath) throws Exception { - try(InputStream is = new FileInputStream(inputFilePath); - BufferedReader r = new BufferedReader(new InputStreamReader(is))) { - - List messages = new ArrayList<>(); - - String conversationName = r.readLine(); - String line; - - while ((line = r.readLine()) != null) { - String[] split = line.split(" "); - StringBuilder content = new StringBuilder(); - - // adds the rest of the splits to the message content - for (int i = 2; i < split.length; i++) { - content.append(split[i]); - // adds spaces in between all the splits (makes sure an extra one isn't added to the end) - if (i < split.length - 1) { - content.append(" "); - } - } - - messages.add(new Message(Instant.ofEpochSecond(Long.parseUnsignedLong(split[0])), split[1], content.toString())); - } - - return new Conversation(conversationName, messages); - } catch (FileNotFoundException e) { - throw new FileNotFoundException("Cannot find file named \'" + inputFilePath + "\'."); - } catch (IllegalArgumentException e) { - throw new IllegalArgumentException("Input buffer size is ef illegal value. Must be a non-zero value"); - } catch (IOException e) { - throw new IOException("An I/O error has occurred."); - } + System.out.println("Time taken: " + exportTime + " milliseconds"); } @@ -196,22 +124,35 @@ public Conversation readConversation(String inputFilePath) throws Exception { * Filters conversation content based on the filter parameters (user, keyword, blacklist) * @param conversation The conversation to be filtered. */ - public void filterConversation(Conversation conversation) { - ConversationFilterer filterer = new ConversationFilterer(); - + public Conversation filterConversation(Conversation conversation) { // if a user is specified, filter conversation to only feature messages by the specified user if (this.user != null) { - filterer.filterConversationByUser(conversation, this.user); + UserFilterer userFilterer = new UserFilterer(); + userFilterer.setUser(this.user); + conversation = userFilterer.filter(conversation); } // if a keyword is specified, filter conversation to only feature messages containing the specified keyword if (this.keyword != null) { - filterer.filterConversationByKeyword(conversation, this.keyword); + KeywordFilterer keywordFilterer = new KeywordFilterer(); + keywordFilterer.setKeyword(this.keyword); + conversation = keywordFilterer.filter(conversation); } // if blacklisted words are specified, redact blacklisted words if (this.blacklistedWords != null) { - filterer.redactBlacklistedWords(conversation, this.blacklistedWords); + BlacklistedWordFilterer blacklistedWordFilterer = new BlacklistedWordFilterer(); + blacklistedWordFilterer.setBlacklistedWords(this.blacklistedWords); + conversation = blacklistedWordFilterer.filter(conversation); + } + + return conversation; + } + + public void checkReport(Conversation conversation) { + if (this.report) { + conversation.trackActivity(); + conversation.sortActivity(); } } @@ -246,11 +187,4 @@ public void setBlacklistedWords(String[] blacklistedWords) { public void setReport(boolean report) { this.report = report; } - - class InstantSerializer implements JsonSerializer { - @Override - public JsonElement serialize(Instant instant, Type type, JsonSerializationContext jsonSerializationContext) { - return new JsonPrimitive(instant.getEpochSecond()); - } - } } diff --git a/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationExporterIO.java b/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationExporterIO.java new file mode 100644 index 00000000..58d91435 --- /dev/null +++ b/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationExporterIO.java @@ -0,0 +1,88 @@ +package com.mindlinksoft.recruitment.mychat; + +import com.google.gson.*; + +import java.io.*; +import java.lang.reflect.Type; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; + +public class ConversationExporterIO { + /** + * Represents a helper to read a conversation from the given {@code inputFilePath}. + * @param inputFilePath The path to the input file. + * @return The {@link Conversation} representing by the input file. + * @throws Exception Thrown when something bad happens. + */ + public Conversation readConversation(String inputFilePath) throws Exception { + try(InputStream is = new FileInputStream(inputFilePath); + BufferedReader r = new BufferedReader(new InputStreamReader(is))) { + + List messages = new ArrayList<>(); + + String conversationName = r.readLine(); + String line; + + while ((line = r.readLine()) != null) { + String[] split = line.split(" "); + StringBuilder content = new StringBuilder(); + + // adds the rest of the splits to the message content + for (int i = 2; i < split.length; i++) { + content.append(split[i]); + // adds spaces in between all the splits (makes sure an extra one isn't added to the end) + if (i < split.length - 1) { + content.append(" "); + } + } + + messages.add(new Message(Instant.ofEpochSecond(Long.parseUnsignedLong(split[0])), split[1], content.toString())); + } + + return new Conversation(conversationName, messages); + } catch (FileNotFoundException e) { + throw new FileNotFoundException("Cannot find file named \'" + inputFilePath + "\'."); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Input buffer size is ef illegal value. Must be a non-zero value"); + } catch (IOException e) { + throw new IOException("An I/O error has occurred."); + } catch (Exception e) { + throw new IOException("An error has occurred."); + } + } + + /** + * Helper method to write the given {@code conversation} as JSON to the given {@code outputFilePath}. + * @param conversation The conversation to write. + * @param outputFilePath The file path where the conversation should be written. + * @throws Exception Thrown when something bad happens. + */ + public void writeConversation(Conversation conversation, String outputFilePath) throws Exception { + try (OutputStream os = new FileOutputStream(outputFilePath, false); + BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os))) { + + GsonBuilder gsonBuilder = new GsonBuilder(); + gsonBuilder.registerTypeAdapter(Instant.class, new InstantSerializer()); + + Gson g = gsonBuilder.create(); + bw.write(g.toJson(conversation)); + + } catch (FileNotFoundException e) { + throw new FileNotFoundException("Cannot find file named \'" + outputFilePath + "\'."); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Output buffer size is ef illegal value. Must be a non-zero value"); + } catch (IOException e) { + throw new IOException("An I/O error has occurred."); + } catch (Exception e) { + throw new IOException("An error has occurred."); + } + } + + class InstantSerializer implements JsonSerializer { + @Override + public JsonElement serialize(Instant instant, Type type, JsonSerializationContext jsonSerializationContext) { + return new JsonPrimitive(instant.getEpochSecond()); + } + } +} diff --git a/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationFilterer.java b/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationFilterer.java deleted file mode 100644 index 21048b30..00000000 --- a/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationFilterer.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.mindlinksoft.recruitment.mychat; - -import java.util.ArrayList; -import java.util.List; - -public class ConversationFilterer { - - /** - * Filters a conversation to only contain messages sent by the specified user - * @param conversation The conversation to filter. - * @param user The user whose messages are being kept. - */ - public void filterConversationByUser(Conversation conversation, String user) { - List messagesToDelete = new ArrayList<>(); - - // set up message for removal if its sender is not the same as the user specified (ignores case if users have capitalised names, for example) - for (Message message : conversation.getMessages()) { - if (!message.getSenderId().equalsIgnoreCase(user)) { - messagesToDelete.add(message); - } - } - // remove those messages - conversation.getMessages().removeAll(messagesToDelete); - - } - - /** - * Filters a conversation to only contain messages that contain the specified keyword - * @param conversation The conversation to filter. - * @param keyword The keyword whose containing messages are being kept. - */ - public void filterConversationByKeyword(Conversation conversation, String keyword) { - List messagesToDelete = new ArrayList<>(); - - // set up message for removal if it does not contain the keyword specified - for (Message message : conversation.getMessages()) { - if (!message.getContent().contains(keyword)) { - messagesToDelete.add(message); - } - } - // remove those messages - conversation.getMessages().removeAll(messagesToDelete); - } - - /** - * Filters a conversation to only contain messages that contain the specified keyword - * @param conversation The conversation to filter. - * @param blacklistedWords The words to be redacted from all messages. - */ - public void redactBlacklistedWords(Conversation conversation, String[] blacklistedWords) { - for (String blacklistedWord : blacklistedWords) { - for (Message message : conversation.getMessages()) { - if (message.getContent().contains(blacklistedWord)) { - // indexes of the start and end of the blacklisted word in the message - int wordStartIndex = message.getContent().indexOf(blacklistedWord); - int wordEndIndex = wordStartIndex + blacklistedWord.length(); - // get the parts of the message that come before and after the blacklisted word - String messageBeforeWord = message.getContent().substring(0, wordStartIndex); - String messageAfterWord = message.getContent().substring(wordEndIndex); - // replace the message with post-redaction message - message.setContent(messageBeforeWord + "*redacted*" + messageAfterWord); - } - } - } - } -} diff --git a/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationFilterers/BlacklistedWordFilterer.java b/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationFilterers/BlacklistedWordFilterer.java new file mode 100644 index 00000000..7a6a9b4a --- /dev/null +++ b/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationFilterers/BlacklistedWordFilterer.java @@ -0,0 +1,54 @@ +package com.mindlinksoft.recruitment.mychat.ConversationFilterers; + +import com.mindlinksoft.recruitment.mychat.Conversation; +import com.mindlinksoft.recruitment.mychat.Message; + +import java.util.ArrayList; +import java.util.List; + +public class BlacklistedWordFilterer extends Filterer { + + private String[] blacklistedWords; + + public void setBlacklistedWords(String[] blacklistedWords) { + this.blacklistedWords = blacklistedWords; + } + + /** + * Filters a conversation to only contain messages that contain the specified keyword + * @param conversation The conversation to filter. + */ + public Conversation filter(Conversation conversation) { + // if there are no blacklisted words, or if the array is null, return original conversation + if (blacklistedWords == null || blacklistedWords.length == 0) { + return conversation; + } + + String messageBeforeWord, messageAfterWord, redactedString; + int wordStartIndex, wordEndIndex; + List filteredMessages = new ArrayList<>(); + + for (Message message : conversation.getMessages()) { + redactedString = message.getContent(); + for (String blacklistedWord : blacklistedWords) { + if (redactedString.contains(blacklistedWord)) { + // indexes of the start and end of the blacklisted word in the message + wordStartIndex = redactedString.indexOf(blacklistedWord); + wordEndIndex = wordStartIndex + blacklistedWord.length(); + // get the parts of the message that come before and after the blacklisted word + messageBeforeWord = redactedString.substring(0, wordStartIndex); + messageAfterWord = redactedString.substring(wordEndIndex); + // replace the message with post-redaction message + redactedString = messageBeforeWord + "*redacted*" + messageAfterWord; + } + } + // add redacted string to new list of filtered messages + filteredMessages.add(new Message(message.getTimestamp(), message.getSenderId(), redactedString)); + } + + // create a new conversation object with the filtered messages + Conversation filteredConversation = new Conversation(conversation.getName(), filteredMessages); + + return filteredConversation; + } +} diff --git a/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationFilterers/Filterer.java b/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationFilterers/Filterer.java new file mode 100644 index 00000000..f67bfb11 --- /dev/null +++ b/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationFilterers/Filterer.java @@ -0,0 +1,7 @@ +package com.mindlinksoft.recruitment.mychat.ConversationFilterers; + +import com.mindlinksoft.recruitment.mychat.Conversation; + +public abstract class Filterer { + public abstract Conversation filter(Conversation conversation); +} diff --git a/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationFilterers/KeywordFilterer.java b/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationFilterers/KeywordFilterer.java new file mode 100644 index 00000000..3426a949 --- /dev/null +++ b/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationFilterers/KeywordFilterer.java @@ -0,0 +1,41 @@ +package com.mindlinksoft.recruitment.mychat.ConversationFilterers; + +import com.mindlinksoft.recruitment.mychat.Conversation; +import com.mindlinksoft.recruitment.mychat.Message; + +import java.util.ArrayList; +import java.util.List; + +public class KeywordFilterer extends Filterer { + + private String keyword; + + public void setKeyword(String keyword) { + this.keyword = keyword; + } + + /** + * Filters a conversation to only contain messages that contain the specified keyword + * @param conversation The conversation to filter. + */ + public Conversation filter(Conversation conversation) { + // if keyword is a null string or an empty string, return original conversation + if (keyword == null || keyword.equals("")) { + return conversation; + } + + List filteredMessages = new ArrayList<>(); + + // add message to filtered messages list if it contains the keyword + for (Message message : conversation.getMessages()) { + if (message.getContent().contains(keyword)) { + filteredMessages.add(message); + } + } + + // create new conversation object with the filtered messages + Conversation filteredConversation = new Conversation(conversation.getName(), filteredMessages); + + return filteredConversation; + } +} diff --git a/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationFilterers/UserFilterer.java b/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationFilterers/UserFilterer.java new file mode 100644 index 00000000..5d9197dc --- /dev/null +++ b/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationFilterers/UserFilterer.java @@ -0,0 +1,41 @@ +package com.mindlinksoft.recruitment.mychat.ConversationFilterers; + +import com.mindlinksoft.recruitment.mychat.Conversation; +import com.mindlinksoft.recruitment.mychat.Message; + +import java.util.ArrayList; +import java.util.List; + +public class UserFilterer extends Filterer { + + private String user; + + public void setUser(String user) { + this.user = user; + } + + /** + * Filters a conversation to only contain messages sent by the specified user + * @param conversation The conversation to filter. + */ + public Conversation filter(Conversation conversation) { + // if user is a null string or an empty string, return original conversation + if (user == null || user.equals("")) { + return conversation; + } + + List filteredMessages = new ArrayList<>(); + + // if message sender is the same as the user specified, add message to filtered messages list (case insensitive) + for (Message message : conversation.getMessages()) { + if (message.getSenderId().equalsIgnoreCase(user)) { + filteredMessages.add(message); + } + } + + // create a new conversation object with the filtered messages + Conversation filteredConversation = new Conversation(conversation.getName(), filteredMessages); + + return filteredConversation; + } +} diff --git a/src/main/java/com/mindlinksoft/recruitment/mychat/Main.java b/src/main/java/com/mindlinksoft/recruitment/mychat/Main.java index 80bb00cd..309264da 100644 --- a/src/main/java/com/mindlinksoft/recruitment/mychat/Main.java +++ b/src/main/java/com/mindlinksoft/recruitment/mychat/Main.java @@ -1,8 +1,10 @@ package com.mindlinksoft.recruitment.mychat; +import java.util.Arrays; + public class Main { - /** + /**s * The application entry point. * @param args The command line arguments. */ diff --git a/src/main/java/com/mindlinksoft/recruitment/mychat/Message.java b/src/main/java/com/mindlinksoft/recruitment/mychat/Message.java index 614da7bb..352a8073 100644 --- a/src/main/java/com/mindlinksoft/recruitment/mychat/Message.java +++ b/src/main/java/com/mindlinksoft/recruitment/mychat/Message.java @@ -9,17 +9,17 @@ public final class Message { /** * The message content. */ - private String content; + private final String content; /** * The message timestamp. */ - private Instant timestamp; + private final Instant timestamp; /** * The message sender. */ - private String senderId; + private final String senderId; /** * Initializes a new instance of the {@link Message} class. @@ -39,23 +39,11 @@ public String getContent() { return content; } - public void setContent(String content) { - this.content = content; - } - public Instant getTimestamp() { return timestamp; } - public void setTimestamp(Instant timestamp) { - this.timestamp = timestamp; - } - public String getSenderId() { return senderId; } - - public void setSenderId(String senderId) { - this.senderId = senderId; - } } diff --git a/src/test/java/com/mindlinksoft/recruitment/mychat/BlacklistedWordFiltererTests.java b/src/test/java/com/mindlinksoft/recruitment/mychat/BlacklistedWordFiltererTests.java new file mode 100644 index 00000000..c744861b --- /dev/null +++ b/src/test/java/com/mindlinksoft/recruitment/mychat/BlacklistedWordFiltererTests.java @@ -0,0 +1,94 @@ +package com.mindlinksoft.recruitment.mychat; + +import com.mindlinksoft.recruitment.mychat.ConversationFilterers.BlacklistedWordFilterer; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public class BlacklistedWordFiltererTests { + /** + * Tests that redacting blacklisted words functions correctly + */ + @Test + public void testRedactBlacklistedWords() throws Exception { + BlacklistedWordFilterer blacklistedWordFilterer = new BlacklistedWordFilterer(); + ConversationExporterIO exporterIO = new ConversationExporterIO(); + Conversation originalConversation; + Conversation c; + List originalMessages; + List ms; + + // gets the original conversation (unfiltered) + originalConversation = exporterIO.readConversation("chat.txt"); + originalMessages = (ArrayList) originalConversation.getMessages(); + + // - [PIE] --------------------------------------------------------- + c = exporterIO.readConversation("chat.txt"); + + blacklistedWordFilterer.setBlacklistedWords(new String[]{"pie"}); + c = blacklistedWordFilterer.filter(c); + ms = (ArrayList) c.getMessages(); + + assertEquals("Hello there!", ms.get(0).getContent()); + assertEquals("how are you?", ms.get(1).getContent()); + assertEquals("I'm good thanks, do you like *redacted*?", ms.get(2).getContent()); + assertEquals("no, let me ask Angus...", ms.get(3).getContent()); + assertEquals("Hell yes! Are we buying some *redacted*?", ms.get(4).getContent()); + assertEquals("No, just want to know if there's anybody else in the *redacted* society...", ms.get(5).getContent()); + assertEquals("YES! I'm the head *redacted* eater there...", ms.get(6).getContent()); + + // - [I'M, YOU] ----------------------------------------------------- + c = exporterIO.readConversation("chat.txt"); + + blacklistedWordFilterer.setBlacklistedWords(new String[]{"I'm", "you"}); + c = blacklistedWordFilterer.filter(c); + ms = (ArrayList) c.getMessages(); + + assertEquals("Hello there!", ms.get(0).getContent()); + assertEquals("how are *redacted*?", ms.get(1).getContent()); + assertEquals("*redacted* good thanks, do *redacted* like pie?", ms.get(2).getContent()); + assertEquals("no, let me ask Angus...", ms.get(3).getContent()); + assertEquals("Hell yes! Are we buying some pie?", ms.get(4).getContent()); + assertEquals("No, just want to know if there's anybody else in the pie society...", ms.get(5).getContent()); + assertEquals("YES! *redacted* the head pie eater there...", ms.get(6).getContent()); + + // - [NON-EXISTENT] ----------------------------------------------------- + c = exporterIO.readConversation("chat.txt"); + + blacklistedWordFilterer.setBlacklistedWords(new String[]{"non-existent"}); + c = blacklistedWordFilterer.filter(c); + ms = (ArrayList) c.getMessages(); + + // messages should be the same as the original as the "non-existent" string has no occurrences in the conversation + for (int i = 0; i < ms.size(); i++) { + assertEquals(originalMessages.get(i).getContent(), ms.get(i).getContent()); + } + + // - NULL --------------------------------------------------------------- + c = exporterIO.readConversation("chat.txt"); + + blacklistedWordFilterer.setBlacklistedWords(null); + c = blacklistedWordFilterer.filter(c); + ms = (ArrayList) c.getMessages(); + + // messages should be the same as the original as the "non-existent" string has no occurrences in the conversation + for (int i = 0; i < ms.size(); i++) { + assertEquals(originalMessages.get(i).getContent(), ms.get(i).getContent()); + } + + // - EMPTY ARRAY -------------------------------------------------------- + c = exporterIO.readConversation("chat.txt"); + + blacklistedWordFilterer.setBlacklistedWords(new String[]{}); + c = blacklistedWordFilterer.filter(c); + ms = (ArrayList) c.getMessages(); + + // messages should be the same as the original as the "non-existent" string has no occurrences in the conversation + for (int i = 0; i < ms.size(); i++) { + assertEquals(originalMessages.get(i).getContent(), ms.get(i).getContent()); + } + } +} diff --git a/src/test/java/com/mindlinksoft/recruitment/mychat/ConversationExporterIOTests.java b/src/test/java/com/mindlinksoft/recruitment/mychat/ConversationExporterIOTests.java new file mode 100644 index 00000000..5e1bc234 --- /dev/null +++ b/src/test/java/com/mindlinksoft/recruitment/mychat/ConversationExporterIOTests.java @@ -0,0 +1,110 @@ +package com.mindlinksoft.recruitment.mychat; + +import org.junit.Test; + +import java.io.File; +import java.io.FileNotFoundException; +import java.time.Instant; +import java.util.ArrayList; + +import static org.junit.Assert.*; + +public class ConversationExporterIOTests { + /** + * Tests that exporting a conversation will export the conversation correctly. + * + * @throws Exception When something bad happens. + */ + @Test + public void testReadConversation() throws Exception { + ConversationExporterIO exporterIO = new ConversationExporterIO(); + Conversation c; + + // - reading from normal text file ------------------------------------------- + + c = exporterIO.readConversation("chat.txt"); + + assertEquals("My Conversation", c.getName()); + + assertEquals(7, c.getMessages().size()); + + Message[] ms = new Message[c.getMessages().size()]; + c.getMessages().toArray(ms); + + assertEquals(Instant.ofEpochSecond(1448470901), ms[0].getTimestamp()); + assertEquals("bob", ms[0].getSenderId()); + assertEquals("Hello there!", ms[0].getContent()); + + assertEquals(Instant.ofEpochSecond(1448470905), ms[1].getTimestamp()); + assertEquals("mike", ms[1].getSenderId()); + assertEquals("how are you?", ms[1].getContent()); + + assertEquals(Instant.ofEpochSecond(1448470906), ms[2].getTimestamp()); + assertEquals("bob", ms[2].getSenderId()); + assertEquals("I'm good thanks, do you like pie?", ms[2].getContent()); + + assertEquals(Instant.ofEpochSecond(1448470910), ms[3].getTimestamp()); + assertEquals("mike", ms[3].getSenderId()); + assertEquals("no, let me ask Angus...", ms[3].getContent()); + + assertEquals(Instant.ofEpochSecond(1448470912), ms[4].getTimestamp()); + assertEquals("angus", ms[4].getSenderId()); + assertEquals("Hell yes! Are we buying some pie?", ms[4].getContent()); + + assertEquals(Instant.ofEpochSecond(1448470914), ms[5].getTimestamp()); + assertEquals("bob", ms[5].getSenderId()); + assertEquals("No, just want to know if there's anybody else in the pie society...", ms[5].getContent()); + + assertEquals(Instant.ofEpochSecond(1448470915), ms[6].getTimestamp()); + assertEquals("angus", ms[6].getSenderId()); + assertEquals("YES! I'm the head pie eater there...", ms[6].getContent()); + + // - reading from file that does not exist ----------------------------------- + + try { + c = exporterIO.readConversation("does_not_exist.txt"); + } catch (Exception e) { + assertEquals("Cannot find file named 'does_not_exist.txt'.", e.getMessage()); + } + + // - reading from a null file ------------------------------------------------ + + try { + c = exporterIO.readConversation(null); + } catch (Exception e) { + assertEquals("An error has occurred.", e.getMessage()); + } + } + + /** + * Tests that exporting a conversation will export the conversation correctly. + * + * @throws Exception When something bad happens. + */ + @Test + public void testWriteConversation() throws Exception { + ConversationExporterIO exporterIO = new ConversationExporterIO(); + Conversation dummyConversation = new Conversation("Empty Convo", new ArrayList<>()); + + // - writing to dummy .json file ----------------------------------------------------------------- + + exporterIO.writeConversation(dummyConversation, "dummy.json"); + + assertTrue((new File("dummy.json")).exists()); + + // - writing to dummy .txt file ------------------------------------------------------------------ + + exporterIO.writeConversation(dummyConversation, "dummy.txt"); + // though it writes to a .txt file, it writes in .json format, so it is not incorrect + assertTrue((new File("dummy.txt")).exists()); + + // - writing to a null file ---------------------------------------------------------------------- + + try { + exporterIO.writeConversation(dummyConversation,null); + } catch (Exception e) { + assertEquals("An error has occurred.", e.getMessage()); + } + + } +} \ No newline at end of file diff --git a/src/test/java/com/mindlinksoft/recruitment/mychat/ConversationExporterTests.java b/src/test/java/com/mindlinksoft/recruitment/mychat/ConversationExporterTests.java index 8addc859..6fae6f8a 100644 --- a/src/test/java/com/mindlinksoft/recruitment/mychat/ConversationExporterTests.java +++ b/src/test/java/com/mindlinksoft/recruitment/mychat/ConversationExporterTests.java @@ -71,172 +71,18 @@ public void testExportingConversationExportsConversation() throws Exception { assertEquals("YES! I'm the head pie eater there...", ms[6].getContent()); } - /** - * Tests that filtering by username functions correctly - */ - @Test - public void testFilterConversationByUser() throws Exception { - ConversationExporter exporter = new ConversationExporter(); - Conversation c; - - // - BOB --------------------------------------------------------- - c = exporter.readConversation("chat.txt"); - - exporter.setUser("bob"); - exporter.filterConversation(c); - - for (Message m : c.getMessages()) { - assertEquals("bob", m.getSenderId()); - } - - assertEquals(3, c.getMessages().size()); - - // - BOB (CAPITALISED) ----------------------------------------- - c = exporter.readConversation("chat.txt"); - // should work the same way - exporter.setUser("BOB"); - exporter.filterConversation(c); - - for (Message m : c.getMessages()) { - assertEquals("bob", m.getSenderId()); - } - - assertEquals(3, c.getMessages().size()); - - // - MIKE -------------------------------------------------------- - c = exporter.readConversation("chat.txt"); - - exporter.setUser("mike"); - exporter.filterConversation(c); - - for (Message m : c.getMessages()) { - assertEquals("mike", m.getSenderId()); - } - - assertEquals(2, c.getMessages().size()); - - // - ALICE ------------------------------------------------------- - c = exporter.readConversation("chat.txt"); - - exporter.setUser("alice"); - exporter.filterConversation(c); - - assertEquals(0, c.getMessages().size()); - } - - - /** - * Tests that filtering for keywords functions correctly - */ - @Test - public void testFilterConversationByKeyword() throws Exception { - ConversationExporter exporter = new ConversationExporter(); - Conversation c; - List ms; - - // - PIE --------------------------------------------------------- - c = exporter.readConversation("chat.txt"); - - exporter.setKeyword("pie"); - exporter.filterConversation(c); - ms = (ArrayList) c.getMessages(); - - assertEquals("I'm good thanks, do you like pie?", ms.get(0).getContent()); - assertEquals("Hell yes! Are we buying some pie?", ms.get(1).getContent()); - assertEquals("No, just want to know if there's anybody else in the pie society...", ms.get(2).getContent()); - assertEquals("YES! I'm the head pie eater there...", ms.get(3).getContent()); - - // - HELL --------------------------------------------------------- - c = exporter.readConversation("chat.txt"); - - exporter.setKeyword("Hell"); - exporter.filterConversation(c); - ms = (ArrayList) c.getMessages(); - - assertEquals("Hello there!", ms.get(0).getContent()); - assertEquals("Hell yes! Are we buying some pie?", ms.get(1).getContent()); - - // - NOPE --------------------------------------------------------- - c = exporter.readConversation("chat.txt"); - - exporter.setKeyword("nope"); - exporter.filterConversation(c); - ms = (ArrayList) c.getMessages(); - - assertEquals(0, ms.size()); - } - - - /** - * Tests that redacting blacklisted words functions correctly - */ - @Test - public void testRedactBlacklistedWords() throws Exception { - ConversationExporter exporter = new ConversationExporter(); - Conversation originalConversation; - Conversation c; - List originalMessages; - List ms; - - // gets the original conversation (unfiltered) - originalConversation = exporter.readConversation("chat.txt"); - originalMessages = (ArrayList) originalConversation.getMessages(); - - // - [PIE] --------------------------------------------------------- - c = exporter.readConversation("chat.txt"); - - exporter.setBlacklistedWords(new String[]{"pie"}); - exporter.filterConversation(c); - ms = (ArrayList) c.getMessages(); - - assertEquals("Hello there!", ms.get(0).getContent()); - assertEquals("how are you?", ms.get(1).getContent()); - assertEquals("I'm good thanks, do you like *redacted*?", ms.get(2).getContent()); - assertEquals("no, let me ask Angus...", ms.get(3).getContent()); - assertEquals("Hell yes! Are we buying some *redacted*?", ms.get(4).getContent()); - assertEquals("No, just want to know if there's anybody else in the *redacted* society...", ms.get(5).getContent()); - assertEquals("YES! I'm the head *redacted* eater there...", ms.get(6).getContent()); - - // - [I'M, YOU] ----------------------------------------------------- - c = exporter.readConversation("chat.txt"); - - exporter.setBlacklistedWords(new String[]{"I'm", "you"}); - exporter.filterConversation(c); - ms = (ArrayList) c.getMessages(); - - assertEquals("Hello there!", ms.get(0).getContent()); - assertEquals("how are *redacted*?", ms.get(1).getContent()); - assertEquals("*redacted* good thanks, do *redacted* like pie?", ms.get(2).getContent()); - assertEquals("no, let me ask Angus...", ms.get(3).getContent()); - assertEquals("Hell yes! Are we buying some pie?", ms.get(4).getContent()); - assertEquals("No, just want to know if there's anybody else in the pie society...", ms.get(5).getContent()); - assertEquals("YES! *redacted* the head pie eater there...", ms.get(6).getContent()); - - // - [NON-EXISTENT] ----------------------------------------------------- - c = exporter.readConversation("chat.txt"); - - exporter.setBlacklistedWords(new String[]{"non-existent"}); - exporter.filterConversation(c); - ms = (ArrayList) c.getMessages(); - - // messages should be the same as the original as the "non-existent" string has no occurrences in the conversation - for (int i = 0; i < ms.size(); i++) { - assertEquals(originalMessages.get(i).getContent(), ms.get(i).getContent()); - } - } - - /** * Tests that multiple filters concurrently function correctly */ @Test public void testMultipleFilters() throws Exception { ConversationExporter exporter = new ConversationExporter(); + ConversationExporterIO exporterIO = new ConversationExporterIO(); Conversation c; List ms; // - USER : BOB, KEYWORD : THERE --------------------------------------- - c = exporter.readConversation("chat.txt"); + c = exporterIO.readConversation("chat.txt"); exporter.setUser("bob"); exporter.setKeyword("there"); @@ -257,7 +103,7 @@ public void testMultipleFilters() throws Exception { // - USER : MIKE, BLACKLIST : [YOU, ANGUS] ------------------------------ - c = exporter.readConversation("chat.txt"); + c = exporterIO.readConversation("chat.txt"); exporter.setUser("mike"); exporter.setKeyword(null); @@ -278,7 +124,7 @@ public void testMultipleFilters() throws Exception { // - KEYWORD : PIE, BLACKLIST : [PIE] ----------------------------------- - c = exporter.readConversation("chat.txt"); + c = exporterIO.readConversation("chat.txt"); exporter.setUser(null); exporter.setKeyword("pie"); @@ -307,7 +153,7 @@ public void testMultipleFilters() throws Exception { // - USER : ANGUS, KEYWORD : YES, BLACKLIST : [SOME] -------------------- - c = exporter.readConversation("chat.txt"); + c = exporterIO.readConversation("chat.txt"); exporter.setUser("angus"); exporter.setKeyword("yes"); @@ -324,6 +170,36 @@ public void testMultipleFilters() throws Exception { } + /** + * Tests that exporter correctly reports on a conversation when asked to + */ + @Test + public void testConversationReporting() throws Exception { + ConversationExporter exporter = new ConversationExporter(); + ConversationExporterIO exporterIO = new ConversationExporterIO(); + Conversation c; + + // - do not report --------------------------------------------------------- + c = exporterIO.readConversation("chat.txt"); + exporter.setReport(false); + exporter.checkReport(c); + + assertNull(c.getActivity()); + + // - report on conversation ------------------------------------------------ + c = exporterIO.readConversation("chat.txt"); + exporter.setReport(true); + exporter.checkReport(c); + + assertEquals("bob", c.getActivity().get(0).getName()); + assertEquals(3, c.getActivity().get(0).getCount()); + + assertEquals("mike", c.getActivity().get(1).getName()); + assertEquals(2, c.getActivity().get(1).getCount()); + + assertEquals("angus", c.getActivity().get(2).getName()); + assertEquals(2, c.getActivity().get(2).getCount()); + } /** * Tests that parameters are correctly parsed into the exporter's parameter variables diff --git a/src/test/java/com/mindlinksoft/recruitment/mychat/ConversationTests.java b/src/test/java/com/mindlinksoft/recruitment/mychat/ConversationTests.java new file mode 100644 index 00000000..47178fe4 --- /dev/null +++ b/src/test/java/com/mindlinksoft/recruitment/mychat/ConversationTests.java @@ -0,0 +1,160 @@ +package com.mindlinksoft.recruitment.mychat; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collection; + +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertEquals; + +public class ConversationTests { + /** + * Tests that a conversation's activity is generated correctly + */ + @Test + public void testActivityGeneration() throws Exception { + Collection testMessages = new ArrayList<>(); + + testMessages.add(new Message(null, "Annie", "*content*")); + testMessages.add(new Message(null, "Carol", "*content*")); + testMessages.add(new Message(null, "Annie", "*content*")); + testMessages.add(new Message(null, "Carol", "*content*")); + testMessages.add(new Message(null, "Dave", "*content*")); + testMessages.add(new Message(null, "Annie", "*content*")); + testMessages.add(new Message(null, "Dave", "*content*")); + testMessages.add(new Message(null, "Carol", "*content*")); + testMessages.add(new Message(null, "Kieran", "*content*")); + + Conversation c = new Conversation("Test", testMessages); + + // - before tracking activity ----------------------------------------- + + assertNull(c.getActivity()); + + // - track activity --------------------------------------------------- + + c.trackActivity(); + + assertEquals("Annie", c.getActivity().get(0).getName()); + assertEquals(3, c.getActivity().get(0).getCount()); + + assertEquals("Carol", c.getActivity().get(1).getName()); + assertEquals(3, c.getActivity().get(1).getCount()); + + assertEquals("Dave", c.getActivity().get(2).getName()); + assertEquals(2, c.getActivity().get(2).getCount()); + + assertEquals("Kieran", c.getActivity().get(3).getName()); + assertEquals(1, c.getActivity().get(3).getCount()); + + // - added more messages ---------------------------------------------- + + testMessages.add(new Message(null, "Carol", "*content*")); + testMessages.add(new Message(null, "Kieran", "*content*")); + testMessages.add(new Message(null, "Carol", "*content*")); + testMessages.add(new Message(null, "Dave", "*content*")); + testMessages.add(new Message(null, "Ella", "*content*")); + testMessages.add(new Message(null, "Kieran", "*content*")); + + c.trackActivity(); + + assertEquals("Annie", c.getActivity().get(0).getName()); + assertEquals(3, c.getActivity().get(0).getCount()); + + assertEquals("Carol", c.getActivity().get(1).getName()); + assertEquals(5, c.getActivity().get(1).getCount()); + + assertEquals("Dave", c.getActivity().get(2).getName()); + assertEquals(3, c.getActivity().get(2).getCount()); + + assertEquals("Kieran", c.getActivity().get(3).getName()); + assertEquals(3, c.getActivity().get(3).getCount()); + + assertEquals("Ella", c.getActivity().get(4).getName()); + assertEquals(1, c.getActivity().get(4).getCount()); + + // - new conversation ------------------------------------------------- + + testMessages = new ArrayList<>(); + c = new Conversation("Test", testMessages); + + c.trackActivity(); + + assertEquals(0, c.getActivity().size()); + + // - null conversation ------------------------------------------------ + + testMessages = null; + c = new Conversation("Test", testMessages); + + c.trackActivity(); + + assertEquals(null, c.getActivity()); + } + + /** + * Tests that a conversation's activity is sorted correctly + */ + @Test + public void testActivitySorting() throws Exception { + Collection testMessages = new ArrayList<>(); + + testMessages.add(new Message(null, "Annie", "*content*")); + testMessages.add(new Message(null, "Carol", "*content*")); + testMessages.add(new Message(null, "Annie", "*content*")); + testMessages.add(new Message(null, "Carol", "*content*")); + testMessages.add(new Message(null, "Dave", "*content*")); + testMessages.add(new Message(null, "Carol", "*content*")); + testMessages.add(new Message(null, "Dave", "*content*")); + testMessages.add(new Message(null, "Carol", "*content*")); + testMessages.add(new Message(null, "Kieran", "*content*")); + + Conversation c = new Conversation("Test", testMessages); + + // - track activity --------------------------------------------------- + + c.trackActivity(); + c.sortActivity(); + + assertEquals("Carol", c.getActivity().get(0).getName()); + assertEquals(4, c.getActivity().get(0).getCount()); + + assertEquals("Annie", c.getActivity().get(1).getName()); + assertEquals(2, c.getActivity().get(1).getCount()); + + assertEquals("Dave", c.getActivity().get(2).getName()); + assertEquals(2, c.getActivity().get(2).getCount()); + + assertEquals("Kieran", c.getActivity().get(3).getName()); + assertEquals(1, c.getActivity().get(3).getCount()); + + // - added more messages ---------------------------------------------- + + testMessages.add(new Message(null, "Carol", "*content*")); + testMessages.add(new Message(null, "Kieran", "*content*")); + testMessages.add(new Message(null, "Kieran", "*content*")); + testMessages.add(new Message(null, "Annie", "*content*")); + testMessages.add(new Message(null, "Ella", "*content*")); + testMessages.add(new Message(null, "Kieran", "*content*")); + + c.trackActivity(); + c.sortActivity(); + + assertEquals("Carol", c.getActivity().get(0).getName()); + assertEquals(5, c.getActivity().get(0).getCount()); + + assertEquals("Kieran", c.getActivity().get(1).getName()); + assertEquals(4, c.getActivity().get(1).getCount()); + + assertEquals("Annie", c.getActivity().get(2).getName()); + assertEquals(3, c.getActivity().get(2).getCount()); + + assertEquals("Dave", c.getActivity().get(3).getName()); + assertEquals(2, c.getActivity().get(3).getCount()); + + assertEquals("Ella", c.getActivity().get(4).getName()); + assertEquals(1, c.getActivity().get(4).getCount()); + } + +} diff --git a/src/test/java/com/mindlinksoft/recruitment/mychat/KeywordFiltererTests.java b/src/test/java/com/mindlinksoft/recruitment/mychat/KeywordFiltererTests.java new file mode 100644 index 00000000..7c992f1b --- /dev/null +++ b/src/test/java/com/mindlinksoft/recruitment/mychat/KeywordFiltererTests.java @@ -0,0 +1,84 @@ +package com.mindlinksoft.recruitment.mychat; + +import com.mindlinksoft.recruitment.mychat.ConversationFilterers.KeywordFilterer; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public class KeywordFiltererTests { + /** + * Tests that filtering for keywords functions correctly + */ + @Test + public void testFilterConversationByKeyword() throws Exception { + KeywordFilterer keywordFilterer = new KeywordFilterer(); + ConversationExporterIO exporterIO = new ConversationExporterIO(); + Conversation c; + List ms; + + // - PIE --------------------------------------------------------- + c = exporterIO.readConversation("chat.txt"); + + keywordFilterer.setKeyword("pie"); + c = keywordFilterer.filter(c); + ms = (ArrayList) c.getMessages(); + + assertEquals("I'm good thanks, do you like pie?", ms.get(0).getContent()); + assertEquals("Hell yes! Are we buying some pie?", ms.get(1).getContent()); + assertEquals("No, just want to know if there's anybody else in the pie society...", ms.get(2).getContent()); + assertEquals("YES! I'm the head pie eater there...", ms.get(3).getContent()); + + // - HELL --------------------------------------------------------- + c = exporterIO.readConversation("chat.txt"); + + keywordFilterer.setKeyword("Hell"); + c = keywordFilterer.filter(c); + ms = (ArrayList) c.getMessages(); + + assertEquals("Hello there!", ms.get(0).getContent()); + assertEquals("Hell yes! Are we buying some pie?", ms.get(1).getContent()); + + // - NOPE --------------------------------------------------------- + c = exporterIO.readConversation("chat.txt"); + + keywordFilterer.setKeyword("nope"); + c = keywordFilterer.filter(c); + ms = (ArrayList) c.getMessages(); + + assertEquals(0, ms.size()); + + // - NULL --------------------------------------------------------- + c = exporterIO.readConversation("chat.txt"); + + keywordFilterer.setKeyword(null); + c = keywordFilterer.filter(c); + ms = (ArrayList) c.getMessages(); + + assertEquals("Hello there!", ms.get(0).getContent()); + assertEquals("how are you?", ms.get(1).getContent()); + assertEquals("I'm good thanks, do you like pie?", ms.get(2).getContent()); + assertEquals("no, let me ask Angus...", ms.get(3).getContent()); + assertEquals("Hell yes! Are we buying some pie?", ms.get(4).getContent()); + assertEquals("No, just want to know if there's anybody else in the pie society...", ms.get(5).getContent()); + assertEquals("YES! I'm the head pie eater there...", ms.get(6).getContent()); + + // - EMPTY STRING ------------------------------------------------- + c = exporterIO.readConversation("chat.txt"); + + keywordFilterer.setKeyword(""); + c = keywordFilterer.filter(c); + ms = (ArrayList) c.getMessages(); + + assertEquals("Hello there!", ms.get(0).getContent()); + assertEquals("how are you?", ms.get(1).getContent()); + assertEquals("I'm good thanks, do you like pie?", ms.get(2).getContent()); + assertEquals("no, let me ask Angus...", ms.get(3).getContent()); + assertEquals("Hell yes! Are we buying some pie?", ms.get(4).getContent()); + assertEquals("No, just want to know if there's anybody else in the pie society...", ms.get(5).getContent()); + assertEquals("YES! I'm the head pie eater there...", ms.get(6).getContent()); + } + +} diff --git a/src/test/java/com/mindlinksoft/recruitment/mychat/UserFiltererTests.java b/src/test/java/com/mindlinksoft/recruitment/mychat/UserFiltererTests.java new file mode 100644 index 00000000..6a1d305f --- /dev/null +++ b/src/test/java/com/mindlinksoft/recruitment/mychat/UserFiltererTests.java @@ -0,0 +1,78 @@ +package com.mindlinksoft.recruitment.mychat; + +import com.mindlinksoft.recruitment.mychat.ConversationFilterers.UserFilterer; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class UserFiltererTests { + /** + * Tests that filtering by username functions correctly + */ + @Test + public void testFilterConversationByUser() throws Exception { + UserFilterer userFilterer = new UserFilterer(); + ConversationExporterIO exporterIO = new ConversationExporterIO(); + Conversation c; + + // - BOB --------------------------------------------------------- + c = exporterIO.readConversation("chat.txt"); + + userFilterer.setUser("bob"); + c = userFilterer.filter(c); + + for (Message m : c.getMessages()) { + assertEquals("bob", m.getSenderId()); + } + + assertEquals(3, c.getMessages().size()); + + // - BOB (CAPITALISED) ----------------------------------------- + c = exporterIO.readConversation("chat.txt"); + // should work the same way as with its lowercase version + userFilterer.setUser("BOB"); + c = userFilterer.filter(c); + + for (Message m : c.getMessages()) { + assertEquals("bob", m.getSenderId()); + } + + assertEquals(3, c.getMessages().size()); + + // - MIKE -------------------------------------------------------- + c = exporterIO.readConversation("chat.txt"); + + userFilterer.setUser("mike"); + c = userFilterer.filter(c); + + for (Message m : c.getMessages()) { + assertEquals("mike", m.getSenderId()); + } + + assertEquals(2, c.getMessages().size()); + + // - ALICE ------------------------------------------------------- + c = exporterIO.readConversation("chat.txt"); + // no alice in the conversation + userFilterer.setUser("alice"); + c = userFilterer.filter(c); + + assertEquals(0, c.getMessages().size()); + + // - NULL -------------------------------------------------------- + c = exporterIO.readConversation("chat.txt"); + + userFilterer.setUser(null); + c = userFilterer.filter(c); + + assertEquals(7, c.getMessages().size()); + + // - EMPTY STRING ------------------------------------------------ + c = exporterIO.readConversation("chat.txt"); + + userFilterer.setUser(""); + c = userFilterer.filter(c); + + assertEquals(7, c.getMessages().size()); + } +}