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

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

public final class Category {
private static final Map<String, Category> CACHE = new ConcurrentHashMap<>();
private final String name;


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

//public static factory category.Of(String name)
public static Category of(String name) {
//validate input - cannot be null
if (name == null) {
throw new IllegalArgumentException("Category name can't be null");
}
if (name.isBlank()) {
throw new IllegalArgumentException("Category name can't be blank");
}
//normalize name with inital capital letter fruit -> Fruit
String normalize = name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase();

return CACHE.computeIfAbsent(normalize, Category::new);
}

public String name(){return name;}

public String getName(){return name;}

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

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

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


}
/*
- Category (value object)
- Use a private constructor and a public static factory Category.of(String name).
- Validate input: null => "Category name can't be null"; empty/blank => "Category name can't be blank".
- Normalize name with initial capital letter (e.g., "fruit" -> "Fruit").
- Cache/flyweight: return the same instance for the same normalized name.
*/
50 changes: 50 additions & 0 deletions src/main/java/com/example/ElectronicsProduct.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.example;

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

public class ElectronicsProduct extends Product implements Shippable{

private final int warrantyMonths;
private final BigDecimal weight;

// - Fields: int warrantyMonths, BigDecimal weight (kg).

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 == null || weight.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Weight cannot be negative."); //OR zero
}

this.warrantyMonths = warrantyMonths;
this.weight = weight;
}

@Override
public String productDetails() {
//- productDetails() should look like: "Electronics: Laptop, Warranty: 24 months".
return "Electronics: " + name() + ", Warranty: " + warrantyMonths + " months";
}

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

@Override
public BigDecimal calculateShippingCost() {
//- Shipping rule: base 79, add 49 if weight > 5.0 kg.
BigDecimal shippingCost = BigDecimal.valueOf(79);
if (weight.compareTo(BigDecimal.valueOf(5.0)) > 0) {
shippingCost = shippingCost.add(BigDecimal.valueOf(49));
}
return shippingCost;
}
}

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

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

public class FoodProduct extends Product implements Perishable, Shippable {

/*
- FoodProduct (extends Product)
- Implements Perishable and Shippable.
- Fields: LocalDate expirationDate, BigDecimal weight (kg).
- Validations: negative price -> IllegalArgumentException("Price cannot be negative."); negative weight ->
IllegalArgumentException("Weight cannot be negative.").
- productDetails() should look like: "Food: Milk, Expires: 2025-12-24".
- Shipping rule: cost = weight * 50.
*/
private final LocalDate expirationDate;
//private final Double weight;
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.");
}
//weight villkor
if (weight == null || weight.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("Weight cannot be negative."); //OR zero.
}

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


//implementerar productDetails
@Override
public String productDetails() {
return "Food: " + name() + ", Expires: " + expirationDate();
};

// ----- Perishable -----
@Override
public LocalDate expirationDate() {return expirationDate;}

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

@Override
public BigDecimal calculateShippingCost(){
//Shipping rule: cost = weight * 50.
//return weight().multiply(BigDecimal.valueOf(50));
return BigDecimal.valueOf(weight.doubleValue() * 50);
}


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

public interface Perishable{
//varor som kan utgå
LocalDate expirationDate();
}
46 changes: 46 additions & 0 deletions src/main/java/com/example/Product.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.example;

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

abstract class Product {
/*
Beskriver en enskild produkt
*/

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;
}

//returnerar UUID
public UUID uuid() {return id;}
//returnerar name
public String name() {return name;}
//returnerar category
public Category category() {return category;}
//returnerar price
public BigDecimal price() {return price;}
public void setPrice(BigDecimal price) {this.price = price;}
public abstract String productDetails();

}

// public UUID getId() {return id;}
// public String getName() {return name;}
// public Category getCategory() {return category;}
// public BigDecimal getPrice() {return price;}

/*
- Product (abstract base class)
- Keep UUID id, String name, Category category, BigDecimal price.
- Provide getters named uuid(), name(), category(), price() and a setter price(BigDecimal).
- Provide an abstract String productDetails() for polymorphism.
*/
10 changes: 10 additions & 0 deletions src/main/java/com/example/Shippable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.example;

import java.math.BigDecimal;

public interface Shippable {
//varor som skickas ut
//BigDecimal weight();
Double weight();
BigDecimal calculateShippingCost();
}
116 changes: 116 additions & 0 deletions src/main/java/com/example/Warehouse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package com.example;

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

public class Warehouse {
/*
Håller listan över alla produkter
*/
private final List<Product> products = new ArrayList<>();
//HashSet tillåter inte dubletter - unika objekt
private final Set<Product> changedProducts = Collections.synchronizedSet(new HashSet<>());

private static final Map<String, Warehouse> INSTANCES = new HashMap<>();

private Warehouse() {}

//returns the same instance per unique name
//Om det inte finns ett värde för nyckeln name,
// skapa ett nytt med new Warehouse() och lägg till det. Annars returnera det befintliga
public static Warehouse getInstance(String name){
return INSTANCES.computeIfAbsent(name, n->new Warehouse());
}
public static Warehouse getInstance(){
return getInstance("Warehouse");
}

//lägg till produkter i warehouse
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);
}

//GetProducts
public List<Product> getProducts() {
return Collections.unmodifiableList(products);
}

//GetproductBYID - stream find first
public Optional<Product> getProductById(UUID id) {
return products.stream()
.filter(product -> product.uuid().equals(id))
.findFirst();
}

// - updateProductPrice(UUID, BigDecimal): when not found,
// throw NoSuchElementException("Product not found with id: <uuid>").
public void updateProductPrice(UUID uuid, BigDecimal newPrice){
Product product = products.stream()
.filter(p->p.uuid().equals(uuid))
.findFirst() //optional product
.orElseThrow(()-> new NoSuchElementException("Product not found with id: " + uuid));

product.setPrice(newPrice);
changedProducts.add(product); //HashSet förhindrar dupletter, garanterar "uniqueness"
}

//use Collections.synchronizedList or validate before adding to ensure uniqueness
public List<Perishable> expiredProducts(){
//- expiredProducts(): return List<Perishable> that are expired.
LocalDate today = LocalDate.now();

return products.stream()
//filtrerar perishable produkter
.filter(p -> p instanceof Perishable)
//casha till perishable
.map(p->(Perishable) p)
//filtrerar om expirationDate har passerat
.filter(p->p.expirationDate().isBefore(today))
//samla i en ny lista
.toList();
}

//shippabeProducts, return list from stored products
public List<Shippable> shippableProducts(){
return products.stream()
.filter(product -> product instanceof Shippable)
.map(product -> (Shippable) product)
.toList();
}

// - remove(UUID): remove the matching product if present
public void remove(UUID uuid){
products.removeIf(product -> product.uuid().equals(uuid));
}

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

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

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

}
}

/*
- Warehouse (singleton per name)
- remove(UUID): remove the matching product if present.
*/
Loading
Loading