Skip to content

Commit 5a09894

Browse files
committed
Add support for round room paint calculations
Refactored RoomDimensions from a concrete record to a sealed interface to support multiple room shapes. Created RectangularRoom and RoundRoom implementations, with RoundRoom calculating area as πr². Added shape selection menu to PaintCalculator to prompt for room type before collecting dimensions.
1 parent 2a8a182 commit 5a09894

8 files changed

Lines changed: 246 additions & 48 deletions

File tree

paint-calculator/src/main/java/dev/delivercraft/paint/PaintCalculator.java

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77

88
final class PaintCalculator {
99

10+
private static final String SHAPE_MENU = "Select room shape: 1) Rectangular 2) Round";
11+
12+
private static final String LENGTH_PROMPT = "What is the length of the room in feet? ";
13+
14+
private static final String WIDTH_PROMPT = "What is the width of the room in feet? ";
15+
16+
private static final String RADIUS_PROMPT = "What is the radius of the room in feet? ";
17+
1018
private final LineReader lineReader;
1119

1220
private final LineWriter lineWriter;
@@ -17,13 +25,30 @@ final class PaintCalculator {
1725
}
1826

1927
void calculatePaint() {
20-
RoomDimension length = readDimension("What is the length of the room in feet? ");
21-
RoomDimension width = readDimension("What is the width of the room in feet? ");
28+
this.lineWriter.writeLine(SHAPE_MENU);
29+
String shapeChoice = this.lineReader.readLine();
30+
31+
RoomDimensions dimensions = switch (shapeChoice != null ? shapeChoice.trim() : null) {
32+
case "1" -> readRectangularDimensions();
33+
case "2" -> readRoundDimensions();
34+
case null, default -> throw new IllegalArgumentException("Please enter 1 or 2");
35+
};
2236

23-
PaintEstimate estimate = PaintEstimator.estimate(new RoomDimensions(length, width));
37+
PaintEstimate estimate = PaintEstimator.estimate(dimensions);
38+
39+
this.lineWriter.writeLine("You will need to purchase %s %s of paint to cover %s square feet.".formatted(
40+
estimate.gallons(), estimate.gallonWord(), estimate.area()));
41+
}
42+
43+
private RectangularRoom readRectangularDimensions() {
44+
RoomDimension length = readDimension(LENGTH_PROMPT);
45+
RoomDimension width = readDimension(WIDTH_PROMPT);
46+
return new RectangularRoom(length, width);
47+
}
2448

25-
this.lineWriter.writeLine("You will need to purchase %s %s of paint to cover %s square feet."
26-
.formatted(estimate.gallons(), estimate.gallonWord(), estimate.area()));
49+
private RoundRoom readRoundDimensions() {
50+
RoomDimension radius = readDimension(RADIUS_PROMPT);
51+
return new RoundRoom(radius);
2752
}
2853

2954
private RoomDimension readDimension(String prompt) {
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package dev.delivercraft.paint;
2+
3+
import java.math.BigDecimal;
4+
import java.util.Objects;
5+
6+
public record RectangularRoom(RoomDimension length, RoomDimension width) implements RoomDimensions {
7+
8+
public RectangularRoom(RoomDimension length, RoomDimension width) {
9+
this.length = Objects.requireNonNull(length, "length must not be null");
10+
this.width = Objects.requireNonNull(width, "width must not be null");
11+
}
12+
13+
@Override
14+
public BigDecimal area() {
15+
return this.length.value().multiply(this.width.value());
16+
}
17+
}
Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,8 @@
11
package dev.delivercraft.paint;
22

33
import java.math.BigDecimal;
4-
import java.util.Objects;
54

6-
record RoomDimensions(RoomDimension length, RoomDimension width) {
5+
sealed interface RoomDimensions permits RectangularRoom, RoundRoom {
76

8-
RoomDimensions {
9-
Objects.requireNonNull(length, "length must not be null");
10-
Objects.requireNonNull(width, "width must not be null");
11-
}
12-
13-
BigDecimal area() {
14-
return this.length.value().multiply(this.width.value());
15-
}
7+
BigDecimal area();
168
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package dev.delivercraft.paint;
2+
3+
import java.math.BigDecimal;
4+
import java.math.RoundingMode;
5+
import java.util.Objects;
6+
7+
public record RoundRoom(RoomDimension radius) implements RoomDimensions {
8+
9+
public RoundRoom(RoomDimension radius) {
10+
this.radius = Objects.requireNonNull(radius, "radius must not be null");
11+
}
12+
13+
@Override
14+
public BigDecimal area() {
15+
return this.radius.value()
16+
.multiply(this.radius.value())
17+
.multiply(BigDecimal.valueOf(Math.PI))
18+
.setScale(2, RoundingMode.HALF_UP);
19+
}
20+
}

paint-calculator/src/test/java/dev/delivercraft/paint/MainTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ class MainTest {
2222
void main_GivenValidInput_ShouldPrintPaintEstimate() throws IOException {
2323
InputStream originalInput = System.in;
2424
PrintStream originalOutput = System.out;
25-
String inputText = "18%n20%n".formatted();
25+
String inputText = "1%n18%n20%n".formatted();
2626
ByteArrayInputStream input = new ByteArrayInputStream(inputText.getBytes(StandardCharsets.UTF_8));
2727
ByteArrayOutputStream output = new ByteArrayOutputStream();
2828

Lines changed: 90 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
package dev.delivercraft.paint;
22

3-
import static org.assertj.core.api.Assertions.assertThat;
4-
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
5-
6-
import java.util.stream.Stream;
7-
83
import dev.delivercraft.io.CapturingLineWriter;
94
import dev.delivercraft.io.LineWriter;
105
import dev.delivercraft.io.StubLineReader;
@@ -14,6 +9,11 @@
149
import org.junit.jupiter.params.provider.MethodSource;
1510
import org.junit.jupiter.params.provider.ValueSource;
1611

12+
import java.util.stream.Stream;
13+
14+
import static org.assertj.core.api.Assertions.assertThat;
15+
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
16+
1717
class PaintCalculatorTest {
1818

1919
private static final String VALID_LENGTH = "18";
@@ -36,59 +36,82 @@ class PaintCalculatorTest {
3636

3737
private static final String WIDTH_PROMPT = "What is the width of the room in feet? ";
3838

39+
private static final String SHAPE_MENU = "Select room shape: 1) Rectangular 2) Round";
40+
41+
private static final String RADIUS_PROMPT = "What is the radius of the room in feet? ";
42+
43+
private static final String RECTANGULAR_SHAPE = "1";
44+
45+
private static final String TEN_FEET = "10";
46+
47+
private static final String INVALID_LENGTH_OUTPUT =
48+
SHAPE_MENU + System.lineSeparator() + LENGTH_PROMPT;
49+
50+
private static final String INVALID_WIDTH_OUTPUT =
51+
SHAPE_MENU + System.lineSeparator() + LENGTH_PROMPT + WIDTH_PROMPT;
52+
3953
@Test
4054
void calculatePaint_GivenExampleDimensions_ShouldDisplayGallonsAndArea() {
4155
LineWriter lineWriter = new CapturingLineWriter();
42-
PaintCalculator calculator = new PaintCalculator(new StubLineReader(VALID_LENGTH, VALID_WIDTH), lineWriter);
56+
PaintCalculator calculator = new PaintCalculator(
57+
new StubLineReader(RECTANGULAR_SHAPE, VALID_LENGTH, VALID_WIDTH), lineWriter);
4358

4459
calculator.calculatePaint();
4560

46-
assertThat(lineWriter).hasToString(LENGTH_PROMPT + WIDTH_PROMPT
61+
assertThat(lineWriter).hasToString(SHAPE_MENU + System.lineSeparator()
62+
+ LENGTH_PROMPT + WIDTH_PROMPT
4763
+ "You will need to purchase 2 gallons of paint to cover 360 square feet."
4864
+ System.lineSeparator());
4965
}
5066

5167
@Test
5268
void calculatePaint_GivenSmallCeiling_ShouldUseSingularGallon() {
5369
LineWriter lineWriter = new CapturingLineWriter();
54-
PaintCalculator calculator = new PaintCalculator(new StubLineReader("10", "10"), lineWriter);
70+
PaintCalculator calculator = new PaintCalculator(
71+
new StubLineReader(RECTANGULAR_SHAPE, TEN_FEET, TEN_FEET), lineWriter);
5572

5673
calculator.calculatePaint();
5774

58-
assertThat(lineWriter).hasToString(LENGTH_PROMPT + WIDTH_PROMPT
75+
assertThat(lineWriter).hasToString(SHAPE_MENU + System.lineSeparator()
76+
+ LENGTH_PROMPT + WIDTH_PROMPT
5977
+ "You will need to purchase 1 gallon of paint to cover 100 square feet."
6078
+ System.lineSeparator());
6179
}
6280

6381
@Test
6482
void calculatePaint_GivenFractionalDimensions_ShouldDisplayExactDecimalArea() {
6583
LineWriter lineWriter = new CapturingLineWriter();
66-
PaintCalculator calculator = new PaintCalculator(new StubLineReader("10.5", "10.1"), lineWriter);
84+
PaintCalculator calculator = new PaintCalculator(
85+
new StubLineReader(RECTANGULAR_SHAPE, "10.5", "10.1"), lineWriter);
6786

6887
calculator.calculatePaint();
6988

70-
assertThat(lineWriter).hasToString(LENGTH_PROMPT + WIDTH_PROMPT
89+
assertThat(lineWriter).hasToString(SHAPE_MENU + System.lineSeparator()
90+
+ LENGTH_PROMPT + WIDTH_PROMPT
7191
+ "You will need to purchase 1 gallon of paint to cover 106.05 square feet."
7292
+ System.lineSeparator());
7393
}
7494

7595
@Test
7696
void calculatePaint_GivenAreaJustOverOneGallon_ShouldRoundGallonsUp() {
7797
LineWriter lineWriter = new CapturingLineWriter();
78-
PaintCalculator calculator = new PaintCalculator(new StubLineReader("35.1", "10"), lineWriter);
98+
PaintCalculator calculator = new PaintCalculator(
99+
new StubLineReader(RECTANGULAR_SHAPE, "35.1", "10"), lineWriter);
79100

80101
calculator.calculatePaint();
81102

82-
assertThat(lineWriter).hasToString(LENGTH_PROMPT + WIDTH_PROMPT
103+
assertThat(lineWriter).hasToString(SHAPE_MENU + System.lineSeparator()
104+
+ LENGTH_PROMPT + WIDTH_PROMPT
83105
+ "You will need to purchase 2 gallons of paint to cover 351 square feet."
84106
+ System.lineSeparator());
85107
}
86108

87109
@ParameterizedTest
88110
@MethodSource("invalidInputFlows")
89-
void calculatePaint_GivenInvalidInput_ShouldFailFast(String length, String width, String expectedOutput) {
111+
void calculatePaint_GivenInvalidInput_ShouldFailFast(String shape, String length, String width,
112+
String expectedOutput) {
90113
LineWriter lineWriter = new CapturingLineWriter();
91-
PaintCalculator calculator = new PaintCalculator(new StubLineReader(length, width), lineWriter);
114+
PaintCalculator calculator = new PaintCalculator(new StubLineReader(shape, length, width), lineWriter);
92115

93116
assertThatIllegalArgumentException()
94117
.isThrownBy(calculator::calculatePaint);
@@ -98,30 +121,67 @@ void calculatePaint_GivenInvalidInput_ShouldFailFast(String length, String width
98121

99122
private static Stream<Arguments> invalidInputFlows() {
100123
return Stream.of(
101-
Arguments.of(EMPTY_INPUT, VALID_WIDTH, LENGTH_PROMPT),
102-
Arguments.of(null, VALID_WIDTH, LENGTH_PROMPT),
103-
Arguments.of(NON_NUMERIC_INPUT, VALID_WIDTH, LENGTH_PROMPT),
104-
Arguments.of(ZERO_INPUT, VALID_WIDTH, LENGTH_PROMPT),
105-
Arguments.of(NEGATIVE_INPUT, VALID_WIDTH, LENGTH_PROMPT),
106-
Arguments.of(SCIENTIFIC_INPUT, VALID_WIDTH, LENGTH_PROMPT),
107-
Arguments.of(SHORT_DECIMAL, VALID_WIDTH, LENGTH_PROMPT),
108-
Arguments.of(VALID_LENGTH, EMPTY_INPUT, LENGTH_PROMPT + WIDTH_PROMPT),
109-
Arguments.of(VALID_LENGTH, null, LENGTH_PROMPT + WIDTH_PROMPT),
110-
Arguments.of(VALID_LENGTH, NON_NUMERIC_INPUT, LENGTH_PROMPT + WIDTH_PROMPT),
111-
Arguments.of(VALID_LENGTH, ZERO_INPUT, LENGTH_PROMPT + WIDTH_PROMPT),
112-
Arguments.of(VALID_LENGTH, NEGATIVE_INPUT, LENGTH_PROMPT + WIDTH_PROMPT),
113-
Arguments.of(VALID_LENGTH, SCIENTIFIC_INPUT, LENGTH_PROMPT + WIDTH_PROMPT),
114-
Arguments.of(VALID_LENGTH, SHORT_DECIMAL, LENGTH_PROMPT + WIDTH_PROMPT));
124+
Arguments.of(RECTANGULAR_SHAPE, EMPTY_INPUT, VALID_WIDTH, INVALID_LENGTH_OUTPUT),
125+
Arguments.of(RECTANGULAR_SHAPE, null, VALID_WIDTH, INVALID_LENGTH_OUTPUT),
126+
Arguments.of(RECTANGULAR_SHAPE, NON_NUMERIC_INPUT, VALID_WIDTH, INVALID_LENGTH_OUTPUT),
127+
Arguments.of(RECTANGULAR_SHAPE, ZERO_INPUT, VALID_WIDTH, INVALID_LENGTH_OUTPUT),
128+
Arguments.of(RECTANGULAR_SHAPE, NEGATIVE_INPUT, VALID_WIDTH, INVALID_LENGTH_OUTPUT),
129+
Arguments.of(RECTANGULAR_SHAPE, SCIENTIFIC_INPUT, VALID_WIDTH, INVALID_LENGTH_OUTPUT),
130+
Arguments.of(RECTANGULAR_SHAPE, SHORT_DECIMAL, VALID_WIDTH, INVALID_LENGTH_OUTPUT),
131+
Arguments.of(RECTANGULAR_SHAPE, VALID_LENGTH, EMPTY_INPUT, INVALID_WIDTH_OUTPUT),
132+
Arguments.of(RECTANGULAR_SHAPE, VALID_LENGTH, null, INVALID_WIDTH_OUTPUT),
133+
Arguments.of(RECTANGULAR_SHAPE, VALID_LENGTH, NON_NUMERIC_INPUT, INVALID_WIDTH_OUTPUT),
134+
Arguments.of(RECTANGULAR_SHAPE, VALID_LENGTH, ZERO_INPUT, INVALID_WIDTH_OUTPUT),
135+
Arguments.of(RECTANGULAR_SHAPE, VALID_LENGTH, NEGATIVE_INPUT, INVALID_WIDTH_OUTPUT),
136+
Arguments.of(RECTANGULAR_SHAPE, VALID_LENGTH, SCIENTIFIC_INPUT, INVALID_WIDTH_OUTPUT),
137+
Arguments.of(RECTANGULAR_SHAPE, VALID_LENGTH, SHORT_DECIMAL, INVALID_WIDTH_OUTPUT));
115138
}
116139

117140
@ParameterizedTest
118141
@ValueSource(strings = {"18", " 18 ", "18.0", "0.5"})
119142
void calculatePaint_GivenPlainDecimalInput_ShouldAcceptInput(String input) {
120143
LineWriter lineWriter = new CapturingLineWriter();
121-
PaintCalculator calculator = new PaintCalculator(new StubLineReader(input, "20"), lineWriter);
144+
PaintCalculator calculator = new PaintCalculator(
145+
new StubLineReader(RECTANGULAR_SHAPE, input, "20"), lineWriter);
122146

123147
calculator.calculatePaint();
124148

125149
assertThat(lineWriter.toString()).contains("You will need to purchase");
126150
}
151+
152+
@Test
153+
void calculatePaint_GivenRoundRoomSelection_ShouldCalculateCorrectArea() {
154+
LineWriter lineWriter = new CapturingLineWriter();
155+
PaintCalculator calculator = new PaintCalculator(new StubLineReader("2", "10"), lineWriter);
156+
157+
calculator.calculatePaint();
158+
159+
assertThat(lineWriter).hasToString(SHAPE_MENU + System.lineSeparator()
160+
+ RADIUS_PROMPT
161+
+ "You will need to purchase 1 gallon of paint to cover 314.16 square feet."
162+
+ System.lineSeparator());
163+
}
164+
165+
@ParameterizedTest
166+
@ValueSource(strings = {"3", "0", "abc", "", " "})
167+
void calculatePaint_GivenInvalidShapeInput_ShouldThrowIllegalArgumentException(String invalidInput) {
168+
LineWriter lineWriter = new CapturingLineWriter();
169+
PaintCalculator calculator = new PaintCalculator(new StubLineReader(invalidInput), lineWriter);
170+
171+
assertThatIllegalArgumentException()
172+
.isThrownBy(calculator::calculatePaint)
173+
.withMessage("Please enter 1 or 2");
174+
assertThat(lineWriter.toString()).isEqualTo(SHAPE_MENU + System.lineSeparator());
175+
}
176+
177+
@Test
178+
void calculatePaint_GivenNullShapeInput_ShouldThrowIllegalArgumentException() {
179+
LineWriter lineWriter = new CapturingLineWriter();
180+
PaintCalculator calculator = new PaintCalculator(() -> null, lineWriter);
181+
182+
assertThatIllegalArgumentException()
183+
.isThrownBy(calculator::calculatePaint)
184+
.withMessage("Please enter 1 or 2");
185+
assertThat(lineWriter.toString()).isEqualTo(SHAPE_MENU + System.lineSeparator());
186+
}
127187
}

paint-calculator/src/test/java/dev/delivercraft/paint/PaintEstimatorTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class PaintEstimatorTest {
1010

1111
@Test
1212
void estimate_GivenExampleDimensions_ShouldCalculateAreaAndGallons() {
13-
RoomDimensions dimensions = new RoomDimensions(
13+
RoomDimensions dimensions = new RectangularRoom(
1414
RoomDimension.of("18"),
1515
RoomDimension.of("20"));
1616

@@ -22,7 +22,7 @@ void estimate_GivenExampleDimensions_ShouldCalculateAreaAndGallons() {
2222

2323
@Test
2424
void estimate_GivenAreaJustOverOneGallon_ShouldRoundGallonsUp() {
25-
RoomDimensions dimensions = new RoomDimensions(
25+
RoomDimensions dimensions = new RectangularRoom(
2626
RoomDimension.of("35.1"),
2727
RoomDimension.of("10"));
2828

0 commit comments

Comments
 (0)