Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .mvn/jvm.config
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
-Xss16m -Xms256m -Xmx1024m -Djava.awt.headless=true
-Xss16m
-XX:+EnableDynamicAgentLoading
-Xms256m
-Xmx1024m
-Djava.awt.headless=true
--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
--add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ PrimeKit Coding Challenges
<img height="40" width="40" src="https://cdn.simpleicons.org/codility?viewbox=auto" alt="Codility" />
</a>
</td>
<td style="padding:10px;">
<a href="https://coderbyte.com/" title="Visit CoderByte">
<img height="40" width="40" src="https://cdn.simpleicons.org/coderbyte?viewbox=auto" alt="CoderByte" />
</a>
</td>
</tr>
</table>

Expand All @@ -56,6 +61,11 @@ PrimeKit Coding Challenges
<img height="40" width="40" src="https://cdn.simpleicons.org/intellijidea?viewbox=auto" alt="IntelliJ IDEA" />
</a>
</td>
<td style="padding:10px;">
<a href="https://www.jetbrains.com" title="JetBrains">
<img height="40" width="40" src="https://cdn.simpleicons.org/jetbrains?viewbox=auto" alt="JetBrains" />
</a>
</td>
<td style="padding:10px;">
<a href="https://openjdk.org/" title="OpenJDK">
<img height="40" width="40" src="https://cdn.simpleicons.org/openjdk?viewbox=auto" alt="OpenJDK" />
Expand Down
31 changes: 20 additions & 11 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
<buildtime.output.log>true</buildtime.output.log>

<lombok.version>1.18.32</lombok.version>
<mockito.version>5.20.0</mockito.version>
<junit-jupiter.version>5.10.2</junit-jupiter.version>
<junit-platform.version>1.10.2</junit-platform.version>
<commons-lang3.version>3.14.0</commons-lang3.version>
Expand Down Expand Up @@ -140,17 +141,23 @@
<version>${instancio.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down Expand Up @@ -250,6 +257,7 @@
<testFailureIgnore>true</testFailureIgnore>
<excludes>
<exclude>*com.shortthirdman.primekit.common.*</exclude>
<exclude>*com.shortthirdman.primekit.core.*</exclude>
</excludes>
<properties>
<configurationParameters>
Expand Down Expand Up @@ -280,6 +288,7 @@
<sourceEncoding>${project.build.sourceEncoding}</sourceEncoding>
<excludes>
<exclude>com/shortthirdman/primekit/common/**/*.java</exclude>
<exclude>com/shortthirdman/primekit/core/**/*.java</exclude>
</excludes>
<outputEncoding>${project.build.sourceEncoding}</outputEncoding>
<formats>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package com.shortthirdman.primekit.coderbyte;

import com.shortthirdman.primekit.core.domain.ProductCategory;
import com.shortthirdman.primekit.core.funcinterface.TaxCalculator;
import com.shortthirdman.primekit.core.strategy.LuxuryTaxCalculation;
import com.shortthirdman.primekit.core.strategy.PremiumTaxCalculation;
import com.shortthirdman.primekit.core.strategy.StandardTaxCalculation;
import com.shortthirdman.primekit.core.strategy.TaxCalculationStrategy;
import lombok.RequiredArgsConstructor;

import java.util.Objects;

/**
* Calculates tax by category and strategy
* @since 1.1.1
* @author ShortThirdMan
*/
public class PatternAnalyzer {

private static final Boolean DEBUG = System.getenv("DEBUG") != null || System.getProperty("DEBUG") != null;

public double calculateTaxByStrategy(double baseAmount, ProductCategory category) {
if (Objects.isNull(category)) {
throw new IllegalArgumentException("Category is null");
}

TaxCalculationStrategy strategy = switch (category) {
case STANDARD -> new StandardTaxCalculation();
case PREMIUM -> new PremiumTaxCalculation();
case LUXURY -> new LuxuryTaxCalculation();
default -> throw new IllegalArgumentException("Unsupported product category");
};

double taxAmount = strategy.calculateTax(baseAmount);
if (DEBUG) {
System.out.println("[CalculateTaxByStrategy] Tax for $" + baseAmount + " (" + category + "): $" + taxAmount);
}

return taxAmount;
}

public double calculateTaxByCategory(double baseAmount, ProductCategory category) {
if (Objects.isNull(category)) {
throw new IllegalArgumentException("Category is null");
}

TaxCalculator calculator = switch (category) {
case STANDARD -> amount1 -> amount1 * 0.10; // 10% tax
case PREMIUM -> amount1 -> amount1 * 0.20; // 20% tax
case LUXURY -> amount1 -> amount1 * 0.30; // 30% tax
default -> throw new IllegalArgumentException("Unsupported product category");
};

double taxAmount = calculator.calculate(baseAmount);
if (DEBUG) {
System.out.println("[CalculateTaxByCategory] Tax for $" + baseAmount + " (" + category + "): $" + taxAmount);
}

return taxAmount;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.shortthirdman.primekit.core.domain;

public enum ProductCategory {

STANDARD, PREMIUM, LUXURY
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.shortthirdman.primekit.core.funcinterface;

@FunctionalInterface
public interface TaxCalculator {

double calculate(double amount);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.shortthirdman.primekit.core.strategy;

public final class LuxuryTaxCalculation implements TaxCalculationStrategy {

@Override
public double calculateTax(double amount) {
return amount * 0.30; // 30% tax
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.shortthirdman.primekit.core.strategy;

public final class PremiumTaxCalculation implements TaxCalculationStrategy {

@Override
public double calculateTax(double amount) {
return amount * 0.20; // 20% tax
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.shortthirdman.primekit.core.strategy;

public final class StandardTaxCalculation implements TaxCalculationStrategy {

@Override
public double calculateTax(double amount) {
return amount * 0.10; // 10% tax
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.shortthirdman.primekit.core.strategy;

public sealed interface TaxCalculationStrategy permits StandardTaxCalculation, PremiumTaxCalculation, LuxuryTaxCalculation {

double calculateTax(double amount);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package com.shortthirdman.primekit.coderbyte;

import com.shortthirdman.primekit.core.domain.ProductCategory;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;

class PatternAnalyzerTest {

private static PatternAnalyzer analyzer;

@BeforeAll
static void setUp() {
analyzer = new PatternAnalyzer();
}

// ==============================================================
// calculateTaxByStrategy() POSITIVE TESTS
// ==============================================================

@Test
@DisplayName("calculateTaxByStrategy - STANDARD tax should be 10%")
void testStrategyStandardTax() {
double tax = analyzer.calculateTaxByStrategy(100.00, ProductCategory.STANDARD);
assertEquals(10.00, tax, 0.001);
}

@Test
@DisplayName("calculateTaxByStrategy - PREMIUM tax should be 20%")
void testStrategyPremiumTax() {
double tax = analyzer.calculateTaxByStrategy(200.00, ProductCategory.PREMIUM);
assertEquals(40.00, tax, 0.001);
}

@Test
@DisplayName("calculateTaxByStrategy - LUXURY tax should be 30%")
void testStrategyLuxuryTax() {
double tax = analyzer.calculateTaxByStrategy(300.00, ProductCategory.LUXURY);
assertEquals(90.00, tax, 0.001);
}

// ==============================================================
// calculateTaxByCategory() POSITIVE TESTS
// ==============================================================

@Test
@DisplayName("calculateTaxByCategory - STANDARD tax should be 10%")
void testCategoryStandardTax() {
double tax = analyzer.calculateTaxByCategory(100.00, ProductCategory.STANDARD);
assertEquals(10.00, tax, 0.001);
}

@Test
@DisplayName("calculateTaxByCategory - PREMIUM tax should be 20%")
void testCategoryPremiumTax() {
double tax = analyzer.calculateTaxByCategory(200.00, ProductCategory.PREMIUM);
assertEquals(40.00, tax, 0.001);
}

@Test
@DisplayName("calculateTaxByCategory - LUXURY tax should be 30%")
void testCategoryLuxuryTax() {
double tax = analyzer.calculateTaxByCategory(300.00, ProductCategory.LUXURY);
assertEquals(90.00, tax, 0.001);
}



// ==============================================================
// NEGATIVE INPUT TESTS (invalid numbers)
// ==============================================================

@Test
@DisplayName("calculateTaxByStrategy - negative amount allowed but calculation valid")
void testStrategyNegativeAmount() {
double tax = analyzer.calculateTaxByStrategy(-100.00, ProductCategory.STANDARD);
assertEquals(-10.00, tax, 0.001);
}

@Test
@DisplayName("calculateTaxByCategory - negative amount allowed but calculation valid")
void testCategoryNegativeAmount() {
double tax = analyzer.calculateTaxByCategory(-100.00, ProductCategory.LUXURY);
assertEquals(-30.00, tax, 0.001);
}

// ==============================================================
// EDGE CASES
// ==============================================================

@Test
@DisplayName("calculateTaxByStrategy - zero amount should return zero")
void testStrategyZeroAmount() {
double tax = analyzer.calculateTaxByStrategy(0.0, ProductCategory.PREMIUM);
assertEquals(0.0, tax);
}

@Test
@DisplayName("calculateTaxByCategory - zero amount should return zero")
void testCategoryZeroAmount() {
double tax = analyzer.calculateTaxByCategory(0.0, ProductCategory.LUXURY);
assertEquals(0.0, tax);
}

@Test
@DisplayName("calculateTaxByStrategy - very large amount precision test")
void testStrategyLargeAmount() {
double tax = analyzer.calculateTaxByStrategy(1_000_000.0, ProductCategory.LUXURY);
assertEquals(300_000.0, tax);
}

@Test
@DisplayName("calculateTaxByCategory - very large amount precision test")
void testCategoryLargeAmount() {
double tax = analyzer.calculateTaxByCategory(1_000_000.0, ProductCategory.STANDARD);
assertEquals(100_000.0, tax);
}

// ==============================================================
// NULL CATEGORY (EXCEPTION TESTS)
// ==============================================================

@Test
@DisplayName("calculateTaxByStrategy - null category throws IllegalArgumentException")
void testStrategyNullCategory() {
Assertions.assertThrows(IllegalArgumentException.class,
() -> analyzer.calculateTaxByStrategy(50.0, null));
}

@Test
@DisplayName("calculateTaxByCategory - null category throws IllegalArgumentException")
void testCategoryNullCategory() {
Assertions.assertThrows(IllegalArgumentException.class,
() -> analyzer.calculateTaxByCategory(50.0, null));
}

// ==============================================================
// UNSUPPORTED CATEGORY (just in case enum grows)
// ==============================================================

@Test
@DisplayName("calculateTaxByStrategy - unsupported category throws IllegalArgumentException")
void testStrategyUnsupportedCategory() {
Assertions.assertThrows(IllegalArgumentException.class,
() -> analyzer.calculateTaxByStrategy(100.0, ProductCategory.valueOf("UNKNOWN")));
}

@Test
@DisplayName("calculateTaxByCategory - unsupported category throws IllegalArgumentException")
void testCategoryUnsupportedCategory() {
Assertions.assertThrows(IllegalArgumentException.class,
() -> analyzer.calculateTaxByCategory(100.0, ProductCategory.valueOf("UNKNOWN")));
}

@Test
@DisplayName("Standard category applies 10% tax")
void standardCategoryApplies10PercentTax() {
PatternAnalyzer analyzer = new PatternAnalyzer();

double result = analyzer.calculateTaxByStrategy(100, ProductCategory.STANDARD);

assertEquals(10.0, result);
}

@Test
@DisplayName("Premium category applies 20% tax")
void premiumCategoryApplies20PercentTax() {
PatternAnalyzer analyzer = new PatternAnalyzer();

double result = analyzer.calculateTaxByStrategy(100, ProductCategory.PREMIUM);

assertEquals(20.0, result);
}

@Test
@DisplayName("Luxury category applies 30% tax")
void luxuryCategoryApplies30PercentTax() {
PatternAnalyzer analyzer = new PatternAnalyzer();

double result = analyzer.calculateTaxByStrategy(100, ProductCategory.LUXURY);

assertEquals(30.0, result);
}
}