From aed5c9917226b03a78a281f465a135513dc967e8 Mon Sep 17 00:00:00 2001 From: JokerYan <770792058@qq.com> Date: Tue, 15 Oct 2019 23:41:30 +0800 Subject: [PATCH 1/8] Add edit distance calculation and test --- .travis.yml | 6 +- .../seedu/duke/email/EmailContentParser.java | 56 +++++++++++++++++++ .../seedu/duke/EmailContentParserTest.java | 21 +++++++ 3 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 src/test/java/seedu/duke/EmailContentParserTest.java diff --git a/.travis.yml b/.travis.yml index 22661e8c30..558d4aa1bb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,4 +2,8 @@ language: java jdk: oraclejdk11 before_install: - - chmod +x gradlew \ No newline at end of file + - chmod +x gradlew + +script: + - ./gradlew check + - ./gradlew test \ No newline at end of file diff --git a/src/main/java/seedu/duke/email/EmailContentParser.java b/src/main/java/seedu/duke/email/EmailContentParser.java index e88045b35a..4692beca32 100644 --- a/src/main/java/seedu/duke/email/EmailContentParser.java +++ b/src/main/java/seedu/duke/email/EmailContentParser.java @@ -3,7 +3,9 @@ import seedu.duke.Duke; import seedu.duke.email.entity.Email; +import java.lang.reflect.Array; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -16,6 +18,7 @@ public class EmailContentParser { private static int KEYWORD_SENDER_WEIGHTAGE = 3; private static int KEYWORD_BODY_WEIGHTAGE = 1; private static ArrayList keywordList; + private static int INFINITY = 0x3f3f3f; public static void allKeywordInEmail(Email email) { for (KeywordPair keywordPair : keywordList) { @@ -87,10 +90,63 @@ public static void initKeywordList() { "Low Mun Bak")))); keywordList.add(new KeywordPair("SEP", new ArrayList(List.of( "SEP", "Student Exchange Programme")))); + keywordList.add(new KeywordPair("Tutorial", new ArrayList(List.of( + "Tutorial")))); + keywordList.add(new KeywordPair("Assignment", new ArrayList(List.of( + "Assignment")))); + keywordList.add(new KeywordPair("Spam", new ArrayList(List.of( + "UHC Wellness", "luminus-do-not-reply", "NUS Libraries")))); EmailContentParser.keywordList = keywordList; } + /** + * Computes the edit distance between A and B, which is the number of steps required to transform A to B + * if only addition, deletion, update of a single character is allowed for each step. + * + * @param A first string + * @param B second string + * @return edit distance between A and B + */ + public static int editDistance(String A, String B) { + if (A.length() == 0 || B.length() == 0) { + return A.length() + B.length(); + } + A = A.toLowerCase(); + B = B.toLowerCase(); + //Prepare a distance array for DP + int[][] dist = new int[A.length() + 1][B.length() + 1]; + //Initialize distance array with all zeros + for (int[] row : dist) { + Arrays.fill(row, 0); + } + //Initialize starting positions for DP + for (int i = 0; i <= A.length(); i++) { + dist[i][0] = i; + } + for (int j = 0; j <= B.length(); j++) { + dist[0][j] = j; + } + //Start DP + for (int i = 1; i <= A.length(); i++) { + for (int j = 1; j <= B.length(); j++) { + int min = INFINITY; + min = Math.min(min, dist[i - 1][j - 1] + (A.charAt(i - 1) == B.charAt(j - 1) ? 0 : 1)); + min = Math.min(min, dist[i - 1][j] + 1); + min = Math.min(min, dist[i][j - 1] + 1); + dist[i][j] = min; + } + } + for (int i = 0; i <= A.length(); i++) { + for (int j = 0; j <= B.length(); j++) { + System.out.print(dist[i][j]); + System.out.print(" "); + } + System.out.println("\n"); + } + return dist[A.length()][B.length()]; + } + /** * A pair of keyword with its possible expressions */ diff --git a/src/test/java/seedu/duke/EmailContentParserTest.java b/src/test/java/seedu/duke/EmailContentParserTest.java new file mode 100644 index 0000000000..86316b0bb5 --- /dev/null +++ b/src/test/java/seedu/duke/EmailContentParserTest.java @@ -0,0 +1,21 @@ +package seedu.duke; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.fail; + +import static seedu.duke.email.EmailContentParser.editDistance; + +public class EmailContentParserTest { + @Test + public void editDistanceTest() { + assertEquals(1, editDistance("a", "b")); + assertEquals(1, editDistance("a", "")); + assertEquals(2, editDistance("a", "bc")); + assertEquals(1, editDistance("a", "ba")); + assertEquals(4, editDistance("food", "money")); + } +} From 5d342dd18ea1c46c43b558b03e4398a2b34ff76c Mon Sep 17 00:00:00 2001 From: JokerYan <770792058@qq.com> Date: Tue, 15 Oct 2019 23:44:45 +0800 Subject: [PATCH 2/8] Fix command parse test error --- .../java/seedu/duke/CommandParserTest.java | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/test/java/seedu/duke/CommandParserTest.java b/src/test/java/seedu/duke/CommandParserTest.java index ce56c42e2e..30b839700a 100644 --- a/src/test/java/seedu/duke/CommandParserTest.java +++ b/src/test/java/seedu/duke/CommandParserTest.java @@ -230,19 +230,19 @@ public void parseAddToDoCommandTest() { try { Class parser = Class.forName("seedu.duke.CommandParser"); Method method = parser.getDeclaredMethod("parseAddToDoCommand", TaskList.class, String.class, - String.class, ArrayList.class); + String.class, ArrayList.class, String.class); method.setAccessible(true); ArrayList tagList = new ArrayList<>(Arrays.asList("123", "234")); String doafter = "345"; - assertTrue(method.invoke(null, null, "todo 123", null, null) instanceof TaskAddCommand); - assertTrue(method.invoke(null, null, "todo 123", null, tagList) instanceof TaskAddCommand); - assertTrue(method.invoke(null, null, "todo 123", doafter, tagList) instanceof TaskAddCommand); - assertTrue(method.invoke(null, null, "todo 123 234", null, null) instanceof TaskAddCommand); - assertTrue(method.invoke(null, null, "todo abc 123 /", null, null) instanceof InvalidCommand); - assertTrue(method.invoke(null, null, "todo ", null, null) instanceof InvalidCommand); - assertTrue(method.invoke(null, null, "todo", null, null) instanceof InvalidCommand); + assertTrue(method.invoke(null, null, "todo 123", null, null, "") instanceof TaskAddCommand); + assertTrue(method.invoke(null, null, "todo 123", null, tagList, "") instanceof TaskAddCommand); + assertTrue(method.invoke(null, null, "todo 123", doafter, tagList, "") instanceof TaskAddCommand); + assertTrue(method.invoke(null, null, "todo 123 234", null, null, "") instanceof TaskAddCommand); + assertTrue(method.invoke(null, null, "todo abc 123 /", null, null, "") instanceof InvalidCommand); + assertTrue(method.invoke(null, null, "todo ", null, null, "") instanceof InvalidCommand); + assertTrue(method.invoke(null, null, "todo", null, null, "") instanceof InvalidCommand); } catch (ClassNotFoundException e) { fail("No such class"); } catch (NoSuchMethodException e) { @@ -259,21 +259,21 @@ public void parseAddDeadlineCommandTest() { try { Class parser = Class.forName("seedu.duke.CommandParser"); Method method = parser.getDeclaredMethod("parseAddDeadlineCommand", TaskList.class, - String.class, LocalDateTime.class, String.class, ArrayList.class); + String.class, LocalDateTime.class, String.class, ArrayList.class, String.class); method.setAccessible(true); ArrayList tagList = new ArrayList<>(Arrays.asList("123", "234")); LocalDateTime time = Task.parseDate("11/12/2019 1220"); String doafter = "345"; - assertTrue(method.invoke(null, null, "deadline 123", time, null, null) instanceof TaskAddCommand); - assertTrue(method.invoke(null, null, "deadline 123", time, null, tagList) instanceof TaskAddCommand); - assertTrue(method.invoke(null, null, "deadline 123", time, doafter, tagList) instanceof TaskAddCommand); - assertTrue(method.invoke(null, null, "deadline 123 234", time, null, null) instanceof TaskAddCommand); - assertTrue(method.invoke(null, null, "deadline abc 123 /", time, null, null) instanceof InvalidCommand); - assertTrue(method.invoke(null, null, "deadline ", time, null, null) instanceof InvalidCommand); - assertTrue(method.invoke(null, null, "deadline", time, null, null) instanceof InvalidCommand); - assertTrue(method.invoke(null, null, "deadline 123", null, null, null) instanceof InvalidCommand); + assertTrue(method.invoke(null, null, "deadline 123", time, null, null, "") instanceof TaskAddCommand); + assertTrue(method.invoke(null, null, "deadline 123", time, null, tagList, "") instanceof TaskAddCommand); + assertTrue(method.invoke(null, null, "deadline 123", time, doafter, tagList, "") instanceof TaskAddCommand); + assertTrue(method.invoke(null, null, "deadline 123 234", time, null, null, "") instanceof TaskAddCommand); + assertTrue(method.invoke(null, null, "deadline abc 123 /", time, null, null, "") instanceof InvalidCommand); + assertTrue(method.invoke(null, null, "deadline ", time, null, null, "") instanceof InvalidCommand); + assertTrue(method.invoke(null, null, "deadline", time, null, null, "") instanceof InvalidCommand); + assertTrue(method.invoke(null, null, "deadline 123", null, null, null, "") instanceof InvalidCommand); } catch (ClassNotFoundException e) { fail("No such class"); } catch (NoSuchMethodException e) { @@ -291,21 +291,21 @@ public void parseAddEventCommandTest() { try { Class parser = Class.forName("seedu.duke.CommandParser"); Method method = parser.getDeclaredMethod("parseEventCommand", TaskList.class, - String.class, LocalDateTime.class, String.class, ArrayList.class); + String.class, LocalDateTime.class, String.class, ArrayList.class, String.class); method.setAccessible(true); ArrayList tagList = new ArrayList<>(Arrays.asList("123", "234")); LocalDateTime time = Task.parseDate("11/12/2019 1220"); String doafter = "345"; - assertTrue(method.invoke(null, null, "event 123", time, null, null) instanceof TaskAddCommand); - assertTrue(method.invoke(null, null, "event 123", time, null, tagList) instanceof TaskAddCommand); - assertTrue(method.invoke(null, null, "event 123", time, doafter, tagList) instanceof TaskAddCommand); - assertTrue(method.invoke(null, null, "event 123 234", time, null, null) instanceof TaskAddCommand); - assertTrue(method.invoke(null, null, "event abc 123 /", time, null, null) instanceof InvalidCommand); - assertTrue(method.invoke(null, null, "event ", time, null, null) instanceof InvalidCommand); - assertTrue(method.invoke(null, null, "event", time, null, null) instanceof InvalidCommand); - assertTrue(method.invoke(null, null, "event 123", null, null, null) instanceof InvalidCommand); + assertTrue(method.invoke(null, null, "event 123", time, null, null, "") instanceof TaskAddCommand); + assertTrue(method.invoke(null, null, "event 123", time, null, tagList, "") instanceof TaskAddCommand); + assertTrue(method.invoke(null, null, "event 123", time, doafter, tagList, "") instanceof TaskAddCommand); + assertTrue(method.invoke(null, null, "event 123 234", time, null, null, "") instanceof TaskAddCommand); + assertTrue(method.invoke(null, null, "event abc 123 /", time, null, null, "") instanceof InvalidCommand); + assertTrue(method.invoke(null, null, "event ", time, null, null, "") instanceof InvalidCommand); + assertTrue(method.invoke(null, null, "event", time, null, null, "") instanceof InvalidCommand); + assertTrue(method.invoke(null, null, "event 123", null, null, null, "") instanceof InvalidCommand); } catch (ClassNotFoundException e) { fail("No such class"); } catch (NoSuchMethodException e) { From 393d00ee66307a594080a7d8f25c2dfda9192337 Mon Sep 17 00:00:00 2001 From: JokerYan <770792058@qq.com> Date: Wed, 16 Oct 2019 19:31:41 +0800 Subject: [PATCH 3/8] Add fuzzy search algorithm --- src/main/java/seedu/duke/Duke.java | 3 ++ .../seedu/duke/email/EmailContentParser.java | 32 +++++++++++++++---- .../seedu/duke/EmailContentParserTest.java | 1 + 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/src/main/java/seedu/duke/Duke.java b/src/main/java/seedu/duke/Duke.java index 625ed2878b..537105c330 100644 --- a/src/main/java/seedu/duke/Duke.java +++ b/src/main/java/seedu/duke/Duke.java @@ -8,6 +8,8 @@ import seedu.duke.task.TaskList; import seedu.duke.task.TaskStorage; +import static seedu.duke.email.EmailContentParser.testFuzzySearchInString; + /** * The main class of the program, which provides the entry point. @@ -39,6 +41,7 @@ public static UI getUI() { */ public Duke() { ui = new UI(); + testFuzzySearchInString(); commandParser = new CommandParser(); ui.setDebug(true); taskList = TaskStorage.readTasks(); diff --git a/src/main/java/seedu/duke/email/EmailContentParser.java b/src/main/java/seedu/duke/email/EmailContentParser.java index 4692beca32..f1e80ca7b3 100644 --- a/src/main/java/seedu/duke/email/EmailContentParser.java +++ b/src/main/java/seedu/duke/email/EmailContentParser.java @@ -19,6 +19,7 @@ public class EmailContentParser { private static int KEYWORD_BODY_WEIGHTAGE = 1; private static ArrayList keywordList; private static int INFINITY = 0x3f3f3f; + private static int FUZZY_LIMIT = 3; public static void allKeywordInEmail(Email email) { for (KeywordPair keywordPair : keywordList) { @@ -137,14 +138,33 @@ public static int editDistance(String A, String B) { dist[i][j] = min; } } - for (int i = 0; i <= A.length(); i++) { - for (int j = 0; j <= B.length(); j++) { - System.out.print(dist[i][j]); - System.out.print(" "); + return dist[A.length()][B.length()]; + } + + /** + * Searches a keyword in input string with some tolerance of inaccuracy. + * + * @param input input string where the keyword is searched + * @param target the target keyword to be searched + * @return a relevance score related to both occurrence and relevance + */ + private static int fuzzySearchInString(String input, String target) { + int score = 0; + String[] inputWords = input.split("\\W"); + String[] targetWords = target.split("\\W"); + for (String inputWord : inputWords) { + for (String targetWord : targetWords) { + if (inputWord.length() == 0 || targetWord.length() == 0) { + continue; + } + int distance = editDistance(inputWord, targetWord); + if (distance <= FUZZY_LIMIT) { + score += FUZZY_LIMIT - distance + 1; + } } - System.out.println("\n"); } - return dist[A.length()][B.length()]; + Duke.getUI().showError(score + " : " + input + " <> " + target); + return score; } /** diff --git a/src/test/java/seedu/duke/EmailContentParserTest.java b/src/test/java/seedu/duke/EmailContentParserTest.java index 86316b0bb5..8e223f7b63 100644 --- a/src/test/java/seedu/duke/EmailContentParserTest.java +++ b/src/test/java/seedu/duke/EmailContentParserTest.java @@ -17,5 +17,6 @@ public void editDistanceTest() { assertEquals(2, editDistance("a", "bc")); assertEquals(1, editDistance("a", "ba")); assertEquals(4, editDistance("food", "money")); + assertEquals(3, editDistance("kitten", "sitting")); } } From 840e6a3ef6db5525f5ece24b450daf8ebd54c541 Mon Sep 17 00:00:00 2001 From: JokerYan <770792058@qq.com> Date: Wed, 16 Oct 2019 21:25:30 +0800 Subject: [PATCH 4/8] Add tag storage --- src/main/java/seedu/duke/Duke.java | 3 - .../seedu/duke/email/EmailContentParser.java | 19 ++- .../seedu/duke/email/EmailFormatParser.java | 31 ++++ .../java/seedu/duke/email/EmailStorage.java | 15 +- .../java/seedu/duke/email/entity/Email.java | 140 +++++++++++++++++- 5 files changed, 187 insertions(+), 21 deletions(-) diff --git a/src/main/java/seedu/duke/Duke.java b/src/main/java/seedu/duke/Duke.java index 537105c330..625ed2878b 100644 --- a/src/main/java/seedu/duke/Duke.java +++ b/src/main/java/seedu/duke/Duke.java @@ -8,8 +8,6 @@ import seedu.duke.task.TaskList; import seedu.duke.task.TaskStorage; -import static seedu.duke.email.EmailContentParser.testFuzzySearchInString; - /** * The main class of the program, which provides the entry point. @@ -41,7 +39,6 @@ public static UI getUI() { */ public Duke() { ui = new UI(); - testFuzzySearchInString(); commandParser = new CommandParser(); ui.setDebug(true); taskList = TaskStorage.readTasks(); diff --git a/src/main/java/seedu/duke/email/EmailContentParser.java b/src/main/java/seedu/duke/email/EmailContentParser.java index f1e80ca7b3..97e891a924 100644 --- a/src/main/java/seedu/duke/email/EmailContentParser.java +++ b/src/main/java/seedu/duke/email/EmailContentParser.java @@ -23,20 +23,21 @@ public class EmailContentParser { public static void allKeywordInEmail(Email email) { for (KeywordPair keywordPair : keywordList) { - if (keywordInEmail(email, keywordPair) > 0) { + int relevance = keywordInEmail(email, keywordPair); + if (relevance > 0) { Duke.getUI().showDebug(keywordPair.getKeyword() + ": " + keywordInEmail(email, keywordPair) + " => " + email.getSubject()); - email.addTag(keywordPair.getKeyword()); + email.addTag(keywordPair, relevance); } } } /** - * Calculates the keyword occurrence score within an email based on its position and number of + * Calculates the keyword relevance score within an email based on its position and number of * occurrence. * * @param email the email where the keyword pair is to be looked for * @param keywordPair the target keyword pair - * @return the occurrence score + * @return the occurrence score */ public static int keywordInEmail(Email email, KeywordPair keywordPair) { int totalScore = 0; @@ -185,6 +186,16 @@ public KeywordPair(String keyword, ArrayList expressions) { this.expressions = expressions; } + /** + * Constructs a keyword pair with only keyword. Expression will be the same as the keyword by default. + * + * @param keyword the value of keyword looked for + */ + public KeywordPair(String keyword) { + this.keyword = keyword; + this.expressions = new ArrayList<>(List.of(keyword)); + } + public String getKeyword() { return this.keyword; } diff --git a/src/main/java/seedu/duke/email/EmailFormatParser.java b/src/main/java/seedu/duke/email/EmailFormatParser.java index 52610b65cd..07d0d848ce 100644 --- a/src/main/java/seedu/duke/email/EmailFormatParser.java +++ b/src/main/java/seedu/duke/email/EmailFormatParser.java @@ -9,6 +9,7 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.format.ResolverStyle; +import java.util.ArrayList; import java.util.Locale; public class EmailFormatParser { @@ -67,6 +68,24 @@ public static Email parseRawJson(String jsonString) throws EmailParsingException } } + public static Email parseIndexJson(String jsonString) throws EmailParsingException { + try { + JSONObject indexJson = new JSONObject(jsonString); + String subject = indexJson.getString("subject"); + Sender sender = new Sender(indexJson.getString("sender")); + LocalDateTime receivedDateTime = parseEmailDateTime(indexJson.getString("receivedDateTime")); + JSONArray tagArray = indexJson.getJSONArray("tags"); + ArrayList tags = new ArrayList<>(); + for (int i = 0; i < tagArray.length(); i++) { + JSONObject tagObject = tagArray.getJSONObject(i); + tags.add(new Email.Tag(tagObject)); + } + return new Email(subject, sender, receivedDateTime, tags); + } catch (JSONException e) { + throw new EmailParsingException("Email index json failed to parse"); + } + } + /** * Parses the email date time string to a LocalDateTime. * @@ -115,6 +134,18 @@ public Sender(JSONObject senderInfo) throws JSONException { this.address = senderInfo.getJSONObject("emailAddress").getString("address"); } + /** + * Constructor of the sender based on the string output of a sender; + * + * @param senderString the string of sender toString() output used to parse a sender + */ + public Sender(String senderString) { + String name = senderString.split("=>")[0].strip(); + String address = senderString.split("=>")[1].strip(); + this.name = name; + this.address = address; + } + public String toString() { return name + " => " + address; } diff --git a/src/main/java/seedu/duke/email/EmailStorage.java b/src/main/java/seedu/duke/email/EmailStorage.java index 5ce6e90a96..6eb4e0ec0f 100644 --- a/src/main/java/seedu/duke/email/EmailStorage.java +++ b/src/main/java/seedu/duke/email/EmailStorage.java @@ -1,5 +1,7 @@ package seedu.duke.email; +import org.json.JSONException; +import org.json.JSONObject; import seedu.duke.Duke; import seedu.duke.common.network.Http; import seedu.duke.email.entity.Email; @@ -127,7 +129,7 @@ public static void saveEmails(EmailList emailList) { FileOutputStream indexOut = new FileOutputStream(indexFile, false); String content = ""; for (Email email : emailList) { - content += email.getFilename() + "\n"; + content += email.getIndexJson().toString() + "\n"; } indexOut.write(content.getBytes()); indexOut.close(); @@ -143,6 +145,8 @@ public static void saveEmails(EmailList emailList) { } catch (IOException e) { e.printStackTrace(); Duke.getUI().showError("Write to output file IO exception!"); + } catch (JSONException e) { + Duke.getUI().showError("Email index formatting exception!"); } } @@ -229,10 +233,8 @@ public static EmailList readEmailFromFile() { Scanner scanner = new Scanner(indexIn); while (scanner.hasNextLine()) { String input = scanner.nextLine(); - if (input.length() <= 2) { - throw new TaskStorage.StorageException("Invalid Save File!"); - } - String filename = input.split("\\|")[0].strip(); + Email email = EmailFormatParser.parseIndexJson(input); + String filename = email.getFilename(); String fileDir = getFolderDir() + filename; File emailFile = new File(fileDir); @@ -251,9 +253,6 @@ public static EmailList readEmailFromFile() { return emailList; } catch (IOException e) { Duke.getUI().showError("Read save file IO exception"); - } catch (TaskStorage.StorageException e) { - Duke.getUI().showError(e.getMessage()); - emailList = new EmailList(); } catch (EmailFormatParser.EmailParsingException e) { Duke.getUI().showError("Email save file is in wrong format"); } diff --git a/src/main/java/seedu/duke/email/entity/Email.java b/src/main/java/seedu/duke/email/entity/Email.java index 32cc6dd2d9..8b13590554 100644 --- a/src/main/java/seedu/duke/email/entity/Email.java +++ b/src/main/java/seedu/duke/email/entity/Email.java @@ -1,6 +1,10 @@ package seedu.duke.email.entity; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; import seedu.duke.Duke; +import seedu.duke.email.EmailContentParser; import seedu.duke.email.EmailFormatParser; import java.io.File; @@ -18,7 +22,7 @@ public class Email { protected LocalDateTime receivedDateTime; protected String body; protected Boolean hasHtml; - protected ArrayList tags = new ArrayList<>(); + protected ArrayList tags = new ArrayList<>(); protected String rawJson; //@FXML @@ -36,7 +40,8 @@ public Email(String subject) { * @param sender the sender of the email * @param receivedDateTime the date and time when the email is received * @param body the body of the email - */ + * @param rawJson the raw json of the email when retrieved from the Outlook server + */ public Email(String subject, EmailFormatParser.Sender sender, LocalDateTime receivedDateTime, String body, String rawJson) { this.subject = subject; @@ -46,6 +51,22 @@ public Email(String subject, EmailFormatParser.Sender sender, LocalDateTime rece this.rawJson = rawJson; } + /** + * Alternative constructor for Email, used with the information retrieved from the index file. + * + * @param subject subject of the + * @param sender the sender of the email + * @param receivedDateTime the date and time when the email is received + * @param tags list of tags of the email + */ + public Email(String subject, EmailFormatParser.Sender sender, LocalDateTime receivedDateTime, + ArrayList tags) { + this.subject = subject; + this.sender = sender; + this.receivedDateTime = receivedDateTime; + this.tags = tags; + } + /** * Get title of this email. * @@ -59,11 +80,34 @@ public LocalDateTime getReceivedDateTime() { return this.receivedDateTime; } - public void addTag(String tag) { - if (this.tags.contains(tag)) { - return; + /** + * Add tag from string if not exist. + * + * @param keyword keyword of the tag + */ + public void addTag(String keyword) { + for (Tag tag : tags) { + if (tag.getKeywordPair().getKeyword().equals(keyword)) { + return; + } + } + this.tags.add(new Tag(keyword)); + } + + /** + * Add tag from keywordPair if not exist. Update relevance if already exists. + * + * @param keywordPair keywordPair of the tag + * @param relevance relevance of the tag + */ + public void addTag(EmailContentParser.KeywordPair keywordPair, int relevance) { + for (Tag tag : tags) { + if (tag.getKeywordPair().getKeyword().equals(keywordPair.getKeyword())) { + tag.setRelevance(relevance); + return; + } } - this.tags.add(tag); + this.tags.add(new Tag(keywordPair, relevance)); } public String getRawJson() { @@ -130,6 +174,24 @@ public String getFilename() { return filename; } + /** + * Formats the email object to a json object to be saved to index file. + * + * @return a json object containing all the parsed information of the email object + */ + public JSONObject getIndexJson() throws JSONException { + JSONObject indexJson = new JSONObject(); + indexJson.put("subject", this.subject); + indexJson.put("sender", this.sender.toString()); + indexJson.put("receivedDateTime", this.getDateTimeString()); + JSONArray tagArray = new JSONArray(); + for (Tag tag : this.tags) { + tagArray.put(tag.toJsonObject()); + } + indexJson.put("tags", tagArray); + return indexJson; + } + private String getDateTimeString() { return EmailFormatParser.formatEmailDateTime(receivedDateTime); } @@ -169,4 +231,70 @@ private String shaHash(String input) { } return input; } + + /** + * Tag of an email with both a keyword pair and a score indicating its relevance; + */ + public static class Tag { + private int INFINITY = 0x3f3f3f; + private EmailContentParser.KeywordPair keywordPair; + private int relevance = INFINITY; + + public Tag(EmailContentParser.KeywordPair keywordPair, int relevance) { + this.keywordPair = keywordPair; + this.relevance = relevance; + } + + public Tag(String keyword) { + this.keywordPair = new EmailContentParser.KeywordPair(keyword); + } + + /** + * Initialize from a json object in the same structure as the json output. + * + * @param json json object containing the information of this tag, in the same format as the json + * output from toJsonObject() + */ + public Tag(JSONObject json) throws JSONException { + String keyword = json.getString("keyword"); + JSONArray expressions = json.getJSONArray("expressions"); + ArrayList expressionList = new ArrayList<>(); + for (int i = 0; i < expressions.length(); i++) { + expressionList.add(expressions.getString(i)); + } + int relevance = json.getInt("relevance"); + + this.keywordPair = new EmailContentParser.KeywordPair(keyword, expressionList); + this.relevance = relevance; + } + + public EmailContentParser.KeywordPair getKeywordPair() { + return this.keywordPair; + } + + public int getRelevance() { + return relevance; + } + + public void setRelevance(int relevance) { + this.relevance = relevance; + } + + /** + * Converts tag to a json object for storage purpose. + * + * @return formatting result in JSONObject + */ + public JSONObject toJsonObject() throws JSONException { + JSONObject json = new JSONObject(); + json.put("keyword", this.keywordPair.getKeyword()); + JSONArray expressionArray = new JSONArray(); + for (String expresion : this.keywordPair.getExpressions()) { + expressionArray.put(expresion); + } + json.put("expressions", expressionArray); + json.put("relevance", this.relevance); + return json; + } + } } From f6be9e4975862e9bfda1ae561a8bec6e71a30d6e Mon Sep 17 00:00:00 2001 From: JokerYan <770792058@qq.com> Date: Wed, 16 Oct 2019 21:39:33 +0800 Subject: [PATCH 5/8] Chaneg parsing keyword for new emails only --- src/main/java/seedu/duke/email/EmailStorage.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/seedu/duke/email/EmailStorage.java b/src/main/java/seedu/duke/email/EmailStorage.java index 6eb4e0ec0f..ce72904ab7 100644 --- a/src/main/java/seedu/duke/email/EmailStorage.java +++ b/src/main/java/seedu/duke/email/EmailStorage.java @@ -104,12 +104,10 @@ public static void syncWithServer() { } } if (!exist) { + allKeywordInEmail(serverEmail); Duke.getEmailList().add(serverEmail); } } - for (Email email : Duke.getEmailList()) { - allKeywordInEmail(email); - } saveEmails(Duke.getEmailList()); } From c6cc4338c0e40803fd99f2609836561261829905 Mon Sep 17 00:00:00 2001 From: JokerYan <770792058@qq.com> Date: Wed, 16 Oct 2019 23:19:43 +0800 Subject: [PATCH 6/8] Fix tag disappear after sync twice --- src/main/java/seedu/duke/email/EmailList.java | 2 +- .../java/seedu/duke/email/EmailStorage.java | 10 ++-- .../java/seedu/duke/email/entity/Email.java | 54 ++++++++++++++++++- 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/src/main/java/seedu/duke/email/EmailList.java b/src/main/java/seedu/duke/email/EmailList.java index c675422ae4..2753520536 100644 --- a/src/main/java/seedu/duke/email/EmailList.java +++ b/src/main/java/seedu/duke/email/EmailList.java @@ -40,7 +40,7 @@ public String[] show(int index) throws CommandParser.UserInputException, IOExcep throw new CommandParser.UserInputException("Invalid index"); } Email email = this.get(index); - String emailContent = email.getBody(); + String emailContent = email.colorBodyOnTag(); String responseMsg = "Showing email in browser: " + email.getSubject(); String[] responseArray = {responseMsg, emailContent}; return responseArray; diff --git a/src/main/java/seedu/duke/email/EmailStorage.java b/src/main/java/seedu/duke/email/EmailStorage.java index ce72904ab7..97cdd26482 100644 --- a/src/main/java/seedu/duke/email/EmailStorage.java +++ b/src/main/java/seedu/duke/email/EmailStorage.java @@ -231,8 +231,8 @@ public static EmailList readEmailFromFile() { Scanner scanner = new Scanner(indexIn); while (scanner.hasNextLine()) { String input = scanner.nextLine(); - Email email = EmailFormatParser.parseIndexJson(input); - String filename = email.getFilename(); + Email indexEmail = EmailFormatParser.parseIndexJson(input); + String filename = indexEmail.getFilename(); String fileDir = getFolderDir() + filename; File emailFile = new File(fileDir); @@ -242,7 +242,11 @@ public static EmailList readEmailFromFile() { while (emailScanner.hasNextLine()) { emailContent += emailScanner.nextLine(); } - emailList.add(EmailFormatParser.parseRawJson(emailContent)); + Email fileEmail = EmailFormatParser.parseRawJson(emailContent); + for (Email.Tag tag : indexEmail.getTags()) { + fileEmail.addTag(tag); + } + emailList.add(fileEmail); } Duke.getUI().showMessage("Saved email file successfully loaded..."); indexIn.close(); diff --git a/src/main/java/seedu/duke/email/entity/Email.java b/src/main/java/seedu/duke/email/entity/Email.java index 8b13590554..fec22069aa 100644 --- a/src/main/java/seedu/duke/email/entity/Email.java +++ b/src/main/java/seedu/duke/email/entity/Email.java @@ -14,6 +14,10 @@ import java.security.NoSuchAlgorithmException; import java.time.LocalDateTime; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class Email { protected String filepath; @@ -80,6 +84,10 @@ public LocalDateTime getReceivedDateTime() { return this.receivedDateTime; } + public ArrayList getTags() { + return this.tags; + } + /** * Add tag from string if not exist. * @@ -95,7 +103,7 @@ public void addTag(String keyword) { } /** - * Add tag from keywordPair if not exist. Update relevance if already exists. + * Add tag from keywordPair if not exist. * * @param keywordPair keywordPair of the tag * @param relevance relevance of the tag @@ -103,13 +111,55 @@ public void addTag(String keyword) { public void addTag(EmailContentParser.KeywordPair keywordPair, int relevance) { for (Tag tag : tags) { if (tag.getKeywordPair().getKeyword().equals(keywordPair.getKeyword())) { - tag.setRelevance(relevance); return; } } this.tags.add(new Tag(keywordPair, relevance)); } + /** + * Add tag to tag list if keyword not exist. + * + * @param newTag the new tag to be added to the list + */ + public void addTag(Tag newTag) { + for (Tag tag : tags) { + if (tag.getKeywordPair().getKeyword().equals(newTag.getKeywordPair().getKeyword())) { + return; + } + } + this.tags.add(newTag); + } + + /** + * Colors the email body with the tag of highest relevance. Also, longer expression will have a higher + * priority to be colored currently. + * + * @return email body after the coloring + */ + public String colorBodyOnTag() { + if (this.tags.size() == 0) { + Duke.getUI().showDebug("Empty tags"); + return this.body; + } + Tag highestTag = null; + for (Tag tag : tags) { + if (highestTag == null || tag.relevance > highestTag.relevance) { + highestTag = tag; + } + } + String output = this.body; + ArrayList expressions = highestTag.getKeywordPair().getExpressions(); + Collections.sort(expressions, (ex1, ex2) -> ex1.length() >= ex2.length() ? 1 : -1); + for (String expression: expressions) { + Pattern colorPattern = Pattern.compile("expression", Pattern.CASE_INSENSITIVE); + Matcher colorMatcher = colorPattern.matcher(output); + colorMatcher.replaceAll(x -> "

" + x + "

"); + } + //Duke.getUI().showDebug(output); + return output; + } + public String getRawJson() { return this.rawJson; } From dbde3a1553f0bec8a62fc964c86bfef85c5978b4 Mon Sep 17 00:00:00 2001 From: JokerYan <770792058@qq.com> Date: Wed, 16 Oct 2019 23:47:02 +0800 Subject: [PATCH 7/8] Add simple color highlight for some tags --- .../seedu/duke/email/EmailContentParser.java | 2 +- .../java/seedu/duke/email/EmailStorage.java | 2 +- .../java/seedu/duke/email/entity/Email.java | 18 +++++++++--------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/seedu/duke/email/EmailContentParser.java b/src/main/java/seedu/duke/email/EmailContentParser.java index 97e891a924..c376f5e1ad 100644 --- a/src/main/java/seedu/duke/email/EmailContentParser.java +++ b/src/main/java/seedu/duke/email/EmailContentParser.java @@ -83,7 +83,7 @@ public static int keywordInString(String input, KeywordPair keywordPair) { public static void initKeywordList() { ArrayList keywordList = new ArrayList<>(); keywordList.add(new KeywordPair("CS2113T", new ArrayList(List.of( - "CS2113T", "CS2113", "TAN KIAN WEI, JASON", "Leow Wei Xiang")))); + "CS2113T", "CS2113", "TAN KIAN WEI, JASON", "Leow Wei Xiang", "Akshay Narayan")))); keywordList.add(new KeywordPair("CS2101", new ArrayList(List.of( "CS2101", "Anita Toh Ann Lee")))); keywordList.add(new KeywordPair("CG2271", new ArrayList(List.of( diff --git a/src/main/java/seedu/duke/email/EmailStorage.java b/src/main/java/seedu/duke/email/EmailStorage.java index 97cdd26482..3cec624812 100644 --- a/src/main/java/seedu/duke/email/EmailStorage.java +++ b/src/main/java/seedu/duke/email/EmailStorage.java @@ -94,7 +94,7 @@ public static ArrayList getHtmlList() { * current email list with local storage after that by calling syncEmailListWithHtml(). */ public static void syncWithServer() { - EmailList serverEmailList = Http.fetchEmail(50); + EmailList serverEmailList = Http.fetchEmail(60); for (Email serverEmail : serverEmailList) { boolean exist = false; for (Email localEmail : Duke.getEmailList()) { diff --git a/src/main/java/seedu/duke/email/entity/Email.java b/src/main/java/seedu/duke/email/entity/Email.java index fec22069aa..90aab7532f 100644 --- a/src/main/java/seedu/duke/email/entity/Email.java +++ b/src/main/java/seedu/duke/email/entity/Email.java @@ -138,25 +138,25 @@ public void addTag(Tag newTag) { * @return email body after the coloring */ public String colorBodyOnTag() { - if (this.tags.size() == 0) { - Duke.getUI().showDebug("Empty tags"); - return this.body; - } Tag highestTag = null; for (Tag tag : tags) { - if (highestTag == null || tag.relevance > highestTag.relevance) { + if (!tag.getKeywordPair().getKeyword().equals("Spam") + && (highestTag == null || tag.relevance > highestTag.relevance)) { highestTag = tag; } } + if (highestTag == null) { + return body; + } String output = this.body; ArrayList expressions = highestTag.getKeywordPair().getExpressions(); - Collections.sort(expressions, (ex1, ex2) -> ex1.length() >= ex2.length() ? 1 : -1); + Collections.sort(expressions, (ex1, ex2) -> ex1.length() >= ex2.length() ? -1 : 1); for (String expression: expressions) { - Pattern colorPattern = Pattern.compile("expression", Pattern.CASE_INSENSITIVE); + //Duke.getUI().showDebug(expression); + Pattern colorPattern = Pattern.compile("(" + expression + ")", Pattern.CASE_INSENSITIVE); Matcher colorMatcher = colorPattern.matcher(output); - colorMatcher.replaceAll(x -> "

" + x + "

"); + output = colorMatcher.replaceAll("" + expression + ""); } - //Duke.getUI().showDebug(output); return output; } From af042273c23b1f972e64777e845d493d8fabdf2b Mon Sep 17 00:00:00 2001 From: JokerYan <770792058@qq.com> Date: Wed, 16 Oct 2019 23:50:07 +0800 Subject: [PATCH 8/8] Fix code format --- .../java/seedu/duke/email/EmailContentParser.java | 10 +++++----- src/main/java/seedu/duke/email/entity/Email.java | 12 ++++++------ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/seedu/duke/email/EmailContentParser.java b/src/main/java/seedu/duke/email/EmailContentParser.java index c376f5e1ad..7ac89b3aa5 100644 --- a/src/main/java/seedu/duke/email/EmailContentParser.java +++ b/src/main/java/seedu/duke/email/EmailContentParser.java @@ -32,12 +32,11 @@ public static void allKeywordInEmail(Email email) { } /** - * Calculates the keyword relevance score within an email based on its position and number of - * occurrence. + * Calculates the keyword relevance score within an email based on its position and number of occurrence. * * @param email the email where the keyword pair is to be looked for * @param keywordPair the target keyword pair - * @return the occurrence score + * @return the occurrence score */ public static int keywordInEmail(Email email, KeywordPair keywordPair) { int totalScore = 0; @@ -145,7 +144,7 @@ public static int editDistance(String A, String B) { /** * Searches a keyword in input string with some tolerance of inaccuracy. * - * @param input input string where the keyword is searched + * @param input input string where the keyword is searched * @param target the target keyword to be searched * @return a relevance score related to both occurrence and relevance */ @@ -187,7 +186,8 @@ public KeywordPair(String keyword, ArrayList expressions) { } /** - * Constructs a keyword pair with only keyword. Expression will be the same as the keyword by default. + * Constructs a keyword pair with only keyword. Expression will be the same as the keyword by + * default. * * @param keyword the value of keyword looked for */ diff --git a/src/main/java/seedu/duke/email/entity/Email.java b/src/main/java/seedu/duke/email/entity/Email.java index 90aab7532f..6d0d056b11 100644 --- a/src/main/java/seedu/duke/email/entity/Email.java +++ b/src/main/java/seedu/duke/email/entity/Email.java @@ -45,7 +45,7 @@ public Email(String subject) { * @param receivedDateTime the date and time when the email is received * @param body the body of the email * @param rawJson the raw json of the email when retrieved from the Outlook server - */ + */ public Email(String subject, EmailFormatParser.Sender sender, LocalDateTime receivedDateTime, String body, String rawJson) { this.subject = subject; @@ -58,10 +58,10 @@ public Email(String subject, EmailFormatParser.Sender sender, LocalDateTime rece /** * Alternative constructor for Email, used with the information retrieved from the index file. * - * @param subject subject of the - * @param sender the sender of the email + * @param subject subject of the + * @param sender the sender of the email * @param receivedDateTime the date and time when the email is received - * @param tags list of tags of the email + * @param tags list of tags of the email */ public Email(String subject, EmailFormatParser.Sender sender, LocalDateTime receivedDateTime, ArrayList tags) { @@ -106,7 +106,7 @@ public void addTag(String keyword) { * Add tag from keywordPair if not exist. * * @param keywordPair keywordPair of the tag - * @param relevance relevance of the tag + * @param relevance relevance of the tag */ public void addTag(EmailContentParser.KeywordPair keywordPair, int relevance) { for (Tag tag : tags) { @@ -151,7 +151,7 @@ public String colorBodyOnTag() { String output = this.body; ArrayList expressions = highestTag.getKeywordPair().getExpressions(); Collections.sort(expressions, (ex1, ex2) -> ex1.length() >= ex2.length() ? -1 : 1); - for (String expression: expressions) { + for (String expression : expressions) { //Duke.getUI().showDebug(expression); Pattern colorPattern = Pattern.compile("(" + expression + ")", Pattern.CASE_INSENSITIVE); Matcher colorMatcher = colorPattern.matcher(output);