Skip to content

Commit 0304a97

Browse files
committed
Add 'Retirement Calculator' solution
1 parent 39b8836 commit 0304a97

File tree

7 files changed

+199
-2
lines changed

7 files changed

+199
-2
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Exercises for Programmers: 57 Challenges to Develop Your Coding Skills by Brian
99
- Exercise 3. [Printing Quotes](https://github.com/durimkryeziu/exercises-for-programmers-java/tree/main/printing-quotes)
1010
- Exercise 4. [Mad Lib](https://github.com/durimkryeziu/exercises-for-programmers-java/tree/main/mad-lib)
1111
- Exercise 5. [Simple Math](https://github.com/durimkryeziu/exercises-for-programmers-java/tree/main/simple-math)
12-
- Exercise 6. Retirement Calculator
12+
- Exercise 6. [Retirement Calculator](https://github.com/durimkryeziu/exercises-for-programmers-java/tree/main/retirement-calculator)
1313

1414
**Calculations**
1515
- Exercise 7. Area of a Rectangular Room

retirement-calculator/build.gradle

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
plugins {
2+
id 'com.craftsmanshipinsoftware.common-conventions'
3+
}
4+
5+
dependencies {
6+
testImplementation(libs.assertj.core)
7+
testImplementation(libs.junit.jupiter)
8+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.craftsmanshipinsoftware.retirement;
2+
3+
import java.time.Year;
4+
5+
public class Main {
6+
7+
public static void main(String[] args) {
8+
RetirementCalculator calculator = new RetirementCalculator(System.in, System.out, Year::now);
9+
calculator.printYearsLeft();
10+
}
11+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.craftsmanshipinsoftware.retirement;
2+
3+
import java.io.InputStream;
4+
import java.io.PrintStream;
5+
import java.time.Year;
6+
import java.util.Objects;
7+
import java.util.Scanner;
8+
9+
class RetirementCalculator {
10+
11+
private final InputStream inputStream;
12+
private final PrintStream printStream;
13+
private final YearProvider yearProvider;
14+
15+
RetirementCalculator(InputStream inputStream, PrintStream printStream, YearProvider yearProvider) {
16+
Objects.requireNonNull(inputStream, "inputStream must not be null");
17+
Objects.requireNonNull(printStream, "printStream must not be null");
18+
Objects.requireNonNull(yearProvider, "yearProvider must not be null");
19+
this.inputStream = inputStream;
20+
this.printStream = printStream;
21+
this.yearProvider = yearProvider;
22+
}
23+
24+
void printYearsLeft() {
25+
try (Scanner scanner = new Scanner(inputStream)) {
26+
int currentAge = toInt(promptForInput("What is your current age? ", scanner));
27+
int retirementAge = toInt(promptForInput("At what age would you like to retire? ", scanner));
28+
int yearsLeft = retirementAge - currentAge;
29+
if (yearsLeft <= 0) {
30+
this.printStream.println("You can already retire.");
31+
} else {
32+
this.printStream.printf("You have %d years left until you can retire.%n", yearsLeft);
33+
}
34+
Year currentYear = this.yearProvider.currentYear();
35+
this.printStream.printf("It's %s, so you can retire in %s.%n", currentYear,
36+
currentYear.plusYears(yearsLeft));
37+
}
38+
}
39+
40+
private String promptForInput(String prompt, Scanner scanner) {
41+
this.printStream.print(prompt);
42+
String input = scanner.hasNext() ? scanner.nextLine() : null;
43+
if (input == null || input.isBlank()) {
44+
throw new IllegalArgumentException("Input must not be empty!");
45+
}
46+
return input;
47+
}
48+
49+
private int toInt(String value) {
50+
try {
51+
int number = Integer.parseInt(value);
52+
if (number < 0) {
53+
throw new IllegalArgumentException("Please enter a positive number! Input: %s".formatted(value));
54+
}
55+
return number;
56+
} catch (NumberFormatException e) {
57+
throw new IllegalArgumentException("Please enter a valid number! Input: %s".formatted(value), e);
58+
}
59+
}
60+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.craftsmanshipinsoftware.retirement;
2+
3+
import java.time.Year;
4+
5+
@FunctionalInterface
6+
interface YearProvider {
7+
8+
Year currentYear();
9+
}
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package com.craftsmanshipinsoftware.retirement;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
5+
6+
import java.io.ByteArrayInputStream;
7+
import java.io.ByteArrayOutputStream;
8+
import java.io.PrintStream;
9+
import java.time.Year;
10+
import org.junit.jupiter.api.Test;
11+
import org.junit.jupiter.params.ParameterizedTest;
12+
import org.junit.jupiter.params.provider.ValueSource;
13+
14+
class RetirementCalculatorTest {
15+
16+
@Test
17+
void asksForCurrentAge() {
18+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
19+
RetirementCalculator calculator = new RetirementCalculator(new ByteArrayInputStream("25\n65".getBytes()),
20+
new PrintStream(outputStream), Year::now);
21+
22+
calculator.printYearsLeft();
23+
24+
assertThat(outputStream.toString()).contains("What is your current age?");
25+
}
26+
27+
@Test
28+
void asksForRetirementAge() {
29+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
30+
RetirementCalculator calculator = new RetirementCalculator(new ByteArrayInputStream("25\n65".getBytes()),
31+
new PrintStream(outputStream), Year::now);
32+
33+
calculator.printYearsLeft();
34+
35+
assertThat(outputStream.toString()).contains("At what age would you like to retire?");
36+
}
37+
38+
@Test
39+
void displaysYearsLeftUntilRetirement() {
40+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
41+
RetirementCalculator calculator = new RetirementCalculator(new ByteArrayInputStream("25\n65".getBytes()),
42+
new PrintStream(outputStream), new FakeYearProvider());
43+
44+
calculator.printYearsLeft();
45+
46+
assertThat(outputStream.toString()).containsSequence("You have 40 years left until you can retire.\n",
47+
"It's 2015, so you can retire in 2055.\n");
48+
}
49+
50+
@ParameterizedTest
51+
@ValueSource(strings = {"65\n65", "75\n65"})
52+
void displaysYouCanAlreadyRetireWhenCurrentAgeIsRetirementAgeOrMore(String input) {
53+
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
54+
RetirementCalculator calculator = new RetirementCalculator(new ByteArrayInputStream(input.getBytes()),
55+
new PrintStream(outputStream), Year::now);
56+
57+
calculator.printYearsLeft();
58+
59+
assertThat(outputStream.toString()).contains("You can already retire.");
60+
}
61+
62+
@ParameterizedTest
63+
@ValueSource(strings = {"\n65", "25\n"})
64+
void inputIsRequired(String input) {
65+
RetirementCalculator calculator = new RetirementCalculator(new ByteArrayInputStream(input.getBytes()),
66+
new PrintStream(new ByteArrayOutputStream()), Year::now);
67+
68+
assertThatIllegalArgumentException()
69+
.isThrownBy(calculator::printYearsLeft)
70+
.withMessage("Input must not be empty!");
71+
}
72+
73+
@ParameterizedTest
74+
@ValueSource(strings = {"abc\n65", "25\nabc"})
75+
void inputMustBeANumber(String input) {
76+
RetirementCalculator calculator = new RetirementCalculator(new ByteArrayInputStream(input.getBytes()),
77+
new PrintStream(new ByteArrayOutputStream()), Year::now);
78+
79+
assertThatIllegalArgumentException()
80+
.isThrownBy(calculator::printYearsLeft)
81+
.withMessage("Please enter a valid number! Input: abc");
82+
}
83+
84+
@ParameterizedTest
85+
@ValueSource(strings = {"-1\n65", "25\n-1"})
86+
void inputMustBeAPositiveNumber(String input) {
87+
RetirementCalculator calculator = new RetirementCalculator(new ByteArrayInputStream(input.getBytes()),
88+
new PrintStream(new ByteArrayOutputStream()), Year::now);
89+
90+
assertThatIllegalArgumentException()
91+
.isThrownBy(calculator::printYearsLeft)
92+
.withMessage("Please enter a positive number! Input: -1");
93+
}
94+
95+
static final class FakeYearProvider implements YearProvider {
96+
97+
@Override
98+
public Year currentYear() {
99+
return Year.of(2015);
100+
}
101+
}
102+
}

settings.gradle

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,9 @@
11
rootProject.name = 'exercises-for-programmers-java'
2-
include('saying-hello', 'characters-count', 'printing-quotes', 'mad-lib', 'simple-math')
2+
include(
3+
'saying-hello',
4+
'characters-count',
5+
'printing-quotes',
6+
'mad-lib',
7+
'simple-math',
8+
'retirement-calculator'
9+
)

0 commit comments

Comments
 (0)