Skip to content
52 changes: 52 additions & 0 deletions src/main/java/com/example/Category.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.example;

import java.util.HashMap;

public final class Category {

private static final HashMap<String, Category> CACHE = new HashMap<>();
private final String name;

private Category(String name) {
this.name = name;
}

public static Category of(String name) {
if(name == null) {
throw new IllegalArgumentException("Category name can't be null");
}
String trimmedName = name.trim();
if(trimmedName.isEmpty()) {
throw new IllegalArgumentException("Category name can't be blank");
}

String normalizeName = trimmedName.substring(0,1).toUpperCase() +
trimmedName.substring(1).toLowerCase();
if(!CACHE.containsKey(normalizeName)) {
CACHE.put(normalizeName, new Category(normalizeName));
}
return CACHE.get(normalizeName);
}

public String getName(){
return name;
}

@Override
public String toString() {
return name;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Category category = (Category) o;
return name.equals(category.name);
}

@Override
public int hashCode() {
return name.hashCode();
}
}
47 changes: 47 additions & 0 deletions src/main/java/com/example/ElectronicsProduct.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.example;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.UUID;

public class ElectronicsProduct extends Product implements Shippable{

private final int warrantyMonths;
private final BigDecimal weight;

public ElectronicsProduct(UUID id,String name, Category category, BigDecimal price, int warrantyMonths, BigDecimal weight) {
super (id, name, category, price);

if (warrantyMonths < 0) {
throw new IllegalArgumentException("Warranty months cannot be negative.");
}
if (weight.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Weight cannot be negative");
}
this.warrantyMonths = warrantyMonths;
this.weight = weight;
}


@Override
public double weight() {
return weight.doubleValue();
}

@Override
public BigDecimal calculateShippingCost() {

BigDecimal cost = new BigDecimal("79");
BigDecimal weightThreshold = new BigDecimal("5.0");

if (weight.compareTo(weightThreshold) > 0) {
cost = cost.add(new BigDecimal("49"));
}
return cost.setScale(2, RoundingMode.HALF_UP);
}

@Override
public String productDetails() {
return String.format("Electronics: %s, Warranty: %d months", name(), warrantyMonths);
}
}
59 changes: 59 additions & 0 deletions src/main/java/com/example/FoodProduct.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.example;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.UUID;

public 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);

if(price == null || price.compareTo(BigDecimal.ZERO) < 0){
throw new IllegalArgumentException("Price cannot be negative.");}
if(weight == null || weight.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Weight cannot be negative.");
}
if(expirationDate == null) {
throw new IllegalArgumentException("Expiration Date can not be null");
}

this.expirationDate = expirationDate;
this.weight = weight;

}


@Override
public LocalDate expirationDate() {
return expirationDate;
}

@Override
public boolean isExpired() {
return Perishable.super.isExpired();
}


@Override
public double weight() {
return weight.doubleValue();
}

@Override
public BigDecimal calculateShippingCost() {
return weight.multiply(BigDecimal.valueOf(50)).setScale(2, RoundingMode.HALF_UP);
}

@Override
public String productDetails() {
// Format: "Food: Milk, Expires: 2025-12-24"
return String.format("Food: %s, Expires: %s", name(), expirationDate());
}


}
14 changes: 14 additions & 0 deletions src/main/java/com/example/Perishable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.example;

import java.time.LocalDate;

public interface Perishable {

LocalDate expirationDate() ;

default boolean isExpired(){
return expirationDate().isBefore(LocalDate.now());
}


}
41 changes: 41 additions & 0 deletions src/main/java/com/example/Product.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.example;

import java.math.BigDecimal;
import java.util.UUID;

public abstract class Product {

private final UUID id;
private final String name;
private final Category category;
private BigDecimal price;

protected 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 newPrice) {

if (newPrice == null || newPrice.compareTo(BigDecimal.ZERO) < 0) {
throw new IllegalArgumentException("Price cannot be negative.");
}
this.price = newPrice;
}


public abstract String productDetails();

@Override
public String toString() {
return String.format("%s - %s", name, price);
}

}
9 changes: 9 additions & 0 deletions src/main/java/com/example/Shippable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.example;

import java.math.BigDecimal;

public interface Shippable {

BigDecimal calculateShippingCost();
double weight();
}
98 changes: 98 additions & 0 deletions src/main/java/com/example/Warehouse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package com.example;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;

public class Warehouse {

private static final Map<String, Warehouse> INSTANCES = new HashMap<>();
private final Map<UUID, Product> products = new HashMap<>();
private final Set<UUID> changedProducts = Collections.synchronizedSet(new HashSet<>());
private final String name;

private Warehouse(String name) {
this.name = name;

}

public static Warehouse getInstance(String name) {
return INSTANCES.computeIfAbsent(name, Warehouse::new);
}

public static Warehouse getInstance(){
return getInstance("default");
}

public void addProduct(Product product) {
if(product == null) {
throw new IllegalArgumentException("Product cannot be null.");
}
if(products.putIfAbsent(product.uuid(), product) != null) {
throw new IllegalArgumentException("Product with that id already exists, use updateProduct for updates.");
}
products.put(product.uuid(), product);
}

public List<Product> getProducts() {

return List.copyOf(products.values());
}

public void updateProductPrice(UUID id, BigDecimal newPrice) {
Product product = products.get(id);
if(product == null) {
throw new NoSuchElementException("Product not found with id: " + id);
}
product.price(newPrice);
changedProducts.add(id);
}

public Optional<Product> getProductById(UUID id) {

return Optional.ofNullable(products.get(id));
}

public Set<UUID> getChangedProducts() {
return Collections.unmodifiableSet(changedProducts);
}

public List<Perishable> expiredProducts() {
LocalDate today = LocalDate.now();
return products.values().stream().filter(p -> p instanceof Perishable)
.map(p -> (Perishable) p)
.filter(per -> per.expirationDate().isBefore(today))
.collect(Collectors.toList());
}

public List<Shippable> shippableProducts() {
return products.values().stream()
.filter(p -> p instanceof Shippable).map(p -> (Shippable) p)
.collect(Collectors.toList());
}

public void remove(UUID id) {
products.remove(id);
}

public void clearProducts() {
products.clear();
changedProducts.clear();
}

public boolean isEmpty() {
return products.isEmpty();
}

public Map<Category, List<Product>> getProductsGroupedByCategories() {
return products.values().stream().collect(Collectors.groupingBy(Product::category));
}


@Override
public String toString() {
return "Warehouse{" + "name='" + name + '\'' + ", products=" + products.size() + '}';
}

}
Loading
Loading