MaterialBundleBuilder.java

package com.university.bookstore.builder;

import java.util.ArrayList;
import java.util.List;

import com.university.bookstore.composite.MaterialBundle;
import com.university.bookstore.composite.MaterialComponent;
import com.university.bookstore.composite.MaterialLeaf;
import com.university.bookstore.model.Material;

/**
 * Builder for creating MaterialBundle instances with a fluent interface.
 * Demonstrates the Builder pattern for complex composite object construction.
 * 
 * @author Navid Mohaghegh
 * @version 3.0
 * @since 2024-09-15
 */
public class MaterialBundleBuilder implements ComponentBuilder<MaterialBundle> {
    
    private String bundleName;
    private double bundleDiscount = 0.0;
    private final List<MaterialComponent> components = new ArrayList<>();
    
    /**
     * Sets the bundle name.
     * 
     * @param bundleName the bundle name
     * @return this builder for method chaining
     */
    public MaterialBundleBuilder setBundleName(String bundleName) {
        this.bundleName = bundleName;
        return this;
    }
    
    /**
     * Sets the bundle discount rate.
     * 
     * @param discount the discount rate (0.0 to 1.0)
     * @return this builder for method chaining
     */
    public MaterialBundleBuilder setBundleDiscount(double discount) {
        this.bundleDiscount = Math.max(0.0, Math.min(1.0, discount));
        return this;
    }
    
    /**
     * Sets the bundle discount percentage.
     * 
     * @param discountPercent the discount percentage (0.0 to 100.0)
     * @return this builder for method chaining
     */
    public MaterialBundleBuilder setBundleDiscountPercent(double discountPercent) {
        return setBundleDiscount(discountPercent / 100.0);
    }
    
    /**
     * Adds a material to the bundle.
     * 
     * @param material the material to add
     * @return this builder for method chaining
     */
    public MaterialBundleBuilder addMaterial(Material material) {
        if (material != null) {
            components.add(new MaterialLeaf(material));
        }
        return this;
    }
    
    /**
     * Adds a bundle to this bundle (nested bundles).
     * 
     * @param bundle the bundle to add
     * @return this builder for method chaining
     */
    public MaterialBundleBuilder addBundle(MaterialBundle bundle) {
        if (bundle != null) {
            components.add(bundle);
        }
        return this;
    }
    
    /**
     * Adds a material component to the bundle.
     * 
     * @param component the component to add
     * @return this builder for method chaining
     */
    public MaterialBundleBuilder addComponent(MaterialComponent component) {
        if (component != null) {
            components.add(component);
        }
        return this;
    }
    
    /**
     * Adds multiple materials to the bundle.
     * 
     * @param materials the materials to add
     * @return this builder for method chaining
     */
    public MaterialBundleBuilder addMaterials(List<Material> materials) {
        if (materials != null) {
            for (Material material : materials) {
                addMaterial(material);
            }
        }
        return this;
    }
    
    /**
     * Adds multiple components to the bundle.
     * 
     * @param components the components to add
     * @return this builder for method chaining
     */
    public MaterialBundleBuilder addComponents(List<MaterialComponent> components) {
        if (components != null) {
            for (MaterialComponent component : components) {
                addComponent(component);
            }
        }
        return this;
    }
    
    /**
     * Removes a material from the bundle.
     * 
     * @param material the material to remove
     * @return this builder for method chaining
     */
    public MaterialBundleBuilder removeMaterial(Material material) {
        if (material != null) {
            components.removeIf(component -> 
                component instanceof MaterialLeaf && 
                ((MaterialLeaf) component).getMaterial().equals(material));
        }
        return this;
    }
    
    /**
     * Removes a component from the bundle.
     * 
     * @param component the component to remove
     * @return this builder for method chaining
     */
    public MaterialBundleBuilder removeComponent(MaterialComponent component) {
        components.remove(component);
        return this;
    }
    
    /**
     * Clears all components from the bundle.
     * 
     * @return this builder for method chaining
     */
    public MaterialBundleBuilder clearComponents() {
        components.clear();
        return this;
    }
    
    /**
     * Sets a 10% discount.
     * 
     * @return this builder for method chaining
     */
    public MaterialBundleBuilder setSmallDiscount() {
        return setBundleDiscount(0.10);
    }
    
    /**
     * Sets a 20% discount.
     * 
     * @return this builder for method chaining
     */
    public MaterialBundleBuilder setMediumDiscount() {
        return setBundleDiscount(0.20);
    }
    
    /**
     * Sets a 30% discount.
     * 
     * @return this builder for method chaining
     */
    public MaterialBundleBuilder setLargeDiscount() {
        return setBundleDiscount(0.30);
    }
    
    @Override
    public MaterialBundle build() {
        validate();
        
        MaterialBundle bundle = new MaterialBundle(bundleName, bundleDiscount);
        for (MaterialComponent component : components) {
            bundle.addComponent(component);
        }
        return bundle;
    }
    
    @Override
    public void validate() {
        if (bundleName == null || bundleName.trim().isEmpty()) {
            throw new IllegalStateException("Bundle name is required");
        }
        if (bundleDiscount < 0.0 || bundleDiscount > 1.0) {
            throw new IllegalStateException("Bundle discount must be between 0.0 and 1.0: " + bundleDiscount);
        }
    }
    
    @Override
    public void reset() {
        this.bundleName = null;
        this.bundleDiscount = 0.0;
        this.components.clear();
    }
    
    /**
     * Gets the current bundle name.
     * 
     * @return the current bundle name
     */
    public String getBundleName() {
        return bundleName;
    }
    
    /**
     * Gets the current bundle discount.
     * 
     * @return the current bundle discount
     */
    public double getBundleDiscount() {
        return bundleDiscount;
    }
    
    /**
     * Gets the current bundle discount percentage.
     * 
     * @return the current bundle discount percentage
     */
    public double getBundleDiscountPercent() {
        return bundleDiscount * 100.0;
    }
    
    /**
     * Gets the current components.
     * 
     * @return the current components
     */
    public List<MaterialComponent> getComponents() {
        return new ArrayList<>(components);
    }
    
    /**
     * Gets the number of components.
     * 
     * @return the component count
     */
    public int getComponentCount() {
        return components.size();
    }
    
    /**
     * Checks if the bundle has any components.
     * 
     * @return true if the bundle has components
     */
    public boolean hasComponents() {
        return !components.isEmpty();
    }
    
    /**
     * Gets the total price of all components.
     * 
     * @return the total price
     */
    public double getTotalPrice() {
        return components.stream()
            .mapToDouble(MaterialComponent::getPrice)
            .sum();
    }
    
    /**
     * Gets the total discounted price of all components.
     * 
     * @return the total discounted price
     */
    public double getTotalDiscountedPrice() {
        double totalPrice = getTotalPrice();
        return totalPrice * (1.0 - bundleDiscount);
    }
    
    /**
     * Gets the total savings from the bundle discount.
     * 
     * @return the total savings
     */
    public double getTotalSavings() {
        return getTotalPrice() - getTotalDiscountedPrice();
    }
    
    @Override
    public String toString() {
        return String.format("MaterialBundleBuilder[Name=%s, Discount=%.1f%%, Components=%d, TotalPrice=$%.2f, Savings=$%.2f]",
            bundleName, getBundleDiscountPercent(), getComponentCount(), getTotalPrice(), getTotalSavings());
    }
}