diff --git a/.gitignore b/.gitignore index f69985ef1f..3a15cdf59e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,17 +1,17 @@ -# IDEA files -/.idea/ -/out/ -/*.iml - -# Gradle build files -/.gradle/ -/build/ -src/main/resources/docs/ - -# MacOS custom attributes files created by Finder -.DS_Store -*.iml -bin/ - -/text-ui-test/ACTUAL.txt -text-ui-test/EXPECTED-UNIX.TXT +# IDEA files +/.idea/ +/out/ +/*.iml + +# Gradle build files +/.gradle/ +/build/ +src/main/resources/docs/ + +# MacOS custom attributes files created by Finder +.DS_Store +*.iml +bin/ + +/text-ui-test/ACTUAL.txt +text-ui-test/EXPECTED-UNIX.TXT diff --git a/.project b/.project new file mode 100644 index 0000000000..3aaffc18f5 --- /dev/null +++ b/.project @@ -0,0 +1,34 @@ +z + + ip + Project ip created by Buildship. + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.buildship.core.gradleprojectbuilder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.buildship.core.gradleprojectnature + + + + 1632124959946 + + 30 + + org.eclipse.core.resources.regexFilterMatcher + node_modules|.git|__CREATED_BY_JAVA_LANGUAGE_SERVER__ + + + + diff --git a/.settings/org.eclipse.buildship.core.prefs b/.settings/org.eclipse.buildship.core.prefs new file mode 100644 index 0000000000..ac31e3c7b5 --- /dev/null +++ b/.settings/org.eclipse.buildship.core.prefs @@ -0,0 +1,13 @@ +arguments= +auto.sync=false +build.scans.enabled=false +connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) +connection.project.dir= +eclipse.preferences.version=1 +gradle.user.home= +java.home=C\:/Program Files/Java/jdk-11.0.9 +jvm.arguments= +offline.mode=false +override.workspace.settings=true +show.console.view=true +show.executions.view=true diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 8e359a0145..419450f2f6 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -1,9 +1,9 @@ -# Contributors - -Display | Name | Github Profile | Homepage ----|:---:|:---:|:---: -![](https://avatars0.githubusercontent.com/u/22460123?s=100) | Jeffry Lum | [Github](https://github.com/j-lum/) | [Homepage](https://se.kasugano.moe) -![](https://avatars0.githubusercontent.com/u/1673303?s=100) | Damith C. Rajapakse | [Github](https://github.com/damithc/) | [Homepage](https://www.comp.nus.edu.sg/~damithch/) -# I would like to join this list. How can I help the project - -For more information, please refer to our [contributor's guide](https://oss-generic.github.io/process/). +# Contributors + +Display | Name | Github Profile | Homepage +---|:---:|:---:|:---: +![](https://avatars0.githubusercontent.com/u/22460123?s=100) | Jeffry Lum | [Github](https://github.com/j-lum/) | [Homepage](https://se.kasugano.moe) +![](https://avatars0.githubusercontent.com/u/1673303?s=100) | Damith C. Rajapakse | [Github](https://github.com/damithc/) | [Homepage](https://www.comp.nus.edu.sg/~damithch/) +# I would like to join this list. How can I help the project + +For more information, please refer to our [contributor's guide](https://oss-generic.github.io/process/). diff --git a/README.md b/README.md index 8715d4d915..9db1777ba0 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,24 @@ -# Duke project template - -This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it. - -## Setting up in Intellij - -Prerequisites: JDK 11, update Intellij to the most recent version. - -1. Open Intellij (if you are not in the welcome screen, click `File` > `Close Project` to close the existing project first) -1. Open the project into Intellij as follows: - 1. Click `Open`. - 1. Select the project directory, and click `OK`. - 1. If there are any further prompts, accept the defaults. -1. Configure the project to use **JDK 11** (not other versions) as explained in [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk).
- In the same dialog, set the **Project language level** field to the `SDK default` option. -3. After that, locate the `src/main/java/Duke.java` file, right-click it, and choose `Run Duke.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output: - ``` - Hello from - ____ _ - | _ \ _ _| | _____ - | | | | | | | |/ / _ \ - | |_| | |_| | < __/ - |____/ \__,_|_|\_\___| - ``` +# Duke project template + +This is a project template for a greenfield Java project. It's named after the Java mascot _Duke_. Given below are instructions on how to use it. + +## Setting up in Intellij + +Prerequisites: JDK 11, update Intellij to the most recent version. + +1. Open Intellij (if you are not in the welcome screen, click `File` > `Close Project` to close the existing project first) +1. Open the project into Intellij as follows: + 1. Click `Open`. + 1. Select the project directory, and click `OK`. + 1. If there are any further prompts, accept the defaults. +1. Configure the project to use **JDK 11** (not other versions) as explained in [here](https://www.jetbrains.com/help/idea/sdk.html#set-up-jdk).
+ In the same dialog, set the **Project language level** field to the `SDK default` option. +3. After that, locate the `src/main/java/Duke.java` file, right-click it, and choose `Run Duke.main()` (if the code editor is showing compile errors, try restarting the IDE). If the setup is correct, you should see something like the below as the output: + ``` + Hello from + ____ _ + | _ \ _ _| | _____ + | | | | | | | |/ / _ \ + | |_| | |_| | < __/ + |____/ \__,_|_|\_\___| + ``` diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..6209a3a9d1 --- /dev/null +++ b/build.gradle @@ -0,0 +1,64 @@ +plugins { + id 'java' + id 'application' + id 'checkstyle' + id 'com.github.johnrengelman.shadow' version '5.1.0' + id "org.openjfx.javafxplugin" version "0.0.10" +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.junit.jupiter:junit-jupiter:5.4.2' + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.5.0' + testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.5.0' + + String javaFxVersion = '11' + + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-base', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-controls', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-fxml', version: javaFxVersion, classifier: 'linux' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'win' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'mac' + implementation group: 'org.openjfx', name: 'javafx-graphics', version: javaFxVersion, classifier: 'linux' +} + +test { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed" + + showExceptions true + exceptionFormat "full" + showCauses true + showStackTraces true + showStandardStreams = false + } +} + +application { + mainClassName = "luke.Launcher" +} + +shadowJar { + archiveBaseName = "luke" + archiveClassifier = null +} + +checkstyle { + toolVersion = '8.29' +} + +run{ + standardInput = System.in + enableAssertions = true +} diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..6d69fcf10b --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,398 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml new file mode 100644 index 0000000000..42f14212c1 --- /dev/null +++ b/config/checkstyle/suppressions.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/data/luke.txt b/data/luke.txt new file mode 100644 index 0000000000..a94d9cc3d0 --- /dev/null +++ b/data/luke.txt @@ -0,0 +1 @@ +T,0,study diff --git a/docs/README.md b/docs/README.md index 8077118ebe..14c6b468fc 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,29 +1,73 @@ -# User Guide - -## Features - -### Feature-ABC - -Description of the feature. - -### Feature-XYZ - -Description of the feature. - -## Usage - -### `Keyword` - Describe action - -Describe the action and its outcome. - -Example of usage: - -`keyword (optional arguments)` - -Expected outcome: - -Description of the outcome. - -``` -expected output -``` +# User Guide + +## Features + +### Add Tasks + +Add tasks that needs to be done in no set time. + +### Add Deadlines + +Add deadlines that need to be completed by a certain date. + +### Add Events + +Add events that will take place at a certain date. + +## Usage + +### `help` - Get Help + +Returns a list of command keywords. + +### `list` - List Entries + +Returns the list of entries. + +### `todo` - Add Tasks + +Adds a task entry. + +Example of usage: + +`todo Service the car` + +`todo Take the snake for a walk` + +### `deadline` - Add Deadlines + +Adds a deadline entry. + +Example of usage: + +`deadline Get an internship / 2022-02-21` + +`deadline Get a job / 2023-03-14` + +### `done` - Finish Tasks/Deadlines/Events + +Marks an entry as done. + +Example of usage: + +`done 3` + +Marks the third entry as done. + +### `delete` - Delete Entry + +Deletes an entry. + +Example of usage: + +`done 2` + +Removes the second entry in the entry list. + +### `bye` - Say Bye to Luke + +Closes the application. + +###Credits + +Photos by Generated Photos diff --git a/docs/Ui.png b/docs/Ui.png new file mode 100644 index 0000000000..cd6421fde3 Binary files /dev/null and b/docs/Ui.png differ diff --git a/docs/_config.yml b/docs/_config.yml new file mode 100644 index 0000000000..cc35c1df2c --- /dev/null +++ b/docs/_config.yml @@ -0,0 +1 @@ +theme: jekyll-theme-modernist \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..f3d88b1c2f Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..b7c8c5dbf5 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.2-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000000..2fe81a7d95 --- /dev/null +++ b/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000000..62bd9b9cce --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,103 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/java/Duke.java b/src/main/java/Duke.java deleted file mode 100644 index 5d313334cc..0000000000 --- a/src/main/java/Duke.java +++ /dev/null @@ -1,10 +0,0 @@ -public class Duke { - public static void main(String[] args) { - String logo = " ____ _ \n" - + "| _ \\ _ _| | _____ \n" - + "| | | | | | | |/ / _ \\\n" - + "| |_| | |_| | < __/\n" - + "|____/ \\__,_|_|\\_\\___|\n"; - System.out.println("Hello from\n" + logo); - } -} diff --git a/src/main/java/luke/Deadline.java b/src/main/java/luke/Deadline.java new file mode 100644 index 0000000000..2217c1d892 --- /dev/null +++ b/src/main/java/luke/Deadline.java @@ -0,0 +1,71 @@ +package luke; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +/** + * Class that inherits from Entry to encapsulate Deadlines. + */ +public class Deadline extends Entry { + + private LocalDate deadline; + + /** + * Constructor for Deadline. + */ + public Deadline() { + super(); + this.deadline = LocalDate.now(); + } + + /** + * Constructor for Deadline. + * + * @param task Entry to be saved. + * @param deadline Deadline of entry. + * @throws LukeException Error thrown if deadline is in wrong format. + */ + public Deadline(String task, String deadline) throws LukeException { + super(task); + try { + this.deadline = LocalDate.parse(deadline); + } catch (DateTimeParseException e) { + this.deadline = LocalDate.now(); + throw LukeException.INVALID_DATE_FORMAT_EXCEPTION; + } + } + + /** + * Overrides Entry's saveString method. + * Returns string to be saved representing the Deadline. + * + * @return String to be saved representing the Deadline. + */ + @Override + public String saveString() { + return "D" + super.saveString() + "," + this.deadline; + } + + /** + * Overrides Entry's toString method. + * Returns String description of deadline. + * + * @return String representing Deadline. + */ + @Override + public String toString() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM dd yyyy"); + return "[D]" + super.toString() + "(by: " + this.deadline.format(formatter) + ")"; + } + + /** + * Returns true if Deadline is Empty. + * + * @return Boolean corresponding to Deadline's length. + */ + @Override + public boolean isEmpty() { + return super.isEmpty() && !deadline.isAfter(LocalDate.now()); + } +} \ No newline at end of file diff --git a/src/main/java/luke/DeadlineTest.java b/src/main/java/luke/DeadlineTest.java new file mode 100644 index 0000000000..d87f6f7ccf --- /dev/null +++ b/src/main/java/luke/DeadlineTest.java @@ -0,0 +1,27 @@ +package luke; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class DeadlineTest { + @Test + public void deadlineToStringTest() { + try { + Deadline deadline = new Deadline("testing-deadline", "2021-08-20"); + assertEquals("[D][ ] testing-deadline(by: Aug 20 2021)", deadline.toString()); + } catch (LukeException e) { + assertEquals(LukeException.INVALID_DATE_FORMAT_EXCEPTION.getMessage(), e.getMessage()); + } + } + + @Test + public void deadlineSaveStringTest() { + try { + Deadline deadline = new Deadline("testing-deadline", "2021-08-20"); + assertEquals("D,0,testing-deadline,2021-08-20", deadline.saveString()); + } catch (LukeException e) { + assertEquals(LukeException.INVALID_DATE_FORMAT_EXCEPTION.getMessage(), e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/luke/DialogBox.java b/src/main/java/luke/DialogBox.java new file mode 100644 index 0000000000..241bb09f0a --- /dev/null +++ b/src/main/java/luke/DialogBox.java @@ -0,0 +1,52 @@ +package luke; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; + +public class DialogBox extends HBox { + + private Label text; + private final ImageView DISPLAY_PICTURE; + + public DialogBox(Label l, ImageView iv) { + text = l; + DISPLAY_PICTURE = iv; + + text.setWrapText(true); + DISPLAY_PICTURE.setFitWidth(100.0); + DISPLAY_PICTURE.setFitHeight(100.0); + + this.setAlignment(Pos.TOP_RIGHT); + this.getChildren().addAll(text, DISPLAY_PICTURE); + } + + /** + * Flips the dialog box such that the ImageView is on the left and text on the right. + */ + private void flip() { + this.setAlignment(Pos.TOP_LEFT); + ObservableList tmp = FXCollections.observableArrayList(this.getChildren()); + FXCollections.reverse(tmp); + this.getChildren().setAll(tmp); + } + + public static DialogBox getUserDialog(Label l, ImageView iv) { + return new DialogBox(l, iv); + } + + public static DialogBox getLukeDialog(Label l, ImageView iv) { + var db = new DialogBox(l, iv); + db.flip(); + return db; + } + + public DialogBox padDialog(int padding) { + text.setAlignment(Pos.BASELINE_RIGHT); + return this; + } +} \ No newline at end of file diff --git a/src/main/java/luke/Entry.java b/src/main/java/luke/Entry.java new file mode 100644 index 0000000000..72352a207f --- /dev/null +++ b/src/main/java/luke/Entry.java @@ -0,0 +1,79 @@ +package luke; + +/** + * Abstract class that defines entry. + */ +public abstract class Entry { + private final String ENTRY; + + private boolean isDone; + + /** + * Constructor for Entry. + */ + Entry() { + this.ENTRY = ""; + this.isDone = false; + } + + /** + * Constructor for Entry with a value. + * + * @param value Entry value. + */ + Entry(String value) { + this.ENTRY = value; + this.isDone = false; + } + + /** + * Method to set the isDone status to true of an Entry. + * + * @return Boolean if successful operation. + */ + public boolean setDone() { + if (this.isDone) { + return false; + } else { + this.isDone = true; + return true; + } + } + + /** + * Returns true if Entry is Empty. + * + * @return Boolean corresponding to Entry's length. + */ + public boolean isEmpty() { + return this.ENTRY.isEmpty() || this.ENTRY.isBlank(); + } + + /** + * Returns the string to be saved representing the Entry. + * + * @return String to represent entry in memory. + */ + public String saveString() { + String isDoneString = "0"; + if (this.isDone) { + isDoneString = "1"; + } + return "," + isDoneString + "," + this.ENTRY; + } + + public boolean contains(String keyword) { + return this.ENTRY.contains(keyword); + } + + /** + * Overrides Object's toString method to return a description of Entry. + * + * @return String to represent Entry. + */ + @Override + public String toString() { + char isDoneDisplay = this.isDone ? 'X' : ' '; + return ("[" + isDoneDisplay + "] " + this.ENTRY); + } +} \ No newline at end of file diff --git a/src/main/java/luke/EntryList.java b/src/main/java/luke/EntryList.java new file mode 100644 index 0000000000..f202a12765 --- /dev/null +++ b/src/main/java/luke/EntryList.java @@ -0,0 +1,129 @@ +package luke; + +import java.util.ArrayList; + +/** + * Class that implements methods to store an Arraylist of Entries + */ +public class EntryList extends ArrayList { + + private int numberOfEntries; + + /** + * Constructor for EntryList. + */ + EntryList() { + super(); + this.numberOfEntries = super.size(); + } + + /** + * Prints all the Entries stored currently. + * + * @throws LukeException Exception if no entries are to be displayed. + */ + public String getEntries() throws LukeException { + StringBuilder entryList = new StringBuilder(); + if (numberOfEntries > 0) { + for (int i = 0; i < numberOfEntries; i++) { + entryList.append(super.get(i)).append("\n"); + } + return entryList.toString(); + } else { + throw new LukeException("No entries to display!"); + } + } + + /** + * Finds entries with given keyword. + * Searches for partial entries as well. + * + * @param keyword to search for. + * @param ui Ui to return info string. + * @return String of found entries. + */ + public String getFindEntry(String keyword, Ui ui) { + int len = super.size(); + int count = 1; + StringBuilder output = new StringBuilder(ui.getListMatches()); + for (int i = 0; i < len; i++) { + Entry currentEntry = super.get(i); + if (currentEntry.contains(keyword)) { + if (count == 1) { + output.append(ui.getFoundMatches()); + } + output.append(ui.getPrintEntry(currentEntry, count++)); + } + } + return output.toString(); + } + + /** + * Marks a given entry number as done. + * + * @param entryNumber The number of the entry to be marked. + * @return String for marked entry. + * @throws LukeException Error thrown when no entry exists for the given entryNumber. + */ + public String getEntryAsDone(int entryNumber) throws LukeException { + StringBuilder output = new StringBuilder(); + if (entryNumber > 0 && entryNumber <= numberOfEntries) { + if (super.get(entryNumber - 1).setDone()) { + output.append("Nice! I've marked this entry as done:"); + output.append("\t").append(super.get(entryNumber - 1)); + } else { + output.append("Entry is already marked as done!"); + } + return output.toString(); + } else { + throw new LukeException("There's no Entry corresponding to that Number!"); + } + } + + /** + * Adds given Entry to the List when adding from memory. + * + * @param entry Entry to be added. + */ + public void addEntry(Entry entry) { + super.add(numberOfEntries++, entry); + } + + /** + * Adds given Entry to the List. + * Prints Success Message if Successful. + * + * @param entry The Entry to be added. + * @param command The command corresponding to the Entry. + * @return String to show added Entry. + * @throws LukeException Error thrown if Entry is empty. + */ + public String getAddEntry(Entry entry, String command, Ui ui) throws LukeException { + StringBuilder output = new StringBuilder(); + if (!entry.isEmpty()){ + super.add(numberOfEntries++, entry); + output.append(ui.getAddEntry(entry, numberOfEntries)); + } else { + throw new LukeException("The " + command + " description can't be empty!"); + } + return output.toString(); + } + + /** + * Deletes entry by index. + * + * @param index Index of Entry to be deleted. + * @param ui UI Object to print deleted Entry message. + * @return String to show deleted entry. + * @throws LukeException Error thrown if no Entry corresponds to the index. + */ + public String getDeleteEntry(int index, Ui ui) throws LukeException { + if (super.isEmpty() || index < 1 || index > numberOfEntries) { + throw new LukeException("Duke can't find anything to delete!"); + } else { + Entry deletedEntry = super.remove(index - 1); + numberOfEntries--; + return ui.getDeletedEntry(deletedEntry); + } + } +} \ No newline at end of file diff --git a/src/main/java/luke/Event.java b/src/main/java/luke/Event.java new file mode 100644 index 0000000000..2255a4b1c2 --- /dev/null +++ b/src/main/java/luke/Event.java @@ -0,0 +1,70 @@ +package luke; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +/** + * Class that inherits from Entry to encapsulate Events. + */ +public class Event extends Entry{ + + private LocalDate event; + + /** + * Constructor for Event. + */ + Event() { + super(); + this.event = LocalDate.now(); + } + + /** + * Constructor for Event. + * + * @param task Entry Task to be saved. + * @param event Event details. + * @throws LukeException Error thrown if event format is incorrect. + */ + Event(String task, String event) throws LukeException { + super(task); + try { + this.event = LocalDate.parse(event); + } catch (DateTimeParseException e) { + this.event = LocalDate.now(); + throw LukeException.INVALID_DATE_FORMAT_EXCEPTION; + } + } + + /** + * Overrides Object's toString method. + * + * @return String representing Event. + */ + @Override + public String toString() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMM dd yyyy"); + return "[E]" + super.toString() + "(at: " + this.event.format(formatter) + ")"; + } + + /** + * Overrides Entry's saveString method. + * Returns string to be saved representing the Event. + * + * @return String to be saved representing the Event. + */ + @Override + public String saveString() { + return "E" + super.saveString() + "," + this.event; + } + + /** + * Returns true if Event is Empty. + * + * @return Boolean corresponding to Event's length. + */ + @Override + public boolean isEmpty() { + return super.isEmpty() || !event.isAfter(LocalDate.now()); + } +} \ No newline at end of file diff --git a/src/main/java/luke/EventTest.java b/src/main/java/luke/EventTest.java new file mode 100644 index 0000000000..1eb2a7bc8f --- /dev/null +++ b/src/main/java/luke/EventTest.java @@ -0,0 +1,27 @@ +package luke; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class EventTest { + @Test + public void eventToStringTest() { + try { + Event event = new Event("testing-event", "2021-08-20"); + assertEquals("[E][ ] testing-event(at: Aug 20 2021)", event.toString()); + } catch (LukeException e) { + assertEquals(LukeException.INVALID_DATE_FORMAT_EXCEPTION.getMessage(), e.getMessage()); + } + } + + @Test + public void eventSaveStringTest() { + try { + Event event = new Event("testing-event", "2021-08-20"); + assertEquals("E,0,testing-event,2021-08-20", event.saveString()); + } catch (LukeException e) { + assertEquals(LukeException.INVALID_DATE_FORMAT_EXCEPTION.getMessage(), e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/luke/Launcher.java b/src/main/java/luke/Launcher.java new file mode 100644 index 0000000000..af1af67312 --- /dev/null +++ b/src/main/java/luke/Launcher.java @@ -0,0 +1,9 @@ +package luke; + +import javafx.application.Application; + +public class Launcher { + public static void main(String[] args) { + Application.launch(Luke.class, args); + } +} \ No newline at end of file diff --git a/src/main/java/luke/Luke.java b/src/main/java/luke/Luke.java new file mode 100644 index 0000000000..c1329e4bf1 --- /dev/null +++ b/src/main/java/luke/Luke.java @@ -0,0 +1,271 @@ +package luke; + +import java.util.ArrayList; +import java.util.Objects; +import java.util.List; + +import javafx.application.Application; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import javafx.scene.paint.Color; +import javafx.scene.text.Font; +import javafx.stage.Stage; + +/** + * Class to encapsulate Luke Chatbot. + */ +public class Luke extends Application { + + private final Ui lukeUi; + private final Parser lukeParser; + private final Storage lukeStorage; + private EntryList entries; + + private final Image USER = new Image(Objects.requireNonNull(this.getClass().getResourceAsStream("/images/UserImage.jpg"))); + private final Image LUKE = new Image(Objects.requireNonNull(this.getClass().getResourceAsStream("/images/LukeImage.jpg"))); + + public static final String TERMINATION_COMMAND = "bye"; + public static final String LIST_ENTRIES_COMMAND = "list"; + public static final String MARK_ENTRY_DONE_COMMAND = "done"; + public static final String DELETE_ENTRY_COMMAND = "delete"; + public static final String TODO_COMMAND = "todo"; + public static final String EVENT_COMMAND = "event"; + public static final String DEADLINE_COMMAND = "deadline"; + public static final String FIND_COMMAND = "find"; + public static final String HELP_COMMAND = "help"; + public static final List commands = + List.of(TERMINATION_COMMAND, LIST_ENTRIES_COMMAND, + MARK_ENTRY_DONE_COMMAND, DELETE_ENTRY_COMMAND, + TODO_COMMAND, EVENT_COMMAND, DEADLINE_COMMAND, + FIND_COMMAND, HELP_COMMAND); + + /** + * Constructor for Luke Chatbot. + */ + + public Luke() { + this.lukeUi = new Ui(); + this.lukeParser = new Parser(); + this.lukeStorage = new Storage(); + try { + this.entries = this.lukeStorage.readData(); + } catch (LukeException e) { + this.lukeUi.handleLoadingError(e); + this.entries = new EntryList(); + } + } + + @Override + public void start(Stage stage) { + VBox lukeContainer = new VBox(); + TextField lukeInput = new TextField(); + ScrollPane scrollPane = configureScrollPane(lukeContainer); + Button sendButton = configureSendButton(lukeContainer, lukeInput, stage); + VBox sideLabel = configureSideLabel(); + HBox topRow = configureTopRow(scrollPane, sideLabel); + AnchorPane container = configureAnchorPane(scrollPane, sendButton, lukeInput, topRow); + Scene scene = new Scene(container); + configureLukeContainerAndLukeInput(lukeContainer, lukeInput, scrollPane, stage); + configureStage(scene, stage); + stage.show(); + } + + private void configureStage(Scene scene, Stage stage) { + stage.setTitle("Luke"); + stage.setResizable(false); + stage.setMinHeight(500.0); + stage.setMinWidth(700.0); + stage.setScene(scene); + } + + private Button configureSendButton(VBox lukeContainer, TextField lukeInput, Stage stage) { + Button sendButton = new Button(); + sendButton.setPrefWidth(200.0); + sendButton.setOnMouseClicked((event) -> { + handleUserInput(lukeContainer, lukeInput, stage); + }); + return sendButton; + } + + private AnchorPane configureAnchorPane(ScrollPane scrollPane, Button sendButton, TextField lukeInput, HBox topRow) { + AnchorPane anchor = new AnchorPane(); + AnchorPane.setTopAnchor(scrollPane, 1.0); + AnchorPane.setBottomAnchor(sendButton, 1.0); + AnchorPane.setRightAnchor(sendButton, 1.0); + AnchorPane.setLeftAnchor(lukeInput , 1.0); + AnchorPane.setBottomAnchor(lukeInput, 1.0); + anchor.getChildren().addAll(topRow, lukeInput, sendButton); + anchor.setPrefSize(700.0, 500.0); + anchor.setStyle("-fx-background-color: #13223b"); + return anchor; + } + + private void configureLukeContainerAndLukeInput( + VBox lukeContainer, TextField lukeInput, ScrollPane scrollPane, Stage stage) { + lukeContainer.setPrefHeight(Region.USE_COMPUTED_SIZE); + lukeContainer.heightProperty().addListener((observable -> scrollPane.setVvalue(1.0))); + + lukeInput.setPrefWidth(496.0); + lukeInput.setOnAction((event) -> { + handleUserInput(lukeContainer, lukeInput, stage); + }); + } + + /** + * Iteration 2: + * Creates two dialog boxes, one echoing USER input and the other containing Luke's reply and then appends them to + * the dialog container. Clears the USER input after processing. + */ + private void handleUserInput(VBox dialogContainer, TextField userInput, Stage stage) { + Label userText = new Label(userInput.getText()); + Label lukeText = new Label(getResponse(userInput.getText(), stage)); + dialogContainer.getChildren().addAll( + DialogBox.getUserDialog(userText, new ImageView(USER)).padDialog(10), + DialogBox.getLukeDialog(lukeText, new ImageView(LUKE)).padDialog(10) + ); + userInput.clear(); + } + + /** + * Function to get response from Luke. + */ + private String getResponse(String input, Stage stage) { + if (input.equals(TERMINATION_COMMAND)) { + stage.close(); + return ""; + } else { + return run(input); + } + } + + private HBox configureTopRow(ScrollPane scrollPane, VBox sideLabel) { + HBox topRow = new HBox(); + topRow.getChildren().addAll(scrollPane, sideLabel); + return topRow; + } + + private ScrollPane configureScrollPane(VBox lukeContainer) { + ScrollPane scrollPane = new ScrollPane(); + scrollPane.setContent(lukeContainer); + scrollPane.setPrefSize(500.0, 473.3); + scrollPane.setHbarPolicy(ScrollPane.ScrollBarPolicy.NEVER); + scrollPane.setVbarPolicy(ScrollPane.ScrollBarPolicy.ALWAYS); + scrollPane.setVvalue(1.0); + scrollPane.setFitToWidth(true); + return scrollPane; + } + + private VBox configureSideLabel() { + Label title = new Label(" LUKE "); + title.setTextFill(Color.color(1,1,1)); + title.setFont(new Font("Georgia", 45)); + title.setStyle("-fx-start-margin: 100"); + + Label description = new Label(this.toString()); + description.setTextFill(Color.color(1,1,1)); + description.setFont(new Font("Georgia", 24)); + description.setStyle("-fx-start-margin: 100"); + + VBox sideLabel = new VBox(); + sideLabel.getChildren().addAll(title, description); + return sideLabel; + } + + /** + * Method containing Logic for processed Input. + * + * @param parsedTerms ArrayList containing the command, entry and timing (in that order). + * @throws LukeException If ProcessedInput is invalid/incoherent. + */ + private String processInput(ArrayList parsedTerms) throws LukeException { + if (parsedTerms.size() < 3) { + throw new LukeException("Luke cannot understand your entry :/\nType 'help' for assistance"); + } + String command = parsedTerms.get(0); + String entry = parsedTerms.get(1); + String timing = parsedTerms.get(2); + String output = ""; + + //Process Command + switch(command) { + case LIST_ENTRIES_COMMAND: + output = entries.getEntries(); + break; + + case MARK_ENTRY_DONE_COMMAND: + output = entries.getEntryAsDone(Integer.parseInt(entry)); + break; + + case TODO_COMMAND: + output = entries.getAddEntry(new Todo(entry), command, this.lukeUi); + break; + + case EVENT_COMMAND: + output = entries.getAddEntry(new Event(entry, timing), command, this.lukeUi); + break; + + case DEADLINE_COMMAND: + output = entries.getAddEntry(new Deadline(entry, timing), command, this.lukeUi); + break; + + case DELETE_ENTRY_COMMAND: + output = entries.getDeleteEntry(Integer.parseInt(entry), this.lukeUi); + break; + + case FIND_COMMAND: + output = entries.getFindEntry(entry, this.lukeUi); + break; + + case HELP_COMMAND: + output = this.lukeUi.getCommands(); + break; + + default: + throw new LukeException("Sorry! Luke can't understand that\nType 'help' for assistance"); + } + return output; + } + + + public String run(String input) { + String output = ""; + if (input.equals("bye")) { + return this.lukeUi.getGoodByeUser(); + } + try { + ArrayList parsedTerms = this.lukeParser.parseInput(input); + output = this.processInput(parsedTerms); + this.lukeStorage.saveEntries(this.entries); + } catch (LukeException e) { + output = this.lukeUi.getParsingError(e); + } catch (AssertionError e) { + output = this.lukeUi.getAssertingError(e); + } + return output; + } + + /** + * Overrides the Object's toString method. + * + * @return String description of Luke. + */ + @Override + public String toString() { + return " Hello! I'm Luke, \n" + + " your slightly useful \n" + + " personal assistant! \n" + + " I can help you remember \n" + + " tasks and other things :D "; + } +} + + diff --git a/src/main/java/luke/LukeException.java b/src/main/java/luke/LukeException.java new file mode 100644 index 0000000000..71f9884d51 --- /dev/null +++ b/src/main/java/luke/LukeException.java @@ -0,0 +1,31 @@ +package luke; + +public class LukeException extends Exception { + + public static final LukeException INVALID_DATE_FORMAT_EXCEPTION = + new LukeException("Sorry I can't read that! Enter dates in YYYY-MM-DD format"); + + /** + * Constructor for LukeException. + */ + LukeException() { + super(); + } + + /** + * Constructor for LukeException. + */ + LukeException(String argument) { + super(argument); + } + + /** + * Overrides Object's toString method. + * + * @return String representing Exception. + */ + @Override + public String toString() { + return super.toString(); + } +} \ No newline at end of file diff --git a/src/main/java/luke/LukeTest.java b/src/main/java/luke/LukeTest.java new file mode 100644 index 0000000000..73e5e63a48 --- /dev/null +++ b/src/main/java/luke/LukeTest.java @@ -0,0 +1,14 @@ +package luke; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class LukeTest { + @Test + public void lukeTest() { + Luke luke = new Luke(); + assertEquals("Hello! I'm Luke, your slightly useful personal assistant!\n" + + "I can help you remember tasks and other things :D", luke.toString()); + } +} \ No newline at end of file diff --git a/src/main/java/luke/Parser.java b/src/main/java/luke/Parser.java new file mode 100644 index 0000000000..0d38f629b8 --- /dev/null +++ b/src/main/java/luke/Parser.java @@ -0,0 +1,105 @@ +package luke; + +import java.util.ArrayList; +import java.util.Arrays; + +/** + * Class that implements method to parse user inputs. + */ +public class Parser { + + /** + * Constructor for Parser. + */ + Parser() {} + + /** + * Returns parsed input in an Arraylist. + * + * @param input String input from user. + * @return ArrayList of command, entry, timing (if present). + * @throws LukeException Error is thrown if Command is invalid. + */ + public ArrayList parseInput(String input) throws LukeException, AssertionError { + ArrayList terms = new ArrayList<>(); + this.parseString(input, terms); + String command = ""; + if (terms.isEmpty()) { + throw new LukeException("Luke can't find any commands :(("); + } else { + command = terms.remove(0); + } + assert isValidCommand(command) : "Invalid Command! Luke can't understand what you mean :(("; + this.parseEntry(terms); + String entry = terms.isEmpty() ? "" : terms.remove(0); + String timing = terms.isEmpty() ? "" : this.parseTiming(terms); + return new ArrayList<>(Arrays.asList(command, entry, timing)); + } + + private boolean isValidCommand(String command) { + boolean isValid = false; + for (String knownCommand : Luke.commands) { + if (knownCommand.equals(command)) { + isValid = true; + break; + } + } + return isValid; + } + + private void parseString(String input, ArrayList terms) throws AssertionError { + // Function to store all terms in input as separate Strings (separated by space in the input) + int length = input.length(); + assert length >= 1 : "Invalid String Input"; + StringBuilder currentWord = new StringBuilder(); + for (int i = 0; i < length; i++) { + char currentChar = input.charAt(i); + if (currentChar == ' ') { + terms.add(currentWord.toString()); + currentWord.setLength(0); + } else { + currentWord.append(currentChar); + } + } + terms.add(currentWord.toString()); + } + + private String parseTiming(ArrayList terms) { + if (terms.get(0).startsWith("/")) { + terms.remove(0); + StringBuilder timing = new StringBuilder(); + int len = terms.size(); + for (int i = 0; i < len; i++) { + if (i != len - 1){ + timing.append(terms.get(i)).append(" "); + } else { + timing.append(terms.get(i)); + } + } + return timing.toString(); + } else { + return ""; + } + } + + private void parseEntry(ArrayList terms) { + boolean isTermsNotEmpty = !terms.isEmpty(); + if (isTermsNotEmpty) { + StringBuilder entry = new StringBuilder(); + // Combine All Strings Until End of List or '/' character is found + ArrayList termsCopy = new ArrayList<>(terms); + for (String term : termsCopy) { + if (term.startsWith("/")) { + String entryDesc = entry.toString(); + terms.add(0, entryDesc.substring(0, entryDesc.length()-1)); + return; + } else { + entry.append(term).append(' '); + terms.remove(0); + } + } + String entryDesc = entry.toString(); + terms.add(0, entryDesc.substring(0, entryDesc.length()-1)); + } + } +} \ No newline at end of file diff --git a/src/main/java/luke/Storage.java b/src/main/java/luke/Storage.java new file mode 100644 index 0000000000..f75f9cedf1 --- /dev/null +++ b/src/main/java/luke/Storage.java @@ -0,0 +1,110 @@ +package luke; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileWriter; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Scanner; + +/** + * Class that implements methods to store Entries. + */ +public class Storage { + + private final String PROJECT_ROOT = System.getProperty("user.dir"); + private final Path DATA_DIRECTORY_PATH = Paths.get(PROJECT_ROOT, "data"); + + private Path dataPath; + private File lukeData; + + /** + * Constructor for Storage. + */ + Storage() { + this.dataPath = DATA_DIRECTORY_PATH.resolve("luke.txt"); + this.lukeData = new File(dataPath.toString()); + } + + /** + * Saves all Entries from entries into memory. + * + * @param entries EntryList of all entries to be saved. + * @throws LukeException Error thrown if data file is corrupted/missing. + */ + public void saveEntries(EntryList entries) throws LukeException { + Path dataPath = DATA_DIRECTORY_PATH.resolve("luke.txt"); + File lukeData = new File(dataPath.toString()); + try { + FileWriter fw = new FileWriter(lukeData); + BufferedWriter lukeWriter = new BufferedWriter(fw); + for (Entry entry : entries){ + String nextEntry = entry.saveString(); + lukeWriter.write(nextEntry); + lukeWriter.write("\n"); + } + lukeWriter.close(); + fw.close(); + } catch (IOException e) { + throw new LukeException("Luke's data file is corrupted/missing! Can't be saved"); + } + } + + /** + * Returns EntryList with data from memory. + * + * @return EntryList containing all data from memory. + * @throws LukeException Error thrown if file is missing/corrupted. + */ + public EntryList readData() throws LukeException { + EntryList entries = new EntryList(); + try { + return addEntriesFromFile(entries); + } catch (FileNotFoundException e) { + try { + if (!(lukeData.getParentFile().mkdir()) && !(lukeData.createNewFile())) { + throw new LukeException("Uh-Oh! Your data can't be stored (I can't create a directory) :/"); + } else { + return new EntryList(); + } + } catch (IOException err) { + throw new LukeException("Uh-Oh! Your data can't be stored (IO Error) :/"); + } + } + } + + private EntryList addEntriesFromFile(EntryList entries) throws LukeException, FileNotFoundException { + Scanner fileScanner = new Scanner(lukeData).useDelimiter("[,\n]"); + while (fileScanner.hasNext()) { + String entryType = fileScanner.next(); + boolean isDone = Integer.parseInt(fileScanner.next()) == 1; + String entryData = fileScanner.next(); + Entry nextEntry = new Todo(""); + boolean hasNextEntry = true; + switch (entryType) { + case "T": + nextEntry = new Todo(entryData); + break; + case "D": + nextEntry = new Deadline(entryData, fileScanner.next()); //fileScanner.next() will contain timing + break; + case "E": + nextEntry = new Event(entryData, fileScanner.next()); //fileScanner.next() will contain timing + break; + default: + //Corrupted Entry Case + hasNextEntry = false; + break; + } + if (hasNextEntry) { + if (isDone) { + nextEntry.setDone(); + } + entries.addEntry(nextEntry); + } + } + return entries; + } +} \ No newline at end of file diff --git a/src/main/java/luke/Todo.java b/src/main/java/luke/Todo.java new file mode 100644 index 0000000000..14128e1d08 --- /dev/null +++ b/src/main/java/luke/Todo.java @@ -0,0 +1,44 @@ +package luke; + +/** + * Class that inherits from Entry to encapsulate Todos. + */ +public class Todo extends Entry { + + /** + * Constructor for Todo. + */ + Todo() { + super(); + } + + /** + * Constructor for Todo. + * + * @param task The String that will be stored as a Todo. + */ + Todo(String task) { + super(task); + } + + /** + * Overrides Entry's toString method. + * + * @return String representation of Todo. + */ + @Override + public String toString() { + return "[T]" + super.toString(); + } + + /** + * Overrides Entry's saveString method. + * Returns string to be saved representing the Todo. + * + * @return String to be saved representing the Todo. + */ + @Override + public String saveString() { + return "T" + super.saveString(); + } +} \ No newline at end of file diff --git a/src/main/java/luke/Ui.java b/src/main/java/luke/Ui.java new file mode 100644 index 0000000000..d95a8237ef --- /dev/null +++ b/src/main/java/luke/Ui.java @@ -0,0 +1,150 @@ +package luke; + +import java.util.Objects; + +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.VBox; +import javafx.scene.Scene; +import javafx.stage.Stage; + +/** + * Class that implements methods to present Luke. + */ +public class Ui { + + private final Image USER = + new Image(Objects.requireNonNull(this.getClass().getResourceAsStream("/images/UserImage.jpg"))); + private final Image LUKE = + new Image(Objects.requireNonNull(this.getClass().getResourceAsStream("/images/LukeImage.jpg"))); + + /** + * Constructor to create a UI object. + */ + Ui() { + } + + private final static String LOGO = " _ _ \n" + + "| | _ _| | _____ \n" + + "| | | | | | |/ / _ \\\n" + + "| |___| |_| | < __/\n" + + "|____/ \\__,_|_|\\_\\___|\n"; + + private final static String HORIZONTAL_DIVIDE = + "___________________________________________________________________"; + + /** + * Prints out a welcome message. + */ + public void welcomeUser() { + System.out.println("Hello from\n" + LOGO); + System.out.println("I'm Luke, your slightly useful personal assistant!" + + "What can Luke do for you today?"); + System.out.println(HORIZONTAL_DIVIDE); + } + + /** + * Prints out the added entry. + * + * @param entry The newly-added entry. + * @param id The #id of the newly-added entry. + */ + public String getAddEntry(Entry entry, int id) { + return "I've added this entry to your list!" + + this.getPrintEntry(entry, id); + } + + public String getListMatches() { + return "Looking for matching tasks in your list..."; + } + + public String getFoundMatches() { + return "Here are the matching tasks in your list: "; + } + + /** + * Prints out given entry. + * + * @param entry Entry to be printed. + * @param id ID of entry to be printed. + */ + public void printEntry(Entry entry, int id) { + System.out.println("\t" + id + "." + entry); + } + + /** + * Returns given entry. + * + * @param entry Entry to be printed. + * @param id ID of entry to be printed. + * @return String for given entry. + */ + public String getPrintEntry(Entry entry, int id) { + return "\t" + id + "." + entry; + } + + /** + * Prints out farewell message. + * + * @return String to say farewell to USER. + */ + public String getGoodByeUser() { + return "Bye! Hope to see you again soon :D"; + } + + /** + * Prints out the Error Message received when Parsing. + * + * @param parsingError Error encountered while parsing input. + */ + public String getParsingError(LukeException parsingError) { + return parsingError.getMessage(); + } + + /** + * Prints out the Error Message received in Assertions when Parsing. + * + * @param assertError Error encountered while parsing input. + */ + public String getAssertingError(AssertionError assertError) { + return assertError.getMessage(); + } + + /** + * Returns all the commands in one large String. + * + * @return String containing all commands Duke uses. + */ + public String getCommands() { + StringBuilder helpPage = new StringBuilder("These are the available commands:\n"); + for (String command : Luke.commands) { + helpPage.append(command).append("\n"); + } + return helpPage.toString(); + } + + + /** + * Prints out the Error Message received when loading data from memory. + * + * @param loadingError Error encountered while loading data from memory. + */ + public void handleLoadingError(LukeException loadingError) { + System.out.println(loadingError.getMessage()); + System.out.println(HORIZONTAL_DIVIDE); + } + + /** + * Prints out entry just after deletion. + * + * @param deletedEntry Entry that has just been deleted. + */ + public String getDeletedEntry(Entry deletedEntry) { + return "Removed entry\n" + deletedEntry; + } +} \ No newline at end of file diff --git a/src/main/resources/images/LukeImage.jpg b/src/main/resources/images/LukeImage.jpg new file mode 100644 index 0000000000..18b37c0b2b Binary files /dev/null and b/src/main/resources/images/LukeImage.jpg differ diff --git a/src/main/resources/images/UserImage.jpg b/src/main/resources/images/UserImage.jpg new file mode 100644 index 0000000000..1d5c15ada7 Binary files /dev/null and b/src/main/resources/images/UserImage.jpg differ diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 657e74f6e7..7a4cb583fc 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,7 +1,55 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - +Hello from + _ _ +| | _ _| | _____ +| | | | | | |/ / _ \ +| |___| |_| | < __/ +|____/ \__,_|_|\_\___| + + ____________________________________________________________ + Hello! I'm Luke, your slightly useful personal assistant! + What can I do for you, my liege? + Type 'list' to show previous inputs + Type 'todo TASK' to indicate that TASK has to be done + Type 'deadline TASK /by DATE/TIME' to indicate that TASK has to be done by DATE/TIME + Type 'event TASK /at DATE/TIME PERIOD' to indicate that TASK occurs at DATE/TIME PERIOD + Type 'done #' to indicate that task # has been done + Type 'bye' to end + ____________________________________________________________ + ____________________________________________________________ + Got it. I've added this task: + [T][ ] borrow book + Now you have 1 tasks in the list. + ____________________________________________________________ + ____________________________________________________________ + Got it. I've added this task: + [D][ ] return book (by: Sunday) + Now you have 2 tasks in the list. + ____________________________________________________________ + ____________________________________________________________ + Got it. I've added this task: + [E][ ] project meeting (at: Mon 2-4pm) + Now you have 3 tasks in the list. + ____________________________________________________________ + ____________________________________________________________ + Here are the tasks in your list: + 1.[T][ ] borrow book + 2.[D][ ] return book (by: Sunday) + 3.[E][ ] project meeting (at: Mon 2-4pm) + ____________________________________________________________ + ____________________________________________________________ + Nice! I've marked this task as done: + [D][X] return book (by: Sunday) + ____________________________________________________________ + ____________________________________________________________ + Nice! I've marked this task as done: + [E][X] project meeting (at: Mon 2-4pm) + ____________________________________________________________ + ____________________________________________________________ + Here are the tasks in your list: + 1.[T][ ] borrow book + 2.[D][X] return book (by: Sunday) + 3.[E][X] project meeting (at: Mon 2-4pm) + ____________________________________________________________ + ____________________________________________________________ + Bye! Talk again sometime! + ____________________________________________________________ \ No newline at end of file diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index e69de29bb2..361a93c834 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -0,0 +1,8 @@ +todo borrow book +deadline return book /by Sunday +event project meeting /at Mon 2-4pm +list +done 2 +done 3 +list +bye diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat index 0873744649..918e1513c3 100644 --- a/text-ui-test/runtest.bat +++ b/text-ui-test/runtest.bat @@ -1,21 +1,21 @@ -@ECHO OFF - -REM create bin directory if it doesn't exist -if not exist ..\bin mkdir ..\bin - -REM delete output from previous run -if exist ACTUAL.TXT del ACTUAL.TXT - -REM compile the code into the bin folder -javac -cp ..\src\main\java -Xlint:none -d ..\bin ..\src\main\java\*.java -IF ERRORLEVEL 1 ( - echo ********** BUILD FAILURE ********** - exit /b 1 -) -REM no error here, errorlevel == 0 - -REM run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT -java -classpath ..\bin Duke < input.txt > ACTUAL.TXT - -REM compare the output to the expected output -FC ACTUAL.TXT EXPECTED.TXT +@ECHO OFF + +REM create bin directory if it doesn't exist +if not exist ..\bin mkdir ..\bin + +REM delete output from previous run +if exist ACTUAL.TXT del ACTUAL.TXT + +REM compile the code into the bin folder +javac -cp ..\src\main\java -Xlint:none -d ..\bin ..\src\main\java\Duke.java +IF ERRORLEVEL 1 ( + echo ********** BUILD FAILURE ********** + exit /b 1 +) +REM no error here, errorlevel == 0 + +REM run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT +java -classpath ..\bin Duke < input.txt > ACTUAL.TXT + +REM compare the output to the expected output +FC ACTUAL.TXT EXPECTED.TXT diff --git a/text-ui-test/runtest.sh b/text-ui-test/runtest.sh old mode 100644 new mode 100755 index c9ec870033..cf8a36c1c2 --- a/text-ui-test/runtest.sh +++ b/text-ui-test/runtest.sh @@ -1,38 +1,38 @@ -#!/usr/bin/env bash - -# create bin directory if it doesn't exist -if [ ! -d "../bin" ] -then - mkdir ../bin -fi - -# delete output from previous run -if [ -e "./ACTUAL.TXT" ] -then - rm ACTUAL.TXT -fi - -# compile the code into the bin folder, terminates if error occurred -if ! javac -cp ../src/main/java -Xlint:none -d ../bin ../src/main/java/*.java -then - echo "********** BUILD FAILURE **********" - exit 1 -fi - -# run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT -java -classpath ../bin Duke < input.txt > ACTUAL.TXT - -# convert to UNIX format -cp EXPECTED.TXT EXPECTED-UNIX.TXT -dos2unix ACTUAL.TXT EXPECTED-UNIX.TXT - -# compare the output to the expected output -diff ACTUAL.TXT EXPECTED-UNIX.TXT -if [ $? -eq 0 ] -then - echo "Test result: PASSED" - exit 0 -else - echo "Test result: FAILED" - exit 1 +#!/usr/bin/env bash + +# create bin directory if it doesn't exist +if [ ! -d "../bin" ] +then + mkdir ../bin +fi + +# delete output from previous run +if [ -e "./ACTUAL.TXT" ] +then + rm ACTUAL.TXT +fi + +# compile the code into the bin folder, terminates if error occurred +if ! javac -cp ../src/main/java -Xlint:none -d ../bin ../src/main/java/*.java +then + echo "********** BUILD FAILURE **********" + exit 1 +fi + +# run the program, feed commands from input.txt file and redirect the output to the ACTUAL.TXT +java -classpath ../bin Duke < input.txt > ACTUAL.TXT + +# convert to UNIX format +cp EXPECTED.TXT EXPECTED-UNIX.TXT +dos2unix ACTUAL.TXT EXPECTED-UNIX.TXT + +# compare the output to the expected output +diff ACTUAL.TXT EXPECTED-UNIX.TXT +if [ $? -eq 0 ] +then + echo "Test result: PASSED" + exit 0 +else + echo "Test result: FAILED" + exit 1 fi \ No newline at end of file