From 68c58c12c1d631736a4b25500248a2956171496f Mon Sep 17 00:00:00 2001 From: damithc Date: Mon, 25 May 2020 00:58:18 +0800 Subject: [PATCH 01/71] Add Gradle support --- build.gradle | 41 +++++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 58695 bytes gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 183 +++++++++++++++++++++++ gradlew.bat | 103 +++++++++++++ text-ui-test/runtest.sh | 0 6 files changed, 332 insertions(+) create mode 100644 build.gradle create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat mode change 100644 => 100755 text-ui-test/runtest.sh diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..885198fcfa --- /dev/null +++ b/build.gradle @@ -0,0 +1,41 @@ +plugins { + id 'java' + id 'application' + id 'com.github.johnrengelman.shadow' version '5.1.0' +} + +repositories { + mavenCentral() +} + +dependencies { + 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' +} + +test { + useJUnitPlatform() + + testLogging { + events "passed", "skipped", "failed" + + showExceptions true + exceptionFormat "full" + showCauses true + showStackTraces true + showStandardStreams = false + } +} + +application { + mainClassName = "seedu.duke.Duke" +} + +shadowJar { + archiveBaseName = "duke" + archiveClassifier = null +} + +run{ + standardInput = System.in +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..f3d88b1c2faf2fc91d853cd5d4242b5547257070 GIT binary patch literal 58695 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 literal 0 HcmV?d00001 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/text-ui-test/runtest.sh b/text-ui-test/runtest.sh old mode 100644 new mode 100755 From 03523ec89e823ff5457b647df22a4e395641da7a Mon Sep 17 00:00:00 2001 From: lzq Date: Sat, 5 Aug 2023 15:24:02 +0800 Subject: [PATCH 02/71] Bump gradle and lib version --- build.gradle | 9 +- gradle/wrapper/gradle-wrapper.jar | Bin 58695 -> 63375 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 281 ++++++++++++++--------- gradlew.bat | 195 ++++++++-------- 5 files changed, 273 insertions(+), 216 deletions(-) diff --git a/build.gradle b/build.gradle index 885198fcfa..a388517ae1 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ plugins { id 'java' id 'application' - id 'com.github.johnrengelman.shadow' version '5.1.0' + id 'com.github.johnrengelman.shadow' version '7.1.2' } repositories { @@ -9,8 +9,8 @@ repositories { } dependencies { - 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' + testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.10.0' + testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.10.0' } test { @@ -28,12 +28,13 @@ test { } application { - mainClassName = "seedu.duke.Duke" + mainClass.set("seedu.duke.Duke") } shadowJar { archiveBaseName = "duke" archiveClassifier = null + dependsOn("distZip", "distTar") } run{ diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index f3d88b1c2faf2fc91d853cd5d4242b5547257070..033e24c4cdf41af1ab109bc7f253b2b887023340 100644 GIT binary patch delta 43723 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 delta 38942 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b7c8c5dbf5..66c01cfeba 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.2-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 2fe81a7d95..fcb6fca147 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,78 +17,110 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # 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 +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac 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"' +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # 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 - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | 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" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -97,87 +129,120 @@ 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. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + 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 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 +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac 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 +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # 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 +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # 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\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg 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" +# 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"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 62bd9b9cce..6689b85bee 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,103 +1,92 @@ -@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 +@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=. +@rem This is normally unused +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% equ 0 goto execute + +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 execute + +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 + +: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 %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 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! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega From 81a9c537afe8492675eb253968939364d65db091 Mon Sep 17 00:00:00 2001 From: aureliony <39163684+aureliony@users.noreply.github.com> Date: Tue, 16 Jul 2024 23:35:06 +0800 Subject: [PATCH 03/71] build.gradle: Prevent generating a second JAR file In build.gradle, the dependencies on distZip and/or distTar causes the shadowJar task to generate a second JAR file for which the mainClass.set("seedu.duke.Duke") does not take effect. Hence, this additional JAR file cannot be run. For this product, there is no need to generate a second JAR file to begin with. Let's remove this dependency from the build.gradle to prevent the shadowJar task from generating the extra JAR file. --- build.gradle | 1 - 1 file changed, 1 deletion(-) diff --git a/build.gradle b/build.gradle index a388517ae1..c361f9811e 100644 --- a/build.gradle +++ b/build.gradle @@ -34,7 +34,6 @@ application { shadowJar { archiveBaseName = "duke" archiveClassifier = null - dependsOn("distZip", "distTar") } run{ From 54effa811955ccc162f64cf7c053e9505d968b05 Mon Sep 17 00:00:00 2001 From: underscoregt Date: Wed, 28 Jan 2026 18:03:37 +0800 Subject: [PATCH 04/71] Delete Duke.java --- src/main/java/Duke.java | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 src/main/java/Duke.java 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); - } -} From 5e12392c7e0e3ffa78d2f4bff3e1c0e995b819cb Mon Sep 17 00:00:00 2001 From: underscoregt Date: Wed, 28 Jan 2026 18:05:14 +0800 Subject: [PATCH 05/71] Update .gitignore Add *.class to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 2873e189e1..b50afce371 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,5 @@ bin/ /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT + +*.class \ No newline at end of file From 6c6bbb432e5c9073d621b3289f1e5e6f99bf4bf4 Mon Sep 17 00:00:00 2001 From: underscoregt Date: Wed, 28 Jan 2026 18:05:40 +0800 Subject: [PATCH 06/71] Create Amia.java Create chatbot --- src/main/java/Amia.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/main/java/Amia.java diff --git a/src/main/java/Amia.java b/src/main/java/Amia.java new file mode 100644 index 0000000000..3065c62e7d --- /dev/null +++ b/src/main/java/Amia.java @@ -0,0 +1,14 @@ +public class Amia { + public static void main(String[] args) { + start(); + exit(); + } + + public static void start() { + System.out.println(" Hello! I'm Amia!\nWhat can I do for you?"); + } + + public static void exit() { + System.out.println("Bye!"); + } +} From 57f574cafb9bfff773b0b64601d39c2c73d0657f Mon Sep 17 00:00:00 2001 From: underscoregt Date: Wed, 28 Jan 2026 18:26:41 +0800 Subject: [PATCH 07/71] Add /vscode to .gitignore --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index b50afce371..ae92eb2db7 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ bin/ /text-ui-test/ACTUAL.TXT text-ui-test/EXPECTED-UNIX.TXT -*.class \ No newline at end of file +*.class +/.vscode/ \ No newline at end of file From 9eab4094bf61621ec1adef97c3f0876600ce0d75 Mon Sep 17 00:00:00 2001 From: underscoregt Date: Wed, 28 Jan 2026 18:26:52 +0800 Subject: [PATCH 08/71] Add in command loop --- src/main/java/Amia.java | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/main/java/Amia.java b/src/main/java/Amia.java index 3065c62e7d..652450e613 100644 --- a/src/main/java/Amia.java +++ b/src/main/java/Amia.java @@ -1,14 +1,41 @@ +import java.util.Scanner; + public class Amia { public static void main(String[] args) { start(); + loop(); exit(); } public static void start() { - System.out.println(" Hello! I'm Amia!\nWhat can I do for you?"); + line(); + System.out.println("\tHello! I'm Amia!\n\tWhat can I do for you?"); + line(); } public static void exit() { - System.out.println("Bye!"); + line(); + System.out.println("\tBye!"); + line(); + } + + public static void loop() { + Scanner scanner = new Scanner(System.in); + while (true) { + String command = scanner.nextLine().trim(); + switch (command.toLowerCase()) { + case "exit": + scanner.close(); + return; + default: + line(); + System.out.println("\t" + command); + line(); + } + } + } + + public static void line() { + System.out.println("\t-----------------------------------"); } } From ae2577403eab8705b1a459da20a8e06950f9919c Mon Sep 17 00:00:00 2001 From: underscoregt Date: Wed, 28 Jan 2026 18:41:27 +0800 Subject: [PATCH 09/71] Add tasks and update loop --- src/main/java/Amia.java | 58 ++++++++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/src/main/java/Amia.java b/src/main/java/Amia.java index 652450e613..f8e91b3260 100644 --- a/src/main/java/Amia.java +++ b/src/main/java/Amia.java @@ -1,24 +1,16 @@ +import java.util.ArrayList; import java.util.Scanner; public class Amia { + private static ArrayList tasks = new ArrayList<>(); + private static int MAX_TASKS = 100; + public static void main(String[] args) { start(); loop(); exit(); } - public static void start() { - line(); - System.out.println("\tHello! I'm Amia!\n\tWhat can I do for you?"); - line(); - } - - public static void exit() { - line(); - System.out.println("\tBye!"); - line(); - } - public static void loop() { Scanner scanner = new Scanner(System.in); while (true) { @@ -27,14 +19,50 @@ public static void loop() { case "exit": scanner.close(); return; + case "list": + listTask(); + break; default: - line(); - System.out.println("\t" + command); - line(); + addTask(command); } } } + public static void addTask(String command) { + line(); + if (tasks.size() < MAX_TASKS) { + System.out.println("\tAdded: " + command); + tasks.add(command); + } else { + System.out.println("\tTask list is full!"); + } + line(); + } + + public static void listTask() { + line(); + if (tasks.size() == 0) { + System.out.println("\tNo tasks to list!"); + return; + } + for (int i = 0; i < tasks.size(); i++) { + System.out.println("\t" + (i + 1) + ". " + tasks.get(i)); + } + line(); + } + + public static void start() { + line(); + System.out.println("\tHello! I'm Amia!\n\tWhat can I do for you?"); + line(); + } + + public static void exit() { + line(); + System.out.println("\tBye!"); + line(); + } + public static void line() { System.out.println("\t-----------------------------------"); } From cde6e9dc78ed80bbb573e76e3fe3ed97e7b9a47f Mon Sep 17 00:00:00 2001 From: underscoregt Date: Wed, 28 Jan 2026 18:41:44 +0800 Subject: [PATCH 10/71] Change "exit" command to "bye" --- src/main/java/Amia.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/Amia.java b/src/main/java/Amia.java index f8e91b3260..58473a00ab 100644 --- a/src/main/java/Amia.java +++ b/src/main/java/Amia.java @@ -16,7 +16,7 @@ public static void loop() { while (true) { String command = scanner.nextLine().trim(); switch (command.toLowerCase()) { - case "exit": + case "bye": scanner.close(); return; case "list": From 74960470b73ecb460612431586c017f4c6615153 Mon Sep 17 00:00:00 2001 From: underscoregt Date: Wed, 28 Jan 2026 23:39:46 +0800 Subject: [PATCH 11/71] Add task class, add mark/unmark class, rewrite printing --- src/main/java/Amia.java | 84 +++++++++++++++++++++++++++++++---------- src/main/java/Task.java | 26 +++++++++++++ 2 files changed, 91 insertions(+), 19 deletions(-) create mode 100644 src/main/java/Task.java diff --git a/src/main/java/Amia.java b/src/main/java/Amia.java index 58473a00ab..75ea434566 100644 --- a/src/main/java/Amia.java +++ b/src/main/java/Amia.java @@ -2,7 +2,7 @@ import java.util.Scanner; public class Amia { - private static ArrayList tasks = new ArrayList<>(); + private static ArrayList tasks = new ArrayList<>(); private static int MAX_TASKS = 100; public static void main(String[] args) { @@ -23,47 +23,93 @@ public static void loop() { listTask(); break; default: - addTask(command); + if (command.startsWith("mark ")) { + markTask(command); + } else if (command.startsWith("unmark ")) { + unmarkTask(command); + } else { + addTask(command); + } } } } public static void addTask(String command) { - line(); + say(line()); if (tasks.size() < MAX_TASKS) { - System.out.println("\tAdded: " + command); - tasks.add(command); + say("Added: " + command); + tasks.add(new Task(command)); } else { - System.out.println("\tTask list is full!"); + say("Task list is full!"); } - line(); + say(line()); } public static void listTask() { - line(); + say(line()); if (tasks.size() == 0) { - System.out.println("\tNo tasks to list!"); + say("No tasks to list!"); + say(line()); return; } for (int i = 0; i < tasks.size(); i++) { - System.out.println("\t" + (i + 1) + ". " + tasks.get(i)); + say((i + 1) + ". " + tasks.get(i)); } - line(); + say(line()); + } + + public static void markTask(String command) { + say(line()); + try { + int idx = Integer.parseInt(command.substring(5)) - 1; + if (idx >= 0 && idx < tasks.size()) { + tasks.get(idx).markDone(); + say("I've marked the task as done!"); + say(" " + tasks.get(idx)); + } else { + say("Invalid task!"); + } + } catch (NumberFormatException e) { + say(e.toString()); + } + say(line()); + } + + public static void unmarkTask(String command) { + say(line()); + try { + int idx = Integer.parseInt(command.substring(7)) - 1; + if (idx >= 0 && idx < tasks.size()) { + tasks.get(idx).markUndone(); + say("I've marked the task as not done yet."); + say(" " + tasks.get(idx)); + } else { + say("Invalid task!"); + } + } catch (NumberFormatException e) { + say(e.toString()); + } + say(line()); } public static void start() { - line(); - System.out.println("\tHello! I'm Amia!\n\tWhat can I do for you?"); - line(); + say(line()); + say("Hello! I'm Amia!"); + say("What can I do for you?"); + say(line()); } public static void exit() { - line(); - System.out.println("\tBye!"); - line(); + say(line()); + say("\tBye!"); + say(line()); + } + + public static void say(String s) { + System.out.println("\t" + s); } - public static void line() { - System.out.println("\t-----------------------------------"); + public static String line() { + return "-----------------------------------"; } } diff --git a/src/main/java/Task.java b/src/main/java/Task.java new file mode 100644 index 0000000000..4a0e83f2ab --- /dev/null +++ b/src/main/java/Task.java @@ -0,0 +1,26 @@ +public class Task { + protected String description; + protected boolean isDone; + + public Task(String description) { + this.description = description; + this.isDone = false; + } + + public String getStatusIcon() { + return isDone ? "X" : " "; + } + + public void markDone() { + this.isDone = true; + } + + public void markUndone() { + this.isDone = false; + } + + @Override + public String toString() { + return "[" + getStatusIcon() + "] " + description; + } +} From b1fc770f01a349a490e4cfbba30a5e2f085582ff Mon Sep 17 00:00:00 2001 From: underscoregt Date: Wed, 28 Jan 2026 23:43:45 +0800 Subject: [PATCH 12/71] fix exit code --- src/main/java/Amia.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/Amia.java b/src/main/java/Amia.java index 75ea434566..b95b1dec36 100644 --- a/src/main/java/Amia.java +++ b/src/main/java/Amia.java @@ -101,7 +101,7 @@ public static void start() { public static void exit() { say(line()); - say("\tBye!"); + say("Bye!"); say(line()); } From b8b4ee5d0e2a577d47214378e0b60a7c802f508b Mon Sep 17 00:00:00 2001 From: underscoregt Date: Thu, 29 Jan 2026 00:02:17 +0800 Subject: [PATCH 13/71] Create subclasses for Task --- src/main/java/Deadline.java | 13 +++++++++++++ src/main/java/Event.java | 15 +++++++++++++++ src/main/java/ToDo.java | 10 ++++++++++ 3 files changed, 38 insertions(+) create mode 100644 src/main/java/Deadline.java create mode 100644 src/main/java/Event.java create mode 100644 src/main/java/ToDo.java diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java new file mode 100644 index 0000000000..573b7b8793 --- /dev/null +++ b/src/main/java/Deadline.java @@ -0,0 +1,13 @@ +public class Deadline extends Task { + protected String by; + + public Deadline(String description, String by) { + super(description); + this.by = by; + } + + @Override + public String toString() { + return "[D]" + super.toString() + " (by: " + by + ")"; + } +} diff --git a/src/main/java/Event.java b/src/main/java/Event.java new file mode 100644 index 0000000000..33eb55319c --- /dev/null +++ b/src/main/java/Event.java @@ -0,0 +1,15 @@ +public class Event extends Task { + protected String from; + protected String to; + + public Event(String description, String from, String to) { + super(description); + this.from = from; + this.to = to; + } + + @Override + public String toString() { + return "[E]" + super.toString() + " (from: " + from + " to: " + to; + } +} diff --git a/src/main/java/ToDo.java b/src/main/java/ToDo.java new file mode 100644 index 0000000000..caf48271b7 --- /dev/null +++ b/src/main/java/ToDo.java @@ -0,0 +1,10 @@ +public class ToDo extends Task{ + public ToDo(String decsription) { + super(decsription); + } + + @Override + public String toString() { + return "[T]" + super.toString(); + } +} From 4d372368ec69237aa0bb8c28b4885d3fb5f5b90a Mon Sep 17 00:00:00 2001 From: underscoregt Date: Thu, 29 Jan 2026 00:23:51 +0800 Subject: [PATCH 14/71] Fix missing bracket in Event.java --- src/main/java/Event.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/Event.java b/src/main/java/Event.java index 33eb55319c..d1378a3149 100644 --- a/src/main/java/Event.java +++ b/src/main/java/Event.java @@ -10,6 +10,6 @@ public Event(String description, String from, String to) { @Override public String toString() { - return "[E]" + super.toString() + " (from: " + from + " to: " + to; + return "[E]" + super.toString() + " (from: " + from + " to: " + to + ")"; } } From 7d39b548803fa70c270f1f35e78dc3211ce79bae Mon Sep 17 00:00:00 2001 From: underscoregt Date: Thu, 29 Jan 2026 00:24:33 +0800 Subject: [PATCH 15/71] Add ToDo, Deadline, Event commands --- src/main/java/Amia.java | 48 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/src/main/java/Amia.java b/src/main/java/Amia.java index b95b1dec36..bec2082d4e 100644 --- a/src/main/java/Amia.java +++ b/src/main/java/Amia.java @@ -27,8 +27,12 @@ public static void loop() { markTask(command); } else if (command.startsWith("unmark ")) { unmarkTask(command); - } else { + } else if (command.startsWith("todo ") + || command.startsWith("deadline ") + || command.startsWith("event ")) { addTask(command); + } else { + say(command); } } } @@ -37,8 +41,43 @@ public static void loop() { public static void addTask(String command) { say(line()); if (tasks.size() < MAX_TASKS) { - say("Added: " + command); - tasks.add(new Task(command)); + Task task; + + if (command.startsWith("todo ")) { + task = new ToDo(command.substring(5)); + } else if (command.startsWith("deadline ")) { + String args = command.substring(9); + int byIdx = args.lastIndexOf("/by "); + if (byIdx != -1) { + String desc = args.substring(0, byIdx - 1); + String by = args.substring(byIdx + 4); + task = new Deadline(desc, by); + } else { + say("Invalid format!"); + return; + } + } else if (command.startsWith("event ")) { + String args = command.substring(6); + int fromIdx = args.lastIndexOf("/from "); + int toIdx = args.lastIndexOf("/to "); + if (fromIdx != -1 && toIdx != -1) { + String desc = args.substring(0, fromIdx - 1); + String from = args.substring(fromIdx + 6, toIdx - 1); + String to = args.substring(toIdx + 4); + task = new Event(desc, from, to); + } else { + say("Invalid format!"); + return; + } + } else { + // should never reach here + task = new ToDo(command); + } + + tasks.add(task); + say("I've added this task!"); + say(" " + task); + say("You have " + tasks.size() + " task" + (tasks.size() == 1 ? "" : "s") + "."); } else { say("Task list is full!"); } @@ -52,6 +91,7 @@ public static void listTask() { say(line()); return; } + say("Here are your tasks: "); for (int i = 0; i < tasks.size(); i++) { say((i + 1) + ". " + tasks.get(i)); } @@ -65,7 +105,7 @@ public static void markTask(String command) { if (idx >= 0 && idx < tasks.size()) { tasks.get(idx).markDone(); say("I've marked the task as done!"); - say(" " + tasks.get(idx)); + say(" " + tasks.get(idx)); } else { say("Invalid task!"); } From 92a620273394c5ab6589f67032877ee47b02a877 Mon Sep 17 00:00:00 2001 From: underscoregt Date: Thu, 29 Jan 2026 01:07:48 +0800 Subject: [PATCH 16/71] Create automated testing --- text-ui-test/EXPECTED.TXT | 65 ++++++++++++++++++++++++++++++++++----- text-ui-test/input.txt | 11 +++++++ text-ui-test/runtest.bat | 2 +- 3 files changed, 70 insertions(+), 8 deletions(-) diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index 657e74f6e7..b8433d84cd 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -1,7 +1,58 @@ -Hello from - ____ _ -| _ \ _ _| | _____ -| | | | | | | |/ / _ \ -| |_| | |_| | < __/ -|____/ \__,_|_|\_\___| - + ----------------------------------- + Hello! I'm Amia! + What can I do for you? + ----------------------------------- + ----------------------------------- + I've added this task! + [T][ ] read book + You have 1 task. + ----------------------------------- + ----------------------------------- + I've added this task! + [D][ ] return book (by: June 6th) + You have 2 tasks. + ----------------------------------- + ----------------------------------- + I've added this task! + [E][ ] project meeting (from: Aug 6th 2pm to: 4pm) + You have 3 tasks. + ----------------------------------- + ----------------------------------- + I've added this task! + [T][ ] join sports club + You have 4 tasks. + ----------------------------------- + ----------------------------------- + I've marked the task as done! + [T][X] read book + ----------------------------------- + ----------------------------------- + I've marked the task as done! + [T][X] join sports club + ----------------------------------- + ----------------------------------- + I've added this task! + [T][ ] borrow book + You have 5 tasks. + ----------------------------------- + ----------------------------------- + Here are your tasks: + 1. [T][X] read book + 2. [D][ ] return book (by: June 6th) + 3. [E][ ] project meeting (from: Aug 6th 2pm to: 4pm) + 4. [T][X] join sports club + 5. [T][ ] borrow book + ----------------------------------- + ----------------------------------- + I've added this task! + [D][ ] return book (by: Sunday) + You have 6 tasks. + ----------------------------------- + ----------------------------------- + I've added this task! + [E][ ] project meeting (from: Mon 2pm to: 4pm) + You have 7 tasks. + ----------------------------------- + ----------------------------------- + Bye! + ----------------------------------- diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index e69de29bb2..6fab7be957 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -0,0 +1,11 @@ +todo read book +deadline return book /by June 6th +event project meeting /from Aug 6th 2pm /to 4pm +todo join sports club +mark 1 +mark 4 +todo borrow book +list +deadline return book /by Sunday +event project meeting /from Mon 2pm /to 4pm +bye \ No newline at end of file diff --git a/text-ui-test/runtest.bat b/text-ui-test/runtest.bat index 0873744649..2107b1410f 100644 --- a/text-ui-test/runtest.bat +++ b/text-ui-test/runtest.bat @@ -15,7 +15,7 @@ IF ERRORLEVEL 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 +java -classpath ..\bin Amia < input.txt > ACTUAL.TXT REM compare the output to the expected output FC ACTUAL.TXT EXPECTED.TXT From d2110859fbe5fd11db0bd0ab22a107858cd5e9b0 Mon Sep 17 00:00:00 2001 From: underscoregt Date: Thu, 29 Jan 2026 02:01:50 +0800 Subject: [PATCH 17/71] Add AmiaException and improve input validation Also update tests --- src/main/java/Amia.java | 132 +++++++++++++++++++------------ src/main/java/AmiaException.java | 5 ++ text-ui-test/EXPECTED.TXT | 39 +++++++++ text-ui-test/input.txt | 13 +++ 4 files changed, 139 insertions(+), 50 deletions(-) create mode 100644 src/main/java/AmiaException.java diff --git a/src/main/java/Amia.java b/src/main/java/Amia.java index bec2082d4e..c2d8574b0f 100644 --- a/src/main/java/Amia.java +++ b/src/main/java/Amia.java @@ -14,7 +14,8 @@ public static void main(String[] args) { public static void loop() { Scanner scanner = new Scanner(System.in); while (true) { - String command = scanner.nextLine().trim(); + try { + String command = scanner.nextLine().trim(); switch (command.toLowerCase()) { case "bye": scanner.close(); @@ -23,63 +24,86 @@ public static void loop() { listTask(); break; default: - if (command.startsWith("mark ")) { + if (command.startsWith("mark")) { markTask(command); - } else if (command.startsWith("unmark ")) { + } else if (command.startsWith("unmark")) { unmarkTask(command); - } else if (command.startsWith("todo ") - || command.startsWith("deadline ") - || command.startsWith("event ")) { + } else if (command.startsWith("todo") + || command.startsWith("deadline") + || command.startsWith("event")) { addTask(command); } else { - say(command); + throw new AmiaException("...?"); } + } + } catch (AmiaException e) { + say(line()); + say(e.getMessage()); + say(line()); } } } public static void addTask(String command) { say(line()); - if (tasks.size() < MAX_TASKS) { - Task task; - - if (command.startsWith("todo ")) { - task = new ToDo(command.substring(5)); - } else if (command.startsWith("deadline ")) { - String args = command.substring(9); - int byIdx = args.lastIndexOf("/by "); - if (byIdx != -1) { - String desc = args.substring(0, byIdx - 1); - String by = args.substring(byIdx + 4); + try { + if (tasks.size() < MAX_TASKS) { + Task task; + if (command.startsWith("todo")) { + String desc = command.substring(4).trim(); + if (desc.isEmpty()) { + throw new AmiaException("... The description of a task cannot be empty..."); + } + task = new ToDo(desc); + } else if (command.startsWith("deadline")) { + String args = command.substring(8).trim(); + int byIdx = args.lastIndexOf("/by"); + if (byIdx == -1 || args.isEmpty()) { + throw new AmiaException("Invalid format... Use: deadline /by "); + } + String desc = args.substring(0, byIdx).trim(); + String by = args.substring(byIdx + 3).trim(); + if (desc.isEmpty()) { + throw new AmiaException("... The description of a task can't be empty..."); + } + if (by.isEmpty()) { + throw new AmiaException("... The deadline can't be empty..."); + } task = new Deadline(desc, by); - } else { - say("Invalid format!"); - return; - } - } else if (command.startsWith("event ")) { - String args = command.substring(6); - int fromIdx = args.lastIndexOf("/from "); - int toIdx = args.lastIndexOf("/to "); - if (fromIdx != -1 && toIdx != -1) { - String desc = args.substring(0, fromIdx - 1); - String from = args.substring(fromIdx + 6, toIdx - 1); - String to = args.substring(toIdx + 4); + } else if (command.startsWith("event")) { + String args = command.substring(5).trim(); + int fromIdx = args.lastIndexOf("/from"); + int toIdx = args.lastIndexOf("/to"); + if (fromIdx == -1 || toIdx == -1 || args.isEmpty()) { + throw new AmiaException("Invalid format... Use: event /from /to "); + } + String desc = args.substring(0, fromIdx).trim(); + String from = args.substring(fromIdx + 5, toIdx).trim(); + String to = args.substring(toIdx + 3).trim(); + if (desc.isEmpty()) { + throw new AmiaException("...The description of a task can't be empty..."); + } + if (from.isEmpty()) { + throw new AmiaException("... The start time can't be empty..."); + } + if (to.isEmpty()) { + throw new AmiaException("... The end time can't be empty..."); + } task = new Event(desc, from, to); } else { - say("Invalid format!"); - return; + // should never reach here + task = new ToDo(command); } + + tasks.add(task); + say("I've added this task!"); + say(" " + task); + say("You have " + tasks.size() + " task" + (tasks.size() == 1 ? "" : "s") + "."); } else { - // should never reach here - task = new ToDo(command); + throw new AmiaException("... The task list is full..."); } - - tasks.add(task); - say("I've added this task!"); - say(" " + task); - say("You have " + tasks.size() + " task" + (tasks.size() == 1 ? "" : "s") + "."); - } else { - say("Task list is full!"); + } catch (AmiaException e) { + say(e.getMessage()); } say(line()); } @@ -87,7 +111,7 @@ public static void addTask(String command) { public static void listTask() { say(line()); if (tasks.size() == 0) { - say("No tasks to list!"); + say("No tasks to list..."); say(line()); return; } @@ -101,16 +125,20 @@ public static void listTask() { public static void markTask(String command) { say(line()); try { - int idx = Integer.parseInt(command.substring(5)) - 1; + String args = command.substring(4).trim(); + if (args.isEmpty()) { + throw new AmiaException("... Invalid format... Use: mark "); + } + int idx = Integer.parseInt(args) - 1; if (idx >= 0 && idx < tasks.size()) { tasks.get(idx).markDone(); say("I've marked the task as done!"); say(" " + tasks.get(idx)); } else { - say("Invalid task!"); + throw new AmiaException("... Invalid task number..."); } - } catch (NumberFormatException e) { - say(e.toString()); + } catch (AmiaException e) { + say(e.getMessage()); } say(line()); } @@ -118,16 +146,20 @@ public static void markTask(String command) { public static void unmarkTask(String command) { say(line()); try { - int idx = Integer.parseInt(command.substring(7)) - 1; + String args = command.substring(6).trim(); + if (args.isEmpty()) { + throw new AmiaException("... Invalid format... Use: unmark "); + } + int idx = Integer.parseInt(args) - 1; if (idx >= 0 && idx < tasks.size()) { tasks.get(idx).markUndone(); say("I've marked the task as not done yet."); say(" " + tasks.get(idx)); } else { - say("Invalid task!"); + throw new AmiaException("... Invalid task number..."); } - } catch (NumberFormatException e) { - say(e.toString()); + } catch (AmiaException e) { + say(e.getMessage()); } say(line()); } diff --git a/src/main/java/AmiaException.java b/src/main/java/AmiaException.java new file mode 100644 index 0000000000..fb8b021a6f --- /dev/null +++ b/src/main/java/AmiaException.java @@ -0,0 +1,5 @@ +public class AmiaException extends Exception { + public AmiaException(String message) { + super(message); + } +} diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index b8433d84cd..b52edbbe92 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -54,5 +54,44 @@ You have 7 tasks. ----------------------------------- ----------------------------------- + ... The description of a todo cannot be empty... + ----------------------------------- + ----------------------------------- + Invalid format... Use: deadline /by + ----------------------------------- + ----------------------------------- + ... The description of a task can't be empty... + ----------------------------------- + ----------------------------------- + ... The deadline can't be empty... + ----------------------------------- + ----------------------------------- + Invalid format... Use: event /from /to + ----------------------------------- + ----------------------------------- + ...The description of a task can't be empty... + ----------------------------------- + ----------------------------------- + ... The end time can't be empty... + ----------------------------------- + ----------------------------------- + ... The start time can't be empty... + ----------------------------------- + ----------------------------------- + ...The description of a task can't be empty... + ----------------------------------- + ----------------------------------- + ... Invalid format... Use: mark + ----------------------------------- + ----------------------------------- + ... Invalid task number... + ----------------------------------- + ----------------------------------- + ... Invalid format... Use: unmark + ----------------------------------- + ----------------------------------- + ... Invalid task number... + ----------------------------------- + ----------------------------------- Bye! ----------------------------------- diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index 6fab7be957..65ece7e6a9 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -8,4 +8,17 @@ todo borrow book list deadline return book /by Sunday event project meeting /from Mon 2pm /to 4pm +todo +deadline +deadline /by 1 +deadline a /by +event +event /from /to +event a /from 1 /to +event a /from /to 2 +event /from 1 /to 2 +mark +mark 999 +unmark +unmark 999 bye \ No newline at end of file From bac672de0c401b3134f083c51feb59cb63660d4e Mon Sep 17 00:00:00 2001 From: underscoregt Date: Thu, 29 Jan 2026 02:18:51 +0800 Subject: [PATCH 18/71] Add deleting of tasks --- src/main/java/Amia.java | 24 ++++++++++++++++++++++++ text-ui-test/EXPECTED.TXT | 29 ++++++++++++++++++++++++++++- text-ui-test/input.txt | 4 ++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/main/java/Amia.java b/src/main/java/Amia.java index c2d8574b0f..1160402c67 100644 --- a/src/main/java/Amia.java +++ b/src/main/java/Amia.java @@ -32,6 +32,8 @@ public static void loop() { || command.startsWith("deadline") || command.startsWith("event")) { addTask(command); + } else if (command.startsWith("delete")) { + deleteTask(command); } else { throw new AmiaException("...?"); } @@ -108,6 +110,28 @@ public static void addTask(String command) { say(line()); } + public static void deleteTask(String command) { + say(line()); + try { + String args = command.substring(6).trim(); + if (args.isEmpty()) { + throw new AmiaException("... Invalid format... Use: delete "); + } + int idx = Integer.parseInt(args) - 1; + if (idx >= 0 && idx < tasks.size()) { + Task removedTask = tasks.remove(idx); + say("I've removed this task:"); + say(" " + removedTask); + say("You have " + tasks.size() + " task" + (tasks.size() == 1 ? "" : "s") + "."); + } else { + throw new AmiaException("... Invalid task number..."); + } + } catch (AmiaException e) { + say(e.getMessage()); + } + say(line()); + } + public static void listTask() { say(line()); if (tasks.size() == 0) { diff --git a/text-ui-test/EXPECTED.TXT b/text-ui-test/EXPECTED.TXT index b52edbbe92..ab620abdc5 100644 --- a/text-ui-test/EXPECTED.TXT +++ b/text-ui-test/EXPECTED.TXT @@ -54,7 +54,7 @@ You have 7 tasks. ----------------------------------- ----------------------------------- - ... The description of a todo cannot be empty... + ... The description of a task cannot be empty... ----------------------------------- ----------------------------------- Invalid format... Use: deadline /by @@ -93,5 +93,32 @@ ... Invalid task number... ----------------------------------- ----------------------------------- + Here are your tasks: + 1. [T][X] read book + 2. [D][ ] return book (by: June 6th) + 3. [E][ ] project meeting (from: Aug 6th 2pm to: 4pm) + 4. [T][X] join sports club + 5. [T][ ] borrow book + 6. [D][ ] return book (by: Sunday) + 7. [E][ ] project meeting (from: Mon 2pm to: 4pm) + ----------------------------------- + ----------------------------------- + ... Invalid format... Use: delete + ----------------------------------- + ----------------------------------- + I've removed this task: + [T][X] read book + You have 6 tasks. + ----------------------------------- + ----------------------------------- + Here are your tasks: + 1. [D][ ] return book (by: June 6th) + 2. [E][ ] project meeting (from: Aug 6th 2pm to: 4pm) + 3. [T][X] join sports club + 4. [T][ ] borrow book + 5. [D][ ] return book (by: Sunday) + 6. [E][ ] project meeting (from: Mon 2pm to: 4pm) + ----------------------------------- + ----------------------------------- Bye! ----------------------------------- diff --git a/text-ui-test/input.txt b/text-ui-test/input.txt index 65ece7e6a9..4fac0852d8 100644 --- a/text-ui-test/input.txt +++ b/text-ui-test/input.txt @@ -21,4 +21,8 @@ mark mark 999 unmark unmark 999 +list +delete +delete 1 +list bye \ No newline at end of file From abf5ff70e497637d2695782c0583176ea1839118 Mon Sep 17 00:00:00 2001 From: underscoregt Date: Thu, 29 Jan 2026 02:27:45 +0800 Subject: [PATCH 19/71] Add CommandType enum and refactor command handling --- src/main/java/Amia.java | 38 +++++++++++++++++++--------------- src/main/java/CommandType.java | 26 +++++++++++++++++++++++ 2 files changed, 47 insertions(+), 17 deletions(-) create mode 100644 src/main/java/CommandType.java diff --git a/src/main/java/Amia.java b/src/main/java/Amia.java index 1160402c67..3be25e4887 100644 --- a/src/main/java/Amia.java +++ b/src/main/java/Amia.java @@ -16,27 +16,31 @@ public static void loop() { while (true) { try { String command = scanner.nextLine().trim(); - switch (command.toLowerCase()) { - case "bye": - scanner.close(); - return; - case "list": - listTask(); - break; - default: - if (command.startsWith("mark")) { + CommandType cmdType = CommandType.fromString(command.toLowerCase()); + + switch (cmdType) { + case TODO: + case DEADLINE: + case EVENT: + addTask(command); + break; + case MARK: markTask(command); - } else if (command.startsWith("unmark")) { + break; + case UNMARK: unmarkTask(command); - } else if (command.startsWith("todo") - || command.startsWith("deadline") - || command.startsWith("event")) { - addTask(command); - } else if (command.startsWith("delete")) { + break; + case DELETE: deleteTask(command); - } else { + break; + case LIST: + listTask(); + break; + case BYE: + scanner.close(); + return; + case UNKNOWN: throw new AmiaException("...?"); - } } } catch (AmiaException e) { say(line()); diff --git a/src/main/java/CommandType.java b/src/main/java/CommandType.java new file mode 100644 index 0000000000..593fb36532 --- /dev/null +++ b/src/main/java/CommandType.java @@ -0,0 +1,26 @@ +public enum CommandType { + TODO("todo"), + DEADLINE("deadline"), + EVENT("event"), + MARK("mark"), + UNMARK("unmark"), + DELETE("delete"), + LIST("list"), + BYE("bye"), + UNKNOWN("unknown"); + + private final String value; + + CommandType(String value) { + this.value = value; + } + + public static CommandType fromString(String input) { + for (CommandType cmd : CommandType.values()) { + if (input.startsWith(cmd.value)) { + return cmd; + } + } + return UNKNOWN; + } +} From 58bfa11ce797812ac01c6720b8c4bbde8f9ba58d Mon Sep 17 00:00:00 2001 From: underscoregt Date: Mon, 9 Feb 2026 20:45:35 +0800 Subject: [PATCH 20/71] Add toFileString and make Task abstract Make Task abstract and add an abstract toFileString() method for file serialization. Implement toFileString() in ToDo, Deadline, and Event to produce pipe-separated representations (e.g. `T | 0 | description`, `D | 0 | description | by`, `E | 0 | description | from | to`). --- src/main/java/Deadline.java | 5 +++++ src/main/java/Event.java | 5 +++++ src/main/java/Task.java | 4 +++- src/main/java/ToDo.java | 5 +++++ 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java index 573b7b8793..9cabae8054 100644 --- a/src/main/java/Deadline.java +++ b/src/main/java/Deadline.java @@ -10,4 +10,9 @@ public Deadline(String description, String by) { public String toString() { return "[D]" + super.toString() + " (by: " + by + ")"; } + + @Override + public String toFileString() { + return "D | " + (isDone ? "1" : "0") + " | " + description + " | " + by; + } } diff --git a/src/main/java/Event.java b/src/main/java/Event.java index d1378a3149..a532510295 100644 --- a/src/main/java/Event.java +++ b/src/main/java/Event.java @@ -12,4 +12,9 @@ public Event(String description, String from, String to) { public String toString() { return "[E]" + super.toString() + " (from: " + from + " to: " + to + ")"; } + + @Override + public String toFileString() { + return "E | " + (isDone ? "1" : "0") + " | " + description + " | " + from + " | " + to; + } } diff --git a/src/main/java/Task.java b/src/main/java/Task.java index 4a0e83f2ab..1e73b831d5 100644 --- a/src/main/java/Task.java +++ b/src/main/java/Task.java @@ -1,4 +1,4 @@ -public class Task { +public abstract class Task { protected String description; protected boolean isDone; @@ -23,4 +23,6 @@ public void markUndone() { public String toString() { return "[" + getStatusIcon() + "] " + description; } + + public abstract String toFileString(); } diff --git a/src/main/java/ToDo.java b/src/main/java/ToDo.java index caf48271b7..5544ac38ce 100644 --- a/src/main/java/ToDo.java +++ b/src/main/java/ToDo.java @@ -7,4 +7,9 @@ public ToDo(String decsription) { public String toString() { return "[T]" + super.toString(); } + + @Override + public String toFileString() { + return "T | " + (isDone ? "1" : "0") + " | " + description; + } } From f6632e3d302fd40b8f7de1118e7407f01101d2bb Mon Sep 17 00:00:00 2001 From: underscoregt Date: Mon, 9 Feb 2026 21:07:36 +0800 Subject: [PATCH 21/71] Add Storage for task persistence * Introduce Storage.java to load/save tasks to ./data/amia.txt and persist task state. * Update Amia.java to load tasks on start and call Storage.save(...) after add/remove/mark/undone operations with basic AmiaException handling. * Update .gitignore to ignore the data/ directory. --- .gitignore | 4 ++- src/main/java/Amia.java | 9 +++++ src/main/java/Storage.java | 68 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 src/main/java/Storage.java diff --git a/.gitignore b/.gitignore index ae92eb2db7..03e335d3be 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,6 @@ bin/ text-ui-test/EXPECTED-UNIX.TXT *.class -/.vscode/ \ No newline at end of file +/.vscode/ + +data/ \ No newline at end of file diff --git a/src/main/java/Amia.java b/src/main/java/Amia.java index 3be25e4887..58f6374751 100644 --- a/src/main/java/Amia.java +++ b/src/main/java/Amia.java @@ -102,6 +102,7 @@ public static void addTask(String command) { } tasks.add(task); + Storage.save(tasks); say("I've added this task!"); say(" " + task); say("You have " + tasks.size() + " task" + (tasks.size() == 1 ? "" : "s") + "."); @@ -124,6 +125,7 @@ public static void deleteTask(String command) { int idx = Integer.parseInt(args) - 1; if (idx >= 0 && idx < tasks.size()) { Task removedTask = tasks.remove(idx); + Storage.save(tasks); say("I've removed this task:"); say(" " + removedTask); say("You have " + tasks.size() + " task" + (tasks.size() == 1 ? "" : "s") + "."); @@ -160,6 +162,7 @@ public static void markTask(String command) { int idx = Integer.parseInt(args) - 1; if (idx >= 0 && idx < tasks.size()) { tasks.get(idx).markDone(); + Storage.save(tasks); say("I've marked the task as done!"); say(" " + tasks.get(idx)); } else { @@ -181,6 +184,7 @@ public static void unmarkTask(String command) { int idx = Integer.parseInt(args) - 1; if (idx >= 0 && idx < tasks.size()) { tasks.get(idx).markUndone(); + Storage.save(tasks); say("I've marked the task as not done yet."); say(" " + tasks.get(idx)); } else { @@ -194,6 +198,11 @@ public static void unmarkTask(String command) { public static void start() { say(line()); + try { + tasks = Storage.load(); + } catch (AmiaException e) { + say(e.getMessage()); + } say("Hello! I'm Amia!"); say("What can I do for you?"); say(line()); diff --git a/src/main/java/Storage.java b/src/main/java/Storage.java new file mode 100644 index 0000000000..ed4bd2251d --- /dev/null +++ b/src/main/java/Storage.java @@ -0,0 +1,68 @@ +import java.io.*; +import java.util.ArrayList; + +public class Storage { + private static final String FILE_PATH = "./data/amia.txt"; + + public static ArrayList load() throws AmiaException { + ArrayList tasks = new ArrayList<>(); + + File file = new File(FILE_PATH); + if (!file.exists()) { + return tasks; // no file yet = no tasks + } + + try (BufferedReader br = new BufferedReader(new FileReader(file))) { + String line; + while ((line = br.readLine()) != null) { + String[] parts = line.split(" \\| "); + + String type = parts[0]; + boolean isDone = parts[1].equals("1"); + + Task task; + switch (type) { + case "T": + task = new ToDo(parts[2]); + break; + case "D": + task = new Deadline(parts[2], parts[3]); + break; + case "E": + task = new Event(parts[2], parts[3], parts[4]); + break; + default: + continue; + } + + if (isDone) { + task.markDone(); + } + + tasks.add(task); + } + } catch (IOException e) { + throw new AmiaException("... I can't load the saved tasks..."); + } + + return tasks; + } + + public static void save(ArrayList tasks) throws AmiaException { + try { + File dir = new File("./data"); + if (!dir.exists()) { + dir.mkdir(); + } + + BufferedWriter bw = new BufferedWriter(new FileWriter(FILE_PATH)); + for (Task task : tasks) { + bw.write(task.toFileString()); + bw.newLine(); + } + bw.close(); + } catch (IOException e) { + throw new AmiaException("... I can't save the tasks..."); + } + } +} From 1cd1e9d4ebc94e557cc019f73a60ceb85a92f3e7 Mon Sep 17 00:00:00 2001 From: underscoregt Date: Tue, 10 Feb 2026 00:26:16 +0800 Subject: [PATCH 22/71] Use LocalDateTime for Deadline/Event dates Change Deadline and Event to store date/time as LocalDateTime and validate input. --- src/main/java/Deadline.java | 22 ++++++++++++++++++---- src/main/java/Event.java | 25 +++++++++++++++++++------ 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java index 9cabae8054..5b7c4e12ec 100644 --- a/src/main/java/Deadline.java +++ b/src/main/java/Deadline.java @@ -1,14 +1,28 @@ +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + public class Deadline extends Task { - protected String by; + private static final DateTimeFormatter INPUT_FORMAT = + DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm"); + private static final DateTimeFormatter OUTPUT_FORMAT = + DateTimeFormatter.ofPattern("MMM dd yyyy, h:mma"); + protected LocalDateTime by; + - public Deadline(String description, String by) { + public Deadline(String description, String byStr) throws AmiaException { super(description); - this.by = by; + try { + this.by = LocalDateTime.parse(byStr, INPUT_FORMAT); + } catch (DateTimeParseException e) { + throw new AmiaException("...Invalid date/time format. Use yyyy-MM-dd HHmm"); + } } @Override public String toString() { - return "[D]" + super.toString() + " (by: " + by + ")"; + return "[D]" + super.toString() + " (by: " + by.format(OUTPUT_FORMAT) + ")"; } @Override diff --git a/src/main/java/Event.java b/src/main/java/Event.java index a532510295..9a0ccdd695 100644 --- a/src/main/java/Event.java +++ b/src/main/java/Event.java @@ -1,16 +1,29 @@ +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + public class Event extends Task { - protected String from; - protected String to; + private LocalDateTime from; + private LocalDateTime to; + + private static final DateTimeFormatter INPUT_FORMAT = + DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm"); + private static final DateTimeFormatter OUTPUT_FORMAT = + DateTimeFormatter.ofPattern("MMM dd yyyy, h:mma"); - public Event(String description, String from, String to) { + public Event(String description, String fromStr, String toStr) throws AmiaException{ super(description); - this.from = from; - this.to = to; + try { + this.from = LocalDateTime.parse(fromStr, INPUT_FORMAT); + this.to = LocalDateTime.parse(toStr, INPUT_FORMAT); + } catch (DateTimeParseException e) { + throw new AmiaException("...Invalid date/time format. Use yyyy-MM-dd HHmm"); + } } @Override public String toString() { - return "[E]" + super.toString() + " (from: " + from + " to: " + to + ")"; + return "[E]" + super.toString() + " (from: " + from.format(OUTPUT_FORMAT) + " to: " + to.format(OUTPUT_FORMAT) + ")"; } @Override From 252a071f27bac0372142c781c9354cc7d38479d7 Mon Sep 17 00:00:00 2001 From: underscoregt Date: Wed, 11 Feb 2026 12:53:15 +0800 Subject: [PATCH 23/71] Add Ui class and refactor Amia I/O Introduce a new Ui class to centralize input/output and replace direct Scanner/System.out usage in Amia. --- src/main/java/Amia.java | 91 +++++++++++++++++++---------------------- src/main/java/Ui.java | 38 +++++++++++++++++ 2 files changed, 79 insertions(+), 50 deletions(-) create mode 100644 src/main/java/Ui.java diff --git a/src/main/java/Amia.java b/src/main/java/Amia.java index 58f6374751..18ddd9b320 100644 --- a/src/main/java/Amia.java +++ b/src/main/java/Amia.java @@ -1,9 +1,9 @@ import java.util.ArrayList; -import java.util.Scanner; public class Amia { private static ArrayList tasks = new ArrayList<>(); private static int MAX_TASKS = 100; + private static Ui ui = new Ui(); public static void main(String[] args) { start(); @@ -12,10 +12,9 @@ public static void main(String[] args) { } public static void loop() { - Scanner scanner = new Scanner(System.in); while (true) { try { - String command = scanner.nextLine().trim(); + String command = ui.readCommand(); CommandType cmdType = CommandType.fromString(command.toLowerCase()); switch (cmdType) { @@ -37,21 +36,21 @@ public static void loop() { listTask(); break; case BYE: - scanner.close(); + ui.close(); return; case UNKNOWN: throw new AmiaException("...?"); } } catch (AmiaException e) { - say(line()); - say(e.getMessage()); - say(line()); + ui.showLine(); + ui.showMessage(e.getMessage()); + ui.showLine(); } } } public static void addTask(String command) { - say(line()); + ui.showLine(); try { if (tasks.size() < MAX_TASKS) { Task task; @@ -103,20 +102,20 @@ public static void addTask(String command) { tasks.add(task); Storage.save(tasks); - say("I've added this task!"); - say(" " + task); - say("You have " + tasks.size() + " task" + (tasks.size() == 1 ? "" : "s") + "."); + ui.showMessage("I've added this task!"); + ui.showMessage(" " + task); + ui.showMessage("You have " + tasks.size() + " task" + (tasks.size() == 1 ? "" : "s") + "."); } else { throw new AmiaException("... The task list is full..."); } } catch (AmiaException e) { - say(e.getMessage()); + ui.showMessage(e.getMessage()); } - say(line()); + ui.showLine(); } public static void deleteTask(String command) { - say(line()); + ui.showLine(); try { String args = command.substring(6).trim(); if (args.isEmpty()) { @@ -126,34 +125,34 @@ public static void deleteTask(String command) { if (idx >= 0 && idx < tasks.size()) { Task removedTask = tasks.remove(idx); Storage.save(tasks); - say("I've removed this task:"); - say(" " + removedTask); - say("You have " + tasks.size() + " task" + (tasks.size() == 1 ? "" : "s") + "."); + ui.showMessage("I've removed this task:"); + ui.showMessage(" " + removedTask); + ui.showMessage("You have " + tasks.size() + " task" + (tasks.size() == 1 ? "" : "s") + "."); } else { throw new AmiaException("... Invalid task number..."); } } catch (AmiaException e) { - say(e.getMessage()); + ui.showMessage(e.getMessage()); } - say(line()); + ui.showLine(); } public static void listTask() { - say(line()); + ui.showLine(); if (tasks.size() == 0) { - say("No tasks to list..."); - say(line()); + ui.showMessage("No tasks to list..."); + ui.showLine(); return; } - say("Here are your tasks: "); + ui.showMessage("Here are your tasks: "); for (int i = 0; i < tasks.size(); i++) { - say((i + 1) + ". " + tasks.get(i)); + ui.showMessage((i + 1) + ". " + tasks.get(i)); } - say(line()); + ui.showLine(); } public static void markTask(String command) { - say(line()); + ui.showLine(); try { String args = command.substring(4).trim(); if (args.isEmpty()) { @@ -163,19 +162,19 @@ public static void markTask(String command) { if (idx >= 0 && idx < tasks.size()) { tasks.get(idx).markDone(); Storage.save(tasks); - say("I've marked the task as done!"); - say(" " + tasks.get(idx)); + ui.showMessage("I've marked the task as done!"); + ui.showMessage(" " + tasks.get(idx)); } else { throw new AmiaException("... Invalid task number..."); } } catch (AmiaException e) { - say(e.getMessage()); + ui.showMessage(e.getMessage()); } - say(line()); + ui.showLine(); } public static void unmarkTask(String command) { - say(line()); + ui.showLine(); try { String args = command.substring(6).trim(); if (args.isEmpty()) { @@ -185,40 +184,32 @@ public static void unmarkTask(String command) { if (idx >= 0 && idx < tasks.size()) { tasks.get(idx).markUndone(); Storage.save(tasks); - say("I've marked the task as not done yet."); - say(" " + tasks.get(idx)); + ui.showMessage("I've marked the task as not done yet."); + ui.showMessage(" " + tasks.get(idx)); } else { throw new AmiaException("... Invalid task number..."); } } catch (AmiaException e) { - say(e.getMessage()); + ui.showMessage(e.getMessage()); } - say(line()); + ui.showLine(); } public static void start() { - say(line()); + ui.showLine(); try { tasks = Storage.load(); } catch (AmiaException e) { - say(e.getMessage()); + ui.showMessage(e.getMessage()); } - say("Hello! I'm Amia!"); - say("What can I do for you?"); - say(line()); + ui.showMessage("Hello! I'm Amia!"); + ui.showMessage("What can I do for you?"); + ui.showLine(); } public static void exit() { - say(line()); - say("Bye!"); - say(line()); + ui.showGoodbye(); } - public static void say(String s) { - System.out.println("\t" + s); - } - - public static String line() { - return "-----------------------------------"; - } + } diff --git a/src/main/java/Ui.java b/src/main/java/Ui.java new file mode 100644 index 0000000000..9b5075dbbd --- /dev/null +++ b/src/main/java/Ui.java @@ -0,0 +1,38 @@ +import java.util.Scanner; + +public class Ui { + private Scanner scanner; + + public Ui() { + scanner = new Scanner(System.in); + } + + public String readCommand() { + return scanner.nextLine().trim(); + } + + public void close() { + scanner.close(); + } + + public void showMessage(String s) { + System.out.println("\t" + s); + } + + public void showLine() { + System.out.println("-----------------------------------"); + } + + public void showWelcome() { + showLine(); + showMessage("Hello! I'm Amia!"); + showMessage("What can I do for you?"); + showLine(); + } + + public void showGoodbye() { + showLine(); + showMessage("Bye!"); + showLine(); + } +} From 3d96c7aaf182ca0fc25a9b14b1543d4efe5eb471 Mon Sep 17 00:00:00 2001 From: underscoregt Date: Wed, 11 Feb 2026 13:49:07 +0800 Subject: [PATCH 24/71] Change Storage to instance-based class Change Storage from a static utility to an instance-based class with a configurable file path. --- src/main/java/Amia.java | 15 +++++++++------ src/main/java/Storage.java | 20 ++++++++++++-------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/main/java/Amia.java b/src/main/java/Amia.java index 18ddd9b320..9be8dc7882 100644 --- a/src/main/java/Amia.java +++ b/src/main/java/Amia.java @@ -1,9 +1,12 @@ import java.util.ArrayList; public class Amia { - private static ArrayList tasks = new ArrayList<>(); private static int MAX_TASKS = 100; + private static String FILE_PATH = "./data/amia.txt"; + + private static ArrayList tasks = new ArrayList<>(); private static Ui ui = new Ui(); + private static Storage storage = new Storage(FILE_PATH); public static void main(String[] args) { start(); @@ -101,7 +104,7 @@ public static void addTask(String command) { } tasks.add(task); - Storage.save(tasks); + storage.save(tasks); ui.showMessage("I've added this task!"); ui.showMessage(" " + task); ui.showMessage("You have " + tasks.size() + " task" + (tasks.size() == 1 ? "" : "s") + "."); @@ -124,7 +127,7 @@ public static void deleteTask(String command) { int idx = Integer.parseInt(args) - 1; if (idx >= 0 && idx < tasks.size()) { Task removedTask = tasks.remove(idx); - Storage.save(tasks); + storage.save(tasks); ui.showMessage("I've removed this task:"); ui.showMessage(" " + removedTask); ui.showMessage("You have " + tasks.size() + " task" + (tasks.size() == 1 ? "" : "s") + "."); @@ -161,7 +164,7 @@ public static void markTask(String command) { int idx = Integer.parseInt(args) - 1; if (idx >= 0 && idx < tasks.size()) { tasks.get(idx).markDone(); - Storage.save(tasks); + storage.save(tasks); ui.showMessage("I've marked the task as done!"); ui.showMessage(" " + tasks.get(idx)); } else { @@ -183,7 +186,7 @@ public static void unmarkTask(String command) { int idx = Integer.parseInt(args) - 1; if (idx >= 0 && idx < tasks.size()) { tasks.get(idx).markUndone(); - Storage.save(tasks); + storage.save(tasks); ui.showMessage("I've marked the task as not done yet."); ui.showMessage(" " + tasks.get(idx)); } else { @@ -198,7 +201,7 @@ public static void unmarkTask(String command) { public static void start() { ui.showLine(); try { - tasks = Storage.load(); + tasks = storage.load(); } catch (AmiaException e) { ui.showMessage(e.getMessage()); } diff --git a/src/main/java/Storage.java b/src/main/java/Storage.java index ed4bd2251d..2d1bf8eb8b 100644 --- a/src/main/java/Storage.java +++ b/src/main/java/Storage.java @@ -2,12 +2,16 @@ import java.util.ArrayList; public class Storage { - private static final String FILE_PATH = "./data/amia.txt"; + private final String filePath; - public static ArrayList load() throws AmiaException { + public Storage(String filePath) { + this.filePath = filePath == null || filePath.isEmpty() ? "./data/amia.txt" : filePath; + } + + public ArrayList load() throws AmiaException { ArrayList tasks = new ArrayList<>(); - File file = new File(FILE_PATH); + File file = new File(filePath); if (!file.exists()) { return tasks; // no file yet = no tasks } @@ -48,14 +52,14 @@ public static ArrayList load() throws AmiaException { return tasks; } - public static void save(ArrayList tasks) throws AmiaException { + public void save(ArrayList tasks) throws AmiaException { try { - File dir = new File("./data"); - if (!dir.exists()) { - dir.mkdir(); + File dir = new File(new File(filePath).getParent()); + if (dir != null && !dir.exists()) { + dir.mkdirs(); } - BufferedWriter bw = new BufferedWriter(new FileWriter(FILE_PATH)); + BufferedWriter bw = new BufferedWriter(new FileWriter(filePath)); for (Task task : tasks) { bw.write(task.toFileString()); bw.newLine(); From be33c6039b73d3b39124ab2fa7a8b5a9db2d6777 Mon Sep 17 00:00:00 2001 From: underscoregt Date: Wed, 11 Feb 2026 21:20:28 +0800 Subject: [PATCH 25/71] Add Parser utility and refactor Amia Introduce a new Parser class to centralize command parsing logic. --- src/main/java/Amia.java | 72 +++++++---------------------- src/main/java/Parser.java | 95 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 57 deletions(-) create mode 100644 src/main/java/Parser.java diff --git a/src/main/java/Amia.java b/src/main/java/Amia.java index 9be8dc7882..16804a5e79 100644 --- a/src/main/java/Amia.java +++ b/src/main/java/Amia.java @@ -18,7 +18,7 @@ public static void loop() { while (true) { try { String command = ui.readCommand(); - CommandType cmdType = CommandType.fromString(command.toLowerCase()); + CommandType cmdType = Parser.parseCommandType(command); switch (cmdType) { case TODO: @@ -58,48 +58,15 @@ public static void addTask(String command) { if (tasks.size() < MAX_TASKS) { Task task; if (command.startsWith("todo")) { - String desc = command.substring(4).trim(); - if (desc.isEmpty()) { - throw new AmiaException("... The description of a task cannot be empty..."); - } + String desc = Parser.extractDescription(command, "todo"); task = new ToDo(desc); } else if (command.startsWith("deadline")) { - String args = command.substring(8).trim(); - int byIdx = args.lastIndexOf("/by"); - if (byIdx == -1 || args.isEmpty()) { - throw new AmiaException("Invalid format... Use: deadline /by "); - } - String desc = args.substring(0, byIdx).trim(); - String by = args.substring(byIdx + 3).trim(); - if (desc.isEmpty()) { - throw new AmiaException("... The description of a task can't be empty..."); - } - if (by.isEmpty()) { - throw new AmiaException("... The deadline can't be empty..."); - } - task = new Deadline(desc, by); + Parser.DeadlineInfo info = Parser.parseDeadline(command); + task = new Deadline(info.description, info.deadline); } else if (command.startsWith("event")) { - String args = command.substring(5).trim(); - int fromIdx = args.lastIndexOf("/from"); - int toIdx = args.lastIndexOf("/to"); - if (fromIdx == -1 || toIdx == -1 || args.isEmpty()) { - throw new AmiaException("Invalid format... Use: event /from /to "); - } - String desc = args.substring(0, fromIdx).trim(); - String from = args.substring(fromIdx + 5, toIdx).trim(); - String to = args.substring(toIdx + 3).trim(); - if (desc.isEmpty()) { - throw new AmiaException("...The description of a task can't be empty..."); - } - if (from.isEmpty()) { - throw new AmiaException("... The start time can't be empty..."); - } - if (to.isEmpty()) { - throw new AmiaException("... The end time can't be empty..."); - } - task = new Event(desc, from, to); + Parser.EventInfo info = Parser.parseEvent(command); + task = new Event(info.description, info.from, info.to); } else { - // should never reach here task = new ToDo(command); } @@ -119,12 +86,9 @@ public static void addTask(String command) { public static void deleteTask(String command) { ui.showLine(); - try { - String args = command.substring(6).trim(); - if (args.isEmpty()) { - throw new AmiaException("... Invalid format... Use: delete "); - } - int idx = Integer.parseInt(args) - 1; + try { + String args = Parser.extractIndexArg(command, "delete"); + int idx = Parser.parseIndex(args); if (idx >= 0 && idx < tasks.size()) { Task removedTask = tasks.remove(idx); storage.save(tasks); @@ -156,12 +120,9 @@ public static void listTask() { public static void markTask(String command) { ui.showLine(); - try { - String args = command.substring(4).trim(); - if (args.isEmpty()) { - throw new AmiaException("... Invalid format... Use: mark "); - } - int idx = Integer.parseInt(args) - 1; + try { + String args = Parser.extractIndexArg(command, "mark"); + int idx = Parser.parseIndex(args); if (idx >= 0 && idx < tasks.size()) { tasks.get(idx).markDone(); storage.save(tasks); @@ -178,12 +139,9 @@ public static void markTask(String command) { public static void unmarkTask(String command) { ui.showLine(); - try { - String args = command.substring(6).trim(); - if (args.isEmpty()) { - throw new AmiaException("... Invalid format... Use: unmark "); - } - int idx = Integer.parseInt(args) - 1; + try { + String args = Parser.extractIndexArg(command, "unmark"); + int idx = Parser.parseIndex(args); if (idx >= 0 && idx < tasks.size()) { tasks.get(idx).markUndone(); storage.save(tasks); diff --git a/src/main/java/Parser.java b/src/main/java/Parser.java new file mode 100644 index 0000000000..62aeaafe08 --- /dev/null +++ b/src/main/java/Parser.java @@ -0,0 +1,95 @@ +public class Parser { + + public static CommandType parseCommandType(String input) throws AmiaException { + if (input == null || input.trim().isEmpty()) { + throw new AmiaException("...?"); + } + CommandType cmdType = CommandType.fromString(input.trim().toLowerCase()); + return cmdType; + } + + public static String extractDescription(String command, String keyword) throws AmiaException { + String desc = command.substring(keyword.length()).trim(); + if (desc.isEmpty()) { + throw new AmiaException("... The description of a task cannot be empty..."); + } + return desc; + } + + public static String extractIndexArg(String command, String keyword) throws AmiaException { + String args = command.substring(keyword.length()).trim(); + if (args.isEmpty()) { + throw new AmiaException("... Invalid format... Use: " + keyword + " "); + } + return args; + } + + public static int parseIndex(String indexStr) throws AmiaException { + try { + return Integer.parseInt(indexStr) - 1; + } catch (NumberFormatException e) { + throw new AmiaException("... Invalid task number..."); + } + } + + public static DeadlineInfo parseDeadline(String command) throws AmiaException { + String args = command.substring(8).trim(); + int byIdx = args.lastIndexOf("/by"); + if (byIdx == -1 || args.isEmpty()) { + throw new AmiaException("Invalid format... Use: deadline /by "); + } + String desc = args.substring(0, byIdx).trim(); + String by = args.substring(byIdx + 3).trim(); + if (desc.isEmpty()) { + throw new AmiaException("... The description of a task can't be empty..."); + } + if (by.isEmpty()) { + throw new AmiaException("... The deadline can't be empty..."); + } + return new DeadlineInfo(desc, by); + } + + public static EventInfo parseEvent(String command) throws AmiaException { + String args = command.substring(5).trim(); + int fromIdx = args.lastIndexOf("/from"); + int toIdx = args.lastIndexOf("/to"); + if (fromIdx == -1 || toIdx == -1 || args.isEmpty()) { + throw new AmiaException("Invalid format... Use: event /from /to "); + } + String desc = args.substring(0, fromIdx).trim(); + String from = args.substring(fromIdx + 5, toIdx).trim(); + String to = args.substring(toIdx + 3).trim(); + if (desc.isEmpty()) { + throw new AmiaException("...The description of a task can't be empty..."); + } + if (from.isEmpty()) { + throw new AmiaException("... The start time can't be empty..."); + } + if (to.isEmpty()) { + throw new AmiaException("... The end time can't be empty..."); + } + return new EventInfo(desc, from, to); + } + + public static class DeadlineInfo { + public String description; + public String deadline; + + public DeadlineInfo(String description, String deadline) { + this.description = description; + this.deadline = deadline; + } + } + + public static class EventInfo { + public String description; + public String from; + public String to; + + public EventInfo(String description, String from, String to) { + this.description = description; + this.from = from; + this.to = to; + } + } +} From 8e6c6545a633b655223afe436af2fe5702e9190c Mon Sep 17 00:00:00 2001 From: underscoregt Date: Wed, 11 Feb 2026 21:31:47 +0800 Subject: [PATCH 26/71] Add TaskList class and implement in Amia --- src/main/java/Amia.java | 38 +++++++++++++++------------- src/main/java/TaskList.java | 50 +++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 17 deletions(-) create mode 100644 src/main/java/TaskList.java diff --git a/src/main/java/Amia.java b/src/main/java/Amia.java index 16804a5e79..efb6d6dc8c 100644 --- a/src/main/java/Amia.java +++ b/src/main/java/Amia.java @@ -4,7 +4,7 @@ public class Amia { private static int MAX_TASKS = 100; private static String FILE_PATH = "./data/amia.txt"; - private static ArrayList tasks = new ArrayList<>(); + private static TaskList tasks = new TaskList(); private static Ui ui = new Ui(); private static Storage storage = new Storage(FILE_PATH); @@ -55,7 +55,7 @@ public static void loop() { public static void addTask(String command) { ui.showLine(); try { - if (tasks.size() < MAX_TASKS) { + if (tasks.size() < MAX_TASKS) { Task task; if (command.startsWith("todo")) { String desc = Parser.extractDescription(command, "todo"); @@ -71,7 +71,7 @@ public static void addTask(String command) { } tasks.add(task); - storage.save(tasks); + storage.save(tasks.toArrayList()); ui.showMessage("I've added this task!"); ui.showMessage(" " + task); ui.showMessage("You have " + tasks.size() + " task" + (tasks.size() == 1 ? "" : "s") + "."); @@ -91,7 +91,7 @@ public static void deleteTask(String command) { int idx = Parser.parseIndex(args); if (idx >= 0 && idx < tasks.size()) { Task removedTask = tasks.remove(idx); - storage.save(tasks); + storage.save(tasks.toArrayList()); ui.showMessage("I've removed this task:"); ui.showMessage(" " + removedTask); ui.showMessage("You have " + tasks.size() + " task" + (tasks.size() == 1 ? "" : "s") + "."); @@ -106,14 +106,18 @@ public static void deleteTask(String command) { public static void listTask() { ui.showLine(); - if (tasks.size() == 0) { - ui.showMessage("No tasks to list..."); - ui.showLine(); - return; - } - ui.showMessage("Here are your tasks: "); - for (int i = 0; i < tasks.size(); i++) { - ui.showMessage((i + 1) + ". " + tasks.get(i)); + try { + if (tasks.size() == 0) { + ui.showMessage("No tasks to list..."); + ui.showLine(); + return; + } + ui.showMessage("Here are your tasks: "); + for (int i = 0; i < tasks.size(); i++) { + ui.showMessage((i + 1) + ". " + tasks.get(i)); + } + } catch (AmiaException e) { + ui.showMessage(e.getMessage()); } ui.showLine(); } @@ -124,8 +128,8 @@ public static void markTask(String command) { String args = Parser.extractIndexArg(command, "mark"); int idx = Parser.parseIndex(args); if (idx >= 0 && idx < tasks.size()) { - tasks.get(idx).markDone(); - storage.save(tasks); + tasks.markDone(idx); + storage.save(tasks.toArrayList()); ui.showMessage("I've marked the task as done!"); ui.showMessage(" " + tasks.get(idx)); } else { @@ -143,8 +147,8 @@ public static void unmarkTask(String command) { String args = Parser.extractIndexArg(command, "unmark"); int idx = Parser.parseIndex(args); if (idx >= 0 && idx < tasks.size()) { - tasks.get(idx).markUndone(); - storage.save(tasks); + tasks.markUndone(idx); + storage.save(tasks.toArrayList()); ui.showMessage("I've marked the task as not done yet."); ui.showMessage(" " + tasks.get(idx)); } else { @@ -159,7 +163,7 @@ public static void unmarkTask(String command) { public static void start() { ui.showLine(); try { - tasks = storage.load(); + tasks = new TaskList(storage.load()); } catch (AmiaException e) { ui.showMessage(e.getMessage()); } diff --git a/src/main/java/TaskList.java b/src/main/java/TaskList.java new file mode 100644 index 0000000000..f9ddef4850 --- /dev/null +++ b/src/main/java/TaskList.java @@ -0,0 +1,50 @@ +import java.util.ArrayList; + +public class TaskList { + private ArrayList tasks; + + public TaskList() { + this.tasks = new ArrayList<>(); + } + + public TaskList(ArrayList tasks) { + this.tasks = tasks == null ? new ArrayList<>() : tasks; + } + + public int size() { + return tasks.size(); + } + + public void add(Task task) throws AmiaException { + if (task == null) { + throw new AmiaException("...Cannot add null task ..."); + } + tasks.add(task); + } + + public Task remove(int index) throws AmiaException { + if (index < 0 || index >= tasks.size()) { + throw new AmiaException("...Invalid task number..."); + } + return tasks.remove(index); + } + + public Task get(int index) throws AmiaException { + if (index < 0 || index >= tasks.size()) { + throw new AmiaException("...Invalid task number..."); + } + return tasks.get(index); + } + + public void markDone(int index) throws AmiaException { + get(index).markDone(); + } + + public void markUndone(int index) throws AmiaException { + get(index).markUndone(); + } + + public ArrayList toArrayList() { + return tasks; + } +} From 7667bec3fc6d19438c605616f6e7da98fbf7662d Mon Sep 17 00:00:00 2001 From: underscoregt Date: Wed, 11 Feb 2026 23:45:17 +0800 Subject: [PATCH 27/71] Centralize index validation in TaskList Refactor Amia and TaskList to centralize index validation as validateIndex(). --- src/main/java/Amia.java | 38 ++++++++++++----------------------- src/main/java/TaskList.java | 40 ++++++++++++++++++++----------------- 2 files changed, 35 insertions(+), 43 deletions(-) diff --git a/src/main/java/Amia.java b/src/main/java/Amia.java index efb6d6dc8c..53d1025332 100644 --- a/src/main/java/Amia.java +++ b/src/main/java/Amia.java @@ -89,15 +89,11 @@ public static void deleteTask(String command) { try { String args = Parser.extractIndexArg(command, "delete"); int idx = Parser.parseIndex(args); - if (idx >= 0 && idx < tasks.size()) { - Task removedTask = tasks.remove(idx); - storage.save(tasks.toArrayList()); - ui.showMessage("I've removed this task:"); - ui.showMessage(" " + removedTask); - ui.showMessage("You have " + tasks.size() + " task" + (tasks.size() == 1 ? "" : "s") + "."); - } else { - throw new AmiaException("... Invalid task number..."); - } + Task removedTask = tasks.remove(idx); + storage.save(tasks.toArrayList()); + ui.showMessage("I've removed this task:"); + ui.showMessage(" " + removedTask); + ui.showMessage("You have " + tasks.size() + " task" + (tasks.size() == 1 ? "" : "s") + "."); } catch (AmiaException e) { ui.showMessage(e.getMessage()); } @@ -127,14 +123,10 @@ public static void markTask(String command) { try { String args = Parser.extractIndexArg(command, "mark"); int idx = Parser.parseIndex(args); - if (idx >= 0 && idx < tasks.size()) { - tasks.markDone(idx); - storage.save(tasks.toArrayList()); - ui.showMessage("I've marked the task as done!"); - ui.showMessage(" " + tasks.get(idx)); - } else { - throw new AmiaException("... Invalid task number..."); - } + tasks.markDone(idx); + storage.save(tasks.toArrayList()); + ui.showMessage("I've marked the task as done!"); + ui.showMessage(" " + tasks.get(idx)); } catch (AmiaException e) { ui.showMessage(e.getMessage()); } @@ -146,14 +138,10 @@ public static void unmarkTask(String command) { try { String args = Parser.extractIndexArg(command, "unmark"); int idx = Parser.parseIndex(args); - if (idx >= 0 && idx < tasks.size()) { - tasks.markUndone(idx); - storage.save(tasks.toArrayList()); - ui.showMessage("I've marked the task as not done yet."); - ui.showMessage(" " + tasks.get(idx)); - } else { - throw new AmiaException("... Invalid task number..."); - } + tasks.markUndone(idx); + storage.save(tasks.toArrayList()); + ui.showMessage("I've marked the task as not done yet."); + ui.showMessage(" " + tasks.get(idx)); } catch (AmiaException e) { ui.showMessage(e.getMessage()); } diff --git a/src/main/java/TaskList.java b/src/main/java/TaskList.java index f9ddef4850..8b4e112b9d 100644 --- a/src/main/java/TaskList.java +++ b/src/main/java/TaskList.java @@ -8,13 +8,15 @@ public TaskList() { } public TaskList(ArrayList tasks) { - this.tasks = tasks == null ? new ArrayList<>() : tasks; + this.tasks = tasks; } - public int size() { - return tasks.size(); + public void validateIndex(int idx) throws AmiaException { + if (idx < 0 || idx >= tasks.size()) { + throw new AmiaException("... Invalid task number..."); + } } - + public void add(Task task) throws AmiaException { if (task == null) { throw new AmiaException("...Cannot add null task ..."); @@ -22,26 +24,28 @@ public void add(Task task) throws AmiaException { tasks.add(task); } - public Task remove(int index) throws AmiaException { - if (index < 0 || index >= tasks.size()) { - throw new AmiaException("...Invalid task number..."); - } - return tasks.remove(index); + public Task remove(int idx) throws AmiaException { + validateIndex(idx); + return tasks.remove(idx); } - public Task get(int index) throws AmiaException { - if (index < 0 || index >= tasks.size()) { - throw new AmiaException("...Invalid task number..."); - } - return tasks.get(index); + public Task get(int idx) throws AmiaException { + validateIndex(idx); + return tasks.get(idx); + } + + public void markDone(int idx) throws AmiaException { + validateIndex(idx); + tasks.get(idx).markDone(); } - public void markDone(int index) throws AmiaException { - get(index).markDone(); + public void markUndone(int idx) throws AmiaException { + validateIndex(idx); + tasks.get(idx).markUndone(); } - public void markUndone(int index) throws AmiaException { - get(index).markUndone(); + public int size() { + return tasks.size(); } public ArrayList toArrayList() { From e8a3aa1a069aaae98008096c2d12e7b6a37f5792 Mon Sep 17 00:00:00 2001 From: underscoregt Date: Thu, 12 Feb 2026 00:29:15 +0800 Subject: [PATCH 28/71] Fix bug regarding loading and saving times Persist task date/time fields using DateTimeFormatter.ISO_LOCAL_DATE_TIME. --- src/main/java/Deadline.java | 3 ++- src/main/java/Event.java | 3 ++- src/main/java/Storage.java | 11 ++++++++--- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/main/java/Deadline.java b/src/main/java/Deadline.java index 5b7c4e12ec..d4ffff4cd2 100644 --- a/src/main/java/Deadline.java +++ b/src/main/java/Deadline.java @@ -8,6 +8,7 @@ public class Deadline extends Task { DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm"); private static final DateTimeFormatter OUTPUT_FORMAT = DateTimeFormatter.ofPattern("MMM dd yyyy, h:mma"); + private static final DateTimeFormatter FILE_FORMAT = DateTimeFormatter.ISO_LOCAL_DATE_TIME; protected LocalDateTime by; @@ -27,6 +28,6 @@ public String toString() { @Override public String toFileString() { - return "D | " + (isDone ? "1" : "0") + " | " + description + " | " + by; + return "D | " + (isDone ? "1" : "0") + " | " + description + " | " + by.format(FILE_FORMAT); } } diff --git a/src/main/java/Event.java b/src/main/java/Event.java index 9a0ccdd695..48689a4c26 100644 --- a/src/main/java/Event.java +++ b/src/main/java/Event.java @@ -10,6 +10,7 @@ public class Event extends Task { DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm"); private static final DateTimeFormatter OUTPUT_FORMAT = DateTimeFormatter.ofPattern("MMM dd yyyy, h:mma"); + private static final DateTimeFormatter FILE_FORMAT = DateTimeFormatter.ISO_LOCAL_DATE_TIME; public Event(String description, String fromStr, String toStr) throws AmiaException{ super(description); @@ -28,6 +29,6 @@ public String toString() { @Override public String toFileString() { - return "E | " + (isDone ? "1" : "0") + " | " + description + " | " + from + " | " + to; + return "E | " + (isDone ? "1" : "0") + " | " + description + " | " + from.format(FILE_FORMAT) + " | " + to.format(FILE_FORMAT); } } diff --git a/src/main/java/Storage.java b/src/main/java/Storage.java index 2d1bf8eb8b..649f65d357 100644 --- a/src/main/java/Storage.java +++ b/src/main/java/Storage.java @@ -1,8 +1,13 @@ import java.io.*; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; public class Storage { private final String filePath; + private static final DateTimeFormatter INPUT_FORMAT = + DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm"); + private static final DateTimeFormatter FILE_FORMAT = DateTimeFormatter.ISO_LOCAL_DATE_TIME; public Storage(String filePath) { this.filePath = filePath == null || filePath.isEmpty() ? "./data/amia.txt" : filePath; @@ -13,7 +18,7 @@ public ArrayList load() throws AmiaException { File file = new File(filePath); if (!file.exists()) { - return tasks; // no file yet = no tasks + return tasks; } try (BufferedReader br = new BufferedReader(new FileReader(file))) { @@ -30,10 +35,10 @@ public ArrayList load() throws AmiaException { task = new ToDo(parts[2]); break; case "D": - task = new Deadline(parts[2], parts[3]); + task = new Deadline(parts[2], LocalDateTime.parse(parts[3], FILE_FORMAT).format(INPUT_FORMAT)); break; case "E": - task = new Event(parts[2], parts[3], parts[4]); + task = new Event(parts[2], LocalDateTime.parse(parts[3], FILE_FORMAT).format(INPUT_FORMAT), LocalDateTime.parse(parts[4], FILE_FORMAT).format(INPUT_FORMAT)); break; default: continue; From 54dfb34258ba1f8a034ecb4c62b44bf7598f64f3 Mon Sep 17 00:00:00 2001 From: underscoregt Date: Thu, 12 Feb 2026 01:17:30 +0800 Subject: [PATCH 29/71] Organize code into amia subpackages Classes were relocated into amia, amia.exception, amia.parser, amia.storage, amia.task, and amia.ui packages. --- src/main/java/{ => amia}/Amia.java | 12 ++++++++++++ .../java/{ => amia/exception}/AmiaException.java | 1 + src/main/java/{ => amia/parser}/CommandType.java | 1 + src/main/java/{ => amia/parser}/Parser.java | 4 ++++ src/main/java/{ => amia/storage}/Storage.java | 7 +++++++ src/main/java/{ => amia/task}/Deadline.java | 3 +++ src/main/java/{ => amia/task}/Event.java | 3 +++ src/main/java/{ => amia/task}/Task.java | 1 + src/main/java/{ => amia/task}/TaskList.java | 3 +++ src/main/java/{ => amia/task}/ToDo.java | 1 + src/main/java/{ => amia/ui}/Ui.java | 1 + 11 files changed, 37 insertions(+) rename src/main/java/{ => amia}/Amia.java (95%) rename src/main/java/{ => amia/exception}/AmiaException.java (83%) rename src/main/java/{ => amia/parser}/CommandType.java (96%) rename src/main/java/{ => amia/parser}/Parser.java (98%) rename src/main/java/{ => amia/storage}/Storage.java (94%) rename src/main/java/{ => amia/task}/Deadline.java (95%) rename src/main/java/{ => amia/task}/Event.java (95%) rename src/main/java/{ => amia/task}/Task.java (96%) rename src/main/java/{ => amia/task}/TaskList.java (95%) rename src/main/java/{ => amia/task}/ToDo.java (94%) rename src/main/java/{ => amia/ui}/Ui.java (97%) diff --git a/src/main/java/Amia.java b/src/main/java/amia/Amia.java similarity index 95% rename from src/main/java/Amia.java rename to src/main/java/amia/Amia.java index 53d1025332..f254f33bc0 100644 --- a/src/main/java/Amia.java +++ b/src/main/java/amia/Amia.java @@ -1,5 +1,17 @@ +package amia; import java.util.ArrayList; +import amia.exception.AmiaException; +import amia.parser.CommandType; +import amia.parser.Parser; +import amia.storage.Storage; +import amia.task.Deadline; +import amia.task.Event; +import amia.task.Task; +import amia.task.TaskList; +import amia.task.ToDo; +import amia.ui.Ui; + public class Amia { private static int MAX_TASKS = 100; private static String FILE_PATH = "./data/amia.txt"; diff --git a/src/main/java/AmiaException.java b/src/main/java/amia/exception/AmiaException.java similarity index 83% rename from src/main/java/AmiaException.java rename to src/main/java/amia/exception/AmiaException.java index fb8b021a6f..b5951ef17e 100644 --- a/src/main/java/AmiaException.java +++ b/src/main/java/amia/exception/AmiaException.java @@ -1,3 +1,4 @@ +package amia.exception; public class AmiaException extends Exception { public AmiaException(String message) { super(message); diff --git a/src/main/java/CommandType.java b/src/main/java/amia/parser/CommandType.java similarity index 96% rename from src/main/java/CommandType.java rename to src/main/java/amia/parser/CommandType.java index 593fb36532..12642c967d 100644 --- a/src/main/java/CommandType.java +++ b/src/main/java/amia/parser/CommandType.java @@ -1,3 +1,4 @@ +package amia.parser; public enum CommandType { TODO("todo"), DEADLINE("deadline"), diff --git a/src/main/java/Parser.java b/src/main/java/amia/parser/Parser.java similarity index 98% rename from src/main/java/Parser.java rename to src/main/java/amia/parser/Parser.java index 62aeaafe08..b040ea2c57 100644 --- a/src/main/java/Parser.java +++ b/src/main/java/amia/parser/Parser.java @@ -1,3 +1,7 @@ +package amia.parser; + +import amia.exception.AmiaException; + public class Parser { public static CommandType parseCommandType(String input) throws AmiaException { diff --git a/src/main/java/Storage.java b/src/main/java/amia/storage/Storage.java similarity index 94% rename from src/main/java/Storage.java rename to src/main/java/amia/storage/Storage.java index 649f65d357..c1f2c1e91e 100644 --- a/src/main/java/Storage.java +++ b/src/main/java/amia/storage/Storage.java @@ -1,8 +1,15 @@ +package amia.storage; import java.io.*; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; +import amia.exception.AmiaException; +import amia.task.Deadline; +import amia.task.Event; +import amia.task.Task; +import amia.task.ToDo; + public class Storage { private final String filePath; private static final DateTimeFormatter INPUT_FORMAT = diff --git a/src/main/java/Deadline.java b/src/main/java/amia/task/Deadline.java similarity index 95% rename from src/main/java/Deadline.java rename to src/main/java/amia/task/Deadline.java index d4ffff4cd2..8df330cc95 100644 --- a/src/main/java/Deadline.java +++ b/src/main/java/amia/task/Deadline.java @@ -1,8 +1,11 @@ +package amia.task; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; +import amia.exception.AmiaException; + public class Deadline extends Task { private static final DateTimeFormatter INPUT_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm"); diff --git a/src/main/java/Event.java b/src/main/java/amia/task/Event.java similarity index 95% rename from src/main/java/Event.java rename to src/main/java/amia/task/Event.java index 48689a4c26..41afb8005d 100644 --- a/src/main/java/Event.java +++ b/src/main/java/amia/task/Event.java @@ -1,7 +1,10 @@ +package amia.task; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; +import amia.exception.AmiaException; + public class Event extends Task { private LocalDateTime from; private LocalDateTime to; diff --git a/src/main/java/Task.java b/src/main/java/amia/task/Task.java similarity index 96% rename from src/main/java/Task.java rename to src/main/java/amia/task/Task.java index 1e73b831d5..bcc52c8508 100644 --- a/src/main/java/Task.java +++ b/src/main/java/amia/task/Task.java @@ -1,3 +1,4 @@ +package amia.task; public abstract class Task { protected String description; protected boolean isDone; diff --git a/src/main/java/TaskList.java b/src/main/java/amia/task/TaskList.java similarity index 95% rename from src/main/java/TaskList.java rename to src/main/java/amia/task/TaskList.java index 8b4e112b9d..227429af2c 100644 --- a/src/main/java/TaskList.java +++ b/src/main/java/amia/task/TaskList.java @@ -1,5 +1,8 @@ +package amia.task; import java.util.ArrayList; +import amia.exception.AmiaException; + public class TaskList { private ArrayList tasks; diff --git a/src/main/java/ToDo.java b/src/main/java/amia/task/ToDo.java similarity index 94% rename from src/main/java/ToDo.java rename to src/main/java/amia/task/ToDo.java index 5544ac38ce..0f656c9804 100644 --- a/src/main/java/ToDo.java +++ b/src/main/java/amia/task/ToDo.java @@ -1,3 +1,4 @@ +package amia.task; public class ToDo extends Task{ public ToDo(String decsription) { super(decsription); diff --git a/src/main/java/Ui.java b/src/main/java/amia/ui/Ui.java similarity index 97% rename from src/main/java/Ui.java rename to src/main/java/amia/ui/Ui.java index 9b5075dbbd..926e754b5b 100644 --- a/src/main/java/Ui.java +++ b/src/main/java/amia/ui/Ui.java @@ -1,3 +1,4 @@ +package amia.ui; import java.util.Scanner; public class Ui { From 415f86c1d584a14f6606909b999c8924a304127b Mon Sep 17 00:00:00 2001 From: underscoregt Date: Thu, 12 Feb 2026 08:16:49 +0800 Subject: [PATCH 30/71] Enable Checkstyle, Java 17, update main class - Add Checkstyle plugin and placeholder config files, set Checkstyle toolVersion to 11.0.0, and configure Java toolchain to use Java 17. - Update application main class from seedu.duke.Duke to amia.Amia. --- build.gradle | 13 ++++++++++++- config/checkstyle/checkstyle.xml | 0 config/checkstyle/suppressions.xml | 0 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 config/checkstyle/checkstyle.xml create mode 100644 config/checkstyle/suppressions.xml diff --git a/build.gradle b/build.gradle index c361f9811e..3902e606ad 100644 --- a/build.gradle +++ b/build.gradle @@ -1,9 +1,20 @@ plugins { id 'java' id 'application' + id 'checkstyle' id 'com.github.johnrengelman.shadow' version '7.1.2' } +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +checkstyle { + toolVersion = '11.0.0' +} + repositories { mavenCentral() } @@ -28,7 +39,7 @@ test { } application { - mainClass.set("seedu.duke.Duke") + mainClass.set("amia.Amia") } shadowJar { diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..e69de29bb2 diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml new file mode 100644 index 0000000000..e69de29bb2 From e95b46acd8578e953aca85ce7d0188f9cbae6ad1 Mon Sep 17 00:00:00 2001 From: underscoregt Date: Fri, 13 Feb 2026 17:38:58 +0800 Subject: [PATCH 31/71] Add checkstyle.xml and suppressions.xml --- config/checkstyle/checkstyle.xml | 434 +++++++++++++++++++++++++++++ config/checkstyle/suppressions.xml | 10 + 2 files changed, 444 insertions(+) diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml index e69de29bb2..d1399810b5 100644 --- a/config/checkstyle/checkstyle.xml +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,434 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml index e69de29bb2..dcaa1af3c3 100644 --- a/config/checkstyle/suppressions.xml +++ b/config/checkstyle/suppressions.xml @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file From dcf511aa2a3f5aeb3b1833d8f6a53089234afd63 Mon Sep 17 00:00:00 2001 From: underscoregt Date: Fri, 13 Feb 2026 17:59:17 +0800 Subject: [PATCH 32/71] Add unit tests for Parser and TaskList Introduce ParserTest and TaskListTest JUnit 5 tests. ParserTest verifies parseCommandType, extractDescription, and parseIndex (including invalid input error). TaskListTest covers adding/removing tasks and markDone/markUndone behavior, asserting size and task string state. Tests use AmiaException for error cases. --- src/test/java/amia/parser/ParserTest.java | 43 ++++++++++++++++++++ src/test/java/amia/task/TaskListTest.java | 48 +++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 src/test/java/amia/parser/ParserTest.java create mode 100644 src/test/java/amia/task/TaskListTest.java diff --git a/src/test/java/amia/parser/ParserTest.java b/src/test/java/amia/parser/ParserTest.java new file mode 100644 index 0000000000..4fcff42e8c --- /dev/null +++ b/src/test/java/amia/parser/ParserTest.java @@ -0,0 +1,43 @@ +package amia.parser; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import amia.exception.AmiaException; + +public class ParserTest { + + @Test + public void testParseCommandType() throws AmiaException { + CommandType type1 = Parser.parseCommandType("todo read book"); + assertEquals(CommandType.TODO, type1); + + CommandType type2 = Parser.parseCommandType("list"); + assertEquals(CommandType.LIST, type2); + + CommandType type3 = Parser.parseCommandType("bye"); + assertEquals(CommandType.BYE, type3); + } + + @Test + public void testExtractDescription() throws AmiaException { + String desc = Parser.extractDescription("todo read a book", "todo"); + assertEquals("read a book", desc); + + String desc2 = Parser.extractDescription("deadline submit project /by 2024-02-15", "deadline"); + assertEquals("submit project /by 2024-02-15", desc2); + } + + @Test + public void testParseIndex() throws AmiaException { + int idx1 = Parser.parseIndex("1"); + assertEquals(0, idx1); + + int idx2 = Parser.parseIndex("5"); + assertEquals(4, idx2); + + assertThrows(AmiaException.class, () -> { + Parser.parseIndex("abc"); + }); + } +} diff --git a/src/test/java/amia/task/TaskListTest.java b/src/test/java/amia/task/TaskListTest.java new file mode 100644 index 0000000000..6797d196d4 --- /dev/null +++ b/src/test/java/amia/task/TaskListTest.java @@ -0,0 +1,48 @@ +package amia.task; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +import amia.exception.AmiaException; + +public class TaskListTest { + private TaskList taskList; + + @BeforeEach + public void setUp() { + taskList = new TaskList(); + } + + @Test + public void testAddValidTask() throws AmiaException { + Task task = new ToDo("Read book"); + taskList.add(task); + assertEquals(1, taskList.size()); + assertEquals(task, taskList.get(0)); + } + + @Test + public void testRemoveValidTask() throws AmiaException { + Task task1 = new ToDo("Task 1"); + Task task2 = new ToDo("Task 2"); + taskList.add(task1); + taskList.add(task2); + + Task removed = taskList.remove(0); + assertEquals(1, taskList.size()); + assertEquals(task2, taskList.get(0)); + } + + @Test + public void testMarkAndUnmarkTask() throws AmiaException { + Task task = new ToDo("Task 1"); + taskList.add(task); + + assertTrue(taskList.get(0).toString().contains("[ ]")); + taskList.markDone(0); + assertTrue(taskList.get(0).toString().contains("[X]")); + taskList.markUndone(0); + assertTrue(taskList.get(0).toString().contains("[ ]")); + } +} From 2ff9a1c6e1ff23840994bdcc5657413633d8569f Mon Sep 17 00:00:00 2001 From: underscoregt Date: Fri, 13 Feb 2026 18:00:26 +0800 Subject: [PATCH 33/71] Format code & clean up whitespace Apply code formatting and minor cleanups across the project: remove unused imports, normalize DateTimeFormatter declarations to single-line, fix indentation/spacing, wrap long lines for readability, and remove stray trailing whitespace. Affects Amia.java, AmiaException.java, Storage.java, Deadline.java, Event.java, Task.java, TaskList.java, and ToDo.java. No functional behavior changes. --- src/main/java/amia/Amia.java | 52 +++++++++---------- .../java/amia/exception/AmiaException.java | 1 + src/main/java/amia/storage/Storage.java | 27 +++++----- src/main/java/amia/task/Deadline.java | 8 ++- src/main/java/amia/task/Event.java | 15 +++--- src/main/java/amia/task/Task.java | 1 + src/main/java/amia/task/TaskList.java | 3 +- src/main/java/amia/task/ToDo.java | 3 +- 8 files changed, 56 insertions(+), 54 deletions(-) diff --git a/src/main/java/amia/Amia.java b/src/main/java/amia/Amia.java index f254f33bc0..05850647eb 100644 --- a/src/main/java/amia/Amia.java +++ b/src/main/java/amia/Amia.java @@ -1,5 +1,4 @@ package amia; -import java.util.ArrayList; import amia.exception.AmiaException; import amia.parser.CommandType; @@ -31,30 +30,30 @@ public static void loop() { try { String command = ui.readCommand(); CommandType cmdType = Parser.parseCommandType(command); - + switch (cmdType) { - case TODO: - case DEADLINE: - case EVENT: - addTask(command); - break; - case MARK: - markTask(command); - break; - case UNMARK: - unmarkTask(command); - break; - case DELETE: - deleteTask(command); - break; - case LIST: - listTask(); - break; - case BYE: - ui.close(); - return; - case UNKNOWN: - throw new AmiaException("...?"); + case TODO: + case DEADLINE: + case EVENT: + addTask(command); + break; + case MARK: + markTask(command); + break; + case UNMARK: + unmarkTask(command); + break; + case DELETE: + deleteTask(command); + break; + case LIST: + listTask(); + break; + case BYE: + ui.close(); + return; + case UNKNOWN: + throw new AmiaException("...?"); } } catch (AmiaException e) { ui.showLine(); @@ -67,7 +66,7 @@ public static void loop() { public static void addTask(String command) { ui.showLine(); try { - if (tasks.size() < MAX_TASKS) { + if (tasks.size() < MAX_TASKS) { Task task; if (command.startsWith("todo")) { String desc = Parser.extractDescription(command, "todo"); @@ -116,7 +115,7 @@ public static void listTask() { ui.showLine(); try { if (tasks.size() == 0) { - ui.showMessage("No tasks to list..."); + ui.showMessage("No tasks to list..."); ui.showLine(); return; } @@ -176,5 +175,4 @@ public static void exit() { ui.showGoodbye(); } - } diff --git a/src/main/java/amia/exception/AmiaException.java b/src/main/java/amia/exception/AmiaException.java index b5951ef17e..f65e8a6dc2 100644 --- a/src/main/java/amia/exception/AmiaException.java +++ b/src/main/java/amia/exception/AmiaException.java @@ -1,4 +1,5 @@ package amia.exception; + public class AmiaException extends Exception { public AmiaException(String message) { super(message); diff --git a/src/main/java/amia/storage/Storage.java b/src/main/java/amia/storage/Storage.java index c1f2c1e91e..bda9645985 100644 --- a/src/main/java/amia/storage/Storage.java +++ b/src/main/java/amia/storage/Storage.java @@ -1,4 +1,5 @@ package amia.storage; + import java.io.*; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @@ -12,8 +13,7 @@ public class Storage { private final String filePath; - private static final DateTimeFormatter INPUT_FORMAT = - DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm"); + private static final DateTimeFormatter INPUT_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm"); private static final DateTimeFormatter FILE_FORMAT = DateTimeFormatter.ISO_LOCAL_DATE_TIME; public Storage(String filePath) { @@ -38,17 +38,18 @@ public ArrayList load() throws AmiaException { Task task; switch (type) { - case "T": - task = new ToDo(parts[2]); - break; - case "D": - task = new Deadline(parts[2], LocalDateTime.parse(parts[3], FILE_FORMAT).format(INPUT_FORMAT)); - break; - case "E": - task = new Event(parts[2], LocalDateTime.parse(parts[3], FILE_FORMAT).format(INPUT_FORMAT), LocalDateTime.parse(parts[4], FILE_FORMAT).format(INPUT_FORMAT)); - break; - default: - continue; + case "T": + task = new ToDo(parts[2]); + break; + case "D": + task = new Deadline(parts[2], LocalDateTime.parse(parts[3], FILE_FORMAT).format(INPUT_FORMAT)); + break; + case "E": + task = new Event(parts[2], LocalDateTime.parse(parts[3], FILE_FORMAT).format(INPUT_FORMAT), + LocalDateTime.parse(parts[4], FILE_FORMAT).format(INPUT_FORMAT)); + break; + default: + continue; } if (isDone) { diff --git a/src/main/java/amia/task/Deadline.java b/src/main/java/amia/task/Deadline.java index 8df330cc95..c3a01ab48f 100644 --- a/src/main/java/amia/task/Deadline.java +++ b/src/main/java/amia/task/Deadline.java @@ -1,4 +1,5 @@ package amia.task; + import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @@ -7,13 +8,10 @@ import amia.exception.AmiaException; public class Deadline extends Task { - private static final DateTimeFormatter INPUT_FORMAT = - DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm"); - private static final DateTimeFormatter OUTPUT_FORMAT = - DateTimeFormatter.ofPattern("MMM dd yyyy, h:mma"); + private static final DateTimeFormatter INPUT_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm"); + private static final DateTimeFormatter OUTPUT_FORMAT = DateTimeFormatter.ofPattern("MMM dd yyyy, h:mma"); private static final DateTimeFormatter FILE_FORMAT = DateTimeFormatter.ISO_LOCAL_DATE_TIME; protected LocalDateTime by; - public Deadline(String description, String byStr) throws AmiaException { super(description); diff --git a/src/main/java/amia/task/Event.java b/src/main/java/amia/task/Event.java index 41afb8005d..d6311022f9 100644 --- a/src/main/java/amia/task/Event.java +++ b/src/main/java/amia/task/Event.java @@ -1,4 +1,5 @@ package amia.task; + import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; @@ -9,13 +10,11 @@ public class Event extends Task { private LocalDateTime from; private LocalDateTime to; - private static final DateTimeFormatter INPUT_FORMAT = - DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm"); - private static final DateTimeFormatter OUTPUT_FORMAT = - DateTimeFormatter.ofPattern("MMM dd yyyy, h:mma"); + private static final DateTimeFormatter INPUT_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm"); + private static final DateTimeFormatter OUTPUT_FORMAT = DateTimeFormatter.ofPattern("MMM dd yyyy, h:mma"); private static final DateTimeFormatter FILE_FORMAT = DateTimeFormatter.ISO_LOCAL_DATE_TIME; - public Event(String description, String fromStr, String toStr) throws AmiaException{ + public Event(String description, String fromStr, String toStr) throws AmiaException { super(description); try { this.from = LocalDateTime.parse(fromStr, INPUT_FORMAT); @@ -27,11 +26,13 @@ public Event(String description, String fromStr, String toStr) throws AmiaExcept @Override public String toString() { - return "[E]" + super.toString() + " (from: " + from.format(OUTPUT_FORMAT) + " to: " + to.format(OUTPUT_FORMAT) + ")"; + return "[E]" + super.toString() + " (from: " + from.format(OUTPUT_FORMAT) + " to: " + to.format(OUTPUT_FORMAT) + + ")"; } @Override public String toFileString() { - return "E | " + (isDone ? "1" : "0") + " | " + description + " | " + from.format(FILE_FORMAT) + " | " + to.format(FILE_FORMAT); + return "E | " + (isDone ? "1" : "0") + " | " + description + " | " + from.format(FILE_FORMAT) + " | " + + to.format(FILE_FORMAT); } } diff --git a/src/main/java/amia/task/Task.java b/src/main/java/amia/task/Task.java index bcc52c8508..39fc1e01e6 100644 --- a/src/main/java/amia/task/Task.java +++ b/src/main/java/amia/task/Task.java @@ -1,4 +1,5 @@ package amia.task; + public abstract class Task { protected String description; protected boolean isDone; diff --git a/src/main/java/amia/task/TaskList.java b/src/main/java/amia/task/TaskList.java index 227429af2c..810cb56be6 100644 --- a/src/main/java/amia/task/TaskList.java +++ b/src/main/java/amia/task/TaskList.java @@ -1,4 +1,5 @@ package amia.task; + import java.util.ArrayList; import amia.exception.AmiaException; @@ -19,7 +20,7 @@ public void validateIndex(int idx) throws AmiaException { throw new AmiaException("... Invalid task number..."); } } - + public void add(Task task) throws AmiaException { if (task == null) { throw new AmiaException("...Cannot add null task ..."); diff --git a/src/main/java/amia/task/ToDo.java b/src/main/java/amia/task/ToDo.java index 0f656c9804..6e25554d3a 100644 --- a/src/main/java/amia/task/ToDo.java +++ b/src/main/java/amia/task/ToDo.java @@ -1,5 +1,6 @@ package amia.task; -public class ToDo extends Task{ + +public class ToDo extends Task { public ToDo(String decsription) { super(decsription); } From 74b9f002cfedaa1747a1f1d8bfd07ee1ffaad52f Mon Sep 17 00:00:00 2001 From: underscoregt Date: Fri, 13 Feb 2026 18:24:45 +0800 Subject: [PATCH 34/71] Update build.gradle Change default archiveBaseName. --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3902e606ad..061de9eb54 100644 --- a/build.gradle +++ b/build.gradle @@ -43,7 +43,7 @@ application { } shadowJar { - archiveBaseName = "duke" + archiveBaseName = "amia" archiveClassifier = null } From 71c6358eeac02919ab1b3406c40abaa2b77683b1 Mon Sep 17 00:00:00 2001 From: underscoregt Date: Sat, 14 Feb 2026 02:22:12 +0800 Subject: [PATCH 35/71] Add Javadoc to core classes and methods --- src/main/java/amia/Amia.java | 43 +++++++++++++ .../java/amia/exception/AmiaException.java | 8 +++ src/main/java/amia/parser/Parser.java | 26 ++++++++ src/main/java/amia/storage/Storage.java | 22 +++++++ src/main/java/amia/task/Deadline.java | 10 ++++ src/main/java/amia/task/Event.java | 11 ++++ src/main/java/amia/task/Task.java | 31 ++++++++++ src/main/java/amia/task/TaskList.java | 60 +++++++++++++++++++ src/main/java/amia/task/ToDo.java | 3 + src/main/java/amia/ui/Ui.java | 29 +++++++++ 10 files changed, 243 insertions(+) diff --git a/src/main/java/amia/Amia.java b/src/main/java/amia/Amia.java index 05850647eb..8b26b4ccfb 100644 --- a/src/main/java/amia/Amia.java +++ b/src/main/java/amia/Amia.java @@ -11,6 +11,10 @@ import amia.task.ToDo; import amia.ui.Ui; +/** + * Main application class for the Amia task manager. Handles the main + * application loop and task management operations. + */ public class Amia { private static int MAX_TASKS = 100; private static String FILE_PATH = "./data/amia.txt"; @@ -19,12 +23,20 @@ public class Amia { private static Ui ui = new Ui(); private static Storage storage = new Storage(FILE_PATH); + /** + * Starts the application and runs the main loop. + * + * @param args Command line arguments (unused). + */ public static void main(String[] args) { start(); loop(); exit(); } + /** + * Continuously reads user commands and processes them until the user exits. + */ public static void loop() { while (true) { try { @@ -63,6 +75,12 @@ public static void loop() { } } + /** + * Adds a new task to the task list based on the command provided. Supports + * todo, deadline, and event task types. + * + * @param command The command string containing task type and details. + */ public static void addTask(String command) { ui.showLine(); try { @@ -95,6 +113,11 @@ public static void addTask(String command) { ui.showLine(); } + /** + * Deletes a task from the task list based on the index provided in the command. + * + * @param command The delete command containing the task index. + */ public static void deleteTask(String command) { ui.showLine(); try { @@ -111,6 +134,9 @@ public static void deleteTask(String command) { ui.showLine(); } + /** + * Displays all tasks in the task list. + */ public static void listTask() { ui.showLine(); try { @@ -129,6 +155,11 @@ public static void listTask() { ui.showLine(); } + /** + * Marks a task as done based on the index provided in the command. + * + * @param command The mark command containing the task index. + */ public static void markTask(String command) { ui.showLine(); try { @@ -144,6 +175,11 @@ public static void markTask(String command) { ui.showLine(); } + /** + * Marks a task as not done based on the index provided in the command. + * + * @param command The unmark command containing the task index. + */ public static void unmarkTask(String command) { ui.showLine(); try { @@ -159,6 +195,10 @@ public static void unmarkTask(String command) { ui.showLine(); } + /** + * Initializes the application by loading previously saved tasks and displaying + * a welcome message. + */ public static void start() { ui.showLine(); try { @@ -171,6 +211,9 @@ public static void start() { ui.showLine(); } + /** + * Displays the goodbye message when the application exits. + */ public static void exit() { ui.showGoodbye(); } diff --git a/src/main/java/amia/exception/AmiaException.java b/src/main/java/amia/exception/AmiaException.java index f65e8a6dc2..c549e276b9 100644 --- a/src/main/java/amia/exception/AmiaException.java +++ b/src/main/java/amia/exception/AmiaException.java @@ -1,6 +1,14 @@ package amia.exception; +/** + * Custom exception class for the Amia application. + */ public class AmiaException extends Exception { + /** + * Constructs an AmiaException with the given message. + * + * @param message The detail message. + */ public AmiaException(String message) { super(message); } diff --git a/src/main/java/amia/parser/Parser.java b/src/main/java/amia/parser/Parser.java index b040ea2c57..b17ef1410f 100644 --- a/src/main/java/amia/parser/Parser.java +++ b/src/main/java/amia/parser/Parser.java @@ -2,8 +2,18 @@ import amia.exception.AmiaException; +/** + * Parses user commands and extracts relevant information. + */ public class Parser { + /** + * Parses a command string and returns the corresponding CommandType. + * + * @param input The command string to parse. + * @return The CommandType corresponding to the input. + * @throws AmiaException If the input is null, empty, or unknown. + */ public static CommandType parseCommandType(String input) throws AmiaException { if (input == null || input.trim().isEmpty()) { throw new AmiaException("...?"); @@ -12,6 +22,14 @@ public static CommandType parseCommandType(String input) throws AmiaException { return cmdType; } + /** + * Extracts the description from a command by removing the keyword prefix. + * + * @param command The full command string. + * @param keyword The keyword prefix to remove (e.g., "todo", "deadline"). + * @return The extracted description. + * @throws AmiaException If the description is empty. + */ public static String extractDescription(String command, String keyword) throws AmiaException { String desc = command.substring(keyword.length()).trim(); if (desc.isEmpty()) { @@ -20,6 +38,14 @@ public static String extractDescription(String command, String keyword) throws A return desc; } + /** + * Extracts the index argument from a command by removing the keyword prefix. + * + * @param command The full command string. + * @param keyword The keyword prefix to remove (e.g., "mark", "delete"). + * @return The extracted index argument as a string. + * @throws AmiaException If the argument is empty. + */ public static String extractIndexArg(String command, String keyword) throws AmiaException { String args = command.substring(keyword.length()).trim(); if (args.isEmpty()) { diff --git a/src/main/java/amia/storage/Storage.java b/src/main/java/amia/storage/Storage.java index bda9645985..d3bdb4c020 100644 --- a/src/main/java/amia/storage/Storage.java +++ b/src/main/java/amia/storage/Storage.java @@ -11,15 +11,31 @@ import amia.task.Task; import amia.task.ToDo; +/** + * Handles loading and saving tasks to a file. + */ public class Storage { private final String filePath; private static final DateTimeFormatter INPUT_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm"); private static final DateTimeFormatter FILE_FORMAT = DateTimeFormatter.ISO_LOCAL_DATE_TIME; + /** + * Constructs a Storage object with the given file path. Uses a default path if + * the given path is null or empty. + * + * @param filePath The path to the file where tasks will be saved. + */ public Storage(String filePath) { this.filePath = filePath == null || filePath.isEmpty() ? "./data/amia.txt" : filePath; } + /** + * Loads tasks from the storage file. Returns an empty list if the file does not + * exist. + * + * @return An ArrayList of tasks loaded from the file. + * @throws AmiaException If an error occurs while reading the file. + */ public ArrayList load() throws AmiaException { ArrayList tasks = new ArrayList<>(); @@ -65,6 +81,12 @@ public ArrayList load() throws AmiaException { return tasks; } + /** + * Saves tasks to the storage file. Creates the directory if it does not exist. + * + * @param tasks The ArrayList of tasks to save. + * @throws AmiaException If an error occurs while writing to the file. + */ public void save(ArrayList tasks) throws AmiaException { try { File dir = new File(new File(filePath).getParent()); diff --git a/src/main/java/amia/task/Deadline.java b/src/main/java/amia/task/Deadline.java index c3a01ab48f..fdbe7f9acb 100644 --- a/src/main/java/amia/task/Deadline.java +++ b/src/main/java/amia/task/Deadline.java @@ -7,12 +7,22 @@ import amia.exception.AmiaException; +/** + * Represents a task with a deadline. + */ public class Deadline extends Task { private static final DateTimeFormatter INPUT_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm"); private static final DateTimeFormatter OUTPUT_FORMAT = DateTimeFormatter.ofPattern("MMM dd yyyy, h:mma"); private static final DateTimeFormatter FILE_FORMAT = DateTimeFormatter.ISO_LOCAL_DATE_TIME; protected LocalDateTime by; + /** + * Constructs a Deadline task with the given description and deadline. + * + * @param description The description of the task. + * @param byStr The deadline in the format "yyyy-MM-dd HHmm". + * @throws AmiaException If the date/time format is invalid. + */ public Deadline(String description, String byStr) throws AmiaException { super(description); try { diff --git a/src/main/java/amia/task/Event.java b/src/main/java/amia/task/Event.java index d6311022f9..b302c4df08 100644 --- a/src/main/java/amia/task/Event.java +++ b/src/main/java/amia/task/Event.java @@ -6,6 +6,9 @@ import amia.exception.AmiaException; +/** + * Represents an event task with a start and end time. + */ public class Event extends Task { private LocalDateTime from; private LocalDateTime to; @@ -14,6 +17,14 @@ public class Event extends Task { private static final DateTimeFormatter OUTPUT_FORMAT = DateTimeFormatter.ofPattern("MMM dd yyyy, h:mma"); private static final DateTimeFormatter FILE_FORMAT = DateTimeFormatter.ISO_LOCAL_DATE_TIME; + /** + * Constructs an Event task with the given description and time range. + * + * @param description The description of the event. + * @param fromStr The start time in the format "yyyy-MM-dd HHmm". + * @param toStr The end time in the format "yyyy-MM-dd HHmm". + * @throws AmiaException If the date/time format is invalid. + */ public Event(String description, String fromStr, String toStr) throws AmiaException { super(description); try { diff --git a/src/main/java/amia/task/Task.java b/src/main/java/amia/task/Task.java index 39fc1e01e6..676c33cdc2 100644 --- a/src/main/java/amia/task/Task.java +++ b/src/main/java/amia/task/Task.java @@ -1,30 +1,61 @@ package amia.task; +/** + * Abstract base class representing a task. Provides the common functionality + * for all task types. + */ public abstract class Task { protected String description; protected boolean isDone; + /** + * Constructs a Task with the given description. + * + * @param description The description of the task. + */ public Task(String description) { this.description = description; this.isDone = false; } + /** + * Returns the status icon for this task. + * + * @return "X" if the task is done, " " otherwise. + */ public String getStatusIcon() { return isDone ? "X" : " "; } + /** + * Marks this task as done. + */ public void markDone() { this.isDone = true; } + /** + * Marks this task as not done. + */ public void markUndone() { this.isDone = false; } + /** + * Returns the string representation of this task with status icon and + * description. + * + * @return A string in the format "[status] description". + */ @Override public String toString() { return "[" + getStatusIcon() + "] " + description; } + /** + * Returns the file representation of this task for storage purposes. + * + * @return A string representation suitable for file storage. + */ public abstract String toFileString(); } diff --git a/src/main/java/amia/task/TaskList.java b/src/main/java/amia/task/TaskList.java index 810cb56be6..88d0dbe6f9 100644 --- a/src/main/java/amia/task/TaskList.java +++ b/src/main/java/amia/task/TaskList.java @@ -4,23 +4,47 @@ import amia.exception.AmiaException; +/** + * Manages a list of tasks. Provides methods to add, remove, retrieve, and mark + * tasks. + */ public class TaskList { private ArrayList tasks; + /** + * Constructs an empty TaskList. + */ public TaskList() { this.tasks = new ArrayList<>(); } + /** + * Constructs a TaskList with the given list of tasks. + * + * @param tasks The initial list of tasks. + */ public TaskList(ArrayList tasks) { this.tasks = tasks; } + /** + * Validates that the given index is within the bounds of the task list. + * + * @param idx The index to validate. + * @throws AmiaException If the index is out of bounds. + */ public void validateIndex(int idx) throws AmiaException { if (idx < 0 || idx >= tasks.size()) { throw new AmiaException("... Invalid task number..."); } } + /** + * Adds a task to the task list. + * + * @param task The task to add. + * @throws AmiaException If the task is null. + */ public void add(Task task) throws AmiaException { if (task == null) { throw new AmiaException("...Cannot add null task ..."); @@ -28,30 +52,66 @@ public void add(Task task) throws AmiaException { tasks.add(task); } + /** + * Removes and returns the task at the given index. + * + * @param idx The index of the task to remove. + * @return The removed task. + * @throws AmiaException If the index is out of bounds. + */ public Task remove(int idx) throws AmiaException { validateIndex(idx); return tasks.remove(idx); } + /** + * Retrieves the task at the given index. + * + * @param idx The index of the task to retrieve. + * @return The task at the specified index. + * @throws AmiaException If the index is out of bounds. + */ public Task get(int idx) throws AmiaException { validateIndex(idx); return tasks.get(idx); } + /** + * Marks the task at the given index as done. + * + * @param idx The index of the task to mark as done. + * @throws AmiaException If the index is out of bounds. + */ public void markDone(int idx) throws AmiaException { validateIndex(idx); tasks.get(idx).markDone(); } + /** + * Marks the task at the given index as not done. + * + * @param idx The index of the task to mark as not done. + * @throws AmiaException If the index is out of bounds. + */ public void markUndone(int idx) throws AmiaException { validateIndex(idx); tasks.get(idx).markUndone(); } + /** + * Returns the number of tasks in the task list. + * + * @return The size of the task list. + */ public int size() { return tasks.size(); } + /** + * Returns the underlying ArrayList of tasks. + * + * @return The ArrayList of tasks. + */ public ArrayList toArrayList() { return tasks; } diff --git a/src/main/java/amia/task/ToDo.java b/src/main/java/amia/task/ToDo.java index 6e25554d3a..e7b2407a8e 100644 --- a/src/main/java/amia/task/ToDo.java +++ b/src/main/java/amia/task/ToDo.java @@ -1,5 +1,8 @@ package amia.task; +/** + * Represents a simple to-do task. + */ public class ToDo extends Task { public ToDo(String decsription) { super(decsription); diff --git a/src/main/java/amia/ui/Ui.java b/src/main/java/amia/ui/Ui.java index 926e754b5b..04d41411de 100644 --- a/src/main/java/amia/ui/Ui.java +++ b/src/main/java/amia/ui/Ui.java @@ -1,29 +1,55 @@ package amia.ui; + import java.util.Scanner; +/** + * Handles the user interface and user input/output. + */ public class Ui { private Scanner scanner; + /** + * Constructs a Ui object and initializes the scanner for reading user input. + */ public Ui() { scanner = new Scanner(System.in); } + /** + * Reads a command line from the user. + * + * @return The user's command with leading and trailing whitespace removed. + */ public String readCommand() { return scanner.nextLine().trim(); } + /** + * Closes the scanner resource. + */ public void close() { scanner.close(); } + /** + * Displays a message to the user with indentation. + * + * @param s The message to display. + */ public void showMessage(String s) { System.out.println("\t" + s); } + /** + * Displays a line separator. + */ public void showLine() { System.out.println("-----------------------------------"); } + /** + * Displays the welcome message. + */ public void showWelcome() { showLine(); showMessage("Hello! I'm Amia!"); @@ -31,6 +57,9 @@ public void showWelcome() { showLine(); } + /** + * Displays the goodbye message. + */ public void showGoodbye() { showLine(); showMessage("Bye!"); From a99ecee977628ae1282f324dc58b0b96b4f7f052 Mon Sep 17 00:00:00 2001 From: underscoregt Date: Sat, 14 Feb 2026 02:36:10 +0800 Subject: [PATCH 36/71] Encapsulate Parser info fields; use getters Make MAX_TASKS and FILE_PATH constants final and encapsulate Parser.DeadlineInfo/EventInfo fields by changing them to private and adding getters. Update Amia to use the new getters when constructing Deadline and Event objects. Improves immutability and encapsulation of parser data. --- src/main/java/amia/Amia.java | 8 +++---- src/main/java/amia/parser/Parser.java | 30 ++++++++++++++++++++++----- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/main/java/amia/Amia.java b/src/main/java/amia/Amia.java index 05850647eb..3227c3c4c1 100644 --- a/src/main/java/amia/Amia.java +++ b/src/main/java/amia/Amia.java @@ -12,8 +12,8 @@ import amia.ui.Ui; public class Amia { - private static int MAX_TASKS = 100; - private static String FILE_PATH = "./data/amia.txt"; + private static final int MAX_TASKS = 100; + private static final String FILE_PATH = "./data/amia.txt"; private static TaskList tasks = new TaskList(); private static Ui ui = new Ui(); @@ -73,10 +73,10 @@ public static void addTask(String command) { task = new ToDo(desc); } else if (command.startsWith("deadline")) { Parser.DeadlineInfo info = Parser.parseDeadline(command); - task = new Deadline(info.description, info.deadline); + task = new Deadline(info.getDescription(), info.getDeadline()); } else if (command.startsWith("event")) { Parser.EventInfo info = Parser.parseEvent(command); - task = new Event(info.description, info.from, info.to); + task = new Event(info.getDescription(), info.getFrom(), info.getTo()); } else { task = new ToDo(command); } diff --git a/src/main/java/amia/parser/Parser.java b/src/main/java/amia/parser/Parser.java index b040ea2c57..ef5d9d87bc 100644 --- a/src/main/java/amia/parser/Parser.java +++ b/src/main/java/amia/parser/Parser.java @@ -76,24 +76,44 @@ public static EventInfo parseEvent(String command) throws AmiaException { } public static class DeadlineInfo { - public String description; - public String deadline; + private String description; + private String deadline; public DeadlineInfo(String description, String deadline) { this.description = description; this.deadline = deadline; } + + public String getDescription() { + return description; + } + + public String getDeadline() { + return deadline; + } } public static class EventInfo { - public String description; - public String from; - public String to; + private String description; + private String from; + private String to; public EventInfo(String description, String from, String to) { this.description = description; this.from = from; this.to = to; } + + public String getDescription() { + return description; + } + + public String getFrom() { + return from; + } + + public String getTo() { + return to; + } } } From e903087639d65bbd094fbb99ad0004f6dd2cc762 Mon Sep 17 00:00:00 2001 From: underscoregt Date: Sat, 14 Feb 2026 02:38:56 +0800 Subject: [PATCH 37/71] Make Deadline.by private; refine imports Change Deadline.by visibility from protected to private to improve encapsulation. Replace java.io.* wildcard import in Storage with explicit io imports for clarity. Reorder Event's from/to field declarations (no behavior change) for consistency and formatting. --- src/main/java/amia/storage/Storage.java | 7 ++++++- src/main/java/amia/task/Deadline.java | 3 ++- src/main/java/amia/task/Event.java | 6 +++--- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/main/java/amia/storage/Storage.java b/src/main/java/amia/storage/Storage.java index bda9645985..d2ac4593de 100644 --- a/src/main/java/amia/storage/Storage.java +++ b/src/main/java/amia/storage/Storage.java @@ -1,6 +1,11 @@ package amia.storage; -import java.io.*; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; diff --git a/src/main/java/amia/task/Deadline.java b/src/main/java/amia/task/Deadline.java index c3a01ab48f..6864017f47 100644 --- a/src/main/java/amia/task/Deadline.java +++ b/src/main/java/amia/task/Deadline.java @@ -11,7 +11,8 @@ public class Deadline extends Task { private static final DateTimeFormatter INPUT_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm"); private static final DateTimeFormatter OUTPUT_FORMAT = DateTimeFormatter.ofPattern("MMM dd yyyy, h:mma"); private static final DateTimeFormatter FILE_FORMAT = DateTimeFormatter.ISO_LOCAL_DATE_TIME; - protected LocalDateTime by; + + private LocalDateTime by; public Deadline(String description, String byStr) throws AmiaException { super(description); diff --git a/src/main/java/amia/task/Event.java b/src/main/java/amia/task/Event.java index d6311022f9..573d62d242 100644 --- a/src/main/java/amia/task/Event.java +++ b/src/main/java/amia/task/Event.java @@ -7,13 +7,13 @@ import amia.exception.AmiaException; public class Event extends Task { - private LocalDateTime from; - private LocalDateTime to; - private static final DateTimeFormatter INPUT_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm"); private static final DateTimeFormatter OUTPUT_FORMAT = DateTimeFormatter.ofPattern("MMM dd yyyy, h:mma"); private static final DateTimeFormatter FILE_FORMAT = DateTimeFormatter.ISO_LOCAL_DATE_TIME; + private LocalDateTime from; + private LocalDateTime to; + public Event(String description, String fromStr, String toStr) throws AmiaException { super(description); try { From 8c512f4512adc553a20f4aceb14bea36461c88cd Mon Sep 17 00:00:00 2001 From: underscoregt Date: Sat, 14 Feb 2026 07:11:53 +0800 Subject: [PATCH 38/71] Add 'find' command and task matching Implement a searchable 'find' command: add FIND to CommandType, handle FIND in Amia command dispatch, and add Amia.findTask to search tasks by keyword. --- src/main/java/amia/Amia.java | 25 ++++++++++++++++++++++ src/main/java/amia/parser/CommandType.java | 1 + src/main/java/amia/task/Task.java | 8 +++++++ 3 files changed, 34 insertions(+) diff --git a/src/main/java/amia/Amia.java b/src/main/java/amia/Amia.java index 05850647eb..15208896f7 100644 --- a/src/main/java/amia/Amia.java +++ b/src/main/java/amia/Amia.java @@ -49,6 +49,9 @@ public static void loop() { case LIST: listTask(); break; + case FIND: + findTask(command); + break; case BYE: ui.close(); return; @@ -129,6 +132,28 @@ public static void listTask() { ui.showLine(); } + public static void findTask(String command) { + ui.showLine(); + try { + String keyword = Parser.extractDescription(command, "find"); + boolean foundAny = false; + ui.showMessage("Here are the matching tasks:"); + for (int i = 0; i < tasks.size(); i++) { + Task task = tasks.get(i); + if (task.matches(keyword)) { + ui.showMessage((i + 1) + ". " + task); + foundAny = true; + } + } + if (!foundAny) { + ui.showMessage("No matching tasks found."); + } + } catch (AmiaException e) { + ui.showMessage(e.getMessage()); + } + ui.showLine(); + } + public static void markTask(String command) { ui.showLine(); try { diff --git a/src/main/java/amia/parser/CommandType.java b/src/main/java/amia/parser/CommandType.java index 12642c967d..94f3d00c3f 100644 --- a/src/main/java/amia/parser/CommandType.java +++ b/src/main/java/amia/parser/CommandType.java @@ -7,6 +7,7 @@ public enum CommandType { UNMARK("unmark"), DELETE("delete"), LIST("list"), + FIND("find"), BYE("bye"), UNKNOWN("unknown"); diff --git a/src/main/java/amia/task/Task.java b/src/main/java/amia/task/Task.java index 39fc1e01e6..21efdf08d9 100644 --- a/src/main/java/amia/task/Task.java +++ b/src/main/java/amia/task/Task.java @@ -26,5 +26,13 @@ public String toString() { return "[" + getStatusIcon() + "] " + description; } + public boolean matches(String keyword) { + if (keyword == null) { + return false; + } + String needle = keyword.trim().toLowerCase(); + return !needle.isEmpty() && description.toLowerCase().contains(needle); + } + public abstract String toFileString(); } From 2530c48aab59e9f629317fa1b2c39f52a328acda Mon Sep 17 00:00:00 2001 From: underscoregt Date: Sat, 14 Feb 2026 07:13:57 +0800 Subject: [PATCH 39/71] Squashed commit of the following: commit 8c512f4512adc553a20f4aceb14bea36461c88cd Author: underscoregt Date: Sat Feb 14 07:11:53 2026 +0800 Add 'find' command and task matching Implement a searchable 'find' command: add FIND to CommandType, handle FIND in Amia command dispatch, and add Amia.findTask to search tasks by keyword. --- src/main/java/amia/Amia.java | 26 +++++++++++++++++++++- src/main/java/amia/parser/CommandType.java | 1 + src/main/java/amia/task/Task.java | 16 +++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/main/java/amia/Amia.java b/src/main/java/amia/Amia.java index 8b26b4ccfb..2b308d006f 100644 --- a/src/main/java/amia/Amia.java +++ b/src/main/java/amia/Amia.java @@ -61,6 +61,9 @@ public static void loop() { case LIST: listTask(); break; + case FIND: + findTask(command); + break; case BYE: ui.close(); return; @@ -155,7 +158,28 @@ public static void listTask() { ui.showLine(); } - /** + ui.showLine(); + try { + String keyword = Parser.extractDescription(command, "find"); + boolean foundAny = false; + ui.showMessage("Here are the matching tasks:"); + for (int i = 0; i < tasks.size(); i++) { + Task task = tasks.get(i); + if (task.matches(keyword)) { + ui.showMessage((i + 1) + ". " + task); + foundAny = true; + } + } + if (!foundAny) { + ui.showMessage("No matching tasks found."); + } + } catch (AmiaException e) { + ui.showMessage(e.getMessage()); + } + ui.showLine(); + } + + * Marks a task as done based on the index provided in the command. * * @param command The mark command containing the task index. diff --git a/src/main/java/amia/parser/CommandType.java b/src/main/java/amia/parser/CommandType.java index 12642c967d..94f3d00c3f 100644 --- a/src/main/java/amia/parser/CommandType.java +++ b/src/main/java/amia/parser/CommandType.java @@ -7,6 +7,7 @@ public enum CommandType { UNMARK("unmark"), DELETE("delete"), LIST("list"), + FIND("find"), BYE("bye"), UNKNOWN("unknown"); diff --git a/src/main/java/amia/task/Task.java b/src/main/java/amia/task/Task.java index 676c33cdc2..04c15ae892 100644 --- a/src/main/java/amia/task/Task.java +++ b/src/main/java/amia/task/Task.java @@ -52,6 +52,22 @@ public String toString() { return "[" + getStatusIcon() + "] " + description; } + public boolean matches(String keyword) { + if (keyword == null) { + return false; + } + String needle = keyword.trim().toLowerCase(); + return !needle.isEmpty() && description.toLowerCase().contains(needle); + } + + public boolean matches(String keyword) { + if (keyword == null) { + return false; + } + String needle = keyword.trim().toLowerCase(); + return !needle.isEmpty() && description.toLowerCase().contains(needle); + } + /** * Returns the file representation of this task for storage purposes. * From 07181ee82948c06d84cd0eaa9b9a2557f07c07ec Mon Sep 17 00:00:00 2001 From: underscoregt Date: Sat, 14 Feb 2026 07:15:04 +0800 Subject: [PATCH 40/71] Fix command duplication when merging --- src/main/java/amia/task/Task.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/main/java/amia/task/Task.java b/src/main/java/amia/task/Task.java index 04c15ae892..58cbae5dd3 100644 --- a/src/main/java/amia/task/Task.java +++ b/src/main/java/amia/task/Task.java @@ -60,14 +60,6 @@ public boolean matches(String keyword) { return !needle.isEmpty() && description.toLowerCase().contains(needle); } - public boolean matches(String keyword) { - if (keyword == null) { - return false; - } - String needle = keyword.trim().toLowerCase(); - return !needle.isEmpty() && description.toLowerCase().contains(needle); - } - /** * Returns the file representation of this task for storage purposes. * From 78c5104b3eed6564a90b93b7e94bfe61b519aed3 Mon Sep 17 00:00:00 2001 From: underscoregt Date: Sat, 14 Feb 2026 07:30:20 +0800 Subject: [PATCH 41/71] Remove duplicated text during merging --- src/main/java/amia/Amia.java | 41 ------------------------------------ 1 file changed, 41 deletions(-) diff --git a/src/main/java/amia/Amia.java b/src/main/java/amia/Amia.java index ec89626160..3bf544f4cf 100644 --- a/src/main/java/amia/Amia.java +++ b/src/main/java/amia/Amia.java @@ -158,47 +158,6 @@ public static void listTask() { ui.showLine(); } - ui.showLine(); - try { - String keyword = Parser.extractDescription(command, "find"); - boolean foundAny = false; - ui.showMessage("Here are the matching tasks:"); - for (int i = 0; i < tasks.size(); i++) { - Task task = tasks.get(i); - if (task.matches(keyword)) { - ui.showMessage((i + 1) + ". " + task); - foundAny = true; - } - } - if (!foundAny) { - ui.showMessage("No matching tasks found."); - } - } catch (AmiaException e) { - ui.showMessage(e.getMessage()); - } - ui.showLine(); - } - - - * Marks a task as done based on the index provided in the command. - * - * @param command The mark command containing the task index. - */ - public static void markTask(String command) { - ui.showLine(); - try { - String args = Parser.extractIndexArg(command, "mark"); - int idx = Parser.parseIndex(args); - tasks.markDone(idx); - storage.save(tasks.toArrayList()); - ui.showMessage("I've marked the task as done!"); - ui.showMessage(" " + tasks.get(idx)); - } catch (AmiaException e) { - ui.showMessage(e.getMessage()); - } - ui.showLine(); - } - /** * Marks a task as not done based on the index provided in the command. * From d44e48dce09293b533a4506afa52e59033306a6d Mon Sep 17 00:00:00 2001 From: underscoregt Date: Sat, 14 Feb 2026 07:30:40 +0800 Subject: [PATCH 42/71] Revert "Remove duplicated text during merging" This reverts commit 78c5104b3eed6564a90b93b7e94bfe61b519aed3. --- src/main/java/amia/Amia.java | 41 ++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/src/main/java/amia/Amia.java b/src/main/java/amia/Amia.java index 3bf544f4cf..ec89626160 100644 --- a/src/main/java/amia/Amia.java +++ b/src/main/java/amia/Amia.java @@ -158,6 +158,47 @@ public static void listTask() { ui.showLine(); } + ui.showLine(); + try { + String keyword = Parser.extractDescription(command, "find"); + boolean foundAny = false; + ui.showMessage("Here are the matching tasks:"); + for (int i = 0; i < tasks.size(); i++) { + Task task = tasks.get(i); + if (task.matches(keyword)) { + ui.showMessage((i + 1) + ". " + task); + foundAny = true; + } + } + if (!foundAny) { + ui.showMessage("No matching tasks found."); + } + } catch (AmiaException e) { + ui.showMessage(e.getMessage()); + } + ui.showLine(); + } + + + * Marks a task as done based on the index provided in the command. + * + * @param command The mark command containing the task index. + */ + public static void markTask(String command) { + ui.showLine(); + try { + String args = Parser.extractIndexArg(command, "mark"); + int idx = Parser.parseIndex(args); + tasks.markDone(idx); + storage.save(tasks.toArrayList()); + ui.showMessage("I've marked the task as done!"); + ui.showMessage(" " + tasks.get(idx)); + } catch (AmiaException e) { + ui.showMessage(e.getMessage()); + } + ui.showLine(); + } + /** * Marks a task as not done based on the index provided in the command. * From 9810e6fbaf976434e7807e5e2c307351c2c9631c Mon Sep 17 00:00:00 2001 From: underscoregt Date: Sat, 14 Feb 2026 07:30:44 +0800 Subject: [PATCH 43/71] Revert "Fix command duplication when merging" This reverts commit 07181ee82948c06d84cd0eaa9b9a2557f07c07ec. --- src/main/java/amia/task/Task.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/amia/task/Task.java b/src/main/java/amia/task/Task.java index 58cbae5dd3..04c15ae892 100644 --- a/src/main/java/amia/task/Task.java +++ b/src/main/java/amia/task/Task.java @@ -60,6 +60,14 @@ public boolean matches(String keyword) { return !needle.isEmpty() && description.toLowerCase().contains(needle); } + public boolean matches(String keyword) { + if (keyword == null) { + return false; + } + String needle = keyword.trim().toLowerCase(); + return !needle.isEmpty() && description.toLowerCase().contains(needle); + } + /** * Returns the file representation of this task for storage purposes. * From e8e06ece74ce4700868321a14bac93bdcc26c5b3 Mon Sep 17 00:00:00 2001 From: underscoregt Date: Sat, 14 Feb 2026 07:32:35 +0800 Subject: [PATCH 44/71] Fix merge errors, add Javadoc to findTask --- src/main/java/amia/Amia.java | 8 +++++++- src/main/java/amia/task/Task.java | 8 -------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/main/java/amia/Amia.java b/src/main/java/amia/Amia.java index ec89626160..c14792cca5 100644 --- a/src/main/java/amia/Amia.java +++ b/src/main/java/amia/Amia.java @@ -158,6 +158,12 @@ public static void listTask() { ui.showLine(); } + /* + * Finds tasks containing a keyword provided in the command. + * + * @param command The find cmomand containing the keyword. + */ + public static void findTask(String command) { ui.showLine(); try { String keyword = Parser.extractDescription(command, "find"); @@ -179,7 +185,7 @@ public static void listTask() { ui.showLine(); } - + /* * Marks a task as done based on the index provided in the command. * * @param command The mark command containing the task index. diff --git a/src/main/java/amia/task/Task.java b/src/main/java/amia/task/Task.java index 04c15ae892..58cbae5dd3 100644 --- a/src/main/java/amia/task/Task.java +++ b/src/main/java/amia/task/Task.java @@ -60,14 +60,6 @@ public boolean matches(String keyword) { return !needle.isEmpty() && description.toLowerCase().contains(needle); } - public boolean matches(String keyword) { - if (keyword == null) { - return false; - } - String needle = keyword.trim().toLowerCase(); - return !needle.isEmpty() && description.toLowerCase().contains(needle); - } - /** * Returns the file representation of this task for storage purposes. * From 5aef3a11ebff2a5ae544532efc53db39d42c8359 Mon Sep 17 00:00:00 2001 From: underscoregt Date: Mon, 16 Feb 2026 16:24:41 +0800 Subject: [PATCH 45/71] Introduce Command classes and refactor Amia Add a Command abstraction and concrete command implementations (Add, Delete, Mark, Unmark, List, Find, Exit, Unknown) to encapsulate command behavior and return response strings. Refactor Amia from a static CLI-style class into an instance-based class that parses input into Command objects (parseCommand), executes them, and exposes getResponse/shouldExit for GUI integration; main now instantiates Amia and runs start/loop/exit. Move task-limit logic into AddCommand, add Task.matches, and adjust Storage/Parser/CommandType with small API/doc updates (parseIndex, parseDeadline, parseEvent). Update tests to use JUnit Assertions and minor imports. Add JavaFX dependencies in build.gradle and change application mainClass to amia.Launcher to prepare for a GUI launcher. --- build.gradle | 17 +- src/main/java/amia/Amia.java | 285 +++++++----------- src/main/java/amia/command/AddCommand.java | 58 ++++ src/main/java/amia/command/Command.java | 29 ++ src/main/java/amia/command/DeleteCommand.java | 38 +++ src/main/java/amia/command/ExitCommand.java | 20 ++ src/main/java/amia/command/FindCommand.java | 46 +++ src/main/java/amia/command/ListCommand.java | 27 ++ src/main/java/amia/command/MarkCommand.java | 36 +++ .../java/amia/command/UnknownCommand.java | 15 + src/main/java/amia/command/UnmarkCommand.java | 36 +++ src/main/java/amia/parser/CommandType.java | 22 +- src/main/java/amia/parser/Parser.java | 41 +++ src/main/java/amia/storage/Storage.java | 2 +- src/main/java/amia/task/Deadline.java | 1 - src/main/java/amia/task/Task.java | 7 + src/test/java/amia/parser/ParserTest.java | 18 +- src/test/java/amia/task/TaskListTest.java | 16 +- 18 files changed, 503 insertions(+), 211 deletions(-) create mode 100644 src/main/java/amia/command/AddCommand.java create mode 100644 src/main/java/amia/command/Command.java create mode 100644 src/main/java/amia/command/DeleteCommand.java create mode 100644 src/main/java/amia/command/ExitCommand.java create mode 100644 src/main/java/amia/command/FindCommand.java create mode 100644 src/main/java/amia/command/ListCommand.java create mode 100644 src/main/java/amia/command/MarkCommand.java create mode 100644 src/main/java/amia/command/UnknownCommand.java create mode 100644 src/main/java/amia/command/UnmarkCommand.java diff --git a/build.gradle b/build.gradle index 061de9eb54..2608048a4f 100644 --- a/build.gradle +++ b/build.gradle @@ -22,6 +22,21 @@ repositories { dependencies { testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.10.0' testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.10.0' + + String javaFxVersion = '17.0.7' + + 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 { @@ -39,7 +54,7 @@ test { } application { - mainClass.set("amia.Amia") + mainClass.set("amia.Launcher") } shadowJar { diff --git a/src/main/java/amia/Amia.java b/src/main/java/amia/Amia.java index c14792cca5..13469faf0b 100644 --- a/src/main/java/amia/Amia.java +++ b/src/main/java/amia/Amia.java @@ -1,14 +1,19 @@ package amia; +import amia.command.AddCommand; +import amia.command.Command; +import amia.command.DeleteCommand; +import amia.command.ExitCommand; +import amia.command.FindCommand; +import amia.command.ListCommand; +import amia.command.MarkCommand; +import amia.command.UnknownCommand; +import amia.command.UnmarkCommand; import amia.exception.AmiaException; import amia.parser.CommandType; import amia.parser.Parser; import amia.storage.Storage; -import amia.task.Deadline; -import amia.task.Event; -import amia.task.Task; import amia.task.TaskList; -import amia.task.ToDo; import amia.ui.Ui; /** @@ -16,226 +21,145 @@ * application loop and task management operations. */ public class Amia { - private static final int MAX_TASKS = 100; private static final String FILE_PATH = "./data/amia.txt"; - private static TaskList tasks = new TaskList(); - private static Ui ui = new Ui(); - private static Storage storage = new Storage(FILE_PATH); + private TaskList tasks; + private Ui ui; + private Storage storage; + private boolean isExit = false; /** - * Starts the application and runs the main loop. - * - * @param args Command line arguments (unused). - */ - public static void main(String[] args) { - start(); - loop(); - exit(); - } - - /** - * Continuously reads user commands and processes them until the user exits. + * Constructs an Amia instance with default file path. Initializes storage, UI, + * and loads saved tasks. */ - public static void loop() { - while (true) { - try { - String command = ui.readCommand(); - CommandType cmdType = Parser.parseCommandType(command); - - switch (cmdType) { - case TODO: - case DEADLINE: - case EVENT: - addTask(command); - break; - case MARK: - markTask(command); - break; - case UNMARK: - unmarkTask(command); - break; - case DELETE: - deleteTask(command); - break; - case LIST: - listTask(); - break; - case FIND: - findTask(command); - break; - case BYE: - ui.close(); - return; - case UNKNOWN: - throw new AmiaException("...?"); - } - } catch (AmiaException e) { - ui.showLine(); - ui.showMessage(e.getMessage()); - ui.showLine(); - } + public Amia() { + this.storage = new Storage(FILE_PATH); + this.ui = new Ui(); + try { + this.tasks = new TaskList(storage.load()); + } catch (AmiaException e) { + this.tasks = new TaskList(); } } /** - * Adds a new task to the task list based on the command provided. Supports - * todo, deadline, and event task types. + * Constructs an Amia instance for testing with custom file path. * - * @param command The command string containing task type and details. + * @param filePath The path to the data file. */ - public static void addTask(String command) { - ui.showLine(); + public Amia(String filePath) { + this.storage = new Storage(filePath); + this.ui = new Ui(); try { - if (tasks.size() < MAX_TASKS) { - Task task; - if (command.startsWith("todo")) { - String desc = Parser.extractDescription(command, "todo"); - task = new ToDo(desc); - } else if (command.startsWith("deadline")) { - Parser.DeadlineInfo info = Parser.parseDeadline(command); - task = new Deadline(info.getDescription(), info.getDeadline()); - } else if (command.startsWith("event")) { - Parser.EventInfo info = Parser.parseEvent(command); - task = new Event(info.getDescription(), info.getFrom(), info.getTo()); - } else { - task = new ToDo(command); - } - - tasks.add(task); - storage.save(tasks.toArrayList()); - ui.showMessage("I've added this task!"); - ui.showMessage(" " + task); - ui.showMessage("You have " + tasks.size() + " task" + (tasks.size() == 1 ? "" : "s") + "."); - } else { - throw new AmiaException("... The task list is full..."); - } + this.tasks = new TaskList(storage.load()); } catch (AmiaException e) { - ui.showMessage(e.getMessage()); + this.tasks = new TaskList(); } - ui.showLine(); } /** - * Deletes a task from the task list based on the index provided in the command. + * Starts the application and runs the main loop. * - * @param command The delete command containing the task index. + * @param args Command line arguments (unused). */ - public static void deleteTask(String command) { - ui.showLine(); - try { - String args = Parser.extractIndexArg(command, "delete"); - int idx = Parser.parseIndex(args); - Task removedTask = tasks.remove(idx); - storage.save(tasks.toArrayList()); - ui.showMessage("I've removed this task:"); - ui.showMessage(" " + removedTask); - ui.showMessage("You have " + tasks.size() + " task" + (tasks.size() == 1 ? "" : "s") + "."); - } catch (AmiaException e) { - ui.showMessage(e.getMessage()); - } - ui.showLine(); + public static void main(String[] args) { + Amia amia = new Amia(); + amia.start(); + amia.loop(); + amia.exit(); } /** - * Displays all tasks in the task list. + * Parses a command string and returns the appropriate Command object. + * + * @param input The user's input string. + * @return The Command object to execute. + * @throws AmiaException If parsing fails. */ - public static void listTask() { - ui.showLine(); - try { - if (tasks.size() == 0) { - ui.showMessage("No tasks to list..."); - ui.showLine(); - return; - } - ui.showMessage("Here are your tasks: "); - for (int i = 0; i < tasks.size(); i++) { - ui.showMessage((i + 1) + ". " + tasks.get(i)); - } - } catch (AmiaException e) { - ui.showMessage(e.getMessage()); + private Command parseCommand(String input) throws AmiaException { + CommandType cmdType = Parser.parseCommandType(input); + + switch (cmdType) { + case TODO: + case DEADLINE: + case EVENT: + return new AddCommand(input); + case MARK: + return new MarkCommand(input); + case UNMARK: + return new UnmarkCommand(input); + case DELETE: + return new DeleteCommand(input); + case LIST: + return new ListCommand(); + case FIND: + return new FindCommand(input); + case BYE: + return new ExitCommand(); + case UNKNOWN: + default: + return new UnknownCommand(); } - ui.showLine(); } - /* - * Finds tasks containing a keyword provided in the command. + /** + * Generates a response for the user's input. Used by the GUI. * - * @param command The find cmomand containing the keyword. + * @param input The user's command. + * @return The response string to display. */ - public static void findTask(String command) { - ui.showLine(); + public String getResponse(String input) { try { - String keyword = Parser.extractDescription(command, "find"); - boolean foundAny = false; - ui.showMessage("Here are the matching tasks:"); - for (int i = 0; i < tasks.size(); i++) { - Task task = tasks.get(i); - if (task.matches(keyword)) { - ui.showMessage((i + 1) + ". " + task); - foundAny = true; - } - } - if (!foundAny) { - ui.showMessage("No matching tasks found."); - } + Command command = parseCommand(input); + String response = command.execute(tasks, ui, storage); + isExit = command.isExit(); + return response; } catch (AmiaException e) { - ui.showMessage(e.getMessage()); + return e.getMessage(); } - ui.showLine(); } - /* - * Marks a task as done based on the index provided in the command. + /** + * Checks if the last command was an exit command. * - * @param command The mark command containing the task index. + * @return true if the application should exit, false otherwise. */ - public static void markTask(String command) { - ui.showLine(); - try { - String args = Parser.extractIndexArg(command, "mark"); - int idx = Parser.parseIndex(args); - tasks.markDone(idx); - storage.save(tasks.toArrayList()); - ui.showMessage("I've marked the task as done!"); - ui.showMessage(" " + tasks.get(idx)); - } catch (AmiaException e) { - ui.showMessage(e.getMessage()); - } - ui.showLine(); + public boolean shouldExit() { + return isExit; } /** - * Marks a task as not done based on the index provided in the command. - * - * @param command The unmark command containing the task index. + * Continuously reads user commands and processes them until the user exits. + * Used by the CLI. */ - public static void unmarkTask(String command) { - ui.showLine(); - try { - String args = Parser.extractIndexArg(command, "unmark"); - int idx = Parser.parseIndex(args); - tasks.markUndone(idx); - storage.save(tasks.toArrayList()); - ui.showMessage("I've marked the task as not done yet."); - ui.showMessage(" " + tasks.get(idx)); - } catch (AmiaException e) { - ui.showMessage(e.getMessage()); + public void loop() { + while (true) { + try { + String input = ui.readCommand(); + Command command = parseCommand(input); + String response = command.execute(tasks, ui, storage); + + ui.showLine(); + ui.showMessage(response); + ui.showLine(); + + if (command.isExit()) { + ui.close(); + return; + } + } catch (Exception e) { + ui.showLine(); + ui.showMessage(e.getMessage()); + ui.showLine(); + } } - ui.showLine(); } /** - * Initializes the application by loading previously saved tasks and displaying - * a welcome message. + * Initializes the application by displaying a welcome message. */ - public static void start() { + public void start() { ui.showLine(); - try { - tasks = new TaskList(storage.load()); - } catch (AmiaException e) { - ui.showMessage(e.getMessage()); - } ui.showMessage("Hello! I'm Amia!"); ui.showMessage("What can I do for you?"); ui.showLine(); @@ -244,8 +168,7 @@ public static void start() { /** * Displays the goodbye message when the application exits. */ - public static void exit() { + public void exit() { ui.showGoodbye(); } - } diff --git a/src/main/java/amia/command/AddCommand.java b/src/main/java/amia/command/AddCommand.java new file mode 100644 index 0000000000..3ec88feda8 --- /dev/null +++ b/src/main/java/amia/command/AddCommand.java @@ -0,0 +1,58 @@ +package amia.command; + +import amia.exception.AmiaException; +import amia.parser.Parser; +import amia.storage.Storage; +import amia.task.Deadline; +import amia.task.Event; +import amia.task.Task; +import amia.task.TaskList; +import amia.task.ToDo; +import amia.ui.Ui; + +/** + * Represents a command to add a task to the task list. + */ +public class AddCommand extends Command { + private static final int MAX_TASKS = 100; + private String commandText; + + /** + * Constructs an AddCommand with the given command text. + * + * @param commandText The full command string. + */ + public AddCommand(String commandText) { + this.commandText = commandText; + } + + @Override + public String execute(TaskList tasks, Ui ui, Storage storage) { + try { + if (tasks.size() >= MAX_TASKS) { + throw new AmiaException("... The task list is full..."); + } + + Task task; + if (commandText.startsWith("todo")) { + String desc = Parser.extractDescription(commandText, "todo"); + task = new ToDo(desc); + } else if (commandText.startsWith("deadline")) { + Parser.DeadlineInfo info = Parser.parseDeadline(commandText); + task = new Deadline(info.getDescription(), info.getDeadline()); + } else if (commandText.startsWith("event")) { + Parser.EventInfo info = Parser.parseEvent(commandText); + task = new Event(info.getDescription(), info.getFrom(), info.getTo()); + } else { + task = new ToDo(commandText); + } + + tasks.add(task); + storage.save(tasks.toArrayList()); + return "I've added this task!\n " + task + "\nYou have " + tasks.size() + " task" + + (tasks.size() == 1 ? "" : "s") + "."; + } catch (AmiaException e) { + return e.getMessage(); + } + } +} diff --git a/src/main/java/amia/command/Command.java b/src/main/java/amia/command/Command.java new file mode 100644 index 0000000000..3876ba9a01 --- /dev/null +++ b/src/main/java/amia/command/Command.java @@ -0,0 +1,29 @@ +package amia.command; + +import amia.storage.Storage; +import amia.task.TaskList; +import amia.ui.Ui; + +/** + * Represents an abstract command that can be executed. + */ +public abstract class Command { + /** + * Executes the command and returns the result message. + * + * @param tasks The task list to operate on. + * @param ui The UI for displaying messages (for CLI mode). + * @param storage The storage for saving tasks. + * @return The result message to display. + */ + public abstract String execute(TaskList tasks, Ui ui, Storage storage); + + /** + * Checks if this command causes the application to exit. + * + * @return True if this is an exit command, false otherwise. + */ + public boolean isExit() { + return false; + } +} diff --git a/src/main/java/amia/command/DeleteCommand.java b/src/main/java/amia/command/DeleteCommand.java new file mode 100644 index 0000000000..22433efb39 --- /dev/null +++ b/src/main/java/amia/command/DeleteCommand.java @@ -0,0 +1,38 @@ +package amia.command; + +import amia.exception.AmiaException; +import amia.parser.Parser; +import amia.storage.Storage; +import amia.task.Task; +import amia.task.TaskList; +import amia.ui.Ui; + +/** + * Represents a command to delete a task from the task list. + */ +public class DeleteCommand extends Command { + private String commandText; + + /** + * Constructs a DeleteCommand with the given command text. + * + * @param commandText The full command string. + */ + public DeleteCommand(String commandText) { + this.commandText = commandText; + } + + @Override + public String execute(TaskList tasks, Ui ui, Storage storage) { + try { + String args = Parser.extractIndexArg(commandText, "delete"); + int idx = Parser.parseIndex(args); + Task removedTask = tasks.remove(idx); + storage.save(tasks.toArrayList()); + return "I've removed this task:\n " + removedTask + "\nYou have " + tasks.size() + " task" + + (tasks.size() == 1 ? "" : "s") + "."; + } catch (AmiaException e) { + return e.getMessage(); + } + } +} diff --git a/src/main/java/amia/command/ExitCommand.java b/src/main/java/amia/command/ExitCommand.java new file mode 100644 index 0000000000..772fab4edd --- /dev/null +++ b/src/main/java/amia/command/ExitCommand.java @@ -0,0 +1,20 @@ +package amia.command; + +import amia.storage.Storage; +import amia.task.TaskList; +import amia.ui.Ui; + +/** + * Represents a command to exit the application. + */ +public class ExitCommand extends Command { + @Override + public String execute(TaskList tasks, Ui ui, Storage storage) { + return "Goodbye~"; + } + + @Override + public boolean isExit() { + return true; + } +} diff --git a/src/main/java/amia/command/FindCommand.java b/src/main/java/amia/command/FindCommand.java new file mode 100644 index 0000000000..aa76280864 --- /dev/null +++ b/src/main/java/amia/command/FindCommand.java @@ -0,0 +1,46 @@ +package amia.command; + +import amia.exception.AmiaException; +import amia.parser.Parser; +import amia.storage.Storage; +import amia.task.Task; +import amia.task.TaskList; +import amia.ui.Ui; + +/** + * Represents a command to find tasks by keyword. + */ +public class FindCommand extends Command { + private String commandText; + + /** + * Constructs a FindCommand with the given command text. + * + * @param commandText The full command string. + */ + public FindCommand(String commandText) { + this.commandText = commandText; + } + + @Override + public String execute(TaskList tasks, Ui ui, Storage storage) { + try { + String keyword = Parser.extractDescription(commandText, "find"); + StringBuilder result = new StringBuilder("Here are the matching tasks:\n"); + boolean foundAny = false; + for (int i = 0; i < tasks.size(); i++) { + Task task = tasks.get(i); + if (task.matches(keyword)) { + result.append((i + 1)).append(". ").append(task).append("\n"); + foundAny = true; + } + } + if (!foundAny) { + result.append("No matching tasks found."); + } + return result.toString().trim(); + } catch (AmiaException e) { + return e.getMessage(); + } + } +} diff --git a/src/main/java/amia/command/ListCommand.java b/src/main/java/amia/command/ListCommand.java new file mode 100644 index 0000000000..c0c335492a --- /dev/null +++ b/src/main/java/amia/command/ListCommand.java @@ -0,0 +1,27 @@ +package amia.command; + +import amia.exception.AmiaException; +import amia.storage.Storage; +import amia.task.TaskList; +import amia.ui.Ui; + +/** + * Represents a command to list all tasks. + */ +public class ListCommand extends Command { + @Override + public String execute(TaskList tasks, Ui ui, Storage storage) { + try { + if (tasks.size() == 0) { + return "No tasks to list..."; + } + StringBuilder result = new StringBuilder("Here are your tasks:\n"); + for (int i = 0; i < tasks.size(); i++) { + result.append((i + 1)).append(". ").append(tasks.get(i)).append("\n"); + } + return result.toString().trim(); + } catch (AmiaException e) { + return e.getMessage(); + } + } +} diff --git a/src/main/java/amia/command/MarkCommand.java b/src/main/java/amia/command/MarkCommand.java new file mode 100644 index 0000000000..c20abf04f6 --- /dev/null +++ b/src/main/java/amia/command/MarkCommand.java @@ -0,0 +1,36 @@ +package amia.command; + +import amia.exception.AmiaException; +import amia.parser.Parser; +import amia.storage.Storage; +import amia.task.TaskList; +import amia.ui.Ui; + +/** + * Represents a command to mark a task as done. + */ +public class MarkCommand extends Command { + private String commandText; + + /** + * Constructs a MarkCommand with the given command text. + * + * @param commandText The full command string. + */ + public MarkCommand(String commandText) { + this.commandText = commandText; + } + + @Override + public String execute(TaskList tasks, Ui ui, Storage storage) { + try { + String args = Parser.extractIndexArg(commandText, "mark"); + int idx = Parser.parseIndex(args); + tasks.markDone(idx); + storage.save(tasks.toArrayList()); + return "I've marked the task as done!\n " + tasks.get(idx); + } catch (AmiaException e) { + return e.getMessage(); + } + } +} diff --git a/src/main/java/amia/command/UnknownCommand.java b/src/main/java/amia/command/UnknownCommand.java new file mode 100644 index 0000000000..5aa2d7f659 --- /dev/null +++ b/src/main/java/amia/command/UnknownCommand.java @@ -0,0 +1,15 @@ +package amia.command; + +import amia.storage.Storage; +import amia.task.TaskList; +import amia.ui.Ui; + +/** + * Represents an unknown or invalid command. + */ +public class UnknownCommand extends Command { + @Override + public String execute(TaskList tasks, Ui ui, Storage storage) { + return "...?"; + } +} diff --git a/src/main/java/amia/command/UnmarkCommand.java b/src/main/java/amia/command/UnmarkCommand.java new file mode 100644 index 0000000000..2d4995add4 --- /dev/null +++ b/src/main/java/amia/command/UnmarkCommand.java @@ -0,0 +1,36 @@ +package amia.command; + +import amia.exception.AmiaException; +import amia.parser.Parser; +import amia.storage.Storage; +import amia.task.TaskList; +import amia.ui.Ui; + +/** + * Represents a command to unmark a task (mark as not done). + */ +public class UnmarkCommand extends Command { + private String commandText; + + /** + * Constructs an UnmarkCommand with the given command text. + * + * @param commandText The full command string. + */ + public UnmarkCommand(String commandText) { + this.commandText = commandText; + } + + @Override + public String execute(TaskList tasks, Ui ui, Storage storage) { + try { + String args = Parser.extractIndexArg(commandText, "unmark"); + int idx = Parser.parseIndex(args); + tasks.markUndone(idx); + storage.save(tasks.toArrayList()); + return "I've marked the task as not done yet.\n " + tasks.get(idx); + } catch (AmiaException e) { + return e.getMessage(); + } + } +} diff --git a/src/main/java/amia/parser/CommandType.java b/src/main/java/amia/parser/CommandType.java index 94f3d00c3f..6da177ac51 100644 --- a/src/main/java/amia/parser/CommandType.java +++ b/src/main/java/amia/parser/CommandType.java @@ -1,15 +1,11 @@ package amia.parser; + +/** + * Enum representing different types of commands that can be parsed. + */ public enum CommandType { - TODO("todo"), - DEADLINE("deadline"), - EVENT("event"), - MARK("mark"), - UNMARK("unmark"), - DELETE("delete"), - LIST("list"), - FIND("find"), - BYE("bye"), - UNKNOWN("unknown"); + TODO("todo"), DEADLINE("deadline"), EVENT("event"), MARK("mark"), UNMARK("unmark"), DELETE("delete"), LIST("list"), + FIND("find"), BYE("bye"), UNKNOWN("unknown"); private final String value; @@ -17,6 +13,12 @@ public enum CommandType { this.value = value; } + /** + * Converts a string to the corresponding CommandType. + * + * @param input The input string to convert. + * @return The CommandType that matches the input, or UNKNOWN if no match. + */ public static CommandType fromString(String input) { for (CommandType cmd : CommandType.values()) { if (input.startsWith(cmd.value)) { diff --git a/src/main/java/amia/parser/Parser.java b/src/main/java/amia/parser/Parser.java index d95b3ee1b7..4642a470e0 100644 --- a/src/main/java/amia/parser/Parser.java +++ b/src/main/java/amia/parser/Parser.java @@ -54,6 +54,13 @@ public static String extractIndexArg(String command, String keyword) throws Amia return args; } + /** + * Parses a string index and converts it to a zero-based integer. + * + * @param indexStr The index string to parse. + * @return The zero-based index as an integer. + * @throws AmiaException If the index string is not a valid number. + */ public static int parseIndex(String indexStr) throws AmiaException { try { return Integer.parseInt(indexStr) - 1; @@ -62,6 +69,13 @@ public static int parseIndex(String indexStr) throws AmiaException { } } + /** + * Parses a deadline command and extracts description and deadline date. + * + * @param command The deadline command string. + * @return A DeadlineInfo object containing the description and deadline. + * @throws AmiaException If the format is invalid or required fields are empty. + */ public static DeadlineInfo parseDeadline(String command) throws AmiaException { String args = command.substring(8).trim(); int byIdx = args.lastIndexOf("/by"); @@ -79,6 +93,14 @@ public static DeadlineInfo parseDeadline(String command) throws AmiaException { return new DeadlineInfo(desc, by); } + /** + * Parses an event command and extracts description, start time, and end time. + * + * @param command The event command string. + * @return An EventInfo object containing the description, start time, and end + * time. + * @throws AmiaException If the format is invalid or required fields are empty. + */ public static EventInfo parseEvent(String command) throws AmiaException { String args = command.substring(5).trim(); int fromIdx = args.lastIndexOf("/from"); @@ -101,10 +123,19 @@ public static EventInfo parseEvent(String command) throws AmiaException { return new EventInfo(desc, from, to); } + /** + * Inner class to hold deadline information. + */ public static class DeadlineInfo { private String description; private String deadline; + /** + * Constructs a DeadlineInfo with the given description and deadline. + * + * @param description The task description. + * @param deadline The deadline date/time. + */ public DeadlineInfo(String description, String deadline) { this.description = description; this.deadline = deadline; @@ -119,11 +150,21 @@ public String getDeadline() { } } + /** + * Inner class to hold event information. + */ public static class EventInfo { private String description; private String from; private String to; + /** + * Constructs an EventInfo with the given description and time range. + * + * @param description The task description. + * @param from The event start time. + * @param to The event end time. + */ public EventInfo(String description, String from, String to) { this.description = description; this.from = from; diff --git a/src/main/java/amia/storage/Storage.java b/src/main/java/amia/storage/Storage.java index 370d361dbc..6c6710a01d 100644 --- a/src/main/java/amia/storage/Storage.java +++ b/src/main/java/amia/storage/Storage.java @@ -20,9 +20,9 @@ * Handles loading and saving tasks to a file. */ public class Storage { - private final String filePath; private static final DateTimeFormatter INPUT_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HHmm"); private static final DateTimeFormatter FILE_FORMAT = DateTimeFormatter.ISO_LOCAL_DATE_TIME; + private final String filePath; /** * Constructs a Storage object with the given file path. Uses a default path if diff --git a/src/main/java/amia/task/Deadline.java b/src/main/java/amia/task/Deadline.java index 5261c65629..9921ba234a 100644 --- a/src/main/java/amia/task/Deadline.java +++ b/src/main/java/amia/task/Deadline.java @@ -1,6 +1,5 @@ package amia.task; -import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; diff --git a/src/main/java/amia/task/Task.java b/src/main/java/amia/task/Task.java index 58cbae5dd3..0ec0be4657 100644 --- a/src/main/java/amia/task/Task.java +++ b/src/main/java/amia/task/Task.java @@ -52,6 +52,13 @@ public String toString() { return "[" + getStatusIcon() + "] " + description; } + /** + * Checks if this task matches the given keyword. + * + * @param keyword The keyword to match against. + * @return True if the keyword is found in the task description, false + * otherwise. + */ public boolean matches(String keyword) { if (keyword == null) { return false; diff --git a/src/test/java/amia/parser/ParserTest.java b/src/test/java/amia/parser/ParserTest.java index 4fcff42e8c..5f685c5590 100644 --- a/src/test/java/amia/parser/ParserTest.java +++ b/src/test/java/amia/parser/ParserTest.java @@ -1,7 +1,7 @@ package amia.parser; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; import amia.exception.AmiaException; @@ -10,33 +10,33 @@ public class ParserTest { @Test public void testParseCommandType() throws AmiaException { CommandType type1 = Parser.parseCommandType("todo read book"); - assertEquals(CommandType.TODO, type1); + Assertions.assertEquals(CommandType.TODO, type1); CommandType type2 = Parser.parseCommandType("list"); - assertEquals(CommandType.LIST, type2); + Assertions.assertEquals(CommandType.LIST, type2); CommandType type3 = Parser.parseCommandType("bye"); - assertEquals(CommandType.BYE, type3); + Assertions.assertEquals(CommandType.BYE, type3); } @Test public void testExtractDescription() throws AmiaException { String desc = Parser.extractDescription("todo read a book", "todo"); - assertEquals("read a book", desc); + Assertions.assertEquals("read a book", desc); String desc2 = Parser.extractDescription("deadline submit project /by 2024-02-15", "deadline"); - assertEquals("submit project /by 2024-02-15", desc2); + Assertions.assertEquals("submit project /by 2024-02-15", desc2); } @Test public void testParseIndex() throws AmiaException { int idx1 = Parser.parseIndex("1"); - assertEquals(0, idx1); + Assertions.assertEquals(0, idx1); int idx2 = Parser.parseIndex("5"); - assertEquals(4, idx2); + Assertions.assertEquals(4, idx2); - assertThrows(AmiaException.class, () -> { + Assertions.assertThrows(AmiaException.class, () -> { Parser.parseIndex("abc"); }); } diff --git a/src/test/java/amia/task/TaskListTest.java b/src/test/java/amia/task/TaskListTest.java index 6797d196d4..b8bc99b43f 100644 --- a/src/test/java/amia/task/TaskListTest.java +++ b/src/test/java/amia/task/TaskListTest.java @@ -1,8 +1,8 @@ package amia.task; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; import amia.exception.AmiaException; @@ -18,8 +18,8 @@ public void setUp() { public void testAddValidTask() throws AmiaException { Task task = new ToDo("Read book"); taskList.add(task); - assertEquals(1, taskList.size()); - assertEquals(task, taskList.get(0)); + Assertions.assertEquals(1, taskList.size()); + Assertions.assertEquals(task, taskList.get(0)); } @Test @@ -30,8 +30,8 @@ public void testRemoveValidTask() throws AmiaException { taskList.add(task2); Task removed = taskList.remove(0); - assertEquals(1, taskList.size()); - assertEquals(task2, taskList.get(0)); + Assertions.assertEquals(1, taskList.size()); + Assertions.assertEquals(task2, taskList.get(0)); } @Test @@ -39,10 +39,10 @@ public void testMarkAndUnmarkTask() throws AmiaException { Task task = new ToDo("Task 1"); taskList.add(task); - assertTrue(taskList.get(0).toString().contains("[ ]")); + Assertions.assertTrue(taskList.get(0).toString().contains("[ ]")); taskList.markDone(0); - assertTrue(taskList.get(0).toString().contains("[X]")); + Assertions.assertTrue(taskList.get(0).toString().contains("[X]")); taskList.markUndone(0); - assertTrue(taskList.get(0).toString().contains("[ ]")); + Assertions.assertTrue(taskList.get(0).toString().contains("[ ]")); } } From fab117e27073d035780b317d0f611e49d517cc26 Mon Sep 17 00:00:00 2001 From: underscoregt Date: Mon, 16 Feb 2026 16:25:35 +0800 Subject: [PATCH 46/71] Add JavaFX GUI components and resources Introduce a JavaFX-based UI for Amia: adds Main application and Launcher (classpath workaround), MainWindow controller, and DialogBox custom control. Includes FXML layouts (MainWindow.fxml, DialogBox.fxml) and avatar images (Amia.jpg, User.png). MainWindow wires Amia into the UI, binds scrolling, handles user input to display paired dialog boxes, and schedules app exit on Amia's exit command. --- src/main/java/amia/DialogBox.java | 61 +++++++++++++++++++++++ src/main/java/amia/Launcher.java | 12 +++++ src/main/java/amia/Main.java | 31 ++++++++++++ src/main/java/amia/MainWindow.java | 62 ++++++++++++++++++++++++ src/main/resources/images/Amia.jpg | Bin 0 -> 11081 bytes src/main/resources/images/User.png | Bin 0 -> 3759 bytes src/main/resources/view/DialogBox.fxml | 22 +++++++++ src/main/resources/view/MainWindow.fxml | 43 ++++++++++++++++ 8 files changed, 231 insertions(+) create mode 100644 src/main/java/amia/DialogBox.java create mode 100644 src/main/java/amia/Launcher.java create mode 100644 src/main/java/amia/Main.java create mode 100644 src/main/java/amia/MainWindow.java create mode 100644 src/main/resources/images/Amia.jpg create mode 100644 src/main/resources/images/User.png create mode 100644 src/main/resources/view/DialogBox.fxml create mode 100644 src/main/resources/view/MainWindow.fxml diff --git a/src/main/java/amia/DialogBox.java b/src/main/java/amia/DialogBox.java new file mode 100644 index 0000000000..8ccea4650b --- /dev/null +++ b/src/main/java/amia/DialogBox.java @@ -0,0 +1,61 @@ +package amia; + +import java.io.IOException; +import java.util.Collections; + +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.geometry.Pos; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; + +/** + * Represents a dialog box consisting of an ImageView to represent the speaker's + * face and a label containing text from the speaker. + */ +public class DialogBox extends HBox { + @FXML + private Label dialog; + @FXML + private ImageView displayPicture; + + private DialogBox(String text, Image img) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(MainWindow.class.getResource("/view/DialogBox.fxml")); + fxmlLoader.setController(this); + fxmlLoader.setRoot(this); + fxmlLoader.load(); + } catch (IOException e) { + e.printStackTrace(); + } + + dialog.setText(text); + displayPicture.setImage(img); + } + + /** + * Flips the dialog box such that the ImageView is on the left and text on the + * right. + */ + private void flip() { + ObservableList tmp = FXCollections.observableArrayList(this.getChildren()); + Collections.reverse(tmp); + getChildren().setAll(tmp); + setAlignment(Pos.TOP_LEFT); + } + + public static DialogBox getUserDialog(String text, Image img) { + return new DialogBox(text, img); + } + + public static DialogBox getAmiaDialog(String text, Image img) { + var db = new DialogBox(text, img); + db.flip(); + return db; + } +} diff --git a/src/main/java/amia/Launcher.java b/src/main/java/amia/Launcher.java new file mode 100644 index 0000000000..f9f68dad42 --- /dev/null +++ b/src/main/java/amia/Launcher.java @@ -0,0 +1,12 @@ +package amia; + +import javafx.application.Application; + +/** + * A launcher class to workaround classpath issues. + */ +public class Launcher { + public static void main(String[] args) { + Application.launch(Main.class, args); + } +} diff --git a/src/main/java/amia/Main.java b/src/main/java/amia/Main.java new file mode 100644 index 0000000000..3575e37ee9 --- /dev/null +++ b/src/main/java/amia/Main.java @@ -0,0 +1,31 @@ +package amia; + +import java.io.IOException; + +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Scene; +import javafx.scene.layout.AnchorPane; +import javafx.stage.Stage; + +/** + * A GUI for Amia using FXML. + */ +public class Main extends Application { + + private Amia amia = new Amia(); + + @Override + public void start(Stage stage) { + try { + FXMLLoader fxmlLoader = new FXMLLoader(Main.class.getResource("/view/MainWindow.fxml")); + AnchorPane ap = fxmlLoader.load(); + Scene scene = new Scene(ap); + stage.setScene(scene); + fxmlLoader.getController().setAmia(amia); // inject the Amia instance + stage.show(); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/amia/MainWindow.java b/src/main/java/amia/MainWindow.java new file mode 100644 index 0000000000..732c5ab13b --- /dev/null +++ b/src/main/java/amia/MainWindow.java @@ -0,0 +1,62 @@ +package amia; + +import javafx.animation.PauseTransition; +import javafx.application.Platform; +import javafx.fxml.FXML; +import javafx.scene.control.Button; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.TextField; +import javafx.scene.image.Image; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import javafx.util.Duration; + +/** + * Controller for the main GUI. + */ +public class MainWindow extends AnchorPane { + @FXML + private ScrollPane scrollPane; + @FXML + private VBox dialogContainer; + @FXML + private TextField userInput; + @FXML + private Button sendButton; + + private Amia amia; + + private Image userImage = new Image(this.getClass().getResourceAsStream("/images/User.png")); + private Image amiaImage = new Image(this.getClass().getResourceAsStream("/images/Amia.jpg")); + + @FXML + public void initialize() { + scrollPane.vvalueProperty().bind(dialogContainer.heightProperty()); + } + + /** Injects the Amia instance */ + public void setAmia(Amia a) { + amia = a; + } + + /** + * Creates two dialog boxes, one echoing user input and the other containing + * Amia's reply and then appends them to the dialog container. Clears the user + * input after processing. + */ + @FXML + private void handleUserInput() { + String input = userInput.getText(); + String response = amia.getResponse(input); + dialogContainer.getChildren().addAll(DialogBox.getUserDialog(input, userImage), + DialogBox.getAmiaDialog(response, amiaImage)); + userInput.clear(); + + // Check if it's an exit command and close after delay + if (amia.shouldExit()) { + PauseTransition delay = new PauseTransition(Duration.seconds(2)); + delay.setOnFinished(event -> Platform.exit()); + delay.play(); + } + } +} diff --git a/src/main/resources/images/Amia.jpg b/src/main/resources/images/Amia.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a4895b95c3812609e4528b13bb8d4bb0e9b1990f GIT binary patch literal 11081 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 W;88A+B=$`=NbKYY)48R+Tl_yfqPkiD literal 0 HcmV?d00001 diff --git a/src/main/resources/images/User.png b/src/main/resources/images/User.png new file mode 100644 index 0000000000000000000000000000000000000000..73c7f7592f12903b6d8e673ddd082b0819308406 GIT binary patch literal 3759 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 N0fu^LL>b&Z{6FQIMFRi; literal 0 HcmV?d00001 diff --git a/src/main/resources/view/DialogBox.fxml b/src/main/resources/view/DialogBox.fxml new file mode 100644 index 0000000000..525b7250cb --- /dev/null +++ b/src/main/resources/view/DialogBox.fxml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/view/MainWindow.fxml b/src/main/resources/view/MainWindow.fxml new file mode 100644 index 0000000000..c825cf33c0 --- /dev/null +++ b/src/main/resources/view/MainWindow.fxml @@ -0,0 +1,43 @@ + + + + + + + + + + + +