ModernConcurrentMaterialStore.java
package com.university.bookstore.impl;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.StampedLock;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.university.bookstore.api.MaterialStore;
import com.university.bookstore.model.Magazine;
import com.university.bookstore.model.Material;
import com.university.bookstore.model.Media;
import com.university.bookstore.model.PrintedBook;
/**
* Modern thread-safe implementation of MaterialStore using best practices.
* Features:
* - StampedLock for optimized read performance
* - ExecutorService for async operations
* - CompletableFuture for non-blocking operations
* - Proper resource management with AutoCloseable
* - Virtual thread support (when available)
*
* @author Navid Mohaghegh
* @version 4.0
* @since 2024-09-15
*/
public class ModernConcurrentMaterialStore implements MaterialStore, AutoCloseable {
private static final Logger LOGGER = LoggerFactory.getLogger(ModernConcurrentMaterialStore.class);
private final Map<String, Material> materials;
private final StampedLock stampedLock;
private final ExecutorService executorService;
private final ScheduledExecutorService scheduledExecutor;
private volatile boolean closed = false;
/**
* Creates a new modern thread-safe material store.
*/
public ModernConcurrentMaterialStore() {
this.materials = new ConcurrentHashMap<>();
this.stampedLock = new StampedLock();
// Use ForkJoinPool for better work-stealing behavior
this.executorService = new ForkJoinPool(
Runtime.getRuntime().availableProcessors(),
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null,
true // Enable async mode for better throughput
);
this.scheduledExecutor = Executors.newScheduledThreadPool(2, r -> {
Thread t = new Thread(r, "MaterialStore-Scheduler");
t.setDaemon(true);
return t;
});
// Schedule periodic cleanup tasks
scheduleMaintenanceTasks();
}
/**
* Creates a material store with initial materials.
*
* @param initialMaterials materials to add initially
*/
public ModernConcurrentMaterialStore(Collection<Material> initialMaterials) {
this();
if (initialMaterials != null) {
// Parallel addition for better performance
initialMaterials.parallelStream().forEach(this::addMaterial);
}
}
private void scheduleMaintenanceTasks() {
// Example: periodic cache cleanup or metrics collection
scheduledExecutor.scheduleAtFixedRate(
this::performMaintenance,
1, 1, TimeUnit.HOURS
);
}
private void performMaintenance() {
if (!closed) {
// Maintenance tasks like clearing old data, collecting metrics, etc.
// This is a placeholder for actual maintenance logic
}
}
@Override
public boolean addMaterial(Material material) {
Objects.requireNonNull(material, "Material cannot be null");
ensureNotClosed();
long stamp = stampedLock.writeLock();
try {
return materials.putIfAbsent(material.getId(), material) == null;
} finally {
stampedLock.unlockWrite(stamp);
}
}
/**
* Adds material asynchronously.
*
* @param material the material to add
* @return CompletableFuture with the result
*/
public CompletableFuture<Boolean> addMaterialAsync(Material material) {
return CompletableFuture.supplyAsync(
() -> addMaterial(material),
executorService
);
}
/**
* Adds multiple materials in batch asynchronously.
*
* @param materials collection of materials to add
* @return CompletableFuture with results map
*/
public CompletableFuture<Map<String, Boolean>> addMaterialsBatchAsync(Collection<Material> materials) {
List<CompletableFuture<Map.Entry<String, Boolean>>> futures = materials.stream()
.map(material -> CompletableFuture.supplyAsync(
() -> Map.entry(material.getId(), addMaterial(material)),
executorService
))
.toList();
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenApply(v -> futures.stream()
.map(CompletableFuture::join)
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue
)));
}
@Override
public Optional<Material> removeMaterial(String id) {
if (id == null) {
return Optional.empty();
}
ensureNotClosed();
long stamp = stampedLock.writeLock();
try {
return Optional.ofNullable(materials.remove(id));
} finally {
stampedLock.unlockWrite(stamp);
}
}
@Override
public Optional<Material> findById(String id) {
if (id == null) {
return Optional.empty();
}
ensureNotClosed();
// Try optimistic read first for better performance
long stamp = stampedLock.tryOptimisticRead();
Material material = materials.get(id);
if (!stampedLock.validate(stamp)) {
// Optimistic read failed, acquire read lock
stamp = stampedLock.readLock();
try {
material = materials.get(id);
} finally {
stampedLock.unlockRead(stamp);
}
}
return Optional.ofNullable(material);
}
/**
* Finds material by ID asynchronously.
*
* @param id the material ID
* @return CompletableFuture with the result
*/
public CompletableFuture<Optional<Material>> findByIdAsync(String id) {
return CompletableFuture.supplyAsync(
() -> findById(id),
executorService
);
}
/**
* Finds multiple materials by IDs asynchronously.
*
* @param ids list of material IDs
* @return CompletableFuture with results map
*/
public CompletableFuture<Map<String, Material>> findByIdsAsync(List<String> ids) {
List<CompletableFuture<Map.Entry<String, Optional<Material>>>> futures = ids.stream()
.map(id -> CompletableFuture.supplyAsync(
() -> Map.entry(id, findById(id)),
executorService
))
.toList();
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenApply(v -> futures.stream()
.map(CompletableFuture::join)
.filter(entry -> entry.getValue().isPresent())
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> entry.getValue().get()
)));
}
@Override
public List<Material> searchByTitle(String title) {
if (title == null || title.trim().isEmpty()) {
return new ArrayList<>();
}
ensureNotClosed();
String searchTerm = title.toLowerCase().trim();
long stamp = stampedLock.readLock();
try {
return materials.values().parallelStream()
.filter(m -> m.getTitle().toLowerCase().contains(searchTerm))
.collect(Collectors.toList());
} finally {
stampedLock.unlockRead(stamp);
}
}
/**
* Searches by title asynchronously.
*
* @param title the title to search for
* @return CompletableFuture with the results
*/
public CompletableFuture<List<Material>> searchByTitleAsync(String title) {
return CompletableFuture.supplyAsync(
() -> searchByTitle(title),
executorService
);
}
@Override
public List<Material> searchByCreator(String creator) {
if (creator == null || creator.trim().isEmpty()) {
return new ArrayList<>();
}
ensureNotClosed();
String searchTerm = creator.toLowerCase().trim();
long stamp = stampedLock.readLock();
try {
return materials.values().parallelStream()
.filter(m -> m.getCreator().toLowerCase().contains(searchTerm))
.collect(Collectors.toList());
} finally {
stampedLock.unlockRead(stamp);
}
}
@Override
public List<Material> getMaterialsByType(Material.MaterialType type) {
if (type == null) {
return new ArrayList<>();
}
ensureNotClosed();
long stamp = stampedLock.readLock();
try {
return materials.values().parallelStream()
.filter(m -> m.getType() == type)
.collect(Collectors.toList());
} finally {
stampedLock.unlockRead(stamp);
}
}
@Override
public List<Media> getMediaMaterials() {
ensureNotClosed();
long stamp = stampedLock.readLock();
try {
return materials.values().parallelStream()
.filter(Media.class::isInstance)
.map(Media.class::cast)
.collect(Collectors.toList());
} finally {
stampedLock.unlockRead(stamp);
}
}
@Override
public List<Material> filterMaterials(Predicate<Material> predicate) {
Objects.requireNonNull(predicate, "Predicate cannot be null");
ensureNotClosed();
long stamp = stampedLock.readLock();
try {
return materials.values().parallelStream()
.filter(predicate)
.collect(Collectors.toList());
} finally {
stampedLock.unlockRead(stamp);
}
}
@Override
public List<Material> getMaterialsByPriceRange(double minPrice, double maxPrice) {
if (minPrice < 0 || maxPrice < 0 || minPrice > maxPrice) {
return new ArrayList<>();
}
ensureNotClosed();
long stamp = stampedLock.readLock();
try {
return materials.values().parallelStream()
.filter(m -> m.getPrice() >= minPrice && m.getPrice() <= maxPrice)
.collect(Collectors.toList());
} finally {
stampedLock.unlockRead(stamp);
}
}
@Override
public List<Material> getMaterialsByYear(int year) {
ensureNotClosed();
long stamp = stampedLock.readLock();
try {
return materials.values().parallelStream()
.filter(m -> m.getYear() == year)
.collect(Collectors.toList());
} finally {
stampedLock.unlockRead(stamp);
}
}
@Override
public List<Material> getAllMaterialsSorted() {
ensureNotClosed();
long stamp = stampedLock.readLock();
try {
return materials.values().stream()
.sorted()
.collect(Collectors.toList());
} finally {
stampedLock.unlockRead(stamp);
}
}
@Override
public List<Material> getAllMaterials() {
ensureNotClosed();
// Use optimistic read for better performance
long stamp = stampedLock.tryOptimisticRead();
List<Material> result = new ArrayList<>(materials.values());
if (!stampedLock.validate(stamp)) {
stamp = stampedLock.readLock();
try {
result = new ArrayList<>(materials.values());
} finally {
stampedLock.unlockRead(stamp);
}
}
return result;
}
@Override
public double getTotalInventoryValue() {
ensureNotClosed();
long stamp = stampedLock.readLock();
try {
return materials.values().parallelStream()
.mapToDouble(Material::getPrice)
.sum();
} finally {
stampedLock.unlockRead(stamp);
}
}
/**
* Gets total inventory value asynchronously.
*
* @return CompletableFuture with the total value
*/
public CompletableFuture<Double> getTotalInventoryValueAsync() {
return CompletableFuture.supplyAsync(
this::getTotalInventoryValue,
executorService
);
}
@Override
public double getTotalDiscountedValue() {
ensureNotClosed();
long stamp = stampedLock.readLock();
try {
return materials.values().parallelStream()
.mapToDouble(Material::getDiscountedPrice)
.sum();
} finally {
stampedLock.unlockRead(stamp);
}
}
@Override
public InventoryStats getInventoryStats() {
ensureNotClosed();
long stamp = stampedLock.readLock();
try {
if (materials.isEmpty()) {
return new InventoryStats(0, 0, 0, 0, 0, 0);
}
List<Double> prices = materials.values().stream()
.map(Material::getPrice)
.sorted()
.collect(Collectors.toList());
double averagePrice = prices.stream()
.mapToDouble(Double::doubleValue)
.average()
.orElse(0.0);
double medianPrice = prices.size() % 2 == 0
? (prices.get(prices.size() / 2 - 1) + prices.get(prices.size() / 2)) / 2
: prices.get(prices.size() / 2);
int uniqueTypes = (int) materials.values().stream()
.map(Material::getType)
.distinct()
.count();
int mediaCount = (int) materials.values().stream()
.filter(Media.class::isInstance)
.count();
int printCount = (int) materials.values().stream()
.filter(m -> m instanceof PrintedBook || m instanceof Magazine)
.count();
return new InventoryStats(
materials.size(),
averagePrice,
medianPrice,
uniqueTypes,
mediaCount,
printCount
);
} finally {
stampedLock.unlockRead(stamp);
}
}
/**
* Gets inventory statistics asynchronously.
*
* @return CompletableFuture with the statistics
*/
public CompletableFuture<InventoryStats> getInventoryStatsAsync() {
return CompletableFuture.supplyAsync(
this::getInventoryStats,
executorService
);
}
@Override
public void clearInventory() {
ensureNotClosed();
long stamp = stampedLock.writeLock();
try {
materials.clear();
} finally {
stampedLock.unlockWrite(stamp);
}
}
@Override
public int size() {
ensureNotClosed();
// Use optimistic read for size check
long stamp = stampedLock.tryOptimisticRead();
int size = materials.size();
if (!stampedLock.validate(stamp)) {
stamp = stampedLock.readLock();
try {
size = materials.size();
} finally {
stampedLock.unlockRead(stamp);
}
}
return size;
}
@Override
public boolean isEmpty() {
return size() == 0;
}
@Override
public List<Material> findRecentMaterials(int years) {
if (years < 0) {
throw new IllegalArgumentException("Years cannot be negative: " + years);
}
ensureNotClosed();
int currentYear = java.time.Year.now().getValue();
int cutoffYear = currentYear - years;
long stamp = stampedLock.readLock();
try {
return materials.values().parallelStream()
.filter(material -> material.getYear() >= cutoffYear)
.collect(Collectors.toList());
} finally {
stampedLock.unlockRead(stamp);
}
}
@Override
public List<Material> findByCreators(String... creators) {
if (creators == null || creators.length == 0) {
return new ArrayList<>();
}
ensureNotClosed();
Set<String> creatorSet = Arrays.stream(creators)
.filter(Objects::nonNull)
.map(String::trim)
.filter(s -> !s.isEmpty())
.collect(Collectors.toSet());
if (creatorSet.isEmpty()) {
return new ArrayList<>();
}
long stamp = stampedLock.readLock();
try {
return materials.values().parallelStream()
.filter(material -> creatorSet.contains(material.getCreator()))
.collect(Collectors.toList());
} finally {
stampedLock.unlockRead(stamp);
}
}
@Override
public List<Material> findWithPredicate(Predicate<Material> condition) {
Objects.requireNonNull(condition, "Predicate cannot be null");
ensureNotClosed();
long stamp = stampedLock.readLock();
try {
return materials.values().parallelStream()
.filter(condition)
.collect(Collectors.toList());
} finally {
stampedLock.unlockRead(stamp);
}
}
@Override
public List<Material> getSorted(Comparator<Material> comparator) {
Objects.requireNonNull(comparator, "Comparator cannot be null");
ensureNotClosed();
long stamp = stampedLock.readLock();
try {
return materials.values().stream()
.sorted(comparator)
.collect(Collectors.toList());
} finally {
stampedLock.unlockRead(stamp);
}
}
/**
* Performs parallel search across multiple criteria.
*
* @param title optional title search term
* @param creator optional creator search term
* @param type optional material type
* @return CompletableFuture with combined results
*/
public CompletableFuture<List<Material>> parallelSearchAsync(
String title, String creator, Material.MaterialType type) {
List<CompletableFuture<List<Material>>> searches = new ArrayList<>();
if (title != null && !title.trim().isEmpty()) {
searches.add(searchByTitleAsync(title));
}
if (creator != null && !creator.trim().isEmpty()) {
searches.add(CompletableFuture.supplyAsync(
() -> searchByCreator(creator), executorService));
}
if (type != null) {
searches.add(CompletableFuture.supplyAsync(
() -> getMaterialsByType(type), executorService));
}
if (searches.isEmpty()) {
return CompletableFuture.completedFuture(new ArrayList<>());
}
return CompletableFuture.allOf(searches.toArray(new CompletableFuture[0]))
.thenApply(v -> searches.stream()
.map(CompletableFuture::join)
.flatMap(List::stream)
.distinct()
.collect(Collectors.toList()));
}
/**
* Groups materials by type for reporting.
*
* @return map of type to materials
*/
public Map<Material.MaterialType, List<Material>> groupByType() {
ensureNotClosed();
long stamp = stampedLock.readLock();
try {
return materials.values().stream()
.collect(Collectors.groupingBy(Material::getType));
} finally {
stampedLock.unlockRead(stamp);
}
}
/**
* Gets materials with active discounts.
*
* @return list of discounted materials
*/
public List<Material> getDiscountedMaterials() {
ensureNotClosed();
long stamp = stampedLock.readLock();
try {
return materials.values().parallelStream()
.filter(m -> m.getDiscountRate() > 0)
.collect(Collectors.toList());
} finally {
stampedLock.unlockRead(stamp);
}
}
/**
* Calculates total savings from discounts.
*
* @return total discount amount
*/
public double getTotalDiscountAmount() {
ensureNotClosed();
long stamp = stampedLock.readLock();
try {
return materials.values().parallelStream()
.mapToDouble(m -> m.getPrice() * m.getDiscountRate())
.sum();
} finally {
stampedLock.unlockRead(stamp);
}
}
private void ensureNotClosed() {
if (closed) {
throw new IllegalStateException("MaterialStore has been closed");
}
}
@Override
public void close() {
if (!closed) {
closed = true;
// Shutdown executors gracefully
executorService.shutdown();
scheduledExecutor.shutdown();
try {
// Wait for existing tasks to complete
if (!executorService.awaitTermination(10, TimeUnit.SECONDS)) {
executorService.shutdownNow();
if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
LOGGER.error("ExecutorService did not terminate within timeout");
}
}
if (!scheduledExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
scheduledExecutor.shutdownNow();
if (!scheduledExecutor.awaitTermination(5, TimeUnit.SECONDS)) {
LOGGER.error("ScheduledExecutor did not terminate within timeout");
}
}
} catch (InterruptedException e) {
executorService.shutdownNow();
scheduledExecutor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
@Override
public String toString() {
ensureNotClosed();
long stamp = stampedLock.readLock();
try {
return String.format("ModernConcurrentMaterialStore[Size=%d, Types=%d, Value=$%.2f]",
size(),
groupByType().size(),
getTotalInventoryValue());
} finally {
stampedLock.unlockRead(stamp);
}
}
}