|  | 
|  | 1 | +🕵️♂️ The Case of the Vanishing Code | 
|  | 2 | + | 
|  | 3 | +Once upon a time in the bustling digital halls of the ITHS Java Lab, a brilliant team of developers built a sleek, | 
|  | 4 | +elegant application. It was fast. It was flawless. It was... undocumented. | 
|  | 5 | + | 
|  | 6 | +But then—disaster struck. During a routine migration to a new version control system, someone (we won’t name names, but | 
|  | 7 | +let’s just say their coffee-to-commit ratio was dangerously high ☕💻) accidentally triggered the mythical git nuke | 
|  | 8 | +--force command. The source code vanished into the void. | 
|  | 9 | + | 
|  | 10 | +Gone. Poof. Like it never existed. | 
|  | 11 | + | 
|  | 12 | +All that remained were the sacred test files—meticulously crafted, oddly untouched by the chaos. These tests, like | 
|  | 13 | +ancient scrolls, held the secrets of what the application was supposed to do. They whispered of methods, behaviors, and | 
|  | 14 | +edge cases. They were our only clue. | 
|  | 15 | + | 
|  | 16 | +Now, the mission falls to you: the brave code archaeologists of Java25. Your task is to reconstruct the lost | 
|  | 17 | +application, piece by piece, using only the tests as your guide. Think of it as reverse-engineering a fossilized | 
|  | 18 | +dinosaur from its footprints... except the dinosaur is a Java program, and the footprints are JUnit assertions. | 
|  | 19 | + | 
|  | 20 | +Luckily, a few fragments of implementation survived—like half-buried ruins in the sand. Use them wisely. Interpret the | 
|  | 21 | +tests. Rebuild the logic. And maybe, just maybe, restore the glory of the lost codebase. | 
|  | 22 | + | 
| 1 | 23 | # Warehouse Kata – Instructions | 
| 2 | 24 | 
 | 
| 3 | 25 | Welcome! In this exercise you will implement a small domain around a Warehouse with products. Your goal is to: | 
| 4 |  | -- Create and complete several classes/interfaces under src/main/java that are currently missing or partially implemented. | 
|  | 26 | + | 
|  | 27 | +- Create and complete several classes/interfaces under src/main/java that are currently missing or partially | 
|  | 28 | +  implemented. | 
| 5 | 29 | - Pick correct data types for parameters and return values so the project compiles and tests can run. | 
| 6 | 30 | - Make all tests in BasicTest green first. | 
| 7 | 31 | - Commit after major steps. When all BasicTest tests are green, push your commits. | 
| 8 | 32 | - Extra credit: implement the advanced features so that EdgeCaseTest is also green. | 
| 9 | 33 | 
 | 
| 10 | 34 | ## 1) Getting started | 
|  | 35 | + | 
| 11 | 36 | 1. Open this project in your IDE (IntelliJ IDEA recommended). | 
| 12 | 37 | 2. Ensure you have JDK 25. | 
| 13 | 38 | 3. Build once to see current state: | 
| 14 |  | -   - ./mvnw compile | 
|  | 39 | +    - ./mvnw compile | 
| 15 | 40 | 
 | 
| 16 | 41 | ## 2) What you will need to create/finish | 
| 17 |  | -You will work primarily in src/main/java/com/example. Some files already exist but contain TODOs or empty methods. Implement the following: | 
|  | 42 | + | 
|  | 43 | +You will work primarily in src/main/java/com/example. Some files already exist but contain TODOs or empty methods. | 
|  | 44 | +Implement the following: | 
| 18 | 45 | 
 | 
| 19 | 46 | - Category (value object) | 
| 20 |  | -  - Use a private constructor and a public static factory Category.of(String name). | 
| 21 |  | -  - Validate input: null => "Category name can't be null"; empty/blank => "Category name can't be blank". | 
| 22 |  | -  - Normalize name with initial capital letter (e.g., "fruit" -> "Fruit"). | 
| 23 |  | -  - Cache/flyweight: return the same instance for the same normalized name. | 
|  | 47 | +    - Use a private constructor and a public static factory Category.of(String name). | 
|  | 48 | +    - Validate input: null => "Category name can't be null"; empty/blank => "Category name can't be blank". | 
|  | 49 | +    - Normalize name with initial capital letter (e.g., "fruit" -> "Fruit"). | 
|  | 50 | +    - Cache/flyweight: return the same instance for the same normalized name. | 
| 24 | 51 | 
 | 
| 25 | 52 | - Product (abstract base class) | 
| 26 |  | -  - Keep UUID id, String name, Category category, BigDecimal price. | 
| 27 |  | -  - Provide getters named uuid(), name(), category(), price() and a setter price(BigDecimal). | 
| 28 |  | -  - Provide an abstract String productDetails() for polymorphism. | 
|  | 53 | +    - Keep UUID id, String name, Category category, BigDecimal price. | 
|  | 54 | +    - Provide getters named uuid(), name(), category(), price() and a setter price(BigDecimal). | 
|  | 55 | +    - Provide an abstract String productDetails() for polymorphism. | 
| 29 | 56 | 
 | 
| 30 | 57 | - FoodProduct (extends Product) | 
| 31 |  | -  - Implements Perishable and Shippable. | 
| 32 |  | -  - Fields: LocalDate expirationDate, BigDecimal weight (kg). | 
| 33 |  | -  - Validations: negative price -> IllegalArgumentException("Price cannot be negative."); negative weight -> IllegalArgumentException("Weight cannot be negative."). | 
| 34 |  | -  - productDetails() should look like: "Food: Milk, Expires: 2025-12-24". | 
| 35 |  | -  - Shipping rule: cost = weight * 50. | 
|  | 58 | +    - Implements Perishable and Shippable. | 
|  | 59 | +    - Fields: LocalDate expirationDate, BigDecimal weight (kg). | 
|  | 60 | +    - Validations: negative price -> IllegalArgumentException("Price cannot be negative."); negative weight -> | 
|  | 61 | +      IllegalArgumentException("Weight cannot be negative."). | 
|  | 62 | +    - productDetails() should look like: "Food: Milk, Expires: 2025-12-24". | 
|  | 63 | +    - Shipping rule: cost = weight * 50. | 
| 36 | 64 | 
 | 
| 37 | 65 | - ElectronicsProduct (extends Product) | 
| 38 |  | -  - Implements Shippable. | 
| 39 |  | -  - Fields: int warrantyMonths, BigDecimal weight (kg). | 
| 40 |  | -  - Validation: negative warranty -> IllegalArgumentException("Warranty months cannot be negative."). | 
| 41 |  | -  - productDetails() should look like: "Electronics: Laptop, Warranty: 24 months". | 
| 42 |  | -  - Shipping rule: base 79, add 49 if weight > 5.0 kg. | 
|  | 66 | +    - Implements Shippable. | 
|  | 67 | +    - Fields: int warrantyMonths, BigDecimal weight (kg). | 
|  | 68 | +    - Validation: negative warranty -> IllegalArgumentException("Warranty months cannot be negative."). | 
|  | 69 | +    - productDetails() should look like: "Electronics: Laptop, Warranty: 24 months". | 
|  | 70 | +    - Shipping rule: base 79, add 49 if weight > 5.0 kg. | 
| 43 | 71 | 
 | 
| 44 | 72 | - Interfaces | 
| 45 |  | -  - Perishable: expose expirationDate() and a default isExpired() based on LocalDate.now(). | 
| 46 |  | -  - Shippable: expose calculateShippingCost() and weight() (used by shipping optimizer in extra tests). | 
|  | 73 | +    - Perishable: expose expirationDate() and a default isExpired() based on LocalDate.now(). | 
|  | 74 | +    - Shippable: expose calculateShippingCost() and weight() (used by shipping optimizer in extra tests). | 
| 47 | 75 | 
 | 
| 48 | 76 | - Warehouse (singleton per name) | 
| 49 |  | -  - getInstance(String name) returns the same instance per unique name. | 
| 50 |  | -  - addProduct(Product): throw IllegalArgumentException("Product cannot be null.") if null. | 
| 51 |  | -  - getProducts(): return an unmodifiable copy. | 
| 52 |  | -  - getProductById(UUID): return Optional. | 
| 53 |  | -  - updateProductPrice(UUID, BigDecimal): when not found, throw NoSuchElementException("Product not found with id: <uuid>"). Also track changed products in getChangedProducts(). | 
| 54 |  | -  - expiredProducts(): return List<Perishable> that are expired. | 
| 55 |  | -  - shippableProducts(): return List<Shippable> from stored products. | 
| 56 |  | -  - remove(UUID): remove the matching product if present. | 
|  | 77 | +    - getInstance(String name) returns the same instance per unique name. | 
|  | 78 | +    - addProduct(Product): throw IllegalArgumentException("Product cannot be null.") if null. | 
|  | 79 | +    - getProducts(): return an unmodifiable copy. | 
|  | 80 | +    - getProductById(UUID): return Optional. | 
|  | 81 | +    - updateProductPrice(UUID, BigDecimal): when not found, throw NoSuchElementException("Product not found with | 
|  | 82 | +      id: <uuid>"). Also track changed products in getChangedProducts(). | 
|  | 83 | +    - expiredProducts(): return List<Perishable> that are expired. | 
|  | 84 | +    - shippableProducts(): return List<Shippable> from stored products. | 
|  | 85 | +    - remove(UUID): remove the matching product if present. | 
| 57 | 86 | 
 | 
| 58 | 87 | - WarehouseAnalyzer (extra credit) | 
| 59 |  | -  - Implement the advanced methods used by EdgeCaseTest: price-range search (inclusive), expiring-within-days, case-insensitive name search, above-price search, weighted average per category (round to 2 decimals), price outliers (population stddev), shipping group optimization (first‑fit decreasing by weight), expiration-based discounts, inventory validation summary, and inventory statistics. | 
|  | 88 | +    - Implement the advanced methods used by EdgeCaseTest: price-range search (inclusive), expiring-within-days, | 
|  | 89 | +      case-insensitive name search, above-price search, weighted average per category (round to 2 decimals), price | 
|  | 90 | +      outliers (population stddev), shipping group optimization (first‑fit decreasing by weight), expiration-based | 
|  | 91 | +      discounts, inventory validation summary, and inventory statistics. | 
| 60 | 92 | 
 | 
| 61 | 93 | ## 3) Workflow to follow | 
|  | 94 | + | 
| 62 | 95 | 1. Implement the missing classes/interfaces and methods so the project compiles. | 
| 63 | 96 | 2. Run tests: | 
| 64 |  | -   - Basic first: ./mvnw -Dtest=BasicTest test | 
| 65 |  | -   - When green, commit with a clear message. | 
|  | 97 | +    - Basic first: ./mvnw -Dtest=BasicTest test | 
|  | 98 | +    - When green, commit with a clear message. | 
| 66 | 99 | 3. Extra credit: make EdgeCaseTest green: | 
| 67 |  | -   -  ./mvnw -Dtest=EdgeCaseTest test | 
| 68 |  | -4. Commit after each major milestone (e.g., "Implement Product & FoodProduct", "Warehouse behaviors", "Analyzer advanced features"). | 
|  | 100 | +    - ./mvnw -Dtest=EdgeCaseTest test | 
|  | 101 | +4. Commit after each major milestone (e.g., "Implement Product & FoodProduct", "Warehouse behaviors", "Analyzer advanced | 
|  | 102 | +   features"). | 
| 69 | 103 | 5. Push when BasicTest is fully green (and EdgeCaseTest too if you do the extra credit). | 
| 70 | 104 | 
 | 
| 71 | 105 | ## 4) Tips | 
| 72 |  | -- Prefer BigDecimal for prices and weights (exact values in tests). Where an interface requires Double (e.g., weight()), convert BigDecimal to double on return. | 
|  | 106 | + | 
|  | 107 | +- Prefer BigDecimal for prices and weights (exact values in tests). Where an interface requires Double (e.g., weight()), | 
|  | 108 | +  convert BigDecimal to double on return. | 
| 73 | 109 | - Always round monetary results to 2 decimals using HALF_UP when tests assert exact values. | 
| 74 | 110 | - Keep public APIs exactly as tests expect (method names, exception messages). | 
| 75 | 111 | - Ensure Warehouse.clearProducts() is called in tests; do not share state between tests. | 
|  | 
0 commit comments