Skip to content
Open
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
53 changes: 53 additions & 0 deletions src/main/java/com/example/Category.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.example;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Objects;

public final class Category {

private static final Map<String, Category> CACHE = new ConcurrentHashMap<>();
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 trimmed = name.trim();
if (trimmed.isEmpty())
throw new IllegalArgumentException("Category name can't be blank");

String normalized = name.trim();
normalized = normalized.substring(0, 1).toUpperCase() + normalized.substring(1).toLowerCase();
return CACHE.computeIfAbsent(normalized, Category::new);
}

public String getName() {
return name;
}

public String name() {
return name;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Category)) return false;
Category c = (Category) o;
return name.equals(c.name);
}

@Override
public int hashCode() {
return Objects.hash(name);
}

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

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

/**
* ElectronicsProduct: shippable product with warranty.
*/
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 months cannot be negative.");

this.warrantyMonths = warrantyMonths;
this.weight = Objects.requireNonNull(weight, "Weight cannot be null");
}

@Override
public String productDetails() {
return "Electronics: %s, Warranty: %d months".formatted(name(), warrantyMonths);
}

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

@Override
public BigDecimal calculateShippingCost() {
BigDecimal base = BigDecimal.valueOf(79);
if (weight.doubleValue() > 5.0) base = base.add(BigDecimal.valueOf(49));
return base;
}

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

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

/**
* FoodProduct: perishable and shippable.
*/
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.compareTo(BigDecimal.ZERO) < 0) throw new IllegalArgumentException("Price cannot be negative.");
if (weight.compareTo(BigDecimal.ZERO) < 0) throw new IllegalArgumentException("Weight cannot be negative.");

this.expirationDate = Objects.requireNonNull(expirationDate, "Expiration date cannot be null");
this.weight = Objects.requireNonNull(weight, "Weight cannot be null");
}

@Override
public String productDetails() {
return "Food: %s, Expires: %s".formatted(name(), expirationDate);
}

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

@Override
public boolean isExpired() {
return expirationDate.isBefore(LocalDate.now());
}

@Override
public BigDecimal calculateShippingCost() {
return weight.multiply(BigDecimal.valueOf(50)); // 50 kr/kg
}

@Override
public Double weight() {
return weight.doubleValue();
}
}
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;

/**
* Interface for products with expiration dates.
*/
public interface Perishable {
LocalDate expirationDate();

default boolean isExpired() {
return expirationDate().isBefore(LocalDate.now());
}
}
58 changes: 58 additions & 0 deletions src/main/java/com/example/Product.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.example;

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

/**
* Base class for all products.
* Holds shared attributes and ensures proper validation.
*/
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) {
if (id == null) throw new IllegalArgumentException("ID cannot be null");
if (name == null || name.isBlank()) throw new IllegalArgumentException("Product name cannot be blank");
if (category == null) throw new IllegalArgumentException("Category cannot be null");
if (price == null) throw new IllegalArgumentException("Price cannot be null");

this.id = id;
this.name = name;
this.category = category;
this.price = price;
}

// Getters
public UUID uuid() {
return id;
}

public String name() {
return name;
}

public Category category() {
return category;
}

public BigDecimal price() {
return price;
}

// Setter
public void price(BigDecimal newPrice) {
if (newPrice == null) throw new IllegalArgumentException("Price cannot be null");
this.price = newPrice;
}

public abstract String productDetails();

@Override
public String toString() {
return String.format("%s (%s) - %s kr", name, category.getName(), price);
}
}
11 changes: 11 additions & 0 deletions src/main/java/com/example/Shippable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.example;

import java.math.BigDecimal;

/**
* Interface for products that can be shipped.
*/
public interface Shippable {
Double weight();
BigDecimal calculateShippingCost();
}
82 changes: 82 additions & 0 deletions src/main/java/com/example/Warehouse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.example;

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

/**
* Warehouse managing products.
*/
public class Warehouse {

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

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

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

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

public String getName() {
return name;
}

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

products.add(product);
}

public List<Product> getProducts() {
return List.copyOf(products);
}

public Optional<Product> getProductById(UUID id) {
return products.stream().filter(p -> p.uuid().equals(id)).findFirst();
}

public void updateProductPrice(UUID id, BigDecimal newPrice) {
Product p = getProductById(id).orElseThrow(() -> new NoSuchElementException("Product not found with id: " + id));
p.price(newPrice);
changedProducts.add(id);
}

public void remove(UUID id) {
products.removeIf(p -> p.uuid().equals(id));
}

public List<Perishable> expiredProducts() {
return products.stream().filter(p -> p instanceof Perishable per && per.isExpired())
.map(p -> (Perishable) p).collect(Collectors.toList());
}

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

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

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

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

Loading
Loading