From 82e5d79d5f13e44d85d76e3f3fe2a27282fbfe7a Mon Sep 17 00:00:00 2001 From: Ravern Koh Date: Sun, 29 Oct 2023 15:43:00 +0800 Subject: [PATCH 1/5] Remove unnecessary comment in attach cmd --- src/main/java/seedu/address/logic/commands/AttachCommand.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/seedu/address/logic/commands/AttachCommand.java b/src/main/java/seedu/address/logic/commands/AttachCommand.java index 099421862f2..232cd41593b 100644 --- a/src/main/java/seedu/address/logic/commands/AttachCommand.java +++ b/src/main/java/seedu/address/logic/commands/AttachCommand.java @@ -58,10 +58,6 @@ public AttachCommand(Index index, List attachments) { @Override public CommandResult execute(Model model) throws CommandException { - // 1. Read the contents of the existing directory - // 2. Copy in all the new attachments, failing first if one of them fails - // 3. Add the attachments to the model - requireNonNull(model); List lastShownList = model.getFilteredPersonList(); From 96ba43fa3f87dd66c54a29919f0568289d09a0a3 Mon Sep 17 00:00:00 2001 From: Ravern Koh Date: Sun, 29 Oct 2023 16:55:31 +0800 Subject: [PATCH 2/5] Add import command --- .../address/logic/commands/ImportCommand.java | 259 ++++++++++++++++++ .../logic/parser/AddressBookParser.java | 4 + .../logic/parser/AttachCommandParser.java | 17 +- .../logic/parser/ImportCommandParser.java | 24 ++ 4 files changed, 297 insertions(+), 7 deletions(-) create mode 100644 src/main/java/seedu/address/logic/commands/ImportCommand.java create mode 100644 src/main/java/seedu/address/logic/parser/ImportCommandParser.java diff --git a/src/main/java/seedu/address/logic/commands/ImportCommand.java b/src/main/java/seedu/address/logic/commands/ImportCommand.java new file mode 100644 index 00000000000..a34aca77064 --- /dev/null +++ b/src/main/java/seedu/address/logic/commands/ImportCommand.java @@ -0,0 +1,259 @@ +package seedu.address.logic.commands; + +import static java.util.Objects.requireNonNull; +import static seedu.address.logic.parser.CliSyntax.FIELD_EMAIL; +import static seedu.address.logic.parser.CliSyntax.FIELD_GPA; +import static seedu.address.logic.parser.CliSyntax.FIELD_NAME; +import static seedu.address.logic.parser.CliSyntax.FIELD_PHONE; +import static seedu.address.logic.parser.CliSyntax.FIELD_PREVIOUS_GRADE; +import static seedu.address.logic.parser.CliSyntax.FIELD_STUDENT_NUMBER; +import static seedu.address.logic.parser.CliSyntax.FIELD_TAGS; +import static seedu.address.logic.parser.CliSyntax.PREFIX_FILE; + +import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Scanner; +import java.util.Set; +import java.util.stream.Collectors; + +import seedu.address.commons.util.ToStringBuilder; +import seedu.address.logic.commands.exceptions.CommandException; +import seedu.address.model.Model; +import seedu.address.model.attachment.Attachment; +import seedu.address.model.person.Email; +import seedu.address.model.person.Gpa; +import seedu.address.model.person.IsBookmarked; +import seedu.address.model.person.IsHidden; +import seedu.address.model.person.Name; +import seedu.address.model.person.Person; +import seedu.address.model.person.Phone; +import seedu.address.model.person.PreviousGrade; +import seedu.address.model.person.StudentNumber; +import seedu.address.model.tag.Tag; + +/** + * Imports new applicants from the given file. + */ +public class ImportCommand extends Command { + + public static final String COMMAND_WORD = "import"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ": Imports applicants from the file " + + "specified, reading it in CSV format. The CSV header row is required, any\n " + + "additional fields other than those supported will be ignored, and any missing " + + "fields will be reported as an error.\n" + + "Supported fields: studentNo, name, phone, email, gpa, previousGrade, tags.\n" + + "Parameters: " + PREFIX_FILE + "FILE\n" + + "Example: " + COMMAND_WORD + " " + PREFIX_FILE + "samples/applicants.csv"; + public static final String MESSAGE_IMPORT_SUCCESS = "Imported %1$s applicants successfully!"; + public static final String MESSAGE_IMPORT_NONE = "No applicants were imported."; + public static final String MESSAGE_IMPORT_FAILURE = "Failed to import %1$s applicants, on line %2$s."; + public static final String MESSAGE_FAILED_TO_OPEN = "Failed to open and load applicant file."; + public static final String MESSAGE_INVALID_FILE_FORMAT = "File format is invalid."; + public static final String MESSAGE_MISSING_FIELDS = "Missing fields from file: %1$s."; + + private static final String CELL_DELIM = ","; + private static final String DATA_ARRAY_DELIM = ";"; + private static final Set REQUIRED_FIELDS = Set.of( + FIELD_STUDENT_NUMBER, FIELD_NAME, + FIELD_PHONE, FIELD_EMAIL, FIELD_GPA, + FIELD_PREVIOUS_GRADE, FIELD_TAGS); + + private final Attachment attachment; + + /** + * Creates a new import command, which will import applicants listed in the + * given + * file (read in using CSV format). + * + * @param attachment The path of the file to be imported + */ + public ImportCommand(Attachment attachment) { + this.attachment = attachment; + } + + @Override + public CommandResult execute(Model model) throws CommandException { + requireNonNull(model); + + String message; + List newApplicants; + + Scanner scanner = null; + try { + scanner = new Scanner(attachment.file); + + if (!scanner.hasNextLine()) { + throw new CommandException(MESSAGE_INVALID_FILE_FORMAT); + } + String header = scanner.nextLine(); + String[] fields = header.split(CELL_DELIM); + + Set fieldSet = Set.of(fields); + if (!fieldSet.containsAll(REQUIRED_FIELDS)) { + Set requiredFieldsCopy = new HashSet<>(REQUIRED_FIELDS); + requiredFieldsCopy.removeAll(fieldSet); + throw new CommandException( + String.format(MESSAGE_MISSING_FIELDS, String.join(", ", requiredFieldsCopy))); + } + + Map fieldIndices = new HashMap<>(fields.length); + for (int i = 0; i < fields.length; i++) { + fieldIndices.put(fields[i], i); + } + + List applicants = new ArrayList<>(); + List failureLineNos = new ArrayList<>(); + int lineNo = 1; + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + String[] data = line.split(CELL_DELIM, -1); + + lineNo++; + + String studentNoString = data[fieldIndices.get(FIELD_STUDENT_NUMBER)]; + if (!StudentNumber.isValidStudentNumber(studentNoString)) { + System.out.println(studentNoString); + failureLineNos.add(lineNo); + continue; + } + StudentNumber studentNo = new StudentNumber(studentNoString); + + String nameString = data[fieldIndices.get(FIELD_NAME)]; + if (!Name.isValidName(nameString)) { + System.out.println(nameString); + failureLineNos.add(lineNo); + continue; + } + Name name = new Name(nameString); + + String phoneString = data[fieldIndices.get(FIELD_PHONE)]; + if (!Phone.isValidPhone(phoneString)) { + System.out.println(phoneString); + failureLineNos.add(lineNo); + continue; + } + Phone phone = new Phone(phoneString); + + String emailString = data[fieldIndices.get(FIELD_EMAIL)]; + if (!Email.isValidEmail(emailString)) { + System.out.println(emailString); + failureLineNos.add(lineNo); + continue; + } + Email email = new Email(emailString); + + String gpaString = data[fieldIndices.get(FIELD_GPA)]; + double gpaDouble; + try { + gpaDouble = Double.parseDouble(gpaString); + } catch (NumberFormatException e) { + failureLineNos.add(lineNo); + continue; + } + if (!Gpa.isValidGpa(gpaDouble)) { + System.out.println(gpaDouble); + failureLineNos.add(lineNo); + continue; + } + Gpa gpa = new Gpa(gpaDouble); + + String previousGradeString = data[fieldIndices.get(FIELD_PREVIOUS_GRADE)]; + if (!PreviousGrade.isValidGrade(previousGradeString)) { + System.out.println(previousGradeString); + failureLineNos.add(lineNo); + continue; + } + PreviousGrade previousGrade = new PreviousGrade(previousGradeString); + + System.out.println(Arrays.toString(data)); + + String tagsString = data[fieldIndices.get(FIELD_TAGS)]; + String[] tagStrings = tagsString.split(DATA_ARRAY_DELIM); + Set tags = Set.of(); + if (tagStrings.length > 1 || tagStrings.length == 0 || !tagStrings[0].trim().equals("")) { + boolean isValid = List.of(tagStrings).stream().map(Tag::isValidTagName).allMatch(v -> v); + if (!isValid) { + System.out.println( + List.of(tagStrings).stream().map(Tag::isValidTagName).collect(Collectors.toList())); + failureLineNos.add(lineNo); + continue; + } + tags = List.of(tagStrings).stream().map(Tag::new).collect(Collectors.toSet()); + } + + Person applicant = new Person( + studentNo, name, phone, email, gpa, previousGrade, + Optional.empty(), Optional.empty(), tags, List.of(), + new IsHidden(false), new IsBookmarked(false)); + applicants.add(applicant); + } + + String successMessage = null; + if (!applicants.isEmpty()) { + successMessage = String.format(MESSAGE_IMPORT_SUCCESS, applicants.size()); + } + + String failureMessage = null; + if (!failureLineNos.isEmpty()) { + List lineNos = failureLineNos.stream().map(Object::toString).collect(Collectors.toList()); + failureMessage = String.format( + MESSAGE_IMPORT_FAILURE, + failureLineNos.size(), + String.join(", ", lineNos)); + } + + if (successMessage != null && failureMessage != null) { + message = successMessage + " " + failureMessage; + } else if (successMessage != null) { + message = successMessage; + } else if (failureMessage != null) { + message = failureMessage; + } else { + message = MESSAGE_IMPORT_NONE; + } + + newApplicants = applicants; + } catch (FileNotFoundException e) { + throw new CommandException(MESSAGE_FAILED_TO_OPEN, e); + } finally { + if (scanner != null) { + scanner.close(); + } + } + + for (Person applicant : newApplicants) { + model.addPerson(applicant); + } + + return new CommandResult(message); + } + + @Override + public boolean equals(Object other) { + if (other == this) { + return true; + } + + // instanceof handles nulls + if (!(other instanceof ImportCommand)) { + return false; + } + + ImportCommand otherImportCommand = (ImportCommand) other; + return attachment.equals(otherImportCommand.attachment); + } + + @Override + public String toString() { + return new ToStringBuilder(this) + .add("attachment", attachment) + .toString(); + } +} diff --git a/src/main/java/seedu/address/logic/parser/AddressBookParser.java b/src/main/java/seedu/address/logic/parser/AddressBookParser.java index b08daa95d3d..c57dd393c48 100644 --- a/src/main/java/seedu/address/logic/parser/AddressBookParser.java +++ b/src/main/java/seedu/address/logic/parser/AddressBookParser.java @@ -21,6 +21,7 @@ import seedu.address.logic.commands.FindCommand; import seedu.address.logic.commands.HelpCommand; import seedu.address.logic.commands.HideCommand; +import seedu.address.logic.commands.ImportCommand; import seedu.address.logic.commands.ListBookmarkedCommand; import seedu.address.logic.commands.ListCommand; import seedu.address.logic.commands.ListHiddenCommand; @@ -84,6 +85,9 @@ public Command parseCommand(String userInput) throws ParseException { case AttachCommand.COMMAND_WORD: return new AttachCommandParser().parse(arguments); + case ImportCommand.COMMAND_WORD: + return new ImportCommandParser().parse(arguments); + case ListCommand.COMMAND_WORD: return new ListCommand(); diff --git a/src/main/java/seedu/address/logic/parser/AttachCommandParser.java b/src/main/java/seedu/address/logic/parser/AttachCommandParser.java index 077253bc3fe..3428416b982 100644 --- a/src/main/java/seedu/address/logic/parser/AttachCommandParser.java +++ b/src/main/java/seedu/address/logic/parser/AttachCommandParser.java @@ -14,19 +14,20 @@ import seedu.address.model.attachment.Attachment; /** - * Parses input arguments and creates a new AttachCommand object + * Parses input arguments and creates a new {@code AttachCommand} object */ public class AttachCommandParser implements Parser { /** - * Parses the given {@code String} of arguments in the context of the DeleteCommand - * and returns a DeleteCommand object for execution. + * Parses the given {@code String} of arguments in the context of the + * {@code AttachCommand} and returns a {@code AttachCommand} object for + * execution. + * * @throws ParseException if the user input does not conform the expected format */ public AttachCommand parse(String args) throws ParseException { requireNonNull(args); - ArgumentMultimap argMultimap = - ArgumentTokenizer.tokenize(args, PREFIX_FILE); + ArgumentMultimap argMultimap = ArgumentTokenizer.tokenize(args, PREFIX_FILE); Index index; try { @@ -44,8 +45,10 @@ public AttachCommand parse(String args) throws ParseException { } /** - * Parses {@code Collection pathStrings} into a {@code List} if {@code pathStrings} is non-empty. - * If {@code pathStrings} contain only one element which is an empty string, it will be parsed into a + * Parses {@code Collection pathStrings} into a {@code List} if + * {@code pathStrings} is non-empty. + * If {@code pathStrings} contain only one element which is an empty string, it + * will be parsed into a * {@code List} containing zero tags. */ private List parseAttachments(Collection pathStrings) throws ParseException { diff --git a/src/main/java/seedu/address/logic/parser/ImportCommandParser.java b/src/main/java/seedu/address/logic/parser/ImportCommandParser.java new file mode 100644 index 00000000000..970bd6e7d4e --- /dev/null +++ b/src/main/java/seedu/address/logic/parser/ImportCommandParser.java @@ -0,0 +1,24 @@ +package seedu.address.logic.parser; + +import static java.util.Objects.requireNonNull; + +import seedu.address.logic.commands.ImportCommand; +import seedu.address.logic.parser.exceptions.ParseException; +import seedu.address.model.attachment.Attachment; + +/** + * Parses input arguments and creates a new {@code ImportCommand} object + */ +public class ImportCommandParser implements Parser { + + /** + * Parses the given {@code String} of arguments in the context of the {@code ImportCommand} + * and returns a {@code ImportCommand} object for execution. + * @throws ParseException if the user input does not conform the expected format + */ + public ImportCommand parse(String args) throws ParseException { + requireNonNull(args); + Attachment attachment = ParserUtil.parseAttachment(args); + return new ImportCommand(attachment); + } +} From 9c0817c00a8d8acce414bee9a4cd81ddc36e1684 Mon Sep 17 00:00:00 2001 From: Ravern Koh Date: Sun, 29 Oct 2023 16:59:09 +0800 Subject: [PATCH 3/5] Fix duplicate persons error --- .../address/logic/commands/ImportCommand.java | 64 ++++++++++--------- 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/src/main/java/seedu/address/logic/commands/ImportCommand.java b/src/main/java/seedu/address/logic/commands/ImportCommand.java index a34aca77064..076fd357428 100644 --- a/src/main/java/seedu/address/logic/commands/ImportCommand.java +++ b/src/main/java/seedu/address/logic/commands/ImportCommand.java @@ -82,8 +82,8 @@ public ImportCommand(Attachment attachment) { public CommandResult execute(Model model) throws CommandException { requireNonNull(model); - String message; - List newApplicants; + List applicants = new ArrayList<>(); + List failureLineNos = new ArrayList<>(); Scanner scanner = null; try { @@ -108,8 +108,6 @@ public CommandResult execute(Model model) throws CommandException { fieldIndices.put(fields[i], i); } - List applicants = new ArrayList<>(); - List failureLineNos = new ArrayList<>(); int lineNo = 1; while (scanner.hasNextLine()) { String line = scanner.nextLine(); @@ -194,32 +192,6 @@ public CommandResult execute(Model model) throws CommandException { new IsHidden(false), new IsBookmarked(false)); applicants.add(applicant); } - - String successMessage = null; - if (!applicants.isEmpty()) { - successMessage = String.format(MESSAGE_IMPORT_SUCCESS, applicants.size()); - } - - String failureMessage = null; - if (!failureLineNos.isEmpty()) { - List lineNos = failureLineNos.stream().map(Object::toString).collect(Collectors.toList()); - failureMessage = String.format( - MESSAGE_IMPORT_FAILURE, - failureLineNos.size(), - String.join(", ", lineNos)); - } - - if (successMessage != null && failureMessage != null) { - message = successMessage + " " + failureMessage; - } else if (successMessage != null) { - message = successMessage; - } else if (failureMessage != null) { - message = failureMessage; - } else { - message = MESSAGE_IMPORT_NONE; - } - - newApplicants = applicants; } catch (FileNotFoundException e) { throw new CommandException(MESSAGE_FAILED_TO_OPEN, e); } finally { @@ -228,10 +200,40 @@ public CommandResult execute(Model model) throws CommandException { } } - for (Person applicant : newApplicants) { + int newApplicantCount = applicants.size(); + for (Person applicant : applicants) { + if (model.hasPerson(applicant)) { + newApplicantCount--; + continue; + } model.addPerson(applicant); } + String successMessage = null; + if (!applicants.isEmpty()) { + successMessage = String.format(MESSAGE_IMPORT_SUCCESS, newApplicantCount); + } + + String failureMessage = null; + if (!failureLineNos.isEmpty()) { + List lineNos = failureLineNos.stream().map(Object::toString).collect(Collectors.toList()); + failureMessage = String.format( + MESSAGE_IMPORT_FAILURE, + failureLineNos.size(), + String.join(", ", lineNos)); + } + + String message; + if (successMessage != null && failureMessage != null) { + message = successMessage + " " + failureMessage; + } else if (successMessage != null) { + message = successMessage; + } else if (failureMessage != null) { + message = failureMessage; + } else { + message = MESSAGE_IMPORT_NONE; + } + return new CommandResult(message); } From f717375982c0b5b11b461948d081fb1427b25a53 Mon Sep 17 00:00:00 2001 From: Ravern Koh Date: Sun, 29 Oct 2023 22:26:05 +0800 Subject: [PATCH 4/5] Remove prints --- .../java/seedu/address/logic/commands/ImportCommand.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/main/java/seedu/address/logic/commands/ImportCommand.java b/src/main/java/seedu/address/logic/commands/ImportCommand.java index 076fd357428..4bbfcfaa8c3 100644 --- a/src/main/java/seedu/address/logic/commands/ImportCommand.java +++ b/src/main/java/seedu/address/logic/commands/ImportCommand.java @@ -117,7 +117,6 @@ public CommandResult execute(Model model) throws CommandException { String studentNoString = data[fieldIndices.get(FIELD_STUDENT_NUMBER)]; if (!StudentNumber.isValidStudentNumber(studentNoString)) { - System.out.println(studentNoString); failureLineNos.add(lineNo); continue; } @@ -125,7 +124,6 @@ public CommandResult execute(Model model) throws CommandException { String nameString = data[fieldIndices.get(FIELD_NAME)]; if (!Name.isValidName(nameString)) { - System.out.println(nameString); failureLineNos.add(lineNo); continue; } @@ -133,7 +131,6 @@ public CommandResult execute(Model model) throws CommandException { String phoneString = data[fieldIndices.get(FIELD_PHONE)]; if (!Phone.isValidPhone(phoneString)) { - System.out.println(phoneString); failureLineNos.add(lineNo); continue; } @@ -141,7 +138,6 @@ public CommandResult execute(Model model) throws CommandException { String emailString = data[fieldIndices.get(FIELD_EMAIL)]; if (!Email.isValidEmail(emailString)) { - System.out.println(emailString); failureLineNos.add(lineNo); continue; } @@ -156,7 +152,6 @@ public CommandResult execute(Model model) throws CommandException { continue; } if (!Gpa.isValidGpa(gpaDouble)) { - System.out.println(gpaDouble); failureLineNos.add(lineNo); continue; } @@ -164,13 +159,11 @@ public CommandResult execute(Model model) throws CommandException { String previousGradeString = data[fieldIndices.get(FIELD_PREVIOUS_GRADE)]; if (!PreviousGrade.isValidGrade(previousGradeString)) { - System.out.println(previousGradeString); failureLineNos.add(lineNo); continue; } PreviousGrade previousGrade = new PreviousGrade(previousGradeString); - System.out.println(Arrays.toString(data)); String tagsString = data[fieldIndices.get(FIELD_TAGS)]; String[] tagStrings = tagsString.split(DATA_ARRAY_DELIM); @@ -178,8 +171,6 @@ public CommandResult execute(Model model) throws CommandException { if (tagStrings.length > 1 || tagStrings.length == 0 || !tagStrings[0].trim().equals("")) { boolean isValid = List.of(tagStrings).stream().map(Tag::isValidTagName).allMatch(v -> v); if (!isValid) { - System.out.println( - List.of(tagStrings).stream().map(Tag::isValidTagName).collect(Collectors.toList())); failureLineNos.add(lineNo); continue; } From 8620e217b959942ea7595358493381e614ebdb0e Mon Sep 17 00:00:00 2001 From: Ravern Koh Date: Sun, 29 Oct 2023 22:38:29 +0800 Subject: [PATCH 5/5] Remove unused import --- src/main/java/seedu/address/logic/commands/ImportCommand.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/seedu/address/logic/commands/ImportCommand.java b/src/main/java/seedu/address/logic/commands/ImportCommand.java index 4bbfcfaa8c3..f3c6c6b3e9e 100644 --- a/src/main/java/seedu/address/logic/commands/ImportCommand.java +++ b/src/main/java/seedu/address/logic/commands/ImportCommand.java @@ -12,7 +12,6 @@ import java.io.FileNotFoundException; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List;