From 0304a971ccf80482b3c562397ff97de270224971 Mon Sep 17 00:00:00 2001 From: Durim Kryeziu Date: Mon, 18 Nov 2024 11:45:48 +0100 Subject: [PATCH] Add 'Retirement Calculator' solution --- README.md | 2 +- retirement-calculator/build.gradle | 8 ++ .../retirement/Main.java | 11 ++ .../retirement/RetirementCalculator.java | 60 +++++++++++ .../retirement/YearProvider.java | 9 ++ .../retirement/RetirementCalculatorTest.java | 102 ++++++++++++++++++ settings.gradle | 9 +- 7 files changed, 199 insertions(+), 2 deletions(-) create mode 100644 retirement-calculator/build.gradle create mode 100644 retirement-calculator/src/main/java/com/craftsmanshipinsoftware/retirement/Main.java create mode 100644 retirement-calculator/src/main/java/com/craftsmanshipinsoftware/retirement/RetirementCalculator.java create mode 100644 retirement-calculator/src/main/java/com/craftsmanshipinsoftware/retirement/YearProvider.java create mode 100644 retirement-calculator/src/test/java/com/craftsmanshipinsoftware/retirement/RetirementCalculatorTest.java diff --git a/README.md b/README.md index 837de8d..6433a50 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Exercises for Programmers: 57 Challenges to Develop Your Coding Skills by Brian - Exercise 3. [Printing Quotes](https://github.com/durimkryeziu/exercises-for-programmers-java/tree/main/printing-quotes) - Exercise 4. [Mad Lib](https://github.com/durimkryeziu/exercises-for-programmers-java/tree/main/mad-lib) - Exercise 5. [Simple Math](https://github.com/durimkryeziu/exercises-for-programmers-java/tree/main/simple-math) -- Exercise 6. Retirement Calculator +- Exercise 6. [Retirement Calculator](https://github.com/durimkryeziu/exercises-for-programmers-java/tree/main/retirement-calculator) **Calculations** - Exercise 7. Area of a Rectangular Room diff --git a/retirement-calculator/build.gradle b/retirement-calculator/build.gradle new file mode 100644 index 0000000..5d05690 --- /dev/null +++ b/retirement-calculator/build.gradle @@ -0,0 +1,8 @@ +plugins { + id 'com.craftsmanshipinsoftware.common-conventions' +} + +dependencies { + testImplementation(libs.assertj.core) + testImplementation(libs.junit.jupiter) +} diff --git a/retirement-calculator/src/main/java/com/craftsmanshipinsoftware/retirement/Main.java b/retirement-calculator/src/main/java/com/craftsmanshipinsoftware/retirement/Main.java new file mode 100644 index 0000000..7c1c9e2 --- /dev/null +++ b/retirement-calculator/src/main/java/com/craftsmanshipinsoftware/retirement/Main.java @@ -0,0 +1,11 @@ +package com.craftsmanshipinsoftware.retirement; + +import java.time.Year; + +public class Main { + + public static void main(String[] args) { + RetirementCalculator calculator = new RetirementCalculator(System.in, System.out, Year::now); + calculator.printYearsLeft(); + } +} diff --git a/retirement-calculator/src/main/java/com/craftsmanshipinsoftware/retirement/RetirementCalculator.java b/retirement-calculator/src/main/java/com/craftsmanshipinsoftware/retirement/RetirementCalculator.java new file mode 100644 index 0000000..2fbd035 --- /dev/null +++ b/retirement-calculator/src/main/java/com/craftsmanshipinsoftware/retirement/RetirementCalculator.java @@ -0,0 +1,60 @@ +package com.craftsmanshipinsoftware.retirement; + +import java.io.InputStream; +import java.io.PrintStream; +import java.time.Year; +import java.util.Objects; +import java.util.Scanner; + +class RetirementCalculator { + + private final InputStream inputStream; + private final PrintStream printStream; + private final YearProvider yearProvider; + + RetirementCalculator(InputStream inputStream, PrintStream printStream, YearProvider yearProvider) { + Objects.requireNonNull(inputStream, "inputStream must not be null"); + Objects.requireNonNull(printStream, "printStream must not be null"); + Objects.requireNonNull(yearProvider, "yearProvider must not be null"); + this.inputStream = inputStream; + this.printStream = printStream; + this.yearProvider = yearProvider; + } + + void printYearsLeft() { + try (Scanner scanner = new Scanner(inputStream)) { + int currentAge = toInt(promptForInput("What is your current age? ", scanner)); + int retirementAge = toInt(promptForInput("At what age would you like to retire? ", scanner)); + int yearsLeft = retirementAge - currentAge; + if (yearsLeft <= 0) { + this.printStream.println("You can already retire."); + } else { + this.printStream.printf("You have %d years left until you can retire.%n", yearsLeft); + } + Year currentYear = this.yearProvider.currentYear(); + this.printStream.printf("It's %s, so you can retire in %s.%n", currentYear, + currentYear.plusYears(yearsLeft)); + } + } + + private String promptForInput(String prompt, Scanner scanner) { + this.printStream.print(prompt); + String input = scanner.hasNext() ? scanner.nextLine() : null; + if (input == null || input.isBlank()) { + throw new IllegalArgumentException("Input must not be empty!"); + } + return input; + } + + private int toInt(String value) { + try { + int number = Integer.parseInt(value); + if (number < 0) { + throw new IllegalArgumentException("Please enter a positive number! Input: %s".formatted(value)); + } + return number; + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Please enter a valid number! Input: %s".formatted(value), e); + } + } +} diff --git a/retirement-calculator/src/main/java/com/craftsmanshipinsoftware/retirement/YearProvider.java b/retirement-calculator/src/main/java/com/craftsmanshipinsoftware/retirement/YearProvider.java new file mode 100644 index 0000000..2f2f8e5 --- /dev/null +++ b/retirement-calculator/src/main/java/com/craftsmanshipinsoftware/retirement/YearProvider.java @@ -0,0 +1,9 @@ +package com.craftsmanshipinsoftware.retirement; + +import java.time.Year; + +@FunctionalInterface +interface YearProvider { + + Year currentYear(); +} diff --git a/retirement-calculator/src/test/java/com/craftsmanshipinsoftware/retirement/RetirementCalculatorTest.java b/retirement-calculator/src/test/java/com/craftsmanshipinsoftware/retirement/RetirementCalculatorTest.java new file mode 100644 index 0000000..66d9f10 --- /dev/null +++ b/retirement-calculator/src/test/java/com/craftsmanshipinsoftware/retirement/RetirementCalculatorTest.java @@ -0,0 +1,102 @@ +package com.craftsmanshipinsoftware.retirement; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.time.Year; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class RetirementCalculatorTest { + + @Test + void asksForCurrentAge() { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + RetirementCalculator calculator = new RetirementCalculator(new ByteArrayInputStream("25\n65".getBytes()), + new PrintStream(outputStream), Year::now); + + calculator.printYearsLeft(); + + assertThat(outputStream.toString()).contains("What is your current age?"); + } + + @Test + void asksForRetirementAge() { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + RetirementCalculator calculator = new RetirementCalculator(new ByteArrayInputStream("25\n65".getBytes()), + new PrintStream(outputStream), Year::now); + + calculator.printYearsLeft(); + + assertThat(outputStream.toString()).contains("At what age would you like to retire?"); + } + + @Test + void displaysYearsLeftUntilRetirement() { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + RetirementCalculator calculator = new RetirementCalculator(new ByteArrayInputStream("25\n65".getBytes()), + new PrintStream(outputStream), new FakeYearProvider()); + + calculator.printYearsLeft(); + + assertThat(outputStream.toString()).containsSequence("You have 40 years left until you can retire.\n", + "It's 2015, so you can retire in 2055.\n"); + } + + @ParameterizedTest + @ValueSource(strings = {"65\n65", "75\n65"}) + void displaysYouCanAlreadyRetireWhenCurrentAgeIsRetirementAgeOrMore(String input) { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + RetirementCalculator calculator = new RetirementCalculator(new ByteArrayInputStream(input.getBytes()), + new PrintStream(outputStream), Year::now); + + calculator.printYearsLeft(); + + assertThat(outputStream.toString()).contains("You can already retire."); + } + + @ParameterizedTest + @ValueSource(strings = {"\n65", "25\n"}) + void inputIsRequired(String input) { + RetirementCalculator calculator = new RetirementCalculator(new ByteArrayInputStream(input.getBytes()), + new PrintStream(new ByteArrayOutputStream()), Year::now); + + assertThatIllegalArgumentException() + .isThrownBy(calculator::printYearsLeft) + .withMessage("Input must not be empty!"); + } + + @ParameterizedTest + @ValueSource(strings = {"abc\n65", "25\nabc"}) + void inputMustBeANumber(String input) { + RetirementCalculator calculator = new RetirementCalculator(new ByteArrayInputStream(input.getBytes()), + new PrintStream(new ByteArrayOutputStream()), Year::now); + + assertThatIllegalArgumentException() + .isThrownBy(calculator::printYearsLeft) + .withMessage("Please enter a valid number! Input: abc"); + } + + @ParameterizedTest + @ValueSource(strings = {"-1\n65", "25\n-1"}) + void inputMustBeAPositiveNumber(String input) { + RetirementCalculator calculator = new RetirementCalculator(new ByteArrayInputStream(input.getBytes()), + new PrintStream(new ByteArrayOutputStream()), Year::now); + + assertThatIllegalArgumentException() + .isThrownBy(calculator::printYearsLeft) + .withMessage("Please enter a positive number! Input: -1"); + } + + static final class FakeYearProvider implements YearProvider { + + @Override + public Year currentYear() { + return Year.of(2015); + } + } +} diff --git a/settings.gradle b/settings.gradle index 7b7d018..6046a37 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,9 @@ rootProject.name = 'exercises-for-programmers-java' -include('saying-hello', 'characters-count', 'printing-quotes', 'mad-lib', 'simple-math') +include( + 'saying-hello', + 'characters-count', + 'printing-quotes', + 'mad-lib', + 'simple-math', + 'retirement-calculator' +)