diff --git a/src/CustomTimer.java b/src/CustomTimer.java new file mode 100644 index 0000000..7825877 --- /dev/null +++ b/src/CustomTimer.java @@ -0,0 +1,203 @@ +import java.io.*; +import java.util.NoSuchElementException; +import java.util.Scanner; +import java.util.Timer; +import java.util.TimerTask; + +/** + * Singleton timer class. + * Warning: gets confusing with all the uses of timer as a word, vs the actual JDK Timer class. + */ +public class CustomTimer { + + private static CustomTimer singleton = new CustomTimer(); + private Timer currentTimer; + protected boolean isTimerRunning = false; + + public Timer getCurrentTimer() { + return currentTimer; + } + + private CustomTimer() {} + + /* Static 'instance' method */ + public static CustomTimer getInstance() { + return singleton; + } + + //File paths + String timerLength = Main.inputPath + "TimerLength.txt"; + String timerTXT = Main.outputPath + "Timer.txt"; + + + /*--- Methods ---*/ + + /** + * Sets the value the timer will go to on initial start, or after a restart or stop + * + * @param seconds a positive integer + * @see #getInitialTimerLength() + */ + public void setInitialTimerLength(int seconds) throws IOException { + if (seconds <= 0) throw new IllegalArgumentException("Cannot set initial timer value to 0"); + //open TimerLength file and set a new value + Writer fileWriter = new FileWriter(timerLength); + fileWriter.write(String.valueOf(seconds)); + fileWriter.close(); + } + + /** + * Resets the timer back to beginning value and starts it again + * @see #start() + * @see #stop() + * @see #pause() + */ + public void restart() throws IOException, InvalidDataException { + //stop timer + if (currentTimer != null) { + currentTimer.cancel(); + isTimerRunning = false; + } + + //write initial timer length to Timer.txt + set(getInitialTimerLength()); + + //start + start(); + } + + /** + * Gets the total length of the timer. Not to be confused with the current length, + * this value is where the timer will go to when restarting or starting after a stop. + * + * @return timer length in seconds + * @throws IOException + * @throws NoSuchElementException TimerLength.txt contains an unexpected character. + * @see #setInitialTimerLength(int) + */ + public int getInitialTimerLength() throws IOException, NoSuchElementException, InvalidDataException { + //open TimerLength file and gets the current value + File timerSettings = new File(timerLength); + Scanner scanner = new Scanner(timerSettings); + int value = scanner.nextInt(); + if (value <= 0) throw new InvalidDataException("TimerLength.txt cannot contain a non-postive value"); + return value; + } + + /** + * Start the timer (initial or after a pause) + * @throws IOException + * @throws NoSuchElementException + * @see #stop() + * @see #pause() + * @see #restart() + */ + public void start() throws IOException, NoSuchElementException, InvalidDataException { + if (!isTimerRunning) { //prevent starting when there is already a timer. no doubling up! + if (get() <= 0) { //if timer is currently at 0, restart + restart(); + } else { //otherwise, resume from last position by starting CountdownTimer task + isTimerRunning = true; + currentTimer = new Timer(); + TimerTask task = new CountdownTimer(); + currentTimer.schedule(task,0, 1000); + } + } else System.err.println("Timer already running!"); + } + + /** + * Completely stops and cancels the timer. NOT EQUIVALENT TO A PAUSE. + * Starting after this state will restart the timer. + * + * @throws NullPointerException there is no current timer or {@link TimerTask} running + * @throws IOException if I/O error is encountered when writing to Timer.txt + * @see #start() + * @see #pause() + * @see #restart() + */ + public void stop() throws NullPointerException, IOException { + //stop the current TimerTask + if (currentTimer != null) { + currentTimer.cancel(); + isTimerRunning = false; + } + //set Timer.txt to nothing [will cause start() to restart timer if called next] + set(0); + } + + /** + * Pauses the current timer state. Starting from this after this state will + * continue the timer from where the timer left off. + * Does nothing if there is no active timer + * + * @see #start() + * @see #stop() + * @see #restart() + */ + public void pause() { + //cancel TimerTask to stop countdown. Keeps value, so start() can resume from it + if (currentTimer != null) { + currentTimer.cancel(); + isTimerRunning = false; + } + } + + /** + * Set the current value of the timer + * + * @param seconds a second value (non-negative) + * @throws IOException if any I/O error occurs when accessing Timer.txt + */ + public void set(int seconds) throws IOException { + if (seconds < 0) { + throw new IllegalArgumentException("Cannot set a timer for negative seconds!"); + } + Writer fileWriter = new FileWriter(timerTXT); + fileWriter.write(String.valueOf(seconds)); + fileWriter.close(); + } + + /** + * Gets the current value of the timer. + * @return time left in seconds. + * @throws FileNotFoundException timer.txt cannot be found + * @throws NoSuchElementException timer.txt contains an unexpected character + */ + public int get() throws FileNotFoundException, NoSuchElementException { + //open Timer.txt and scan first integer + File timerSettings = new File(timerTXT); + Scanner scanner = new Scanner(timerSettings); + return scanner.nextInt(); + } +} + +/** + * Begins countdown from current value in the Timer.txt file. {@link TimerTask} to be scheduled in {@link Timer#schedule(TimerTask, long, long)} for the countdown timer system. + * @implNote Be careful with scheduling this function too fast/often, it doesn't implement any + * file locks and is not atomic as of the current version. + * @implNote Does not use system clock (or at least efficiently. Timer will start to lag behind if more system resources are used or application is active with other tasks + */ +class CountdownTimer extends TimerTask { + + public void run() { + + /*todo, implement lock*/ + + CustomTimer timer = CustomTimer.getInstance(); + + try { + int currentTime = timer.get(); + if (currentTime <= 0) { + System.out.println("Timer Finished!\n>"); + CustomTimer.getInstance().isTimerRunning = false; + timer.getCurrentTimer().cancel(); + } else { + timer.set(currentTime - 1); + } + } catch (FileNotFoundException exception) { + exception.printStackTrace(); + } catch (IOException exception) { + exception.printStackTrace(); + } + } +} diff --git a/src/InvalidDataException.java b/src/InvalidDataException.java new file mode 100644 index 0000000..0a47611 --- /dev/null +++ b/src/InvalidDataException.java @@ -0,0 +1,8 @@ +/** + * A file read in by the application contains invalid data, typically due to outside tampering. + */ +public class InvalidDataException extends Exception { + public InvalidDataException(String message) { + super(message); + } +} diff --git a/src/Main.java b/src/Main.java index bfd2b9d..42d00b4 100644 --- a/src/Main.java +++ b/src/Main.java @@ -2,9 +2,7 @@ import java.awt.image.BufferedImage; import java.io.*; import java.nio.file.*; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.Scanner; +import java.util.*; import java.util.stream.Stream; public class Main { @@ -37,6 +35,13 @@ private static void CLIParse(String input) { System.out.println("TOURNAMENT NUMBER"); System.out.println(" 'number set', update current tourney number"); + System.out.println("TIMER"); + System.out.println(" 'timer set', determine timer length"); + System.out.println(" 'timer start', start the timer"); + System.out.println(" 'timer pause', pause the timer"); + System.out.println(" 'timer stop', ends the timer early"); + System.out.println(" 'timer restart', restarts the timer"); + System.out.println("TEAMS"); System.out.println(" 'team add', add a team"); System.out.println(" 'team add-noimg', add a team (no image)"); @@ -122,6 +127,58 @@ private static void CLIParse(String input) { } break; + case "timer set": + CustomTimer timer = CustomTimer.getInstance(); + try { + System.out.println("How many seconds would you like to set the timer for"); + int setTime = scanner.nextInt(); + timer.pause(); //pause timer to prevent ticking + timer.setInitialTimerLength(setTime); //set new initial + timer.set(setTime); //set currently displayed timer to not confuse user + } catch (IOException exception) { + exception.printStackTrace(); + } catch (InputMismatchException exception) { + exception.printStackTrace(); //thrown by scanner + } catch (NoSuchElementException exception) { + exception.printStackTrace(); + } catch (IllegalArgumentException exception) { + System.out.println("Cannot set initial timer value to 0!"); + } + break; + + case "timer start": + try { + CustomTimer.getInstance().start(); + } catch (IOException exception) { + exception.printStackTrace(); + } catch (NoSuchElementException exception) { + exception.printStackTrace(); + } catch (InvalidDataException e) { + e.printStackTrace(); + } + break; + + case "timer stop": + try { + CustomTimer.getInstance().stop(); + System.out.println("Stopped!"); + } catch (IOException exception) { + exception.printStackTrace(); + } + break; + + case "timer pause": + CustomTimer.getInstance().pause(); + break; + + case "timer restart": case "timer reset": + try { + CustomTimer.getInstance().restart(); + } catch (IOException | InvalidDataException exception) { + exception.printStackTrace(); + } + break; + default: System.out.println("Unknown Input. Type 'help' for commands"); } @@ -207,6 +264,10 @@ public static void verifyContent() throws IOException { //check teams.txt File teams = new File(inputPath + "teams.txt"); teams.createNewFile(); // if file already exists will do nothing + + //check TimerLength.txt + File timerLength = new File(inputPath + "TimerLength.txt"); + teams.createNewFile(); // if file already exists will do nothing } /**