diff --git a/.classpath b/.classpath new file mode 100644 index 00000000..f0257c5a --- /dev/null +++ b/.classpath @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.gitignore b/.gitignore index 4fb13b9a..0ed16e0b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ target/ bin/ *.iml +.vscode +*.json diff --git a/.project b/.project new file mode 100644 index 00000000..6813f66d --- /dev/null +++ b/.project @@ -0,0 +1,34 @@ + + + my-chat + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + + + 1614007406848 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/.settings/org.eclipse.jdt.apt.core.prefs b/.settings/org.eclipse.jdt.apt.core.prefs new file mode 100644 index 00000000..d4313d4b --- /dev/null +++ b/.settings/org.eclipse.jdt.apt.core.prefs @@ -0,0 +1,2 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.apt.aptEnabled=false diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000..1b6e1ef2 --- /dev/null +++ b/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,9 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.processAnnotations=disabled +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/.settings/org.eclipse.m2e.core.prefs b/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 00000000..f897a7f1 --- /dev/null +++ b/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/src/main/java/com/mindlinksoft/recruitment/mychat/Conversation.java b/src/main/java/com/mindlinksoft/recruitment/mychat/Conversation.java deleted file mode 100644 index d7809f00..00000000 --- a/src/main/java/com/mindlinksoft/recruitment/mychat/Conversation.java +++ /dev/null @@ -1,28 +0,0 @@ -package com.mindlinksoft.recruitment.mychat; - -import java.util.Collection; - -/** - * Represents the model of a conversation. - */ -public final class Conversation { - /** - * The name of the conversation. - */ - public String name; - - /** - * The messages in the conversation. - */ - public Collection messages; - - /** - * Initializes a new instance of the {@link Conversation} class. - * @param name The name of the conversation. - * @param messages The messages in the conversation. - */ - public Conversation(String name, Collection messages) { - this.name = name; - this.messages = messages; - } -} diff --git a/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationArgumentExecution.java b/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationArgumentExecution.java new file mode 100644 index 00000000..053d7023 --- /dev/null +++ b/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationArgumentExecution.java @@ -0,0 +1,55 @@ +package com.mindlinksoft.recruitment.mychat; + +import com.mindlinksoft.recruitment.mychat.Model.Conversation; + +import picocli.CommandLine.ParseResult; +import picocli.CommandLine.Model.OptionSpec; + +public class ConversationArgumentExecution implements IConversationArgumentExecution { + + /** + * Called to process the handed conversation with respects to the ParseResult + * parameter. + * @param conversation The conversation for the command options to work on/with. + * @param pr The parseResult containing the options and their values. + * @throws Exception + */ + @Override + public Conversation executue(Conversation conversation, ParseResult pr) throws Exception { + try { + Conversation convo = conversation; + + for(OptionSpec option : pr.matchedOptions()) { + convo = processOption(convo, option); + } + + return convo; + + } catch (Exception e) { + e.printStackTrace(); + throw new Exception("Error in processing matched options."); + } + } + + + /** + * This handles each matched option individually, peforming their respective tasks. + * @param conversation The convosation that the command option is called on. + * @param option The option that has been called. + * @return The {@link Conversation} that is freshly constructed from the filter/modifications to the original. + */ + protected Conversation processOption(Conversation convo, OptionSpec option) throws Exception { + + ConversationTransformer convoT = new ConversationTransformer(convo); + + switch (option.longestName()){ + case "--filterByUser": return convoT.filterConvoByUser(option.getValue()); + case "--filterByKeyword": return convoT.filterConvoByKeyword(option.getValue()); + case "--blacklist": return convoT.censorConvo(option.getValue()); + case "--inputFilePath": return convo; + case "--outputFilePath": return convo; + default: + throw new Exception("Error: " + option.longestName() + " has not been implemented in either processOption method or its overrides"); + } + } +} diff --git a/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationExporter.java b/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationExporter.java index bf5fa9bf..7beacdd7 100644 --- a/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationExporter.java +++ b/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationExporter.java @@ -1,6 +1,8 @@ package com.mindlinksoft.recruitment.mychat; import com.google.gson.*; +import com.mindlinksoft.recruitment.mychat.Model.Conversation; +import com.mindlinksoft.recruitment.mychat.Model.Message; import picocli.CommandLine; import picocli.CommandLine.ParameterException; @@ -11,6 +13,7 @@ import java.lang.reflect.Type; import java.time.Instant; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; /** @@ -25,6 +28,12 @@ public class ConversationExporter { */ public static void main(String[] args) throws Exception { // We use picocli to parse the command line - see https://picocli.info/ + + // for(String s : args){ + // System.out.println(s); + // } + + ConversationExporterConfiguration configuration = new ConversationExporterConfiguration(); CommandLine cmd = new CommandLine(configuration); @@ -45,9 +54,10 @@ public static void main(String[] args) throws Exception { ConversationExporter exporter = new ConversationExporter(); - exporter.exportConversation(configuration.inputFilePath, configuration.outputFilePath); + exporter.exportConversation(parseResult); System.exit(cmd.getCommandSpec().exitCodeOnSuccess()); + } catch (ParameterException ex) { cmd.getErr().println(ex.getMessage()); if (!UnmatchedArgumentException.printSuggestions(ex, cmd.getErr())) { @@ -69,11 +79,40 @@ public static void main(String[] args) throws Exception { */ public void exportConversation(String inputFilePath, String outputFilePath) throws Exception { Conversation conversation = this.readConversation(inputFilePath); + + this.writeConversation(conversation, outputFilePath); + + System.out.println("Conversation exported from '" + inputFilePath + "' to '" + outputFilePath + "'"); + } + + /** + * Exports the conversation at {@code inputFilePath} as JSON to {@code outputFilePath}. + * @param inputFilePath The input file path. + * @param outputFilePath The output file path. + * @param pr ParseResult containing matched command options. + * @throws Exception Thrown when something bad happens. + */ + public void exportConversation(ParseResult pr) throws Exception { + + if( !pr.hasMatchedOption("--inputFilePath") ){ + throw new Exception("Does not have i/p option"); + } + + if( !pr.hasMatchedOption("--outputFilePath") ){ + throw new Exception("Does not have o/p option"); + } + + String inputFilePath = pr.matchedOption("--inputFilePath").getValue(); + String outputFilePath = pr.matchedOption("outputFilePath").getValue(); + + Conversation conversation = this.readConversation(inputFilePath); + + IConversationArgumentExecution cae = new ConversationArgumentExecution(); + conversation = cae.executue(conversation, pr); this.writeConversation(conversation, outputFilePath); - // TODO: Add more logging... - System.out.println("Conversation exported from '" + inputFilePath + "' to '" + outputFilePath); + System.out.println("Conversation successfully exported from '" + inputFilePath + "' to '" + outputFilePath + "'"); } /** @@ -83,23 +122,22 @@ 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); + 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.setPrettyPrinting(); 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."); + e.printStackTrace(); + throw new IllegalArgumentException(outputFilePath + " file was not found."); } catch (IOException e) { - // TODO: Should probably throw different exception to be more meaningful :/ - throw new Exception("Something went wrong"); + e.printStackTrace(); + throw new IOException("IOException thrown in writing o/p JSON."); } } @@ -121,14 +159,21 @@ public Conversation readConversation(String inputFilePath) throws Exception { while ((line = r.readLine()) != null) { String[] split = line.split(" "); - messages.add(new Message(Instant.ofEpochSecond(Long.parseUnsignedLong(split[0])), split[1], split[2])); + //This bit was apparently wrong, the content of the message was only ever the first word. + messages.add(new Message( + Instant.ofEpochSecond(Long.parseUnsignedLong(split[0])), + split[1], + String.join(" ", Arrays.copyOfRange(split, 2, split.length)) + )); } return new Conversation(conversationName, messages); } catch (FileNotFoundException e) { - throw new IllegalArgumentException("The file was not found."); + e.printStackTrace(); + throw new IllegalArgumentException(inputFilePath + " file was not found."); } catch (IOException e) { - throw new Exception("Something went wrong"); + e.printStackTrace(); + throw new IOException("IOException thrown in reading i/p file."); } } diff --git a/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationExporterConfiguration.java b/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationExporterConfiguration.java index 5d785d40..5cb21d06 100644 --- a/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationExporterConfiguration.java +++ b/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationExporterConfiguration.java @@ -1,5 +1,7 @@ package com.mindlinksoft.recruitment.mychat; +import java.util.List; + import picocli.CommandLine.Option; import picocli.CommandLine.Command; @@ -20,4 +22,22 @@ public final class ConversationExporterConfiguration { */ @Option(names = { "-o", "--outputFilePath" }, description = "The path to the output JSON file.", required = true) public String outputFilePath; + + /** + * Filters msgs to only those sent by given user. + */ + @Option(names = { "-u", "--filterByUser"}, description = "Filters messeges o/p to JSON file to only those sent by provided user.") + public String userIdFilter; + + /** + * Filters msgs to only those containing keyword. + */ + @Option(names = { "-k", "--filterByKeyword"}, description = "Filters messeges o/p to JSON file to only those containing the keyword.") + public String keyWordFilter; + + /** + * Replaces any word that is blacklisted with *redacted*. + */ + @Option(names = { "-b", "--blacklist"}, description = "If word is in o/p JSON file, it is replaced with *redacted*") + public List blacklist; } diff --git a/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationTransformer.java b/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationTransformer.java new file mode 100644 index 00000000..065de811 --- /dev/null +++ b/src/main/java/com/mindlinksoft/recruitment/mychat/ConversationTransformer.java @@ -0,0 +1,83 @@ +package com.mindlinksoft.recruitment.mychat; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import com.mindlinksoft.recruitment.mychat.Model.Conversation; +import com.mindlinksoft.recruitment.mychat.Model.Message; + + +public class ConversationTransformer { + + Conversation convo; + + /** + * Class for Transforming a handed conversation. + * Each method returns a new Conversation that has been transformed from the original by some means. + * @param convo Initial conversation. + */ + public ConversationTransformer(Conversation convo) { + this.convo = convo; + } + + /** + * A method that returns a Conversation object that is the result of filtering this Conversation's messages by the userId that sent them. + * @param userID The userId used to filter the messages of this Conversation + * @return {@link Conversation} freshly constructed with its list of messages filtered to those sent by userId param. + */ + public Conversation filterConvoByUser(String userId) { + Collection filteredMsgs = new ArrayList(); + + for(Message msg : convo.getMessages()) { + if(msg.senderId.equals(userId)){ + filteredMsgs.add(msg); + } + } + + return new Conversation(convo.name, filteredMsgs); + } + + /** + * A method that returns a Conversation object that is the result of filtering this Conversation's messages to those that contain a keyword. + * @param keyword The keyword used to filter the messages of this Conversation + * @return {@link Conversation} freshly constructed with its list of messages filtered to those with keyword param. + */ + public Conversation filterConvoByKeyword(String keyword) { + Collection filteredMsgs = new ArrayList(); + + for(Message msg : convo.getMessages()) { + if(msg.content.contains(keyword)){ + filteredMsgs.add(msg); + } + } + + return new Conversation(convo.name, filteredMsgs); + } + + /** + * A method that returns a new Conversation object of this conversation but with certain words on the blacklist censored by replacing them with *redacted* + * Case sensitive. + * @param blacklist The list of words that will be censored. + * @return {@link Conversation} freshly constructed with the blacklist words cencored from its of messages. + */ + public Conversation censorConvo(List blacklist) { + Collection filteredMsgs = new ArrayList(); + + for(Message msg : convo.getMessages()) { + String content = msg.content; + + for(String s : blacklist) { + + content = content.replace(s, "*redacted*"); + + } + + filteredMsgs.add(new Message(msg.timestamp, msg.senderId, content)); + + } + + return new Conversation(convo.name, filteredMsgs); + } + +} diff --git a/src/main/java/com/mindlinksoft/recruitment/mychat/IConversationArgumentExecution.java b/src/main/java/com/mindlinksoft/recruitment/mychat/IConversationArgumentExecution.java new file mode 100644 index 00000000..53e38a23 --- /dev/null +++ b/src/main/java/com/mindlinksoft/recruitment/mychat/IConversationArgumentExecution.java @@ -0,0 +1,11 @@ +package com.mindlinksoft.recruitment.mychat; + +import com.mindlinksoft.recruitment.mychat.Model.Conversation; + +import picocli.CommandLine.ParseResult; + +public interface IConversationArgumentExecution { + + public Conversation executue(Conversation convo, ParseResult pr) throws Exception; + +} diff --git a/src/main/java/com/mindlinksoft/recruitment/mychat/Message.java b/src/main/java/com/mindlinksoft/recruitment/mychat/Message.java deleted file mode 100644 index 4135d497..00000000 --- a/src/main/java/com/mindlinksoft/recruitment/mychat/Message.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.mindlinksoft.recruitment.mychat; - -import java.time.Instant; - -/** - * Represents a chat message. - */ -public final class Message { - /** - * The message content. - */ - public String content; - - /** - * The message timestamp. - */ - public Instant timestamp; - - /** - * The message sender. - */ - public String senderId; - - /** - * Initializes a new instance of the {@link Message} class. - * @param timestamp The timestamp at which the message was sent. - * @param senderId The ID of the sender. - * @param content The message content. - */ - public Message(Instant timestamp, String senderId, String content) { - this.content = content; - this.timestamp = timestamp; - this.senderId = senderId; - } -} diff --git a/src/main/java/com/mindlinksoft/recruitment/mychat/Model/Conversation.java b/src/main/java/com/mindlinksoft/recruitment/mychat/Model/Conversation.java new file mode 100644 index 00000000..bdf32eed --- /dev/null +++ b/src/main/java/com/mindlinksoft/recruitment/mychat/Model/Conversation.java @@ -0,0 +1,58 @@ +package com.mindlinksoft.recruitment.mychat.Model; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Represents the model of a conversation. + */ +public final class Conversation { + + //Making variables final (and private for messages) to make the class immutable. + + /** + * The name of the conversation. + */ + public final String name; + + /** + * The messages in the conversation. + */ + private final Collection messages; + + //By returning a copy of the original list, this prevents people from modifing the contents of the messages variable + //Note: Messages are immutable as well, so one still can't change the elements of the orginal list by ref shenanigans. + public Collection getMessages() { + return new ArrayList(messages); + } + + /** + * Initializes a new instance of the {@link Conversation} class. + * @param name The name of the conversation. + * @param messages The messages in the conversation. + */ + public Conversation(String name, Collection messages) { + this.name = name; + this.messages = messages; + } + + @Override + public boolean equals(Object obj){ + if(this == obj) + return true; + if((obj == null) || (obj.getClass() != this.getClass())) + return false; + Conversation convo = (Conversation)obj; + return (name == convo.name || (name != null && name.equals(convo.name))) + && (messages == convo.messages || (messages != null && messages.equals(convo.messages))); + } + + @Override + public int hashCode(){ + int hash = 17; + hash = 31 * hash + (null == name ? 0 : name.hashCode()); + hash = 31 * hash + (null == messages ? 0 : messages.hashCode()); + return hash; + } + +} diff --git a/src/main/java/com/mindlinksoft/recruitment/mychat/Model/Message.java b/src/main/java/com/mindlinksoft/recruitment/mychat/Model/Message.java new file mode 100644 index 00000000..1652ee01 --- /dev/null +++ b/src/main/java/com/mindlinksoft/recruitment/mychat/Model/Message.java @@ -0,0 +1,62 @@ +package com.mindlinksoft.recruitment.mychat.Model; + +import java.time.Instant; + +/** + * Represents a chat message. + */ +public final class Message { + + //Note - I've made these properties final as I believe that there should be no reason to change. + + /** + * The message content. + */ + public final String content; + + /** + * The message timestamp. + */ + public final Instant timestamp; + + /** + * The message sender. + */ + public final String senderId; + + /** + * Initializes a new instance of the {@link Message} class. + * @param timestamp The timestamp at which the message was sent. + * @param senderId The ID of the sender. + * @param content The message content. + */ + public Message(Instant timestamp, String senderId, String content) { + this.content = content; + this.timestamp = timestamp; + this.senderId = senderId; + } + + @Override + public boolean equals(Object obj) { + if(this == obj) + return true; + if((obj == null) || (obj.getClass() != this.getClass())) + return false; + + Message msg = (Message)obj; + return (senderId == msg.senderId || (senderId != null && senderId.equals(msg.senderId))) + && (content == msg.content || (content != null && content.equals(msg.content))) + && (timestamp == msg.timestamp || (timestamp != null && timestamp.equals(msg.timestamp))); + } + + @Override + public int hashCode(){ + int hash = 17; + hash = 31 * hash + (null == content ? 0 : content.hashCode()); + hash = 31 * hash + (null == timestamp ? 0 : timestamp.hashCode()); + hash = 31 * hash + (null == senderId ? 0 : senderId.hashCode()); + return hash; + } + + +} diff --git a/src/test/java/com/mindlinksoft/recruitment/mychat/ConversationExporterTests.java b/src/test/java/com/mindlinksoft/recruitment/mychat/ConversationExporterTests.java index ebd59fe0..6e555eba 100644 --- a/src/test/java/com/mindlinksoft/recruitment/mychat/ConversationExporterTests.java +++ b/src/test/java/com/mindlinksoft/recruitment/mychat/ConversationExporterTests.java @@ -1,12 +1,17 @@ package com.mindlinksoft.recruitment.mychat; import com.google.gson.*; +import com.mindlinksoft.recruitment.mychat.Model.Conversation; +import com.mindlinksoft.recruitment.mychat.Model.Message; + import org.junit.Test; import java.io.FileInputStream; import java.io.InputStreamReader; import java.lang.reflect.Type; import java.time.Instant; +import java.util.ArrayList; +import java.util.List; import static org.junit.Assert.assertEquals; @@ -14,6 +19,168 @@ * Tests for the {@link ConversationExporter}. */ public class ConversationExporterTests { + + //Unit Testing + + //Filter by user function unit test + @Test + public void userFilterUnitTest() throws Exception { + + Message msg1 = new Message(Instant.ofEpochSecond(10), "Rodger", "How you doing today?"); + Message msg2 = new Message(Instant.ofEpochSecond(20), "Tim", "Not great."); + Message msg3 = new Message(Instant.ofEpochSecond(30), "Rodger", "Why's that?"); + Message msg4 = new Message(Instant.ofEpochSecond(40), "Tim", "Had some mac n' cheese for dinner."); + Message msg5 = new Message(Instant.ofEpochSecond(50), "Rodger", "Isn't that your favorite?"); + Message msg6 = new Message(Instant.ofEpochSecond(60), "Tim", "yeah."); + Message msg7 = new Message(Instant.ofEpochSecond(70), "Rodger", "So what's wrong?"); + Message msg8 = new Message(Instant.ofEpochSecond(80), "Tim", "Mark cooked for dinner."); + Message msg9 = new Message(Instant.ofEpochSecond(90), "Rodger", "OH."); + + + List msgs = new ArrayList(); + msgs.add(msg1); + msgs.add(msg2); + msgs.add(msg3); + msgs.add(msg4); + msgs.add(msg5); + msgs.add(msg6); + msgs.add(msg7); + msgs.add(msg8); + msgs.add(msg9); + + List exptMsgs = new ArrayList(); + exptMsgs.add(msg2); + exptMsgs.add(msg4); + exptMsgs.add(msg6); + exptMsgs.add(msg8); + + Conversation baseConvo = new Conversation("Cooking", msgs); + Conversation exptConvo = new Conversation("Cooking", exptMsgs); + + Conversation actualConvo = new ConversationTransformer(baseConvo).filterConvoByUser("Tim"); + + System.out.println(exptConvo.equals(actualConvo)); + + assertEquals(exptConvo, actualConvo); + + } + + + //Filter by keyword function unit test + @Test + public void contentFilterUnitTest() throws Exception { + Message msg1 = new Message(Instant.ofEpochSecond(10), "Rodger", "How you doing today?"); + Message msg2 = new Message(Instant.ofEpochSecond(20), "Tim", "Not great."); + Message msg3 = new Message(Instant.ofEpochSecond(30), "Rodger", "Why's that?"); + Message msg4 = new Message(Instant.ofEpochSecond(40), "Tim", "Had some mac n' cheese for dinner."); + Message msg5 = new Message(Instant.ofEpochSecond(50), "Rodger", "Isn't that your favorite?"); + Message msg6 = new Message(Instant.ofEpochSecond(60), "Tim", "yeah."); + Message msg7 = new Message(Instant.ofEpochSecond(70), "Rodger", "So what's wrong?"); + Message msg8 = new Message(Instant.ofEpochSecond(80), "Tim", "Mark cooked for dinner."); + Message msg9 = new Message(Instant.ofEpochSecond(90), "Rodger", "OH."); + + + List msgs = new ArrayList(); + msgs.add(msg1); + msgs.add(msg2); + msgs.add(msg3); + msgs.add(msg4); + msgs.add(msg5); + msgs.add(msg6); + msgs.add(msg7); + msgs.add(msg8); + msgs.add(msg9); + + List exptMsgs = new ArrayList(); + exptMsgs.add(msg4); + exptMsgs.add(msg8); + + Conversation baseConvo = new Conversation("Cooking", msgs); + Conversation exptConvo = new Conversation("Cooking", exptMsgs); + + Conversation actualConvo = new ConversationTransformer(baseConvo).filterConvoByKeyword("dinner"); + + System.out.println(exptConvo.equals(actualConvo)); + + assertEquals(exptConvo, actualConvo); + } + + //Censorship function unit test + @Test + public void censorConversationUnitTest() throws Exception { + + List msgs = new ArrayList(); + msgs.add(new Message(Instant.ofEpochSecond(10), "Rodger", "How you doing today?")); + msgs.add(new Message(Instant.ofEpochSecond(20), "Tim", "Not great.")); + msgs.add(new Message(Instant.ofEpochSecond(30), "Rodger", "Why's that?")); + msgs.add(new Message(Instant.ofEpochSecond(40), "Tim", "Had some mac n' cheese for dinner.")); + msgs.add(new Message(Instant.ofEpochSecond(50), "Rodger", "Isn't that your favorite?")); + msgs.add(new Message(Instant.ofEpochSecond(60), "Tim", "yeah.")); + msgs.add(new Message(Instant.ofEpochSecond(70), "Rodger", "So what's wrong?")); + msgs.add(new Message(Instant.ofEpochSecond(80), "Tim", "Mark cooked for dinner.")); + msgs.add(new Message(Instant.ofEpochSecond(90), "Rodger", "OH.")); + + Conversation baseConvo = new Conversation("Cooking", msgs); + + + List exptMsgs = new ArrayList(); + exptMsgs.add(new Message(Instant.ofEpochSecond(10), "Rodger", "How you doing today?")); + exptMsgs.add(new Message(Instant.ofEpochSecond(20), "Tim", "Not great.")); + exptMsgs.add(new Message(Instant.ofEpochSecond(30), "Rodger", "Why's that?")); + exptMsgs.add(new Message(Instant.ofEpochSecond(40), "Tim", "Had some mac n' cheese for *redacted*.")); + exptMsgs.add(new Message(Instant.ofEpochSecond(50), "Rodger", "Isn't that your favorite?")); + exptMsgs.add(new Message(Instant.ofEpochSecond(60), "Tim", "yeah.")); + exptMsgs.add(new Message(Instant.ofEpochSecond(70), "Rodger", "So what's wrong?")); + exptMsgs.add(new Message(Instant.ofEpochSecond(80), "Tim", "Mark *redacted*ed for *redacted*.")); + exptMsgs.add(new Message(Instant.ofEpochSecond(90), "Rodger", "OH.")); + + Conversation exptConvo = new Conversation("Cooking", exptMsgs); + + + List blacklist = new ArrayList(); + blacklist.add("dinner"); + blacklist.add("cook"); + + + Conversation actualConvo = new ConversationTransformer(baseConvo).censorConvo(blacklist); + + for (int i = 0; i < actualConvo.getMessages().size(); i++) { + Message msg1 = ((ArrayList)actualConvo.getMessages()).get(i); + Message msg2 = ((ArrayList)exptConvo.getMessages()).get(i); + if(!msg1.equals(msg2)){ + System.out.println("content: " + msg1.content.equals(msg2.content)); + System.out.println("Timestamp: " + msg1.timestamp.equals(msg2.timestamp)); + System.out.println("ID: " + msg1.senderId.equals(msg2.senderId)); + } + } + + assertEquals(exptConvo, actualConvo); + } + + + //Reading file into Conversation object unit test + @Test + public void readingUnitTest() throws Exception { + + List msgs = new ArrayList(); + + msgs.add(new Message( Instant.ofEpochSecond(1448470901), "bob", "Hello there!")); + msgs.add(new Message( Instant.ofEpochSecond(1448470905), "mike", "how are you?")); + msgs.add(new Message( Instant.ofEpochSecond(1448470906), "bob", "I'm good thanks, do you like pie?")); + msgs.add(new Message( Instant.ofEpochSecond(1448470910), "mike", "no, let me ask Angus...")); + msgs.add(new Message( Instant.ofEpochSecond(1448470912), "angus", "Hell yes! Are we buying some pie?")); + msgs.add(new Message( Instant.ofEpochSecond(1448470914), "bob", "No, just want to know if there's anybody else in the pie society...")); + msgs.add(new Message( Instant.ofEpochSecond(1448470915), "angus", "YES! I'm the head pie eater there...")); + + Conversation expected = new Conversation("My Conversation", msgs); + + ConversationExporter ce = new ConversationExporter(); + assertEquals(expected, ce.readConversation("chat.txt")); + } + + + //End to End testing. + /** * Tests that exporting a conversation will export the conversation correctly. * @throws Exception When something bad happens. @@ -33,10 +200,10 @@ public void testExportingConversationExportsConversation() throws Exception { assertEquals("My Conversation", c.name); - 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); @@ -67,6 +234,132 @@ public void testExportingConversationExportsConversation() throws Exception { assertEquals("YES! I'm the head pie eater there...", ms[6].content); } + /** + * Tests for the functions in {@link Conversation} designed to filter/censor + * said conversation. + * @throws Exception + */ + @Test + public void optionFunctionTests() throws Exception { + + ConversationExporter exporter = new ConversationExporter(); + + Conversation convo = exporter.readConversation("chat.txt"); + ConversationTransformer c = new ConversationTransformer(convo); + + //User filtered convosation. + Conversation userf_c = c.filterConvoByUser("bob"); + Message[] ms = new Message[userf_c.getMessages().size()]; + userf_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(1448470906), ms[1].timestamp); + assertEquals("bob", ms[1].senderId); + assertEquals("I'm good thanks, do you like pie?", ms[1].content); + + assertEquals(Instant.ofEpochSecond(1448470914), ms[2].timestamp); + assertEquals("bob", ms[2].senderId); + assertEquals("No, just want to know if there's anybody else in the pie society...", ms[2].content); + + + + //Keyword filtered convosation. + Conversation keyf_c = c.filterConvoByKeyword("pie"); + ms = new Message[keyf_c.getMessages().size()]; + keyf_c.getMessages().toArray(ms); + + assertEquals(Instant.ofEpochSecond(1448470906), ms[0].timestamp); + assertEquals("bob", ms[0].senderId); + assertEquals("I'm good thanks, do you like pie?", ms[0].content); + + assertEquals(Instant.ofEpochSecond(1448470912), ms[1].timestamp); + assertEquals("angus", ms[1].senderId); + assertEquals("Hell yes! Are we buying some pie?", ms[1].content); + + assertEquals(Instant.ofEpochSecond(1448470914), ms[2].timestamp); + assertEquals("bob", ms[2].senderId); + assertEquals("No, just want to know if there's anybody else in the pie society...", ms[2].content); + + assertEquals(Instant.ofEpochSecond(1448470915), ms[3].timestamp); + assertEquals("angus", ms[3].senderId); + assertEquals("YES! I'm the head pie eater there...", ms[3].content); + + + + //Censored convosation. + ArrayList blacklist = new ArrayList(); + blacklist.add("pie"); + blacklist.add("society"); + Conversation censored_c = c.censorConvo(blacklist); + ms = new Message[censored_c.getMessages().size()]; + censored_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(1448470905), ms[1].timestamp); + assertEquals("mike", ms[1].senderId); + assertEquals("how are you?", ms[1].content); + + assertEquals(Instant.ofEpochSecond(1448470906), ms[2].timestamp); + assertEquals("bob", ms[2].senderId); + assertEquals("I'm good thanks, do you like *redacted*?", ms[2].content); + + assertEquals(Instant.ofEpochSecond(1448470910), ms[3].timestamp); + assertEquals("mike", ms[3].senderId); + assertEquals("no, let me ask Angus...", ms[3].content); + + assertEquals(Instant.ofEpochSecond(1448470912), ms[4].timestamp); + assertEquals("angus", ms[4].senderId); + assertEquals("Hell yes! Are we buying some *redacted*?", ms[4].content); + + 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 *redacted* *redacted*...", ms[5].content); + + assertEquals(Instant.ofEpochSecond(1448470915), ms[6].timestamp); + assertEquals("angus", ms[6].senderId); + assertEquals("YES! I'm the head *redacted* eater there...", ms[6].content); + + + } + + /** + * Will call the ConversationExporter's main method, testing that the program + * parses the args correctly. + * + * Note - Order of options in command matter. + * + * @throws Exception + */ + @Test + public void TestingParsing() throws Exception { + + ConversationExporter.main(new String[]{"-i", "chat.txt", "-o", "chat.json", "-u=bob", "-k=pie", "-b=pie", "-b=society"}); + + GsonBuilder builder = new GsonBuilder(); + builder.registerTypeAdapter(Instant.class, new InstantDeserializer()); + + Gson g = builder.create(); + + Conversation c = g.fromJson(new InputStreamReader(new FileInputStream("chat.json")), Conversation.class); + + Message[] ms = new Message[c.getMessages().size()]; + c.getMessages().toArray(ms); + + assertEquals(Instant.ofEpochSecond(1448470906), ms[0].timestamp); + assertEquals("bob", ms[0].senderId); + assertEquals("I'm good thanks, do you like *redacted*?", ms[0].content); + + assertEquals(Instant.ofEpochSecond(1448470914), ms[1].timestamp); + assertEquals("bob", ms[1].senderId); + assertEquals("No, just want to know if there's anybody else in the *redacted* *redacted*...", ms[1].content); + } + class InstantDeserializer implements JsonDeserializer { @Override