Skip to content

Commit

Permalink
Merge pull request AY2324S1-CS2103T-W10-1#101 from ravern/add-import-…
Browse files Browse the repository at this point in the history
…command

Add import command
  • Loading branch information
ravern authored Oct 29, 2023
2 parents 5ae0719 + 8620e21 commit 3780de0
Show file tree
Hide file tree
Showing 5 changed files with 289 additions and 11 deletions.
4 changes: 0 additions & 4 deletions src/main/java/seedu/address/logic/commands/AttachCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,6 @@ public AttachCommand(Index index, List<Attachment> 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<Person> lastShownList = model.getFilteredPersonList();

Expand Down
251 changes: 251 additions & 0 deletions src/main/java/seedu/address/logic/commands/ImportCommand.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
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.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<String> 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);

List<Person> applicants = new ArrayList<>();
List<Integer> failureLineNos = new ArrayList<>();

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<String> fieldSet = Set.of(fields);
if (!fieldSet.containsAll(REQUIRED_FIELDS)) {
Set<String> requiredFieldsCopy = new HashSet<>(REQUIRED_FIELDS);
requiredFieldsCopy.removeAll(fieldSet);
throw new CommandException(
String.format(MESSAGE_MISSING_FIELDS, String.join(", ", requiredFieldsCopy)));
}

Map<String, Integer> fieldIndices = new HashMap<>(fields.length);
for (int i = 0; i < fields.length; i++) {
fieldIndices.put(fields[i], i);
}

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)) {
failureLineNos.add(lineNo);
continue;
}
StudentNumber studentNo = new StudentNumber(studentNoString);

String nameString = data[fieldIndices.get(FIELD_NAME)];
if (!Name.isValidName(nameString)) {
failureLineNos.add(lineNo);
continue;
}
Name name = new Name(nameString);

String phoneString = data[fieldIndices.get(FIELD_PHONE)];
if (!Phone.isValidPhone(phoneString)) {
failureLineNos.add(lineNo);
continue;
}
Phone phone = new Phone(phoneString);

String emailString = data[fieldIndices.get(FIELD_EMAIL)];
if (!Email.isValidEmail(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)) {
failureLineNos.add(lineNo);
continue;
}
Gpa gpa = new Gpa(gpaDouble);

String previousGradeString = data[fieldIndices.get(FIELD_PREVIOUS_GRADE)];
if (!PreviousGrade.isValidGrade(previousGradeString)) {
failureLineNos.add(lineNo);
continue;
}
PreviousGrade previousGrade = new PreviousGrade(previousGradeString);


String tagsString = data[fieldIndices.get(FIELD_TAGS)];
String[] tagStrings = tagsString.split(DATA_ARRAY_DELIM);
Set<Tag> 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) {
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);
}
} catch (FileNotFoundException e) {
throw new CommandException(MESSAGE_FAILED_TO_OPEN, e);
} finally {
if (scanner != null) {
scanner.close();
}
}

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<String> 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);
}

@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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();

Expand Down
17 changes: 10 additions & 7 deletions src/main/java/seedu/address/logic/parser/AttachCommandParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<AttachCommand> {

/**
* 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 {
Expand All @@ -44,8 +45,10 @@ public AttachCommand parse(String args) throws ParseException {
}

/**
* Parses {@code Collection<String> pathStrings} into a {@code List<Path>} 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<String> pathStrings} into a {@code List<Path>} 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<Path>} containing zero tags.
*/
private List<Attachment> parseAttachments(Collection<String> pathStrings) throws ParseException {
Expand Down
24 changes: 24 additions & 0 deletions src/main/java/seedu/address/logic/parser/ImportCommandParser.java
Original file line number Diff line number Diff line change
@@ -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<ImportCommand> {

/**
* 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);
}
}

0 comments on commit 3780de0

Please sign in to comment.