MaterialEventPublisher.java

package com.university.bookstore.observer;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.university.bookstore.model.Material;

/**
 * Concrete implementation of MaterialSubject that publishes material events to observers.
 * Provides thread-safe event publishing and observer management.
 * 
 * <p>This class demonstrates the Observer pattern by managing a list of observers
 * and broadcasting events to all registered observers when material events occur.</p>
 * 
 * @author Navid Mohaghegh
 * @version 3.0
 * @since 2024-09-15
 */
public class MaterialEventPublisher implements MaterialSubject {
    
    private static final Logger LOGGER = LoggerFactory.getLogger(MaterialEventPublisher.class);
    private final List<MaterialObserver> observers;
    
    /**
     * Creates a new material event publisher.
     */
    public MaterialEventPublisher() {
        // Use CopyOnWriteArrayList for thread safety
        this.observers = new CopyOnWriteArrayList<>();
    }
    
    @Override
    public void addObserver(MaterialObserver observer) {
        if (observer == null) {
            throw new IllegalArgumentException("Observer cannot be null");
        }
        
        if (!observers.contains(observer)) {
            observers.add(observer);
            observer.onAdded(this);
        }
    }
    
    @Override
    public boolean removeObserver(MaterialObserver observer) {
        if (observer == null) {
            return false;
        }
        
        boolean removed = observers.remove(observer);
        if (removed) {
            observer.onRemoved(this);
        }
        return removed;
    }
    
    @Override
    public void notifyObservers(MaterialEvent event) {
        if (event == null) {
            throw new IllegalArgumentException("Event cannot be null");
        }
        
        for (MaterialObserver observer : observers) {
            try {
                observer.onEvent(event);
            } catch (Exception e) {
                // Log error but don't let one observer's failure affect others
                LOGGER.error("Observer {} failed to handle event: {}", 
                    observer.getObserverName(), event.getEventType(), e);
            }
        }
    }
    
    @Override
    public int getObserverCount() {
        return observers.size();
    }
    
    @Override
    public boolean hasNoObservers() {
        return observers.isEmpty();
    }
    
    @Override
    public void clearObservers() {
        // Notify observers they are being removed
        for (MaterialObserver observer : observers) {
            try {
                observer.onRemoved(this);
            } catch (Exception e) {
                LOGGER.warn("Error notifying observer of removal", e);
            }
        }
        observers.clear();
    }
    
    /**
     * Publishes a material added event.
     * 
     * @param material the material that was added
     */
    public void publishMaterialAdded(Material material) {
        notifyObservers(new MaterialAddedEvent(material));
    }
    
    /**
     * Publishes a price changed event.
     * 
     * @param material the material whose price changed
     * @param oldPrice the previous price
     * @param newPrice the new price
     */
    public void publishPriceChanged(Material material, double oldPrice, double newPrice) {
        notifyObservers(new PriceChangedEvent(material, oldPrice, newPrice));
    }
    
    /**
     * Publishes a custom material event.
     * 
     * @param event the event to publish
     */
    public void publishEvent(MaterialEvent event) {
        notifyObservers(event);
    }
    
    /**
     * Gets a list of all registered observers.
     * 
     * @return list of observers (defensive copy)
     */
    public List<MaterialObserver> getObservers() {
        return new ArrayList<>(observers);
    }
    
    /**
     * Checks if a specific observer is registered.
     * 
     * @param observer the observer to check
     * @return true if the observer is registered
     */
    public boolean hasObserver(MaterialObserver observer) {
        return observers.contains(observer);
    }
    
    /**
     * Gets observers of a specific type.
     * 
     * @param observerType the type of observers to find
     * @return list of observers of the specified type
     */
    public <T extends MaterialObserver> List<T> getObserversOfType(Class<T> observerType) {
        List<T> result = new ArrayList<>();
        for (MaterialObserver observer : observers) {
            if (observerType.isInstance(observer)) {
                result.add(observerType.cast(observer));
            }
        }
        return result;
    }
    
    @Override
    public String toString() {
        return String.format("MaterialEventPublisher[Observers=%d]", getObserverCount());
    }
}