diff --git a/.mvn/jvm.config b/.mvn/jvm.config index 1d210e8..3e3059a 100644 --- a/.mvn/jvm.config +++ b/.mvn/jvm.config @@ -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 diff --git a/README.md b/README.md index ceaef88..83cd1d9 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,11 @@ PrimeKit Coding Challenges Codility + + + CoderByte + + @@ -56,6 +61,11 @@ PrimeKit Coding Challenges IntelliJ IDEA + + + JetBrains + + OpenJDK diff --git a/pom.xml b/pom.xml index b631601..1161fa8 100644 --- a/pom.xml +++ b/pom.xml @@ -35,6 +35,7 @@ true 1.18.32 + 5.20.0 5.10.2 1.10.2 3.14.0 @@ -140,17 +141,23 @@ ${instancio.version} test - - org.junit.jupiter - junit-jupiter - test - - - org.mockito - mockito-core - ${mockito.version} - test - + + org.junit.jupiter + junit-jupiter + test + + + org.mockito + mockito-core + ${mockito.version} + test + + + org.mockito + mockito-junit-jupiter + ${mockito.version} + test + @@ -250,6 +257,7 @@ true *com.shortthirdman.primekit.common.* + *com.shortthirdman.primekit.core.* @@ -280,6 +288,7 @@ ${project.build.sourceEncoding} com/shortthirdman/primekit/common/**/*.java + com/shortthirdman/primekit/core/**/*.java ${project.build.sourceEncoding} diff --git a/src/main/java/com/shortthirdman/primekit/coderbyte/PatternAnalyzer.java b/src/main/java/com/shortthirdman/primekit/coderbyte/PatternAnalyzer.java new file mode 100644 index 0000000..ccd7f47 --- /dev/null +++ b/src/main/java/com/shortthirdman/primekit/coderbyte/PatternAnalyzer.java @@ -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; + } +} diff --git a/src/main/java/com/shortthirdman/primekit/core/domain/ProductCategory.java b/src/main/java/com/shortthirdman/primekit/core/domain/ProductCategory.java new file mode 100644 index 0000000..a0d1d0f --- /dev/null +++ b/src/main/java/com/shortthirdman/primekit/core/domain/ProductCategory.java @@ -0,0 +1,6 @@ +package com.shortthirdman.primekit.core.domain; + +public enum ProductCategory { + + STANDARD, PREMIUM, LUXURY +} diff --git a/src/main/java/com/shortthirdman/primekit/core/funcinterface/TaxCalculator.java b/src/main/java/com/shortthirdman/primekit/core/funcinterface/TaxCalculator.java new file mode 100644 index 0000000..11cffc1 --- /dev/null +++ b/src/main/java/com/shortthirdman/primekit/core/funcinterface/TaxCalculator.java @@ -0,0 +1,7 @@ +package com.shortthirdman.primekit.core.funcinterface; + +@FunctionalInterface +public interface TaxCalculator { + + double calculate(double amount); +} diff --git a/src/main/java/com/shortthirdman/primekit/core/strategy/LuxuryTaxCalculation.java b/src/main/java/com/shortthirdman/primekit/core/strategy/LuxuryTaxCalculation.java new file mode 100644 index 0000000..dba914c --- /dev/null +++ b/src/main/java/com/shortthirdman/primekit/core/strategy/LuxuryTaxCalculation.java @@ -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 + } +} diff --git a/src/main/java/com/shortthirdman/primekit/core/strategy/PremiumTaxCalculation.java b/src/main/java/com/shortthirdman/primekit/core/strategy/PremiumTaxCalculation.java new file mode 100644 index 0000000..f60466d --- /dev/null +++ b/src/main/java/com/shortthirdman/primekit/core/strategy/PremiumTaxCalculation.java @@ -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 + } +} diff --git a/src/main/java/com/shortthirdman/primekit/core/strategy/StandardTaxCalculation.java b/src/main/java/com/shortthirdman/primekit/core/strategy/StandardTaxCalculation.java new file mode 100644 index 0000000..c5ef91b --- /dev/null +++ b/src/main/java/com/shortthirdman/primekit/core/strategy/StandardTaxCalculation.java @@ -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 + } +} diff --git a/src/main/java/com/shortthirdman/primekit/core/strategy/TaxCalculationStrategy.java b/src/main/java/com/shortthirdman/primekit/core/strategy/TaxCalculationStrategy.java new file mode 100644 index 0000000..1b2f075 --- /dev/null +++ b/src/main/java/com/shortthirdman/primekit/core/strategy/TaxCalculationStrategy.java @@ -0,0 +1,6 @@ +package com.shortthirdman.primekit.core.strategy; + +public sealed interface TaxCalculationStrategy permits StandardTaxCalculation, PremiumTaxCalculation, LuxuryTaxCalculation { + + double calculateTax(double amount); +} diff --git a/src/test/java/com/shortthirdman/primekit/coderbyte/PatternAnalyzerTest.java b/src/test/java/com/shortthirdman/primekit/coderbyte/PatternAnalyzerTest.java new file mode 100644 index 0000000..ada42a1 --- /dev/null +++ b/src/test/java/com/shortthirdman/primekit/coderbyte/PatternAnalyzerTest.java @@ -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); + } +} \ No newline at end of file