Skip to content

Commit 3ac774a

Browse files
committed
Add paint calculator exercise
1 parent 3903e30 commit 3ac774a

15 files changed

Lines changed: 451 additions & 4 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Exercises for Programmers: 57 Challenges to Develop Your Coding Skills by Brian
2121
**Calculations**
2222
- Exercise 7. [Area of a Rectangular Room](https://github.com/durimkryeziu/exercises-for-programmers-java/tree/main/area-rectangular-room)
2323
- Exercise 8. [Pizza Party](https://github.com/durimkryeziu/exercises-for-programmers-java/tree/main/pizza-party)
24-
- Exercise 9. Paint Calculator
24+
- Exercise 9. [Paint Calculator](https://github.com/durimkryeziu/exercises-for-programmers-java/tree/main/paint-calculator)
2525
- Exercise 10. Self-Checkout
2626
- Exercise 11. Currency Conversion
2727
- Exercise 12. Computing Simple Interest

config/pmd/ruleset.xml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,11 @@
8686
<rule ref="category/java/codestyle.xml/LocalHomeNamingConvention"/>
8787
<rule ref="category/java/codestyle.xml/LocalInterfaceSessionNamingConvention"/>
8888
<rule ref="category/java/codestyle.xml/LocalVariableNamingConventions"/>
89-
<rule ref="category/java/codestyle.xml/LongVariable"/>
89+
<rule ref="category/java/codestyle.xml/LongVariable">
90+
<properties>
91+
<property name="minimum" value="25"/>
92+
</properties>
93+
</rule>
9094
<rule ref="category/java/codestyle.xml/MethodNamingConventions">
9195
<properties>
9296
<property name="junit5TestPattern" value="^[a-z][a-zA-Z0-9]*(_[a-zA-Z0-9]+)*$"/>
@@ -166,7 +170,9 @@
166170
<rule ref="category/java/design.xml/UseObjectForClearerAPI"/>
167171
<!-- Design -->
168172

169-
<rule ref="category/java/errorprone.xml"/>
173+
<rule ref="category/java/errorprone.xml">
174+
<exclude name="AvoidFieldNameMatchingMethodName"/>
175+
</rule>
170176
<rule ref="category/java/multithreading.xml"/>
171177
<rule ref="category/java/performance.xml"/>
172178
<rule ref="category/java/security.xml"/>

paint-calculator/build.gradle

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
plugins {
2+
id 'dev.delivercraft.common-conventions'
3+
}
4+
5+
dependencies {
6+
implementation(project(':line-io'))
7+
testImplementation(testFixtures(project(':line-io')))
8+
testImplementation(libs.assertj.core)
9+
testImplementation(libs.junit.jupiter)
10+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package dev.delivercraft.paint;
2+
3+
import dev.delivercraft.io.InputStreamLineReader;
4+
import dev.delivercraft.io.LineReader;
5+
import dev.delivercraft.io.LineWriter;
6+
import dev.delivercraft.io.OutputStreamLineWriter;
7+
8+
public final class Main {
9+
10+
void main() {
11+
LineReader lineReader = new InputStreamLineReader(System.in);
12+
LineWriter lineWriter = new OutputStreamLineWriter(System.out);
13+
PaintCalculator calculator = new PaintCalculator(lineReader, lineWriter);
14+
calculator.calculatePaint();
15+
}
16+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package dev.delivercraft.paint;
2+
3+
import dev.delivercraft.io.LineReader;
4+
import dev.delivercraft.io.LineWriter;
5+
6+
import java.util.Objects;
7+
8+
final class PaintCalculator {
9+
10+
private final LineReader lineReader;
11+
12+
private final LineWriter lineWriter;
13+
14+
PaintCalculator(LineReader lineReader, LineWriter lineWriter) {
15+
this.lineReader = Objects.requireNonNull(lineReader, "lineReader must not be null");
16+
this.lineWriter = Objects.requireNonNull(lineWriter, "lineWriter must not be null");
17+
}
18+
19+
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? ");
22+
23+
PaintEstimate estimate = PaintEstimator.estimate(new RoomDimensions(length, width));
24+
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()));
27+
}
28+
29+
private RoomDimension readDimension(String prompt) {
30+
this.lineWriter.write(prompt);
31+
return RoomDimension.of(this.lineReader.readLine());
32+
}
33+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package dev.delivercraft.paint;
2+
3+
import java.math.BigDecimal;
4+
import java.math.BigInteger;
5+
import java.util.Objects;
6+
7+
final class PaintEstimate {
8+
9+
private final BigDecimal area;
10+
11+
private final BigInteger gallons;
12+
13+
PaintEstimate(BigDecimal area, BigInteger gallons) {
14+
this.area = Objects.requireNonNull(area, "area must not be null");
15+
this.gallons = Objects.requireNonNull(gallons, "gallons must not be null");
16+
}
17+
18+
BigInteger gallons() {
19+
return this.gallons;
20+
}
21+
22+
String area() {
23+
return this.area.stripTrailingZeros().toPlainString();
24+
}
25+
26+
String gallonWord() {
27+
if (BigInteger.ONE.equals(this.gallons)) {
28+
return "gallon";
29+
}
30+
return "gallons";
31+
}
32+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package dev.delivercraft.paint;
2+
3+
import java.math.BigDecimal;
4+
import java.math.BigInteger;
5+
import java.math.RoundingMode;
6+
import java.util.Objects;
7+
8+
final class PaintEstimator {
9+
10+
private static final int SQUARE_FEET_PER_GALLON = 350;
11+
12+
private PaintEstimator() {
13+
}
14+
15+
static PaintEstimate estimate(RoomDimensions dimensions) {
16+
Objects.requireNonNull(dimensions, "dimensions must not be null");
17+
BigDecimal area = dimensions.area();
18+
BigInteger gallons = area.divide(BigDecimal.valueOf(SQUARE_FEET_PER_GALLON), 0, RoundingMode.CEILING)
19+
.toBigIntegerExact();
20+
return new PaintEstimate(area, gallons);
21+
}
22+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package dev.delivercraft.paint;
2+
3+
import java.math.BigDecimal;
4+
import java.util.Objects;
5+
import java.util.regex.Pattern;
6+
7+
record RoomDimension(BigDecimal value) {
8+
9+
private static final Pattern PLAIN_DECIMAL = Pattern.compile("\\d+(\\.\\d+)?");
10+
11+
RoomDimension {
12+
Objects.requireNonNull(value, "value must not be null");
13+
if (BigDecimal.ZERO.compareTo(value) >= 0) {
14+
throw new IllegalArgumentException("Please enter a positive number! Input: %s".formatted(value));
15+
}
16+
value = value.stripTrailingZeros();
17+
}
18+
19+
static RoomDimension of(String input) {
20+
if (input == null || input.isBlank()) {
21+
throw new IllegalArgumentException("Input must not be empty!");
22+
}
23+
24+
String trimmedInput = input.trim();
25+
if (!PLAIN_DECIMAL.matcher(trimmedInput).matches()) {
26+
throw new IllegalArgumentException("Please enter a valid number! Input: %s".formatted(trimmedInput));
27+
}
28+
29+
BigDecimal number = new BigDecimal(trimmedInput);
30+
return new RoomDimension(number);
31+
}
32+
33+
@Override
34+
public String toString() {
35+
return this.value.toPlainString();
36+
}
37+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package dev.delivercraft.paint;
2+
3+
import java.math.BigDecimal;
4+
import java.util.Objects;
5+
6+
record RoomDimensions(RoomDimension length, RoomDimension width) {
7+
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+
}
16+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package dev.delivercraft.paint;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import java.io.ByteArrayInputStream;
6+
import java.io.ByteArrayOutputStream;
7+
import java.io.IOException;
8+
import java.io.InputStream;
9+
import java.io.PrintStream;
10+
import java.nio.charset.StandardCharsets;
11+
12+
import org.junit.jupiter.api.Test;
13+
import org.junit.jupiter.api.parallel.ResourceLock;
14+
import org.junit.jupiter.api.parallel.Resources;
15+
16+
class MainTest {
17+
18+
@Test
19+
@ResourceLock("SYSTEM_IN")
20+
@ResourceLock(Resources.SYSTEM_OUT)
21+
@SuppressWarnings("PMD.CloseResource")
22+
void main_GivenValidInput_ShouldPrintPaintEstimate() throws IOException {
23+
InputStream originalInput = System.in;
24+
PrintStream originalOutput = System.out;
25+
String inputText = "18%n20%n".formatted();
26+
ByteArrayInputStream input = new ByteArrayInputStream(inputText.getBytes(StandardCharsets.UTF_8));
27+
ByteArrayOutputStream output = new ByteArrayOutputStream();
28+
29+
try (input; output; PrintStream printStream = new PrintStream(output, true, StandardCharsets.UTF_8)) {
30+
System.setIn(input);
31+
System.setOut(printStream);
32+
33+
new Main().main();
34+
35+
assertThat(output.toString(StandardCharsets.UTF_8)).contains(
36+
"You will need to purchase 2 gallons of paint to cover 360 square feet.");
37+
} finally {
38+
System.setIn(originalInput);
39+
System.setOut(originalOutput);
40+
}
41+
}
42+
}

0 commit comments

Comments
 (0)