diff --git a/src/main/java/com/example/WarehouseAnalyzer.java b/src/main/java/com/example/WarehouseAnalyzer.java index 1779fc33..c8977ae9 100644 --- a/src/main/java/com/example/WarehouseAnalyzer.java +++ b/src/main/java/com/example/WarehouseAnalyzer.java @@ -1,12 +1,279 @@ package com.example; import java.math.BigDecimal; -import java.math.MathContext; import java.math.RoundingMode; import java.time.LocalDate; import java.util.*; +import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; +interface Shippable{ + BigDecimal calculateShippingCost(); + double weight(); +} + + +interface Perishable{ + LocalDate expirationDate(); + + default boolean isExpired(){ + return expirationDate().isBefore(LocalDate.now()); + } +} + +class Category{ + private final String name; + private static final Map categoryCache = new ConcurrentHashMap<>(); + + public static Category of(String name){ + validateCategory(name); + name = name.substring(0,1).toUpperCase() + name.substring(1).toLowerCase(); + + return categoryCache.computeIfAbsent(name, Category::new); + } + + private Category(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + private static void validateCategory(String name){ + if (name == null){ + throw new IllegalArgumentException("Category name can't be null"); + } else if (name.isBlank()){ + throw new IllegalArgumentException("Category name can't be blank"); + } + } + +} + +abstract class Product { + private final UUID id; + private final String name; + private final Category category; + private BigDecimal price; + + public Product(UUID id, String name, Category category, BigDecimal price){ + this.id = id; + this.name = name; + this.category = category; + this.price = price; + } + + public UUID uuid() { + return id; + } + + public String name() { + return name; + } + + public Category category() { + return category; + } + + public BigDecimal price() { + return price; + } + + public void price(BigDecimal price) { + this.price = price; + } + + abstract String productDetails(); + +} + +class FoodProduct extends Product implements Perishable, Shippable{ + private final LocalDate expirationDate; + private final BigDecimal weight; + + public FoodProduct(UUID id, String name, Category category, BigDecimal price, LocalDate expirationDate, BigDecimal weight){ + super(id, name, category, price); + + this.expirationDate = expirationDate; + this.weight = weight; + + validateFoodProduct(price, weight); + } + + private void validateFoodProduct(BigDecimal price, BigDecimal weight){ + if (price.signum() == -1){ + throw new IllegalArgumentException("Price cannot be negative."); + } + + if (weight.signum() == -1){ + throw new IllegalArgumentException("Weight cannot be negative."); + } + + } + + public BigDecimal calculateShippingCost(){ + BigDecimal multiplyWeight = new BigDecimal(50); + return this.weight.multiply(multiplyWeight); + } + + @Override + public LocalDate expirationDate() { + return expirationDate; + } + + @Override + public double weight() { + return weight.doubleValue(); + } + + @Override + String productDetails() { + return "Food: " + name() + ", Expires: " + expirationDate(); + } +} + +class ElectronicsProduct extends Product implements Shippable{ + private final int warrantyMonths; + private final BigDecimal weight; + + private void warrantyValidation(){ + if (warrantyMonths < 0) + throw new IllegalArgumentException("Warranty months cannot be negative."); + } + + public ElectronicsProduct(UUID id, String name, Category category, BigDecimal price, int warrantyMonths, BigDecimal weight){ + super(id, name, category, price); + this.warrantyMonths = warrantyMonths; + this.weight = weight; + + warrantyValidation(); + } + + public int warrantyMonths() { + return warrantyMonths; + } + + @Override + public double weight() { + return weight.doubleValue(); + } + + @Override + String productDetails() { + return "Electronics: " + name() + ", Warranty: " + warrantyMonths() + " months"; + } + + @Override + public BigDecimal calculateShippingCost() { + BigDecimal weightLimit = new BigDecimal(5); + BigDecimal baseCost = new BigDecimal(79); + BigDecimal extraCost = new BigDecimal(49); + + if(weight.compareTo(weightLimit) > 0){ + return baseCost.add(extraCost); + } + return baseCost; + } +} + +class Warehouse{ + private static final Map warehouse = new HashMap<>(); + private final List listOfProducts = new ArrayList<>(); + private final List changedProducts = new ArrayList<>(); + + public void addProduct(Product product){ + validateProduct(product); + if(doesIdExist(product.uuid())) + throw new IllegalArgumentException("Product with that id already exists, use updateProduct for updates."); + + listOfProducts.add(product); + } + + public void remove(UUID id){ + Optional removeProduct = getProductById(id); + removeProduct.ifPresent(listOfProducts::remove); + } + + public void clearProducts(){ + warehouse.clear(); + } + + public boolean isEmpty(){ + return warehouse.isEmpty(); + } + + public Map> getProductsGroupedByCategories(){ + return listOfProducts.stream() + .collect(Collectors.groupingBy(Product::category)); + } + + public Optional getProductById(UUID id){ + return listOfProducts.stream() + .filter(product -> product.uuid().equals(id)) + .findFirst(); + } + + public List getProducts(){ + return Collections.unmodifiableList(listOfProducts); + } + + public List expiredProducts(){ + return listOfProducts.stream() + .filter(product -> product instanceof Perishable) + .filter(product -> ((Perishable) product).isExpired()) + .map(product -> (Perishable) product) + .toList(); + } + + public List shippableProducts(){ + return listOfProducts.stream() + .filter(product -> product instanceof Shippable) + .map(product -> (Shippable) product) + .toList(); + } + + public List getChangedProducts(){ + return Collections.unmodifiableList(changedProducts); + } + + public void updateProductPrice(UUID id, BigDecimal newPrice){ + if(!doesIdExist(id)) + throw new NoSuchElementException("Product not found with id: " + id); + + Optional updateProduct = getProductById(id); + updateProduct.ifPresent(product -> { + product.price(newPrice); + changedProducts.add(product); + }); + } + + public boolean doesIdExist(UUID id){ + Optional product = getProductById(id); + return product.isPresent(); + } + + public void validateProduct(Product product){ + if(product == null){ + throw new IllegalArgumentException("Product cannot be null."); + } + } + + public static Warehouse getInstance(){ + return getInstance("Default warehouse"); + } + + public static Warehouse getInstance(String name){ + if (name == null || name.isBlank()){ + throw new IllegalArgumentException("You must choose a name for the warehouse."); + } + return warehouse.computeIfAbsent(name, key -> new Warehouse()); + } + + private Warehouse(){} + +} + + /** * Analyzer class that provides advanced warehouse operations. * Students must implement these methods for the advanced tests to pass.