Untitled

 avatar
unknown
java
2 months ago
11 kB
7
Indexable
package org.example;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;

public class FoodOrderingApplication {
    public enum OrderStatus{
        PLACED, PREPARING, FULFILLED
    }

    public static class User{
        String userId;
        String name;

        public User(String userId, String name) {
            this.userId = userId;
            this.name = name;
        }

        @Override
        public String toString() {
            return "User{" +
                    "userId='" + userId + '\'' +
                    ", name='" + name + '\'' +
                    '}';
        }
    }


    public static class MenuItem{
        String name;
        long price;
        int prepTimeSeconds;

        public MenuItem(String name, long price, int prepTimeSeconds) {
            this.name = name;
            this.price = price;
            this.prepTimeSeconds = prepTimeSeconds;
        }
    }

    public static class Restaurant{
        private String name;
        private Map<String, MenuItem> menu = new ConcurrentHashMap<>();
        private AtomicInteger currentCapacity;
        private int maxCapacity;

        public Restaurant(String name, List<MenuItem> items, int totalCapacity) {
            this.name = name;
            this.currentCapacity = new AtomicInteger(totalCapacity);
            this.maxCapacity = totalCapacity;
            items.forEach(item -> menu.put(item.name, item));
        }

        public boolean hasItem(String name){
            return menu.containsKey(name);
        }

        public MenuItem getItem(String name){
            return menu.get(name);
        }

        public boolean reserve(int quantity){
            while(true){
                int current = currentCapacity.get();
                if(current<quantity) return false;
                if(currentCapacity.compareAndSet(current, current-quantity)) return true;
            }
        }

        public void release(int quantity){
            currentCapacity.getAndUpdate(capacity -> Math.min(maxCapacity, capacity+quantity));
        }

        public String getName(){
            return name;
        }

        public int getAvailableCapacity() {
            return currentCapacity.get();
        }
    }

    public static class Order{
        String orderId;
        User user;
        Map<String, Integer> itemCounts;
        Map<String, String> itemToRestaurantMap;
        OrderStatus orderStatus;
        AtomicInteger itemsRemaining;

        public Order(User user, Map<String, Integer> itemCounts, Map<String, String> itemToRestaurantMap) {
            this.user = user;
            this.itemCounts = itemCounts;
            this.itemToRestaurantMap = itemToRestaurantMap;
            this.orderStatus = OrderStatus.PLACED;
            this.itemsRemaining = new AtomicInteger(itemCounts.values().stream().mapToInt(Integer::intValue).sum());
        }

        @Override
        public String toString() {
            return "Order{" +
                    "orderId='" + orderId + '\'' +
                    ", user=" + user +
                    ", itemCounts=" + itemCounts +
                    ", itemToRestaurantMap=" + itemToRestaurantMap +
                    ", orderStatus=" + orderStatus +
                    ", itemsRemaining=" + itemsRemaining +
                    '}';
        }
    }

    public interface RestaurantRepository{
        void save(Restaurant restaurant);
        List<Restaurant> findAll();
    }

    public static class InMemoryRestaurantRepository implements RestaurantRepository{
        Map<String, Restaurant> map = new ConcurrentHashMap<>();
        @Override
        public void save(Restaurant restaurant) {
            map.put(restaurant.getName(), restaurant);
        }

        @Override
        public List<Restaurant> findAll() {
            return new ArrayList<>(map.values());
        }
    }

    public interface OrderRepository{
        void save(Order order);
        Optional<Order> findById(String id);
    }

    public static class InMemoryOrderRepository implements OrderRepository{
        Map<String, Order> map = new ConcurrentHashMap<>();
        AtomicInteger orderIdCounter = new AtomicInteger();
        @Override
        public void save(Order order) {
            String id = "ORD-" + orderIdCounter.incrementAndGet();
            order.orderId = id;
            map.put(id, order);
        }

        @Override
        public Optional<Order> findById(String id) {
            return Optional.ofNullable(map.get(id));
        }
    }

    public interface SelectionStrategy{
        Map<String, Restaurant> select(Map<String, Integer> itemCounts, List<Restaurant> restaurants);
    }

    public static class LowestPriceStrategy implements SelectionStrategy{
        @Override
        public Map<String, Restaurant> select(Map<String, Integer> itemCounts, List<Restaurant> restaurants) {
            Map<String, Restaurant> map = new HashMap<>();
            for(String itemName: itemCounts.keySet()){
                int required = itemCounts.get(itemName);
                restaurants.stream()
                        .filter(r-> r.hasItem(itemName) && r.getAvailableCapacity()>=required)
                        .min(Comparator.comparingLong(r-> r.getItem(itemName).price))
                        .ifPresent(r-> map.put(itemName, r));
            }
            return  map;
        }
    }

    public static class FoodOrderingService{
        RestaurantRepository restaurantRepository;
        OrderRepository orderRepository;
        Set<String> idempotencyKeys = Collections.newSetFromMap(new ConcurrentHashMap<>());
        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(4);

        public FoodOrderingService(RestaurantRepository restaurantRepository, OrderRepository orderRepository) {
            this.restaurantRepository = restaurantRepository;
            this.orderRepository = orderRepository;
        }

        public void addRestaurant(String name, List<MenuItem> menu, int capacity){
            restaurantRepository.save(new Restaurant(name, menu, capacity));
        }

        public void placeOrder(String requestId, User user, Map<String,Integer> itemCount, SelectionStrategy strategy){
            if(!idempotencyKeys.add(requestId)){
                System.out.println("Rejecting, Duplicate request."+requestId);
                return;
            }

            Map<String, Restaurant> plan = strategy.select(itemCount, restaurantRepository.findAll());
            if(plan.size()<itemCount.size()){
                System.out.println("Items insufficient");
                idempotencyKeys.remove(requestId);
                return;
            }

            List<Map.Entry<Restaurant, Integer>> comitted = new ArrayList<>();
            boolean success = true;
            for(Map.Entry<String, Restaurant> entry: plan.entrySet()){
                Restaurant r = entry.getValue();
                int qty = itemCount.get(entry.getKey());
                if(r.reserve(qty)){
                    comitted.add(new AbstractMap.SimpleEntry<>(r, qty));
                }else{
                    success = false;
                    break;
                }
            }

            if(!success){
                comitted.forEach(e-> e.getKey().release(e.getValue()));
                System.out.println("Items quantity insufficient");
                idempotencyKeys.remove(requestId);
                return;
            }

            Map<String, String> assignmentNames = plan.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e-> e.getValue().getName()));
            Order order = new Order(user, itemCount, assignmentNames);
            order.orderStatus = OrderStatus.PREPARING;
            orderRepository.save(order);
            System.out.println("Order accepted"+ order +" for user "+user);

            plan.forEach((itemName, r) -> {
                int qty = itemCount.get(itemName);
                int prepTime = r.getItem(itemName).prepTimeSeconds;

                for(int i=0;i<qty;i++){
                    scheduler.schedule(()-> {
                       r.release(1);
                       if(order.itemsRemaining.decrementAndGet()==0){
                           order.orderStatus = OrderStatus.FULFILLED;
                           System.out.println(order + " is fully prepared");
                       }
                    }, prepTime, TimeUnit.SECONDS);
                }
            });
        }

        public void printStats(){
            System.out.println("\n Stats: ");
            restaurantRepository.findAll().forEach(r-> {
                System.out.println(r.getName()+" -> Available Capacity: "+ r.getAvailableCapacity());
            });
            System.out.println("------------");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        RestaurantRepository restaurantRepository = new InMemoryRestaurantRepository();
        OrderRepository orderRepository = new InMemoryOrderRepository();
        FoodOrderingService foodOrderingService = new FoodOrderingService(restaurantRepository, orderRepository);
        SelectionStrategy lowestPrice = new LowestPriceStrategy();

        foodOrderingService.addRestaurant("A2B", Arrays.asList(
                new MenuItem("Idly", 40, 1),
                new MenuItem("Vada", 30, 2),
                new MenuItem("Paper Plain Dosa", 50, 1)), 4);

        foodOrderingService.addRestaurant("Rasaganga", Arrays.asList(
                new MenuItem("Idly", 45, 1),
                new MenuItem("Set Dosa", 60, 1),
                new MenuItem("Poori", 25, 1)), 6);

        foodOrderingService.addRestaurant("Eat Fit", Arrays.asList(
                new MenuItem("Idly", 30, 3),
                new MenuItem("Vada", 40, 1)), 2);

        User user = new User("U1", "Kush");

        Map<String, Integer> req1 = new HashMap<>();
        req1.put("Idly", 1); req1.put("Poori", 1);
        foodOrderingService.placeOrder("REQ1", user, req1, lowestPrice);

        Map<String, Integer> req2 = new HashMap<>();
        req2.put("Idly", 1); req2.put("Vada", 1);
        foodOrderingService.placeOrder("REQ2", user, req2, lowestPrice);

        foodOrderingService.printStats();

        Map<String, Integer> req3 = new HashMap<>();
        req3.put("Idly", 1);
        foodOrderingService.placeOrder("REQ3", user, req3, lowestPrice);
        Thread.sleep(4000);


        System.out.println();
    }
}
Editor is loading...
Leave a Comment