From 4c1ef2781743b5f97d4eef5d345595234069d7f1 Mon Sep 17 00:00:00 2001 From: Sean Yap Date: Tue, 26 Feb 2019 04:43:36 +0800 Subject: [PATCH 1/2] Add separate classes to aid in development. --- PlanMySem.txt | 2 - docs/images/{SeanYap.jpg => seanyap.jpg} | Bin src/planmysem/Main.java | 4 +- src/planmysem/commands/AddCommand.java | 2 +- src/planmysem/commands/AddCommandP.java | 67 ++++++ src/planmysem/commands/CommandP.java | 61 ++++++ src/planmysem/commands/CommandResultP.java | 41 ++++ src/planmysem/commands/HelpCommandP.java | 28 +++ src/planmysem/commands/IncorrectCommandP.java | 20 ++ src/planmysem/common/Utils.java | 8 + src/planmysem/data/Planner.java | 13 +- .../data/person/UniquePersonList.java | 2 +- src/planmysem/data/semester/Semester.java | 16 +- src/planmysem/logic/Logic.java | 2 +- src/planmysem/logic/LogicP.java | 110 ++++++++++ src/planmysem/parser/Parser.java | 6 +- src/planmysem/parser/ParserP.java | 191 ++++++++++++++++++ src/planmysem/storage/StorageFileP.java | 154 ++++++++++++++ src/planmysem/storage/jaxb/AdaptedDay.java | 8 +- src/planmysem/storage/jaxb/AdaptedPerson.java | 1 - .../storage/jaxb/AdaptedPlanner.java | 10 +- .../storage/jaxb/AdaptedSemester.java | 20 +- src/planmysem/storage/jaxb/AdaptedSlot.java | 14 +- src/planmysem/storage/jaxb/AdaptedTag.java | 1 - src/planmysem/ui/Formatter.java | 16 ++ src/planmysem/ui/Gui.java | 28 ++- src/planmysem/ui/MainWindowP.java | 121 +++++++++++ src/planmysem/ui/mainwindowP.fxml | 20 ++ test/java/planmysem/logic/LogicTest.java | 20 +- test/java/planmysem/parser/ParserTest.java | 18 +- 30 files changed, 934 insertions(+), 70 deletions(-) delete mode 100644 PlanMySem.txt rename docs/images/{SeanYap.jpg => seanyap.jpg} (100%) create mode 100644 src/planmysem/commands/AddCommandP.java create mode 100644 src/planmysem/commands/CommandP.java create mode 100644 src/planmysem/commands/CommandResultP.java create mode 100644 src/planmysem/commands/HelpCommandP.java create mode 100644 src/planmysem/commands/IncorrectCommandP.java create mode 100644 src/planmysem/logic/LogicP.java create mode 100644 src/planmysem/parser/ParserP.java create mode 100644 src/planmysem/storage/StorageFileP.java create mode 100644 src/planmysem/ui/MainWindowP.java create mode 100644 src/planmysem/ui/mainwindowP.fxml diff --git a/PlanMySem.txt b/PlanMySem.txt deleted file mode 100644 index ccedbe77c..000000000 --- a/PlanMySem.txt +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/docs/images/SeanYap.jpg b/docs/images/seanyap.jpg similarity index 100% rename from docs/images/SeanYap.jpg rename to docs/images/seanyap.jpg diff --git a/src/planmysem/Main.java b/src/planmysem/Main.java index 986561074..debbbaae4 100644 --- a/src/planmysem/Main.java +++ b/src/planmysem/Main.java @@ -4,6 +4,7 @@ import javafx.application.Platform; import javafx.stage.Stage; import planmysem.logic.Logic; +import planmysem.logic.LogicP; import planmysem.ui.Gui; import planmysem.ui.Stoppable; @@ -18,6 +19,7 @@ public class Main extends Application implements Stoppable { public static final String VERSION = "PlanMySem - Version 1.0"; private Gui gui; + private Gui guiP; public static void main(String[] args) { launch(args); @@ -25,7 +27,7 @@ public static void main(String[] args) { @Override public void start(Stage primaryStage) throws Exception { - gui = new Gui(new Logic(), VERSION); + gui = new Gui(new Logic(), new LogicP(), VERSION); gui.start(primaryStage, this); } diff --git a/src/planmysem/commands/AddCommand.java b/src/planmysem/commands/AddCommand.java index e040f7961..69d9acecd 100644 --- a/src/planmysem/commands/AddCommand.java +++ b/src/planmysem/commands/AddCommand.java @@ -18,7 +18,7 @@ */ public class AddCommand extends Command { - public static final String COMMAND_WORD = "add"; + public static final String COMMAND_WORD = "addDay"; public static final String MESSAGE_USAGE = COMMAND_WORD + ":\n" + "Adds a person to the address book. " + "Contact details can be marked private by prepending 'p' to the prefix.\n\t" diff --git a/src/planmysem/commands/AddCommandP.java b/src/planmysem/commands/AddCommandP.java new file mode 100644 index 000000000..c5d55d063 --- /dev/null +++ b/src/planmysem/commands/AddCommandP.java @@ -0,0 +1,67 @@ +package planmysem.commands; + +import java.util.HashSet; +import java.util.Set; + +import planmysem.common.Utils; +import planmysem.data.exception.IllegalValueException; +import planmysem.data.slot.Description; +import planmysem.data.slot.Location; +import planmysem.data.slot.Name; +import planmysem.data.slot.ReadOnlySlot; +import planmysem.data.slot.Slot; +import planmysem.data.tag.Tag; + +/** + * Adds a person to the address book. + */ +public class AddCommandP extends CommandP { + + public static final String COMMAND_WORD = "add"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ":\n" + "Adds a slot to the Planner. " + + "Contact details can be marked private by prepending 'p' to the prefix.\n\t" + + "Parameters: NAME [p]p/PHONE [p]e/EMAIL [p]a/ADDRESS [t/TAG]...\n\t" + + "Example: " + COMMAND_WORD + + " John Doe p/98765432 e/johnd@gmail.com a/311, Clementi Ave 2, #02-25 t/friends t/owesMoney"; + + public static final String MESSAGE_SUCCESS = "New slot added: %1$s"; + // public static final String MESSAGE_DUPLICATE_PERSON = "This person already exists in the address book"; + + private final Slot slot; + + /** + * Convenience constructor using raw values. + * + * @throws IllegalValueException if any of the raw values are invalid + */ + public AddCommandP(String name, String location, String description, String startTime, + int duration, Set tags) throws IllegalValueException { + final Set tagSet = new HashSet<>(); + + for (String tagName : tags) { + tagSet.add(new Tag(tagName)); + } + + this.slot = new Slot(new Name(name), new Location(location), new Description(description), + Utils.getLocalTime(startTime), duration, tagSet); + } + + public AddCommandP(Slot slot) { + this.slot = slot; + } + public ReadOnlySlot getSlot() { + return slot; + } + + @Override + public CommandResultP execute() { + // try { + planner.addSlot(slot); + return new CommandResultP(String.format(MESSAGE_SUCCESS, slot)); + // } catch (UniquePersonList.DuplicatePersonException dpe) { + // return new CommandResult(MESSAGE_DUPLICATE_PERSON); + // } + } + +} diff --git a/src/planmysem/commands/CommandP.java b/src/planmysem/commands/CommandP.java new file mode 100644 index 000000000..9df9b2e3b --- /dev/null +++ b/src/planmysem/commands/CommandP.java @@ -0,0 +1,61 @@ +package planmysem.commands; + +import java.time.LocalDate; +import java.util.HashMap; + +import planmysem.data.Planner; +import planmysem.data.semester.ReadOnlyDay; + +/** + * Represents an executable command. + */ +public abstract class CommandP { + protected Planner planner; + private HashMap relevantDays; + private LocalDate targetDate = null; + + /** + * @param targetDate last visible listing index of the target person + */ + public CommandP(LocalDate targetDate) { + this.setTargetDate(targetDate); + } + + protected CommandP() { + } + + /** + * Executes the command and returns the result. + */ + public CommandResultP execute() { + throw new UnsupportedOperationException("This method should be implement in child classes"); + } + + //Note: it is better to make the execute() method abstract, by replacing the above method with the line below: + //public abstract CommandResult execute(); + + /** + * Supplies the data the command will operate on. + */ + public void setData(Planner planner, HashMap relevantDays) { + this.planner = planner; + this.relevantDays = relevantDays; + } + + /** + * Extracts the the target Day in the last shown list from the given arguments. + * + * @throws IndexOutOfBoundsException if the target index is out of bounds of the last viewed listing + */ + protected ReadOnlyDay getTargetDay() throws IndexOutOfBoundsException { + return relevantDays.get(getTargetDate()); + } + + public LocalDate getTargetDate() { + return targetDate; + } + + public void setTargetDate(LocalDate targetDate) { + this.targetDate = targetDate; + } +} diff --git a/src/planmysem/commands/CommandResultP.java b/src/planmysem/commands/CommandResultP.java new file mode 100644 index 000000000..fdcc421c7 --- /dev/null +++ b/src/planmysem/commands/CommandResultP.java @@ -0,0 +1,41 @@ +package planmysem.commands; + +import java.time.LocalDate; +import java.util.HashMap; +import java.util.Optional; + +import planmysem.data.semester.ReadOnlyDay; + +/** + * Represents the result of a command execution. + */ +public class CommandResultP { + + /** + * The feedback message to be shown to the user. Contains a description of the execution result + */ + public final String feedbackToUser; + + /** + * The list of persons that was produced by the command + */ + private final HashMap days; + + public CommandResultP(String feedbackToUser) { + this.feedbackToUser = feedbackToUser; + days = null; + } + + public CommandResultP(String feedbackToUser, HashMap days) { + this.feedbackToUser = feedbackToUser; + this.days = days; + } + + /** + * Returns list of Days relevant to the command command result, if any. + */ + public Optional> getRelevantDays() { + return Optional.ofNullable(days); + } + +} diff --git a/src/planmysem/commands/HelpCommandP.java b/src/planmysem/commands/HelpCommandP.java new file mode 100644 index 000000000..bb6a97495 --- /dev/null +++ b/src/planmysem/commands/HelpCommandP.java @@ -0,0 +1,28 @@ +package planmysem.commands; + + +/** + * Shows help instructions. + */ +public class HelpCommandP extends CommandP { + + public static final String COMMAND_WORD = "help"; + + public static final String MESSAGE_USAGE = COMMAND_WORD + ":\n" + "Shows program usage instructions.\n\t" + + "Example: " + COMMAND_WORD; + + public static final String MESSAGE_ALL_USAGES = AddCommand.MESSAGE_USAGE + + "\n" + DeleteCommand.MESSAGE_USAGE + + "\n" + ClearCommand.MESSAGE_USAGE + + "\n" + FindCommand.MESSAGE_USAGE + + "\n" + ListCommand.MESSAGE_USAGE + + "\n" + ViewCommand.MESSAGE_USAGE + + "\n" + ViewAllCommand.MESSAGE_USAGE + + "\n" + HelpCommandP.MESSAGE_USAGE + + "\n" + ExitCommand.MESSAGE_USAGE; + + @Override + public CommandResultP execute() { + return new CommandResultP(MESSAGE_ALL_USAGES); + } +} diff --git a/src/planmysem/commands/IncorrectCommandP.java b/src/planmysem/commands/IncorrectCommandP.java new file mode 100644 index 000000000..f5dd0ef8a --- /dev/null +++ b/src/planmysem/commands/IncorrectCommandP.java @@ -0,0 +1,20 @@ +package planmysem.commands; + + +/** + * Represents an incorrect command. Upon execution, produces some feedback to the user. + */ +public class IncorrectCommandP extends CommandP { + + public final String feedbackToUser; + + public IncorrectCommandP(String feedbackToUser) { + this.feedbackToUser = feedbackToUser; + } + + @Override + public CommandResultP execute() { + return new CommandResultP(feedbackToUser); + } + +} diff --git a/src/planmysem/common/Utils.java b/src/planmysem/common/Utils.java index 5f47a77bf..40200b584 100644 --- a/src/planmysem/common/Utils.java +++ b/src/planmysem/common/Utils.java @@ -1,5 +1,6 @@ package planmysem.common; +import java.time.LocalTime; import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -34,4 +35,11 @@ public static boolean elementsAreUnique(Collection items) { } return true; } + + /** + * TODO + */ + public static LocalTime getLocalTime(String date) { + return LocalTime.of(1, 1, 1); + } } diff --git a/src/planmysem/data/Planner.java b/src/planmysem/data/Planner.java index f4858432a..02c5a864a 100644 --- a/src/planmysem/data/Planner.java +++ b/src/planmysem/data/Planner.java @@ -5,6 +5,7 @@ import planmysem.data.semester.Day; import planmysem.data.semester.ReadOnlyDay; import planmysem.data.semester.Semester; +import planmysem.data.slot.Slot; /** * Represents the entire address book. Contains the data of the address book. @@ -38,12 +39,20 @@ public static Planner empty() { } /** - * Adds a day to the address book. + * Adds a day to the Planner. * * @throws Semester.DuplicateDayException if an equivalent Day already exists. */ public void addDay(LocalDate date, Day day) throws Semester.DuplicateDayException { - semester.add(date, day); + semester.addDay(date, day); + } + + /** + * Adds a slot to the Planner. + * + */ + public void addSlot(Slot slot) { + semester.addSlot(slot); } /** diff --git a/src/planmysem/data/person/UniquePersonList.java b/src/planmysem/data/person/UniquePersonList.java index 8067e6377..195f57372 100644 --- a/src/planmysem/data/person/UniquePersonList.java +++ b/src/planmysem/data/person/UniquePersonList.java @@ -76,7 +76,7 @@ public boolean contains(ReadOnlyPerson toCheck) { /** * Adds a person to the list. * - * @throws DuplicatePersonException if the person to add is a duplicate of an existing person in the list. + * @throws DuplicatePersonException if the person to addDay is a duplicate of an existing person in the list. */ public void add(Person toAdd) throws DuplicatePersonException { if (contains(toAdd)) { diff --git a/src/planmysem/data/semester/Semester.java b/src/planmysem/data/semester/Semester.java index 05978b5eb..7de54d50e 100644 --- a/src/planmysem/data/semester/Semester.java +++ b/src/planmysem/data/semester/Semester.java @@ -5,6 +5,7 @@ import java.util.Map; import planmysem.data.exception.DuplicateDataException; +import planmysem.data.slot.Slot; /** * A list of days. Does not allow null elements or duplicates. @@ -53,21 +54,28 @@ public Semester(Semester source) { this.startDate = source.startDate; this.endDate = source.endDate; this.noOfWeeks = source.noOfWeeks; - } /** - * Adds a person to the list. + * Adds a day to the list. * - * @throws DuplicateDayException if the Day to add is a duplicate of an existing Day in the list. + * @throws DuplicateDayException if the Day to addDay is a duplicate of an existing Day in the list. */ - public void add(LocalDate date, Day day) throws DuplicateDayException { + public void addDay(LocalDate date, Day day) throws DuplicateDayException { if (contains(day)) { throw new DuplicateDayException(); } days.put(date, day); } + /** + * Adds a person to the list. + * + */ + public void addSlot(Slot slot) { + // days.put(date, day); + } + /** * Removes the equivalent Day from the list. * diff --git a/src/planmysem/logic/Logic.java b/src/planmysem/logic/Logic.java index f2cb1fe16..08c20d87a 100644 --- a/src/planmysem/logic/Logic.java +++ b/src/planmysem/logic/Logic.java @@ -28,7 +28,7 @@ public Logic() throws Exception { setAddressBook(storage.load()); } - Logic(StorageFile storageFile, AddressBook addressBook) { + public Logic(StorageFile storageFile, AddressBook addressBook) { setStorage(storageFile); setAddressBook(addressBook); } diff --git a/src/planmysem/logic/LogicP.java b/src/planmysem/logic/LogicP.java new file mode 100644 index 000000000..6cda965ea --- /dev/null +++ b/src/planmysem/logic/LogicP.java @@ -0,0 +1,110 @@ +package planmysem.logic; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Optional; + +import planmysem.commands.CommandP; +import planmysem.commands.CommandResultP; +import planmysem.data.Planner; +import planmysem.data.semester.ReadOnlyDay; +import planmysem.data.slot.Slot; +import planmysem.parser.ParserP; +import planmysem.storage.StorageFileP; + +/** + * Represents the main Logic of the Planner. + */ +public class LogicP { + private StorageFileP storage; + private Planner planner; + + /** + * The list of person shown to the user most recently. + */ + private HashMap lastShownDays = null; + private ArrayList lastShownSlots = null; + + public LogicP() throws Exception { + setStorage(initializeStorage()); + setPlanner(storage.load()); + } + + public LogicP(StorageFileP storageFile, Planner planner) { + setStorage(storageFile); + setPlanner(planner); + } + + void setStorage(StorageFileP storage) { + this.storage = storage; + } + + void setPlanner(Planner planner) { + this.planner = planner; + } + + /** + * Creates the StorageFile object based on the user specified path (if any) or the default storage path. + * + * @throws StorageFileP.InvalidStorageFilePathException if the target file path is incorrect. + */ + private StorageFileP initializeStorage() throws StorageFileP.InvalidStorageFilePathException { + return new StorageFileP(); + } + + public String getStorageFilePath() { + return storage.getPath(); + } + + /** + * Unmodifiable view of the current last shown list. + */ + public HashMap getLastShownDays() { + return lastShownDays; + } + + protected void setLastShownDays(HashMap days) { + lastShownDays = days; + } + + protected void setLastShownSlots(ArrayList slots) { + lastShownSlots = slots; + } + + /** + * Parses the user command, executes it, and returns the result. + * + * @throws Exception if there was any problem during command execution. + */ + public CommandResultP execute(String userCommandText) throws Exception { + CommandP command = new ParserP().parseCommand(userCommandText); + CommandResultP result = execute(command); + recordResult(result); + return result; + } + + /** + * Executes the command, updates storage, and returns the result. + * + * @param command user command + * @return result of the command + * @throws Exception if there was any problem during command execution. + */ + private CommandResultP execute(CommandP command) throws Exception { + command.setData(planner, lastShownDays); + CommandResultP result = command.execute(); + storage.save(planner); + return result; + } + + /** + * Updates the {@link #lastShownDays} if the result contains a list of Days. + */ + private void recordResult(CommandResultP result) { + final Optional> days = result.getRelevantDays(); + if (days.isPresent()) { + lastShownDays = days.get(); + } + } +} diff --git a/src/planmysem/parser/Parser.java b/src/planmysem/parser/Parser.java index 731bdbf42..cf43f57d5 100644 --- a/src/planmysem/parser/Parser.java +++ b/src/planmysem/parser/Parser.java @@ -45,14 +45,14 @@ public class Parser { public static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); /** - * Checks whether the private prefix of a contact detail in the add command's arguments string is present. + * Checks whether the private prefix of a contact detail in the addDay command's arguments string is present. */ private static boolean isPrivatePrefixPresent(String matchedPrefix) { return matchedPrefix.equals("p"); } /** - * Extracts the new person's tags from the add command's tag arguments string. + * Extracts the new person's tags from the addDay command's tag arguments string. * Merges duplicate tag strings. */ private static Set getTagsFromArgs(String tagArguments) throws IllegalValueException { @@ -112,7 +112,7 @@ public Command parseCommand(String userInput) { } /** - * Parses arguments in the context of the add person command. + * Parses arguments in the context of the addDay person command. * * @param args full command args string * @return the prepared command diff --git a/src/planmysem/parser/ParserP.java b/src/planmysem/parser/ParserP.java new file mode 100644 index 000000000..35f07b188 --- /dev/null +++ b/src/planmysem/parser/ParserP.java @@ -0,0 +1,191 @@ +package planmysem.parser; + +import static planmysem.common.Messages.MESSAGE_INVALID_COMMAND_FORMAT; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import planmysem.commands.AddCommand; +import planmysem.commands.Command; +import planmysem.commands.CommandP; +import planmysem.commands.DeleteCommand; +import planmysem.commands.FindCommand; +import planmysem.commands.HelpCommand; +import planmysem.commands.HelpCommandP; +import planmysem.commands.IncorrectCommand; +import planmysem.commands.IncorrectCommandP; +import planmysem.commands.ViewAllCommand; +import planmysem.commands.ViewCommand; +import planmysem.data.exception.IllegalValueException; + +/** + * Parses user input. + */ +public class ParserP { + + public static final Pattern PERSON_INDEX_ARGS_FORMAT = Pattern.compile("(?.+)"); + + public static final Pattern KEYWORDS_ARGS_FORMAT = + Pattern.compile("(?\\S+(?:\\s+\\S+)*)"); // one or more keywords separated by whitespace + + public static final Pattern PERSON_DATA_ARGS_FORMAT = // '/' forward slashes are reserved for delimiter prefixes + Pattern.compile("(?[^/]+)" + + " (?p?)p/(?[^/]+)" + + " (?p?)e/(?[^/]+)" + + " (?p?)a/(?
[^/]+)" + + "(?(?: t/[^/]+)*)"); // variable number of tags + /** + * Used for initial separation of command word and args. + */ + public static final Pattern BASIC_COMMAND_FORMAT = Pattern.compile("(?\\S+)(?.*)"); + + /** + * Checks whether the private prefix of a contact detail in the addDay command's arguments string is present. + */ + private static boolean isPrivatePrefixPresent(String matchedPrefix) { + return matchedPrefix.equals("p"); + } + + /** + * Extracts the new person's tags from the addDay command's tag arguments string. + * Merges duplicate tag strings. + */ + private static Set getTagsFromArgs(String tagArguments) throws IllegalValueException { + // no tags + if (tagArguments.isEmpty()) { + return Collections.emptySet(); + } + // replace first delimiter prefix, then split + final Collection tagStrings = Arrays.asList(tagArguments.replaceFirst(" t/", "").split(" t/")); + return new HashSet<>(tagStrings); + } + + /** + * Parses user input into command for execution. + * + * @param userInput full user input string + * @return the command based on the user input + */ + public CommandP parseCommand(String userInput) { + final Matcher matcher = BASIC_COMMAND_FORMAT.matcher(userInput.trim()); + if (!matcher.matches()) { + return new IncorrectCommandP(String.format(MESSAGE_INVALID_COMMAND_FORMAT, HelpCommand.MESSAGE_USAGE)); + } + + return new HelpCommandP(); + } + + /** + * Parses arguments in the context of the addDay person command. + * + * @param args full command args string + * @return the prepared command + */ + private CommandP prepareAdd(String args) { + final Matcher matcher = PERSON_DATA_ARGS_FORMAT.matcher(args.trim()); + // Validate arg string format + if (!matcher.matches()) { + return new IncorrectCommandP(String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE)); + } + + return new HelpCommandP(); + } + + /** + * Parses arguments in the context of the delete person command. + * + * @param args full command args string + * @return the prepared command + */ + private Command prepareDelete(String args) { + try { + final int targetIndex = parseArgsAsDisplayedIndex(args); + return new DeleteCommand(targetIndex); + } catch (ParseException | NumberFormatException e) { + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, DeleteCommand.MESSAGE_USAGE)); + } + } + + /** + * Parses arguments in the context of the view command. + * + * @param args full command args string + * @return the prepared command + */ + private Command prepareView(String args) { + + try { + final int targetIndex = parseArgsAsDisplayedIndex(args); + return new ViewCommand(targetIndex); + } catch (ParseException | NumberFormatException e) { + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ViewCommand.MESSAGE_USAGE)); + } + } + + /** + * Parses arguments in the context of the view all command. + * + * @param args full command args string + * @return the prepared command + */ + private Command prepareViewAll(String args) { + + try { + final int targetIndex = parseArgsAsDisplayedIndex(args); + return new ViewAllCommand(targetIndex); + } catch (ParseException | NumberFormatException e) { + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + ViewAllCommand.MESSAGE_USAGE)); + } + } + + /** + * Parses the given arguments string as a single index number. + * + * @param args arguments string to parse as index number + * @return the parsed index number + * @throws ParseException if no region of the args string could be found for the index + * @throws NumberFormatException the args string region is not a valid number + */ + private int parseArgsAsDisplayedIndex(String args) throws ParseException, NumberFormatException { + final Matcher matcher = PERSON_INDEX_ARGS_FORMAT.matcher(args.trim()); + if (!matcher.matches()) { + throw new ParseException("Could not find index number to parse"); + } + return Integer.parseInt(matcher.group("targetIndex")); + } + + /** + * Parses arguments in the context of the find person command. + * + * @param args full command args string + * @return the prepared command + */ + private Command prepareFind(String args) { + final Matcher matcher = KEYWORDS_ARGS_FORMAT.matcher(args.trim()); + if (!matcher.matches()) { + return new IncorrectCommand(String.format(MESSAGE_INVALID_COMMAND_FORMAT, + FindCommand.MESSAGE_USAGE)); + } + + // keywords delimited by whitespace + final String[] keywords = matcher.group("keywords").split("\\s+"); + final Set keywordSet = new HashSet<>(Arrays.asList(keywords)); + return new FindCommand(keywordSet); + } + + /** + * Signals that the user input could not be parsed. + */ + public static class ParseException extends Exception { + ParseException(String message) { + super(message); + } + } +} diff --git a/src/planmysem/storage/StorageFileP.java b/src/planmysem/storage/StorageFileP.java new file mode 100644 index 000000000..ac1711e2d --- /dev/null +++ b/src/planmysem/storage/StorageFileP.java @@ -0,0 +1,154 @@ +package planmysem.storage; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.nio.file.Path; +import java.nio.file.Paths; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; + +import planmysem.data.Planner; +import planmysem.data.exception.IllegalValueException; +import planmysem.storage.jaxb.AdaptedPlanner; + +/** + * Represents the file used to store Planner data. + */ +public class StorageFileP { + /** + * Default file path used if the user doesn't provide the file name. + */ + public static final String DEFAULT_STORAGE_FILEPATH = "PlanMySem.txt"; + + /* Note: Note the use of nested classes below. + * More info https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html + */ + public final Path path; + private final JAXBContext jaxbContext; + + /** + * @throws InvalidStorageFilePathException if the default path is invalid + */ + public StorageFileP() throws InvalidStorageFilePathException { + this(DEFAULT_STORAGE_FILEPATH); + } + + /** + * @throws InvalidStorageFilePathException if the given file path is invalid + */ + public StorageFileP(String filePath) throws InvalidStorageFilePathException { + try { + jaxbContext = JAXBContext.newInstance(AdaptedPlanner.class); + } catch (JAXBException ex) { + throw new RuntimeException("jaxb initialisation error"); + } + + path = Paths.get(filePath); + if (!isValidPath(path)) { + throw new InvalidStorageFilePathException("Storage file should end with '.txt'"); + } + } + + /** + * Returns true if the given path is acceptable as a storage file. + * The file path is considered acceptable if it ends with '.txt' + */ + private static boolean isValidPath(Path filePath) { + return filePath.toString().endsWith(".txt"); + } + + /** + * Saves all data to this storage file. + * + * @throws StorageOperationException if there were errors converting and/or storing data to file. + */ + public void save(Planner planner) throws StorageOperationException { + /* Note: Note the 'try with resource' statement below. + * More info: https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html + */ + try (final Writer fileWriter = + new BufferedWriter(new FileWriter(path.toFile()))) { + + final AdaptedPlanner toSave = new AdaptedPlanner(planner); + final Marshaller marshaller = jaxbContext.createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + marshaller.marshal(toSave, fileWriter); + + } catch (IOException ioe) { + throw new StorageOperationException("Error writing to file: " + path + " error: " + ioe.getMessage()); + } catch (JAXBException jaxbe) { + throw new StorageOperationException("Error converting Planner into storage format"); + } + } + + /** + * Loads data from this storage file. + * + * @throws StorageOperationException if there were errors reading and/or converting data from file. + */ + public Planner load() throws StorageOperationException { + try (final Reader fileReader = + new BufferedReader(new FileReader(path.toFile()))) { + + final Unmarshaller unmarshaller = jaxbContext.createUnmarshaller(); + final AdaptedPlanner loaded = (AdaptedPlanner) unmarshaller.unmarshal(fileReader); + // manual check for missing elements + if (loaded.isAnyRequiredFieldMissing()) { + throw new StorageOperationException("File data missing some elements"); + } + return loaded.toModelType(); + + /* Note: Here, we are using an exception to create the file if it is missing. However, we should minimize + * using exceptions to facilitate normal paths of execution. If we consider the missing file as a 'normal' + * situation (i.e. not truly exceptional) we should not use an exception to handle it. + */ + + // create empty file if not found + } catch (FileNotFoundException ex) { + final Planner empty = new Planner(); + save(empty); + return empty; + + // other errors + } catch (IOException ioe) { + throw new StorageOperationException("Error writing to file: " + path); + } catch (JAXBException jaxbe) { + throw new StorageOperationException("Error parsing file data format"); + } catch (IllegalValueException ive) { + throw new StorageOperationException("File contains illegal data values; data type constraints not met"); + } + } + + public String getPath() { + return path.toString(); + } + + /** + * Signals that the given file path does not fulfill the storage filepath constraints. + */ + public static class InvalidStorageFilePathException extends IllegalValueException { + public InvalidStorageFilePathException(String message) { + super(message); + } + } + + /** + * Signals that some error has occured while trying to convert and read/write data between the application + * and the storage file. + */ + public static class StorageOperationException extends Exception { + public StorageOperationException(String message) { + super(message); + } + } + +} diff --git a/src/planmysem/storage/jaxb/AdaptedDay.java b/src/planmysem/storage/jaxb/AdaptedDay.java index e5910a1b9..d15eb1b5a 100644 --- a/src/planmysem/storage/jaxb/AdaptedDay.java +++ b/src/planmysem/storage/jaxb/AdaptedDay.java @@ -4,9 +4,7 @@ import java.util.ArrayList; import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlValue; -import planmysem.common.Utils; import planmysem.data.exception.IllegalValueException; import planmysem.data.semester.Day; import planmysem.data.semester.ReadOnlyDay; @@ -16,8 +14,7 @@ * JAXB-friendly adapted Day data holder class. */ public class AdaptedDay { - - @XmlValue + @XmlElement(required = true) private DayOfWeek dayOfWeek; @XmlElement(required = true) private ArrayList slots = new ArrayList<>(); @@ -57,7 +54,8 @@ public boolean isAnyRequiredFieldMissing() { } } // second call only happens if phone/email/address are all not null - return Utils.isAnyNull(dayOfWeek, slots); + // return Utils.isAnyNull(dayOfWeek, slots); + return false; } /** diff --git a/src/planmysem/storage/jaxb/AdaptedPerson.java b/src/planmysem/storage/jaxb/AdaptedPerson.java index 595e92d64..bb1cc1943 100644 --- a/src/planmysem/storage/jaxb/AdaptedPerson.java +++ b/src/planmysem/storage/jaxb/AdaptedPerson.java @@ -23,7 +23,6 @@ * JAXB-friendly adapted person data holder class. */ public class AdaptedPerson { - @XmlElement(required = true) private String name; @XmlElement(required = true) diff --git a/src/planmysem/storage/jaxb/AdaptedPlanner.java b/src/planmysem/storage/jaxb/AdaptedPlanner.java index df1dde80f..417f59c2a 100644 --- a/src/planmysem/storage/jaxb/AdaptedPlanner.java +++ b/src/planmysem/storage/jaxb/AdaptedPlanner.java @@ -5,14 +5,13 @@ import planmysem.data.Planner; import planmysem.data.exception.IllegalValueException; -import planmysem.data.semester.Semester; /** * JAXB-friendly adapted Planner data holder class. */ @XmlRootElement(name = "Planner") public class AdaptedPlanner { - @XmlElement + @XmlElement(required = true) private AdaptedSemester semester = new AdaptedSemester(); /** @@ -27,13 +26,8 @@ public AdaptedPlanner() { * @param source future changes to this will not affect the created AdaptedPlanner */ public AdaptedPlanner(Planner source) { - semester = new AdaptedSemester(); semester = new AdaptedSemester(source.getSemester()); } - // public AdaptedAddressBook(AddressBook source) { - // persons = new ArrayList<>(); - // source.getAllPersons().forEach(person -> persons.add(new AdaptedPerson(person))); - // } /** * Returns true if any required field is missing. @@ -54,6 +48,6 @@ public boolean isAnyRequiredFieldMissing() { * @throws IllegalValueException if there were any data constraints violated in the adapted person */ public Planner toModelType() throws IllegalValueException { - return new Planner(new Semester(semester.toModelType())); + return new Planner(semester.toModelType()); } } diff --git a/src/planmysem/storage/jaxb/AdaptedSemester.java b/src/planmysem/storage/jaxb/AdaptedSemester.java index 03ad20af8..51cccad93 100644 --- a/src/planmysem/storage/jaxb/AdaptedSemester.java +++ b/src/planmysem/storage/jaxb/AdaptedSemester.java @@ -5,9 +5,7 @@ import java.util.Map; import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlValue; -import planmysem.common.Utils; import planmysem.data.exception.IllegalValueException; import planmysem.data.semester.Day; import planmysem.data.semester.ReadOnlySemester; @@ -17,18 +15,17 @@ * JAXB-friendly adapted person data holder class. */ public class AdaptedSemester { - - @XmlValue + @XmlElement(required = false) private String name; - @XmlValue + @XmlElement(required = false) private String academicYear; - @XmlValue + @XmlElement(required = false) private String startDate; - @XmlValue + @XmlElement(required = false) private String endDate; - @XmlValue + @XmlElement(required = false) private int noOfWeeks; - @XmlElement + @XmlElement(required = true) private HashMap days = new HashMap<>(); /** @@ -71,7 +68,8 @@ public boolean isAnyRequiredFieldMissing() { } // second call only happens if phone/email/address are all not null - return Utils.isAnyNull(name, academicYear, days, startDate, endDate); + // return Utils.isAnyNull(name, academicYear, days, startDate, endDate); + return false; } /** @@ -86,7 +84,7 @@ public Semester toModelType() throws IllegalValueException { final String endDate = this.endDate; final int noOfWeeks = this.noOfWeeks; - final HashMap days = new HashMap<>(); + final HashMap days = new HashMap(); for (Map.Entry day : this.days.entrySet()) { days.put(day.getKey(), day.getValue().toModelType()); } diff --git a/src/planmysem/storage/jaxb/AdaptedSlot.java b/src/planmysem/storage/jaxb/AdaptedSlot.java index 7233ef1a0..7afbceaaf 100644 --- a/src/planmysem/storage/jaxb/AdaptedSlot.java +++ b/src/planmysem/storage/jaxb/AdaptedSlot.java @@ -7,7 +7,6 @@ import java.util.Set; import javax.xml.bind.annotation.XmlElement; -import javax.xml.bind.annotation.XmlValue; import planmysem.common.Utils; import planmysem.data.exception.IllegalValueException; @@ -22,18 +21,17 @@ * JAXB-friendly adapted person data holder class. */ public class AdaptedSlot { - - @XmlValue + @XmlElement(required = true) private String name; - @XmlValue + @XmlElement(required = true) private String location; - @XmlValue + @XmlElement(required = true) private String description; - @XmlValue + @XmlElement(required = true) private int duration; - @XmlValue + @XmlElement(required = true) private LocalTime time; - @XmlElement + @XmlElement(required = true) private List tagged = new ArrayList<>(); /** diff --git a/src/planmysem/storage/jaxb/AdaptedTag.java b/src/planmysem/storage/jaxb/AdaptedTag.java index 99dc5e123..88b07e3af 100644 --- a/src/planmysem/storage/jaxb/AdaptedTag.java +++ b/src/planmysem/storage/jaxb/AdaptedTag.java @@ -10,7 +10,6 @@ * JAXB-friendly adapted tag data holder class. */ public class AdaptedTag { - @XmlValue private String tagName; diff --git a/src/planmysem/ui/Formatter.java b/src/planmysem/ui/Formatter.java index f3a8bfe70..87c60940e 100644 --- a/src/planmysem/ui/Formatter.java +++ b/src/planmysem/ui/Formatter.java @@ -1,9 +1,12 @@ package planmysem.ui; +import java.time.LocalDate; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import planmysem.data.person.ReadOnlyPerson; +import planmysem.data.semester.ReadOnlyDay; /** * Used for formatting text for display. e.g. for adding text decorations. @@ -76,4 +79,17 @@ public String format(List persons) { return format(asIndexedList(formattedPersons)); } + /** + * Formats the given list of days for displaying to the user. + */ + public String format(HashMap days) { + final List formattedDays = new ArrayList<>(); + + for (LocalDate date : days.keySet()) { + formattedDays.add(date + " " + days.get(date).toString()); + } + + return format(asIndexedList(formattedDays)); + } + } diff --git a/src/planmysem/ui/Gui.java b/src/planmysem/ui/Gui.java index 9dcf02eff..0ec5960bb 100644 --- a/src/planmysem/ui/Gui.java +++ b/src/planmysem/ui/Gui.java @@ -7,6 +7,7 @@ import javafx.stage.Stage; import planmysem.Main; import planmysem.logic.Logic; +import planmysem.logic.LogicP; /** * The GUI of the App @@ -21,11 +22,14 @@ public class Gui { public static final int INITIAL_WINDOW_WIDTH = 800; public static final int INITIAL_WINDOW_HEIGHT = 600; private final Logic logic; + private final LogicP logicP; private MainWindow mainWindow; + private MainWindowP mainWindowP; private String version; - public Gui(Logic logic, String version) { + public Gui(Logic logic, LogicP logicP, String version) { + this.logicP = logicP; this.logic = logic; this.version = version; } @@ -35,7 +39,11 @@ public Gui(Logic logic, String version) { */ public void start(Stage stage, Stoppable mainApp) throws IOException { mainWindow = createMainWindow(stage, mainApp); - mainWindow.displayWelcomeMessage(version, logic.getStorageFilePath()); + mainWindow.displayWelcomeMessage(version + " old Address Book", logic.getStorageFilePath()); + + Stage stage2 = new Stage(); + mainWindowP = createMainWindowP(stage2, mainApp); + mainWindowP.displayWelcomeMessage(version, logicP.getStorageFilePath()); } /** @@ -58,4 +66,20 @@ private MainWindow createMainWindow(Stage stage, Stoppable mainApp) throws IOExc return mainWindow; } + /** + * TODO: Add Javadoc comment. + */ + private MainWindowP createMainWindowP(Stage stage, Stoppable mainApp) throws IOException { + FXMLLoader loader = new FXMLLoader(); + loader.setLocation(Main.class.getResource("ui/mainwindowP.fxml")); + + stage.setTitle(version); + stage.setScene(new Scene(loader.load(), INITIAL_WINDOW_WIDTH, INITIAL_WINDOW_HEIGHT)); + stage.show(); + MainWindowP mainWindowP = null; + mainWindowP = loader.getController(); + mainWindowP.setLogic(logicP); + mainWindowP.setMainApp(mainApp); + return mainWindowP; + } } diff --git a/src/planmysem/ui/MainWindowP.java b/src/planmysem/ui/MainWindowP.java new file mode 100644 index 000000000..98298329a --- /dev/null +++ b/src/planmysem/ui/MainWindowP.java @@ -0,0 +1,121 @@ +package planmysem.ui; + +import java.time.LocalDate; +import java.util.HashMap; +import java.util.Optional; + +import javafx.event.ActionEvent; +import javafx.fxml.FXML; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextField; +import planmysem.commands.CommandResultP; +import planmysem.commands.ExitCommand; +import planmysem.common.Messages; +import planmysem.data.semester.ReadOnlyDay; +import planmysem.logic.LogicP; + +/** + * Main Window of the GUI. + */ +public class MainWindowP { + + private LogicP logic; + private Stoppable mainApp; + @FXML + private TextArea outputConsole; + @FXML + private TextField commandInput; + + public MainWindowP() { + } + + public void setLogic(LogicP logic) { + this.logic = logic; + } + + public void setMainApp(Stoppable mainApp) { + this.mainApp = mainApp; + } + + + /** + * TODO: Add Javadoc comment. + */ + @FXML + void onCommand(ActionEvent event) { + try { + String userCommandText = commandInput.getText(); + CommandResultP result = logic.execute(userCommandText); + if (isExitCommand(result)) { + exitApp(); + return; + } + displayResult(result); + clearCommandInput(); + } catch (Exception e) { + display(e.getMessage()); + throw new RuntimeException(e); + } + } + + private void exitApp() throws Exception { + mainApp.stop(); + } + + /** + * Returns true of the result given is the result of an exit command + */ + private boolean isExitCommand(CommandResultP result) { + return result.feedbackToUser.equals(ExitCommand.MESSAGE_EXIT_ACKNOWEDGEMENT); + } + + /** + * Clears the command input box + */ + private void clearCommandInput() { + commandInput.setText(""); + } + + /** + * Clears the output display area + */ + public void clearOutputConsole() { + outputConsole.clear(); + } + + /** + * Displays the result of a command execution to the user. + */ + public void displayResult(CommandResultP result) { + clearOutputConsole(); + final Optional> resultDays = result.getRelevantDays(); + if (resultDays.isPresent()) { + display(resultDays.get()); + } + display(result.feedbackToUser); + } + + /** + * TODO: Add Javadoc comment. + */ + public void displayWelcomeMessage(String version, String storageFilePath) { + String storageFileInfo = String.format(Messages.MESSAGE_USING_STORAGE_FILE, storageFilePath); + display(Messages.MESSAGE_WELCOME, version, Messages.MESSAGE_PROGRAM_LAUNCH_ARGS_USAGE, storageFileInfo); + } + + /** + * Displays the list of persons in the output display area, formatted as an indexed list. + * Private contact details are hidden. + */ + private void display(HashMap days) { + display(new Formatter().format(days)); + } + + /** + * Displays the given messages on the output display area, after formatting appropriately. + */ + private void display(String... messages) { + outputConsole.setText(outputConsole.getText() + new Formatter().format(messages)); + } + +} diff --git a/src/planmysem/ui/mainwindowP.fxml b/src/planmysem/ui/mainwindowP.fxml new file mode 100644 index 000000000..97571ae87 --- /dev/null +++ b/src/planmysem/ui/mainwindowP.fxml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + diff --git a/test/java/planmysem/logic/LogicTest.java b/test/java/planmysem/logic/LogicTest.java index cb6331bb6..da1664221 100644 --- a/test/java/planmysem/logic/LogicTest.java +++ b/test/java/planmysem/logic/LogicTest.java @@ -143,25 +143,25 @@ public void execute_clear() throws Exception { public void execute_add_invalidArgsFormat() throws Exception { String expectedMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE); assertCommandBehavior( - "add wrong args wrong args", expectedMessage); + "addDay wrong args wrong args", expectedMessage); assertCommandBehavior( - "add Valid Name 12345 e/valid@email.butNoPhonePrefix a/valid, address", expectedMessage); + "addDay Valid Name 12345 e/valid@email.butNoPhonePrefix a/valid, address", expectedMessage); assertCommandBehavior( - "add Valid Name p/12345 valid@email.butNoPrefix a/valid, address", expectedMessage); + "addDay Valid Name p/12345 valid@email.butNoPrefix a/valid, address", expectedMessage); assertCommandBehavior( - "add Valid Name p/12345 e/valid@email.butNoAddressPrefix valid, address", expectedMessage); + "addDay Valid Name p/12345 e/valid@email.butNoAddressPrefix valid, address", expectedMessage); } @Test public void execute_add_invalidPersonData() throws Exception { assertCommandBehavior( - "add []\\[;] p/12345 e/valid@e.mail a/valid, address", Name.MESSAGE_NAME_CONSTRAINTS); + "addDay []\\[;] p/12345 e/valid@e.mail a/valid, address", Name.MESSAGE_NAME_CONSTRAINTS); assertCommandBehavior( - "add Valid Name p/not_numbers e/valid@e.mail a/valid, address", Phone.MESSAGE_PHONE_CONSTRAINTS); + "addDay Valid Name p/not_numbers e/valid@e.mail a/valid, address", Phone.MESSAGE_PHONE_CONSTRAINTS); assertCommandBehavior( - "add Valid Name p/12345 e/notAnEmail a/valid, address", Email.MESSAGE_EMAIL_CONSTRAINTS); + "addDay Valid Name p/12345 e/notAnEmail a/valid, address", Email.MESSAGE_EMAIL_CONSTRAINTS); assertCommandBehavior( - "add Valid Name p/12345 e/valid@e.mail a/valid, address t/invalid_-[.tag", Tag.MESSAGE_TAG_CONSTRAINTS); + "addDay Valid Name p/12345 e/valid@e.mail a/valid, address t/invalid_-[.tag", Tag.MESSAGE_TAG_CONSTRAINTS); } @@ -512,12 +512,12 @@ Person generatePerson(int seed, boolean isAllFieldsPrivate) throws Exception { } /** - * Generates the correct add command based on the person given + * Generates the correct addDay command based on the person given */ String generateAddCommand(Person p) { StringJoiner cmd = new StringJoiner(" "); - cmd.add("add"); + cmd.add("addDay"); cmd.add(p.getName().toString()); cmd.add((p.getPhone().isPrivate() ? "pp/" : "p/") + p.getPhone()); diff --git a/test/java/planmysem/parser/ParserTest.java b/test/java/planmysem/parser/ParserTest.java index c5bce95e8..bc4528415 100644 --- a/test/java/planmysem/parser/ParserTest.java +++ b/test/java/planmysem/parser/ParserTest.java @@ -49,7 +49,7 @@ private static Person generateTestPerson() { } private static String convertPersonToAddCommandString(ReadOnlyPerson person) { - String addCommand = "add " + String addCommand = "addDay " + person.getName().fullName + (person.getPhone().isPrivate() ? " pp/" : " p/") + person.getPhone().value + (person.getEmail().isPrivate() ? " pe/" : " e/") + person.getEmail().value @@ -217,21 +217,21 @@ public void findCommand_duplicateKeys_parsedCorrectly() { } /** - * Test add person command + * Test addDay person command */ @Test public void addCommand_invalidArgs() { final String[] inputs = { - "add", - "add ", - "add wrong args format", + "addDay", + "addDay ", + "addDay wrong args format", // no phone prefix - String.format("add $s $s e/$s a/$s", Name.EXAMPLE, Phone.EXAMPLE, Email.EXAMPLE, Address.EXAMPLE), + String.format("addDay $s $s e/$s a/$s", Name.EXAMPLE, Phone.EXAMPLE, Email.EXAMPLE, Address.EXAMPLE), // no email prefix - String.format("add $s p/$s $s a/$s", Name.EXAMPLE, Phone.EXAMPLE, Email.EXAMPLE, Address.EXAMPLE), + String.format("addDay $s p/$s $s a/$s", Name.EXAMPLE, Phone.EXAMPLE, Email.EXAMPLE, Address.EXAMPLE), // no address prefix - String.format("add $s p/$s e/$s $s", Name.EXAMPLE, Phone.EXAMPLE, Email.EXAMPLE, Address.EXAMPLE) + String.format("addDay $s p/$s e/$s $s", Name.EXAMPLE, Phone.EXAMPLE, Email.EXAMPLE, Address.EXAMPLE) }; final String resultMessage = String.format(MESSAGE_INVALID_COMMAND_FORMAT, AddCommand.MESSAGE_USAGE); parseAndAssertIncorrectWithMessage(resultMessage, inputs); @@ -248,7 +248,7 @@ public void addCommand_invalidPersonDataInArgs() { final String invalidTagArg = "t/invalid_-[.tag"; // address can be any string, so no invalid address - final String addCommandFormatString = "add $s $s $s a/" + Address.EXAMPLE; + final String addCommandFormatString = "addDay $s $s $s a/" + Address.EXAMPLE; // test each incorrect person data field argument individually final String[] inputs = { From d704e6a9ab23842a46921a111a64f25338a575dd Mon Sep 17 00:00:00 2001 From: Sean Yap Date: Tue, 26 Feb 2019 05:13:39 +0800 Subject: [PATCH 2/2] update some code issues and UserGuide.adoc --- docs/UserGuide.adoc | 199 ++++++------------------ src/planmysem/Main.java | 1 - src/planmysem/logic/LogicP.java | 4 +- src/planmysem/storage/StorageFileP.java | 2 +- src/planmysem/ui/MainWindowP.java | 3 - 5 files changed, 48 insertions(+), 161 deletions(-) diff --git a/docs/UserGuide.adoc b/docs/UserGuide.adoc index 0e4ee21f0..1d8d86288 100644 --- a/docs/UserGuide.adoc +++ b/docs/UserGuide.adoc @@ -18,9 +18,9 @@ By: `Team T08-3` Since: `Jan 2019` Licence: `MIT` == Introduction -*PlanMySem* is a CLI (Command Line Interface) scheduling/calendar application that targets NUS students and staff who prefer to use a desktop application for managing their schedule/calendar. -More importantly, *PlanMySem* is optimized for those who prefer to work with a Command Line Interface (CLI) and/or are learning to work more efficiently with CLI tools. -Additionally, unlike traditional calendar/scheduling applications, *PlanMySem* utilizes minimal resources on the user’s machine while still allowing the user to view their schedules swiftly and efficiently. +PlanMySem is a CLI (Command Line Interface) scheduling/calendar application that targets NUS students and staff who prefer to use a desktop application for managing their schedule/calendar. PlanMySem automatically creates a calendar and synchronises it according to the NUS academic calendar for a particular semester. + +PlanMySem is optimized for those who prefer to work with a Command Line Interface (CLI) and/or are learning to work more efficiently with CLI tools. Additionally, unlike traditional calendar/scheduling applications, PlanMySem utilizes minimal resources on the user’s machine while still allowing the user to view their schedules swiftly and efficiently. == Quick Start @@ -59,156 +59,51 @@ e.g. typing *`help`* and pressing kbd:[Enter] will open the help window. * `n/`: Name specific to the object at hand. ** e.g. Add _Category_: `add cat n/NAME [n/MORE_NAMES]...` ** The `n/` here refers to the _category_ name. -* `a/`: _Activity’s_ name. -** e.g. Add _Slot_: `add slot a/ACTIVITY_NAME d/DAY t/TIME [r/norecess] [r/noreading] [r/nonormal] [r/recess] [r/reading] [r/normal] [tag/TAG]...` -** The `a/` here refers to the _activity_ in which to add the _slot_ to. -* `c/`: _Category_ name -** e.g. `List Slots: list slot [c/CATEGORY_NAME]... [a/ACTIVITY_NAME]...` -** The `c/` here refers to the _category_ in which the _slots_ are listed from. -* `d/`: Day. -** Format: `Monday | Tuesday | Wednesday | Thursday | Friday` -** e.g. Add _slot_: `add slot a/ACTIVITY_NAME d/DAY t/TIME [r/norecess] [r/noreading] [r/nonormal] [r/recess] [r/reading] [r/normal] [tag/TAG]...` -** The `d/` here refers to the day in which to add the _slot_ to. +* `d/`: Date. +** Format: `01-01`, `2019-01-02` +** e.g. Add _slot_: `add slot d/DATE t/TIME [r/norecess] [r/noreading] [r/nonormal] [r/recess] [r/reading] [r/normal] [tag/TAG]...` * `t/`: Time. ** Format: 24-Hour in the form of “hhmm-hhmm” or 12-Hour format in the form of `HOUR+AM|PM-Hour+AM|PM` -** e.g. Add _slot_: `add slot a/ACTIVITY_NAME d/DAY t/TIME [r/norecess] [r/noreading] [r/nonormal] [r/recess] [r/reading] [r/normal] [tag/TAG]...` +** e.g. Add: `add d/DATE t/TIME [r/norecess] [r/noreading] [r/nonormal] [r/recess] [r/reading] [r/normal] [tag/TAG]...` ** The `t/` here refers to the time in which to add the _slot_ to. * `tag/`: Tag. -** e.g. Add _slot_: `add slot a/ACTIVITY_NAME d/DAY t/TIME [r/norecess] [r/noreading] [r/nonormal] [r/recess] [r/reading] [r/normal] [tag/TAG]...` +** e.g. Add: `add d/DATE t/TIME [r/norecess] [r/noreading] [r/nonormal] [r/recess] [r/reading] [r/normal] [tag/TAG]...` ** The `tag/` here refers the _tags_ to attach to the _slot_. * `r/`: recurrence ** Format: `norecess | noreading | nonormal | recess | reading | normal` -** e.g. Add _slot_: `add slot a/ACTIVITY_NAME d/DAY t/TIME [r/norecess] [r/noreading] [r/nonormal] [r/recess] [r/reading] [r/normal] [tag/TAG]...` +** e.g. Add: `add d/DATE t/TIME [r/norecess] [r/noreading] [r/nonormal] [r/recess] [r/reading] [r/normal] [tag/TAG]...` ** The `r/` here refers to the recurrence of the _slot_. * `nn/`: New name of the object at hand. -** e.g. Edit _category_: `edit cat n/NAME nn/NEW_NAME` -** The `nn/` here refers to the new name for the _category_ specified. -* `nc/`: New _category_ name. -** e.g. Edit _Activity_: `edit act n/NAME [nn/NEW_NAME] [nc/NEW_CATEGORY]` -** The `nc/` here refers to the new _category_ name of which the _activity_ should belong to. - +* `nd/`: New Date +* `nt/`: New Time ==== === Viewing help : `help` Format: `help` -=== Adding a Category : `add cat` - -Add a _category_ to the planner. + -Format: `add cat n/NAME [n/MORE_NAMES]...` - -Examples: - -* `add cat n/Sports` + -Add a _category_ called "Sports". -* `add cat n/Sports n/Interest Groups` + -Add two _categories_ called "Sports" and "Interest Groups". - -=== Editing a Category : `edit cat` - -Edit a Category. + -Format: `edit cat n/NAME nn/NEW_NAME` - -Examples: - -* `edit cat n/Sports nn/Frisbee` -Rename the _category_ "Sports" to "Frisbee". -* `edit cat n/Interest Groups nn/CCA` -Rename the _category_ "Interest Groups" to "CCA". - -=== Deleting a Category : `del cat` - -Delete a category + -Format: `del cat n/NAME [n/MORE_NAMES]...` - -**** -* Deleting a _category_ will delete all associated _activities_ as well as _slots_. -* When multiple _categories_ are selected, a prompt will appear to confirm the action. -**** - -Examples: - -* `del cat n/Sports n/Frisbee` + -Delete both the _categories_ 'Sports' and 'Frisbee' and all _activities_ and _slots_ associated with them. -* `del cat n/Interest Groups` + -Delete the _category_ 'Sports' and all _activities_ and _slots_ associated with it. - -=== Adding an Activity : add act - -Add an _activity_ to the planner. + -Format: `add act n/NAME c/CATEGORY` - -[NOTE] -==== -An _activity_ must belong to a _category_. -==== - -Examples: - -* `add act n/Frisbee c/Sports` + -Add an _activity_ named "Frisbee" to the _category_ "Sports". - -=== Listing all Activities : `list act` - -List all _activities_ in the planner. + -Format: `list act` - -=== Editing an Activity : `edit act` - -Edit an Activity. + -Format: `edit act n/NAME [nn/NEW_NAME] [nc/NEW_CATEGORY]` - -**** -* Editing an _activity_ allows the renaming of it's name and or changing the _category_ it belongs to; hence, either and or `nn/` and `nc/` can be specified in one command. -* Minimum of one option have to be specified. -**** - -[NOTE] -==== -When editing an _activity_ such that a new _category_ is replacing the current _category_, the new _category_ have to been already created. -==== - -Examples: - -* `edit act n/Frisbee nn/Barefoots nc/Sports` + -The existing _activity_ `Frisbee` is renamed to `Barefoots` and is moved from some _category_ to the _category_ `Sports`. - -=== Deleting an Activity : `del act` - -Delete an _activity_ from the planner. + -Format: `del act n/NAME [n/MORE_NAMES]...` - -Examples: - -* `del act n/Frisbee` + -Delete the _activity_ "Frisbee". -* `del act n/Frisbee n/Basketball` + -Delete the _activities_ "Frisbee" and "Basketball". - -=== Adding a Slot : `add slot` +=== Adding Slots : `add` Add a _slot_ to the planner. + -Format: `add slot a/ACTIVITY_NAME d/DAY t/TIME [r/norecess] [r/noreading] [r/nonormal] [r/recess] [r/reading] [r/normal] [tag/TAG]...` +Format: `add slot d/DATE t/TIME d/DURATION [r/norecess] [r/noreading] [r/nonormal] [r/recess] [r/reading] [r/normal] [tag/TAG]...` Examples: -* `add slot a/Sport d/monday t/0800-0900 tag/Frisbee tag/Tembusu College` + +* `add d/01-01 t/0800-0900 tag/Frisbee tag/Tembusu College` + Add a _slot_ to the _activity_ "Sport" on monday, from 0800hrs to 0900hrs with the tags "Frisbee" and "Tembusu College". * `add slot a/Interest Group d/monday t/0800-0900 r/recess r/reading tag/Tembusu College tag/Barefoots` + Do the same but additionally, recurse the slot on recess and reading week. [NOTE] ==== -1. The `a/ACTIVITY_NAME` option is mandatory as a _slot_ must belong to an _activity_. + -2. The default for recurrence is no recess week and no reading week. +The default for recurrence is no recess week and no reading week. + If the recurrence options are not defined, then there the _slot_ will recurse every week except recess week and reading week. ==== -=== Listing Slots: `list slot` +=== Listing Slots: `list` -List all _slots_ in the planner belonging to a _category_/_activity_. + -Format: `list slot [c/CATEGORY_NAME]... [a/ACTIVITY_NAME]...` +List all _slots_ in the planner. + +Format: `list` [NOTE] ==== @@ -219,61 +114,52 @@ Examples: * `list slot` + List all _slots_ in the planner. -* `list slot c/Modules` + -List all slots in the _category_ "Modules". -* `list slot c/Sport c/Interest Group` + -List all slots in either the _category_ "Sport" or the _category_ "Interest Group". -=== Editing a Slot: `edit slot` +=== Editing Slots: `edit` Edit a _slot_. + -Format: `edit slot n/ACTIVITY_NAME d/DAY t/TIME [nd/NEW_DAY] [nt/NEW_TIME] [r/norecess] [r/noreading] [r/nonormal] [r/recess] [r/reading] [r/normal]` +Format: `edit d/DATE t/TIME [nd/NEW_DATE] [nt/NEW_TIME] [r/norecess] [r/noreading] [r/nonormal] [r/recess] [r/reading] [r/normal]` + +[NOTE] +==== +If the date and time specified does not correspond directly to a slot, the nearest slot will be selected for editing. +==== Examples: -* `edit slot n/Sport d/monday t/0800-0900 nd/tuesday nt/0900-1000` + -Select the _slot_ on 0800-0900hrs Monday and change it's day from Monday to Tuesday and it's time from "0800-0900" to "0900-1000". -* `edit slot n/Sport d/monday t/0800-0900 r/recess r/reading` + -Select the _slot_ on 0800-0900hrs Monday and set it to recurse on recess and reading week. -* `edit slot n/Sport d/monday t/0800-0900 nd/tuesday nt/0900-1000 r/recess r/reading` + -Select the _slot_ on 0800-0900hrs Monday and change it's day from Monday to Tuesday, it's time from "0800-0900" to "0900-1000" and set it to recurse on recess and reading week. +* `edit d/01-01 t/0800 nd/01-03 nt/0900` + +Select the _slot_ on the first of January, at 0800hrs and change it's date to the Third of January and it's time to 0900hrs. -=== Delete slots: `del slot` +=== Delete Slots: `del` Deleting _slots_. + -Format: `del slot a/ACTIVITY_NAME d/DAY t/TIME` - -Example: - -* - -=== Locating Categories/Activities/tags: `find` - -Find all _categories_/_activities_/_tag_ whose name contains any of the given keywords. + -Format: `find k/KEYWORD [k/MORE_KEYWORDS]... [c/CATEGORY_NAME] [a/ACTIVITY_NAME] [tag/TAG_NAME]` +Format: `del [d/DATE]... [t/TIME]... [t/TAG]... ` [NOTE] ==== -If the option to find by _category_ or _activity_ or _tag_ is given, then the result will filter by those only. +All slots that strictly fits all the options given will be deleted. + +This is useful for deleting a whole "category" of slots, such as an entire module's lab sessions. ==== Example: -* +* `del d/01-03 t/0800` + +Delete the slot located on the 3th of January, 0800hrs. +* TODO -=== Deleting a Categories/Activities/tags (shorthand) : `del` +=== Locating Slots: `find` -Delete a _category_/_activity_/_slot_/_tag_. + -Format: `del [c/CATEGORY]... [a/ACTIVITY]... [tag/TAG]...` +Find all _slots_ whose name and or tags contains any of the given keywords. + +Format: `find [n/SLOT_NAMES]... [tag/TAG]...` [NOTE] ==== -Deleting a _category_ or _activity_ will delete all _slots_ under them as well. Deleting a _tag_ will not delete the _slots_ associated with them. +TODO ==== Example: -* +* TODO === View the Planner : `view` @@ -288,7 +174,12 @@ Format: `view day DATE | view week WEEK | view month MONTH | view all` Example: -* +* `view day` +* `view day 1-03` + +View the first of January. +* `view all` + +all the details in the planner. + === Listing previous inputted commands : `history` diff --git a/src/planmysem/Main.java b/src/planmysem/Main.java index debbbaae4..8417a34cf 100644 --- a/src/planmysem/Main.java +++ b/src/planmysem/Main.java @@ -19,7 +19,6 @@ public class Main extends Application implements Stoppable { public static final String VERSION = "PlanMySem - Version 1.0"; private Gui gui; - private Gui guiP; public static void main(String[] args) { launch(args); diff --git a/src/planmysem/logic/LogicP.java b/src/planmysem/logic/LogicP.java index 6cda965ea..b69c90e71 100644 --- a/src/planmysem/logic/LogicP.java +++ b/src/planmysem/logic/LogicP.java @@ -36,11 +36,11 @@ public LogicP(StorageFileP storageFile, Planner planner) { setPlanner(planner); } - void setStorage(StorageFileP storage) { + public void setStorage(StorageFileP storage) { this.storage = storage; } - void setPlanner(Planner planner) { + public void setPlanner(Planner planner) { this.planner = planner; } diff --git a/src/planmysem/storage/StorageFileP.java b/src/planmysem/storage/StorageFileP.java index ac1711e2d..943e9704c 100644 --- a/src/planmysem/storage/StorageFileP.java +++ b/src/planmysem/storage/StorageFileP.java @@ -49,7 +49,7 @@ public StorageFileP(String filePath) throws InvalidStorageFilePathException { try { jaxbContext = JAXBContext.newInstance(AdaptedPlanner.class); } catch (JAXBException ex) { - throw new RuntimeException("jaxb initialisation error"); + throw new RuntimeException("jaxb initialisation error" + ex); } path = Paths.get(filePath); diff --git a/src/planmysem/ui/MainWindowP.java b/src/planmysem/ui/MainWindowP.java index 98298329a..bf3d68bd5 100644 --- a/src/planmysem/ui/MainWindowP.java +++ b/src/planmysem/ui/MainWindowP.java @@ -26,9 +26,6 @@ public class MainWindowP { @FXML private TextField commandInput; - public MainWindowP() { - } - public void setLogic(LogicP logic) { this.logic = logic; }